range_agg

Started by Paul Jungwirthover 6 years ago147 messages
#1Paul Jungwirth
pj@illuminatedcomputing.com

Hello,

I wrote an extension to add a range_agg function with similar behavior
to existing *_agg functions, and I'm wondering if folks would like to
have it in core? Here is the repo: https://github.com/pjungwir/range_agg

I'm also working on a patch for temporal foreign keys, and having
range_agg would make the FK check easier and faster, which is why I'd
like to get it added. But also it just seems useful, like array_agg,
json_agg, etc.

One question is how to aggregate ranges that would leave gaps and/or
overlaps. So in my extension there is a one-param version that forbids
gaps & overlaps, but I let you permit them by passing extra parameters,
so the signature is:

range_agg(r anyrange, permit_gaps boolean, permit_overlaps boolean)

Perhaps another useful choice would be to return NULL if a gap/overlap
is found, so that each param would have three choices instead of just
two: accept the inputs, raise an error, return a NULL.

What do people think? I plan to work on a patch regardless, so that I
can use it for temporal FKs, but I'd appreciate some feedback on the
"user interface".

Thanks,

--
Paul ~{:-)
pj@illuminatedcomputing.com

#2David Fetter
david@fetter.org
In reply to: Paul Jungwirth (#1)
Re: range_agg

On Fri, May 03, 2019 at 03:56:41PM -0700, Paul Jungwirth wrote:

Hello,

I wrote an extension to add a range_agg function with similar behavior to
existing *_agg functions, and I'm wondering if folks would like to have it
in core? Here is the repo: https://github.com/pjungwir/range_agg

I'm also working on a patch for temporal foreign keys, and having range_agg
would make the FK check easier and faster, which is why I'd like to get it
added. But also it just seems useful, like array_agg, json_agg, etc.

One question is how to aggregate ranges that would leave gaps and/or
overlaps.

This suggests two different ways to extend ranges over aggregation:
one which is a union of (in general) disjoint intervals, two others
are a union of intervals, each of which has a weight. Please pardon
the ASCII art.

The aggregation of:

[1, 4)
[2, 5)
[8, 10)

could turn into:

{[1,5), [8, 10)} (union without weight)

{{[1,2),1}, {[2,4),2}, {[4,5),1}, {[8,10),1}} (strictly positive weights which don't (in general) cover the space)

{{[1,2),1}, {[2,4),2}, {[4,5),1}, {[5,8),0}, {[8,10),1}} (non-negative weights which guarantee the space is covered)

There is no principled reason to choose one over the others.

What do people think? I plan to work on a patch regardless, so that I can
use it for temporal FKs, but I'd appreciate some feedback on the "user
interface".

I think the cases above, or at least the first two of them, should be
available. They could be called range_agg, weighted_range_agg, and
covering_range_agg.

Best,
David.
--
David Fetter <david(at)fetter(dot)org> http://fetter.org/
Phone: +1 415 235 3778

Remember to vote!
Consider donating to Postgres: http://www.postgresql.org/about/donate

#3Corey Huinker
corey.huinker@gmail.com
In reply to: Paul Jungwirth (#1)
Re: range_agg

One question is how to aggregate ranges that would leave gaps and/or
overlaps. So in my extension there is a one-param version that forbids
gaps & overlaps, but I let you permit them by passing extra parameters,
so the signature is:

Perhaps a third way would be to allow and preserve the gaps.

A while back I wrote an extension called disjoint_date_range for storing
sets of dates where it was assumed that most dates would be contiguous. The
basic idea was that The core datatype was an array of ranges of dates, and
with every modification you'd unnest them all to their discrete elements
and use a window function to identify "runs" of dates and recompose them
into a canonical set. It was an efficient way of representing "Every day
last year except for June 2nd and August 4th, when we closed business for
special events."

For arrays of ranges the principle is the same but it'd get a bit more
tricky, you'd have to order by low bound, use window functions to detect
adjacency/overlap to identify your runs, and the generate the canonical
minimum set of ranges in your array.

#4Paul Jungwirth
pj@illuminatedcomputing.com
In reply to: Corey Huinker (#3)
Re: range_agg

On 5/4/19 3:11 PM, Corey Huinker wrote:

One question is how to aggregate ranges that would leave gaps and/or
overlaps. So in my extension there is a one-param version that forbids
gaps & overlaps, but I let you permit them by passing extra parameters,
so the signature is:

Perhaps a third way would be to allow and preserve the gaps.

Thanks for the feedback! I think this is what I'm doing already
(returning an array of ranges), but let me know if I'm misunderstanding.
My extension has these signatures:

range_agg(anyrange) returning anyrange
range_agg(anyrange, bool) returning anyarray
range_agg(anyrange, bool, bool) returning anyarray.

The first variant raises an error if there are gaps or overlaps and
always returns a single range, but the other two return an array of ranges.

I was planning to use the same signatures for my patch to pg, unless
someone thinks they should be different. But I'm starting to wonder if
they shouldn't *all* return arrays. I have two concrete use-cases for
these functions and they both require the array-returning versions. Is
it helpful to have a version that always returns a single range? Or
should I make them all consistent?

Thanks,

--
Paul ~{:-)
pj@illuminatedcomputing.com

#5Paul Jungwirth
pj@illuminatedcomputing.com
In reply to: David Fetter (#2)
Re: range_agg

On 5/3/19 6:41 PM, David Fetter wrote:

This suggests two different ways to extend ranges over aggregation:
one which is a union of (in general) disjoint intervals, two others
are a union of intervals, each of which has a weight.
. . .
I think the cases above, or at least the first two of them, should be
available. They could be called range_agg, weighted_range_agg, and
covering_range_agg.

Thanks David! I think these two new functions make sense. Before I
implement them too I wonder if anyone else has uses for them?

Thanks,

--
Paul ~{:-)
pj@illuminatedcomputing.com

#6David Fetter
david@fetter.org
In reply to: Paul Jungwirth (#5)
Re: range_agg

On Mon, May 06, 2019 at 11:19:27AM -0700, Paul Jungwirth wrote:

On 5/3/19 6:41 PM, David Fetter wrote:

This suggests two different ways to extend ranges over aggregation:
one which is a union of (in general) disjoint intervals, two others
are a union of intervals, each of which has a weight.
. . .
I think the cases above, or at least the first two of them, should be
available. They could be called range_agg, weighted_range_agg, and
covering_range_agg.

Thanks David! I think these two new functions make sense. Before I implement
them too I wonder if anyone else has uses for them?

I suspect that if you build it, the will come, "they" being anyone who
has to schedule coverage, check usage of a resource over time, etc. Is
this something you want help with at some level? Coding, testing,
promoting...

Best,
David.
--
David Fetter <david(at)fetter(dot)org> http://fetter.org/
Phone: +1 415 235 3778

Remember to vote!
Consider donating to Postgres: http://www.postgresql.org/about/donate

#7Paul Jungwirth
pj@illuminatedcomputing.com
In reply to: David Fetter (#6)
Re: range_agg

I suspect that if you build it, the will come, "they" being anyone who
has to schedule coverage, check usage of a resource over time, etc. Is
this something you want help with at some level? Coding, testing,
promoting...

You might be right. :-) Most of this is done already, since it was
largely copy/paste from my extension plus figuring out how to register
built-in functions with the .dat files. I need to write some docs and do
some cleanup and I'll have a CF entry. And I'll probably go ahead and
add your two suggestions too.... Things I'd love help with:

- Getting more opinions about the functions' interface, either from you
or others, especially:
- In the extension I have a boolean param to let you accept gaps or
raise an error, and another for overlaps. But what about
accepting/raising/returning null? How should the parameters expose that?
Maybe leave them as bools but accept true/false/null for
permit/raise/nullify respectively? That seems like a questionable UI,
but I'm not sure what would be better. Maybe someone with better taste
can weigh in. :-) I tried to find existing built-in functions that gave
a enumeration of options like that but couldn't find an existing example.
- Also: what do you think of the question I asked in my reply to
Corey? Is it better to have *all* range_agg functions return an array of
ranges, or it is nicer to have a variant that always returns a single range?
- Getting it reviewed.
- Advice about sequencing it with respect to my temporal foreign keys
patch, where I'm planning to call range_agg to check an FK. E.g. should
my FK patch be a diff on top of the range_agg code? I assume they should
have separate CF entries though?

Oh and here's something specific:

- I gave oids to my new functions starting with 8000, because I thought
I saw some discussion about that recently, and the final committer will
correct the oids to the current n+1? But I can't find that discussion
anymore, so if that's the wrong approach let me know.

Thanks!

--
Paul ~{:-)
pj@illuminatedcomputing.com

#8Paul A Jungwirth
pj@illuminatedcomputing.com
In reply to: Paul Jungwirth (#7)
1 attachment(s)
Re: range_agg

On Mon, May 6, 2019 at 4:21 PM Paul Jungwirth
<pj@illuminatedcomputing.com> wrote:

I need to write some docs and do
some cleanup and I'll have a CF entry.

Here is an initial patch. I'd love to have some feedback! :-)

One challenge was handling polymorphism, since I want to have this:

anyrange[] range_agg(anyrange, bool, bool)

But there is no anyrange[]. I asked about this back when I wrote my
extension too:

/messages/by-id/CA+renyVOjb4xQZGjdCnA54-1nzVSY+47-h4nkM-EP5J=1z=b9w@mail.gmail.com

Like then, I handled it by overloading the function with separate
signatures for each built-in range type:

int4range[] range_agg(int4range, bool, bool);
int8range[] range_agg(int8range, bool, bool);
numrange[] range_agg(numrange, bool, bool);
tsrange[] range_agg(tsrange, bool, bool);
tstzrange[] range_agg(tstzrange, bool, bool);
daterange[] range_agg(daterange, bool, bool);

(And users can still define a range_agg for their own custom range
types using similar instructions to those in my extension's README.)

The problem was the opr_sanity regression test, which rejects
functions that share the same C-function implementation (roughly). I
took a few stabs at changing my code to pass that test, e.g. defining
separate wrapper functions for everything like this:

Datum
int4range_agg_transfn(PG_FUNCTION_ARGS) {
return range_agg_transfn(fcinfo);
}

But that felt like it was getting ridiculous (8 range types *
transfn+finalfn * 1-bool and 2-bool variants), so in the end I just
added my functions to the "permitted" output in opr_sanity. Let me
know if I should avoid that though.

Also I would still appreciate some bikeshedding over the functions' UI
since I don't feel great about it.

If the overall approach seems okay, I'm still open to adding David's
suggestions for weighted_range_agg and covering_range_agg.

Thanks!
Paul

Attachments:

range_agg_v1.patchapplication/octet-stream; name=range_agg_v1.patchDownload
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index da0f305981..bb93b64ee4 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -14421,6 +14421,11 @@ NULL baz</literallayout>(3 rows)</entry>
    <function>lower_inf</function>, and <function>upper_inf</function>
    functions all return false for an empty range.
   </para>
+
+  <para>
+   See also <xref linkend="functions-aggregate"/> about the aggregate
+   function <function>range_agg</function> for use with ranges.
+  </para>
   </sect1>
 
  <sect1 id="functions-aggregate">
@@ -14739,6 +14744,76 @@ NULL baz</literallayout>(3 rows)</entry>
      <row>
       <entry>
        <indexterm>
+        <primary>range_agg</primary>
+       </indexterm>
+       <function>
+         range_agg(<replaceable class="parameter">expression</replaceable>)
+       </function>
+      </entry>
+      <entry>
+       any range type
+      </entry>
+      <entry>
+       same as argument type
+      </entry>
+      <entry>No</entry>
+      <entry>input ranges merged into a single range, or an error if there are any gaps or overlaps</entry>
+     </row>
+
+     <row>
+      <entry>
+       <indexterm>
+        <primary>range_agg</primary>
+       </indexterm>
+       <function>
+         range_agg(<replaceable class="parameter">expression</replaceable>,
+          <replaceable>permit_gaps</replaceable>)
+       </function>
+      </entry>
+      <entry>
+       (any range type, <type>boolean</type>)
+      </entry>
+      <entry>
+       array of the same range type as argument type
+      </entry>
+      <entry>No</entry>
+      <entry>
+       input ranges merged into a list of non-touching ranges,
+       with gaps permitted, rejected, or yielding a NULL result
+       as the second argument is true, false, or NULL respectively
+      </entry>
+     </row>
+
+     <row>
+      <entry>
+       <indexterm>
+        <primary>range_agg</primary>
+       </indexterm>
+       <function>
+         range_agg(<replaceable class="parameter">expression</replaceable>,
+          <replaceable>permit_gaps</replaceable>,
+          <replaceable>permit_overlaps</replaceable>)
+       </function>
+      </entry>
+      <entry>
+       (any range type, <type>boolean</type>, <type>boolean</type>)
+      </entry>
+      <entry>
+       array of the same range type as argument type
+      </entry>
+      <entry>No</entry>
+      <entry>
+       input ranges merged into a list of non-touching ranges,
+       with gaps permitted, rejected, or yielding a NULL result
+       as the second argument is true, false, or NULL respectively,
+       and overlaps permitted, rejected, or yielding a NULL result
+       as the third argument is true, false, or NULL respectively
+      </entry>
+     </row>
+
+     <row>
+      <entry>
+       <indexterm>
         <primary>string_agg</primary>
        </indexterm>
        <function>
diff --git a/src/backend/utils/adt/rangetypes.c b/src/backend/utils/adt/rangetypes.c
index 72c450c70e..020284a386 100644
--- a/src/backend/utils/adt/rangetypes.c
+++ b/src/backend/utils/adt/rangetypes.c
@@ -34,6 +34,7 @@
 #include "lib/stringinfo.h"
 #include "libpq/pqformat.h"
 #include "miscadmin.h"
+#include "utils/array.h"
 #include "utils/builtins.h"
 #include "utils/date.h"
 #include "utils/hashutils.h"
@@ -54,6 +55,18 @@ typedef struct RangeIOData
 	FmgrInfo	proc;			/* lookup result for typiofunc */
 } RangeIOData;
 
+/* aggregate state for range_agg */
+typedef struct RangeAggState
+{
+	ArrayBuildState *inputs;
+	uint8 flags;
+} RangeAggState;
+#define RANGE_AGG_GAPS_RAISE		0x01
+#define RANGE_AGG_GAPS_SET_NULL		0x02
+#define RANGE_AGG_OVERLAPS_RAISE	0x04
+#define RANGE_AGG_OVERLAPS_SET_NULL	0x08
+#define RANGE_AGG_GAPS_FLAGS		(RANGE_AGG_GAPS_RAISE | RANGE_AGG_GAPS_SET_NULL)
+#define RANGE_AGG_OVERLAPS_FLAGS	(RANGE_AGG_OVERLAPS_RAISE | RANGE_AGG_OVERLAPS_SET_NULL)
 
 static RangeIOData *get_range_io_data(FunctionCallInfo fcinfo, Oid rngtypid,
 				  IOFuncSelector func);
@@ -69,6 +82,7 @@ static Size datum_compute_size(Size sz, Datum datum, bool typbyval,
 				   char typalign, int16 typlen, char typstorage);
 static Pointer datum_write(Pointer ptr, Datum datum, bool typbyval,
 			char typalign, int16 typlen, char typstorage);
+static int element_compare(const void *key1, const void *key2, void *arg);
 
 
 /*
@@ -1135,6 +1149,189 @@ range_intersect(PG_FUNCTION_ARGS)
 	PG_RETURN_RANGE_P(make_range(typcache, result_lower, result_upper, false));
 }
 
+/* Aggregate functions */
+
+/*
+ * range_agg_transfn: combine adjacent/overlapping ranges.
+ *
+ * All we do here is gather the input ranges into an array
+ * so that the finalfn can sort and combine them.
+ * We can't simply use array_append as the transfn
+ * because we have to capture the second & third parameters
+ * and put them in the aggregate's running state,
+ * so that our finalfn can use them.
+ */
+Datum
+range_agg_transfn(PG_FUNCTION_ARGS)
+{
+	Oid rangeTypeId;
+	MemoryContext aggContext;
+	RangeAggState *state;
+
+	if (!AggCheckCallContext(fcinfo, &aggContext))
+	{
+		elog(ERROR, "range_agg_transfn called in non-aggregate context");
+	}
+
+	rangeTypeId = get_fn_expr_argtype(fcinfo->flinfo, 1);
+	if (!type_is_range(rangeTypeId))
+	{
+		ereport(ERROR, (errmsg("range_agg must be called with a range")));
+	}
+	if (PG_ARGISNULL(0))
+	{
+		state = MemoryContextAlloc(aggContext, sizeof(RangeAggState));
+		state->inputs = initArrayResult(rangeTypeId, aggContext, false);
+
+		state->flags = 0x0;
+		switch (PG_NARGS())
+		{
+			case 2:
+				state->flags |= RANGE_AGG_GAPS_RAISE;
+				state->flags |= RANGE_AGG_OVERLAPS_RAISE;
+				break;
+			case 3:
+				if (PG_ARGISNULL(2))         state->flags |= RANGE_AGG_GAPS_SET_NULL;
+				else if (!PG_GETARG_BOOL(2)) state->flags |= RANGE_AGG_GAPS_RAISE;
+				state->flags |= RANGE_AGG_OVERLAPS_RAISE;
+				break;
+			case 4:
+				if (PG_ARGISNULL(2))         state->flags |= RANGE_AGG_GAPS_SET_NULL;
+				else if (!PG_GETARG_BOOL(2)) state->flags |= RANGE_AGG_GAPS_RAISE;
+				if (PG_ARGISNULL(3))         state->flags |= RANGE_AGG_OVERLAPS_SET_NULL;
+				else if (!PG_GETARG_BOOL(3)) state->flags |= RANGE_AGG_OVERLAPS_RAISE;
+				break;
+			default:
+				Assert(false);
+		}
+	}
+	else
+	{
+		state = (RangeAggState *)PG_GETARG_POINTER(0);
+	}
+
+	/* Might as well skip NULLs here so the finalfn doesn't have to: */
+	if (!PG_ARGISNULL(1))
+	{
+		accumArrayResult(state->inputs, PG_GETARG_DATUM(1), false,
+				rangeTypeId, aggContext);
+	}
+
+	PG_RETURN_POINTER(state);
+}
+
+/* range_agg_finalfn: combine adjacent/overlapping ranges */
+Datum
+range_agg_finalfn(PG_FUNCTION_ARGS)
+{
+	MemoryContext aggContext;
+	Oid rangeTypeId;
+	TypeCacheEntry *typcache;
+	RangeAggState *state;
+	ArrayBuildState *inputArray;
+	int inputLength;
+	Datum *inputVals;
+	bool *inputNulls;
+	int i;
+	RangeType *currentRange;
+	RangeType *lastRange;
+	char *r1Str, *r2Str;
+	ArrayBuildState *resultContent;
+	Datum result;
+
+	if (!AggCheckCallContext(fcinfo, &aggContext))
+	{
+		elog(ERROR, "range_agg_finalfn called in non-aggregate context");
+	}
+
+	state = PG_ARGISNULL(0) ? NULL : (RangeAggState *)PG_GETARG_POINTER(0);
+	if (state == NULL) PG_RETURN_NULL();
+	inputArray  = state->inputs;
+	inputVals   = inputArray->dvalues;
+	inputNulls  = inputArray->dnulls;
+	inputLength = inputArray->nelems;
+	rangeTypeId = inputArray->element_type;
+
+	typcache = range_get_typcache(fcinfo, rangeTypeId);
+	if (inputLength == 0) PG_RETURN_NULL();
+	qsort_arg(inputVals, inputLength, sizeof(Datum), element_compare, typcache);
+
+	resultContent = initArrayResult(rangeTypeId, aggContext, false);
+	lastRange = DatumGetRangeTypeP(inputVals[0]);
+	for (i = 1; i < inputLength; i++)
+	{
+		Assert(!inputNulls[i]);
+		currentRange = DatumGetRangeTypeP(inputVals[i]);
+		/* range_adjacent_interval gives true
+		 * if *either* A meets B or B meets A,
+		 * which is not quite what we want,
+		 * but we rely on the sorting above
+		 * to rule out B meets A ever happening.
+		 */
+		if (range_adjacent_internal(typcache, lastRange, currentRange))
+		{
+			/* The two ranges touch without overlap: */
+			lastRange = range_union_internal(typcache, lastRange, currentRange, false);
+		}
+		else if (range_before_internal(typcache, lastRange, currentRange))
+		{
+			/* There is a gap: */
+			if (state->flags & RANGE_AGG_GAPS_FLAGS)
+			{
+				if (state->flags & RANGE_AGG_GAPS_RAISE)
+				{
+					r1Str = "lastRange";
+					r2Str = "currentRange";
+					// TODO: Why is this segfaulting?:
+					// r1Str = DatumGetCString(DirectFunctionCall1(range_out, RangeTypePGetDatum(lastRange)));
+					// r2Str = DatumGetCString(DirectFunctionCall1(range_out, RangeTypePGetDatum(currentRange)));
+					ereport(ERROR, (errmsg("range_agg: gap detected between %s and %s", r1Str, r2Str)));
+				}
+				else
+				{
+					Assert(state->flags & RANGE_AGG_GAPS_SET_NULL);
+					PG_RETURN_NULL();
+				}
+			}
+			accumArrayResult(resultContent, RangeTypePGetDatum(lastRange), false, rangeTypeId, aggContext);
+			lastRange = currentRange;
+
+		}
+		else
+		{
+			/* They must overlap: */
+			if (state->flags & RANGE_AGG_OVERLAPS_FLAGS)
+			{
+				if (state->flags & RANGE_AGG_OVERLAPS_RAISE)
+				{
+					r1Str = "lastRange"; r2Str = "currentRange";
+					// TODO: Why is this segfaulting?:
+					// r1Str = DatumGetCString(DirectFunctionCall1(range_out, RangeTypePGetDatum(lastRange)));
+					// r2Str = DatumGetCString(DirectFunctionCall1(range_out, RangeTypePGetDatum(currentRange)));
+					ereport(ERROR, (errmsg("range_agg: overlap detected between %s and %s", r1Str, r2Str)));
+				}
+				else
+				{
+					Assert(state->flags & RANGE_AGG_OVERLAPS_SET_NULL);
+					PG_RETURN_NULL();
+				}
+			}
+			lastRange = range_union_internal(typcache, lastRange, currentRange, false);
+		}
+	}
+	accumArrayResult(resultContent, RangeTypePGetDatum(lastRange), false, rangeTypeId, aggContext);
+
+	if (type_is_array(get_fn_expr_rettype(fcinfo->flinfo)))
+	{
+		result = makeArrayResult(resultContent, CurrentMemoryContext);
+		PG_RETURN_DATUM(result);
+	}
+	else
+	{
+		PG_RETURN_DATUM(RangeTypePGetDatum(lastRange));
+	}
+}
+
 /* Btree support */
 
 /* btree comparator */
@@ -2488,3 +2685,33 @@ datum_write(Pointer ptr, Datum datum, bool typbyval, char typalign,
 
 	return ptr;
 }
+
+/*
+ * Compare two ranges so we can qsort them.
+ */
+static int
+element_compare(const void *key1, const void *key2, void *arg)
+{
+	Datum *d1 = (Datum *)key1;
+	Datum *d2 = (Datum *)key2;
+	RangeType *r1 = DatumGetRangeTypeP(*d1);
+	RangeType *r2 = DatumGetRangeTypeP(*d2);
+	TypeCacheEntry *typcache = (TypeCacheEntry *) arg;
+	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);
+
+	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;
+}
diff --git a/src/include/catalog/pg_aggregate.dat b/src/include/catalog/pg_aggregate.dat
index 044695a046..1c8aec3977 100644
--- a/src/include/catalog/pg_aggregate.dat
+++ b/src/include/catalog/pg_aggregate.dat
@@ -544,6 +544,47 @@
 { aggfnoid => 'jsonb_object_agg', aggtransfn => 'jsonb_object_agg_transfn',
   aggfinalfn => 'jsonb_object_agg_finalfn', aggtranstype => 'internal' },
 
+# range
+{ aggfnoid => 'range_agg(anyrange)', aggtransfn => 'range_agg_transfn',
+  aggfinalfn => 'range_agg_finalfn', aggfinalextra => 't',
+  aggtranstype => 'internal' },
+{ aggfnoid => 'range_agg(int4range,bool)', aggtransfn => 'int4range_agg_transfn',
+  aggfinalfn => 'int4range_agg_finalfn', aggfinalextra => 't',
+  aggtranstype => 'internal' },
+{ aggfnoid => 'range_agg(int8range,bool)', aggtransfn => 'int8range_agg_transfn',
+  aggfinalfn => 'int8range_agg_finalfn', aggfinalextra => 't',
+  aggtranstype => 'internal' },
+{ aggfnoid => 'range_agg(numrange,bool)', aggtransfn => 'numrange_agg_transfn',
+  aggfinalfn => 'numrange_agg_finalfn', aggfinalextra => 't',
+  aggtranstype => 'internal' },
+{ aggfnoid => 'range_agg(tsrange,bool)', aggtransfn => 'tsrange_agg_transfn',
+  aggfinalfn => 'tsrange_agg_finalfn', aggfinalextra => 't',
+  aggtranstype => 'internal' },
+{ aggfnoid => 'range_agg(tstzrange,bool)', aggtransfn => 'tstzrange_agg_transfn',
+  aggfinalfn => 'tstzrange_agg_finalfn', aggfinalextra => 't',
+  aggtranstype => 'internal' },
+{ aggfnoid => 'range_agg(daterange,bool)', aggtransfn => 'daterange_agg_transfn',
+  aggfinalfn => 'daterange_agg_finalfn', aggfinalextra => 't',
+  aggtranstype => 'internal' },
+{ aggfnoid => 'range_agg(int4range,bool,bool)', aggtransfn => 'int4rangebb_agg_transfn',
+  aggfinalfn => 'int4rangebb_agg_finalfn', aggfinalextra => 't',
+  aggtranstype => 'internal' },
+{ aggfnoid => 'range_agg(int8range,bool,bool)', aggtransfn => 'int8rangebb_agg_transfn',
+  aggfinalfn => 'int8rangebb_agg_finalfn', aggfinalextra => 't',
+  aggtranstype => 'internal' },
+{ aggfnoid => 'range_agg(numrange,bool,bool)', aggtransfn => 'numrangebb_agg_transfn',
+  aggfinalfn => 'numrangebb_agg_finalfn', aggfinalextra => 't',
+  aggtranstype => 'internal' },
+{ aggfnoid => 'range_agg(tsrange,bool,bool)', aggtransfn => 'tsrangebb_agg_transfn',
+  aggfinalfn => 'tsrangebb_agg_finalfn', aggfinalextra => 't',
+  aggtranstype => 'internal' },
+{ aggfnoid => 'range_agg(tstzrange,bool,bool)', aggtransfn => 'tstzrangebb_agg_transfn',
+  aggfinalfn => 'tstzrangebb_agg_finalfn', aggfinalextra => 't',
+  aggtranstype => 'internal' },
+{ aggfnoid => 'range_agg(daterange,bool,bool)', aggtransfn => 'daterangebb_agg_transfn',
+  aggfinalfn => 'daterangebb_agg_finalfn', aggfinalextra => 't',
+  aggtranstype => 'internal' },
+
 # ordered-set and hypothetical-set aggregates
 { aggfnoid => 'percentile_disc(float8,anyelement)', aggkind => 'o',
   aggnumdirectargs => '1', aggtransfn => 'ordered_set_transition',
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 87335248a0..249220c8a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -9538,6 +9538,144 @@
 { oid => '3869',
   proname => 'range_minus', prorettype => 'anyrange',
   proargtypes => 'anyrange anyrange', prosrc => 'range_minus' },
+{ oid => '8000', descr => 'aggregate transition function',
+  proname => 'range_agg_transfn', proisstrict => 'f', prorettype => 'internal',
+  proargtypes => 'internal anyrange', prosrc => 'range_agg_transfn' },
+{ oid => '8001', descr => 'aggregate final function',
+  proname => 'range_agg_finalfn', proisstrict => 'f', prorettype => 'anyrange',
+  proargtypes => 'internal anyrange', prosrc => 'range_agg_finalfn' },
+{ oid => '8002', descr => 'combinate aggregate input into a range',
+  proname => 'range_agg', prokind => 'a', proisstrict => 'f',
+  prorettype => 'anyrange', proargtypes => 'anyrange',
+  prosrc => 'aggregate_dummy' },
+
+# Making 2- and 3-param range_agg polymorphic is difficult
+# because it would take an anyrange and return an anyrange[],
+# which doesn't exist.
+# As a workaround we define separate functions for each built-in range type.
+# This is what causes the mess in src/test/regress/expected/opr_sanity.out.
+{ oid => '8003', descr => 'aggregate transition function',
+  proname => 'int4range_agg_transfn', proisstrict => 'f', prorettype => 'internal',
+  proargtypes => 'internal int4range bool', prosrc => 'range_agg_transfn' },
+{ oid => '8004', descr => 'aggregate final function',
+  proname => 'int4range_agg_finalfn', proisstrict => 'f', prorettype => '_int4range',
+  proargtypes => 'internal int4range bool', prosrc => 'range_agg_finalfn' },
+{ oid => '8005', descr => 'combinate aggregate input into a range',
+  proname => 'range_agg', prokind => 'a', proisstrict => 'f',
+  prorettype => '_int4range', proargtypes => 'int4range bool',
+  prosrc => 'aggregate_dummy' },
+{ oid => '8006', descr => 'aggregate transition function',
+  proname => 'int8range_agg_transfn', proisstrict => 'f', prorettype => 'internal',
+  proargtypes => 'internal int8range bool', prosrc => 'range_agg_transfn' },
+{ oid => '8007', descr => 'aggregate final function',
+  proname => 'int8range_agg_finalfn', proisstrict => 'f', prorettype => '_int8range',
+  proargtypes => 'internal int8range bool', prosrc => 'range_agg_finalfn' },
+{ oid => '8008', descr => 'combinate aggregate input into a range',
+  proname => 'range_agg', prokind => 'a', proisstrict => 'f',
+  prorettype => '_int8range', proargtypes => 'int8range bool',
+  prosrc => 'aggregate_dummy' },
+{ oid => '8009', descr => 'aggregate transition function',
+  proname => 'numrange_agg_transfn', proisstrict => 'f', prorettype => 'internal',
+  proargtypes => 'internal numrange bool', prosrc => 'range_agg_transfn' },
+{ oid => '8010', descr => 'aggregate final function',
+  proname => 'numrange_agg_finalfn', proisstrict => 'f', prorettype => '_numrange',
+  proargtypes => 'internal numrange bool', prosrc => 'range_agg_finalfn' },
+{ oid => '8011', descr => 'combinate aggregate input into a range',
+  proname => 'range_agg', prokind => 'a', proisstrict => 'f',
+  prorettype => '_numrange', proargtypes => 'numrange bool',
+  prosrc => 'aggregate_dummy' },
+{ oid => '8012', descr => 'aggregate transition function',
+  proname => 'tsrange_agg_transfn', proisstrict => 'f', prorettype => 'internal',
+  proargtypes => 'internal tsrange bool', prosrc => 'range_agg_transfn' },
+{ oid => '8013', descr => 'aggregate final function',
+  proname => 'tsrange_agg_finalfn', proisstrict => 'f', prorettype => '_tsrange',
+  proargtypes => 'internal tsrange bool', prosrc => 'range_agg_finalfn' },
+{ oid => '8014', descr => 'combinate aggregate input into a range',
+  proname => 'range_agg', prokind => 'a', proisstrict => 'f',
+  prorettype => '_tsrange', proargtypes => 'tsrange bool',
+  prosrc => 'aggregate_dummy' },
+{ oid => '8015', descr => 'aggregate transition function',
+  proname => 'tstzrange_agg_transfn', proisstrict => 'f', prorettype => 'internal',
+  proargtypes => 'internal tstzrange bool', prosrc => 'range_agg_transfn' },
+{ oid => '8016', descr => 'aggregate final function',
+  proname => 'tstzrange_agg_finalfn', proisstrict => 'f', prorettype => '_tstzrange',
+  proargtypes => 'internal tstzrange bool', prosrc => 'range_agg_finalfn' },
+{ oid => '8017', descr => 'combinate aggregate input into a range',
+  proname => 'range_agg', prokind => 'a', proisstrict => 'f',
+  prorettype => '_tstzrange', proargtypes => 'tstzrange bool',
+  prosrc => 'aggregate_dummy' },
+{ oid => '8018', descr => 'aggregate transition function',
+  proname => 'daterange_agg_transfn', proisstrict => 'f', prorettype => 'internal',
+  proargtypes => 'internal daterange bool', prosrc => 'range_agg_transfn' },
+{ oid => '8019', descr => 'aggregate final function',
+  proname => 'daterange_agg_finalfn', proisstrict => 'f', prorettype => '_daterange',
+  proargtypes => 'internal daterange bool', prosrc => 'range_agg_finalfn' },
+{ oid => '8020', descr => 'combinate aggregate input into a range',
+  proname => 'range_agg', prokind => 'a', proisstrict => 'f',
+  prorettype => '_daterange', proargtypes => 'daterange bool',
+  prosrc => 'aggregate_dummy' },
+
+{ oid => '8021', descr => 'aggregate transition function',
+  proname => 'int4rangebb_agg_transfn', proisstrict => 'f', prorettype => 'internal',
+  proargtypes => 'internal int4range bool bool', prosrc => 'range_agg_transfn' },
+{ oid => '8022', descr => 'aggregate final function',
+  proname => 'int4rangebb_agg_finalfn', proisstrict => 'f', prorettype => '_int4range',
+  proargtypes => 'internal int4range bool bool', prosrc => 'range_agg_finalfn' },
+{ oid => '8023', descr => 'combinate aggregate input into a range',
+  proname => 'range_agg', prokind => 'a', proisstrict => 'f',
+  prorettype => '_int4range', proargtypes => 'int4range bool bool',
+  prosrc => 'aggregate_dummy' },
+{ oid => '8024', descr => 'aggregate transition function',
+  proname => 'int8rangebb_agg_transfn', proisstrict => 'f', prorettype => 'internal',
+  proargtypes => 'internal int8range bool bool', prosrc => 'range_agg_transfn' },
+{ oid => '8025', descr => 'aggregate final function',
+  proname => 'int8rangebb_agg_finalfn', proisstrict => 'f', prorettype => '_int8range',
+  proargtypes => 'internal int8range bool bool', prosrc => 'range_agg_finalfn' },
+{ oid => '8026', descr => 'combinate aggregate input into a range',
+  proname => 'range_agg', prokind => 'a', proisstrict => 'f',
+  prorettype => '_int8range', proargtypes => 'int8range bool bool',
+  prosrc => 'aggregate_dummy' },
+{ oid => '8027', descr => 'aggregate transition function',
+  proname => 'numrangebb_agg_transfn', proisstrict => 'f', prorettype => 'internal',
+  proargtypes => 'internal numrange bool bool', prosrc => 'range_agg_transfn' },
+{ oid => '8028', descr => 'aggregate final function',
+  proname => 'numrangebb_agg_finalfn', proisstrict => 'f', prorettype => '_numrange',
+  proargtypes => 'internal numrange bool bool', prosrc => 'range_agg_finalfn' },
+{ oid => '8029', descr => 'combinate aggregate input into a range',
+  proname => 'range_agg', prokind => 'a', proisstrict => 'f',
+  prorettype => '_numrange', proargtypes => 'numrange bool bool',
+  prosrc => 'aggregate_dummy' },
+{ oid => '8030', descr => 'aggregate transition function',
+  proname => 'tsrangebb_agg_transfn', proisstrict => 'f', prorettype => 'internal',
+  proargtypes => 'internal tsrange bool bool', prosrc => 'range_agg_transfn' },
+{ oid => '8031', descr => 'aggregate final function',
+  proname => 'tsrangebb_agg_finalfn', proisstrict => 'f', prorettype => '_tsrange',
+  proargtypes => 'internal tsrange bool bool', prosrc => 'range_agg_finalfn' },
+{ oid => '8032', descr => 'combinate aggregate input into a range',
+  proname => 'range_agg', prokind => 'a', proisstrict => 'f',
+  prorettype => '_tsrange', proargtypes => 'tsrange bool bool',
+  prosrc => 'aggregate_dummy' },
+{ oid => '8033', descr => 'aggregate transition function',
+  proname => 'tstzrangebb_agg_transfn', proisstrict => 'f', prorettype => 'internal',
+  proargtypes => 'internal tstzrange bool bool', prosrc => 'range_agg_transfn' },
+{ oid => '8034', descr => 'aggregate final function',
+  proname => 'tstzrangebb_agg_finalfn', proisstrict => 'f', prorettype => '_tstzrange',
+  proargtypes => 'internal tstzrange bool bool', prosrc => 'range_agg_finalfn' },
+{ oid => '8035', descr => 'combinate aggregate input into a range',
+  proname => 'range_agg', prokind => 'a', proisstrict => 'f',
+  prorettype => '_tstzrange', proargtypes => 'tstzrange bool bool',
+  prosrc => 'aggregate_dummy' },
+{ oid => '8036', descr => 'aggregate transition function',
+  proname => 'daterangebb_agg_transfn', proisstrict => 'f', prorettype => 'internal',
+  proargtypes => 'internal daterange bool bool', prosrc => 'range_agg_transfn' },
+{ oid => '8037', descr => 'aggregate final function',
+  proname => 'daterangebb_agg_finalfn', proisstrict => 'f', prorettype => '_daterange',
+  proargtypes => 'internal daterange bool bool', prosrc => 'range_agg_finalfn' },
+{ oid => '8038', descr => 'combinate aggregate input into a range',
+  proname => 'range_agg', prokind => 'a', proisstrict => 'f',
+  prorettype => '_daterange', proargtypes => 'daterange bool bool',
+  prosrc => 'aggregate_dummy' },
+
 { oid => '3870', descr => 'less-equal-greater',
   proname => 'range_cmp', prorettype => 'int4',
   proargtypes => 'anyrange anyrange', prosrc => 'range_cmp' },
diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out
index 85af36ee5b..a2cd607b31 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -168,9 +168,105 @@ WHERE p1.oid < p2.oid AND
      p1.proretset != p2.proretset OR
      p1.provolatile != p2.provolatile OR
      p1.pronargs != p2.pronargs);
- oid | proname | oid | proname 
------+---------+-----+---------
-(0 rows)
+ oid  |        proname        | oid  |         proname         
+------+-----------------------+------+-------------------------
+ 8019 | daterange_agg_finalfn | 8028 | numrangebb_agg_finalfn
+ 8019 | daterange_agg_finalfn | 8037 | daterangebb_agg_finalfn
+ 8019 | daterange_agg_finalfn | 8034 | tstzrangebb_agg_finalfn
+ 8019 | daterange_agg_finalfn | 8031 | tsrangebb_agg_finalfn
+ 8019 | daterange_agg_finalfn | 8025 | int8rangebb_agg_finalfn
+ 8019 | daterange_agg_finalfn | 8022 | int4rangebb_agg_finalfn
+ 8016 | tstzrange_agg_finalfn | 8028 | numrangebb_agg_finalfn
+ 8016 | tstzrange_agg_finalfn | 8037 | daterangebb_agg_finalfn
+ 8016 | tstzrange_agg_finalfn | 8034 | tstzrangebb_agg_finalfn
+ 8016 | tstzrange_agg_finalfn | 8031 | tsrangebb_agg_finalfn
+ 8016 | tstzrange_agg_finalfn | 8025 | int8rangebb_agg_finalfn
+ 8016 | tstzrange_agg_finalfn | 8022 | int4rangebb_agg_finalfn
+ 8013 | tsrange_agg_finalfn   | 8028 | numrangebb_agg_finalfn
+ 8013 | tsrange_agg_finalfn   | 8037 | daterangebb_agg_finalfn
+ 8013 | tsrange_agg_finalfn   | 8034 | tstzrangebb_agg_finalfn
+ 8013 | tsrange_agg_finalfn   | 8031 | tsrangebb_agg_finalfn
+ 8013 | tsrange_agg_finalfn   | 8025 | int8rangebb_agg_finalfn
+ 8013 | tsrange_agg_finalfn   | 8022 | int4rangebb_agg_finalfn
+ 8010 | numrange_agg_finalfn  | 8028 | numrangebb_agg_finalfn
+ 8010 | numrange_agg_finalfn  | 8037 | daterangebb_agg_finalfn
+ 8010 | numrange_agg_finalfn  | 8034 | tstzrangebb_agg_finalfn
+ 8010 | numrange_agg_finalfn  | 8031 | tsrangebb_agg_finalfn
+ 8010 | numrange_agg_finalfn  | 8025 | int8rangebb_agg_finalfn
+ 8010 | numrange_agg_finalfn  | 8022 | int4rangebb_agg_finalfn
+ 8007 | int8range_agg_finalfn | 8028 | numrangebb_agg_finalfn
+ 8007 | int8range_agg_finalfn | 8037 | daterangebb_agg_finalfn
+ 8007 | int8range_agg_finalfn | 8034 | tstzrangebb_agg_finalfn
+ 8007 | int8range_agg_finalfn | 8031 | tsrangebb_agg_finalfn
+ 8007 | int8range_agg_finalfn | 8025 | int8rangebb_agg_finalfn
+ 8007 | int8range_agg_finalfn | 8022 | int4rangebb_agg_finalfn
+ 8004 | int4range_agg_finalfn | 8028 | numrangebb_agg_finalfn
+ 8004 | int4range_agg_finalfn | 8037 | daterangebb_agg_finalfn
+ 8004 | int4range_agg_finalfn | 8034 | tstzrangebb_agg_finalfn
+ 8004 | int4range_agg_finalfn | 8031 | tsrangebb_agg_finalfn
+ 8004 | int4range_agg_finalfn | 8025 | int8rangebb_agg_finalfn
+ 8004 | int4range_agg_finalfn | 8022 | int4rangebb_agg_finalfn
+ 8001 | range_agg_finalfn     | 8028 | numrangebb_agg_finalfn
+ 8001 | range_agg_finalfn     | 8037 | daterangebb_agg_finalfn
+ 8001 | range_agg_finalfn     | 8034 | tstzrangebb_agg_finalfn
+ 8001 | range_agg_finalfn     | 8031 | tsrangebb_agg_finalfn
+ 8001 | range_agg_finalfn     | 8025 | int8rangebb_agg_finalfn
+ 8001 | range_agg_finalfn     | 8022 | int4rangebb_agg_finalfn
+ 8001 | range_agg_finalfn     | 8019 | daterange_agg_finalfn
+ 8001 | range_agg_finalfn     | 8016 | tstzrange_agg_finalfn
+ 8001 | range_agg_finalfn     | 8013 | tsrange_agg_finalfn
+ 8001 | range_agg_finalfn     | 8010 | numrange_agg_finalfn
+ 8001 | range_agg_finalfn     | 8007 | int8range_agg_finalfn
+ 8001 | range_agg_finalfn     | 8004 | int4range_agg_finalfn
+ 8003 | int4range_agg_transfn | 8036 | daterangebb_agg_transfn
+ 8003 | int4range_agg_transfn | 8027 | numrangebb_agg_transfn
+ 8003 | int4range_agg_transfn | 8030 | tsrangebb_agg_transfn
+ 8003 | int4range_agg_transfn | 8021 | int4rangebb_agg_transfn
+ 8003 | int4range_agg_transfn | 8024 | int8rangebb_agg_transfn
+ 8003 | int4range_agg_transfn | 8033 | tstzrangebb_agg_transfn
+ 8018 | daterange_agg_transfn | 8036 | daterangebb_agg_transfn
+ 8018 | daterange_agg_transfn | 8027 | numrangebb_agg_transfn
+ 8018 | daterange_agg_transfn | 8030 | tsrangebb_agg_transfn
+ 8018 | daterange_agg_transfn | 8021 | int4rangebb_agg_transfn
+ 8018 | daterange_agg_transfn | 8024 | int8rangebb_agg_transfn
+ 8018 | daterange_agg_transfn | 8033 | tstzrangebb_agg_transfn
+ 8015 | tstzrange_agg_transfn | 8036 | daterangebb_agg_transfn
+ 8015 | tstzrange_agg_transfn | 8027 | numrangebb_agg_transfn
+ 8015 | tstzrange_agg_transfn | 8030 | tsrangebb_agg_transfn
+ 8015 | tstzrange_agg_transfn | 8021 | int4rangebb_agg_transfn
+ 8015 | tstzrange_agg_transfn | 8024 | int8rangebb_agg_transfn
+ 8015 | tstzrange_agg_transfn | 8033 | tstzrangebb_agg_transfn
+ 8006 | int8range_agg_transfn | 8036 | daterangebb_agg_transfn
+ 8006 | int8range_agg_transfn | 8027 | numrangebb_agg_transfn
+ 8006 | int8range_agg_transfn | 8030 | tsrangebb_agg_transfn
+ 8006 | int8range_agg_transfn | 8021 | int4rangebb_agg_transfn
+ 8006 | int8range_agg_transfn | 8024 | int8rangebb_agg_transfn
+ 8006 | int8range_agg_transfn | 8033 | tstzrangebb_agg_transfn
+ 8012 | tsrange_agg_transfn   | 8036 | daterangebb_agg_transfn
+ 8012 | tsrange_agg_transfn   | 8027 | numrangebb_agg_transfn
+ 8012 | tsrange_agg_transfn   | 8030 | tsrangebb_agg_transfn
+ 8012 | tsrange_agg_transfn   | 8021 | int4rangebb_agg_transfn
+ 8012 | tsrange_agg_transfn   | 8024 | int8rangebb_agg_transfn
+ 8012 | tsrange_agg_transfn   | 8033 | tstzrangebb_agg_transfn
+ 8000 | range_agg_transfn     | 8036 | daterangebb_agg_transfn
+ 8000 | range_agg_transfn     | 8027 | numrangebb_agg_transfn
+ 8000 | range_agg_transfn     | 8030 | tsrangebb_agg_transfn
+ 8000 | range_agg_transfn     | 8003 | int4range_agg_transfn
+ 8000 | range_agg_transfn     | 8021 | int4rangebb_agg_transfn
+ 8000 | range_agg_transfn     | 8018 | daterange_agg_transfn
+ 8000 | range_agg_transfn     | 8015 | tstzrange_agg_transfn
+ 8000 | range_agg_transfn     | 8024 | int8rangebb_agg_transfn
+ 8000 | range_agg_transfn     | 8006 | int8range_agg_transfn
+ 8000 | range_agg_transfn     | 8012 | tsrange_agg_transfn
+ 8000 | range_agg_transfn     | 8009 | numrange_agg_transfn
+ 8000 | range_agg_transfn     | 8033 | tstzrangebb_agg_transfn
+ 8009 | numrange_agg_transfn  | 8036 | daterangebb_agg_transfn
+ 8009 | numrange_agg_transfn  | 8027 | numrangebb_agg_transfn
+ 8009 | numrange_agg_transfn  | 8030 | tsrangebb_agg_transfn
+ 8009 | numrange_agg_transfn  | 8021 | int4rangebb_agg_transfn
+ 8009 | numrange_agg_transfn  | 8024 | int8rangebb_agg_transfn
+ 8009 | numrange_agg_transfn  | 8033 | tstzrangebb_agg_transfn
+(96 rows)
 
 -- Look for uses of different type OIDs in the argument/result type fields
 -- for different aliases of the same built-in function.
@@ -196,7 +292,28 @@ ORDER BY 1, 2;
 ------------+------------
          25 |       1043
        1114 |       1184
-(2 rows)
+       3831 |       3905
+       3831 |       3907
+       3831 |       3909
+       3831 |       3911
+       3831 |       3913
+       3831 |       3927
+       3905 |       3907
+       3905 |       3909
+       3905 |       3911
+       3905 |       3913
+       3905 |       3927
+       3907 |       3909
+       3907 |       3911
+       3907 |       3913
+       3907 |       3927
+       3909 |       3911
+       3909 |       3913
+       3909 |       3927
+       3911 |       3913
+       3911 |       3927
+       3913 |       3927
+(23 rows)
 
 SELECT DISTINCT p1.proargtypes[0], p2.proargtypes[0]
 FROM pg_proc AS p1, pg_proc AS p2
@@ -231,7 +348,28 @@ ORDER BY 1, 2;
           23 |          28
         1114 |        1184
         1560 |        1562
-(3 rows)
+        3831 |        3904
+        3831 |        3906
+        3831 |        3908
+        3831 |        3910
+        3831 |        3912
+        3831 |        3926
+        3904 |        3906
+        3904 |        3908
+        3904 |        3910
+        3904 |        3912
+        3904 |        3926
+        3906 |        3908
+        3906 |        3910
+        3906 |        3912
+        3906 |        3926
+        3908 |        3910
+        3908 |        3912
+        3908 |        3926
+        3910 |        3912
+        3910 |        3926
+        3912 |        3926
+(24 rows)
 
 SELECT DISTINCT p1.proargtypes[2], p2.proargtypes[2]
 FROM pg_proc AS p1, pg_proc AS p2
@@ -1716,10 +1854,58 @@ WHERE p1.oid < p2.oid AND p1.proname = p2.proname AND
     p1.prokind = 'a' AND p2.prokind = 'a' AND
     array_dims(p1.proargtypes) != array_dims(p2.proargtypes)
 ORDER BY 1;
-     oid      |   oid   
---------------+---------
- count("any") | count()
-(1 row)
+             oid              |                 oid                  
+------------------------------+--------------------------------------
+ count("any")                 | count()
+ range_agg(anyrange)          | range_agg(daterange,boolean,boolean)
+ range_agg(anyrange)          | range_agg(tstzrange,boolean,boolean)
+ range_agg(anyrange)          | range_agg(tsrange,boolean,boolean)
+ range_agg(anyrange)          | range_agg(numrange,boolean,boolean)
+ range_agg(anyrange)          | range_agg(int8range,boolean,boolean)
+ range_agg(anyrange)          | range_agg(int4range,boolean,boolean)
+ range_agg(anyrange)          | range_agg(daterange,boolean)
+ range_agg(anyrange)          | range_agg(tstzrange,boolean)
+ range_agg(anyrange)          | range_agg(tsrange,boolean)
+ range_agg(anyrange)          | range_agg(numrange,boolean)
+ range_agg(anyrange)          | range_agg(int8range,boolean)
+ range_agg(anyrange)          | range_agg(int4range,boolean)
+ range_agg(int4range,boolean) | range_agg(daterange,boolean,boolean)
+ range_agg(int4range,boolean) | range_agg(tstzrange,boolean,boolean)
+ range_agg(int4range,boolean) | range_agg(tsrange,boolean,boolean)
+ range_agg(int4range,boolean) | range_agg(numrange,boolean,boolean)
+ range_agg(int4range,boolean) | range_agg(int8range,boolean,boolean)
+ range_agg(int4range,boolean) | range_agg(int4range,boolean,boolean)
+ range_agg(int8range,boolean) | range_agg(daterange,boolean,boolean)
+ range_agg(int8range,boolean) | range_agg(tstzrange,boolean,boolean)
+ range_agg(int8range,boolean) | range_agg(tsrange,boolean,boolean)
+ range_agg(int8range,boolean) | range_agg(numrange,boolean,boolean)
+ range_agg(int8range,boolean) | range_agg(int8range,boolean,boolean)
+ range_agg(int8range,boolean) | range_agg(int4range,boolean,boolean)
+ range_agg(numrange,boolean)  | range_agg(daterange,boolean,boolean)
+ range_agg(numrange,boolean)  | range_agg(tstzrange,boolean,boolean)
+ range_agg(numrange,boolean)  | range_agg(tsrange,boolean,boolean)
+ range_agg(numrange,boolean)  | range_agg(numrange,boolean,boolean)
+ range_agg(numrange,boolean)  | range_agg(int8range,boolean,boolean)
+ range_agg(numrange,boolean)  | range_agg(int4range,boolean,boolean)
+ range_agg(tsrange,boolean)   | range_agg(daterange,boolean,boolean)
+ range_agg(tsrange,boolean)   | range_agg(tstzrange,boolean,boolean)
+ range_agg(tsrange,boolean)   | range_agg(tsrange,boolean,boolean)
+ range_agg(tsrange,boolean)   | range_agg(numrange,boolean,boolean)
+ range_agg(tsrange,boolean)   | range_agg(int8range,boolean,boolean)
+ range_agg(tsrange,boolean)   | range_agg(int4range,boolean,boolean)
+ range_agg(tstzrange,boolean) | range_agg(daterange,boolean,boolean)
+ range_agg(tstzrange,boolean) | range_agg(tstzrange,boolean,boolean)
+ range_agg(tstzrange,boolean) | range_agg(tsrange,boolean,boolean)
+ range_agg(tstzrange,boolean) | range_agg(numrange,boolean,boolean)
+ range_agg(tstzrange,boolean) | range_agg(int8range,boolean,boolean)
+ range_agg(tstzrange,boolean) | range_agg(int4range,boolean,boolean)
+ range_agg(daterange,boolean) | range_agg(daterange,boolean,boolean)
+ range_agg(daterange,boolean) | range_agg(tstzrange,boolean,boolean)
+ range_agg(daterange,boolean) | range_agg(tsrange,boolean,boolean)
+ range_agg(daterange,boolean) | range_agg(numrange,boolean,boolean)
+ range_agg(daterange,boolean) | range_agg(int8range,boolean,boolean)
+ range_agg(daterange,boolean) | range_agg(int4range,boolean,boolean)
+(49 rows)
 
 -- For the same reason, built-in aggregates with default arguments are no good.
 SELECT oid, proname
diff --git a/src/test/regress/expected/rangetypes.out b/src/test/regress/expected/rangetypes.out
index accf1e0d9e..703bc41491 100644
--- a/src/test/regress/expected/rangetypes.out
+++ b/src/test/regress/expected/rangetypes.out
@@ -1407,3 +1407,281 @@ create function table_fail(i anyelement) returns table(i anyelement, r anyrange)
   as $$ select $1, '[1,10]' $$ language sql;
 ERROR:  cannot determine result data type
 DETAIL:  A function returning "anyrange" must have at least one "anyrange" argument.
+--
+-- range_agg function
+--
+create table reservations ( room_id integer not null, booked_during daterange );
+insert into reservations values
+-- 1: has a meets and a gap
+(1, daterange('2018-07-01', '2018-07-07')),
+(1, daterange('2018-07-07', '2018-07-14')),
+(1, daterange('2018-07-20', '2018-07-22')),
+-- 2: just a single row
+(2, daterange('2018-07-01', '2018-07-03')),
+-- 3: one null range
+(3, NULL),
+-- 4: two null ranges
+(4, NULL),
+(4, NULL),
+-- 5: a null range and a non-null range
+(5, NULL),
+(5, daterange('2018-07-01', '2018-07-03')),
+-- 6: has overlap
+(6, daterange('2018-07-01', '2018-07-07')),
+(6, daterange('2018-07-05', '2018-07-10')),
+-- 7: two ranges that meet: no gap or overlap
+(7, daterange('2018-07-01', '2018-07-07')),
+(7, daterange('2018-07-07', '2018-07-14'))
+;
+-- range_agg with 1 arg:
+-- Forbidding gaps and overlaps:
+SELECT  room_id, range_agg(booked_during)
+FROM    reservations
+WHERE   room_id = 1
+GROUP BY room_id
+ORDER BY room_id;
+ERROR:  range_agg: gap detected between lastRange and currentRange
+SELECT  room_id, range_agg(booked_during)
+FROM    reservations
+WHERE   room_id = 6
+GROUP BY room_id
+ORDER BY room_id;
+ERROR:  range_agg: overlap detected between lastRange and currentRange
+SELECT  room_id, range_agg(booked_during)
+FROM    reservations
+WHERE   room_id NOT IN (1, 6)
+GROUP BY room_id
+ORDER BY room_id;
+ room_id |        range_agg        
+---------+-------------------------
+       2 | [07-01-2018,07-03-2018)
+       3 | 
+       4 | 
+       5 | [07-01-2018,07-03-2018)
+       7 | [07-01-2018,07-14-2018)
+(5 rows)
+
+-- Obeying discrete base types:
+SELECT	range_agg(r)
+FROM		(VALUES
+  (int4range( 0,  9, '[]')),
+  (int4range(10, 19, '[]'))
+) t(r);
+ range_agg 
+-----------
+ [0,20)
+(1 row)
+
+-- range_agg with 2 args:
+-- Forbidding gaps (and overlaps):
+SELECT  room_id, range_agg(booked_during, false)
+FROM    reservations
+WHERE   room_id = 1
+GROUP BY room_id;
+ERROR:  range_agg: gap detected between lastRange and currentRange
+SELECT  room_id, range_agg(booked_during, false)
+FROM    reservations
+WHERE   room_id = 6
+GROUP BY room_id;
+ERROR:  range_agg: overlap detected between lastRange and currentRange
+SELECT  room_id, range_agg(booked_during, false)
+FROM    reservations
+WHERE   room_id NOT IN (1, 6)
+GROUP BY room_id;
+ room_id |          range_agg          
+---------+-----------------------------
+       3 | 
+       5 | {"[07-01-2018,07-03-2018)"}
+       4 | 
+       2 | {"[07-01-2018,07-03-2018)"}
+       7 | {"[07-01-2018,07-14-2018)"}
+(5 rows)
+
+-- Permitting gaps (but forbidding overlaps):
+SELECT  room_id, range_agg(booked_during, true)
+FROM    reservations
+WHERE   room_id = 1
+GROUP BY room_id;
+ room_id |                       range_agg                       
+---------+-------------------------------------------------------
+       1 | {"[07-01-2018,07-14-2018)","[07-20-2018,07-22-2018)"}
+(1 row)
+
+SELECT  room_id, range_agg(booked_during, true)
+FROM    reservations
+WHERE   room_id = 6
+GROUP BY room_id;
+ERROR:  range_agg: overlap detected between lastRange and currentRange
+SELECT  room_id, range_agg(booked_during, true)
+FROM    reservations
+WHERE   room_id NOT IN (1, 6)
+GROUP BY room_id;
+ room_id |          range_agg          
+---------+-----------------------------
+       3 | 
+       5 | {"[07-01-2018,07-03-2018)"}
+       4 | 
+       2 | {"[07-01-2018,07-03-2018)"}
+       7 | {"[07-01-2018,07-14-2018)"}
+(5 rows)
+
+-- range_agg with 3 args:
+-- Forbidding gaps and overlaps:
+SELECT  room_id, range_agg(booked_during, false, false)
+FROM    reservations
+WHERE   room_id = 1
+GROUP BY room_id;
+ERROR:  range_agg: gap detected between lastRange and currentRange
+SELECT  room_id, range_agg(booked_during, false, false)
+FROM    reservations
+WHERE   room_id = 6
+GROUP BY room_id;
+ERROR:  range_agg: overlap detected between lastRange and currentRange
+SELECT  room_id, range_agg(booked_during, false, false)
+FROM    reservations
+WHERE   room_id NOT IN (1, 6)
+GROUP BY room_id;
+ room_id |          range_agg          
+---------+-----------------------------
+       3 | 
+       5 | {"[07-01-2018,07-03-2018)"}
+       4 | 
+       2 | {"[07-01-2018,07-03-2018)"}
+       7 | {"[07-01-2018,07-14-2018)"}
+(5 rows)
+
+-- Forbidding gaps but permitting overlaps
+SELECT  room_id, range_agg(booked_during, false, true)
+FROM    reservations
+WHERE   room_id = 1
+GROUP BY room_id;
+ERROR:  range_agg: gap detected between lastRange and currentRange
+SELECT  room_id, range_agg(booked_during, false, true)
+FROM    reservations
+WHERE   room_id = 6
+GROUP BY room_id;
+ room_id |          range_agg          
+---------+-----------------------------
+       6 | {"[07-01-2018,07-10-2018)"}
+(1 row)
+
+SELECT  room_id, range_agg(booked_during, false, true)
+FROM    reservations
+WHERE   room_id NOT IN (1, 6)
+GROUP BY room_id;
+ room_id |          range_agg          
+---------+-----------------------------
+       3 | 
+       5 | {"[07-01-2018,07-03-2018)"}
+       4 | 
+       2 | {"[07-01-2018,07-03-2018)"}
+       7 | {"[07-01-2018,07-14-2018)"}
+(5 rows)
+
+-- Permitting gaps but forbidding overlaps
+SELECT  room_id, range_agg(booked_during, true, false)
+FROM    reservations
+WHERE   room_id = 1
+GROUP BY room_id;
+ room_id |                       range_agg                       
+---------+-------------------------------------------------------
+       1 | {"[07-01-2018,07-14-2018)","[07-20-2018,07-22-2018)"}
+(1 row)
+
+SELECT  room_id, range_agg(booked_during, true, false)
+FROM    reservations
+WHERE   room_id = 6
+GROUP BY room_id;
+ERROR:  range_agg: overlap detected between lastRange and currentRange
+SELECT  room_id, range_agg(booked_during, true, false)
+FROM    reservations
+WHERE   room_id NOT IN (1, 6)
+GROUP BY room_id;
+ room_id |          range_agg          
+---------+-----------------------------
+       3 | 
+       5 | {"[07-01-2018,07-03-2018)"}
+       4 | 
+       2 | {"[07-01-2018,07-03-2018)"}
+       7 | {"[07-01-2018,07-14-2018)"}
+(5 rows)
+
+-- Permitting gaps and overlaps:
+SELECT  room_id, range_agg(booked_during, true, true)
+FROM    reservations
+WHERE   room_id = 1
+GROUP BY room_id;
+ room_id |                       range_agg                       
+---------+-------------------------------------------------------
+       1 | {"[07-01-2018,07-14-2018)","[07-20-2018,07-22-2018)"}
+(1 row)
+
+SELECT  room_id, range_agg(booked_during, true, true)
+FROM    reservations
+WHERE   room_id = 6
+GROUP BY room_id;
+ room_id |          range_agg          
+---------+-----------------------------
+       6 | {"[07-01-2018,07-10-2018)"}
+(1 row)
+
+SELECT  room_id, range_agg(booked_during, true, true)
+FROM    reservations
+WHERE   room_id NOT IN (1, 6)
+GROUP BY room_id;
+ room_id |          range_agg          
+---------+-----------------------------
+       3 | 
+       5 | {"[07-01-2018,07-03-2018)"}
+       4 | 
+       2 | {"[07-01-2018,07-03-2018)"}
+       7 | {"[07-01-2018,07-14-2018)"}
+(5 rows)
+
+-- Obeying discrete base types:
+SELECT	range_agg(r, false, false)
+FROM		(VALUES
+  (int4range( 0,  5, '[]')),
+  (int4range( 7,  9, '[]'))
+) t(r);
+ERROR:  range_agg: gap detected between lastRange and currentRange
+SELECT	range_agg(r, false, false)
+FROM		(VALUES
+  (int4range( 0,  5, '[]')),
+  (int4range( 5,  9, '[]'))
+) t(r);
+ERROR:  range_agg: overlap detected between lastRange and currentRange
+SELECT	range_agg(r, true, true)
+FROM		(VALUES
+  (int4range( 0,  9, '[]')),
+  (int4range(10, 15, '[]')),
+  (int4range(20, 26, '[]')),
+  (int4range(26, 29, '[]'))
+) t(r);
+      range_agg       
+----------------------
+ {"[0,16)","[20,30)"}
+(1 row)
+
+-- It combines with UNNEST
+-- to implement the temporal database "coalesce" function
+-- (see Snodgrass 6.5.2):
+SELECT  room_id, t2.booked_during
+FROM    (
+          SELECT  room_id, range_agg(booked_during, true, true) AS booked_during
+          FROM    reservations
+          GROUP BY room_id
+        ) AS t1,
+        UNNEST(t1.booked_during) AS t2(booked_during)
+ORDER BY room_id, booked_during
+;
+ room_id |      booked_during      
+---------+-------------------------
+       1 | [07-01-2018,07-14-2018)
+       1 | [07-20-2018,07-22-2018)
+       2 | [07-01-2018,07-03-2018)
+       5 | [07-01-2018,07-03-2018)
+       6 | [07-01-2018,07-10-2018)
+       7 | [07-01-2018,07-14-2018)
+(6 rows)
+
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index 392e8a4957..bf4558952c 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -169,6 +169,7 @@ quad_poly_tbl|t
 radix_text_tbl|t
 ramp|f
 real_city|f
+reservations|f
 road|t
 shighway|t
 slow_emp4000|f
diff --git a/src/test/regress/sql/rangetypes.sql b/src/test/regress/sql/rangetypes.sql
index 55638a85ee..9d1be6f3ca 100644
--- a/src/test/regress/sql/rangetypes.sql
+++ b/src/test/regress/sql/rangetypes.sql
@@ -503,3 +503,192 @@ create function inoutparam_fail(inout i anyelement, out r anyrange)
 --should fail
 create function table_fail(i anyelement) returns table(i anyelement, r anyrange)
   as $$ select $1, '[1,10]' $$ language sql;
+
+--
+-- range_agg function
+--
+create table reservations ( room_id integer not null, booked_during daterange );
+insert into reservations values
+-- 1: has a meets and a gap
+(1, daterange('2018-07-01', '2018-07-07')),
+(1, daterange('2018-07-07', '2018-07-14')),
+(1, daterange('2018-07-20', '2018-07-22')),
+-- 2: just a single row
+(2, daterange('2018-07-01', '2018-07-03')),
+-- 3: one null range
+(3, NULL),
+-- 4: two null ranges
+(4, NULL),
+(4, NULL),
+-- 5: a null range and a non-null range
+(5, NULL),
+(5, daterange('2018-07-01', '2018-07-03')),
+-- 6: has overlap
+(6, daterange('2018-07-01', '2018-07-07')),
+(6, daterange('2018-07-05', '2018-07-10')),
+-- 7: two ranges that meet: no gap or overlap
+(7, daterange('2018-07-01', '2018-07-07')),
+(7, daterange('2018-07-07', '2018-07-14'))
+;
+
+-- range_agg with 1 arg:
+
+-- Forbidding gaps and overlaps:
+SELECT  room_id, range_agg(booked_during)
+FROM    reservations
+WHERE   room_id = 1
+GROUP BY room_id
+ORDER BY room_id;
+
+SELECT  room_id, range_agg(booked_during)
+FROM    reservations
+WHERE   room_id = 6
+GROUP BY room_id
+ORDER BY room_id;
+
+SELECT  room_id, range_agg(booked_during)
+FROM    reservations
+WHERE   room_id NOT IN (1, 6)
+GROUP BY room_id
+ORDER BY room_id;
+
+-- Obeying discrete base types:
+SELECT	range_agg(r)
+FROM		(VALUES
+  (int4range( 0,  9, '[]')),
+  (int4range(10, 19, '[]'))
+) t(r);
+
+-- range_agg with 2 args:
+
+-- Forbidding gaps (and overlaps):
+SELECT  room_id, range_agg(booked_during, false)
+FROM    reservations
+WHERE   room_id = 1
+GROUP BY room_id;
+
+SELECT  room_id, range_agg(booked_during, false)
+FROM    reservations
+WHERE   room_id = 6
+GROUP BY room_id;
+
+SELECT  room_id, range_agg(booked_during, false)
+FROM    reservations
+WHERE   room_id NOT IN (1, 6)
+GROUP BY room_id;
+
+-- Permitting gaps (but forbidding overlaps):
+SELECT  room_id, range_agg(booked_during, true)
+FROM    reservations
+WHERE   room_id = 1
+GROUP BY room_id;
+
+SELECT  room_id, range_agg(booked_during, true)
+FROM    reservations
+WHERE   room_id = 6
+GROUP BY room_id;
+
+SELECT  room_id, range_agg(booked_during, true)
+FROM    reservations
+WHERE   room_id NOT IN (1, 6)
+GROUP BY room_id;
+
+-- range_agg with 3 args:
+
+-- Forbidding gaps and overlaps:
+SELECT  room_id, range_agg(booked_during, false, false)
+FROM    reservations
+WHERE   room_id = 1
+GROUP BY room_id;
+
+SELECT  room_id, range_agg(booked_during, false, false)
+FROM    reservations
+WHERE   room_id = 6
+GROUP BY room_id;
+
+SELECT  room_id, range_agg(booked_during, false, false)
+FROM    reservations
+WHERE   room_id NOT IN (1, 6)
+GROUP BY room_id;
+
+-- Forbidding gaps but permitting overlaps
+SELECT  room_id, range_agg(booked_during, false, true)
+FROM    reservations
+WHERE   room_id = 1
+GROUP BY room_id;
+
+SELECT  room_id, range_agg(booked_during, false, true)
+FROM    reservations
+WHERE   room_id = 6
+GROUP BY room_id;
+
+SELECT  room_id, range_agg(booked_during, false, true)
+FROM    reservations
+WHERE   room_id NOT IN (1, 6)
+GROUP BY room_id;
+
+-- Permitting gaps but forbidding overlaps
+SELECT  room_id, range_agg(booked_during, true, false)
+FROM    reservations
+WHERE   room_id = 1
+GROUP BY room_id;
+
+SELECT  room_id, range_agg(booked_during, true, false)
+FROM    reservations
+WHERE   room_id = 6
+GROUP BY room_id;
+
+SELECT  room_id, range_agg(booked_during, true, false)
+FROM    reservations
+WHERE   room_id NOT IN (1, 6)
+GROUP BY room_id;
+
+-- Permitting gaps and overlaps:
+SELECT  room_id, range_agg(booked_during, true, true)
+FROM    reservations
+WHERE   room_id = 1
+GROUP BY room_id;
+
+SELECT  room_id, range_agg(booked_during, true, true)
+FROM    reservations
+WHERE   room_id = 6
+GROUP BY room_id;
+
+SELECT  room_id, range_agg(booked_during, true, true)
+FROM    reservations
+WHERE   room_id NOT IN (1, 6)
+GROUP BY room_id;
+
+-- Obeying discrete base types:
+SELECT	range_agg(r, false, false)
+FROM		(VALUES
+  (int4range( 0,  5, '[]')),
+  (int4range( 7,  9, '[]'))
+) t(r);
+
+SELECT	range_agg(r, false, false)
+FROM		(VALUES
+  (int4range( 0,  5, '[]')),
+  (int4range( 5,  9, '[]'))
+) t(r);
+
+SELECT	range_agg(r, true, true)
+FROM		(VALUES
+  (int4range( 0,  9, '[]')),
+  (int4range(10, 15, '[]')),
+  (int4range(20, 26, '[]')),
+  (int4range(26, 29, '[]'))
+) t(r);
+
+-- It combines with UNNEST
+-- to implement the temporal database "coalesce" function
+-- (see Snodgrass 6.5.2):
+SELECT  room_id, t2.booked_during
+FROM    (
+          SELECT  room_id, range_agg(booked_during, true, true) AS booked_during
+          FROM    reservations
+          GROUP BY room_id
+        ) AS t1,
+        UNNEST(t1.booked_during) AS t2(booked_during)
+ORDER BY room_id, booked_during
+;
#9Paul A Jungwirth
pj@illuminatedcomputing.com
In reply to: Paul A Jungwirth (#8)
1 attachment(s)
Re: range_agg

On Wed, May 8, 2019 at 9:54 PM Paul A Jungwirth
<pj@illuminatedcomputing.com> wrote:

Here is an initial patch. I'd love to have some feedback! :-)

Here is a v2 rebased off current master. No substantive changes, but
it does fix one trivial git conflict.

After talking with David in Ottawa and hearing a good use-case from
one other person for his proposed weighted_range_agg and
covering_range_agg, I think *will* add those to this patch, but if
anyone wants to offer feedback on my approach so far, I'd appreciate
that too.

Yours,
Paul

Attachments:

range_agg_v0002.patchapplication/octet-stream; name=range_agg_v0002.patchDownload
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index e918133874..768d7fb0c0 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -14514,6 +14514,11 @@ NULL baz</literallayout>(3 rows)</entry>
    <function>lower_inf</function>, and <function>upper_inf</function>
    functions all return false for an empty range.
   </para>
+
+  <para>
+   See also <xref linkend="functions-aggregate"/> about the aggregate
+   function <function>range_agg</function> for use with ranges.
+  </para>
   </sect1>
 
  <sect1 id="functions-aggregate">
@@ -14832,6 +14837,76 @@ NULL baz</literallayout>(3 rows)</entry>
      <row>
       <entry>
        <indexterm>
+        <primary>range_agg</primary>
+       </indexterm>
+       <function>
+         range_agg(<replaceable class="parameter">expression</replaceable>)
+       </function>
+      </entry>
+      <entry>
+       any range type
+      </entry>
+      <entry>
+       same as argument type
+      </entry>
+      <entry>No</entry>
+      <entry>input ranges merged into a single range, or an error if there are any gaps or overlaps</entry>
+     </row>
+
+     <row>
+      <entry>
+       <indexterm>
+        <primary>range_agg</primary>
+       </indexterm>
+       <function>
+         range_agg(<replaceable class="parameter">expression</replaceable>,
+          <replaceable>permit_gaps</replaceable>)
+       </function>
+      </entry>
+      <entry>
+       (any range type, <type>boolean</type>)
+      </entry>
+      <entry>
+       array of the same range type as argument type
+      </entry>
+      <entry>No</entry>
+      <entry>
+       input ranges merged into a list of non-touching ranges,
+       with gaps permitted, rejected, or yielding a NULL result
+       as the second argument is true, false, or NULL respectively
+      </entry>
+     </row>
+
+     <row>
+      <entry>
+       <indexterm>
+        <primary>range_agg</primary>
+       </indexterm>
+       <function>
+         range_agg(<replaceable class="parameter">expression</replaceable>,
+          <replaceable>permit_gaps</replaceable>,
+          <replaceable>permit_overlaps</replaceable>)
+       </function>
+      </entry>
+      <entry>
+       (any range type, <type>boolean</type>, <type>boolean</type>)
+      </entry>
+      <entry>
+       array of the same range type as argument type
+      </entry>
+      <entry>No</entry>
+      <entry>
+       input ranges merged into a list of non-touching ranges,
+       with gaps permitted, rejected, or yielding a NULL result
+       as the second argument is true, false, or NULL respectively,
+       and overlaps permitted, rejected, or yielding a NULL result
+       as the third argument is true, false, or NULL respectively
+      </entry>
+     </row>
+
+     <row>
+      <entry>
+       <indexterm>
         <primary>string_agg</primary>
        </indexterm>
        <function>
diff --git a/src/backend/utils/adt/rangetypes.c b/src/backend/utils/adt/rangetypes.c
index cb16d701d8..bfa46bd389 100644
--- a/src/backend/utils/adt/rangetypes.c
+++ b/src/backend/utils/adt/rangetypes.c
@@ -34,6 +34,7 @@
 #include "lib/stringinfo.h"
 #include "libpq/pqformat.h"
 #include "miscadmin.h"
+#include "utils/array.h"
 #include "utils/builtins.h"
 #include "utils/date.h"
 #include "utils/hashutils.h"
@@ -54,6 +55,18 @@ typedef struct RangeIOData
 	FmgrInfo	proc;			/* lookup result for typiofunc */
 } RangeIOData;
 
+/* aggregate state for range_agg */
+typedef struct RangeAggState
+{
+	ArrayBuildState *inputs;
+	uint8 flags;
+} RangeAggState;
+#define RANGE_AGG_GAPS_RAISE		0x01
+#define RANGE_AGG_GAPS_SET_NULL		0x02
+#define RANGE_AGG_OVERLAPS_RAISE	0x04
+#define RANGE_AGG_OVERLAPS_SET_NULL	0x08
+#define RANGE_AGG_GAPS_FLAGS		(RANGE_AGG_GAPS_RAISE | RANGE_AGG_GAPS_SET_NULL)
+#define RANGE_AGG_OVERLAPS_FLAGS	(RANGE_AGG_OVERLAPS_RAISE | RANGE_AGG_OVERLAPS_SET_NULL)
 
 static RangeIOData *get_range_io_data(FunctionCallInfo fcinfo, Oid rngtypid,
 									  IOFuncSelector func);
@@ -69,6 +82,7 @@ static Size datum_compute_size(Size sz, Datum datum, bool typbyval,
 							   char typalign, int16 typlen, char typstorage);
 static Pointer datum_write(Pointer ptr, Datum datum, bool typbyval,
 						   char typalign, int16 typlen, char typstorage);
+static int element_compare(const void *key1, const void *key2, void *arg);
 
 
 /*
@@ -1135,6 +1149,189 @@ range_intersect(PG_FUNCTION_ARGS)
 	PG_RETURN_RANGE_P(make_range(typcache, result_lower, result_upper, false));
 }
 
+/* Aggregate functions */
+
+/*
+ * range_agg_transfn: combine adjacent/overlapping ranges.
+ *
+ * All we do here is gather the input ranges into an array
+ * so that the finalfn can sort and combine them.
+ * We can't simply use array_append as the transfn
+ * because we have to capture the second & third parameters
+ * and put them in the aggregate's running state,
+ * so that our finalfn can use them.
+ */
+Datum
+range_agg_transfn(PG_FUNCTION_ARGS)
+{
+	Oid rangeTypeId;
+	MemoryContext aggContext;
+	RangeAggState *state;
+
+	if (!AggCheckCallContext(fcinfo, &aggContext))
+	{
+		elog(ERROR, "range_agg_transfn called in non-aggregate context");
+	}
+
+	rangeTypeId = get_fn_expr_argtype(fcinfo->flinfo, 1);
+	if (!type_is_range(rangeTypeId))
+	{
+		ereport(ERROR, (errmsg("range_agg must be called with a range")));
+	}
+	if (PG_ARGISNULL(0))
+	{
+		state = MemoryContextAlloc(aggContext, sizeof(RangeAggState));
+		state->inputs = initArrayResult(rangeTypeId, aggContext, false);
+
+		state->flags = 0x0;
+		switch (PG_NARGS())
+		{
+			case 2:
+				state->flags |= RANGE_AGG_GAPS_RAISE;
+				state->flags |= RANGE_AGG_OVERLAPS_RAISE;
+				break;
+			case 3:
+				if (PG_ARGISNULL(2))         state->flags |= RANGE_AGG_GAPS_SET_NULL;
+				else if (!PG_GETARG_BOOL(2)) state->flags |= RANGE_AGG_GAPS_RAISE;
+				state->flags |= RANGE_AGG_OVERLAPS_RAISE;
+				break;
+			case 4:
+				if (PG_ARGISNULL(2))         state->flags |= RANGE_AGG_GAPS_SET_NULL;
+				else if (!PG_GETARG_BOOL(2)) state->flags |= RANGE_AGG_GAPS_RAISE;
+				if (PG_ARGISNULL(3))         state->flags |= RANGE_AGG_OVERLAPS_SET_NULL;
+				else if (!PG_GETARG_BOOL(3)) state->flags |= RANGE_AGG_OVERLAPS_RAISE;
+				break;
+			default:
+				Assert(false);
+		}
+	}
+	else
+	{
+		state = (RangeAggState *)PG_GETARG_POINTER(0);
+	}
+
+	/* Might as well skip NULLs here so the finalfn doesn't have to: */
+	if (!PG_ARGISNULL(1))
+	{
+		accumArrayResult(state->inputs, PG_GETARG_DATUM(1), false,
+				rangeTypeId, aggContext);
+	}
+
+	PG_RETURN_POINTER(state);
+}
+
+/* range_agg_finalfn: combine adjacent/overlapping ranges */
+Datum
+range_agg_finalfn(PG_FUNCTION_ARGS)
+{
+	MemoryContext aggContext;
+	Oid rangeTypeId;
+	TypeCacheEntry *typcache;
+	RangeAggState *state;
+	ArrayBuildState *inputArray;
+	int inputLength;
+	Datum *inputVals;
+	bool *inputNulls;
+	int i;
+	RangeType *currentRange;
+	RangeType *lastRange;
+	char *r1Str, *r2Str;
+	ArrayBuildState *resultContent;
+	Datum result;
+
+	if (!AggCheckCallContext(fcinfo, &aggContext))
+	{
+		elog(ERROR, "range_agg_finalfn called in non-aggregate context");
+	}
+
+	state = PG_ARGISNULL(0) ? NULL : (RangeAggState *)PG_GETARG_POINTER(0);
+	if (state == NULL) PG_RETURN_NULL();
+	inputArray  = state->inputs;
+	inputVals   = inputArray->dvalues;
+	inputNulls  = inputArray->dnulls;
+	inputLength = inputArray->nelems;
+	rangeTypeId = inputArray->element_type;
+
+	typcache = range_get_typcache(fcinfo, rangeTypeId);
+	if (inputLength == 0) PG_RETURN_NULL();
+	qsort_arg(inputVals, inputLength, sizeof(Datum), element_compare, typcache);
+
+	resultContent = initArrayResult(rangeTypeId, aggContext, false);
+	lastRange = DatumGetRangeTypeP(inputVals[0]);
+	for (i = 1; i < inputLength; i++)
+	{
+		Assert(!inputNulls[i]);
+		currentRange = DatumGetRangeTypeP(inputVals[i]);
+		/* range_adjacent_interval gives true
+		 * if *either* A meets B or B meets A,
+		 * which is not quite what we want,
+		 * but we rely on the sorting above
+		 * to rule out B meets A ever happening.
+		 */
+		if (range_adjacent_internal(typcache, lastRange, currentRange))
+		{
+			/* The two ranges touch without overlap: */
+			lastRange = range_union_internal(typcache, lastRange, currentRange, false);
+		}
+		else if (range_before_internal(typcache, lastRange, currentRange))
+		{
+			/* There is a gap: */
+			if (state->flags & RANGE_AGG_GAPS_FLAGS)
+			{
+				if (state->flags & RANGE_AGG_GAPS_RAISE)
+				{
+					r1Str = "lastRange";
+					r2Str = "currentRange";
+					// TODO: Why is this segfaulting?:
+					// r1Str = DatumGetCString(DirectFunctionCall1(range_out, RangeTypePGetDatum(lastRange)));
+					// r2Str = DatumGetCString(DirectFunctionCall1(range_out, RangeTypePGetDatum(currentRange)));
+					ereport(ERROR, (errmsg("range_agg: gap detected between %s and %s", r1Str, r2Str)));
+				}
+				else
+				{
+					Assert(state->flags & RANGE_AGG_GAPS_SET_NULL);
+					PG_RETURN_NULL();
+				}
+			}
+			accumArrayResult(resultContent, RangeTypePGetDatum(lastRange), false, rangeTypeId, aggContext);
+			lastRange = currentRange;
+
+		}
+		else
+		{
+			/* They must overlap: */
+			if (state->flags & RANGE_AGG_OVERLAPS_FLAGS)
+			{
+				if (state->flags & RANGE_AGG_OVERLAPS_RAISE)
+				{
+					r1Str = "lastRange"; r2Str = "currentRange";
+					// TODO: Why is this segfaulting?:
+					// r1Str = DatumGetCString(DirectFunctionCall1(range_out, RangeTypePGetDatum(lastRange)));
+					// r2Str = DatumGetCString(DirectFunctionCall1(range_out, RangeTypePGetDatum(currentRange)));
+					ereport(ERROR, (errmsg("range_agg: overlap detected between %s and %s", r1Str, r2Str)));
+				}
+				else
+				{
+					Assert(state->flags & RANGE_AGG_OVERLAPS_SET_NULL);
+					PG_RETURN_NULL();
+				}
+			}
+			lastRange = range_union_internal(typcache, lastRange, currentRange, false);
+		}
+	}
+	accumArrayResult(resultContent, RangeTypePGetDatum(lastRange), false, rangeTypeId, aggContext);
+
+	if (type_is_array(get_fn_expr_rettype(fcinfo->flinfo)))
+	{
+		result = makeArrayResult(resultContent, CurrentMemoryContext);
+		PG_RETURN_DATUM(result);
+	}
+	else
+	{
+		PG_RETURN_DATUM(RangeTypePGetDatum(lastRange));
+	}
+}
+
 /* Btree support */
 
 /* btree comparator */
@@ -2488,3 +2685,33 @@ datum_write(Pointer ptr, Datum datum, bool typbyval, char typalign,
 
 	return ptr;
 }
+
+/*
+ * Compare two ranges so we can qsort them.
+ */
+static int
+element_compare(const void *key1, const void *key2, void *arg)
+{
+	Datum *d1 = (Datum *)key1;
+	Datum *d2 = (Datum *)key2;
+	RangeType *r1 = DatumGetRangeTypeP(*d1);
+	RangeType *r2 = DatumGetRangeTypeP(*d2);
+	TypeCacheEntry *typcache = (TypeCacheEntry *) arg;
+	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);
+
+	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;
+}
diff --git a/src/include/catalog/pg_aggregate.dat b/src/include/catalog/pg_aggregate.dat
index 044695a046..1c8aec3977 100644
--- a/src/include/catalog/pg_aggregate.dat
+++ b/src/include/catalog/pg_aggregate.dat
@@ -544,6 +544,47 @@
 { aggfnoid => 'jsonb_object_agg', aggtransfn => 'jsonb_object_agg_transfn',
   aggfinalfn => 'jsonb_object_agg_finalfn', aggtranstype => 'internal' },
 
+# range
+{ aggfnoid => 'range_agg(anyrange)', aggtransfn => 'range_agg_transfn',
+  aggfinalfn => 'range_agg_finalfn', aggfinalextra => 't',
+  aggtranstype => 'internal' },
+{ aggfnoid => 'range_agg(int4range,bool)', aggtransfn => 'int4range_agg_transfn',
+  aggfinalfn => 'int4range_agg_finalfn', aggfinalextra => 't',
+  aggtranstype => 'internal' },
+{ aggfnoid => 'range_agg(int8range,bool)', aggtransfn => 'int8range_agg_transfn',
+  aggfinalfn => 'int8range_agg_finalfn', aggfinalextra => 't',
+  aggtranstype => 'internal' },
+{ aggfnoid => 'range_agg(numrange,bool)', aggtransfn => 'numrange_agg_transfn',
+  aggfinalfn => 'numrange_agg_finalfn', aggfinalextra => 't',
+  aggtranstype => 'internal' },
+{ aggfnoid => 'range_agg(tsrange,bool)', aggtransfn => 'tsrange_agg_transfn',
+  aggfinalfn => 'tsrange_agg_finalfn', aggfinalextra => 't',
+  aggtranstype => 'internal' },
+{ aggfnoid => 'range_agg(tstzrange,bool)', aggtransfn => 'tstzrange_agg_transfn',
+  aggfinalfn => 'tstzrange_agg_finalfn', aggfinalextra => 't',
+  aggtranstype => 'internal' },
+{ aggfnoid => 'range_agg(daterange,bool)', aggtransfn => 'daterange_agg_transfn',
+  aggfinalfn => 'daterange_agg_finalfn', aggfinalextra => 't',
+  aggtranstype => 'internal' },
+{ aggfnoid => 'range_agg(int4range,bool,bool)', aggtransfn => 'int4rangebb_agg_transfn',
+  aggfinalfn => 'int4rangebb_agg_finalfn', aggfinalextra => 't',
+  aggtranstype => 'internal' },
+{ aggfnoid => 'range_agg(int8range,bool,bool)', aggtransfn => 'int8rangebb_agg_transfn',
+  aggfinalfn => 'int8rangebb_agg_finalfn', aggfinalextra => 't',
+  aggtranstype => 'internal' },
+{ aggfnoid => 'range_agg(numrange,bool,bool)', aggtransfn => 'numrangebb_agg_transfn',
+  aggfinalfn => 'numrangebb_agg_finalfn', aggfinalextra => 't',
+  aggtranstype => 'internal' },
+{ aggfnoid => 'range_agg(tsrange,bool,bool)', aggtransfn => 'tsrangebb_agg_transfn',
+  aggfinalfn => 'tsrangebb_agg_finalfn', aggfinalextra => 't',
+  aggtranstype => 'internal' },
+{ aggfnoid => 'range_agg(tstzrange,bool,bool)', aggtransfn => 'tstzrangebb_agg_transfn',
+  aggfinalfn => 'tstzrangebb_agg_finalfn', aggfinalextra => 't',
+  aggtranstype => 'internal' },
+{ aggfnoid => 'range_agg(daterange,bool,bool)', aggtransfn => 'daterangebb_agg_transfn',
+  aggfinalfn => 'daterangebb_agg_finalfn', aggfinalextra => 't',
+  aggtranstype => 'internal' },
+
 # ordered-set and hypothetical-set aggregates
 { aggfnoid => 'percentile_disc(float8,anyelement)', aggkind => 'o',
   aggnumdirectargs => '1', aggtransfn => 'ordered_set_transition',
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 87335248a0..249220c8a2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -9538,6 +9538,144 @@
 { oid => '3869',
   proname => 'range_minus', prorettype => 'anyrange',
   proargtypes => 'anyrange anyrange', prosrc => 'range_minus' },
+{ oid => '8000', descr => 'aggregate transition function',
+  proname => 'range_agg_transfn', proisstrict => 'f', prorettype => 'internal',
+  proargtypes => 'internal anyrange', prosrc => 'range_agg_transfn' },
+{ oid => '8001', descr => 'aggregate final function',
+  proname => 'range_agg_finalfn', proisstrict => 'f', prorettype => 'anyrange',
+  proargtypes => 'internal anyrange', prosrc => 'range_agg_finalfn' },
+{ oid => '8002', descr => 'combinate aggregate input into a range',
+  proname => 'range_agg', prokind => 'a', proisstrict => 'f',
+  prorettype => 'anyrange', proargtypes => 'anyrange',
+  prosrc => 'aggregate_dummy' },
+
+# Making 2- and 3-param range_agg polymorphic is difficult
+# because it would take an anyrange and return an anyrange[],
+# which doesn't exist.
+# As a workaround we define separate functions for each built-in range type.
+# This is what causes the mess in src/test/regress/expected/opr_sanity.out.
+{ oid => '8003', descr => 'aggregate transition function',
+  proname => 'int4range_agg_transfn', proisstrict => 'f', prorettype => 'internal',
+  proargtypes => 'internal int4range bool', prosrc => 'range_agg_transfn' },
+{ oid => '8004', descr => 'aggregate final function',
+  proname => 'int4range_agg_finalfn', proisstrict => 'f', prorettype => '_int4range',
+  proargtypes => 'internal int4range bool', prosrc => 'range_agg_finalfn' },
+{ oid => '8005', descr => 'combinate aggregate input into a range',
+  proname => 'range_agg', prokind => 'a', proisstrict => 'f',
+  prorettype => '_int4range', proargtypes => 'int4range bool',
+  prosrc => 'aggregate_dummy' },
+{ oid => '8006', descr => 'aggregate transition function',
+  proname => 'int8range_agg_transfn', proisstrict => 'f', prorettype => 'internal',
+  proargtypes => 'internal int8range bool', prosrc => 'range_agg_transfn' },
+{ oid => '8007', descr => 'aggregate final function',
+  proname => 'int8range_agg_finalfn', proisstrict => 'f', prorettype => '_int8range',
+  proargtypes => 'internal int8range bool', prosrc => 'range_agg_finalfn' },
+{ oid => '8008', descr => 'combinate aggregate input into a range',
+  proname => 'range_agg', prokind => 'a', proisstrict => 'f',
+  prorettype => '_int8range', proargtypes => 'int8range bool',
+  prosrc => 'aggregate_dummy' },
+{ oid => '8009', descr => 'aggregate transition function',
+  proname => 'numrange_agg_transfn', proisstrict => 'f', prorettype => 'internal',
+  proargtypes => 'internal numrange bool', prosrc => 'range_agg_transfn' },
+{ oid => '8010', descr => 'aggregate final function',
+  proname => 'numrange_agg_finalfn', proisstrict => 'f', prorettype => '_numrange',
+  proargtypes => 'internal numrange bool', prosrc => 'range_agg_finalfn' },
+{ oid => '8011', descr => 'combinate aggregate input into a range',
+  proname => 'range_agg', prokind => 'a', proisstrict => 'f',
+  prorettype => '_numrange', proargtypes => 'numrange bool',
+  prosrc => 'aggregate_dummy' },
+{ oid => '8012', descr => 'aggregate transition function',
+  proname => 'tsrange_agg_transfn', proisstrict => 'f', prorettype => 'internal',
+  proargtypes => 'internal tsrange bool', prosrc => 'range_agg_transfn' },
+{ oid => '8013', descr => 'aggregate final function',
+  proname => 'tsrange_agg_finalfn', proisstrict => 'f', prorettype => '_tsrange',
+  proargtypes => 'internal tsrange bool', prosrc => 'range_agg_finalfn' },
+{ oid => '8014', descr => 'combinate aggregate input into a range',
+  proname => 'range_agg', prokind => 'a', proisstrict => 'f',
+  prorettype => '_tsrange', proargtypes => 'tsrange bool',
+  prosrc => 'aggregate_dummy' },
+{ oid => '8015', descr => 'aggregate transition function',
+  proname => 'tstzrange_agg_transfn', proisstrict => 'f', prorettype => 'internal',
+  proargtypes => 'internal tstzrange bool', prosrc => 'range_agg_transfn' },
+{ oid => '8016', descr => 'aggregate final function',
+  proname => 'tstzrange_agg_finalfn', proisstrict => 'f', prorettype => '_tstzrange',
+  proargtypes => 'internal tstzrange bool', prosrc => 'range_agg_finalfn' },
+{ oid => '8017', descr => 'combinate aggregate input into a range',
+  proname => 'range_agg', prokind => 'a', proisstrict => 'f',
+  prorettype => '_tstzrange', proargtypes => 'tstzrange bool',
+  prosrc => 'aggregate_dummy' },
+{ oid => '8018', descr => 'aggregate transition function',
+  proname => 'daterange_agg_transfn', proisstrict => 'f', prorettype => 'internal',
+  proargtypes => 'internal daterange bool', prosrc => 'range_agg_transfn' },
+{ oid => '8019', descr => 'aggregate final function',
+  proname => 'daterange_agg_finalfn', proisstrict => 'f', prorettype => '_daterange',
+  proargtypes => 'internal daterange bool', prosrc => 'range_agg_finalfn' },
+{ oid => '8020', descr => 'combinate aggregate input into a range',
+  proname => 'range_agg', prokind => 'a', proisstrict => 'f',
+  prorettype => '_daterange', proargtypes => 'daterange bool',
+  prosrc => 'aggregate_dummy' },
+
+{ oid => '8021', descr => 'aggregate transition function',
+  proname => 'int4rangebb_agg_transfn', proisstrict => 'f', prorettype => 'internal',
+  proargtypes => 'internal int4range bool bool', prosrc => 'range_agg_transfn' },
+{ oid => '8022', descr => 'aggregate final function',
+  proname => 'int4rangebb_agg_finalfn', proisstrict => 'f', prorettype => '_int4range',
+  proargtypes => 'internal int4range bool bool', prosrc => 'range_agg_finalfn' },
+{ oid => '8023', descr => 'combinate aggregate input into a range',
+  proname => 'range_agg', prokind => 'a', proisstrict => 'f',
+  prorettype => '_int4range', proargtypes => 'int4range bool bool',
+  prosrc => 'aggregate_dummy' },
+{ oid => '8024', descr => 'aggregate transition function',
+  proname => 'int8rangebb_agg_transfn', proisstrict => 'f', prorettype => 'internal',
+  proargtypes => 'internal int8range bool bool', prosrc => 'range_agg_transfn' },
+{ oid => '8025', descr => 'aggregate final function',
+  proname => 'int8rangebb_agg_finalfn', proisstrict => 'f', prorettype => '_int8range',
+  proargtypes => 'internal int8range bool bool', prosrc => 'range_agg_finalfn' },
+{ oid => '8026', descr => 'combinate aggregate input into a range',
+  proname => 'range_agg', prokind => 'a', proisstrict => 'f',
+  prorettype => '_int8range', proargtypes => 'int8range bool bool',
+  prosrc => 'aggregate_dummy' },
+{ oid => '8027', descr => 'aggregate transition function',
+  proname => 'numrangebb_agg_transfn', proisstrict => 'f', prorettype => 'internal',
+  proargtypes => 'internal numrange bool bool', prosrc => 'range_agg_transfn' },
+{ oid => '8028', descr => 'aggregate final function',
+  proname => 'numrangebb_agg_finalfn', proisstrict => 'f', prorettype => '_numrange',
+  proargtypes => 'internal numrange bool bool', prosrc => 'range_agg_finalfn' },
+{ oid => '8029', descr => 'combinate aggregate input into a range',
+  proname => 'range_agg', prokind => 'a', proisstrict => 'f',
+  prorettype => '_numrange', proargtypes => 'numrange bool bool',
+  prosrc => 'aggregate_dummy' },
+{ oid => '8030', descr => 'aggregate transition function',
+  proname => 'tsrangebb_agg_transfn', proisstrict => 'f', prorettype => 'internal',
+  proargtypes => 'internal tsrange bool bool', prosrc => 'range_agg_transfn' },
+{ oid => '8031', descr => 'aggregate final function',
+  proname => 'tsrangebb_agg_finalfn', proisstrict => 'f', prorettype => '_tsrange',
+  proargtypes => 'internal tsrange bool bool', prosrc => 'range_agg_finalfn' },
+{ oid => '8032', descr => 'combinate aggregate input into a range',
+  proname => 'range_agg', prokind => 'a', proisstrict => 'f',
+  prorettype => '_tsrange', proargtypes => 'tsrange bool bool',
+  prosrc => 'aggregate_dummy' },
+{ oid => '8033', descr => 'aggregate transition function',
+  proname => 'tstzrangebb_agg_transfn', proisstrict => 'f', prorettype => 'internal',
+  proargtypes => 'internal tstzrange bool bool', prosrc => 'range_agg_transfn' },
+{ oid => '8034', descr => 'aggregate final function',
+  proname => 'tstzrangebb_agg_finalfn', proisstrict => 'f', prorettype => '_tstzrange',
+  proargtypes => 'internal tstzrange bool bool', prosrc => 'range_agg_finalfn' },
+{ oid => '8035', descr => 'combinate aggregate input into a range',
+  proname => 'range_agg', prokind => 'a', proisstrict => 'f',
+  prorettype => '_tstzrange', proargtypes => 'tstzrange bool bool',
+  prosrc => 'aggregate_dummy' },
+{ oid => '8036', descr => 'aggregate transition function',
+  proname => 'daterangebb_agg_transfn', proisstrict => 'f', prorettype => 'internal',
+  proargtypes => 'internal daterange bool bool', prosrc => 'range_agg_transfn' },
+{ oid => '8037', descr => 'aggregate final function',
+  proname => 'daterangebb_agg_finalfn', proisstrict => 'f', prorettype => '_daterange',
+  proargtypes => 'internal daterange bool bool', prosrc => 'range_agg_finalfn' },
+{ oid => '8038', descr => 'combinate aggregate input into a range',
+  proname => 'range_agg', prokind => 'a', proisstrict => 'f',
+  prorettype => '_daterange', proargtypes => 'daterange bool bool',
+  prosrc => 'aggregate_dummy' },
+
 { oid => '3870', descr => 'less-equal-greater',
   proname => 'range_cmp', prorettype => 'int4',
   proargtypes => 'anyrange anyrange', prosrc => 'range_cmp' },
diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out
index 85af36ee5b..a2cd607b31 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -168,9 +168,105 @@ WHERE p1.oid < p2.oid AND
      p1.proretset != p2.proretset OR
      p1.provolatile != p2.provolatile OR
      p1.pronargs != p2.pronargs);
- oid | proname | oid | proname 
------+---------+-----+---------
-(0 rows)
+ oid  |        proname        | oid  |         proname         
+------+-----------------------+------+-------------------------
+ 8019 | daterange_agg_finalfn | 8028 | numrangebb_agg_finalfn
+ 8019 | daterange_agg_finalfn | 8037 | daterangebb_agg_finalfn
+ 8019 | daterange_agg_finalfn | 8034 | tstzrangebb_agg_finalfn
+ 8019 | daterange_agg_finalfn | 8031 | tsrangebb_agg_finalfn
+ 8019 | daterange_agg_finalfn | 8025 | int8rangebb_agg_finalfn
+ 8019 | daterange_agg_finalfn | 8022 | int4rangebb_agg_finalfn
+ 8016 | tstzrange_agg_finalfn | 8028 | numrangebb_agg_finalfn
+ 8016 | tstzrange_agg_finalfn | 8037 | daterangebb_agg_finalfn
+ 8016 | tstzrange_agg_finalfn | 8034 | tstzrangebb_agg_finalfn
+ 8016 | tstzrange_agg_finalfn | 8031 | tsrangebb_agg_finalfn
+ 8016 | tstzrange_agg_finalfn | 8025 | int8rangebb_agg_finalfn
+ 8016 | tstzrange_agg_finalfn | 8022 | int4rangebb_agg_finalfn
+ 8013 | tsrange_agg_finalfn   | 8028 | numrangebb_agg_finalfn
+ 8013 | tsrange_agg_finalfn   | 8037 | daterangebb_agg_finalfn
+ 8013 | tsrange_agg_finalfn   | 8034 | tstzrangebb_agg_finalfn
+ 8013 | tsrange_agg_finalfn   | 8031 | tsrangebb_agg_finalfn
+ 8013 | tsrange_agg_finalfn   | 8025 | int8rangebb_agg_finalfn
+ 8013 | tsrange_agg_finalfn   | 8022 | int4rangebb_agg_finalfn
+ 8010 | numrange_agg_finalfn  | 8028 | numrangebb_agg_finalfn
+ 8010 | numrange_agg_finalfn  | 8037 | daterangebb_agg_finalfn
+ 8010 | numrange_agg_finalfn  | 8034 | tstzrangebb_agg_finalfn
+ 8010 | numrange_agg_finalfn  | 8031 | tsrangebb_agg_finalfn
+ 8010 | numrange_agg_finalfn  | 8025 | int8rangebb_agg_finalfn
+ 8010 | numrange_agg_finalfn  | 8022 | int4rangebb_agg_finalfn
+ 8007 | int8range_agg_finalfn | 8028 | numrangebb_agg_finalfn
+ 8007 | int8range_agg_finalfn | 8037 | daterangebb_agg_finalfn
+ 8007 | int8range_agg_finalfn | 8034 | tstzrangebb_agg_finalfn
+ 8007 | int8range_agg_finalfn | 8031 | tsrangebb_agg_finalfn
+ 8007 | int8range_agg_finalfn | 8025 | int8rangebb_agg_finalfn
+ 8007 | int8range_agg_finalfn | 8022 | int4rangebb_agg_finalfn
+ 8004 | int4range_agg_finalfn | 8028 | numrangebb_agg_finalfn
+ 8004 | int4range_agg_finalfn | 8037 | daterangebb_agg_finalfn
+ 8004 | int4range_agg_finalfn | 8034 | tstzrangebb_agg_finalfn
+ 8004 | int4range_agg_finalfn | 8031 | tsrangebb_agg_finalfn
+ 8004 | int4range_agg_finalfn | 8025 | int8rangebb_agg_finalfn
+ 8004 | int4range_agg_finalfn | 8022 | int4rangebb_agg_finalfn
+ 8001 | range_agg_finalfn     | 8028 | numrangebb_agg_finalfn
+ 8001 | range_agg_finalfn     | 8037 | daterangebb_agg_finalfn
+ 8001 | range_agg_finalfn     | 8034 | tstzrangebb_agg_finalfn
+ 8001 | range_agg_finalfn     | 8031 | tsrangebb_agg_finalfn
+ 8001 | range_agg_finalfn     | 8025 | int8rangebb_agg_finalfn
+ 8001 | range_agg_finalfn     | 8022 | int4rangebb_agg_finalfn
+ 8001 | range_agg_finalfn     | 8019 | daterange_agg_finalfn
+ 8001 | range_agg_finalfn     | 8016 | tstzrange_agg_finalfn
+ 8001 | range_agg_finalfn     | 8013 | tsrange_agg_finalfn
+ 8001 | range_agg_finalfn     | 8010 | numrange_agg_finalfn
+ 8001 | range_agg_finalfn     | 8007 | int8range_agg_finalfn
+ 8001 | range_agg_finalfn     | 8004 | int4range_agg_finalfn
+ 8003 | int4range_agg_transfn | 8036 | daterangebb_agg_transfn
+ 8003 | int4range_agg_transfn | 8027 | numrangebb_agg_transfn
+ 8003 | int4range_agg_transfn | 8030 | tsrangebb_agg_transfn
+ 8003 | int4range_agg_transfn | 8021 | int4rangebb_agg_transfn
+ 8003 | int4range_agg_transfn | 8024 | int8rangebb_agg_transfn
+ 8003 | int4range_agg_transfn | 8033 | tstzrangebb_agg_transfn
+ 8018 | daterange_agg_transfn | 8036 | daterangebb_agg_transfn
+ 8018 | daterange_agg_transfn | 8027 | numrangebb_agg_transfn
+ 8018 | daterange_agg_transfn | 8030 | tsrangebb_agg_transfn
+ 8018 | daterange_agg_transfn | 8021 | int4rangebb_agg_transfn
+ 8018 | daterange_agg_transfn | 8024 | int8rangebb_agg_transfn
+ 8018 | daterange_agg_transfn | 8033 | tstzrangebb_agg_transfn
+ 8015 | tstzrange_agg_transfn | 8036 | daterangebb_agg_transfn
+ 8015 | tstzrange_agg_transfn | 8027 | numrangebb_agg_transfn
+ 8015 | tstzrange_agg_transfn | 8030 | tsrangebb_agg_transfn
+ 8015 | tstzrange_agg_transfn | 8021 | int4rangebb_agg_transfn
+ 8015 | tstzrange_agg_transfn | 8024 | int8rangebb_agg_transfn
+ 8015 | tstzrange_agg_transfn | 8033 | tstzrangebb_agg_transfn
+ 8006 | int8range_agg_transfn | 8036 | daterangebb_agg_transfn
+ 8006 | int8range_agg_transfn | 8027 | numrangebb_agg_transfn
+ 8006 | int8range_agg_transfn | 8030 | tsrangebb_agg_transfn
+ 8006 | int8range_agg_transfn | 8021 | int4rangebb_agg_transfn
+ 8006 | int8range_agg_transfn | 8024 | int8rangebb_agg_transfn
+ 8006 | int8range_agg_transfn | 8033 | tstzrangebb_agg_transfn
+ 8012 | tsrange_agg_transfn   | 8036 | daterangebb_agg_transfn
+ 8012 | tsrange_agg_transfn   | 8027 | numrangebb_agg_transfn
+ 8012 | tsrange_agg_transfn   | 8030 | tsrangebb_agg_transfn
+ 8012 | tsrange_agg_transfn   | 8021 | int4rangebb_agg_transfn
+ 8012 | tsrange_agg_transfn   | 8024 | int8rangebb_agg_transfn
+ 8012 | tsrange_agg_transfn   | 8033 | tstzrangebb_agg_transfn
+ 8000 | range_agg_transfn     | 8036 | daterangebb_agg_transfn
+ 8000 | range_agg_transfn     | 8027 | numrangebb_agg_transfn
+ 8000 | range_agg_transfn     | 8030 | tsrangebb_agg_transfn
+ 8000 | range_agg_transfn     | 8003 | int4range_agg_transfn
+ 8000 | range_agg_transfn     | 8021 | int4rangebb_agg_transfn
+ 8000 | range_agg_transfn     | 8018 | daterange_agg_transfn
+ 8000 | range_agg_transfn     | 8015 | tstzrange_agg_transfn
+ 8000 | range_agg_transfn     | 8024 | int8rangebb_agg_transfn
+ 8000 | range_agg_transfn     | 8006 | int8range_agg_transfn
+ 8000 | range_agg_transfn     | 8012 | tsrange_agg_transfn
+ 8000 | range_agg_transfn     | 8009 | numrange_agg_transfn
+ 8000 | range_agg_transfn     | 8033 | tstzrangebb_agg_transfn
+ 8009 | numrange_agg_transfn  | 8036 | daterangebb_agg_transfn
+ 8009 | numrange_agg_transfn  | 8027 | numrangebb_agg_transfn
+ 8009 | numrange_agg_transfn  | 8030 | tsrangebb_agg_transfn
+ 8009 | numrange_agg_transfn  | 8021 | int4rangebb_agg_transfn
+ 8009 | numrange_agg_transfn  | 8024 | int8rangebb_agg_transfn
+ 8009 | numrange_agg_transfn  | 8033 | tstzrangebb_agg_transfn
+(96 rows)
 
 -- Look for uses of different type OIDs in the argument/result type fields
 -- for different aliases of the same built-in function.
@@ -196,7 +292,28 @@ ORDER BY 1, 2;
 ------------+------------
          25 |       1043
        1114 |       1184
-(2 rows)
+       3831 |       3905
+       3831 |       3907
+       3831 |       3909
+       3831 |       3911
+       3831 |       3913
+       3831 |       3927
+       3905 |       3907
+       3905 |       3909
+       3905 |       3911
+       3905 |       3913
+       3905 |       3927
+       3907 |       3909
+       3907 |       3911
+       3907 |       3913
+       3907 |       3927
+       3909 |       3911
+       3909 |       3913
+       3909 |       3927
+       3911 |       3913
+       3911 |       3927
+       3913 |       3927
+(23 rows)
 
 SELECT DISTINCT p1.proargtypes[0], p2.proargtypes[0]
 FROM pg_proc AS p1, pg_proc AS p2
@@ -231,7 +348,28 @@ ORDER BY 1, 2;
           23 |          28
         1114 |        1184
         1560 |        1562
-(3 rows)
+        3831 |        3904
+        3831 |        3906
+        3831 |        3908
+        3831 |        3910
+        3831 |        3912
+        3831 |        3926
+        3904 |        3906
+        3904 |        3908
+        3904 |        3910
+        3904 |        3912
+        3904 |        3926
+        3906 |        3908
+        3906 |        3910
+        3906 |        3912
+        3906 |        3926
+        3908 |        3910
+        3908 |        3912
+        3908 |        3926
+        3910 |        3912
+        3910 |        3926
+        3912 |        3926
+(24 rows)
 
 SELECT DISTINCT p1.proargtypes[2], p2.proargtypes[2]
 FROM pg_proc AS p1, pg_proc AS p2
@@ -1716,10 +1854,58 @@ WHERE p1.oid < p2.oid AND p1.proname = p2.proname AND
     p1.prokind = 'a' AND p2.prokind = 'a' AND
     array_dims(p1.proargtypes) != array_dims(p2.proargtypes)
 ORDER BY 1;
-     oid      |   oid   
---------------+---------
- count("any") | count()
-(1 row)
+             oid              |                 oid                  
+------------------------------+--------------------------------------
+ count("any")                 | count()
+ range_agg(anyrange)          | range_agg(daterange,boolean,boolean)
+ range_agg(anyrange)          | range_agg(tstzrange,boolean,boolean)
+ range_agg(anyrange)          | range_agg(tsrange,boolean,boolean)
+ range_agg(anyrange)          | range_agg(numrange,boolean,boolean)
+ range_agg(anyrange)          | range_agg(int8range,boolean,boolean)
+ range_agg(anyrange)          | range_agg(int4range,boolean,boolean)
+ range_agg(anyrange)          | range_agg(daterange,boolean)
+ range_agg(anyrange)          | range_agg(tstzrange,boolean)
+ range_agg(anyrange)          | range_agg(tsrange,boolean)
+ range_agg(anyrange)          | range_agg(numrange,boolean)
+ range_agg(anyrange)          | range_agg(int8range,boolean)
+ range_agg(anyrange)          | range_agg(int4range,boolean)
+ range_agg(int4range,boolean) | range_agg(daterange,boolean,boolean)
+ range_agg(int4range,boolean) | range_agg(tstzrange,boolean,boolean)
+ range_agg(int4range,boolean) | range_agg(tsrange,boolean,boolean)
+ range_agg(int4range,boolean) | range_agg(numrange,boolean,boolean)
+ range_agg(int4range,boolean) | range_agg(int8range,boolean,boolean)
+ range_agg(int4range,boolean) | range_agg(int4range,boolean,boolean)
+ range_agg(int8range,boolean) | range_agg(daterange,boolean,boolean)
+ range_agg(int8range,boolean) | range_agg(tstzrange,boolean,boolean)
+ range_agg(int8range,boolean) | range_agg(tsrange,boolean,boolean)
+ range_agg(int8range,boolean) | range_agg(numrange,boolean,boolean)
+ range_agg(int8range,boolean) | range_agg(int8range,boolean,boolean)
+ range_agg(int8range,boolean) | range_agg(int4range,boolean,boolean)
+ range_agg(numrange,boolean)  | range_agg(daterange,boolean,boolean)
+ range_agg(numrange,boolean)  | range_agg(tstzrange,boolean,boolean)
+ range_agg(numrange,boolean)  | range_agg(tsrange,boolean,boolean)
+ range_agg(numrange,boolean)  | range_agg(numrange,boolean,boolean)
+ range_agg(numrange,boolean)  | range_agg(int8range,boolean,boolean)
+ range_agg(numrange,boolean)  | range_agg(int4range,boolean,boolean)
+ range_agg(tsrange,boolean)   | range_agg(daterange,boolean,boolean)
+ range_agg(tsrange,boolean)   | range_agg(tstzrange,boolean,boolean)
+ range_agg(tsrange,boolean)   | range_agg(tsrange,boolean,boolean)
+ range_agg(tsrange,boolean)   | range_agg(numrange,boolean,boolean)
+ range_agg(tsrange,boolean)   | range_agg(int8range,boolean,boolean)
+ range_agg(tsrange,boolean)   | range_agg(int4range,boolean,boolean)
+ range_agg(tstzrange,boolean) | range_agg(daterange,boolean,boolean)
+ range_agg(tstzrange,boolean) | range_agg(tstzrange,boolean,boolean)
+ range_agg(tstzrange,boolean) | range_agg(tsrange,boolean,boolean)
+ range_agg(tstzrange,boolean) | range_agg(numrange,boolean,boolean)
+ range_agg(tstzrange,boolean) | range_agg(int8range,boolean,boolean)
+ range_agg(tstzrange,boolean) | range_agg(int4range,boolean,boolean)
+ range_agg(daterange,boolean) | range_agg(daterange,boolean,boolean)
+ range_agg(daterange,boolean) | range_agg(tstzrange,boolean,boolean)
+ range_agg(daterange,boolean) | range_agg(tsrange,boolean,boolean)
+ range_agg(daterange,boolean) | range_agg(numrange,boolean,boolean)
+ range_agg(daterange,boolean) | range_agg(int8range,boolean,boolean)
+ range_agg(daterange,boolean) | range_agg(int4range,boolean,boolean)
+(49 rows)
 
 -- For the same reason, built-in aggregates with default arguments are no good.
 SELECT oid, proname
diff --git a/src/test/regress/expected/rangetypes.out b/src/test/regress/expected/rangetypes.out
index accf1e0d9e..703bc41491 100644
--- a/src/test/regress/expected/rangetypes.out
+++ b/src/test/regress/expected/rangetypes.out
@@ -1407,3 +1407,281 @@ create function table_fail(i anyelement) returns table(i anyelement, r anyrange)
   as $$ select $1, '[1,10]' $$ language sql;
 ERROR:  cannot determine result data type
 DETAIL:  A function returning "anyrange" must have at least one "anyrange" argument.
+--
+-- range_agg function
+--
+create table reservations ( room_id integer not null, booked_during daterange );
+insert into reservations values
+-- 1: has a meets and a gap
+(1, daterange('2018-07-01', '2018-07-07')),
+(1, daterange('2018-07-07', '2018-07-14')),
+(1, daterange('2018-07-20', '2018-07-22')),
+-- 2: just a single row
+(2, daterange('2018-07-01', '2018-07-03')),
+-- 3: one null range
+(3, NULL),
+-- 4: two null ranges
+(4, NULL),
+(4, NULL),
+-- 5: a null range and a non-null range
+(5, NULL),
+(5, daterange('2018-07-01', '2018-07-03')),
+-- 6: has overlap
+(6, daterange('2018-07-01', '2018-07-07')),
+(6, daterange('2018-07-05', '2018-07-10')),
+-- 7: two ranges that meet: no gap or overlap
+(7, daterange('2018-07-01', '2018-07-07')),
+(7, daterange('2018-07-07', '2018-07-14'))
+;
+-- range_agg with 1 arg:
+-- Forbidding gaps and overlaps:
+SELECT  room_id, range_agg(booked_during)
+FROM    reservations
+WHERE   room_id = 1
+GROUP BY room_id
+ORDER BY room_id;
+ERROR:  range_agg: gap detected between lastRange and currentRange
+SELECT  room_id, range_agg(booked_during)
+FROM    reservations
+WHERE   room_id = 6
+GROUP BY room_id
+ORDER BY room_id;
+ERROR:  range_agg: overlap detected between lastRange and currentRange
+SELECT  room_id, range_agg(booked_during)
+FROM    reservations
+WHERE   room_id NOT IN (1, 6)
+GROUP BY room_id
+ORDER BY room_id;
+ room_id |        range_agg        
+---------+-------------------------
+       2 | [07-01-2018,07-03-2018)
+       3 | 
+       4 | 
+       5 | [07-01-2018,07-03-2018)
+       7 | [07-01-2018,07-14-2018)
+(5 rows)
+
+-- Obeying discrete base types:
+SELECT	range_agg(r)
+FROM		(VALUES
+  (int4range( 0,  9, '[]')),
+  (int4range(10, 19, '[]'))
+) t(r);
+ range_agg 
+-----------
+ [0,20)
+(1 row)
+
+-- range_agg with 2 args:
+-- Forbidding gaps (and overlaps):
+SELECT  room_id, range_agg(booked_during, false)
+FROM    reservations
+WHERE   room_id = 1
+GROUP BY room_id;
+ERROR:  range_agg: gap detected between lastRange and currentRange
+SELECT  room_id, range_agg(booked_during, false)
+FROM    reservations
+WHERE   room_id = 6
+GROUP BY room_id;
+ERROR:  range_agg: overlap detected between lastRange and currentRange
+SELECT  room_id, range_agg(booked_during, false)
+FROM    reservations
+WHERE   room_id NOT IN (1, 6)
+GROUP BY room_id;
+ room_id |          range_agg          
+---------+-----------------------------
+       3 | 
+       5 | {"[07-01-2018,07-03-2018)"}
+       4 | 
+       2 | {"[07-01-2018,07-03-2018)"}
+       7 | {"[07-01-2018,07-14-2018)"}
+(5 rows)
+
+-- Permitting gaps (but forbidding overlaps):
+SELECT  room_id, range_agg(booked_during, true)
+FROM    reservations
+WHERE   room_id = 1
+GROUP BY room_id;
+ room_id |                       range_agg                       
+---------+-------------------------------------------------------
+       1 | {"[07-01-2018,07-14-2018)","[07-20-2018,07-22-2018)"}
+(1 row)
+
+SELECT  room_id, range_agg(booked_during, true)
+FROM    reservations
+WHERE   room_id = 6
+GROUP BY room_id;
+ERROR:  range_agg: overlap detected between lastRange and currentRange
+SELECT  room_id, range_agg(booked_during, true)
+FROM    reservations
+WHERE   room_id NOT IN (1, 6)
+GROUP BY room_id;
+ room_id |          range_agg          
+---------+-----------------------------
+       3 | 
+       5 | {"[07-01-2018,07-03-2018)"}
+       4 | 
+       2 | {"[07-01-2018,07-03-2018)"}
+       7 | {"[07-01-2018,07-14-2018)"}
+(5 rows)
+
+-- range_agg with 3 args:
+-- Forbidding gaps and overlaps:
+SELECT  room_id, range_agg(booked_during, false, false)
+FROM    reservations
+WHERE   room_id = 1
+GROUP BY room_id;
+ERROR:  range_agg: gap detected between lastRange and currentRange
+SELECT  room_id, range_agg(booked_during, false, false)
+FROM    reservations
+WHERE   room_id = 6
+GROUP BY room_id;
+ERROR:  range_agg: overlap detected between lastRange and currentRange
+SELECT  room_id, range_agg(booked_during, false, false)
+FROM    reservations
+WHERE   room_id NOT IN (1, 6)
+GROUP BY room_id;
+ room_id |          range_agg          
+---------+-----------------------------
+       3 | 
+       5 | {"[07-01-2018,07-03-2018)"}
+       4 | 
+       2 | {"[07-01-2018,07-03-2018)"}
+       7 | {"[07-01-2018,07-14-2018)"}
+(5 rows)
+
+-- Forbidding gaps but permitting overlaps
+SELECT  room_id, range_agg(booked_during, false, true)
+FROM    reservations
+WHERE   room_id = 1
+GROUP BY room_id;
+ERROR:  range_agg: gap detected between lastRange and currentRange
+SELECT  room_id, range_agg(booked_during, false, true)
+FROM    reservations
+WHERE   room_id = 6
+GROUP BY room_id;
+ room_id |          range_agg          
+---------+-----------------------------
+       6 | {"[07-01-2018,07-10-2018)"}
+(1 row)
+
+SELECT  room_id, range_agg(booked_during, false, true)
+FROM    reservations
+WHERE   room_id NOT IN (1, 6)
+GROUP BY room_id;
+ room_id |          range_agg          
+---------+-----------------------------
+       3 | 
+       5 | {"[07-01-2018,07-03-2018)"}
+       4 | 
+       2 | {"[07-01-2018,07-03-2018)"}
+       7 | {"[07-01-2018,07-14-2018)"}
+(5 rows)
+
+-- Permitting gaps but forbidding overlaps
+SELECT  room_id, range_agg(booked_during, true, false)
+FROM    reservations
+WHERE   room_id = 1
+GROUP BY room_id;
+ room_id |                       range_agg                       
+---------+-------------------------------------------------------
+       1 | {"[07-01-2018,07-14-2018)","[07-20-2018,07-22-2018)"}
+(1 row)
+
+SELECT  room_id, range_agg(booked_during, true, false)
+FROM    reservations
+WHERE   room_id = 6
+GROUP BY room_id;
+ERROR:  range_agg: overlap detected between lastRange and currentRange
+SELECT  room_id, range_agg(booked_during, true, false)
+FROM    reservations
+WHERE   room_id NOT IN (1, 6)
+GROUP BY room_id;
+ room_id |          range_agg          
+---------+-----------------------------
+       3 | 
+       5 | {"[07-01-2018,07-03-2018)"}
+       4 | 
+       2 | {"[07-01-2018,07-03-2018)"}
+       7 | {"[07-01-2018,07-14-2018)"}
+(5 rows)
+
+-- Permitting gaps and overlaps:
+SELECT  room_id, range_agg(booked_during, true, true)
+FROM    reservations
+WHERE   room_id = 1
+GROUP BY room_id;
+ room_id |                       range_agg                       
+---------+-------------------------------------------------------
+       1 | {"[07-01-2018,07-14-2018)","[07-20-2018,07-22-2018)"}
+(1 row)
+
+SELECT  room_id, range_agg(booked_during, true, true)
+FROM    reservations
+WHERE   room_id = 6
+GROUP BY room_id;
+ room_id |          range_agg          
+---------+-----------------------------
+       6 | {"[07-01-2018,07-10-2018)"}
+(1 row)
+
+SELECT  room_id, range_agg(booked_during, true, true)
+FROM    reservations
+WHERE   room_id NOT IN (1, 6)
+GROUP BY room_id;
+ room_id |          range_agg          
+---------+-----------------------------
+       3 | 
+       5 | {"[07-01-2018,07-03-2018)"}
+       4 | 
+       2 | {"[07-01-2018,07-03-2018)"}
+       7 | {"[07-01-2018,07-14-2018)"}
+(5 rows)
+
+-- Obeying discrete base types:
+SELECT	range_agg(r, false, false)
+FROM		(VALUES
+  (int4range( 0,  5, '[]')),
+  (int4range( 7,  9, '[]'))
+) t(r);
+ERROR:  range_agg: gap detected between lastRange and currentRange
+SELECT	range_agg(r, false, false)
+FROM		(VALUES
+  (int4range( 0,  5, '[]')),
+  (int4range( 5,  9, '[]'))
+) t(r);
+ERROR:  range_agg: overlap detected between lastRange and currentRange
+SELECT	range_agg(r, true, true)
+FROM		(VALUES
+  (int4range( 0,  9, '[]')),
+  (int4range(10, 15, '[]')),
+  (int4range(20, 26, '[]')),
+  (int4range(26, 29, '[]'))
+) t(r);
+      range_agg       
+----------------------
+ {"[0,16)","[20,30)"}
+(1 row)
+
+-- It combines with UNNEST
+-- to implement the temporal database "coalesce" function
+-- (see Snodgrass 6.5.2):
+SELECT  room_id, t2.booked_during
+FROM    (
+          SELECT  room_id, range_agg(booked_during, true, true) AS booked_during
+          FROM    reservations
+          GROUP BY room_id
+        ) AS t1,
+        UNNEST(t1.booked_during) AS t2(booked_during)
+ORDER BY room_id, booked_during
+;
+ room_id |      booked_during      
+---------+-------------------------
+       1 | [07-01-2018,07-14-2018)
+       1 | [07-20-2018,07-22-2018)
+       2 | [07-01-2018,07-03-2018)
+       5 | [07-01-2018,07-03-2018)
+       6 | [07-01-2018,07-10-2018)
+       7 | [07-01-2018,07-14-2018)
+(6 rows)
+
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index 8ff0da185e..395bfeb75d 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -170,6 +170,7 @@ quad_poly_tbl|t
 radix_text_tbl|t
 ramp|f
 real_city|f
+reservations|f
 road|t
 shighway|t
 slow_emp4000|f
diff --git a/src/test/regress/sql/rangetypes.sql b/src/test/regress/sql/rangetypes.sql
index 55638a85ee..9d1be6f3ca 100644
--- a/src/test/regress/sql/rangetypes.sql
+++ b/src/test/regress/sql/rangetypes.sql
@@ -503,3 +503,192 @@ create function inoutparam_fail(inout i anyelement, out r anyrange)
 --should fail
 create function table_fail(i anyelement) returns table(i anyelement, r anyrange)
   as $$ select $1, '[1,10]' $$ language sql;
+
+--
+-- range_agg function
+--
+create table reservations ( room_id integer not null, booked_during daterange );
+insert into reservations values
+-- 1: has a meets and a gap
+(1, daterange('2018-07-01', '2018-07-07')),
+(1, daterange('2018-07-07', '2018-07-14')),
+(1, daterange('2018-07-20', '2018-07-22')),
+-- 2: just a single row
+(2, daterange('2018-07-01', '2018-07-03')),
+-- 3: one null range
+(3, NULL),
+-- 4: two null ranges
+(4, NULL),
+(4, NULL),
+-- 5: a null range and a non-null range
+(5, NULL),
+(5, daterange('2018-07-01', '2018-07-03')),
+-- 6: has overlap
+(6, daterange('2018-07-01', '2018-07-07')),
+(6, daterange('2018-07-05', '2018-07-10')),
+-- 7: two ranges that meet: no gap or overlap
+(7, daterange('2018-07-01', '2018-07-07')),
+(7, daterange('2018-07-07', '2018-07-14'))
+;
+
+-- range_agg with 1 arg:
+
+-- Forbidding gaps and overlaps:
+SELECT  room_id, range_agg(booked_during)
+FROM    reservations
+WHERE   room_id = 1
+GROUP BY room_id
+ORDER BY room_id;
+
+SELECT  room_id, range_agg(booked_during)
+FROM    reservations
+WHERE   room_id = 6
+GROUP BY room_id
+ORDER BY room_id;
+
+SELECT  room_id, range_agg(booked_during)
+FROM    reservations
+WHERE   room_id NOT IN (1, 6)
+GROUP BY room_id
+ORDER BY room_id;
+
+-- Obeying discrete base types:
+SELECT	range_agg(r)
+FROM		(VALUES
+  (int4range( 0,  9, '[]')),
+  (int4range(10, 19, '[]'))
+) t(r);
+
+-- range_agg with 2 args:
+
+-- Forbidding gaps (and overlaps):
+SELECT  room_id, range_agg(booked_during, false)
+FROM    reservations
+WHERE   room_id = 1
+GROUP BY room_id;
+
+SELECT  room_id, range_agg(booked_during, false)
+FROM    reservations
+WHERE   room_id = 6
+GROUP BY room_id;
+
+SELECT  room_id, range_agg(booked_during, false)
+FROM    reservations
+WHERE   room_id NOT IN (1, 6)
+GROUP BY room_id;
+
+-- Permitting gaps (but forbidding overlaps):
+SELECT  room_id, range_agg(booked_during, true)
+FROM    reservations
+WHERE   room_id = 1
+GROUP BY room_id;
+
+SELECT  room_id, range_agg(booked_during, true)
+FROM    reservations
+WHERE   room_id = 6
+GROUP BY room_id;
+
+SELECT  room_id, range_agg(booked_during, true)
+FROM    reservations
+WHERE   room_id NOT IN (1, 6)
+GROUP BY room_id;
+
+-- range_agg with 3 args:
+
+-- Forbidding gaps and overlaps:
+SELECT  room_id, range_agg(booked_during, false, false)
+FROM    reservations
+WHERE   room_id = 1
+GROUP BY room_id;
+
+SELECT  room_id, range_agg(booked_during, false, false)
+FROM    reservations
+WHERE   room_id = 6
+GROUP BY room_id;
+
+SELECT  room_id, range_agg(booked_during, false, false)
+FROM    reservations
+WHERE   room_id NOT IN (1, 6)
+GROUP BY room_id;
+
+-- Forbidding gaps but permitting overlaps
+SELECT  room_id, range_agg(booked_during, false, true)
+FROM    reservations
+WHERE   room_id = 1
+GROUP BY room_id;
+
+SELECT  room_id, range_agg(booked_during, false, true)
+FROM    reservations
+WHERE   room_id = 6
+GROUP BY room_id;
+
+SELECT  room_id, range_agg(booked_during, false, true)
+FROM    reservations
+WHERE   room_id NOT IN (1, 6)
+GROUP BY room_id;
+
+-- Permitting gaps but forbidding overlaps
+SELECT  room_id, range_agg(booked_during, true, false)
+FROM    reservations
+WHERE   room_id = 1
+GROUP BY room_id;
+
+SELECT  room_id, range_agg(booked_during, true, false)
+FROM    reservations
+WHERE   room_id = 6
+GROUP BY room_id;
+
+SELECT  room_id, range_agg(booked_during, true, false)
+FROM    reservations
+WHERE   room_id NOT IN (1, 6)
+GROUP BY room_id;
+
+-- Permitting gaps and overlaps:
+SELECT  room_id, range_agg(booked_during, true, true)
+FROM    reservations
+WHERE   room_id = 1
+GROUP BY room_id;
+
+SELECT  room_id, range_agg(booked_during, true, true)
+FROM    reservations
+WHERE   room_id = 6
+GROUP BY room_id;
+
+SELECT  room_id, range_agg(booked_during, true, true)
+FROM    reservations
+WHERE   room_id NOT IN (1, 6)
+GROUP BY room_id;
+
+-- Obeying discrete base types:
+SELECT	range_agg(r, false, false)
+FROM		(VALUES
+  (int4range( 0,  5, '[]')),
+  (int4range( 7,  9, '[]'))
+) t(r);
+
+SELECT	range_agg(r, false, false)
+FROM		(VALUES
+  (int4range( 0,  5, '[]')),
+  (int4range( 5,  9, '[]'))
+) t(r);
+
+SELECT	range_agg(r, true, true)
+FROM		(VALUES
+  (int4range( 0,  9, '[]')),
+  (int4range(10, 15, '[]')),
+  (int4range(20, 26, '[]')),
+  (int4range(26, 29, '[]'))
+) t(r);
+
+-- It combines with UNNEST
+-- to implement the temporal database "coalesce" function
+-- (see Snodgrass 6.5.2):
+SELECT  room_id, t2.booked_during
+FROM    (
+          SELECT  room_id, range_agg(booked_during, true, true) AS booked_during
+          FROM    reservations
+          GROUP BY room_id
+        ) AS t1,
+        UNNEST(t1.booked_during) AS t2(booked_during)
+ORDER BY room_id, booked_during
+;
#10Jeff Davis
pgsql@j-davis.com
In reply to: Paul Jungwirth (#1)
Re: range_agg

On Fri, 2019-05-03 at 15:56 -0700, Paul Jungwirth wrote:

Hello,

I wrote an extension to add a range_agg function with similar
behavior
to existing *_agg functions, and I'm wondering if folks would like
to
have it in core? Here is the repo:
https://github.com/pjungwir/range_agg

This seems like a very useful extension, thank you.

For getting into core though, it should be a more complete set of
related operations. The patch is implicitly introducing the concept of
a "multirange" (in this case, an array of ranges), but it's not making
the concept whole.

What else should return a multirange? This patch implements the union-
agg of ranges, but we also might want an intersection-agg of ranges
(that is, the set of ranges that are subranges of every input). Given
that there are other options here, the name "range_agg" is too generic
to mean union-agg specifically.

What can we do with a multirange? A lot of range operators still make
sense, like "contains" or "overlaps"; but "adjacent" doesn't quite
work. What about new operations like inverting a multirange to get the
gaps?

Do we want to continue with the array-of-ranges implementation of a
multirange, or do we want a first-class multirange concept that might
eliminate the boilerplate around defining all of these operations?

If we have a more complete set of operators here, the flags for
handling overlapping ranges and gaps will be unnecessary.

Regards,
Jeff Davis

#11Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Paul A Jungwirth (#9)
Re: range_agg

I noticed that this patch has a // comment about it segfaulting. Did
you ever figure that out? Is the resulting code the one you intend as
final?

Did you make any inroads regarding Jeff Davis' suggestion about
implementing "multiranges"? I wonder if that's going to end up being a
Pandora's box.

Stylistically, the code does not match pgindent's choices very closely.
I can return a diff to a pgindented version of your v0002 for your
perusal, if it would be useful for you to learn its style. (A committer
would eventually run pgindent himself[1]No female committers yet ... is this 2019?, but it's good to have
submissions be at least close to the final form.) BTW, I suggest "git
format-patch -vN" to prepare files for submission.

[1]: No female committers yet ... is this 2019?

--
�lvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

#12Pavel Stehule
pavel.stehule@gmail.com
In reply to: Jeff Davis (#10)
Re: range_agg

Hi

út 2. 7. 2019 v 0:38 odesílatel Jeff Davis <pgsql@j-davis.com> napsal:

On Fri, 2019-05-03 at 15:56 -0700, Paul Jungwirth wrote:

Hello,

I wrote an extension to add a range_agg function with similar
behavior
to existing *_agg functions, and I'm wondering if folks would like
to
have it in core? Here is the repo:
https://github.com/pjungwir/range_agg

This seems like a very useful extension, thank you.

For getting into core though, it should be a more complete set of
related operations. The patch is implicitly introducing the concept of
a "multirange" (in this case, an array of ranges), but it's not making
the concept whole.

What else should return a multirange? This patch implements the union-
agg of ranges, but we also might want an intersection-agg of ranges
(that is, the set of ranges that are subranges of every input). Given
that there are other options here, the name "range_agg" is too generic
to mean union-agg specifically.

What can we do with a multirange? A lot of range operators still make
sense, like "contains" or "overlaps"; but "adjacent" doesn't quite
work. What about new operations like inverting a multirange to get the
gaps?

Do we want to continue with the array-of-ranges implementation of a
multirange, or do we want a first-class multirange concept that might
eliminate the boilerplate around defining all of these operations?

If we have a more complete set of operators here, the flags for
handling overlapping ranges and gaps will be unnecessary.

I think so scope of this patch is correct. Introduction of set of ranges
data type based on a array or not, should be different topic.

The question is naming - should be this agg function named "range_agg", and
multi range agg "multirange_agg"? Personally, I have not a problem with
range_agg, and I have not a problem so it is based on union operation. It
is true so only result of union can be implemented as range simply. When I
though about multi range result, then there are really large set of
possible algorithms how to do some operations over two, three values. So
personally, I am satisfied with scope of simple range_agg functions,
because I see a benefits, and I don't think so this implementation block
any more complex designs in the future. There is really big questions how
to implement multi range, and now I think so special data type will be
better than possible unordered arrays.

Regards

Pavel

Regards,

Show quoted text

Jeff Davis

#13Pavel Stehule
pavel.stehule@gmail.com
In reply to: Alvaro Herrera (#11)
Re: range_agg

čt 4. 7. 2019 v 20:34 odesílatel Alvaro Herrera <alvherre@2ndquadrant.com>
napsal:

I noticed that this patch has a // comment about it segfaulting. Did
you ever figure that out? Is the resulting code the one you intend as
final?

Did you make any inroads regarding Jeff Davis' suggestion about
implementing "multiranges"? I wonder if that's going to end up being a
Pandora's box.

Introduction of new datatype can be better, because we can ensure so data
are correctly ordered

Stylistically, the code does not match pgindent's choices very closely.
I can return a diff to a pgindented version of your v0002 for your
perusal, if it would be useful for you to learn its style. (A committer
would eventually run pgindent himself[1], but it's good to have
submissions be at least close to the final form.) BTW, I suggest "git
format-patch -vN" to prepare files for submission.

The first issue is unstable regress tests - there is a problem with
opr_sanity

SELECT p1.oid, p1.proname, p2.oid, p2.proname
FROM pg_proc AS p1, pg_proc AS p2
WHERE p1.oid < p2.oid AND
p1.prosrc = p2.prosrc AND
p1.prolang = 12 AND p2.prolang = 12 AND
(p1.prokind != 'a' OR p2.prokind != 'a') AND
(p1.prolang != p2.prolang OR
p1.prokind != p2.prokind OR
p1.prosecdef != p2.prosecdef OR
p1.proleakproof != p2.proleakproof OR
p1.proisstrict != p2.proisstrict OR
p1.proretset != p2.proretset OR
p1.provolatile != p2.provolatile OR
p1.pronargs != p2.pronargs)
ORDER BY p1.oid, p2.oid; -- requires explicit ORDER BY now

+   rangeTypeId = get_fn_expr_argtype(fcinfo->flinfo, 1);
+   if (!type_is_range(rangeTypeId))
+   {
+       ereport(ERROR, (errmsg("range_agg must be called with a range")));
+   }

???

+                   r1Str = "lastRange";
+                   r2Str = "currentRange";
+                   // TODO: Why is this segfaulting?:
+                   // r1Str =
DatumGetCString(DirectFunctionCall1(range_out,
RangeTypePGetDatum(lastRange)));
+                   // r2Str =
DatumGetCString(DirectFunctionCall1(range_out,
RangeTypePGetDatum(currentRange)));
+                   ereport(ERROR, (errmsg("range_agg: gap detected between
%s and %s", r1Str, r2Str)));
+               }

???

The patch doesn't respect Postgres formatting

+# Making 2- and 3-param range_agg polymorphic is difficult
+# because it would take an anyrange and return an anyrange[],
+# which doesn't exist.
+# As a workaround we define separate functions for each built-in range
type.
+# This is what causes the mess in src/test/regress/expected/opr_sanity.out.
+{ oid => '8003', descr => 'aggregate transition function',

This is strange. Does it means so range_agg will not work with custom range
types?

I am not sure about implementation. I think so accumulating all ranges,
sorting and processing on the end can be memory and CPU expensive.

Regards

Pavel

Show quoted text

[1] No female committers yet ... is this 2019?

--
Álvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

#14Jeff Davis
pgsql@j-davis.com
In reply to: Pavel Stehule (#12)
Re: range_agg

On Fri, 2019-07-05 at 07:58 +0200, Pavel Stehule wrote:

The question is naming - should be this agg function named
"range_agg", and multi range agg "multirange_agg"? Personally, I have
not a problem with range_agg, and I have not a problem so it is based
on union operation. It is true so only result of union can be
implemented as range simply. When I though about multi range result,
then there are really large set of possible algorithms how to do some
operations over two, three values.

Hi Pavel,

Can you explain in more detail? Would an intersection-based aggregate
be useful? If so, and we implement it in the future, what would we call
it?

Regards,
Jeff Davis

#15Paul A Jungwirth
pj@illuminatedcomputing.com
In reply to: Jeff Davis (#10)
Re: range_agg

On Mon, Jul 1, 2019 at 3:38 PM Jeff Davis <pgsql@j-davis.com> wrote:

For getting into core though, it should be a more complete set of
related operations. The patch is implicitly introducing the concept of
a "multirange" (in this case, an array of ranges), but it's not making
the concept whole.

What else should return a multirange? This patch implements the union-
agg of ranges, but we also might want an intersection-agg of ranges
(that is, the set of ranges that are subranges of every input). Given
that there are other options here, the name "range_agg" is too generic
to mean union-agg specifically.

Thanks for the review!

I like the idea of adding a multirange type that works like range
types, although I'm not sure I want to build it. :-)

My own motivations for the range_agg patch are for temporal databases,
where I have two use-cases: checking temporal foreign keys [1]With range_agg I can make the temporal FK check use similar SQL to the non-temporal FK check: and
doing a Snodgrass "coalesce" operation [2]page number 159ff in https://www2.cs.arizona.edu/~rts/tdbbook.pdf (section 6.5.1, starts on page 183 of the PDF). The FKs use-case is more
important. For coalesce I just immediately UNNEST the array, so a
multirange would sort of "get in the way". It's no big deal if I can
cast a multirange to an array, although for something that would run
on every INSERT/UPDATE/DELETE I'd like to understand what the cast
would cost us in performance. But coalesce isn't part of SQL:2011 and
should be optional behavior or just something in an extension. The FKs
use case matters to me a lot more, and I think a multirange would be
fine for that. Also a multirange would solve the generics problem
Pavel asked about. (I'll say more about that in a reply to him.)

I'm not convinced that an intersection aggregate function for
multiranges would be used by anyone, but I don't mind building one.
Every other *_agg function has an "additive" sense, not a
"subtractive" sense. json{,b}_object_agg are the closest since you
*could* imagine intersection semantics for those, but they are unions.
So in terms of *naming* I think using "range_agg" for union semantics
is natural and would fit expectations. (I'm not the first to name this
function range_agg btw: [3]https://git.proteus-tech.com/open-source/django-postgres/blob/fa91cf9b43ce942e84b1a9be22f445f3515ca360/postgres/sql/range_agg.sql).

But there is clearly more than one worthwhile way to aggregate ranges:

- David suggested weighted_range_agg and covering_range_agg. At PGCon
2019 someone else said he has had to build something that was
essentially weighted_range_agg. I can see it used for
scheduling/capacity/max-utilization problems.
- You suggested an intersection range_agg.
- At [4]https://schinckel.net/2014/11/18/aggregating-ranges-in-postgres/ there is a missing_ranges function that only gives the *gaps*
between the input ranges.

Nonetheless I still think I would call the union behavior simply
range_agg, and then use weighted_range_agg, covering_range_agg,
intersection_range_agg, and missing_range_agg for the rest (assuming
we built them all). I'm not going to insist on any of that, but it's
what feels most user-friendly to me.

What can we do with a multirange? A lot of range operators still make
sense, like "contains" or "overlaps"; but "adjacent" doesn't quite
work. What about new operations like inverting a multirange to get the
gaps?

I can think of a lot of cool operators for `multirange \bigoplus
multirange` or `multirange \bigoplus range` (commuting of course). And
I've certainly wanted `range + range` and `range - range` in the past,
which would both return a multirange.

If we have a more complete set of operators here, the flags for
handling overlapping ranges and gaps will be unnecessary.

Both of my use cases should permit overlaps & gaps (range_agg(r, true,
true)), so I'm actually pretty okay with dropping the flags entirely
and just giving a one-param function that behavior. Or defining a
strict_range_agg that offers more control. But also I don't understand
how richer *operators* make the flags for the *aggregate* unnecessary.

So I really like the idea of multiranges, but I'm reluctant to take it
on myself, especially since this patch is just a utility function for
my other temporal patches. But I don't want my rush to leave a blemish
in our standard library either. But I think what really persuades me
to add multiranges is making a range_agg that more easily supports
user-defined range types. So how about I start on it and see how it
goes? I expect I can follow the existing code for range types pretty
closely, so maybe it won't be too hard.

Another option would be to rename my function range_array_agg (or
something) so that we are leaving space for a multirange function in
the future. I don't love this idea myself but it would could a Plan B.
What do you think of that?

Regards,
Paul

[1]: With range_agg I can make the temporal FK check use similar SQL to the non-temporal FK check:
the non-temporal FK check:

SELECT 1
FROM [ONLY] <pktable> x
WHERE pkatt1 = $1 [AND ...]
FOR KEY SHARE OF x

vs

SELECT 1
FROM (
SELECT range_agg(r, true, true) AS r
FROM (
SELECT pkperiodatt AS r
FROM [ONLY] pktable x
WHERE pkatt1 = $1 [AND ...]
FOR KEY SHARE OF x
) x1
) x2
WHERE $n <@ x2.r[1]With range_agg I can make the temporal FK check use similar SQL to the non-temporal FK check:

(The temporal version could be simplified a bit if FOR KEY SHARE ever
supports aggregate functions.)

[2]: page number 159ff in https://www2.cs.arizona.edu/~rts/tdbbook.pdf (section 6.5.1, starts on page 183 of the PDF)
(section 6.5.1, starts on page 183 of the PDF)

[3]: https://git.proteus-tech.com/open-source/django-postgres/blob/fa91cf9b43ce942e84b1a9be22f445f3515ca360/postgres/sql/range_agg.sql

[4]: https://schinckel.net/2014/11/18/aggregating-ranges-in-postgres/

#16Paul A Jungwirth
pj@illuminatedcomputing.com
In reply to: Alvaro Herrera (#11)
Re: range_agg

On Thu, Jul 4, 2019 at 11:34 AM Alvaro Herrera <alvherre@2ndquadrant.com> wrote:

I noticed that this patch has a // comment about it segfaulting. Did
you ever figure that out? Is the resulting code the one you intend as
final?

Thanks for the review! I haven't revisited it but I'll see if I can
track it down. I consider this a WIP patch, not something final. (I
don't think Postgres likes C++-style comments, so anything that is //
marks something I consider needs more work.)

Stylistically, the code does not match pgindent's choices very closely.
I can return a diff to a pgindented version of your v0002 for your
perusal, if it would be useful for you to learn its style.

Sorry about that, and thank you for making it easier for me to learn
how to do it the right way. :-)

Paul

#17Paul A Jungwirth
pj@illuminatedcomputing.com
In reply to: Pavel Stehule (#13)
Re: range_agg

On Fri, Jul 5, 2019 at 4:31 AM Pavel Stehule <pavel.stehule@gmail.com> wrote:

The first issue is unstable regress tests - there is a problem with opr_sanity

I would prefer to avoid needing to add anything to opr_sanity really.
A multirange would let me achieve that I think. But otherwise I'll add
the ordering. Thanks!

+   rangeTypeId = get_fn_expr_argtype(fcinfo->flinfo, 1);
+   if (!type_is_range(rangeTypeId))
+   {
+       ereport(ERROR, (errmsg("range_agg must be called with a range")));
+   }

I think this was left over from my attempts to support user-defined
ranges. I believe I can remove it now.

+# Making 2- and 3-param range_agg polymorphic is difficult
+# because it would take an anyrange and return an anyrange[],
+# which doesn't exist.
+# As a workaround we define separate functions for each built-in range type.
+# This is what causes the mess in src/test/regress/expected/opr_sanity.out.
+{ oid => '8003', descr => 'aggregate transition function',

This is strange. Does it means so range_agg will not work with custom range types?

You would have to define your own range_agg with your own custom type
in the signature. I gave instructions about that in my range_agg
extension (https://github.com/pjungwir/range_agg), but it's not as
easy with range_agg in core because I don't think you can define a
custom function that is backed by a built-in C function. At least I
couldn't get that to work.

The biggest argument for a multirange to me is that we could have
anymultirange, with similar rules as anyarray. That way I could have
`range_agg(r anyrange) RETURNS anymultirange`. [1]/messages/by-id/CA+renyVOjb4xQZGjdCnA54-1nzVSY+47-h4nkM-EP5J=1z=b9w@mail.gmail.com is a conversation
where I asked about doing this before multiranges were suggested. Also
I'm aware of your own recent work on polymorphic types at [2]/messages/by-id/CAFj8pRDna7VqNi8gR+Tt2Ktmz0cq5G93guc3Sbn_NVPLdXAkqA@mail.gmail.com but I
haven't read it closely enough to see if it would help me here. Do you
think it applies?

I am not sure about implementation. I think so accumulating all ranges, sorting and processing on the end can be memory and CPU expensive.

I did some research and couldn't find any algorithm that was better
than O(n log n), but if you're aware of any I'd like to know about it.
Assuming we can't improve on the complexity bounds, I think a sort +
iteration is desirable because it keeps things simple and benefits
from past & future work on the sorting code. I care more about
optimizing time here than RAM, especially since we'll use this in FK
checks, where the inputs will rarely be more than a few and probably
never in the millions.

We especially want to avoid an O(n^2) algorithm, which would be easy
to stumble into if we naively accumulated the result as we go. For
example if we had multiranges you could imagine an implementation that
just did `result + r` for all inputs r. But that would have the same
n^2 pattern as looping over strcat.

We could use a tree to keep things sorted and accumulate as we go, but
the implementation would be more complicated and I think slower.

Thanks for the review!

Paul

[1]: /messages/by-id/CA+renyVOjb4xQZGjdCnA54-1nzVSY+47-h4nkM-EP5J=1z=b9w@mail.gmail.com

[2]: /messages/by-id/CAFj8pRDna7VqNi8gR+Tt2Ktmz0cq5G93guc3Sbn_NVPLdXAkqA@mail.gmail.com

#18Paul A Jungwirth
pj@illuminatedcomputing.com
In reply to: Jeff Davis (#10)
Re: range_agg

On Mon, Jul 1, 2019 at 3:38 PM Jeff Davis <pgsql@j-davis.com> wrote:

The patch is implicitly introducing the concept of
a "multirange" (in this case, an array of ranges),

I meant to say before: this patch always returns a sorted array, and I
think a multirange should always act as if sorted when we stringify it
or cast it to an array. If you disagree let me know. :-)

You could imagine that when returning arrays we rely on the caller to
do the sorting (range_agg(r ORDER BY r)) and otherwise give wrong
results. But hopefully everyone agrees that would not be nice. :-) So
even the array-returning version should always return a sorted array I
think. (I'm not sure anything else is really coherent or at least easy
to describe.)

Paul

#19David Fetter
david@fetter.org
In reply to: Paul A Jungwirth (#15)
Re: range_agg

On Fri, Jul 05, 2019 at 09:58:02AM -0700, Paul A Jungwirth wrote:

On Mon, Jul 1, 2019 at 3:38 PM Jeff Davis <pgsql@j-davis.com> wrote:

For getting into core though, it should be a more complete set of
related operations. The patch is implicitly introducing the concept of
a "multirange" (in this case, an array of ranges), but it's not making
the concept whole.

What else should return a multirange? This patch implements the union-
agg of ranges, but we also might want an intersection-agg of ranges
(that is, the set of ranges that are subranges of every input). Given
that there are other options here, the name "range_agg" is too generic
to mean union-agg specifically.

Thanks for the review!

I like the idea of adding a multirange type that works like range
types, although I'm not sure I want to build it. :-)

My own motivations for the range_agg patch are for temporal databases,
where I have two use-cases: checking temporal foreign keys [1] and
doing a Snodgrass "coalesce" operation [2]. The FKs use-case is more
important. For coalesce I just immediately UNNEST the array, so a
multirange would sort of "get in the way". It's no big deal if I can
cast a multirange to an array, although for something that would run
on every INSERT/UPDATE/DELETE I'd like to understand what the cast
would cost us in performance. But coalesce isn't part of SQL:2011 and
should be optional behavior or just something in an extension. The FKs
use case matters to me a lot more, and I think a multirange would be
fine for that. Also a multirange would solve the generics problem
Pavel asked about. (I'll say more about that in a reply to him.)

I'm not convinced that an intersection aggregate function for
multiranges would be used by anyone, but I don't mind building one.
Every other *_agg function has an "additive" sense, not a
"subtractive" sense. json{,b}_object_agg are the closest since you
*could* imagine intersection semantics for those, but they are unions.
So in terms of *naming* I think using "range_agg" for union semantics
is natural and would fit expectations. (I'm not the first to name this
function range_agg btw: [3]).

But there is clearly more than one worthwhile way to aggregate ranges:

- David suggested weighted_range_agg and covering_range_agg. At PGCon

If I understand the cases correctly, the combination of covering_range
and multi_range types covers all cases. To recap, covering_range_agg
assigns a weight, possibly 0, to each non-overlapping sub-range. A
cast from covering_range to multi_range would meld adjacent ranges
with non-zero weights into single ranges in O(N) (N sub-ranges) time
and some teensy amount of memory for tracking progress of adjacent
ranges of non-zero weight. Gaps would just be multi_range consisting
of the sub-ranges of the covering_range with weight 0, and wouldn't
require any tracking.

What have I missed?

Best,
David.
--
David Fetter <david(at)fetter(dot)org> http://fetter.org/
Phone: +1 415 235 3778

Remember to vote!
Consider donating to Postgres: http://www.postgresql.org/about/donate

#20Paul A Jungwirth
pj@illuminatedcomputing.com
In reply to: David Fetter (#19)
Re: range_agg

On Fri, Jul 5, 2019 at 10:45 AM David Fetter <david@fetter.org> wrote:

If I understand the cases correctly, the combination of covering_range
and multi_range types covers all cases. To recap, covering_range_agg
assigns a weight, possibly 0, to each non-overlapping sub-range. A
cast from covering_range to multi_range would meld adjacent ranges
with non-zero weights into single ranges in O(N) (N sub-ranges) time
and some teensy amount of memory for tracking progress of adjacent
ranges of non-zero weight. Gaps would just be multi_range consisting
of the sub-ranges of the covering_range with weight 0, and wouldn't
require any tracking.

I take it that a multirange contains of *disjoint* ranges, so instead
of {[1,2), [2,3), [6,7)} you'd have {[1,3), [6,7)}. Jeff does that
match your expectation?

I just realized that since weighted_range_agg and covering_range_agg
return tuples of (anyrange, integer) (maybe other numeric types too?),
their elements are *not ranges*, so they couldn't return a multirange.
They would have to return an array of those tuples.

I agree that if you had a pre-sorted list of weighted ranges (with or
without zero-weight elements), you could convert it to a multirange in
O(n) very easily.

Paul

#21Paul A Jungwirth
pj@illuminatedcomputing.com
In reply to: Paul A Jungwirth (#20)
Re: range_agg

On Fri, Jul 5, 2019 at 10:57 AM Paul A Jungwirth
<pj@illuminatedcomputing.com> wrote:

I take it that a multirange contains of *disjoint* ranges,

*consists* of. :-)

#22Pavel Stehule
pavel.stehule@gmail.com
In reply to: Jeff Davis (#14)
Re: range_agg

Hi

pá 5. 7. 2019 v 18:48 odesílatel Jeff Davis <pgsql@j-davis.com> napsal:

On Fri, 2019-07-05 at 07:58 +0200, Pavel Stehule wrote:

The question is naming - should be this agg function named
"range_agg", and multi range agg "multirange_agg"? Personally, I have
not a problem with range_agg, and I have not a problem so it is based
on union operation. It is true so only result of union can be
implemented as range simply. When I though about multi range result,
then there are really large set of possible algorithms how to do some
operations over two, three values.

Hi Pavel,

Can you explain in more detail? Would an intersection-based aggregate
be useful? If so, and we implement it in the future, what would we call
it?

Intersection can be interesting - you can use it for planning - "is there a
intersection of free time ranges?"

About naming - what range_union_agg(), range_intersect_agg() ?

Regards

Pavel

Show quoted text

Regards,
Jeff Davis

#23Pavel Stehule
pavel.stehule@gmail.com
In reply to: Paul A Jungwirth (#17)
Re: range_agg

pá 5. 7. 2019 v 19:22 odesílatel Paul A Jungwirth <
pj@illuminatedcomputing.com> napsal:

On Fri, Jul 5, 2019 at 4:31 AM Pavel Stehule <pavel.stehule@gmail.com>
wrote:

The first issue is unstable regress tests - there is a problem with

opr_sanity

I would prefer to avoid needing to add anything to opr_sanity really.
A multirange would let me achieve that I think. But otherwise I'll add
the ordering. Thanks!

+   rangeTypeId = get_fn_expr_argtype(fcinfo->flinfo, 1);
+   if (!type_is_range(rangeTypeId))
+   {
+       ereport(ERROR, (errmsg("range_agg must be called with a

range")));

+ }

I think this was left over from my attempts to support user-defined
ranges. I believe I can remove it now.

+# Making 2- and 3-param range_agg polymorphic is difficult
+# because it would take an anyrange and return an anyrange[],
+# which doesn't exist.
+# As a workaround we define separate functions for each built-in range

type.

+# This is what causes the mess in

src/test/regress/expected/opr_sanity.out.

+{ oid => '8003', descr => 'aggregate transition function',

This is strange. Does it means so range_agg will not work with custom

range types?

You would have to define your own range_agg with your own custom type
in the signature. I gave instructions about that in my range_agg
extension (https://github.com/pjungwir/range_agg), but it's not as
easy with range_agg in core because I don't think you can define a
custom function that is backed by a built-in C function. At least I
couldn't get that to work.

I understand so anybody can implement own function, but this is fundamental
problem in implementation.

In this case I prefer to start implement anyrangearray type first. It is
not hard work.

The biggest argument for a multirange to me is that we could have
anymultirange, with similar rules as anyarray. That way I could have
`range_agg(r anyrange) RETURNS anymultirange`. [1] is a conversation
where I asked about doing this before multiranges were suggested. Also
I'm aware of your own recent work on polymorphic types at [2] but I
haven't read it closely enough to see if it would help me here. Do you
think it applies?

I don't see any difference between anymultirange or anyrangearray - it is
just name

ANSI SQL has a special kind for this purpose - "set". It is maybe better,
because PostgreSQL's arrays revoke a idea of ordering, but it is hard to do
some generic order for ranges.

Functionality is important - if you can do some special functionality, that
cannot be implemented as "array of ranges", then the new type is necessary.
If all functionality can be covered by array of ranges, then there is not
necessity of new type.

I don't talk about polymorphic types - probably it needs one -
"anymultirange" or "anyrangearray".

If some operation can be done smarter with new type, then I am ok for new
type, if not, then we should to use array of ranges.

I am not sure about implementation. I think so accumulating all ranges,

sorting and processing on the end can be memory and CPU expensive.

I did some research and couldn't find any algorithm that was better
than O(n log n), but if you're aware of any I'd like to know about it.
Assuming we can't improve on the complexity bounds, I think a sort +
iteration is desirable because it keeps things simple and benefits
from past & future work on the sorting code. I care more about
optimizing time here than RAM, especially since we'll use this in FK
checks, where the inputs will rarely be more than a few and probably
never in the millions.

We especially want to avoid an O(n^2) algorithm, which would be easy
to stumble into if we naively accumulated the result as we go. For
example if we had multiranges you could imagine an implementation that
just did `result + r` for all inputs r. But that would have the same
n^2 pattern as looping over strcat.

We could use a tree to keep things sorted and accumulate as we go, but
the implementation would be more complicated and I think slower.

I don't think - working with large arrays is slow, due often cache miss. I
understand so it depends on data, but in this area, I can imagine
significant memory reduction based on running processing.

array builder doesn't respect work_men, and I think so any different way is
safer.

Regards

Pavel

Show quoted text

Thanks for the review!

Paul

[1]
/messages/by-id/CA+renyVOjb4xQZGjdCnA54-1nzVSY+47-h4nkM-EP5J=1z=b9w@mail.gmail.com

[2]
/messages/by-id/CAFj8pRDna7VqNi8gR+Tt2Ktmz0cq5G93guc3Sbn_NVPLdXAkqA@mail.gmail.com

#24Jeff Davis
pgsql@j-davis.com
In reply to: Paul A Jungwirth (#15)
Re: range_agg

On Fri, 2019-07-05 at 09:58 -0700, Paul A Jungwirth wrote:

user-defined range types. So how about I start on it and see how it
goes? I expect I can follow the existing code for range types pretty
closely, so maybe it won't be too hard.

That would be great to at least take a look. If it starts to look like
a bad idea, then we can re-evaluate and see if it's better to just
return arrays.

The "weighted" version of the aggregate might be interesting... what
would it return exactly? An array of (range, weight) pairs, or an array
of ranges and an array of weights, or a multirange and an array of
weights?

Another option would be to rename my function range_array_agg (or
something) so that we are leaving space for a multirange function in
the future. I don't love this idea myself but it would could a Plan
B.
What do you think of that?

Not excited about that either.

Regards,
Jeff Davis

#25Jeff Davis
pgsql@j-davis.com
In reply to: Paul A Jungwirth (#20)
Re: range_agg

On Fri, 2019-07-05 at 10:57 -0700, Paul A Jungwirth wrote:

I take it that a multirange contains of *disjoint* ranges, so instead
of {[1,2), [2,3), [6,7)} you'd have {[1,3), [6,7)}. Jeff does that
match your expectation?

Yes.

I just realized that since weighted_range_agg and covering_range_agg
return tuples of (anyrange, integer) (maybe other numeric types
too?),
their elements are *not ranges*, so they couldn't return a
multirange.
They would have to return an array of those tuples.

I think you are right. I was originally thinking a multirange and an
array of weights would work, but the multirange would coalesce adjacent
ranges because it would have no way to know they have different
weights.

Regards,
Jeff Davis

#26Paul A Jungwirth
pj@illuminatedcomputing.com
In reply to: Jeff Davis (#24)
Re: range_agg

On Sat, Jul 6, 2019 at 12:13 PM Jeff Davis <pgsql@j-davis.com> wrote:

On Fri, 2019-07-05 at 09:58 -0700, Paul A Jungwirth wrote:

user-defined range types. So how about I start on it and see how it
goes? I expect I can follow the existing code for range types pretty
closely, so maybe it won't be too hard.

That would be great to at least take a look. If it starts to look like
a bad idea, then we can re-evaluate and see if it's better to just
return arrays.

I made some progress over the weekend. I don't have a patch yet but I
thought I'd ask for opinions on the approach I'm taking:

- A multirange type is an extra thing you get when you define a range
(just like how you get a tstzrange[]). Therefore....
- I don't need separate commands to add/drop multirange types. You get
one when you define a range type, and if you drop a range type it gets
dropped automatically.
- I'm adding a new typtype for multiranges. ('m' in pg_type).
- I'm just adding a mltrngtypeid column to pg_range. I don't think I
need a new pg_multirange table.
- You can have a multirange[].
- Multirange in/out work just like arrays, e.g. '{"[1,3)", "[5,6)"}'
- I'll add an anymultirange pseudotype. When it's the return type of a
function that has an "anyrange" parameter, it will use the same base
element type. (So basically anymultirange : anyrange :: anyarray ::
anyelement.)
- You can cast from a multirange to an array. The individual ranges
are always sorted in the result array.
- You can cast from an array to a multirange but it will error if
there are overlaps (or not?). The array's ranges don't have to be
sorted but they will be after a "round trip".
- Interesting functions:
- multirange_length
- range_agg (range_union_agg if you like)
- range_intersection_agg
- You can subscript a multirange like you do an array (? This could be
a function instead.)
- operators:
- union (++) and intersection (*):
- We already have + for range union but it raises an error if
there is a gap, so ++ is the same but with no errors.
- r ++ r = mr (commutative, associative)
- mr ++ r = mr
- r ++ mr = mr
- r * r = r (unchanged)
- mr * r = r
- r * mr = r
- mr - r = mr
- r - mr = mr
- mr - mr = mr
- comparison
- mr = mr
- mr @> x
- mr @> r
- mr @> mr
- x <@ mr
- r <@ mr
- mr <@ mr
- mr << mr (strictly left of)
- mr >> mr (strictly right of)
- mr &< mr (does not extend to the right of)
- mr &> mr (does not extend to the left of)
- inverse operator?:
- the inverse of {"[1,2)"} would be {"[null, 1)", "[2, null)"}.
- not sure we want this or what the symbol should be. I don't like
-mr as an inverse because then mr - mr != mr ++ -mr.

Anything in there you think should be different?

Thanks,
Paul

#27Pavel Stehule
pavel.stehule@gmail.com
In reply to: Paul A Jungwirth (#26)
Re: range_agg

Hi

po 8. 7. 2019 v 18:47 odesílatel Paul A Jungwirth <
pj@illuminatedcomputing.com> napsal:

On Sat, Jul 6, 2019 at 12:13 PM Jeff Davis <pgsql@j-davis.com> wrote:

On Fri, 2019-07-05 at 09:58 -0700, Paul A Jungwirth wrote:

user-defined range types. So how about I start on it and see how it
goes? I expect I can follow the existing code for range types pretty
closely, so maybe it won't be too hard.

That would be great to at least take a look. If it starts to look like
a bad idea, then we can re-evaluate and see if it's better to just
return arrays.

I made some progress over the weekend. I don't have a patch yet but I
thought I'd ask for opinions on the approach I'm taking:

- A multirange type is an extra thing you get when you define a range
(just like how you get a tstzrange[]). Therefore....

I am not against a multirange type, but I miss a explanation why you
introduce new kind of types and don't use just array of ranges.

Introduction of new kind of types is not like introduction new type.

Regards

Pavel

- I don't need separate commands to add/drop multirange types. You get

Show quoted text

one when you define a range type, and if you drop a range type it gets
dropped automatically.
- I'm adding a new typtype for multiranges. ('m' in pg_type).
- I'm just adding a mltrngtypeid column to pg_range. I don't think I
need a new pg_multirange table.
- You can have a multirange[].
- Multirange in/out work just like arrays, e.g. '{"[1,3)", "[5,6)"}'
- I'll add an anymultirange pseudotype. When it's the return type of a
function that has an "anyrange" parameter, it will use the same base
element type. (So basically anymultirange : anyrange :: anyarray ::
anyelement.)
- You can cast from a multirange to an array. The individual ranges
are always sorted in the result array.
- You can cast from an array to a multirange but it will error if
there are overlaps (or not?). The array's ranges don't have to be
sorted but they will be after a "round trip".
- Interesting functions:
- multirange_length
- range_agg (range_union_agg if you like)
- range_intersection_agg
- You can subscript a multirange like you do an array (? This could be
a function instead.)
- operators:
- union (++) and intersection (*):
- We already have + for range union but it raises an error if
there is a gap, so ++ is the same but with no errors.
- r ++ r = mr (commutative, associative)
- mr ++ r = mr
- r ++ mr = mr
- r * r = r (unchanged)
- mr * r = r
- r * mr = r
- mr - r = mr
- r - mr = mr
- mr - mr = mr
- comparison
- mr = mr
- mr @> x
- mr @> r
- mr @> mr
- x <@ mr
- r <@ mr
- mr <@ mr
- mr << mr (strictly left of)
- mr >> mr (strictly right of)
- mr &< mr (does not extend to the right of)
- mr &> mr (does not extend to the left of)
- inverse operator?:
- the inverse of {"[1,2)"} would be {"[null, 1)", "[2, null)"}.
- not sure we want this or what the symbol should be. I don't like
-mr as an inverse because then mr - mr != mr ++ -mr.

Anything in there you think should be different?

Thanks,
Paul

#28David Fetter
david@fetter.org
In reply to: Paul A Jungwirth (#26)
Re: range_agg

On Mon, Jul 08, 2019 at 09:46:44AM -0700, Paul A Jungwirth wrote:

On Sat, Jul 6, 2019 at 12:13 PM Jeff Davis <pgsql@j-davis.com> wrote:

On Fri, 2019-07-05 at 09:58 -0700, Paul A Jungwirth wrote:

user-defined range types. So how about I start on it and see how it
goes? I expect I can follow the existing code for range types pretty
closely, so maybe it won't be too hard.

That would be great to at least take a look. If it starts to look like
a bad idea, then we can re-evaluate and see if it's better to just
return arrays.

I made some progress over the weekend. I don't have a patch yet but I
thought I'd ask for opinions on the approach I'm taking:

- A multirange type is an extra thing you get when you define a range
(just like how you get a tstzrange[]). Therefore....
- I don't need separate commands to add/drop multirange types. You get
one when you define a range type, and if you drop a range type it gets
dropped automatically.

Yay for fewer manual steps!

- I'm adding a new typtype for multiranges. ('m' in pg_type).
- I'm just adding a mltrngtypeid column to pg_range. I don't think I
need a new pg_multirange table.

Makes sense, as they'd no longer be separate concepts.

- You can have a multirange[].

I can see how that would fall out of this, but I'm a little puzzled as
to what people might use it for. Aggregates, maybe?

- Multirange in/out work just like arrays, e.g. '{"[1,3)", "[5,6)"}'
- I'll add an anymultirange pseudotype. When it's the return type of a
function that has an "anyrange" parameter, it will use the same base
element type. (So basically anymultirange : anyrange :: anyarray ::
anyelement.)

Neat!

- You can cast from a multirange to an array. The individual ranges
are always sorted in the result array.

Is this so people can pick individual ranges out of the multirange,
or...? Speaking of casts, it's possible that a multirange is also a
range. Would it make sense to have a cast from multirange to range?

- You can cast from an array to a multirange but it will error if
there are overlaps (or not?).

An alternative would be to canonicalize into non-overlapping ranges.
There's some precedent for this in casts to JSONB. Maybe a function
that isn't a cast should handle such things.

The array's ranges don't have to be sorted but they will be after a
"round trip".

Makes sense.

- Interesting functions:
- multirange_length

Is that the sum of the lengths of the ranges? Are we guaranteeing a
measure in addition to ordering on ranges now?

- range_agg (range_union_agg if you like)
- range_intersection_agg
- You can subscript a multirange like you do an array (? This could be
a function instead.)

How would this play with the generic subscripting patch in flight?

- operators:
- union (++) and intersection (*):
- We already have + for range union but it raises an error if
there is a gap, so ++ is the same but with no errors.
- r ++ r = mr (commutative, associative)
- mr ++ r = mr
- r ++ mr = mr
- r * r = r (unchanged)
- mr * r = r
- r * mr = r

Shouldn't the two above both yield multirange ? For example, if I
understand correctly,

{"[1,3)","[5,7)"} * [2,6) should be {"[2,3)","[5,6)"}

- mr - r = mr
- r - mr = mr
- mr - mr = mr
- comparison
- mr = mr
- mr @> x

x is in the domain of the (multi)range?

- mr @> r
- mr @> mr
- x <@ mr
- r <@ mr
- mr <@ mr
- mr << mr (strictly left of)
- mr >> mr (strictly right of)
- mr &< mr (does not extend to the right of)
- mr &> mr (does not extend to the left of)
- inverse operator?:
- the inverse of {"[1,2)"} would be {"[null, 1)", "[2, null)"}.

Is that the same as ["(∞, ∞)"] - {"[1,2)"}? I seem to recall that the
usual convention (at least in math) is to use intervals that are
generally represented as open on the infinity side, but that might not
fit how we do things.

- not sure we want this or what the symbol should be. I don't like
-mr as an inverse because then mr - mr != mr ++ -mr.

!mr , perhaps?

Best,
David.
--
David Fetter <david(at)fetter(dot)org> http://fetter.org/
Phone: +1 415 235 3778

Remember to vote!
Consider donating to Postgres: http://www.postgresql.org/about/donate

#29Paul A Jungwirth
pj@illuminatedcomputing.com
In reply to: Pavel Stehule (#27)
Re: range_agg

On Mon, Jul 8, 2019 at 10:09 PM Pavel Stehule <pavel.stehule@gmail.com> wrote:

po 8. 7. 2019 v 18:47 odesílatel Paul A Jungwirth <pj@illuminatedcomputing.com> napsal:
I am not against a multirange type, but I miss a explanation why you introduce new kind of types and don't use just array of ranges.

Hi Pavel, I'm sorry, and thanks for your feedback! I had a response to
your earlier email but it was stuck in my Drafts folder.

I do think a multirange would have enough new functionality to be
worth doing. I was pretty reluctant to take it on at first but the
idea is growing on me, and it does seem to offer a more sensible
interface. A lot of the value would come from range and multirange
operators, which we can't do with just arrays (I think). Having a
range get merged correctly when you add it would be very helpful. Also
it would be nice to have a data type that enforces a valid structure,
since not all range arrays are valid multiranges. My reply yesterday
to Jeff expands on this with all the functions/operators/etc we could
offer.

Your other email also asked:

I don't think - working with large arrays is slow, due often cache miss.
I understand so it depends on data, but in this area, I can imagine significant memory reduction based on running processing.

array builder doesn't respect work_men, and I think so any different way is safer.

I'm still thinking about this one. I tried to work out how I'd
implement a tree-based sorted list of ranges so that I can quickly
insert/remove ranges. It is very complicated, and I started to feel
like I was just re-implementing GiST but in memory. I did find the
interval_set class from Boost's boost_icl library which could offer
some guidance. But for now I want to press forward with a
sort-then-iterate implementation and consider a different
implementation later. If you have any guidance I would appreciate it!
I especially want something that is O(n log n) to insert n ranges.
Other suggestions here are very welcome! :-)

Regards,
Paul

#30Paul A Jungwirth
pj@illuminatedcomputing.com
In reply to: David Fetter (#28)
Re: range_agg

On Tue, Jul 9, 2019 at 8:51 AM David Fetter <david@fetter.org> wrote:

- A multirange type is an extra thing you get when you define a range
(just like how you get a tstzrange[]). Therefore....
- I don't need separate commands to add/drop multirange types. You get
one when you define a range type, and if you drop a range type it gets
dropped automatically.

Yay for fewer manual steps!

Thanks for taking a look and sharing your thoughts!

- You can have a multirange[].

I can see how that would fall out of this, but I'm a little puzzled as
to what people might use it for. Aggregates, maybe?

I don't know either, but I thought it was standard to define a T[] for
every T. Anyway it doesn't seem difficult.

- You can cast from a multirange to an array. The individual ranges
are always sorted in the result array.

Is this so people can pick individual ranges out of the multirange,
or...?

Yes. I want this for foreign keys actually, where I construct a
multirange and ask for just its first range.

Speaking of casts, it's possible that a multirange is also a
range. Would it make sense to have a cast from multirange to range?

Hmm, that seems strange to me. You don't cast from an array to one of
its elements. If we have subscripting, why use casting to get the
first element?

- You can cast from an array to a multirange but it will error if
there are overlaps (or not?).

An alternative would be to canonicalize into non-overlapping ranges.
There's some precedent for this in casts to JSONB. Maybe a function
that isn't a cast should handle such things.

I agree it'd be nice to have both.

- Interesting functions:
- multirange_length

Is that the sum of the lengths of the ranges? Are we guaranteeing a
measure in addition to ordering on ranges now?

Just the number of disjoint ranges in the multirange.

- You can subscript a multirange like you do an array (? This could be
a function instead.)

How would this play with the generic subscripting patch in flight?

I'm not aware of that patch but I guess I better check it out. :-)

- operators:
- mr * r = r
- r * mr = r

Shouldn't the two above both yield multirange ? For example, if I
understand correctly,

You're right! Thanks for the correction.

- comparison
- mr = mr
- mr @> x

x is in the domain of the (multi)range?

Yes. It's the scalar base type the range type is based on. I had in
mind the math/ML convention of `x` for scalar and `X` for
vector/matrix.

- inverse operator?:
- the inverse of {"[1,2)"} would be {"[null, 1)", "[2, null)"}.

Is that the same as ["(∞, ∞)"] - {"[1,2)"}?

Yes.

I seem to recall that the
usual convention (at least in math) is to use intervals that are
generally represented as open on the infinity side, but that might not
fit how we do things.

I think it does, unless I'm misunderstanding?

- not sure we want this or what the symbol should be. I don't like
-mr as an inverse because then mr - mr != mr ++ -mr.

!mr , perhaps?

I like that suggestion. Honestly I'm not sure we even want an inverse,
but it's so important theoretically we should at least consider
whether it is appropriate here. Or maybe "inverse" is the wrong word
for this, or there is a different meaning it should have.

Thanks,
Paul

#31Jeff Davis
pgsql@j-davis.com
In reply to: Pavel Stehule (#27)
Re: range_agg

On Tue, 2019-07-09 at 07:08 +0200, Pavel Stehule wrote:

I am not against a multirange type, but I miss a explanation why you
introduce new kind of types and don't use just array of ranges.

Introduction of new kind of types is not like introduction new type.

The biggest benefit, in my opinion, is that it means you can define
functions/operators that take an "anyrange" and return an
"anymultirange". That way you don't have to define different functions
for int4 ranges, date ranges, etc.

It starts to get even more complex when you want to add opclasses, etc.

Ranges and arrays are effectively generic types that need a type
parameter to become a concrete type. Ideally, we'd have first-class
support for generic types, but I think that's a different topic ;-)

Regards,
Jeff Davis

#32Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Paul A Jungwirth (#26)
Re: range_agg

On 2019-Jul-08, Paul A Jungwirth wrote:

- You can subscript a multirange like you do an array (? This could be
a function instead.)

Note that we already have a patch in the pipe to make subscripting an
extensible operation, which would fit pretty well here, I think.

Also, I suppose you would need unnest(multirange) to yield the set of
ranges.

--
�lvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

#33Jeff Davis
pgsql@j-davis.com
In reply to: Paul A Jungwirth (#26)
Re: range_agg

On Mon, 2019-07-08 at 09:46 -0700, Paul A Jungwirth wrote:

- A multirange type is an extra thing you get when you define a range
(just like how you get a tstzrange[]). Therefore....

Agreed.

- I'm adding a new typtype for multiranges. ('m' in pg_type).

Sounds reasonable.

- I'm just adding a mltrngtypeid column to pg_range. I don't think I
need a new pg_multirange table.
- You can have a multirange[].
- Multirange in/out work just like arrays, e.g. '{"[1,3)", "[5,6)"}'

It would be cool to have a better text representation. We could go
simple like:

'[1,3) [5,6)'

Or maybe someone has another idea how to represent a multirange to be
more visually descriptive?

- I'll add an anymultirange pseudotype. When it's the return type of
a
function that has an "anyrange" parameter, it will use the same base
element type. (So basically anymultirange : anyrange :: anyarray ::
anyelement.)

I like it.

- range_agg (range_union_agg if you like)
- range_intersection_agg

I'm fine with those names.

- You can subscript a multirange like you do an array (? This could
be
a function instead.)

I wouldn't try to hard to make them subscriptable. I'm not opposed to
it, but it's easy enough to cast to an array and then subscript.

- operators:
- union (++) and intersection (*):
- We already have + for range union but it raises an error if
there is a gap, so ++ is the same but with no errors.
- r ++ r = mr (commutative, associative)
- mr ++ r = mr
- r ++ mr = mr

I like it.

- inverse operator?:
- the inverse of {"[1,2)"} would be {"[null, 1)", "[2, null)"}.
- not sure we want this or what the symbol should be. I don't
like
-mr as an inverse because then mr - mr != mr ++ -mr.

I think "complement" might be a better name than "inverse".

m1 - m2 = m1 * complement(m2)

What about "~"?

There will be some changes to parse_coerce.c, just like in range types.
I took a brief look here and it looks pretty reasonable; hopefully
there aren't any hidden surprises.

Regards,
Jeff Davis

#34Paul Jungwirth
pj@illuminatedcomputing.com
In reply to: Alvaro Herrera (#32)
Re: range_agg

On 7/9/19 12:01 PM, Alvaro Herrera wrote:

On 2019-Jul-08, Paul A Jungwirth wrote:

- You can subscript a multirange like you do an array (? This could be
a function instead.)

Note that we already have a patch in the pipe to make subscripting an
extensible operation, which would fit pretty well here, I think.

I'll take a look at that!

Also, I suppose you would need unnest(multirange) to yield the set of
ranges.

I think that would be really nice, although it isn't critical I think if
you can do something like UNNEST(multirange::tstzrange[]).

--
Paul ~{:-)
pj@illuminatedcomputing.com

#35Pavel Stehule
pavel.stehule@gmail.com
In reply to: Jeff Davis (#31)
Re: range_agg

út 9. 7. 2019 v 20:25 odesílatel Jeff Davis <pgsql@j-davis.com> napsal:

On Tue, 2019-07-09 at 07:08 +0200, Pavel Stehule wrote:

I am not against a multirange type, but I miss a explanation why you
introduce new kind of types and don't use just array of ranges.

Introduction of new kind of types is not like introduction new type.

The biggest benefit, in my opinion, is that it means you can define
functions/operators that take an "anyrange" and return an
"anymultirange". That way you don't have to define different functions
for int4 ranges, date ranges, etc.

I am not sure how strong is this argument.

I think so introduction of anyrangearray polymorphic type and enhancing
some type deduction can do same work.

It starts to get even more complex when you want to add opclasses, etc.

Ranges and arrays are effectively generic types that need a type
parameter to become a concrete type. Ideally, we'd have first-class
support for generic types, but I think that's a different topic ;-)

I afraid so with generic multiragetype there lot of array infrastructure
will be duplicated

Regards

Pavel

Show quoted text

Regards,
Jeff Davis

#36Pavel Stehule
pavel.stehule@gmail.com
In reply to: Pavel Stehule (#35)
Re: range_agg

út 9. 7. 2019 v 21:10 odesílatel Pavel Stehule <pavel.stehule@gmail.com>
napsal:

út 9. 7. 2019 v 20:25 odesílatel Jeff Davis <pgsql@j-davis.com> napsal:

On Tue, 2019-07-09 at 07:08 +0200, Pavel Stehule wrote:

I am not against a multirange type, but I miss a explanation why you
introduce new kind of types and don't use just array of ranges.

Introduction of new kind of types is not like introduction new type.

The biggest benefit, in my opinion, is that it means you can define
functions/operators that take an "anyrange" and return an
"anymultirange". That way you don't have to define different functions
for int4 ranges, date ranges, etc.

I am not sure how strong is this argument.

I think so introduction of anyrangearray polymorphic type and enhancing
some type deduction can do same work.

It starts to get even more complex when you want to add opclasses, etc.

Ranges and arrays are effectively generic types that need a type
parameter to become a concrete type. Ideally, we'd have first-class
support for generic types, but I think that's a different topic ;-)

I afraid so with generic multiragetype there lot of array infrastructure
will be duplicated

on second hand - it is true so classic array concat is not optimal for set
of ranges, so some functionality should be redefined every time.

I don't know what is possible, but for me - multiranges is special kind
(subset) of arrays and can be implement as subset of arrays. I remember
other possible kind of arrays - "sets" without duplicates. It is similar
case, I think.

Maybe introduction of multirages as new generic type is bad direction, and
can be better and more enhanceable in future to introduce some like special
kinds of arrays. So for example - unnest can be used directly for arrays
and multiranges too - because there will be common base.

Regards

Pavel

Show quoted text

Regards

Pavel

Regards,
Jeff Davis

#37Paul A Jungwirth
pj@illuminatedcomputing.com
In reply to: Jeff Davis (#33)
Re: range_agg

On Tue, Jul 9, 2019 at 12:02 PM Jeff Davis <pgsql@j-davis.com> wrote:

- Multirange in/out work just like arrays, e.g. '{"[1,3)", "[5,6)"}'

It would be cool to have a better text representation. We could go
simple like:

'[1,3) [5,6)'

Will that work with all ranges, even user-defined ones? With a
tstzrange[] there is a lot of quoting:

=# select array[tstzrange(now(), now() + interval '1 hour')];
array
---------------------------------------------------------------------------
{"[\"2019-07-09 12:40:20.794054-07\",\"2019-07-09 13:40:20.794054-07\")"}

I'm more inclined to follow the array syntax both because it will be
familiar & consistent to users (no need to remember any differences)
and because it's already built so we can use it and know it won't have
gotchas.

I think "complement" might be a better name than "inverse".

m1 - m2 = m1 * complement(m2)

What about "~"?

That seems like the right term and a good symbol.

Paul

#38Paul A Jungwirth
pj@illuminatedcomputing.com
In reply to: Pavel Stehule (#36)
Re: range_agg

On Tue, Jul 9, 2019 at 12:24 PM Pavel Stehule <pavel.stehule@gmail.com> wrote:

út 9. 7. 2019 v 21:10 odesílatel Pavel Stehule <pavel.stehule@gmail.com> napsal:

I afraid so with generic multiragetype there lot of array infrastructure will be duplicated

on second hand - it is true so classic array concat is not optimal for set of ranges, so some functionality should be redefined every time.

I don't know what is possible, but for me - multiranges is special kind (subset) of arrays and can be implement as subset of arrays. I remember other possible kind of arrays - "sets" without duplicates. It is similar case, I think.

Maybe introduction of multirages as new generic type is bad direction, and can be better and more enhanceable in future to introduce some like special kinds of arrays. So for example - unnest can be used directly for arrays and multiranges too - because there will be common base.

Well I'm afraid of that too a bit, although I also agree it may be an
opportunity to share some common behavior and implementation. For
example in the discussion about string syntax, I think keeping it the
same as arrays is nicer for people and lets us share more between the
two types.

That said I don't think a multirange is a subtype of arrays (speaking
as a traditional object-oriented subtype), just something that shares
a lot of the same behavior. I'm inclined to maximize the overlap where
feasible though, e.g. string syntax, UNNEST, indexing, function naming
(`range_length`), etc. Something like Rust traits (or Java interfaces)
seems a closer mental model, not that we have to formalize that
somehow, particularly up front.

Yours,
Paul

#39Pavel Stehule
pavel.stehule@gmail.com
In reply to: Paul A Jungwirth (#38)
Re: range_agg

st 10. 7. 2019 v 6:26 odesílatel Paul A Jungwirth <
pj@illuminatedcomputing.com> napsal:

On Tue, Jul 9, 2019 at 12:24 PM Pavel Stehule <pavel.stehule@gmail.com>
wrote:

út 9. 7. 2019 v 21:10 odesílatel Pavel Stehule <pavel.stehule@gmail.com>

napsal:

I afraid so with generic multiragetype there lot of array

infrastructure will be duplicated

on second hand - it is true so classic array concat is not optimal for

set of ranges, so some functionality should be redefined every time.

I don't know what is possible, but for me - multiranges is special kind

(subset) of arrays and can be implement as subset of arrays. I remember
other possible kind of arrays - "sets" without duplicates. It is similar
case, I think.

Maybe introduction of multirages as new generic type is bad direction,

and can be better and more enhanceable in future to introduce some like
special kinds of arrays. So for example - unnest can be used directly for
arrays and multiranges too - because there will be common base.

Well I'm afraid of that too a bit, although I also agree it may be an
opportunity to share some common behavior and implementation. For
example in the discussion about string syntax, I think keeping it the
same as arrays is nicer for people and lets us share more between the
two types.

That said I don't think a multirange is a subtype of arrays (speaking
as a traditional object-oriented subtype), just something that shares
a lot of the same behavior. I'm inclined to maximize the overlap where
feasible though, e.g. string syntax, UNNEST, indexing, function naming
(`range_length`), etc. Something like Rust traits (or Java interfaces)
seems a closer mental model, not that we have to formalize that
somehow, particularly up front.

A introduction of new generic type can have some other impacts - there can
be necessary special support for PL languages.

I understand so it is hard to decide - because we miss some more generic
base "sets".

Probably we cannot to think more about it now, and we have to wait to some
patches. Later we can see how much code is duplicated and if it is a
problem or not.

Regards

Pavel

Show quoted text

Yours,
Paul

#40David Fetter
david@fetter.org
In reply to: Paul A Jungwirth (#30)
Re: range_agg

On Tue, Jul 09, 2019 at 09:40:59AM -0700, Paul A Jungwirth wrote:

On Tue, Jul 9, 2019 at 8:51 AM David Fetter <david@fetter.org> wrote:

- A multirange type is an extra thing you get when you define a range
(just like how you get a tstzrange[]). Therefore....
- I don't need separate commands to add/drop multirange types. You get
one when you define a range type, and if you drop a range type it gets
dropped automatically.

Yay for fewer manual steps!

Thanks for taking a look and sharing your thoughts!

- You can have a multirange[].

I can see how that would fall out of this, but I'm a little puzzled as
to what people might use it for. Aggregates, maybe?

I don't know either, but I thought it was standard to define a T[] for
every T. Anyway it doesn't seem difficult.

- You can cast from a multirange to an array. The individual ranges
are always sorted in the result array.

Is this so people can pick individual ranges out of the multirange,
or...?

Yes. I want this for foreign keys actually, where I construct a
multirange and ask for just its first range.

I'm sure I'll understand this better once I get my head around
temporal foreign keys.

Speaking of casts, it's possible that a multirange is also a
range. Would it make sense to have a cast from multirange to range?

Hmm, that seems strange to me. You don't cast from an array to one of
its elements. If we have subscripting, why use casting to get the
first element?

Excellent point.

- Interesting functions:
- multirange_length

Is that the sum of the lengths of the ranges? Are we guaranteeing a
measure in addition to ordering on ranges now?

Just the number of disjoint ranges in the multirange.

Thanks for clarifying.

- You can subscript a multirange like you do an array (? This could be
a function instead.)

How would this play with the generic subscripting patch in flight?

I'm not aware of that patch but I guess I better check it out. :-)

Looks like I'm the second to mention it. Worth a review?

- inverse operator?:
- the inverse of {"[1,2)"} would be {"[null, 1)", "[2, null)"}.

Is that the same as ["(∞, ∞)"] - {"[1,2)"}?

Yes.

I seem to recall that the usual convention (at least in math) is
to use intervals that are generally represented as open on the
infinity side, but that might not fit how we do things.

I think it does, unless I'm misunderstanding?

Oh, I was just wondering about the square bracket on the left side of
[null, 1). It's not super important.

- not sure we want this or what the symbol should be. I don't like
-mr as an inverse because then mr - mr != mr ++ -mr.

!mr , perhaps?

I like that suggestion. Honestly I'm not sure we even want an inverse,
but it's so important theoretically we should at least consider
whether it is appropriate here. Or maybe "inverse" is the wrong word
for this, or there is a different meaning it should have.

Jeff's suggestion of ~ for complement is better.

Best,
David.
--
David Fetter <david(at)fetter(dot)org> http://fetter.org/
Phone: +1 415 235 3778

Remember to vote!
Consider donating to Postgres: http://www.postgresql.org/about/donate

#41Paul Jungwirth
pj@illuminatedcomputing.com
In reply to: David Fetter (#40)
Re: range_agg

On 7/9/19 11:24 PM, David Fetter wrote:

I seem to recall that the usual convention (at least in math) is
to use intervals that are generally represented as open on the
infinity side, but that might not fit how we do things.

I think it does, unless I'm misunderstanding?

Oh, I was just wondering about the square bracket on the left side of
[null, 1). It's not super important.

Ah, I understand now. Just a typo on my part. Thanks for catching it,
and sorry for the confusion!

!mr , perhaps?

I like that suggestion. Honestly I'm not sure we even want an inverse,
but it's so important theoretically we should at least consider
whether it is appropriate here. Or maybe "inverse" is the wrong word
for this, or there is a different meaning it should have.

Jeff's suggestion of ~ for complement is better.

Okay, thanks. I like it better too.

Yours,

--
Paul ~{:-)
pj@illuminatedcomputing.com

#42Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Paul Jungwirth (#1)
Re: range_agg

Hi Paul,

Just checking if you've had a chance to make progress on this.

Thanks,

--
�lvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

#43Paul A Jungwirth
pj@illuminatedcomputing.com
In reply to: Alvaro Herrera (#42)
Re: range_agg

On Tue, Jul 23, 2019 at 3:32 PM Alvaro Herrera <alvherre@2ndquadrant.com> wrote:

Just checking if you've had a chance to make progress on this.

Not a lot. :-) But I should have more time for it the next few weeks
than I did the last few. I do have some code for creating concrete
multirange types (used when you create a concrete range type) and
filling in a TypeCacheEntry based on the range type oid---which I know
is all very modest progress. I've been working on a multirange_in
function and mostly just learning about Postgres varlena and TOASTed
objects by reading the code for range_in & array_in.

Here is something from my multirangetypes.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
{
int32 vl_len_; /* varlena header (do not touch
directly!) */
Oid multirangetypid; /* multirange type's own OID */
/*
* 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;

I'm working on parsing a multirange much like we parse an array,
although it's a lot simpler because it's a single dimension and there
are no nulls.

I know that's not much to go on, but let me know if any of it worries you. :-)

Paul

#44Thomas Munro
thomas.munro@gmail.com
In reply to: Paul A Jungwirth (#43)
Re: range_agg

On Wed, Jul 24, 2019 at 5:13 PM Paul A Jungwirth
<pj@illuminatedcomputing.com> wrote:

On Tue, Jul 23, 2019 at 3:32 PM Alvaro Herrera <alvherre@2ndquadrant.com> wrote:

Just checking if you've had a chance to make progress on this.

Not a lot. :-) But I should have more time for it the next few weeks
than I did the last few. ...

Hi Paul,

I didn't follow this thread, but as the CF is coming to a close, I'm
interpreting the above to mean that this is being worked on and there
is a good chance of a new patch in time for September. Therefore I
have moved this entry to that 'fest.

Thanks,

--
Thomas Munro
https://enterprisedb.com

#45Paul A Jungwirth
pj@illuminatedcomputing.com
In reply to: Paul A Jungwirth (#26)
Re: range_agg

On Mon, Jul 8, 2019 at 9:46 AM Paul A Jungwirth
<pj@illuminatedcomputing.com> wrote:

- A multirange type is an extra thing you get when you define a range
(just like how you get a tstzrange[]). Therefore....

I've been able to make a little more progress on multiranges the last
few days, but it reminded me of an open question I've had for awhile:
typmods! I see places in the range code that gesture toward supporting
typmods, but none of the existing range types permit them. For
example:

postgres=# select '5'::numeric(4,2);
numeric
---------
5.00
(1 row)

postgres=# select '[1,4)'::numrange(4,2);
ERROR: type modifier is not allowed for type "numrange"
LINE 1: select '[1,4)'::numrange(4,2);

So I'm wondering how seriously I should take this for multiranges? I
guess if a range type did support typmods, it would just delegate to
the underlying element type for their meaning, and so a multirange
should delegate it too? Is there any historical discussion around
typemods on range types?

Thanks!
Paul

#46Jeff Davis
pgsql@j-davis.com
In reply to: Paul A Jungwirth (#45)
Re: range_agg

On Sat, 2019-08-17 at 10:47 -0700, Paul A Jungwirth wrote:

So I'm wondering how seriously I should take this for multiranges? I
guess if a range type did support typmods, it would just delegate to
the underlying element type for their meaning, and so a multirange
should delegate it too? Is there any historical discussion around
typemods on range types?

I did find a few references:

/messages/by-id/1288029716.8645.4.camel@jdavis-ux.asterdata.local

/messages/by-id/20110111191334.GB11603@fetter.org
/messages/by-id/1296974485.27157.136.camel@jdavis

I'd be interested in ways that we can use a typmod-like concept to
improve the type system. Unfortunately, typmod is just not
sophisticated enough to do very much because it's lost through function
calls. Improving that would be a separate and challenging project.

So, I wouldn't spend a lot of time on typmod for multiranges.

Regards,
Jeff Davis

#47Paul A Jungwirth
pj@illuminatedcomputing.com
In reply to: Jeff Davis (#46)
Re: range_agg

On Tue, Aug 20, 2019 at 10:33 PM Jeff Davis <pgsql@j-davis.com> wrote:

Is there any historical discussion around
typemods on range types?

I did find a few references:

Thanks for looking those up! It's very interesting to see some of the
original discussion around range types.

Btw this is true of so much, isn't it?:

It's more a property of the
column than the type.

Sometimes I think about having a maybe<T> type instead of null/not
null. SQL nulls are already very "monadic" I think but nullability
doesn't travel. I feel like someone ought to write a paper about that,
but I don't know of any. This is tantalizingly close (and a fun read)
but really about something else:
https://www.researchgate.net/publication/266657590_Incomplete_data_what_went_wrong_and_how_to_fix_it
Since you're getting into Rust maybe you can update the wiki page
mentioned in those threads about refactoring the type system. :-)
Anyway sorry for the digression. . . .

So, I wouldn't spend a lot of time on typmod for multiranges.

Okay, thanks! There is plenty else to do. I think I'm already
supporting it as much as range types do.

Btw I have working multirange_{send,recv,in,out} now, and I
automatically create a multirange type and its array type when someone
creates a new range type. I have a decent start on passing tests and
no compiler warnings. I also have a start on anymultirange and
anyrangearray. (I think I need the latter to support a range-like
constructor function, so you can say `int4multirange(int4range(1,4),
int4range(8,10))`.) I want to get the any* types done and improve the
test coverage, and then I'll probably be ready to share a patch.

Here are a couple other questions:

- Does anyone have advice for the typanalyze function? I feel pretty
out of my depth there (although I haven't looked into typanalyze stuff
very deeply yet). I can probably get some inspiration from
range_typanalyze and array_typanalyze, but those are both quite
detailed (their statistics functions that is).

- What should a multirange do if you give it an empty range? I'm
thinking it should just ignore it, but then `'{}'::int4multirange =
'{empty}'::int4multirange`. Does that seem okay? (It does to me
actually, if we think of `empty` as the additive identity. Similarly
mr + empty = mr.

- What should a multirange do if you give it a null, like
`int4multirange(int4range(1,4), null)`. I'm thinking it should be
null, just like mr + null = null. Right?

Thanks!
Paul

#48Jeff Davis
pgsql@j-davis.com
In reply to: Paul A Jungwirth (#47)
Re: range_agg

On Wed, 2019-08-21 at 21:54 -0700, Paul A Jungwirth wrote:

Sometimes I think about having a maybe<T> type instead of null/not
null. SQL nulls are already very "monadic" I think but nullability
doesn't travel.

Yeah, that would be a great direction, but there is some additional
complexity that we'd need to deal with that a "normal" compiler does
not:

* handling both existing global types (like int) as well as on-the-
fly types like Maybe<Either<Int,Bool>>
* types need to do more things, like have serialized representations,
interface with indexing strategies, and present the optimizer with
choices that may influence which indexes can be used or not
* at some point needs to work with normal SQL types and NULL
* there are a lot of times we care not just whether a type is
sortable, but we actually care about the way it's sorted (e.g.
localization). typeclasses+newtype would probably be unacceptable for
trying to match SQL behavior here.

I'm all in favor of pursuing this, but it's not going to bear fruit
very soon.

Btw I have working multirange_{send,recv,in,out} now, and I
automatically create a multirange type and its array type when
someone
creates a new range type. I have a decent start on passing tests and
no compiler warnings. I also have a start on anymultirange and
anyrangearray. (I think I need the latter to support a range-like
constructor function, so you can say `int4multirange(int4range(1,4),
int4range(8,10))`.) I want to get the any* types done and improve the
test coverage, and then I'll probably be ready to share a patch.

Awesome!

Here are a couple other questions:

- Does anyone have advice for the typanalyze function? I feel pretty
out of my depth there (although I haven't looked into typanalyze
stuff
very deeply yet). I can probably get some inspiration from
range_typanalyze and array_typanalyze, but those are both quite
detailed (their statistics functions that is).

I think Alexander Korotkov did a lot of the heavy lifting here, perhaps
he has a comment? I'd keep it simple for now if you can, and we can try
to improve it later.

- What should a multirange do if you give it an empty range? I'm
thinking it should just ignore it, but then `'{}'::int4multirange =
'{empty}'::int4multirange`. Does that seem okay? (It does to me
actually, if we think of `empty` as the additive identity. Similarly
mr + empty = mr.

I agree. Multiranges are more than just an array of ranges, so they
coalesce into some canonical form.

- What should a multirange do if you give it a null, like
`int4multirange(int4range(1,4), null)`. I'm thinking it should be
null, just like mr + null = null. Right?

Yes. NULL is for the overall multirange datum (that is, a multirange
column can be NULL), but I don't think individual parts of a datatype
make much sense as NULL. So, I agree that mr + null = null. (Note that
arrays and records can have NULL parts, but I don't see a reason we
should follow those examples for multiranges.)

Regards,
Jeff Davis

#49Paul A Jungwirth
pj@illuminatedcomputing.com
In reply to: Jeff Davis (#48)
Re: range_agg

Btw I have working multirange_{send,recv,in,out} now. . . .

Just about all the other operators are done too, but I wonder what
symbols people like for union and minus? Range uses + for union. I
have working code and tests that adds this:

r + mr = mr
mr + r = mr
mr + mr = mr

But I would like to use a different symbol instead, like ++, so I can
have all four:

r ++ r = mr
r ++ mr = mr
mr ++ r = mr
mr ++ mr = mr

(The existing r + r operator throws an error if the inputs have a gap.)

The trouble is that ++ isn't allowed. (Neither is --.) From
https://www.postgresql.org/docs/11/sql-createoperator.html :

A multicharacter operator name cannot end in + or -, unless the name also contains at least one of these characters:
~ ! @ # % ^ & | ` ?

So are there any other suggestions? I'm open to arguments that I
should just use +, but I think having a way to add two simple ranges
and get a multirange would be nice too, so my preference is to find a
new operator. It should work with minus and intersection (* for
ranges) too. Some proposals:

+* and -* and ** (* as in regex "zero or many" reminds me of how a
multirange holds zero or many ranges. ** is confusing though because
it's like exponentiation.)

@+ and @- and @* (I dunno why but I kind of like it. We already have @> and <@.)

<+> and <-> and <*> (I was hoping for (+) etc for the math connection
to circled operators, but this is close. Maybe this would be stronger
if multirange_{in,out} used <> delims instead of {}, although I also
like how {} is consistent with arrays.)

Anyone else have any thoughts?

Thanks,
Paul

#50Jeff Davis
pgsql@j-davis.com
In reply to: Paul A Jungwirth (#49)
Re: range_agg

On Sun, 2019-09-01 at 06:26 -0700, Paul A Jungwirth wrote:

@+ and @- and @* (I dunno why but I kind of like it. We already have
@> and <@.)

I think I like this proposal best; it reminds me of perl. Though some
might say that's an argument against it.

Regards,
Jeff Davis

#51Paul A Jungwirth
pj@illuminatedcomputing.com
In reply to: Jeff Davis (#50)
Re: range_agg

On Thu, Sep 5, 2019 at 10:15 AM Jeff Davis <pgsql@j-davis.com> wrote:

On Sun, 2019-09-01 at 06:26 -0700, Paul A Jungwirth wrote:

@+ and @- and @* (I dunno why but I kind of like it. We already have
@> and <@.)

I think I like this proposal best; it reminds me of perl. Though some
might say that's an argument against it.

Thanks Jeff, it's my favorite too. :-) Strangely it feels the hardest
to justify. Right now I have + and - and * implemented but I'll change
them to @+ and @- and @* so that I can support `range R range =
multirange` too.

Btw is there any reason to send a "preview" patch with my current
progress, since we're starting a new commit fest? Here is what I have
left to do:

- Change those three operators.
- Write range_intersect_agg. (range_agg is done but needs some tests
before I commit it.)
- Write documentation.
- Add multiranges to resolve_generic_type, and figure out how to test
that (see the other thread about two latent range-related bugs there).
- Rebase on current master. (It should be just a few weeks behind right now.)
- Run pgindent to make sure I'm conforming to whitespace/style guidelines.
- Split it up into a few separate patch files.

Right now I'm planning to do all that before sending a patch. I'm
happy to send something something in-progress too, but I don't want to
waste any reviewers' time. If folks want an early peak though let me
know. (You can also find my messy progress at
https://github.com/pjungwir/postgresql/tree/multirange)

Also here are some other items that won't be in my next patch, but
should probably be done (maybe by someone else but I'm happy to figure
it out too) before this is really committed:

- typanalyze
- selectivity
- gist support
- spgist support

If anyone would like to help with those, let me know. :-)

Yours,
Paul

#52Jeff Davis
pgsql@j-davis.com
In reply to: Paul A Jungwirth (#51)
Re: range_agg

On Thu, 2019-09-05 at 10:45 -0700, Paul A Jungwirth wrote:

Right now I'm planning to do all that before sending a patch. I'm
happy to send something something in-progress too, but I don't want
to
waste any reviewers' time. If folks want an early peak though let me
know. (You can also find my messy progress at
https://github.com/pjungwir/postgresql/tree/multirange)

Sounds good. The rule I use is: "will the feedback I get be helpful, or
just tell me about obvious problems I already know about".

Regards,
Jeff Davis

#53Paul A Jungwirth
pj@illuminatedcomputing.com
In reply to: Jeff Davis (#52)
4 attachment(s)
Re: range_agg

On Thu, Sep 5, 2019 at 11:52 AM Jeff Davis <pgsql@j-davis.com> wrote:

On Thu, 2019-09-05 at 10:45 -0700, Paul A Jungwirth wrote:

Right now I'm planning to do all that before sending a patch. I'm
happy to send something something in-progress too, but I don't want
to
waste any reviewers' time. If folks want an early peak though let me
know. (You can also find my messy progress at
https://github.com/pjungwir/postgresql/tree/multirange)

Sounds good. The rule I use is: "will the feedback I get be helpful, or
just tell me about obvious problems I already know about".

Here are some patches to add multiranges. I tried to split things up a
bit but most things landed in parts 1 & 2.

Things I haven't done (but would be interested in doing or getting help with):

- gist opclass
- spgist opclass
- typanalyze
- selectivity
- anyrangearray
- anymultirangearray?
- UNNEST for multirange and/or a way to convert it to an array
- indexing/subscripting (see patch for standardized subscripting)

Attachments:

v3-0003-multirange-pg_dump.patchapplication/octet-stream; name=v3-0003-multirange-pg_dump.patchDownload
From c372b651a8d575cf3c6ed80e4f581ef03373c06a Mon Sep 17 00:00:00 2001
From: "Paul A. Jungwirth" <pj@illuminatedcomputing.com>
Date: Sat, 21 Sep 2019 21:28:37 -0700
Subject: [PATCH v3 3/4] multirange pg_dump

---
 src/bin/pg_dump/pg_dump.c | 7 ++++++-
 src/bin/pg_dump/pg_dump.h | 1 +
 2 files changed, 7 insertions(+), 1 deletion(-)

diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index f01fea5b91..124ee1a842 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1586,7 +1586,7 @@ selectDumpableType(TypeInfo *tyinfo, Archive *fout)
 	}
 
 	/* skip auto-generated array types */
-	if (tyinfo->isArray)
+	if (tyinfo->isArray || tyinfo->isMultirange)
 	{
 		tyinfo->dobj.objType = DO_DUMMY_TYPE;
 
@@ -4926,6 +4926,11 @@ getTypes(Archive *fout, int *numTypes)
 		else
 			tyinfo[i].isArray = false;
 
+		if (tyinfo[i].typtype == 'm')
+			tyinfo[i].isMultirange = true;
+		else
+			tyinfo[i].isMultirange = false;
+
 		/* Decide whether we want to dump it */
 		selectDumpableType(&tyinfo[i], fout);
 
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index ec5a924b8f..98ba052d5b 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -175,6 +175,7 @@ typedef struct _typeInfo
 	char		typrelkind;		/* 'r', 'v', 'c', etc */
 	char		typtype;		/* 'b', 'c', etc */
 	bool		isArray;		/* true if auto-generated array type */
+	bool		isMultirange;	/* true if auto-generated multirange type */
 	bool		isDefined;		/* true if typisdefined */
 	/* If needed, we'll create a "shell type" entry for it; link that here: */
 	struct _shellTypeInfo *shellType;	/* shell-type entry, or NULL */
-- 
2.11.0

v3-0004-multirange-docs.patchapplication/octet-stream; name=v3-0004-multirange-docs.patchDownload
From 8d1c8596d5712acc3719d5e734e6c49847477f06 Mon Sep 17 00:00:00 2001
From: "Paul A. Jungwirth" <pj@illuminatedcomputing.com>
Date: Sat, 21 Sep 2019 21:28:47 -0700
Subject: [PATCH v3 4/4] multirange docs

---
 doc/src/sgml/extend.sgml     |  28 +++-
 doc/src/sgml/func.sgml       | 302 +++++++++++++++++++++++++++++++++++++++----
 doc/src/sgml/rangetypes.sgml |  47 ++++++-
 3 files changed, 342 insertions(+), 35 deletions(-)

diff --git a/doc/src/sgml/extend.sgml b/doc/src/sgml/extend.sgml
index 8dc2b893f7..0bda0f35bf 100644
--- a/doc/src/sgml/extend.sgml
+++ b/doc/src/sgml/extend.sgml
@@ -229,9 +229,9 @@
    </indexterm>
 
     <para>
-     Five pseudo-types of special interest are <type>anyelement</type>,
+     Six pseudo-types of special interest are <type>anyelement</type>,
      <type>anyarray</type>, <type>anynonarray</type>, <type>anyenum</type>,
-     and <type>anyrange</type>,
+     <type>anyrange</type>, and <type>anymultirange</type>.
      which are collectively called <firstterm>polymorphic types</firstterm>.
      Any function declared using these types is said to be
      a <firstterm>polymorphic function</firstterm>.  A polymorphic function can
@@ -250,15 +250,15 @@
      position declared as <type>anyarray</type> can have any array data type,
      but similarly they must all be the same type.  And similarly,
      positions declared as <type>anyrange</type> must all be the same range
-     type.  Furthermore, if there are
+     type.  Likewise for <type>anymultirange</type>.
+    </para>
+
+    <para>
+     Furthermore, if there are
      positions declared <type>anyarray</type> and others declared
      <type>anyelement</type>, the actual array type in the
      <type>anyarray</type> positions must be an array whose elements are
      the same type appearing in the <type>anyelement</type> positions.
-     Similarly, if there are positions declared <type>anyrange</type>
-     and others declared <type>anyelement</type>, the actual range type in
-     the <type>anyrange</type> positions must be a range whose subtype is
-     the same type appearing in the <type>anyelement</type> positions.
      <type>anynonarray</type> is treated exactly the same as <type>anyelement</type>,
      but adds the additional constraint that the actual type must not be
      an array type.
@@ -268,6 +268,20 @@
     </para>
 
     <para>
+     Similarly, if there are positions declared <type>anyrange</type>
+     and others declared <type>anyelement</type>, the actual range type in
+     the <type>anyrange</type> positions must be a range whose subtype is
+     the same type appearing in the <type>anyelement</type> positions.
+     The type <type>anymultirange</type> accepts a multirange
+     whose contents match the other polymorphic types.
+     That is it must hold ranges matching <type>anyrange</type>
+     and base type elements matching <type>anyelement</type> (if either of
+     those are present). If <type>anyarray</type> is present its elements
+     must match the base type of the <type>anyrange</type> and/or the base
+     type of the ranges in an </type>anymultirange</type>.
+    </para>
+
+    <para>
      Thus, when more than one argument position is declared with a polymorphic
      type, the net effect is that only certain combinations of actual argument
      types are allowed.  For example, a function declared as
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 567d2ecf3a..2eb1ccfc1f 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -14473,7 +14473,9 @@ NULL baz</literallayout>(3 rows)</entry>
 
   <para>
    <xref linkend="range-operators-table"/> shows the operators
-   available for range types.
+   available for range and multirange types.
+   Many of these operators will accept either a range or multirange
+   on either side.
   </para>
 
     <table id="range-operators-table">
@@ -14483,7 +14485,7 @@ NULL baz</literallayout>(3 rows)</entry>
        <row>
         <entry>Operator</entry>
         <entry>Description</entry>
-        <entry>Example</entry>
+        <entry>Examples</entry>
         <entry>Result</entry>
        </row>
       </thead>
@@ -14491,136 +14493,247 @@ NULL baz</literallayout>(3 rows)</entry>
        <row>
         <entry> <literal>=</literal> </entry>
         <entry>equal</entry>
-        <entry><literal>int4range(1,5) = '[1,4]'::int4range</literal></entry>
+        <entry><literallayout class="monospaced">int4range(1,5) = '[1,4]'::int4range
+'{[1,5)}'::int4multirange = '{[1,4]}'::int4multirange</literallayout></entry>
         <entry><literal>t</literal></entry>
        </row>
 
        <row>
         <entry> <literal>&lt;&gt;</literal> </entry>
         <entry>not equal</entry>
-        <entry><literal>numrange(1.1,2.2) &lt;&gt; numrange(1.1,2.3)</literal></entry>
+        <entry><literallayout class="monospaced">numrange(1.1,2.2) &lt;&gt; numrange(1.1,2.3)
+'{[1.1,2.2)}'::nummultirange &lt;&gt; '{[1.1,2.3)}'::nummultirange</literallayout></entry>
         <entry><literal>t</literal></entry>
        </row>
 
        <row>
         <entry> <literal>&lt;</literal> </entry>
         <entry>less than</entry>
-        <entry><literal>int4range(1,10) &lt; int4range(2,3)</literal></entry>
+        <entry><literallayout class="monospaced">int4range(1,10) &lt; int4range(2,3)
+'{[1,10)}'::int4multirange &lt; '{[2,3)}'::int4multirange</literallayout>
+        </entry>
         <entry><literal>t</literal></entry>
        </row>
 
        <row>
         <entry> <literal>&gt;</literal> </entry>
         <entry>greater than</entry>
-        <entry><literal>int4range(1,10) &gt; int4range(1,5)</literal></entry>
+        <entry>
+          <literallayout class="monospaced">int4range(1,10) &gt; int4range(1,5)
+'{[1,10)}'::int4multirange &gt; '{[1,5)}'::int4multirange</literallayout>
+        </entry>
         <entry><literal>t</literal></entry>
        </row>
 
        <row>
         <entry> <literal>&lt;=</literal> </entry>
         <entry>less than or equal</entry>
-        <entry><literal>numrange(1.1,2.2) &lt;= numrange(1.1,2.2)</literal></entry>
+        <entry>
+          <literallayout class="monospaced">numrange(1.1,2.2) &lt;= numrange(1.1,2.2)
+'{[1.1,2.2)}'::nummultirange &lt;= '{[1.1,2.2)}'::nummultirange</literallayout>
+        </entry>
         <entry><literal>t</literal></entry>
        </row>
 
        <row>
         <entry> <literal>&gt;=</literal> </entry>
         <entry>greater than or equal</entry>
-        <entry><literal>numrange(1.1,2.2) &gt;= numrange(1.1,2.0)</literal></entry>
+        <entry>
+          <literallayout class="monospaced">numrange(1.1,2.2) &gt;= numrange(1.1,2.0)
+'{[1.1,2.2)}'::nummultirange &gt;= '{[1.1,2.0)}'::nummultirange</literallayout>
+        </entry>
+        <entry><literal>t</literal></entry>
+       </row>
+
+       <row>
+        <entry> <literal>@&gt;</literal> </entry>
+        <entry>contains multirange</entry>
+        <entry>
+          <literallayout class="monospaced">int4range(2,4) @&gt; '{[2,3)}'::int4multirange
+'{[2,4)}'::int4multirange @&gt; '{[2,3)}'::int4multirange</literallayout>
+        </entry>
         <entry><literal>t</literal></entry>
        </row>
 
        <row>
         <entry> <literal>@&gt;</literal> </entry>
         <entry>contains range</entry>
-        <entry><literal>int4range(2,4) @&gt; int4range(2,3)</literal></entry>
+        <entry>
+          <literallayout class="monospaced">int4range(2,4) @&gt; int4range(2,3)
+'{[2,4)}'::int4multirange @&gt; int4range(2,3)</literallayout>
+        </entry>
         <entry><literal>t</literal></entry>
        </row>
 
        <row>
         <entry> <literal>@&gt;</literal> </entry>
         <entry>contains element</entry>
-        <entry><literal>'[2011-01-01,2011-03-01)'::tsrange @&gt; '2011-01-10'::timestamp</literal></entry>
+        <entry>
+          <literallayout class="monospaced">'[2011-01-01,2011-03-01)'::tsrange @&gt; '2011-01-10'::timestamp
+'{[2011-01-01,2011-03-01)}'::tsmultirange @&gt; '2011-01-10'::timestamp</literallayout>
+        </entry>
+        <entry><literal>t</literal></entry>
+       </row>
+
+       <row>
+        <entry> <literal>&lt;@</literal> </entry>
+        <entry>multirange is contained by</entry>
+        <entry>
+          <literallayout class="monospaced">'{[2,4)}'::int4multirange &lt;@ int4range(1,7)
+'{[2,4)}'::int4multirange &lt;@ '{[1,7)}'::int4multirange</literallayout>
+        </entry>
         <entry><literal>t</literal></entry>
        </row>
 
        <row>
         <entry> <literal>&lt;@</literal> </entry>
         <entry>range is contained by</entry>
-        <entry><literal>int4range(2,4) &lt;@ int4range(1,7)</literal></entry>
+        <entry>
+          <literallayout class="monospaced">int4range(2,4) &lt;@ int4range(1,7)
+int4range(2,4) &lt;@ '{[1,7)}'::int4multirange</literallayout>
+        </entry>
         <entry><literal>t</literal></entry>
        </row>
 
        <row>
         <entry> <literal>&lt;@</literal> </entry>
         <entry>element is contained by</entry>
-        <entry><literal>42 &lt;@ int4range(1,7)</literal></entry>
+        <entry>
+          <literallayout class="monospaced">42 &lt;@ int4range(1,7)
+42 &lt;@ '{[1,7)}'::int4multirange</literallayout>
+        </entry>
         <entry><literal>f</literal></entry>
        </row>
 
        <row>
         <entry> <literal>&amp;&amp;</literal> </entry>
         <entry>overlap (have points in common)</entry>
-        <entry><literal>int8range(3,7) &amp;&amp; int8range(4,12)</literal></entry>
+        <entry>
+          <literallayout class="monospaced">int8range(3,7) &amp;&amp; int8range(4,12)
+int8range(3,7) &amp;&amp; '{[4,12)}'::int8multirange
+'{[3,7)}'::int8multirange &amp;&amp; int8range(4,12)
+'{[3,7)}'::int8multirange &amp;&amp; '{[4,12)}'::int8multirange</literallayout>
+        </entry>
         <entry><literal>t</literal></entry>
        </row>
 
        <row>
         <entry> <literal>&lt;&lt;</literal> </entry>
         <entry>strictly left of</entry>
-        <entry><literal>int8range(1,10) &lt;&lt; int8range(100,110)</literal></entry>
+        <entry>
+          <literallayout class="monospaced">int8range(1,10) &lt;&lt; int8range(100,110)
+int8range(1,10) &lt;&lt; '{[100,110)}'::int8multirange
+'{[1,10)}'::int8multirange &lt;&lt; int8range(100,110)
+'{[1,10)}'::int8multirange &lt;&lt; '{[100,110)}'::int8multirange</literallayout>
+        </entry>
         <entry><literal>t</literal></entry>
        </row>
 
        <row>
         <entry> <literal>&gt;&gt;</literal> </entry>
         <entry>strictly right of</entry>
-        <entry><literal>int8range(50,60) &gt;&gt; int8range(20,30)</literal></entry>
+        <entry>
+          <literallayout class="monospaced">int8range(50,60) &gt;&gt; int8range(20,30)
+int8range(50,60) &gt;&gt; '{[20,30)}'::int8multirange
+'{[50,60)}'::int8multirange &gt;&gt; int8range(20,30)
+'{[50,60)}'::int8multirange &gt;&gt; '{[20,30)}'::int8multirange</literallayout>
+        </entry>
         <entry><literal>t</literal></entry>
        </row>
 
        <row>
         <entry> <literal>&amp;&lt;</literal> </entry>
         <entry>does not extend to the right of</entry>
-        <entry><literal>int8range(1,20) &amp;&lt; int8range(18,20)</literal></entry>
+        <entry>
+          <literallayout class="monospaced">int8range(1,20) &amp;&lt; int8range(18,20)
+int8range(1,20) &amp;&lt; '{[18,20)}'::int8multirange
+'{[1,20)}'::int8multirange &amp;&lt; int8range(18,20)
+'{[1,20)}'::int8multirange &amp;&lt; '{[18,20)}'::int8multirange</literallayout>
+        </entry>
         <entry><literal>t</literal></entry>
        </row>
 
        <row>
         <entry> <literal>&amp;&gt;</literal> </entry>
         <entry>does not extend to the left of</entry>
-        <entry><literal>int8range(7,20) &amp;&gt; int8range(5,10)</literal></entry>
+        <entry>
+          <literallayout class="monospaced">int8range(7,20) &amp;&gt; int8range(5,10)
+int8range(7,20) &amp;&gt; '{[5,10)}'::int8multirange
+'{[7,20)}'::int8multirange &amp;&gt; int8range(5,10)
+'{[7,20)}'::int8multirange &amp;&gt; '{[5,10)}'::int8multirange</literallayout>
+        </entry>
         <entry><literal>t</literal></entry>
        </row>
 
        <row>
         <entry> <literal>-|-</literal> </entry>
         <entry>is adjacent to</entry>
-        <entry><literal>numrange(1.1,2.2) -|- numrange(2.2,3.3)</literal></entry>
+        <entry>
+          <literallayout class="monospaced">numrange(1.1,2.2) -|- numrange(2.2,3.3)
+numrange(1.1,2.2) -|- '{[2.2,3.3)}'::nummultirange
+'{[1.1,2.2)}'::nummultirange -|- numrange(2.2,3.3)
+'{[1.1,2.2)}'::nummultirange -|- '{[2.2,3.3)}'::nummultirange</literallayout>
+        </entry>
         <entry><literal>t</literal></entry>
        </row>
 
        <row>
         <entry> <literal>+</literal> </entry>
         <entry>union</entry>
-        <entry><literal>numrange(5,15) + numrange(10,20)</literal></entry>
+        <entry><literallayout class="monospaced">numrange(5,15) + numrange(10,20)</literallayout></entry>
         <entry><literal>[5,20)</literal></entry>
        </row>
 
        <row>
+        <entry> <literal>@+</literal> </entry>
+        <entry>safe union</entry>
+        <entry>
+          <literallayout class="monospaced">numrange(5,10) @+ numrange(15,20)
+numrange(5,10) @+ '{[15,20)}'::nummultirange
+'{[5,10)}'::nummultirange @+ numrange(15,20)
+'{[5,10)}'::nummultirange @+ '{[15,20)}'::nummultirange</literallayout>
+        </entry>
+        <entry><literal>{[5,10), [15,20)}</literal></entry>
+       </row>
+
+       <row>
         <entry> <literal>*</literal> </entry>
         <entry>intersection</entry>
-        <entry><literal>int8range(5,15) * int8range(10,20)</literal></entry>
+        <entry><literallayout class="monospaced">int8range(5,15) * int8range(10,20)</literallayout></entry>
         <entry><literal>[10,15)</literal></entry>
        </row>
 
        <row>
+        <entry> <literal>@*</literal> </entry>
+        <entry>safe intersection</entry>
+        <entry>
+          <literallayout class="monospaced">int8range(5,15) @* int8range(10,20)
+int8range(5,15) @* '{[10,20)}'::int8multirange
+'{[5,15)}'::int8multirange @* int8range(10,20)
+'{[5,15)}'::int8multirange @* '{[10,20)}'::int8multirange</literallayout>
+        </entry>
+        <entry><literal>{[10,15)}</literal></entry>
+       </row>
+
+       <row>
         <entry> <literal>-</literal> </entry>
         <entry>difference</entry>
-        <entry><literal>int8range(5,15) - int8range(10,20)</literal></entry>
+        <entry><literallayout class="monospaced">int8range(5,15) - int8range(10,20)</literallayout></entry>
         <entry><literal>[5,10)</literal></entry>
        </row>
 
+       <row>
+        <entry> <literal>@-</literal> </entry>
+        <entry>safe difference</entry>
+        <entry>
+          <literallayout class="monospaced">int8range(5,20) @- int8range(10,15)
+int8range(5,20) @- '{[10,15)}'::int8multirange
+'{[5,20)}'::int8multirange @- int8range(10,15)
+'{[5,20)}'::int8multirange @- '{[10,15)}'::int8multirange</literallayout>
+        </entry>
+        <entry><literal>{[5,10), [15,20)}</literal></entry>
+       </row>
+
       </tbody>
      </tgroup>
     </table>
@@ -14636,19 +14749,28 @@ NULL baz</literallayout>(3 rows)</entry>
 
   <para>
    The left-of/right-of/adjacent operators always return false when an empty
-   range is involved; that is, an empty range is not considered to be either
-   before or after any other range.
+   range or multirange is involved; that is, an empty range is not considered to
+   be either before or after any other range.
+  </para>
+
+  <para>
+   Elsewhere empty ranges and multiranges are treated as the additive identity:
+   anything unioned with an empty value is itself. Anything minus an empty
+   value is itself. An empty multirange has exactly the same points as an empty
+   range. Every range contains the empty range. Every multirange contains as many
+   empty ranges as you like.
   </para>
 
   <para>
    The union and difference operators will fail if the resulting range would
    need to contain two disjoint sub-ranges, as such a range cannot be
-   represented.
+   represented. The safe versions of union, intersection, and difference
+   return multiranges, so they can handle gaps without failing.
   </para>
 
   <para>
    <xref linkend="range-functions-table"/> shows the functions
-   available for use with range types.
+   available for use with range and multirange types.
   </para>
 
   <indexterm>
@@ -14700,6 +14822,17 @@ NULL baz</literallayout>(3 rows)</entry>
        <row>
         <entry>
          <literal>
+          <function>lower</function>(<type>anymultirange</type>)
+         </literal>
+        </entry>
+        <entry>multirange's element type</entry>
+        <entry>lower bound of multirange</entry>
+        <entry><literal>lower('{[1.1,2.2)}'::nummultirange)</literal></entry>
+        <entry><literal>1.1</literal></entry>
+       </row>
+       <row>
+        <entry>
+         <literal>
           <function>upper</function>(<type>anyrange</type>)
          </literal>
         </entry>
@@ -14711,6 +14844,17 @@ NULL baz</literallayout>(3 rows)</entry>
        <row>
         <entry>
          <literal>
+          <function>upper</function>(<type>anymultirange</type>)
+         </literal>
+        </entry>
+        <entry>multirange's element type</entry>
+        <entry>upper bound of multirange</entry>
+        <entry><literal>upper('{[1.1,2.2)}'::nummultirange)</literal></entry>
+        <entry><literal>2.2</literal></entry>
+       </row>
+       <row>
+        <entry>
+         <literal>
           <function>isempty</function>(<type>anyrange</type>)
          </literal>
         </entry>
@@ -14722,6 +14866,17 @@ NULL baz</literallayout>(3 rows)</entry>
        <row>
         <entry>
          <literal>
+          <function>isempty</function>(<type>anymultirange</type>)
+         </literal>
+        </entry>
+        <entry><type>boolean</type></entry>
+        <entry>is the multirange empty?</entry>
+        <entry><literal>isempty('{[1.1,2.2)}'::nummultirange)</literal></entry>
+        <entry><literal>false</literal></entry>
+       </row>
+       <row>
+        <entry>
+         <literal>
           <function>lower_inc</function>(<type>anyrange</type>)
          </literal>
         </entry>
@@ -14733,6 +14888,17 @@ NULL baz</literallayout>(3 rows)</entry>
        <row>
         <entry>
          <literal>
+          <function>lower_inc</function>(<type>anymultirange</type>)
+         </literal>
+        </entry>
+        <entry><type>boolean</type></entry>
+        <entry>is the lower bound inclusive?</entry>
+        <entry><literal>lower_inc('{[1.1,2.2)}'::nummultirange)</literal></entry>
+        <entry><literal>true</literal></entry>
+       </row>
+       <row>
+        <entry>
+         <literal>
           <function>upper_inc</function>(<type>anyrange</type>)
          </literal>
         </entry>
@@ -14744,6 +14910,17 @@ NULL baz</literallayout>(3 rows)</entry>
        <row>
         <entry>
          <literal>
+          <function>upper_inc</function>(<type>anymultirange</type>)
+         </literal>
+        </entry>
+        <entry><type>boolean</type></entry>
+        <entry>is the upper bound inclusive?</entry>
+        <entry><literal>upper_inc('{[1.1,2.2)}'::nummultirange)</literal></entry>
+        <entry><literal>false</literal></entry>
+       </row>
+       <row>
+        <entry>
+         <literal>
           <function>lower_inf</function>(<type>anyrange</type>)
          </literal>
         </entry>
@@ -14755,6 +14932,17 @@ NULL baz</literallayout>(3 rows)</entry>
        <row>
         <entry>
          <literal>
+          <function>lower_inf</function>(<type>anymultirange</type>)
+         </literal>
+        </entry>
+        <entry><type>boolean</type></entry>
+        <entry>is the lower bound infinite?</entry>
+        <entry><literal>lower_inf('{(,)}'::datemultirange)</literal></entry>
+        <entry><literal>true</literal></entry>
+       </row>
+       <row>
+        <entry>
+         <literal>
           <function>upper_inf</function>(<type>anyrange</type>)
          </literal>
         </entry>
@@ -14766,6 +14954,17 @@ NULL baz</literallayout>(3 rows)</entry>
        <row>
         <entry>
          <literal>
+          <function>upper_inf</function>(<type>anymultirange</type>)
+         </literal>
+        </entry>
+        <entry><type>boolean</type></entry>
+        <entry>is the upper bound infinite?</entry>
+        <entry><literal>upper_inf('{(,)}'::datemultirange)</literal></entry>
+        <entry><literal>true</literal></entry>
+       </row>
+       <row>
+        <entry>
+         <literal>
           <function>range_merge</function>(<type>anyrange</type>, <type>anyrange</type>)
          </literal>
         </entry>
@@ -14774,16 +14973,27 @@ NULL baz</literallayout>(3 rows)</entry>
         <entry><literal>range_merge('[1,2)'::int4range, '[3,4)'::int4range)</literal></entry>
         <entry><literal>[1,4)</literal></entry>
        </row>
+       <row>
+        <entry>
+         <literal>
+          <function>range_merge</function>(<type>anymultirange</type>)
+         </literal>
+        </entry>
+        <entry><type>anyrange</type></entry>
+        <entry>the smallest range which includes the entire multirange</entry>
+        <entry><literal>range_merge('{[1,2), [3,4)}'::int4multirange)</literal></entry>
+        <entry><literal>[1,4)</literal></entry>
+       </row>
       </tbody>
      </tgroup>
     </table>
 
   <para>
    The <function>lower</function> and  <function>upper</function> functions return null
-   if the range is empty or the requested bound is infinite.
+   if the input range/multirange is empty or the requested bound is infinite.
    The <function>lower_inc</function>, <function>upper_inc</function>,
    <function>lower_inf</function>, and <function>upper_inf</function>
-   functions all return false for an empty range.
+   functions all return false for an empty range or multirange.
   </para>
   </sect1>
 
@@ -15105,6 +15315,44 @@ NULL baz</literallayout>(3 rows)</entry>
      <row>
       <entry>
        <indexterm>
+        <primary>range_agg</primary>
+       </indexterm>
+       <function>
+         range_agg(<replaceable class="parameter">expression</replaceable>)
+       </function>
+      </entry>
+      <entry>
+       <type>anyrange</type>
+      </entry>
+      <entry>
+       <type>multirange</type> of the argument range type
+      </entry>
+      <entry>Yes</entry>
+      <entry>union of the non-null input values</entry>
+     </row>
+
+     <row>
+      <entry>
+       <indexterm>
+        <primary>range_intersect_agg</primary>
+       </indexterm>
+       <function>
+         range_intersect_agg(<replaceable class="parameter">expression</replaceable>)
+       </function>
+      </entry>
+      <entry>
+       <type>anyrange</type>
+      </entry>
+      <entry>
+       <type>multirange</type> of the argument range type
+      </entry>
+      <entry>No</entry>
+      <entry>intersection of the non-null input values</entry>
+     </row>
+
+     <row>
+      <entry>
+       <indexterm>
         <primary>string_agg</primary>
        </indexterm>
        <function>
diff --git a/doc/src/sgml/rangetypes.sgml b/doc/src/sgml/rangetypes.sgml
index 3a034d9b06..3e37b352fc 100644
--- a/doc/src/sgml/rangetypes.sgml
+++ b/doc/src/sgml/rangetypes.sgml
@@ -27,6 +27,13 @@
   ranges from an instrument, and so forth can also be useful.
  </para>
 
+ <para>
+  Every range type has a corresponding multirange type. A multirange is
+  an ordered list of non-continguous, non-empty, non-null ranges. Most
+  range operators also work on multiranges, and they have a few functions
+  of their own.
+ </para>
+
  <sect2 id="rangetypes-builtin">
   <title>Built-in Range Types</title>
 
@@ -235,10 +242,30 @@ SELECT '[4,4]'::int4range;
 SELECT '[4,4)'::int4range;
 </programlisting>
   </para>
+
+  <para>
+   The input for a multirange is curly brackets (<literal>{</literal> and
+   <literal>}</literal>) containing zero or more valid ranges,
+   separated by commas. Whitespace is permitted around the brackets and
+   commas. This is intended to be reminiscent of array syntax, although
+   multiranges are much simpler: they have just one dimension and there is
+   no need to quote their contents. (The bounds of their ranges may be
+   quoted as above however.)
+  </para>
+
+  <para>
+  Examples:
+<programlisting>
+SELECT '{}'::int4multirange;
+SELECT '{[3,7)}'::int4multirange;
+SELECT '{[3,7), [8,9)}'::int4multirange;
+</programlisting>
+  </para>
+
  </sect2>
 
  <sect2 id="rangetypes-construct">
-  <title>Constructing Ranges</title>
+  <title>Constructing Ranges and Multiranges</title>
 
   <para>
    Each range type has a constructor function with the same name as the range
@@ -272,6 +299,19 @@ SELECT int8range(1, 14, '(]');
 SELECT numrange(NULL, 2.2);
 </programlisting>
   </para>
+
+  <para>
+   Each range type also has a multirange constructor with the same name as the
+   multirange type.  The constructor function takes zero or more arguments
+   which are all ranges of the appropriate type.
+   For example:
+
+<programlisting>
+SELECT nummultirange();
+SELECT nummultirange(numrange(1.0, 14.0));
+SELECT nummultirange(numrange(1.0, 14.0), numrange(20.0, 25.0));
+</programlisting>
+  </para>
  </sect2>
 
  <sect2 id="rangetypes-discrete">
@@ -345,6 +385,11 @@ SELECT '[1.234, 5.678]'::floatrange;
   </para>
 
   <para>
+   When you define your own range your automatically get a corresponding
+   multirange type.
+  </para>
+
+  <para>
    Defining your own range type also allows you to specify a different
    subtype B-tree operator class or collation to use, so as to change the sort
    ordering that determines which values fall into a given range.
-- 
2.11.0

v3-0001-multirange-type-basics.patchapplication/octet-stream; name=v3-0001-multirange-type-basics.patchDownload
From ede3130324d795005e195961547d5305336a96ad 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 v3 1/4] multirange type basics

---
 src/backend/catalog/pg_proc.c                 |  16 +-
 src/backend/catalog/pg_range.c                |  11 +-
 src/backend/catalog/pg_type.c                 | 134 +++-
 src/backend/commands/typecmds.c               | 171 ++++-
 src/backend/parser/parse_coerce.c             | 301 +++++++-
 src/backend/utils/adt/Makefile                |   2 +-
 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/catversion.h              |   2 +-
 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 +-
 42 files changed, 2895 insertions(+), 138 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 2a51501d8d..08552850c8 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,34 +788,10 @@ makeArrayTypeName(const char *typeName, Oid typeNamespace)
 {
 	char	   *arr = (char *) palloc(NAMEDATALEN);
 	int			namelen = strlen(typeName);
-	Relation	pg_type_desc;
-	int			i;
-
-	/*
-	 * The idea is to prepend underscores as needed until we make a name that
-	 * doesn't collide with anything...
-	 */
-	pg_type_desc = table_open(TypeRelationId, AccessShareLock);
-
-	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;
-	}
-
-	table_close(pg_type_desc, AccessShareLock);
+	int			underscores;
 
-	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\"",
@@ -883,3 +862,104 @@ 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)
+{
+	Relation	pg_type_desc;
+	int			i;
+
+	pg_type_desc = table_open(TypeRelationId, AccessShareLock);
+
+	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;
+		}
+	}
+
+	table_close(pg_type_desc, AccessShareLock);
+
+	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..f0b1f6f831 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			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 580043233b..92967939f9 100644
--- a/src/backend/utils/adt/Makefile
+++ b/src/backend/utils/adt/Makefile
@@ -18,7 +18,7 @@ OBJS = acl.o amutils.o arrayfuncs.o array_expanded.o array_selfuncs.o \
 	geo_ops.o geo_selfuncs.o geo_spgist.o inet_cidr_ntop.o inet_net_pton.o \
 	int.o int8.o json.o jsonb.o jsonb_gin.o jsonb_op.o jsonb_util.o \
 	jsonfuncs.o jsonpath_gram.o jsonpath.o jsonpath_exec.o \
-	like.o like_support.o lockfuncs.o mac.o mac8.o misc.o name.o \
+	like.o like_support.o lockfuncs.o mac.o mac8.o misc.o multirangetypes.o name.o \
 	network.o network_gist.o network_selfuncs.o network_spgist.o \
 	numeric.o numutils.o oid.o oracle_compat.o \
 	orderedsetaggs.o partitionfuncs.o pg_locale.o pg_lsn.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"
 
 
 /*
@@ -185,6 +186,30 @@ anyrange_out(PG_FUNCTION_ARGS)
 }
 
 /*
+ * 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.
  *
  * We allow this so that PL functions can return VOID without any special
diff --git a/src/backend/utils/adt/rangetypes.c b/src/backend/utils/adt/rangetypes.c
index e5c7e5c7ee..1feebc48dc 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));
 }
 
 /*
@@ -1938,6 +2006,45 @@ range_cmp_bound_values(TypeCacheEntry *typcache, RangeBound *b1,
 }
 
 /*
+ * 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.
  */
 RangeType *
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
@@ -2469,6 +2469,16 @@ type_is_range(Oid typid)
 }
 
 /*
+ * 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
  *
  *		Given the type OID, fetch its category and preferred-type status.
@@ -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 16297a52a1..3f0e7f8576 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;
@@ -759,6 +777,16 @@ lookup_type_cache(Oid type_id, int flags)
 	}
 
 	/*
+	 * 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
 	 */
 	if ((flags & TYPECACHE_DOMAIN_BASE_INFO) &&
@@ -871,6 +899,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
  *
  * Note: we assume we're called in a relatively short-lived context, so it's
@@ -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 b7fac5d295..e6e82fda63 100644
--- a/src/backend/utils/fmgr/funcapi.c
+++ b/src/backend/utils/fmgr/funcapi.c
@@ -438,11 +438,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;
 
@@ -468,12 +470,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;
 
 	/*
@@ -501,6 +506,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;
 		}
@@ -508,7 +517,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 */
@@ -529,19 +538,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))
@@ -608,6 +720,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;
 		}
@@ -632,9 +752,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;
 
@@ -693,6 +815,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;
 		}
@@ -702,12 +839,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 */
@@ -728,19 +865,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 */
 
@@ -760,6 +1000,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/catversion.h b/src/include/catalog/catversion.h
index e312c8f194..6f41eda45c 100644
--- a/src/include/catalog/catversion.h
+++ b/src/include/catalog/catversion.h
@@ -53,6 +53,6 @@
  */
 
 /*							yyyymmddN */
-#define CATALOG_VERSION_NO	201909214
+#define CATALOG_VERSION_NO	201909215
 
 #endif
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 fa7dc96ece..4d319bb5b3 100644
--- a/src/include/catalog/pg_operator.dat
+++ b/src/include/catalog/pg_operator.dat
@@ -3289,5 +3289,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 5e69deeb7a..db1bef821f 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -9702,6 +9702,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 be49e00114..c95a38849a 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 2a584b4b13..6a25fcc9bf 100644
--- a/src/include/catalog/pg_type.h
+++ b/src/include/catalog/pg_type.h
@@ -259,6 +259,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 */
 
@@ -270,6 +271,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'
@@ -285,7 +287,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 */
 
@@ -344,4 +347,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 580e476501..6323034548 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
@@ -99,6 +99,11 @@ typedef struct TypeCacheEntry
 	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 0c24ba692d..0012901432 100644
--- a/src/pl/plpgsql/src/pl_comp.c
+++ b/src/pl/plpgsql/src/pl_comp.c
@@ -513,6 +513,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? */
@@ -2496,6 +2498,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 fc0f14122b..c8c1cd0671 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 68ac56acdb..35155436f0 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.11.0

v3-0002-multirange-operators-and-functions.patchapplication/octet-stream; name=v3-0002-multirange-operators-and-functions.patchDownload
From 759c5f4647cd75932aac57b00f6db06707a2ca06 Mon Sep 17 00:00:00 2001
From: "Paul A. Jungwirth" <pj@illuminatedcomputing.com>
Date: Sat, 21 Sep 2019 21:28:20 -0700
Subject: [PATCH v3 2/4] multirange operators and functions

---
 src/backend/utils/adt/multirangetypes.c       | 1422 ++++++++++++++-
 src/backend/utils/adt/rangetypes.c            |  230 ++-
 src/include/catalog/pg_aggregate.dat          |   11 +
 src/include/catalog/pg_amproc.dat             |    5 +-
 src/include/catalog/pg_operator.dat           |  169 ++
 src/include/catalog/pg_proc.dat               |  185 ++
 src/include/utils/multirangetypes.h           |   34 +
 src/include/utils/rangetypes.h                |   24 +-
 src/test/regress/expected/dependency.out      |    1 +
 src/test/regress/expected/multirangetypes.out | 2397 +++++++++++++++++++++++++
 src/test/regress/expected/opr_sanity.out      |    4 +-
 src/test/regress/expected/rangetypes.out      |   38 +-
 src/test/regress/expected/sanity_check.out    |    2 +
 src/test/regress/sql/multirangetypes.sql      |  607 +++++++
 src/test/regress/sql/rangetypes.sql           |   13 +
 15 files changed, 5041 insertions(+), 101 deletions(-)

diff --git a/src/backend/utils/adt/multirangetypes.c b/src/backend/utils/adt/multirangetypes.c
index 5323a6a635..c509994796 100644
--- a/src/backend/utils/adt/multirangetypes.c
+++ b/src/backend/utils/adt/multirangetypes.c
@@ -726,45 +726,1267 @@ multirange_constructor0(PG_FUNCTION_ARGS)
 }
 
 
+/* multirange, multirange -> multirange type functions */
+
+/* multirange union */
+Datum
+range_union_range(PG_FUNCTION_ARGS)
+{
+	RangeType  *r1 = PG_GETARG_RANGE_P(0);
+	RangeType  *r2 = PG_GETARG_RANGE_P(1);
+	Oid			mltrngtypoid = get_fn_expr_rettype(fcinfo->flinfo);
+	TypeCacheEntry *typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
+	MultirangeType *mr = make_multirange(mltrngtypoid, typcache->rngtype, 1, &r2);
+
+	PG_RETURN_MULTIRANGE_P(range_union_multirange_internal(typcache, r1, mr));
+}
+
+Datum
+range_union_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_MULTIRANGE_P(range_union_multirange_internal(typcache, r, mr));
+}
+
+Datum
+multirange_union_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_MULTIRANGE_P(range_union_multirange_internal(typcache, r, mr));
+}
+
+Datum
+multirange_union_multirange(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
+	MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count1;
+	int32		range_count2;
+	int32		range_count3;
+	RangeType **ranges1;
+	RangeType **ranges2;
+	RangeType **ranges3;
+
+	if (MultirangeIsEmpty(mr1))
+		PG_RETURN_MULTIRANGE_P(mr2);
+	if (MultirangeIsEmpty(mr2))
+		PG_RETURN_MULTIRANGE_P(mr1);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr1));
+
+	multirange_deserialize(mr1, &range_count1, &ranges1);
+	multirange_deserialize(mr2, &range_count2, &ranges2);
+
+	range_count3 = range_count1 + range_count2;
+	ranges3 = palloc0(range_count3 * sizeof(RangeType *));
+	memcpy(ranges3, ranges1, range_count1 * sizeof(RangeType *));
+	memcpy(ranges3 + range_count1, ranges2, range_count2 * sizeof(RangeType *));
+	PG_RETURN_MULTIRANGE_P(make_multirange(typcache->type_id, typcache->rngtype,
+										   range_count3, ranges3));
+}
+
+MultirangeType *
+range_union_multirange_internal(TypeCacheEntry *typcache, RangeType *r,
+								MultirangeType * mr)
+{
+	int32		range_count;
+	RangeType **ranges1;
+	RangeType **ranges2;
+
+	if (RangeIsEmpty(r))
+		return mr;
+	if (MultirangeIsEmpty(mr))
+		return make_multirange(typcache->type_id, typcache->rngtype, 1, &r);
+
+	multirange_deserialize(mr, &range_count, &ranges1);
+
+	ranges2 = palloc0((range_count + 1) * sizeof(RangeType *));
+
+	memcpy(ranges2, ranges1, range_count * sizeof(RangeType *));
+	ranges2[range_count] = r;
+	return make_multirange(typcache->type_id, typcache->rngtype, range_count + 1, ranges2);
+}
+
+/* multirange minus */
+Datum
+range_minus_range(PG_FUNCTION_ARGS)
+{
+	RangeType  *r1 = PG_GETARG_RANGE_P(0);
+	RangeType  *r2 = PG_GETARG_RANGE_P(1);
+	Oid			mltrngtypoid = get_fn_expr_rettype(fcinfo->flinfo);
+	TypeCacheEntry *typcache;
+	TypeCacheEntry *rangetyp;
+
+	typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
+	rangetyp = typcache->rngtype;
+
+	if (RangeIsEmpty(r1) || RangeIsEmpty(r2))
+		PG_RETURN_MULTIRANGE_P(make_multirange(mltrngtypoid, rangetyp, 1, &r1));
+
+	PG_RETURN_MULTIRANGE_P(multirange_minus_multirange_internal(mltrngtypoid,
+																rangetyp,
+																1,
+																&r1,
+																1,
+																&r2));
+}
+
+Datum
+range_minus_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	Oid			mltrngtypoid = MultirangeTypeGetOid(mr);
+	TypeCacheEntry *typcache;
+	TypeCacheEntry *rangetyp;
+	int32		range_count;
+	RangeType **ranges;
+
+	typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
+	rangetyp = typcache->rngtype;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		PG_RETURN_MULTIRANGE_P(make_multirange(mltrngtypoid, rangetyp, 1, &r));
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_MULTIRANGE_P(multirange_minus_multirange_internal(mltrngtypoid,
+																rangetyp,
+																1,
+																&r,
+																range_count,
+																ranges));
+}
+
+Datum
+multirange_minus_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	Oid			mltrngtypoid = MultirangeTypeGetOid(mr);
+	TypeCacheEntry *typcache;
+	TypeCacheEntry *rangetyp;
+	int32		range_count;
+	RangeType **ranges;
+
+	typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
+	rangetyp = typcache->rngtype;
+
+	if (MultirangeIsEmpty(mr) || RangeIsEmpty(r))
+		PG_RETURN_MULTIRANGE_P(mr);
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_MULTIRANGE_P(multirange_minus_multirange_internal(mltrngtypoid,
+																rangetyp,
+																range_count,
+																ranges,
+																1,
+																&r));
+}
+
+Datum
+multirange_minus_multirange(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
+	MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
+	Oid			mltrngtypoid = MultirangeTypeGetOid(mr1);
+	TypeCacheEntry *typcache;
+	TypeCacheEntry *rangetyp;
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+
+	typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
+	rangetyp = typcache->rngtype;
+
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		PG_RETURN_MULTIRANGE_P(mr1);
+
+	multirange_deserialize(mr1, &range_count1, &ranges1);
+	multirange_deserialize(mr2, &range_count2, &ranges2);
+
+	PG_RETURN_MULTIRANGE_P(multirange_minus_multirange_internal(mltrngtypoid,
+																rangetyp,
+																range_count1,
+																ranges1,
+																range_count2,
+																ranges2));
+}
+
+MultirangeType *
+multirange_minus_multirange_internal(Oid mltrngtypoid, TypeCacheEntry *rangetyp,
+									 int32 range_count1, RangeType **ranges1, int32 range_count2, RangeType **ranges2)
+{
+	RangeType  *r1;
+	RangeType  *r2;
+	RangeType **ranges3;
+	int32		range_count3;
+	int32		i1;
+	int32		i2;
+
+	/*
+	 * Worst case: every range in ranges1 makes a different cut to some range
+	 * in ranges2.
+	 */
+	ranges3 = palloc0((range_count1 + range_count2) * sizeof(RangeType *));
+	range_count3 = 0;
+
+	/*
+	 * For each range in mr1, keep subtracting until it's gone or the ranges
+	 * in mr2 have passed it. After a subtraction we assign what's left back
+	 * to r1. The parallel progress through mr1 and mr2 is similar to
+	 * multirange_overlaps_multirange_internal.
+	 */
+	r2 = ranges2[0];
+	for (i1 = 0, i2 = 0; i1 < range_count1; i1++)
+	{
+		r1 = ranges1[i1];
+
+		/* Discard r2s while r2 << r1 */
+		while (r2 != NULL && range_before_internal(rangetyp, r2, r1))
+		{
+			r2 = ++i2 >= range_count2 ? NULL : ranges2[i2];
+		}
+
+		while (r2 != NULL)
+		{
+			if (range_split_internal(rangetyp, r1, r2, &ranges3[range_count3], &r1))
+			{
+				/*
+				 * If r2 takes a bite out of the middle of r1, we need two
+				 * outputs
+				 */
+				range_count3++;
+				r2 = ++i2 >= range_count2 ? NULL : ranges2[i2];
+
+			}
+			else if (range_overlaps_internal(rangetyp, r1, r2))
+			{
+				/*
+				 * If r2 overlaps r1, replace r1 with r1 - r2.
+				 */
+				r1 = range_minus_internal(rangetyp, r1, r2);
+
+				/*
+				 * If r2 goes past r1, then we need to stay with it, in case
+				 * it hits future r1s. Otherwise we need to keep r1, in case
+				 * future r2s hit it. Since we already subtracted, there's no
+				 * point in using the overright/overleft calls.
+				 */
+				if (RangeIsEmpty(r1) || range_before_internal(rangetyp, r1, r2))
+					break;
+				else
+					r2 = ++i2 >= range_count2 ? NULL : ranges2[i2];
+
+			}
+			else
+			{
+				/*
+				 * This and all future r2s are past r1, so keep them. Also
+				 * assign whatever is left of r1 to the result.
+				 */
+				break;
+			}
+		}
+
+		/*
+		 * Nothing else can remove anything from r1, so keep it. Even if r1 is
+		 * empty here, make_multirange will remove it.
+		 */
+		ranges3[range_count3++] = r1;
+	}
+
+	return make_multirange(mltrngtypoid, rangetyp, range_count3, ranges3);
+}
+
+/* multirange intersection */
+Datum
+range_intersect_range(PG_FUNCTION_ARGS)
+{
+	RangeType  *r1 = PG_GETARG_RANGE_P(0);
+	RangeType  *r2 = PG_GETARG_RANGE_P(1);
+	Oid			mltrngtypoid = get_fn_expr_rettype(fcinfo->flinfo);
+	TypeCacheEntry *typcache;
+	TypeCacheEntry *rangetyp;
+
+	typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
+	rangetyp = typcache->rngtype;
+
+	if (RangeIsEmpty(r1) || RangeIsEmpty(r2))
+		PG_RETURN_MULTIRANGE_P(make_empty_multirange(mltrngtypoid, rangetyp));
+
+	PG_RETURN_MULTIRANGE_P(multirange_intersect_multirange_internal(mltrngtypoid,
+																	rangetyp,
+																	1,
+																	&r1,
+																	1,
+																	&r2));
+}
+
+Datum
+range_intersect_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r1 = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
+	Oid			mltrngtypoid = MultirangeTypeGetOid(mr2);
+	TypeCacheEntry *typcache;
+	TypeCacheEntry *rangetyp;
+	int32		range_count2;
+	RangeType **ranges2;
+
+	typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
+	rangetyp = typcache->rngtype;
+
+	if (RangeIsEmpty(r1) || MultirangeIsEmpty(mr2))
+		PG_RETURN_MULTIRANGE_P(make_empty_multirange(mltrngtypoid, rangetyp));
+
+	multirange_deserialize(mr2, &range_count2, &ranges2);
+
+	PG_RETURN_MULTIRANGE_P(multirange_intersect_multirange_internal(mltrngtypoid,
+																	rangetyp,
+																	1,
+																	&r1,
+																	range_count2,
+																	ranges2));
+}
+
+Datum
+multirange_intersect_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r2 = PG_GETARG_RANGE_P(1);
+	Oid			mltrngtypoid = MultirangeTypeGetOid(mr1);
+	TypeCacheEntry *typcache;
+	TypeCacheEntry *rangetyp;
+	int32		range_count1;
+	RangeType **ranges1;
+
+	typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
+	rangetyp = typcache->rngtype;
+
+	if (MultirangeIsEmpty(mr1) || RangeIsEmpty(r2))
+		PG_RETURN_MULTIRANGE_P(make_empty_multirange(mltrngtypoid, rangetyp));
+
+	multirange_deserialize(mr1, &range_count1, &ranges1);
+
+	PG_RETURN_MULTIRANGE_P(multirange_intersect_multirange_internal(mltrngtypoid,
+																	rangetyp,
+																	range_count1,
+																	ranges1,
+																	1,
+																	&r2));
+}
+
+Datum
+multirange_intersect_multirange(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
+	MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
+	Oid			mltrngtypoid = MultirangeTypeGetOid(mr1);
+	TypeCacheEntry *typcache;
+	TypeCacheEntry *rangetyp;
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+
+	typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
+	rangetyp = typcache->rngtype;
+
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		PG_RETURN_MULTIRANGE_P(make_empty_multirange(mltrngtypoid, rangetyp));
+
+	multirange_deserialize(mr1, &range_count1, &ranges1);
+	multirange_deserialize(mr2, &range_count2, &ranges2);
+
+	PG_RETURN_MULTIRANGE_P(multirange_intersect_multirange_internal(mltrngtypoid,
+																	rangetyp,
+																	range_count1,
+																	ranges1,
+																	range_count2,
+																	ranges2));
+}
+
+MultirangeType *
+multirange_intersect_multirange_internal(Oid mltrngtypoid, TypeCacheEntry *rangetyp,
+										 int32 range_count1, RangeType **ranges1, int32 range_count2, RangeType **ranges2)
+{
+	RangeType  *r1;
+	RangeType  *r2;
+	RangeType **ranges3;
+	int32		range_count3;
+	int32		i1;
+	int32		i2;
+
+	/*
+	 * Worst case is a stitching pattern like this:
+	 *
+	 * mr1: --- --- --- ---
+	 * mr2:   --- --- ---
+	 * mr3:   - - - - - -
+	 *
+	 * That seems to be range_count1 + range_count2 - 1, but one extra won't
+	 * hurt.
+	 */
+	ranges3 = palloc0((range_count1 + range_count2) * sizeof(RangeType *));
+	range_count3 = 0;
+
+	/*
+	 * For each range in mr1, keep intersecting until the ranges in mr2 have
+	 * passed it. The parallel progress through mr1 and mr2 is similar to
+	 * multirange_minus_multirange_internal, but we don't have to assign back
+	 * to r1.
+	 */
+	r2 = ranges2[0];
+	for (i1 = 0, i2 = 0; i1 < range_count1; i1++)
+	{
+		r1 = ranges1[i1];
+
+		/* Discard r2s while r2 << r1 */
+		while (r2 != NULL && range_before_internal(rangetyp, r2, r1))
+		{
+			r2 = ++i2 >= range_count2 ? NULL : ranges2[i2];
+		}
+
+		while (r2 != NULL)
+		{
+			if (range_overlaps_internal(rangetyp, r1, r2))
+			{
+				/* Keep the overlapping part */
+				ranges3[range_count3++] = range_intersect_internal(rangetyp, r1, r2);
+
+				/* If we "used up" all of r2, go to the next one... */
+				if (range_overleft_internal(rangetyp, r2, r1))
+					r2 = ++i2 >= range_count2 ? NULL : ranges2[i2];
+
+				/* ...otherwise go to the next r1 */
+				else
+					break;
+			}
+			else
+				/* We're past r1, so move to the next one */
+				break;
+		}
+
+		/* If we're out of r2s, there can be no more intersections */
+		if (r2 == NULL)
+			break;
+	}
+
+	return make_multirange(mltrngtypoid, rangetyp, range_count3, ranges3);
+}
+
+/*
+ * range_agg_transfn: combine adjacent/overlapping ranges.
+ *
+ * All we do here is gather the input ranges into an array
+ * so that the finalfn can sort and combine them.
+ */
+Datum
+range_agg_transfn(PG_FUNCTION_ARGS)
+{
+	MemoryContext aggContext;
+	Oid			rngtypoid;
+	ArrayBuildState *state;
+
+	if (!AggCheckCallContext(fcinfo, &aggContext))
+		elog(ERROR, "range_agg_transfn called in non-aggregate context");
+
+	rngtypoid = get_fn_expr_argtype(fcinfo->flinfo, 1);
+	if (!type_is_range(rngtypoid))
+		ereport(ERROR, (errmsg("range_agg must be called with a range")));
+
+	if (PG_ARGISNULL(0))
+		state = initArrayResult(rngtypoid, aggContext, false);
+	else
+		state = (ArrayBuildState *) PG_GETARG_POINTER(0);
+
+	/* skip NULLs */
+	if (!PG_ARGISNULL(1))
+		accumArrayResult(state, PG_GETARG_DATUM(1), false, rngtypoid, aggContext);
+
+	PG_RETURN_POINTER(state);
+}
+
+/*
+ * range_agg_finalfn: use our internal array to merge touching ranges.
+ */
+Datum
+range_agg_finalfn(PG_FUNCTION_ARGS)
+{
+	MemoryContext aggContext;
+	Oid			mltrngtypoid;
+	TypeCacheEntry *typcache;
+	ArrayBuildState *state;
+	int32		range_count;
+	RangeType **ranges;
+	int			i;
+
+	if (!AggCheckCallContext(fcinfo, &aggContext))
+		elog(ERROR, "range_agg_finalfn called in non-aggregate context");
+
+	state = PG_ARGISNULL(0) ? NULL : (ArrayBuildState *) PG_GETARG_POINTER(0);
+	if (state == NULL)
+		/* This shouldn't be possible, but just in case.... */
+		PG_RETURN_NULL();
+
+	/* Also return NULL if we had zero inputs, like other aggregates */
+	range_count = state->nelems;
+	if (range_count == 0)
+		PG_RETURN_NULL();
+
+	mltrngtypoid = get_fn_expr_rettype(fcinfo->flinfo);
+	typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
+
+	ranges = palloc0(range_count * sizeof(RangeType *));
+	for (i = 0; i < range_count; i++)
+		ranges[i] = DatumGetRangeTypeP(state->dvalues[i]);
+
+	PG_RETURN_MULTIRANGE_P(make_multirange(mltrngtypoid, typcache->rngtype, range_count, ranges));
+}
+
+Datum
+multirange_intersect_agg_transfn(PG_FUNCTION_ARGS)
+{
+	MemoryContext aggContext;
+	Oid			mltrngtypoid;
+	TypeCacheEntry *typcache;
+	MultirangeType *result;
+	MultirangeType *current;
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+
+	if (!AggCheckCallContext(fcinfo, &aggContext))
+		elog(ERROR, "multirange_intersect_agg_transfn called in non-aggregate context");
+
+	mltrngtypoid = get_fn_expr_argtype(fcinfo->flinfo, 1);
+	if (!type_is_multirange(mltrngtypoid))
+		ereport(ERROR, (errmsg("range_intersect_agg must be called with a multirange")));
+
+	typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
+
+	/* strictness ensures these are non-null */
+	result = PG_GETARG_MULTIRANGE_P(0);
+	current = PG_GETARG_MULTIRANGE_P(1);
+
+	multirange_deserialize(result, &range_count1, &ranges1);
+	multirange_deserialize(current, &range_count2, &ranges2);
+
+	result = multirange_intersect_multirange_internal(mltrngtypoid,
+													  typcache->rngtype,
+													  range_count1,
+													  ranges1,
+													  range_count2,
+													  ranges2);
+	PG_RETURN_RANGE_P(result);
+}
+
+
+/* multirange -> element type functions */
+
+/* extract lower bound value */
+Datum
+multirange_lower(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+	bool		isnull;
+	Datum		result;
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_NULL();
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	result = range_lower_internal(typcache->rngtype, ranges[0], &isnull);
+
+	if (isnull)
+		PG_RETURN_NULL();
+	else
+		PG_RETURN_DATUM(result);
+}
+
+/* extract upper bound value */
+Datum
+multirange_upper(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+	bool		isnull;
+	Datum		result;
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_NULL();
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	result = range_upper_internal(typcache->rngtype, ranges[range_count - 1], &isnull);
+
+	if (isnull)
+		PG_RETURN_NULL();
+	else
+		PG_RETURN_DATUM(result);
+}
+
+
+/* multirange -> bool functions */
+
+/* is multirange empty? */
+Datum
+multirange_empty(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+
+	PG_RETURN_BOOL(mr->rangeCount == 0);
+}
+
+/* is lower bound inclusive? */
+Datum
+multirange_lower_inc(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	int32		range_count;
+	RangeType **ranges;
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_has_flag(ranges[0], RANGE_LB_INC));
+}
+
+/* is upper bound inclusive? */
+Datum
+multirange_upper_inc(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	int32		range_count;
+	RangeType **ranges;
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_has_flag(ranges[range_count - 1], RANGE_UB_INC));
+}
+
+/* is lower bound infinite? */
+Datum
+multirange_lower_inf(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	int32		range_count;
+	RangeType **ranges;
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_has_flag(ranges[0], RANGE_LB_INF));
+}
+
+/* is upper bound infinite? */
+Datum
+multirange_upper_inf(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	int32		range_count;
+	RangeType **ranges;
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_has_flag(ranges[range_count - 1], RANGE_UB_INF));
+}
+
+
+
+/* multirange, element -> bool functions */
+
+/* contains? */
+Datum
+multirange_contains_elem(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	Datum		val = PG_GETARG_DATUM(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(multirange_contains_elem_internal(typcache, mr, val));
+}
+
+/* contained by? */
+Datum
+elem_contained_by_multirange(PG_FUNCTION_ARGS)
+{
+	Datum		val = PG_GETARG_DATUM(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(multirange_contains_elem_internal(typcache, mr, val));
+}
+
+/*
+ * Test whether multirange mr contains a specific element value.
+ */
+bool
+multirange_contains_elem_internal(TypeCacheEntry *typcache, MultirangeType * mr, Datum val)
+{
+	TypeCacheEntry *rangetyp;
+	int32		range_count;
+	RangeType **ranges;
+	RangeType  *r;
+	int			i;
+
+	rangetyp = typcache->rngtype;
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	for (i = 0; i < range_count; i++)
+	{
+		r = ranges[i];
+		if (range_contains_elem_internal(rangetyp, r, val))
+			return true;
+	}
+
+	return false;
+}
+
+/* multirange, range -> bool functions */
+
+/* contains? */
+Datum
+multirange_contains_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(multirange_contains_range_internal(typcache, mr, r));
+}
+
+/* contained by? */
+Datum
+range_contained_by_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(multirange_contains_range_internal(typcache, mr, r));
+}
+
+/*
+ * Test whether multirange mr contains a specific range r.
+ */
+bool
+multirange_contains_range_internal(TypeCacheEntry *typcache, MultirangeType * mr, RangeType *r)
+{
+	TypeCacheEntry *rangetyp;
+	int32		range_count;
+	RangeType **ranges;
+	RangeType  *mrr;
+	int			i;
+
+	rangetyp = typcache->rngtype;
+
+	/*
+	 * Every multirange contains an infinite number of empty ranges, even an
+	 * empty one.
+	 */
+	if (RangeIsEmpty(r))
+		return true;
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	for (i = 0; i < range_count; i++)
+	{
+		mrr = ranges[i];
+		if (range_contains_internal(rangetyp, mrr, r))
+			return true;
+	}
+
+	return false;
+}
+
+
 /* multirange, multirange -> bool functions */
 
-/* equality (internal version) */
+/* 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));
+}
+
+/* overlaps? */
+Datum
+range_overlaps_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(range_overlaps_multirange_internal(typcache, r, mr));
+}
+
+Datum
+multirange_overlaps_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(range_overlaps_multirange_internal(typcache, r, mr));
+}
+
+Datum
+multirange_overlaps_multirange(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_overlaps_multirange_internal(typcache, mr1, mr2));
+}
+
+bool
+range_overlaps_multirange_internal(TypeCacheEntry *typcache, RangeType *r, MultirangeType * mr)
+{
+	TypeCacheEntry *rangetyp;
+	int32		range_count;
+	int32		i;
+	RangeType **ranges;
+	RangeType  *mrr;
+
+	/*
+	 * Empties never overlap, even with empties. (This seems strange since
+	 * they *do* contain each other, but we want to follow how ranges work.)
+	 */
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		return false;
+
+	rangetyp = typcache->rngtype;
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	/* Scan through mrr and once it gets to r they either overlap or not */
+	mrr = ranges[0];
+
+	/* Discard mrrs while mrr << r */
+	i = 0;
+	while (range_before_internal(rangetyp, mrr, r))
+	{
+		if (++i >= range_count)
+			return false;
+		mrr = ranges[i];
+	}
+
+	/* Now either we overlap or we passed r */
+	return range_overlaps_internal(rangetyp, mrr, r);
+}
+
+bool
+multirange_overlaps_multirange_internal(TypeCacheEntry *typcache, MultirangeType * mr1,
+										MultirangeType * mr2)
+{
+	TypeCacheEntry *rangetyp;
+	int32		range_count1;
+	int32		range_count2;
+	int32		i1;
+	int32		i2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+	RangeType  *r1;
+	RangeType  *r2;
+
+	/*
+	 * Empties never overlap, even with empties. (This seems strange since
+	 * they *do* contain each other, but we want to follow how ranges work.)
+	 */
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		return false;
+
+	rangetyp = typcache->rngtype;
+
+	multirange_deserialize(mr1, &range_count1, &ranges1);
+	multirange_deserialize(mr2, &range_count2, &ranges2);
+
+	/*
+	 * Every range in mr1 gets a chance to overlap with the ranges in mr2, but
+	 * we can use their ordering to avoid O(n^2). This is similar to
+	 * range_overlaps_multirange where r1 : r2 :: mrr : r, but there if we
+	 * don't find an overlap with r we're done, and here if we don't find an
+	 * overlap with r2 we try the next r2.
+	 */
+	r1 = ranges1[0];
+	for (i1 = 0, i2 = 0; i2 < range_count2; i2++)
+	{
+		r2 = ranges2[i2];
+
+		/* Discard r1s while r1 << r2 */
+		while (range_before_internal(rangetyp, r1, r2))
+		{
+			if (++i1 >= range_count1)
+				return false;
+			r1 = ranges1[i1];
+		}
+
+		/*
+		 * If r1 && r2, we're done, otherwise we failed to find an overlap for
+		 * r2, so go to the next one.
+		 */
+		if (range_overlaps_internal(rangetyp, r1, r2))
+			return true;
+	}
+
+	/* We looked through all of mr2 without finding an overlap */
+	return false;
+}
+
+/* does not extend to right of? */
+Datum
+range_overleft_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_overleft_internal(typcache->rngtype, r, ranges[range_count - 1]));
+}
+
+Datum
+multirange_overleft_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_overleft_internal(typcache->rngtype, ranges[range_count - 1], r));
+}
+
+Datum
+multirange_overleft_multirange(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
+	MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr1));
+
+	multirange_deserialize(mr1, &range_count1, &ranges1);
+	multirange_deserialize(mr2, &range_count2, &ranges2);
+
+	PG_RETURN_BOOL(range_overleft_internal(typcache->rngtype, ranges1[range_count1 - 1], ranges2[range_count2 - 1]));
+}
+
+/* does not extend to left of? */
+Datum
+range_overright_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_overright_internal(typcache->rngtype, r, ranges[0]));
+}
+
+Datum
+multirange_overright_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_overright_internal(typcache->rngtype, ranges[0], r));
+}
+
+Datum
+multirange_overright_multirange(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
+	MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr1));
+
+	multirange_deserialize(mr1, &range_count1, &ranges1);
+	multirange_deserialize(mr2, &range_count2, &ranges2);
+
+	PG_RETURN_BOOL(range_overright_internal(typcache->rngtype, ranges1[0], ranges2[0]));
+}
+
+/* contains? */
+Datum
+multirange_contains_multirange(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_contains_multirange_internal(typcache, mr1, mr2));
+}
+
+/* contained by? */
+Datum
+multirange_contained_by_multirange(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_contains_multirange_internal(typcache, mr2, mr1));
+}
+
+/*
+ * Test whether multirange mr1 contains every range from another multirange mr2.
+ */
 bool
-multirange_eq_internal(TypeCacheEntry *typcache, MultirangeType * mr1, MultirangeType * mr2)
+multirange_contains_multirange_internal(TypeCacheEntry *typcache,
+										MultirangeType * mr1, MultirangeType * mr2)
 {
-	int32		range_count_1;
-	int32		range_count_2;
-	int32		i;
+	TypeCacheEntry *rangetyp;
+	int32		range_count1;
+	int32		range_count2;
 	RangeType **ranges1;
 	RangeType **ranges2;
 	RangeType  *r1;
 	RangeType  *r2;
+	int			i1,
+				i2;
 
-	/* Different types should be prevented by ANYMULTIRANGE matching rules */
-	if (MultirangeTypeGetOid(mr1) != MultirangeTypeGetOid(mr2))
-		elog(ERROR, "multirange types do not match");
+	rangetyp = typcache->rngtype;
 
-	multirange_deserialize(mr1, &range_count_1, &ranges1);
-	multirange_deserialize(mr2, &range_count_2, &ranges2);
+	multirange_deserialize(mr1, &range_count1, &ranges1);
+	multirange_deserialize(mr2, &range_count2, &ranges2);
 
-	if (range_count_1 != range_count_2)
+	/*
+	 * We follow the same logic for empties as ranges: - an empty multirange
+	 * contains an empty range/multirange. - an empty multirange can't contain
+	 * any other range/multirange. - an empty multirange is contained by any
+	 * other range/multirange.
+	 */
+
+	if (range_count2 == 0)
+		return true;
+	if (range_count1 == 0)
 		return false;
 
-	for (i = 0; i < range_count_1; i++)
+	/*
+	 * Every range in mr2 must be contained by some range in mr1. To avoid
+	 * O(n^2) we walk through both ranges in tandem.
+	 */
+	r1 = ranges1[0];
+	for (i1 = 0, i2 = 0; i2 < range_count2; i2++)
 	{
-		r1 = ranges1[i];
-		r2 = ranges2[i];
+		r2 = ranges2[i2];
 
-		if (!range_eq_internal(typcache->rngtype, r1, r2))
+		/* Discard r1s while r1 << r2 */
+		while (range_before_internal(rangetyp, r1, r2))
+		{
+			if (++i1 >= range_count1)
+				return false;
+			r1 = ranges1[i1];
+		}
+
+		/*
+		 * If r1 @> r2, go to the next r2, otherwise return false (since every
+		 * r1[n] and r1[n+1] must have a gap). Note this will give weird
+		 * answers if you don't canonicalize, e.g. with a custom
+		 * int2multirange {[1,1], [2,2]} there is a "gap". But that is
+		 * consistent with other range operators, e.g. '[1,1]'::int2range -|-
+		 * '[2,2]'::int2range is false.
+		 */
+		if (!range_contains_internal(rangetyp, r1, r2))
 			return false;
 	}
 
+	/* All ranges in mr2 are satisfied */
 	return true;
 }
 
-/* equality */
+/* strictly left of? */
 Datum
-multirange_eq(PG_FUNCTION_ARGS)
+range_before_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(range_before_multirange_internal(typcache, r, mr));
+}
+
+Datum
+multirange_before_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(range_after_multirange_internal(typcache, r, mr));
+}
+
+Datum
+multirange_before_multirange(PG_FUNCTION_ARGS)
 {
 	MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
 	MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
@@ -772,27 +1994,156 @@ multirange_eq(PG_FUNCTION_ARGS)
 
 	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr1));
 
-	PG_RETURN_BOOL(multirange_eq_internal(typcache, mr1, mr2));
+	PG_RETURN_BOOL(multirange_before_multirange_internal(typcache, mr1, mr2));
 }
 
-/* inequality (internal version) */
+/* strictly right of? */
+Datum
+range_after_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(range_after_multirange_internal(typcache, r, mr));
+}
+
+Datum
+multirange_after_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(range_before_multirange_internal(typcache, r, mr));
+}
+
+Datum
+multirange_after_multirange(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_before_multirange_internal(typcache, mr2, mr1));
+}
+
+/* strictly left of? (internal version) */
 bool
-multirange_ne_internal(TypeCacheEntry *typcache, MultirangeType * mr1, MultirangeType * mr2)
+range_before_multirange_internal(TypeCacheEntry *typcache, RangeType *r,
+								 MultirangeType * mr)
 {
-	return (!multirange_eq_internal(typcache, mr1, mr2));
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		return false;
+
+	int32		range_count;
+	RangeType **ranges;
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	return range_before_internal(typcache->rngtype, r, ranges[0]);
 }
 
-/* inequality */
+bool
+multirange_before_multirange_internal(TypeCacheEntry *typcache, MultirangeType * mr1,
+									  MultirangeType * mr2)
+{
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		return false;
+
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+
+	multirange_deserialize(mr1, &range_count1, &ranges1);
+	multirange_deserialize(mr2, &range_count2, &ranges2);
+
+	return range_before_internal(typcache->rngtype, ranges1[range_count1 - 1],
+								 ranges2[0]);
+}
+
+/* strictly right of? (internal version) */
+bool
+range_after_multirange_internal(TypeCacheEntry *typcache, RangeType *r,
+								MultirangeType * mr)
+{
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		return false;
+
+	int32		range_count;
+	RangeType **ranges;
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	return range_after_internal(typcache->rngtype, r, ranges[range_count - 1]);
+}
+
+/* adjacent to? */
 Datum
-multirange_ne(PG_FUNCTION_ARGS)
+range_adjacent_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		return false;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_adjacent_internal(typcache->rngtype, r, ranges[0]));
+}
+
+Datum
+multirange_adjacent_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		return false;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_adjacent_internal(typcache->rngtype, ranges[range_count - 1], r));
+}
+
+Datum
+multirange_adjacent_multirange(PG_FUNCTION_ARGS)
 {
 	MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
 	MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
 	TypeCacheEntry *typcache;
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		return false;
 
 	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr1));
 
-	PG_RETURN_BOOL(multirange_ne_internal(typcache, mr1, mr2));
+	multirange_deserialize(mr1, &range_count1, &ranges1);
+	multirange_deserialize(mr2, &range_count2, &ranges2);
+
+	PG_RETURN_BOOL(range_adjacent_internal(typcache->rngtype, ranges1[range_count1 - 1], ranges2[0]));
 }
 
 /* Btree support */
@@ -891,6 +2242,29 @@ multirange_gt(PG_FUNCTION_ARGS)
 	PG_RETURN_BOOL(cmp > 0);
 }
 
+/* multirange -> range functions */
+
+/* Find the smallest range that includes everything in the multirange */
+Datum
+range_merge_from_multirange(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	Oid			mltrngtypoid = MultirangeTypeGetOid(mr);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_RANGE_P(make_empty_range(typcache->rngtype));
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_RANGE_P(range_union_internal(typcache->rngtype, ranges[0],
+										   ranges[range_count - 1], false));
+}
+
 /* Hash support */
 
 /* hash a multirange value */
diff --git a/src/backend/utils/adt/rangetypes.c b/src/backend/utils/adt/rangetypes.c
index 1feebc48dc..bd9ad67672 100644
--- a/src/backend/utils/adt/rangetypes.c
+++ b/src/backend/utils/adt/rangetypes.c
@@ -429,19 +429,37 @@ range_lower(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
 	TypeCacheEntry *typcache;
+	bool		isnull;
+	Datum		result;
+
+	typcache = range_get_typcache(fcinfo, RangeTypeGetOid(r1));
+
+	result = range_lower_internal(typcache, r1, &isnull);
+
+	if (isnull)
+		PG_RETURN_NULL();
+	else
+		PG_RETURN_DATUM(result);
+}
+
+Datum
+range_lower_internal(TypeCacheEntry *typcache, RangeType *r1, bool *isnull)
+{
 	RangeBound	lower;
 	RangeBound	upper;
 	bool		empty;
 
-	typcache = range_get_typcache(fcinfo, RangeTypeGetOid(r1));
-
 	range_deserialize(typcache, r1, &lower, &upper, &empty);
 
 	/* Return NULL if there's no finite lower bound */
 	if (empty || lower.infinite)
-		PG_RETURN_NULL();
+	{
+		*isnull = true;
+		return 0;
+	}
 
-	PG_RETURN_DATUM(lower.val);
+	*isnull = false;
+	return lower.val;
 }
 
 /* extract upper bound value */
@@ -450,19 +468,37 @@ range_upper(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
 	TypeCacheEntry *typcache;
+	bool		isnull;
+	Datum		result;
+
+	typcache = range_get_typcache(fcinfo, RangeTypeGetOid(r1));
+
+	result = range_upper_internal(typcache, r1, &isnull);
+
+	if (isnull)
+		PG_RETURN_NULL();
+	else
+		PG_RETURN_DATUM(result);
+}
+
+Datum
+range_upper_internal(TypeCacheEntry *typcache, RangeType *r1, bool *isnull)
+{
 	RangeBound	lower;
 	RangeBound	upper;
 	bool		empty;
 
-	typcache = range_get_typcache(fcinfo, RangeTypeGetOid(r1));
-
 	range_deserialize(typcache, r1, &lower, &upper, &empty);
 
 	/* Return NULL if there's no finite upper bound */
 	if (empty || upper.infinite)
-		PG_RETURN_NULL();
+	{
+		*isnull = true;
+		return 0;
+	}
 
-	PG_RETURN_DATUM(upper.val);
+	*isnull = false;
+	return upper.val;
 }
 
 
@@ -473,9 +509,8 @@ Datum
 range_empty(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
-	char		flags = range_get_flags(r1);
 
-	PG_RETURN_BOOL(flags & RANGE_EMPTY);
+	PG_RETURN_BOOL(range_has_flag(r1, RANGE_EMPTY));
 }
 
 /* is lower bound inclusive? */
@@ -483,9 +518,8 @@ Datum
 range_lower_inc(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
-	char		flags = range_get_flags(r1);
 
-	PG_RETURN_BOOL(flags & RANGE_LB_INC);
+	PG_RETURN_BOOL(range_has_flag(r1, RANGE_LB_INC));
 }
 
 /* is upper bound inclusive? */
@@ -493,9 +527,8 @@ Datum
 range_upper_inc(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
-	char		flags = range_get_flags(r1);
 
-	PG_RETURN_BOOL(flags & RANGE_UB_INC);
+	PG_RETURN_BOOL(range_has_flag(r1, RANGE_UB_INC));
 }
 
 /* is lower bound infinite? */
@@ -503,9 +536,8 @@ Datum
 range_lower_inf(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
-	char		flags = range_get_flags(r1);
 
-	PG_RETURN_BOOL(flags & RANGE_LB_INF);
+	PG_RETURN_BOOL(range_has_flag(r1, RANGE_LB_INF));
 }
 
 /* is upper bound infinite? */
@@ -513,9 +545,8 @@ Datum
 range_upper_inf(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
-	char		flags = range_get_flags(r1);
 
-	PG_RETURN_BOOL(flags & RANGE_UB_INF);
+	PG_RETURN_BOOL(range_has_flag(r1, RANGE_UB_INF));
 }
 
 
@@ -955,7 +986,25 @@ range_minus(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
 	RangeType  *r2 = PG_GETARG_RANGE_P(1);
+	RangeType  *ret;
 	TypeCacheEntry *typcache;
+
+	/* 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));
+
+	ret = range_minus_internal(typcache, r1, r2);
+	if (ret)
+		PG_RETURN_RANGE_P(ret);
+	else
+		PG_RETURN_NULL();
+}
+
+RangeType *
+range_minus_internal(TypeCacheEntry *typcache, RangeType *r1, RangeType *r2)
+{
 	RangeBound	lower1,
 				lower2;
 	RangeBound	upper1,
@@ -967,18 +1016,12 @@ range_minus(PG_FUNCTION_ARGS)
 				cmp_u1l2,
 				cmp_u1u2;
 
-	/* 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));
-
 	range_deserialize(typcache, r1, &lower1, &upper1, &empty1);
 	range_deserialize(typcache, r2, &lower2, &upper2, &empty2);
 
 	/* if either is empty, r1 is the correct answer */
 	if (empty1 || empty2)
-		PG_RETURN_RANGE_P(r1);
+		return r1;
 
 	cmp_l1l2 = range_cmp_bounds(typcache, &lower1, &lower2);
 	cmp_l1u2 = range_cmp_bounds(typcache, &lower1, &upper2);
@@ -991,34 +1034,34 @@ range_minus(PG_FUNCTION_ARGS)
 				 errmsg("result of range difference would not be contiguous")));
 
 	if (cmp_l1u2 > 0 || cmp_u1l2 < 0)
-		PG_RETURN_RANGE_P(r1);
+		return r1;
 
 	if (cmp_l1l2 >= 0 && cmp_u1u2 <= 0)
-		PG_RETURN_RANGE_P(make_empty_range(typcache));
+		return make_empty_range(typcache);
 
 	if (cmp_l1l2 <= 0 && cmp_u1l2 >= 0 && cmp_u1u2 <= 0)
 	{
 		lower2.inclusive = !lower2.inclusive;
 		lower2.lower = false;	/* it will become the upper bound */
-		PG_RETURN_RANGE_P(make_range(typcache, &lower1, &lower2, false));
+		return make_range(typcache, &lower1, &lower2, false);
 	}
 
 	if (cmp_l1l2 >= 0 && cmp_u1u2 >= 0 && cmp_l1u2 <= 0)
 	{
 		upper2.inclusive = !upper2.inclusive;
 		upper2.lower = true;	/* it will become the lower bound */
-		PG_RETURN_RANGE_P(make_range(typcache, &upper2, &upper1, false));
+		return make_range(typcache, &upper2, &upper1, false);
 	}
 
 	elog(ERROR, "unexpected case in range_minus");
-	PG_RETURN_NULL();
+	return NULL;
 }
 
 /*
  * Set union.  If strict is true, it is an error that the two input ranges
  * are not adjacent or overlapping.
  */
-static RangeType *
+RangeType *
 range_union_internal(TypeCacheEntry *typcache, RangeType *r1, RangeType *r2,
 					 bool strict)
 {
@@ -1099,6 +1142,19 @@ range_intersect(PG_FUNCTION_ARGS)
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
 	RangeType  *r2 = PG_GETARG_RANGE_P(1);
 	TypeCacheEntry *typcache;
+
+	/* 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));
+
+	PG_RETURN_RANGE_P(range_intersect_internal(typcache, r1, r2));
+}
+
+RangeType *
+range_intersect_internal(TypeCacheEntry *typcache, RangeType *r1, RangeType *r2)
+{
 	RangeBound	lower1,
 				lower2;
 	RangeBound	upper1,
@@ -1108,17 +1164,11 @@ range_intersect(PG_FUNCTION_ARGS)
 	RangeBound *result_lower;
 	RangeBound *result_upper;
 
-	/* 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));
-
 	range_deserialize(typcache, r1, &lower1, &upper1, &empty1);
 	range_deserialize(typcache, r2, &lower2, &upper2, &empty2);
 
-	if (empty1 || empty2 || !DatumGetBool(range_overlaps(fcinfo)))
-		PG_RETURN_RANGE_P(make_empty_range(typcache));
+	if (empty1 || empty2 || !range_overlaps_internal(typcache, r1, r2))
+		return make_empty_range(typcache);
 
 	if (range_cmp_bounds(typcache, &lower1, &lower2) >= 0)
 		result_lower = &lower1;
@@ -1130,54 +1180,83 @@ range_intersect(PG_FUNCTION_ARGS)
 	else
 		result_upper = &upper2;
 
-	PG_RETURN_RANGE_P(make_range(typcache, result_lower, result_upper, false));
+	return make_range(typcache, result_lower, result_upper, false);
 }
 
-/* Btree support */
+/* range, range -> range, range functions */
 
-/* btree comparator */
-Datum
-range_cmp(PG_FUNCTION_ARGS)
+/*
+ * range_split_internal - if r2 intersects the middle of r1, leaving non-empty
+ * ranges on both sides, then return true and set output1 and output2 to the
+ * results of r1 - r2 (in order). Otherwise return false and don't set output1
+ * or output2. Neither input range should be empty.
+ */
+bool
+range_split_internal(TypeCacheEntry *typcache, RangeType *r1, RangeType *r2,
+					 RangeType **output1, RangeType **output2)
 {
-	RangeType  *r1 = PG_GETARG_RANGE_P(0);
-	RangeType  *r2 = PG_GETARG_RANGE_P(1);
-	TypeCacheEntry *typcache;
 	RangeBound	lower1,
 				lower2;
 	RangeBound	upper1,
 				upper2;
 	bool		empty1,
 				empty2;
-	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));
 
 	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
+	if (range_cmp_bounds(typcache, &lower1, &lower2) < 0 &&
+		range_cmp_bounds(typcache, &upper1, &upper2) > 0)
 	{
-		cmp = range_cmp_bounds(typcache, &lower1, &lower2);
-		if (cmp == 0)
-			cmp = range_cmp_bounds(typcache, &upper1, &upper2);
+		/*
+		 * Need to invert inclusive/exclusive for the lower2 and upper2
+		 * points. They can't be infinite though. We're allowed to overwrite
+		 * these RangeBounds since they only exist locally.
+		 */
+		lower2.inclusive = !lower2.inclusive;
+		lower2.lower = false;
+		upper2.inclusive = !upper2.inclusive;
+		upper2.lower = true;
+
+		*output1 = make_range(typcache, &lower1, &lower2, false);
+		*output2 = make_range(typcache, &upper2, &upper1, false);
+		return true;
 	}
 
-	return cmp;
+	return false;
+}
+
+/* range -> range aggregate functions */
+
+Datum
+range_intersect_agg_transfn(PG_FUNCTION_ARGS)
+{
+	MemoryContext aggContext;
+	Oid			rngtypoid;
+	TypeCacheEntry *typcache;
+	RangeType  *result;
+	RangeType  *current;
+
+	if (!AggCheckCallContext(fcinfo, &aggContext))
+		elog(ERROR, "range_intersect_agg_transfn called in non-aggregate context");
+
+	rngtypoid = get_fn_expr_argtype(fcinfo->flinfo, 1);
+	if (!type_is_range(rngtypoid))
+		ereport(ERROR, (errmsg("range_intersect_agg must be called with a range")));
+
+	typcache = range_get_typcache(fcinfo, rngtypoid);
+
+	/* strictness ensures these are non-null */
+	result = PG_GETARG_RANGE_P(0);
+	current = PG_GETARG_RANGE_P(1);
+
+	result = range_intersect_internal(typcache, result, current);
+	PG_RETURN_RANGE_P(result);
 }
 
+
+/* Btree support */
+
 /* btree comparator */
 Datum
 range_cmp(PG_FUNCTION_ARGS)
@@ -1838,6 +1917,21 @@ range_get_flags(RangeType *range)
 }
 
 /*
+ * range_has_flag: set whether a range has a specific flag.
+ *
+ * This lets expose some of our functions that just check flags
+ * to the rest of the code base (like to multiranges)
+ * without writing full-fledged *_internal versions.
+ */
+bool
+range_has_flag(RangeType *r1, char flag)
+{
+	char		flags = range_get_flags(r1);
+
+	return flags & flag;
+}
+
+/*
  * range_set_contain_empty: set the RANGE_CONTAIN_EMPTY bit in the value.
  *
  * This is only needed in GiST operations, so we don't include a provision
diff --git a/src/include/catalog/pg_aggregate.dat b/src/include/catalog/pg_aggregate.dat
index 242d843347..07a7211c15 100644
--- a/src/include/catalog/pg_aggregate.dat
+++ b/src/include/catalog/pg_aggregate.dat
@@ -538,6 +538,17 @@
   aggtransfn => 'bytea_string_agg_transfn',
   aggfinalfn => 'bytea_string_agg_finalfn', aggtranstype => 'internal' },
 
+# range
+{ aggfnoid => 'range_intersect_agg(anyrange)',
+  aggtransfn => 'range_intersect_agg_transfn',
+  aggcombinefn => 'range_intersect_agg_transfn', aggtranstype => 'anyrange' },
+{ aggfnoid => 'range_intersect_agg(anymultirange)',
+  aggtransfn => 'multirange_intersect_agg_transfn',
+  aggcombinefn => 'multirange_intersect_agg_transfn', aggtranstype => 'anymultirange' },
+{ aggfnoid => 'range_agg(anyrange)', aggtransfn => 'range_agg_transfn',
+  aggfinalfn => 'range_agg_finalfn', aggfinalextra => 't',
+  aggtranstype => 'internal' },
+
 # json
 { aggfnoid => 'json_agg', aggtransfn => 'json_agg_transfn',
   aggfinalfn => 'json_agg_finalfn', aggtranstype => 'internal' },
diff --git a/src/include/catalog/pg_amproc.dat b/src/include/catalog/pg_amproc.dat
index b7cb2cb000..32965d6b3d 100644
--- a/src/include/catalog/pg_amproc.dat
+++ b/src/include/catalog/pg_amproc.dat
@@ -1210,12 +1210,13 @@
   amprocrighttype => 'anyrange', amprocnum => '4',
   amproc => 'brin_inclusion_union' },
 { amprocfamily => 'brin/range_inclusion_ops', amproclefttype => 'anyrange',
-  amprocrighttype => 'anyrange', amprocnum => '11', amproc => 'range_merge' },
+  amprocrighttype => 'anyrange', amprocnum => '11',
+  amproc => 'range_merge(anyrange,anyrange)' },
 { amprocfamily => 'brin/range_inclusion_ops', amproclefttype => 'anyrange',
   amprocrighttype => 'anyrange', amprocnum => '13',
   amproc => 'range_contains' },
 { amprocfamily => 'brin/range_inclusion_ops', amproclefttype => 'anyrange',
-  amprocrighttype => 'anyrange', amprocnum => '14', amproc => 'isempty' },
+  amprocrighttype => 'anyrange', amprocnum => '14', amproc => 'isempty(anyrange)' },
 
 # minmax pg_lsn
 { amprocfamily => 'brin/pg_lsn_minmax_ops', amproclefttype => 'pg_lsn',
diff --git a/src/include/catalog/pg_operator.dat b/src/include/catalog/pg_operator.dat
index 4d319bb5b3..ade223b6f3 100644
--- a/src/include/catalog/pg_operator.dat
+++ b/src/include/catalog/pg_operator.dat
@@ -3322,5 +3322,174 @@
   oprresult => 'bool', oprcom => '<(anymultirange,anymultirange)',
   oprnegate => '<=(anymultirange,anymultirange)', oprcode => 'multirange_gt',
   oprrest => 'scalargtsel', oprjoin => 'scalargtjoinsel' },
+{ oid => '8073', oid_symbol => 'OID_RANGE_OVERLAPS_MULTIRANGE_OP',
+  descr => 'overlaps',
+  oprname => '&&', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '&&(anymultirange,anyrange)',
+  oprcode => 'range_overlaps_multirange', oprrest => 'areasel',
+  oprjoin => 'areajoinsel' },
+{ oid => '8074', oid_symbol => 'OID_MULTIRANGE_OVERLAPS_RANGE_OP', descr => 'contains',
+  oprname => '&&', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcom => '&&(anyrange,anymultirange)',
+  oprcode => 'multirange_overlaps_range', oprrest => 'areasel',
+  oprjoin => 'areajoinsel' },
+{ oid => '8075', oid_symbol => 'OID_MULTIRANGE_OVERLAPS_MULTIRANGE_OP', descr => 'contains',
+  oprname => '&&', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '&&(anymultirange,anymultirange)',
+  oprcode => 'multirange_overlaps_multirange', oprrest => 'areasel',
+  oprjoin => 'areajoinsel' },
+{ oid => '8064', oid_symbol => 'OID_MULTIRANGE_CONTAINS_ELEM_OP',
+  descr => 'contains',
+  oprname => '@>', oprleft => 'anymultirange', oprright => 'anyelement',
+  oprresult => 'bool', oprcom => '<@(anyelement,anymultirange)',
+  oprcode => 'multirange_contains_elem', oprrest => 'contsel',
+  oprjoin => 'contjoinsel' },
+{ oid => '8065', oid_symbol => 'OID_MULTIRANGE_CONTAINS_RANGE_OP', descr => 'contains',
+  oprname => '@>', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcom => '<@(anyrange,anymultirange)',
+  oprcode => 'multirange_contains_range', oprrest => 'contsel',
+  oprjoin => 'contjoinsel' },
+{ oid => '8066', oid_symbol => 'OID_MULTIRANGE_CONTAINS_MULTIRANGE_OP', descr => 'contains',
+  oprname => '@>', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '<@(anymultirange,anymultirange)',
+  oprcode => 'multirange_contains_multirange', oprrest => 'contsel',
+  oprjoin => 'contjoinsel' },
+{ oid => '8067', oid_symbol => 'OID_MULTIRANGE_ELEM_CONTAINED_OP',
+  descr => 'is contained by',
+  oprname => '<@', oprleft => 'anyelement', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '@>(anymultirange,anyelement)',
+  oprcode => 'elem_contained_by_multirange', oprrest => 'contsel',
+  oprjoin => 'contjoinsel' },
+{ oid => '8068', oid_symbol => 'OID_MULTIRANGE_RANGE_CONTAINED_OP',
+  descr => 'is contained by',
+  oprname => '<@', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '@>(anymultirange,anyrange)',
+  oprcode => 'range_contained_by_multirange', oprrest => 'contsel',
+  oprjoin => 'contjoinsel' },
+{ oid => '8069', oid_symbol => 'OID_MULTIRANGE_MULTIRANGE_CONTAINED_OP',
+  descr => 'is contained by',
+  oprname => '<@', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '@>(anymultirange,anymultirange)',
+  oprcode => 'multirange_contained_by_multirange', oprrest => 'contsel',
+  oprjoin => 'contjoinsel' },
+{ oid => '8106', oid_symbol => 'OID_RANGE_OVERLAPS_LEFT_MULTIRANGE_OP',
+  descr => 'overlaps or is left of',
+  oprname => '&<', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcode => 'range_overleft_multirange', oprrest => 'scalarltsel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8107', oid_symbol => 'OID_MULTIRANGE_OVERLAPS_LEFT_RANGE_OP',
+  descr => 'overlaps or is left of',
+  oprname => '&<', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcode => 'multirange_overleft_range', oprrest => 'scalarltsel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8108', oid_symbol => 'OID_MULTIRANGE_OVERLAPS_LEFT_MULTIRANGE_OP',
+  descr => 'overlaps or is left of',
+  oprname => '&<', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcode => 'multirange_overleft_multirange', oprrest => 'scalarltsel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8109', oid_symbol => 'OID_RANGE_OVERLAPS_RIGHT_MULTIRANGE_OP',
+  descr => 'overlaps or is right of',
+  oprname => '&>', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcode => 'range_overright_multirange', oprrest => 'scalargtsel',
+  oprjoin => 'scalargtjoinsel' },
+{ oid => '8110', oid_symbol => 'OID_MULTIRANGE_OVERLAPS_RIGHT_RANGE_OP',
+  descr => 'overlaps or is right of',
+  oprname => '&>', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcode => 'multirange_overright_range', oprrest => 'scalargtsel',
+  oprjoin => 'scalargtjoinsel' },
+{ oid => '8111', oid_symbol => 'OID_MULTIRANGE_OVERLAPS_RIGHT_MULTIRANGE_OP',
+  descr => 'overlaps or is right of',
+  oprname => '&>', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcode => 'multirange_overright_multirange', oprrest => 'scalargtsel',
+  oprjoin => 'scalargtjoinsel' },
+{ oid => '8091', oid_symbol => 'OID_RANGE_ADJACENT_MULTIRANGE_OP', descr => 'is adjacent to',
+  oprname => '-|-', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '-|-(anymultirange,anyrange)',
+  oprcode => 'range_adjacent_multirange', oprrest => 'scalarltsel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8092', oid_symbol => 'OID_MULTIRANGE_ADJACENT_RANGE_OP', descr => 'is adjacent to',
+  oprname => '-|-', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcom => '-|-(anyrange,anymultirange)',
+  oprcode => 'multirange_adjacent_range', oprrest => 'scalarltsel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8093', oid_symbol => 'OID_MULTIRANGE_ADJACENT_MULTIRANGE_OP', descr => 'is adjacent to',
+  oprname => '-|-', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '-|-(anymultirange,anymultirange)',
+  oprcode => 'multirange_adjacent_multirange', oprrest => 'scalarltsel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8138', descr => 'multirange union',
+  oprname => '@+', oprleft => 'anyrange', oprright => 'anyrange',
+  oprresult => 'anymultirange', oprcom => '@+(anyrange,anyrange)',
+  oprcode => 'range_union_range' },
+{ oid => '8115', descr => 'multirange union',
+  oprname => '@+', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'anymultirange', oprcom => '@+(anymultirange,anyrange)',
+  oprcode => 'range_union_multirange' },
+{ oid => '8116', descr => 'multirange union',
+  oprname => '@+', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'anymultirange', oprcom => '@+(anyrange,anymultirange)',
+  oprcode => 'multirange_union_range' },
+{ oid => '8117', descr => 'multirange union',
+  oprname => '@+', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'anymultirange', oprcom => '@+(anymultirange,anymultirange)',
+  oprcode => 'multirange_union_multirange' },
+{ oid => '8140', descr => 'multirange minus',
+  oprname => '@-', oprleft => 'anyrange', oprright => 'anyrange',
+  oprresult => 'anymultirange', oprcode => 'range_minus_range' },
+{ oid => '8121', descr => 'multirange minus',
+  oprname => '@-', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'anymultirange', oprcode => 'range_minus_multirange' },
+{ oid => '8122', descr => 'multirange minus',
+  oprname => '@-', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'anymultirange', oprcode => 'multirange_minus_range' },
+{ oid => '8123', descr => 'multirange minus',
+  oprname => '@-', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'anymultirange', oprcode => 'multirange_minus_multirange' },
+{ oid => '8142', descr => 'multirange intersect',
+  oprname => '@*', oprleft => 'anyrange', oprright => 'anyrange',
+  oprresult => 'anymultirange', oprcom => '@*(anyrange,anyrange)',
+  oprcode => 'range_intersect_range' },
+{ oid => '8127', descr => 'multirange intersect',
+  oprname => '@*', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'anymultirange', oprcom => '@*(anymultirange,anyrange)',
+  oprcode => 'range_intersect_multirange' },
+{ oid => '8128', descr => 'multirange intersect',
+  oprname => '@*', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'anymultirange', oprcom => '@*(anyrange,anymultirange)',
+  oprcode => 'multirange_intersect_range' },
+{ oid => '8129', descr => 'multirange intersect',
+  oprname => '@*', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'anymultirange', oprcom => '@*(anymultirange,anymultirange)',
+  oprcode => 'multirange_intersect_multirange' },
+{ oid => '8082', oid_symbol => 'OID_RANGE_BEFORE_MULTIRANGE_OP', descr => 'is left of',
+  oprname => '<<', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '>>(anymultirange,anyrange)',
+  oprcode => 'range_before_multirange', oprrest => 'scalarltsel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8083', oid_symbol => 'OID_MULTIRANGE_BEFORE_RANGE_OP', descr => 'is left of',
+  oprname => '<<', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcom => '>>(anyrange,anymultirange)',
+  oprcode => 'multirange_before_range', oprrest => 'scalarltsel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8084', oid_symbol => 'OID_MULTIRANGE_BEFORE_MULTIRANGE_OP', descr => 'is left of',
+  oprname => '<<', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '>>(anymultirange,anymultirange)',
+  oprcode => 'multirange_before_multirange', oprrest => 'scalarltsel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8085', oid_symbol => 'OID_RANGE_AFTER_MULTIRANGE_OP', descr => 'is right of',
+  oprname => '>>', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '<<(anymultirange,anyrange)',
+  oprcode => 'range_after_multirange', oprrest => 'scalargtsel',
+  oprjoin => 'scalargtjoinsel' },
+{ oid => '8086', oid_symbol => 'OID_MULTIRANGE_AFTER_RANGE_OP', descr => 'is right of',
+  oprname => '>>', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcom => '<<(anyrange,anymultirange)',
+  oprcode => 'multirange_after_range', oprrest => 'scalargtsel',
+  oprjoin => 'scalargtjoinsel' },
+{ oid => '8087', oid_symbol => 'OID_MULTIRANGE_AFTER_MULTIRANGE_OP', descr => 'is right of',
+  oprname => '>>', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '<<(anymultirange,anymultirange)',
+  oprcode => 'multirange_after_multirange', oprrest => 'scalargtsel',
+  oprjoin => 'scalargtjoinsel' },
 
 ]
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index db1bef821f..274a25027f 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -9585,6 +9585,10 @@
   descr => 'the smallest range which includes both of the given ranges',
   proname => 'range_merge', prorettype => 'anyrange',
   proargtypes => 'anyrange anyrange', prosrc => 'range_merge' },
+{ oid => '8143',
+  descr => 'the smallest range which includes the whole multirange',
+  proname => 'range_merge', prorettype => 'anyrange',
+  proargtypes => 'anymultirange', prosrc => 'range_merge_from_multirange' },
 { oid => '3868',
   proname => 'range_intersect', prorettype => 'anyrange',
   proargtypes => 'anyrange anyrange', prosrc => 'range_intersect' },
@@ -9634,6 +9638,13 @@
 { oid => '3169', descr => 'restriction selectivity for range operators',
   proname => 'rangesel', provolatile => 's', prorettype => 'float8',
   proargtypes => 'internal oid internal int4', prosrc => 'rangesel' },
+{ oid => '8133', descr => 'range aggregate by intersecting',
+  proname => 'range_intersect_agg_transfn', prorettype => 'anyrange',
+  proargtypes => 'anyrange anyrange', prosrc => 'range_intersect_agg_transfn'},
+{ oid => '8134', descr => 'range aggregate by intersecting',
+  proname => 'range_intersect_agg', prokind => 'a', proisstrict => 'f',
+  prorettype => 'anyrange', proargtypes => 'anyrange',
+  prosrc => 'aggregate_dummy'},
 
 { oid => '3914', descr => 'convert an int4 range to canonical form',
   proname => 'int4range_canonical', prorettype => 'int4range',
@@ -9721,9 +9732,165 @@
 { oid => '8007', descr => 'I/O',
   proname => 'multirange_send', provolatile => 's', prorettype => 'bytea',
   proargtypes => 'anymultirange', prosrc => 'multirange_send' },
+{ oid => '8094', descr => 'lower bound of multirange',
+  proname => 'lower', prorettype => 'anyelement', proargtypes => 'anymultirange',
+  prosrc => 'multirange_lower' },
+{ oid => '8095', descr => 'upper bound of multirange',
+  proname => 'upper', prorettype => 'anyelement', proargtypes => 'anymultirange',
+  prosrc => 'multirange_upper' },
+{ oid => '8057', descr => 'is the multirange empty?',
+  proname => 'isempty', prorettype => 'bool',
+  proargtypes => 'anymultirange', prosrc => 'multirange_empty' },
+{ oid => '8096', descr => 'is the multirange\'s lower bound inclusive?',
+  proname => 'lower_inc', prorettype => 'bool', proargtypes => 'anymultirange',
+  prosrc => 'multirange_lower_inc' },
+{ oid => '8097', descr => 'is the multirange\'s upper bound inclusive?',
+  proname => 'upper_inc', prorettype => 'bool', proargtypes => 'anymultirange',
+  prosrc => 'multirange_upper_inc' },
+{ oid => '8098', descr => 'is the multirange\'s lower bound infinite?',
+  proname => 'lower_inf', prorettype => 'bool', proargtypes => 'anymultirange',
+  prosrc => 'multirange_lower_inf' },
+{ oid => '8099', descr => 'is the multirange\'s upper bound infinite?',
+  proname => 'upper_inf', prorettype => 'bool', proargtypes => 'anymultirange',
+  prosrc => 'multirange_upper_inf' },
 { oid => '8008', descr => 'multirange typanalyze',
   proname => 'multirange_typanalyze', provolatile => 's', prorettype => 'bool',
   proargtypes => 'internal', prosrc => 'multirange_typanalyze' },
+{ oid => '8033',
+  proname => 'multirange_eq', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_eq' },
+{ oid => '8034',
+  proname => 'multirange_ne', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_ne' },
+{ oid => '8070',
+  proname => 'range_overlaps_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_overlaps_multirange' },
+{ oid => '8071',
+  proname => 'multirange_overlaps_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_overlaps_range' },
+{ oid => '8072',
+  proname => 'multirange_overlaps_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_overlaps_multirange' },
+{ oid => '8058',
+  proname => 'multirange_contains_elem', prorettype => 'bool',
+  proargtypes => 'anymultirange anyelement', prosrc => 'multirange_contains_elem' },
+{ oid => '8059',
+  proname => 'multirange_contains_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_contains_range' },
+{ oid => '8060',
+  proname => 'multirange_contains_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_contains_multirange' },
+{ oid => '8061',
+  proname => 'elem_contained_by_multirange', prorettype => 'bool',
+  proargtypes => 'anyelement anymultirange', prosrc => 'elem_contained_by_multirange' },
+{ oid => '8062',
+  proname => 'range_contained_by_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_contained_by_multirange' },
+{ oid => '8063',
+  proname => 'multirange_contained_by_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_contained_by_multirange' },
+{ oid => '8088',
+  proname => 'range_adjacent_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_adjacent_multirange' },
+{ oid => '8089',
+  proname => 'multirange_adjacent_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_adjacent_multirange' },
+{ oid => '8090',
+  proname => 'multirange_adjacent_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_adjacent_range' },
+{ oid => '8076',
+  proname => 'range_before_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_before_multirange' },
+{ oid => '8077',
+  proname => 'multirange_before_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_before_range' },
+{ oid => '8078',
+  proname => 'multirange_before_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_before_multirange' },
+{ oid => '8079',
+  proname => 'range_after_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_after_multirange' },
+{ oid => '8080',
+  proname => 'multirange_after_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_after_range' },
+{ oid => '8081',
+  proname => 'multirange_after_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_after_multirange' },
+{ oid => '8100',
+  proname => 'range_overleft_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_overleft_multirange' },
+{ oid => '8101',
+  proname => 'multirange_overleft_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_overleft_range' },
+{ oid => '8102',
+  proname => 'multirange_overleft_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_overleft_multirange' },
+{ oid => '8103',
+  proname => 'range_overright_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_overright_multirange' },
+{ oid => '8104',
+  proname => 'multirange_overright_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_overright_range' },
+{ oid => '8105',
+  proname => 'multirange_overright_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_overright_multirange' },
+{ oid => '8137',
+  proname => 'range_union_range', prorettype => 'anymultirange',
+  proargtypes => 'anyrange anyrange', prosrc => 'range_union_range' },
+{ oid => '8112',
+  proname => 'range_union_multirange', prorettype => 'anymultirange',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_union_multirange' },
+{ oid => '8113',
+  proname => 'multirange_union_range', prorettype => 'anymultirange',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_union_range' },
+{ oid => '8114',
+  proname => 'multirange_union_multirange', prorettype => 'anymultirange',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_union_multirange' },
+{ oid => '8139',
+  proname => 'range_minus_range', prorettype => 'anymultirange',
+  proargtypes => 'anyrange anyrange', prosrc => 'range_minus_range' },
+{ oid => '8118',
+  proname => 'range_minus_multirange', prorettype => 'anymultirange',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_minus_multirange' },
+{ oid => '8119',
+  proname => 'multirange_minus_range', prorettype => 'anymultirange',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_minus_range' },
+{ oid => '8120',
+  proname => 'multirange_minus_multirange', prorettype => 'anymultirange',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_minus_multirange' },
+{ oid => '8141',
+  proname => 'range_intersect_range', prorettype => 'anymultirange',
+  proargtypes => 'anyrange anyrange', prosrc => 'range_intersect_range' },
+{ oid => '8124',
+  proname => 'range_intersect_multirange', prorettype => 'anymultirange',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_intersect_multirange' },
+{ oid => '8125',
+  proname => 'multirange_intersect_range', prorettype => 'anymultirange',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_intersect_range' },
+{ oid => '8126',
+  proname => 'multirange_intersect_multirange', prorettype => 'anymultirange',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_intersect_multirange' },
+{ oid => '8022', descr => 'less-equal-greater',
+  proname => 'multirange_cmp', prorettype => 'int4',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_cmp' },
+{ oid => '8023',
+  proname => 'multirange_lt', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_lt' },
+{ oid => '8024',
+  proname => 'multirange_le', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_le' },
+{ oid => '8025',
+  proname => 'multirange_ge', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_ge' },
+{ oid => '8026',
+  proname => 'multirange_gt', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_gt' },
+{ oid => '8038', descr => 'hash a multirange',
+  proname => 'hash_multirange', prorettype => 'int4', proargtypes => 'anymultirange',
+  prosrc => 'hash_multirange' },
+{ oid => '8039', descr => 'hash a multirange',
+  proname => 'hash_multirange_extended', prorettype => 'int8',
+  proargtypes => 'anymultirange int8', prosrc => 'hash_multirange_extended' },
 
 { oid => '8045', descr => 'int4multirange constructor',
   proname => 'int4multirange', proisstrict => 'f',
@@ -9779,6 +9946,24 @@
   prorettype => 'int8multirange', proargtypes => '_int8range',
   proallargtypes => '{_int8range}', proargmodes => '{v}',
   prosrc => 'multirange_constructor1' },
+{ oid => '8130', descr => 'aggregate transition function',
+  proname => 'range_agg_transfn', proisstrict => 'f', prorettype => 'internal',
+  proargtypes => 'internal anyrange', prosrc => 'range_agg_transfn' },
+{ oid => '8131', descr => 'aggregate final function',
+  proname => 'range_agg_finalfn', proisstrict => 'f', prorettype => 'anymultirange',
+  proargtypes => 'internal anyrange', prosrc => 'range_agg_finalfn' },
+{ oid => '8132', descr => 'combine aggregate input into a multirange',
+  proname => 'range_agg', prokind => 'a', proisstrict => 'f',
+  prorettype => 'anymultirange', proargtypes => 'anyrange',
+  prosrc => 'aggregate_dummy' },
+{ oid => '8135', descr => 'range aggregate by intersecting',
+  proname => 'multirange_intersect_agg_transfn', prorettype => 'anymultirange',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_intersect_agg_transfn'},
+{ oid => '8136', descr => 'range aggregate by intersecting',
+  proname => 'range_intersect_agg', prokind => 'a', proisstrict => 'f',
+  prorettype => 'anymultirange', proargtypes => 'anymultirange',
+  prosrc => 'aggregate_dummy'},
+
 # date, time, timestamp constructors
 { oid => '3846', descr => 'construct date',
   proname => 'make_date', prorettype => 'date', proargtypes => 'int4 int4 int4',
diff --git a/src/include/utils/multirangetypes.h b/src/include/utils/multirangetypes.h
index ed2e19aafa..cc4f82b0ba 100644
--- a/src/include/utils/multirangetypes.h
+++ b/src/include/utils/multirangetypes.h
@@ -59,6 +59,40 @@ extern bool multirange_eq_internal(TypeCacheEntry *typcache, MultirangeType * mr
 								   MultirangeType * mr2);
 extern bool multirange_ne_internal(TypeCacheEntry *typcache, MultirangeType * mr1,
 								   MultirangeType * mr2);
+extern bool multirange_contains_elem_internal(TypeCacheEntry *typcache, MultirangeType * mr,
+											  Datum elem);
+extern bool multirange_contains_range_internal(TypeCacheEntry *typcache, MultirangeType * mr,
+											   RangeType *r);
+extern bool multirange_contains_multirange_internal(TypeCacheEntry *typcache,
+													MultirangeType * mr1,
+													MultirangeType * mr2);
+extern bool range_overlaps_multirange_internal(TypeCacheEntry *typcache, RangeType *r,
+											   MultirangeType * mr);
+extern bool multirange_overlaps_multirange_internal(TypeCacheEntry *typcache,
+													MultirangeType * mr1,
+													MultirangeType * mr2);
+extern bool range_before_multirange_internal(TypeCacheEntry *typcache, RangeType *r,
+											 MultirangeType * mr);
+extern bool range_after_multirange_internal(TypeCacheEntry *typcache, RangeType *r,
+											MultirangeType * mr);
+extern bool multirange_before_multirange_internal(TypeCacheEntry *typcache,
+												  MultirangeType * mr1,
+												  MultirangeType * mr2);
+extern MultirangeType * range_union_multirange_internal(TypeCacheEntry *typcache,
+														RangeType *r,
+														MultirangeType * mr);
+extern MultirangeType * multirange_minus_multirange_internal(Oid mltrngtypoid,
+															 TypeCacheEntry *rangetyp,
+															 int32 range_count1,
+															 RangeType **ranges1,
+															 int32 range_count2,
+															 RangeType **ranges2);
+extern MultirangeType * multirange_intersect_multirange_internal(Oid mltrngtypoid,
+																 TypeCacheEntry *rangetyp,
+																 int32 range_count1,
+																 RangeType **ranges1,
+																 int32 range_count2,
+																 RangeType **ranges2);
 
 /* assorted support functions */
 extern TypeCacheEntry *multirange_get_typcache(FunctionCallInfo fcinfo,
diff --git a/src/include/utils/rangetypes.h b/src/include/utils/rangetypes.h
index 6323034548..33775a9d42 100644
--- a/src/include/utils/rangetypes.h
+++ b/src/include/utils/rangetypes.h
@@ -94,13 +94,22 @@ typedef struct
  * prototypes for functions defined in rangetypes.c
  */
 
-extern bool range_contains_elem_internal(TypeCacheEntry *typcache, RangeType *r, Datum val);
-
 /* internal versions of the above */
+extern int	range_cmp_internal(TypeCacheEntry *typcache, RangeType *r1,
+							   RangeType *r2);
+extern uint32 hash_range_internal(TypeCacheEntry *typcache, RangeType *r);
+extern uint64 hash_range_extended_internal(TypeCacheEntry *typcache, RangeType *r,
+										   Datum seed);
+extern Datum range_lower_internal(TypeCacheEntry *typcache, RangeType *r1,
+								  bool *isnull);
+extern Datum range_upper_internal(TypeCacheEntry *typcache, RangeType *r1,
+								  bool *isnull);
 extern bool range_eq_internal(TypeCacheEntry *typcache, RangeType *r1,
 							  RangeType *r2);
 extern bool range_ne_internal(TypeCacheEntry *typcache, RangeType *r1,
 							  RangeType *r2);
+extern bool range_contains_elem_internal(TypeCacheEntry *typcache, RangeType *r,
+										 Datum val);
 extern bool range_contains_internal(TypeCacheEntry *typcache, RangeType *r1,
 									RangeType *r2);
 extern bool range_contained_by_internal(TypeCacheEntry *typcache, RangeType *r1,
@@ -117,6 +126,12 @@ extern bool range_overleft_internal(TypeCacheEntry *typcache, RangeType *r1,
 									RangeType *r2);
 extern bool range_overright_internal(TypeCacheEntry *typcache, RangeType *r1,
 									 RangeType *r2);
+extern RangeType *range_union_internal(TypeCacheEntry *typcache, RangeType *r1,
+									   RangeType *r2, bool strict);
+extern RangeType *range_minus_internal(TypeCacheEntry *typcache, RangeType *r1,
+									   RangeType *r2);
+extern RangeType *range_intersect_internal(TypeCacheEntry *typcache, RangeType *r1,
+										   RangeType *r2);
 
 /* assorted support functions */
 extern TypeCacheEntry *range_get_typcache(FunctionCallInfo fcinfo,
@@ -127,6 +142,7 @@ extern void range_deserialize(TypeCacheEntry *typcache, RangeType *range,
 							  RangeBound *lower, RangeBound *upper,
 							  bool *empty);
 extern char range_get_flags(RangeType *range);
+extern bool range_has_flag(RangeType *range, char flag);
 extern void range_set_contain_empty(RangeType *range);
 extern RangeType *make_range(TypeCacheEntry *typcache, RangeBound *lower,
 							 RangeBound *upper, bool empty);
@@ -134,8 +150,12 @@ extern int	range_cmp_bounds(TypeCacheEntry *typcache, RangeBound *b1,
 							 RangeBound *b2);
 extern int	range_cmp_bound_values(TypeCacheEntry *typcache, RangeBound *b1,
 								   RangeBound *b2);
+extern int	range_compare(const void *key1, const void *key2, void *arg);
 extern bool bounds_adjacent(TypeCacheEntry *typcache, RangeBound bound1,
 							RangeBound bound2);
 extern RangeType *make_empty_range(TypeCacheEntry *typcache);
+extern bool range_split_internal(TypeCacheEntry *typcache, RangeType *r1,
+								 RangeType *r2, RangeType **output1,
+								 RangeType **output2);
 
 #endif							/* RANGETYPES_H */
diff --git a/src/test/regress/expected/dependency.out b/src/test/regress/expected/dependency.out
index b4a11b8aa9..778699a961 100644
--- a/src/test/regress/expected/dependency.out
+++ b/src/test/regress/expected/dependency.out
@@ -141,6 +141,7 @@ owner of table deptest
 owner of function deptest_func()
 owner of type deptest_enum
 owner of type deptest_range
+owner of type deptest_multirange
 owner of table deptest2
 owner of sequence ss1
 owner of type deptest_t
diff --git a/src/test/regress/expected/multirangetypes.out b/src/test/regress/expected/multirangetypes.out
index 5749537635..2276d6d002 100644
--- a/src/test/regress/expected/multirangetypes.out
+++ b/src/test/regress/expected/multirangetypes.out
@@ -290,3 +290,2400 @@ select textmultirange(textrange('a', 'c'), textrange('b', 'd'));
  {[a,d)}
 (1 row)
 
+--
+-- create some test data and test the operators
+--
+CREATE TABLE nummultirange_test (nmr NUMMULTIRANGE);
+CREATE INDEX nummultirange_test_btree ON nummultirange_test(nmr);
+INSERT INTO nummultirange_test VALUES('{}');
+INSERT INTO nummultirange_test VALUES('{[,)}');
+INSERT INTO nummultirange_test VALUES('{[3,]}');
+INSERT INTO nummultirange_test VALUES('{[,), [3,]}');
+INSERT INTO nummultirange_test VALUES('{[, 5)}');
+INSERT INTO nummultirange_test VALUES(nummultirange());
+INSERT INTO nummultirange_test VALUES(nummultirange(variadic '{}'::numrange[]));
+INSERT INTO nummultirange_test VALUES(nummultirange(numrange(1.1, 2.2)));
+INSERT INTO nummultirange_test VALUES('{empty}');
+INSERT INTO nummultirange_test VALUES(nummultirange(numrange(1.7, 1.7, '[]'), numrange(1.7, 1.9)));
+INSERT INTO nummultirange_test VALUES(nummultirange(numrange(1.7, 1.7, '[]'), numrange(1.9, 2.1)));
+SELECT nmr, isempty(nmr), lower(nmr), upper(nmr) FROM nummultirange_test ORDER BY nmr;
+          nmr          | isempty | lower | upper 
+-----------------------+---------+-------+-------
+ {}                    | t       |       |      
+ {}                    | t       |       |      
+ {}                    | t       |       |      
+ {}                    | t       |       |      
+ {(,5)}                | f       |       |     5
+ {(,)}                 | f       |       |      
+ {(,)}                 | f       |       |      
+ {[1.1,2.2)}           | f       |   1.1 |   2.2
+ {[1.7,1.7],[1.9,2.1)} | f       |   1.7 |   2.1
+ {[1.7,1.9)}           | f       |   1.7 |   1.9
+ {[3,)}                | f       |     3 |      
+(11 rows)
+
+SELECT nmr, lower_inc(nmr), lower_inf(nmr), upper_inc(nmr), upper_inf(nmr) FROM nummultirange_test ORDER BY nmr;
+          nmr          | lower_inc | lower_inf | upper_inc | upper_inf 
+-----------------------+-----------+-----------+-----------+-----------
+ {}                    | f         | f         | f         | f
+ {}                    | f         | f         | f         | f
+ {}                    | f         | f         | f         | f
+ {}                    | f         | f         | f         | f
+ {(,5)}                | f         | t         | f         | f
+ {(,)}                 | f         | t         | f         | t
+ {(,)}                 | f         | t         | f         | t
+ {[1.1,2.2)}           | t         | f         | f         | f
+ {[1.7,1.7],[1.9,2.1)} | t         | f         | f         | f
+ {[1.7,1.9)}           | t         | f         | f         | f
+ {[3,)}                | t         | f         | f         | t
+(11 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr = '{}';
+ nmr 
+-----
+ {}
+ {}
+ {}
+ {}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr = '{(,5)}';
+  nmr   
+--------
+ {(,5)}
+(1 row)
+
+SELECT * FROM nummultirange_test WHERE nmr = '{[3,)}';
+  nmr   
+--------
+ {[3,)}
+(1 row)
+
+SELECT * FROM nummultirange_test WHERE nmr = '{[1.7,1.7]}';
+ nmr 
+-----
+(0 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr = '{[1.7,1.7],[1.9,2.1)}';
+          nmr          
+-----------------------
+ {[1.7,1.7],[1.9,2.1)}
+(1 row)
+
+SELECT * FROM nummultirange_test WHERE nmr < '{}';
+ nmr 
+-----
+(0 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr < '{[-1000.0, -1000.0]}';
+  nmr   
+--------
+ {}
+ {(,)}
+ {(,)}
+ {(,5)}
+ {}
+ {}
+ {}
+(7 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr < '{[0.0, 1.0]}';
+  nmr   
+--------
+ {}
+ {(,)}
+ {(,)}
+ {(,5)}
+ {}
+ {}
+ {}
+(7 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr < '{[1000.0, 1001.0]}';
+          nmr          
+-----------------------
+ {}
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+ {}
+ {}
+ {[1.1,2.2)}
+ {}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(11 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr <= '{}';
+ nmr 
+-----
+ {}
+ {}
+ {}
+ {}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr <= '{[3,)}';
+          nmr          
+-----------------------
+ {}
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+ {}
+ {}
+ {[1.1,2.2)}
+ {}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(11 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr >= '{}';
+          nmr          
+-----------------------
+ {}
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+ {}
+ {}
+ {[1.1,2.2)}
+ {}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(11 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr >= '{[3,)}';
+  nmr   
+--------
+ {[3,)}
+(1 row)
+
+SELECT * FROM nummultirange_test WHERE nmr > '{}';
+          nmr          
+-----------------------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+ {[1.1,2.2)}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(7 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr > '{[-1000.0, -1000.0]}';
+          nmr          
+-----------------------
+ {[3,)}
+ {[1.1,2.2)}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr > '{[0.0, 1.0]}';
+          nmr          
+-----------------------
+ {[3,)}
+ {[1.1,2.2)}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr > '{[1000.0, 1001.0]}';
+ nmr 
+-----
+(0 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr <> '{}';
+          nmr          
+-----------------------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+ {[1.1,2.2)}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(7 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr <> '{(,5)}';
+          nmr          
+-----------------------
+ {}
+ {(,)}
+ {[3,)}
+ {(,)}
+ {}
+ {}
+ {[1.1,2.2)}
+ {}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(10 rows)
+
+select nummultirange(numrange(2.0, 1.0));
+ERROR:  range lower bound must be less than or equal to range upper bound
+select nummultirange(numrange(5.0, 6.0), numrange(1.0, 2.0));
+     nummultirange     
+-----------------------
+ {[1.0,2.0),[5.0,6.0)}
+(1 row)
+
+-- overlaps
+SELECT * FROM nummultirange_test WHERE range_overlaps_multirange(numrange(4.0, 4.2), nmr);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE numrange(4.0, 4.2) && nmr;
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE multirange_overlaps_range(nmr, numrange(4.0, 4.2));
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr && numrange(4.0, 4.2);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE multirange_overlaps_multirange(nmr, nummultirange(numrange(4.0, 4.2), numrange(6.0, 7.0)));
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr && nummultirange(numrange(4.0, 4.2), numrange(6.0, 7.0));
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr && nummultirange(numrange(6.0, 7.0));
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+(3 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr && nummultirange(numrange(6.0, 7.0), numrange(8.0, 9.0));
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+(3 rows)
+
+-- mr contains x
+SELECT * FROM nummultirange_test WHERE multirange_contains_elem(nmr, 4.0);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr @> 4.0;
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE multirange_contains_range(nmr, numrange(4.0, 4.2));
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr @> numrange(4.0, 4.2);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE multirange_contains_multirange(nmr, '{[4.0,4.2), [6.0, 8.0)}');
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+(3 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr @> '{[4.0,4.2), [6.0, 8.0)}'::nummultirange;
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+(3 rows)
+
+-- x is contained by mr
+SELECT * FROM nummultirange_test WHERE elem_contained_by_multirange(4.0, nmr);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE 4.0 <@ nmr;
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE range_contained_by_multirange(numrange(4.0, 4.2), nmr);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE numrange(4.0, 4.2) <@ nmr;
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE multirange_contained_by_multirange('{[4.0,4.2), [6.0, 8.0)}', nmr);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+(3 rows)
+
+SELECT * FROM nummultirange_test WHERE '{[4.0,4.2), [6.0, 8.0)}'::nummultirange <@ nmr;
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+(3 rows)
+
+-- overlaps
+SELECT 'empty'::numrange && nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT 'empty'::numrange && nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() && 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) && 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() && nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() && nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) && nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) && nummultirange(numrange(1,2), numrange(7,8));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(7,8)) && nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) && nummultirange(numrange(1,2), numrange(3.5,8));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(3.5,8)) && numrange(3,4);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(3.5,8)) && nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+-- contains
+SELECT nummultirange() @> nummultirange();
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange() @> 'empty'::numrange;
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(null,null)) @> numrange(1,2);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(null,null)) @> numrange(null,2);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(null,null)) @> numrange(2,null);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(null,5)) @> numrange(null,3);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(null,5)) @> numrange(null,8);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(5,null)) @> numrange(8,null);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(5,null)) @> numrange(3,null);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,5)) @> numrange(8,9);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,5)) @> numrange(3,9);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,5)) @> numrange(1,4);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,5)) @> numrange(1,5);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(-4,-2), numrange(1,5)) @> numrange(1,5);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,5), numrange(8,9)) @> numrange(1,5);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,5), numrange(8,9)) @> numrange(6,7);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,5), numrange(6,9)) @> numrange(6,7);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[1,5)}'::nummultirange @> '{[1,5)}';
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[-4,-2), [1,5)}'::nummultirange @> '{[1,5)}';
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[1,5), [8,9)}'::nummultirange @> '{[1,5)}';
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[1,5), [8,9)}'::nummultirange @> '{[6,7)}';
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT '{[1,5), [6,9)}'::nummultirange @> '{[6,7)}';
+ ?column? 
+----------
+ t
+(1 row)
+
+-- is contained by
+SELECT nummultirange() <@ nummultirange();
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT 'empty'::numrange <@ nummultirange();
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,2) <@ nummultirange(numrange(null,null));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(null,2) <@ nummultirange(numrange(null,null));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(2,null) <@ nummultirange(numrange(null,null));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(null,3) <@ nummultirange(numrange(null,5));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(null,8) <@ nummultirange(numrange(null,5));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(8,null) <@ nummultirange(numrange(5,null));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(3,null) <@ nummultirange(numrange(5,null));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(8,9) <@ nummultirange(numrange(1,5));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(3,9) <@ nummultirange(numrange(1,5));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(1,4) <@ nummultirange(numrange(1,5));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,5) <@ nummultirange(numrange(1,5));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,5) <@ nummultirange(numrange(-4,-2), numrange(1,5));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,5) <@ nummultirange(numrange(1,5), numrange(8,9));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(6,7) <@ nummultirange(numrange(1,5), numrange(8,9));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(6,7) <@ nummultirange(numrange(1,5), numrange(6,9));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[1,5)}' <@ '{[1,5)}'::nummultirange;
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[1,5)}' <@ '{[-4,-2), [1,5)}'::nummultirange;
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[1,5)}' <@ '{[1,5), [8,9)}'::nummultirange;
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[6,7)}' <@ '{[1,5), [8,9)}'::nummultirange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT '{[6,7)}' <@ '{[1,5), [6,9)}'::nummultirange;
+ ?column? 
+----------
+ t
+(1 row)
+
+-- overleft
+SELECT 'empty'::numrange &< nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT 'empty'::numrange &< nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() &< 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) &< 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() &< nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) &< nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() &< nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(6,7) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(1,2) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,4) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,6) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(3.5,6) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(6,7)) &< numrange(3,4);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) &< numrange(3,4);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,4)) &< numrange(3,4);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,6)) &< numrange(3,4);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3.5,6)) &< numrange(3,4);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(6,7)) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,4)) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,6)) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3.5,6)) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+-- overright
+SELECT nummultirange() &> 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) &> 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT 'empty'::numrange &> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT 'empty'::numrange &> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() &> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() &> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) &> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> numrange(6,7);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> numrange(1,2);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> numrange(1,4);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> numrange(1,6);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> numrange(3.5,6);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(3,4) &> nummultirange(numrange(6,7));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(3,4) &> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(3,4) &> nummultirange(numrange(1,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(3,4) &> nummultirange(numrange(1,6));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(3,4) &> nummultirange(numrange(3.5,6));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(6,7));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(1,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(1,6));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(3.5,6));
+ ?column? 
+----------
+ f
+(1 row)
+
+-- meets
+SELECT 'empty'::numrange -|- nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT 'empty'::numrange -|- nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() -|- 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() -|- nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() -|- nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(1,2) -|- nummultirange(numrange(2,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,2) -|- nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- numrange(2,4);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- numrange(3,4);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- nummultirange(numrange(2,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(5,6)) -|- nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(5,6)) -|- nummultirange(numrange(6,7));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(5,6)) -|- nummultirange(numrange(8,9));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- nummultirange(numrange(2,4), numrange(6,7));
+ ?column? 
+----------
+ t
+(1 row)
+
+-- strictly left
+select 'empty'::numrange << nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(1,2) << nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(1,2) << nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+select numrange(1,2) << nummultirange(numrange(0,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(1,2) << nummultirange(numrange(0,4), numrange(7,8));
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange() << 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange() << numrange(1,2);
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(3,4)) << numrange(3,6);
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(0,2)) << numrange(3,6);
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange(numrange(0,2), numrange(7,8)) << numrange(3,6);
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(-4,-2), numrange(0,2)) << numrange(3,6);
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange() << nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange() << nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(1,2)) << nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(1,2)) << nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(1,2)) << nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange(numrange(1,2)) << nummultirange(numrange(3,4), numrange(7,8));
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange(numrange(1,2), numrange(4,5)) << nummultirange(numrange(3,4), numrange(7,8));
+ ?column? 
+----------
+ f
+(1 row)
+
+-- strictly right
+select nummultirange() >> 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange() >> numrange(1,2);
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(3,4)) >> numrange(1,2);
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange(numrange(0,4)) >> numrange(1,2);
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(0,4), numrange(7,8)) >> numrange(1,2);
+ ?column? 
+----------
+ f
+(1 row)
+
+select 'empty'::numrange >> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(1,2) >> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(3,6) >> nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(3,6) >> nummultirange(numrange(0,2));
+ ?column? 
+----------
+ t
+(1 row)
+
+select numrange(3,6) >> nummultirange(numrange(0,2), numrange(7,8));
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(3,6) >> nummultirange(numrange(-4,-2), numrange(0,2));
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange() >> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(1,2)) >> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange() >> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(1,2)) >> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(3,4)) >> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange(numrange(3,4), numrange(7,8)) >> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange(numrange(3,4), numrange(7,8)) >> nummultirange(numrange(1,2), numrange(4,5));
+ ?column? 
+----------
+ f
+(1 row)
+
+-- union
+SELECT 'empty'::numrange @+ 'empty'::numrange;
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT 'empty'::numrange @+ nummultirange();
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange() @+ 'empty'::numrange;
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange() @+ nummultirange();
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT 'empty'::numrange @+ numrange(1,2);
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT 'empty'::numrange @+ nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT numrange(1,2) @+ nummultirange();
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) @+ 'empty'::numrange;
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT numrange(1,2) @+ 'empty'::numrange;
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange() @+ numrange(1,2);
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange() @+ nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) @+ nummultirange();
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT numrange(1,2) @+ numrange(1,2);
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT numrange(1,2) @+ numrange(2,4);
+ ?column? 
+----------
+ {[1,4)}
+(1 row)
+
+SELECT numrange(1,2) @+ numrange(3,4);
+   ?column?    
+---------------
+ {[1,2),[3,4)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) @+ nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) @+ nummultirange(numrange(2,4));
+ ?column? 
+----------
+ {[1,4)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) @+ nummultirange(numrange(3,4));
+   ?column?    
+---------------
+ {[1,2),[3,4)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) @+ nummultirange(numrange(2,4));
+ ?column? 
+----------
+ {[1,5)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) @+ nummultirange(numrange(3,4));
+   ?column?    
+---------------
+ {[1,2),[3,5)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) @+ nummultirange(numrange(0,9));
+ ?column? 
+----------
+ {[0,9)}
+(1 row)
+
+-- merge
+SELECT range_merge(nummultirange());
+ range_merge 
+-------------
+ empty
+(1 row)
+
+SELECT range_merge(nummultirange(numrange(1,2)));
+ range_merge 
+-------------
+ [1,2)
+(1 row)
+
+SELECT range_merge(nummultirange(numrange(1,2), numrange(7,8)));
+ range_merge 
+-------------
+ [1,8)
+(1 row)
+
+-- minus
+SELECT 'empty'::numrange @- 'empty'::numrange;
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT 'empty'::numrange @- nummultirange();
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange() @- 'empty'::numrange;
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange() @- nummultirange();
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT 'empty'::numrange @- numrange(1,2);
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT 'empty'::numrange @- nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT numrange(1,2) @- nummultirange();
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) @- 'empty'::numrange;
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT 'empty'::numrange @- numrange(1,2);
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT numrange(1,2) @- 'empty'::numrange;
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange() @- numrange(1,2);
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange() @- nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) @- nummultirange();
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT numrange(1,3) @- numrange(1,3);
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT numrange(1,3) @- numrange(1,2);
+ ?column? 
+----------
+ {[2,3)}
+(1 row)
+
+SELECT numrange(1,3) @- numrange(2,4);
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT numrange(1,3) @- numrange(3,4);
+ ?column? 
+----------
+ {[1,3)}
+(1 row)
+
+SELECT numrange(1,3) @- nummultirange(numrange(1,3));
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT numrange(1,3) @- nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {[2,3)}
+(1 row)
+
+SELECT numrange(1,3) @- nummultirange(numrange(2,4));
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT numrange(1,3) @- nummultirange(numrange(3,4));
+ ?column? 
+----------
+ {[1,3)}
+(1 row)
+
+SELECT numrange(1,10) @- nummultirange(numrange(1,2), numrange(4,5));
+    ?column?    
+----------------
+ {[2,4),[5,10)}
+(1 row)
+
+SELECT nummultirange(numrange(1,10)) @- numrange(0,2);
+ ?column? 
+----------
+ {[2,10)}
+(1 row)
+
+SELECT nummultirange(numrange(1,10)) @- numrange(1,2);
+ ?column? 
+----------
+ {[2,10)}
+(1 row)
+
+SELECT nummultirange(numrange(1,10)) @- numrange(3,4);
+    ?column?    
+----------------
+ {[1,3),[4,10)}
+(1 row)
+
+SELECT nummultirange(numrange(1,10)) @- numrange(13,18);
+ ?column? 
+----------
+ {[1,10)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(3,4)) @- nummultirange();
+   ?column?    
+---------------
+ {[1,2),[3,4)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) @- nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) @- nummultirange(numrange(2,4));
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) @- nummultirange(numrange(3,4));
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,4)) @- nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {[2,4)}
+(1 row)
+
+SELECT nummultirange(numrange(1,4)) @- nummultirange(numrange(2,3));
+   ?column?    
+---------------
+ {[1,2),[3,4)}
+(1 row)
+
+SELECT nummultirange(numrange(1,4)) @- nummultirange(numrange(0,8));
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange(numrange(1,4)) @- nummultirange(numrange(0,2));
+ ?column? 
+----------
+ {[2,4)}
+(1 row)
+
+SELECT nummultirange(numrange(1,8)) @- nummultirange(numrange(0,2), numrange(3,4));
+   ?column?    
+---------------
+ {[2,3),[4,8)}
+(1 row)
+
+SELECT nummultirange(numrange(1,8)) @- nummultirange(numrange(2,3), numrange(5,null));
+   ?column?    
+---------------
+ {[1,2),[3,5)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) @- nummultirange(numrange(-2,0));
+   ?column?    
+---------------
+ {[1,2),[4,5)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) @- nummultirange(numrange(2,4));
+   ?column?    
+---------------
+ {[1,2),[4,5)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) @- nummultirange(numrange(3,5));
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) @- nummultirange(numrange(0,9));
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange(numrange(1,3), numrange(4,5)) @- nummultirange(numrange(2,9));
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) @- nummultirange(numrange(8,9));
+   ?column?    
+---------------
+ {[1,2),[4,5)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) @- nummultirange(numrange(-2,0), numrange(8,9));
+   ?column?    
+---------------
+ {[1,2),[4,5)}
+(1 row)
+
+-- intersection
+SELECT 'empty'::numrange @* 'empty'::numrange;
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT 'empty'::numrange @* nummultirange();
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange() @* 'empty'::numrange;
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange() @* nummultirange();
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT 'empty'::numrange @* numrange(1,2);
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT 'empty'::numrange @* nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT numrange(1,2) @* 'empty'::numrange;
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT numrange(1,2) @* nummultirange();
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) @* 'empty'::numrange;
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange() @* numrange(1,2);
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange() @* nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) @* nummultirange();
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT numrange(1,3) @* numrange(1,3);
+ ?column? 
+----------
+ {[1,3)}
+(1 row)
+
+SELECT numrange(1,3) @* numrange(1,2);
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT numrange(1,3) @* numrange(1,5);
+ ?column? 
+----------
+ {[1,3)}
+(1 row)
+
+SELECT numrange(1,3) @* numrange(2,5);
+ ?column? 
+----------
+ {[2,3)}
+(1 row)
+
+SELECT numrange(1,5) @* numrange(2,3);
+ ?column? 
+----------
+ {[2,3)}
+(1 row)
+
+SELECT '{[1,3)}'::nummultirange @* numrange(1,3);
+ ?column? 
+----------
+ {[1,3)}
+(1 row)
+
+SELECT '{[1,3)}'::nummultirange @* numrange(1,2);
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT '{[1,3)}'::nummultirange @* numrange(1,5);
+ ?column? 
+----------
+ {[1,3)}
+(1 row)
+
+SELECT '{[1,3)}'::nummultirange @* numrange(2,5);
+ ?column? 
+----------
+ {[2,3)}
+(1 row)
+
+SELECT '{[1,5)}'::nummultirange @* numrange(2,3);
+ ?column? 
+----------
+ {[2,3)}
+(1 row)
+
+SELECT numrange(1,3) @* '{[1,3)}'::nummultirange;
+ ?column? 
+----------
+ {[1,3)}
+(1 row)
+
+SELECT numrange(1,2) @* '{[1,3)}'::nummultirange;
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT numrange(1,5) @* '{[1,3)}'::nummultirange;
+ ?column? 
+----------
+ {[1,3)}
+(1 row)
+
+SELECT numrange(2,5) @* '{[1,3)}'::nummultirange;
+ ?column? 
+----------
+ {[2,3)}
+(1 row)
+
+SELECT numrange(2,3) @* '{[1,5)}'::nummultirange;
+ ?column? 
+----------
+ {[2,3)}
+(1 row)
+
+SELECT '{[1,3)}'::nummultirange @* '{[1,5)}'::nummultirange;
+ ?column? 
+----------
+ {[1,3)}
+(1 row)
+
+SELECT '{[1,3)}'::nummultirange @* '{[0,5)}'::nummultirange;
+ ?column? 
+----------
+ {[1,3)}
+(1 row)
+
+SELECT '{[1,3)}'::nummultirange @* '{[0,2)}'::nummultirange;
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT '{[1,3)}'::nummultirange @* '{[2,5)}'::nummultirange;
+ ?column? 
+----------
+ {[2,3)}
+(1 row)
+
+SELECT '{[1,4)}'::nummultirange @* '{[2,3)}'::nummultirange;
+ ?column? 
+----------
+ {[2,3)}
+(1 row)
+
+SELECT '{[1,4)}'::nummultirange @* '{[0,2), [3,5)}'::nummultirange;
+   ?column?    
+---------------
+ {[1,2),[3,4)}
+(1 row)
+
+SELECT '{[1,4), [7,10)}'::nummultirange @* '{[0,8), [9,12)}'::nummultirange;
+       ?column?       
+----------------------
+ {[1,4),[7,8),[9,10)}
+(1 row)
+
+SELECT '{[1,4), [7,10)}'::nummultirange @* '{[9,12)}'::nummultirange;
+ ?column? 
+----------
+ {[9,10)}
+(1 row)
+
+SELECT '{[1,4), [7,10)}'::nummultirange @* '{[-5,-4), [5,6), [9,12)}'::nummultirange;
+ ?column? 
+----------
+ {[9,10)}
+(1 row)
+
+SELECT '{[1,4), [7,10)}'::nummultirange @* '{[0,2), [3,8), [9,12)}'::nummultirange;
+          ?column?          
+----------------------------
+ {[1,2),[3,4),[7,8),[9,10)}
+(1 row)
+
+SELECT '{[1,4), [7,10)}'::nummultirange @* '{[0,2), [3,8), [9,12)}'::nummultirange;
+          ?column?          
+----------------------------
+ {[1,2),[3,4),[7,8),[9,10)}
+(1 row)
+
+--
+-- range_agg function
+--
+create table reservations ( room_id integer not null, booked_during daterange );
+insert into reservations values
+-- 1: has a meets and a gap
+(1, daterange('2018-07-01', '2018-07-07')),
+(1, daterange('2018-07-07', '2018-07-14')),
+(1, daterange('2018-07-20', '2018-07-22')),
+-- 2: just a single row
+(2, daterange('2018-07-01', '2018-07-03')),
+-- 3: one null range
+(3, NULL),
+-- 4: two null ranges
+(4, NULL),
+(4, NULL),
+-- 5: a null range and a non-null range
+(5, NULL),
+(5, daterange('2018-07-01', '2018-07-03')),
+-- 6: has overlap
+(6, daterange('2018-07-01', '2018-07-07')),
+(6, daterange('2018-07-05', '2018-07-10')),
+-- 7: two ranges that meet: no gap or overlap
+(7, daterange('2018-07-01', '2018-07-07')),
+(7, daterange('2018-07-07', '2018-07-14')),
+-- 8: an empty range
+(8, 'empty'::daterange)
+;
+SELECT   room_id, range_agg(booked_during)
+FROM     reservations
+GROUP BY room_id
+ORDER BY room_id;
+ room_id |                     range_agg                     
+---------+---------------------------------------------------
+       1 | {[07-01-2018,07-14-2018),[07-20-2018,07-22-2018)}
+       2 | {[07-01-2018,07-03-2018)}
+       3 | 
+       4 | 
+       5 | {[07-01-2018,07-03-2018)}
+       6 | {[07-01-2018,07-10-2018)}
+       7 | {[07-01-2018,07-14-2018)}
+       8 | {}
+(8 rows)
+
+-- range_agg on a custom range type too
+SELECT  range_agg(r)
+FROM    (VALUES
+          ('[a,c]'::textrange),
+          ('[b,b]'::textrange),
+          ('[c,f]'::textrange),
+          ('[g,h)'::textrange),
+          ('[h,j)'::textrange)
+        ) t(r);
+   range_agg   
+---------------
+ {[a,f],[g,j)}
+(1 row)
+
+select range_intersect_agg(nmr) from nummultirange_test;
+ range_intersect_agg 
+---------------------
+ {}
+(1 row)
+
+select range_intersect_agg(nmr) from nummultirange_test where false;
+ range_intersect_agg 
+---------------------
+ 
+(1 row)
+
+-- test with just one input:
+select range_intersect_agg(nmr) from (values ('{[1,2]}'::nummultirange)) t(nmr);
+ range_intersect_agg 
+---------------------
+ {[1,2]}
+(1 row)
+
+select range_intersect_agg(nmr) from nummultirange_test where nmr @> 4.0;
+ range_intersect_agg 
+---------------------
+ {[3,5)}
+(1 row)
+
+create table nummultirange_test2(nmr nummultirange);
+create index nummultirange_test2_hash_idx on nummultirange_test2 using hash (nmr);
+INSERT INTO nummultirange_test2 VALUES('{[, 5)}');
+INSERT INTO nummultirange_test2 VALUES(nummultirange(numrange(1.1, 2.2)));
+INSERT INTO nummultirange_test2 VALUES(nummultirange(numrange(1.1, 2.2)));
+INSERT INTO nummultirange_test2 VALUES(nummultirange(numrange(1.1, 2.2,'()')));
+INSERT INTO nummultirange_test2 VALUES('{}');
+select * from nummultirange_test2 where nmr = '{}';
+ nmr 
+-----
+ {}
+(1 row)
+
+select * from nummultirange_test2 where nmr = nummultirange(numrange(1.1, 2.2));
+     nmr     
+-------------
+ {[1.1,2.2)}
+ {[1.1,2.2)}
+(2 rows)
+
+select * from nummultirange_test2 where nmr = nummultirange(numrange(1.1, 2.3));
+ nmr 
+-----
+(0 rows)
+
+set enable_nestloop=t;
+set enable_hashjoin=f;
+set enable_mergejoin=f;
+select * from nummultirange_test natural join nummultirange_test2 order by nmr;
+     nmr     
+-------------
+ {}
+ {}
+ {}
+ {}
+ {(,5)}
+ {[1.1,2.2)}
+ {[1.1,2.2)}
+(7 rows)
+
+set enable_nestloop=f;
+set enable_hashjoin=t;
+set enable_mergejoin=f;
+select * from nummultirange_test natural join nummultirange_test2 order by nmr;
+     nmr     
+-------------
+ {}
+ {}
+ {}
+ {}
+ {(,5)}
+ {[1.1,2.2)}
+ {[1.1,2.2)}
+(7 rows)
+
+set enable_nestloop=f;
+set enable_hashjoin=f;
+set enable_mergejoin=t;
+select * from nummultirange_test natural join nummultirange_test2 order by nmr;
+     nmr     
+-------------
+ {}
+ {}
+ {}
+ {}
+ {(,5)}
+ {[1.1,2.2)}
+ {[1.1,2.2)}
+(7 rows)
+
+set enable_nestloop to default;
+set enable_hashjoin to default;
+set enable_mergejoin to default;
+DROP TABLE nummultirange_test2;
+--
+-- Test user-defined multirange of floats
+--
+select '{[123.001, 5.e9)}'::float8multirange @> 888.882::float8;
+ ?column? 
+----------
+ t
+(1 row)
+
+create table float8multirange_test(f8mr float8multirange, i int);
+insert into float8multirange_test values(float8multirange(float8range(-100.00007, '1.111113e9')), 42);
+select * from float8multirange_test;
+           f8mr            | i  
+---------------------------+----
+ {[-100.00007,1111113000)} | 42
+(1 row)
+
+drop table float8multirange_test;
+--
+-- Test multirange types over domains
+--
+create domain mydomain as int4;
+create type mydomainrange as range(subtype=mydomain);
+select '{[4,50)}'::mydomainmultirange @> 7::mydomain;
+ ?column? 
+----------
+ t
+(1 row)
+
+drop domain mydomain cascade;
+NOTICE:  drop cascades to type mydomainrange
+--
+-- Test domains over multirange types
+--
+create domain restrictedmultirange as int4multirange check (upper(value) < 10);
+select '{[4,5)}'::restrictedmultirange @> 7;
+ ?column? 
+----------
+ f
+(1 row)
+
+select '{[4,50)}'::restrictedmultirange @> 7; -- should fail
+ERROR:  value for domain restrictedmultirange violates check constraint "restrictedmultirange_check"
+drop domain restrictedmultirange;
+--
+-- Test multiple multirange types over the same subtype
+--
+create type textrange1 as range(subtype=text, collation="C");
+create type textrange2 as range(subtype=text, collation="C");
+select textmultirange1(textrange2('a','Z'));  -- should fail
+ERROR:  function textmultirange1(textrange2) does not exist
+LINE 1: select textmultirange1(textrange2('a','Z'));
+               ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+select textmultirange1(textrange1('a','Z')) @> 'b'::text;
+ERROR:  range lower bound must be less than or equal to range upper bound
+select textmultirange2(textrange2('a','z')) @> 'b'::text;
+ ?column? 
+----------
+ t
+(1 row)
+
+drop type textrange1;
+drop type textrange2;
+--
+-- Test polymorphic type system
+--
+create function anyarray_anymultirange_func(a anyarray, r anymultirange)
+  returns anyelement as 'select $1[1] + lower($2);' language sql;
+select anyarray_anymultirange_func(ARRAY[1,2], int4multirange(int4range(10,20)));
+ anyarray_anymultirange_func 
+-----------------------------
+                          11
+(1 row)
+
+-- should fail
+select anyarray_anymultirange_func(ARRAY[1,2], nummultirange(numrange(10,20)));
+ERROR:  function anyarray_anymultirange_func(integer[], nummultirange) does not exist
+LINE 1: select anyarray_anymultirange_func(ARRAY[1,2], nummultirange...
+               ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+drop function anyarray_anymultirange_func(anyarray, anymultirange);
+-- should fail
+create function bogus_func(anyelement)
+  returns anymultirange as 'select int4multirange(int4range(1,10))' language sql;
+ERROR:  cannot determine result data type
+DETAIL:  A function returning "anyrange" or "anymultirange" must have at least one "anyrange" or "anymultirange" argument.
+-- should fail
+create function bogus_func(int)
+  returns anymultirange as 'select int4multirange(int4range(1,10))' language sql;
+ERROR:  cannot determine result data type
+DETAIL:  A function returning a polymorphic type must have at least one polymorphic argument.
+create function range_add_bounds(anymultirange)
+  returns anyelement as 'select lower($1) + upper($1)' language sql;
+select range_add_bounds(int4multirange(int4range(1, 17)));
+ range_add_bounds 
+------------------
+               18
+(1 row)
+
+select range_add_bounds(nummultirange(numrange(1.0001, 123.123)));
+ range_add_bounds 
+------------------
+         124.1231
+(1 row)
+
+create function multirangetypes_sql(q anymultirange, b anyarray, out c anyelement)
+  as $$ select upper($1) + $2[1] $$
+  language sql;
+select multirangetypes_sql(int4multirange(int4range(1,10)), ARRAY[2,20]);
+ multirangetypes_sql 
+---------------------
+                  12
+(1 row)
+
+select multirangetypes_sql(nummultirange(numrange(1,10)), ARRAY[2,20]);  -- match failure
+ERROR:  function multirangetypes_sql(nummultirange, integer[]) does not exist
+LINE 1: select multirangetypes_sql(nummultirange(numrange(1,10)), AR...
+               ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+--
+-- Arrays of multiranges
+--
+select ARRAY[nummultirange(numrange(1.1, 1.2)), nummultirange(numrange(12.3, 155.5))];
+              array               
+----------------------------------
+ {"{[1.1,1.2)}","{[12.3,155.5)}"}
+(1 row)
+
+create table i8mr_array (f1 int, f2 int8multirange[]);
+insert into i8mr_array values (42, array[int8multirange(int8range(1,10)), int8multirange(int8range(2,20))]);
+select * from i8mr_array;
+ f1 |           f2            
+----+-------------------------
+ 42 | {"{[1,10)}","{[2,20)}"}
+(1 row)
+
+drop table i8mr_array;
+--
+-- Multiranges of arrays
+--
+select arraymultirange(arrayrange(ARRAY[1,2], ARRAY[2,1]));
+   arraymultirange   
+---------------------
+ {["{1,2}","{2,1}")}
+(1 row)
+
+select arraymultirange(arrayrange(ARRAY[2,1], ARRAY[1,2]));  -- fail
+ERROR:  range lower bound must be less than or equal to range upper bound
+select array[1,1] <@ arraymultirange(arrayrange(array[1,2], array[2,1]));
+ ?column? 
+----------
+ f
+(1 row)
+
+select array[1,3] <@ arraymultirange(arrayrange(array[1,2], array[2,1]));
+ ?column? 
+----------
+ t
+(1 row)
+
+--
+-- Ranges of composites
+--
+create type two_ints as (a int, b int);
+create type two_ints_range as range (subtype = two_ints);
+-- with force_parallel_mode on, this exercises tqueue.c's range remapping
+select *, row_to_json(upper(t)) as u from
+  (values (two_ints_multirange(two_ints_range(row(1,2), row(3,4)))),
+          (two_ints_multirange(two_ints_range(row(5,6), row(7,8))))) v(t);
+          t          |       u       
+---------------------+---------------
+ {["(1,2)","(3,4)")} | {"a":3,"b":4}
+ {["(5,6)","(7,8)")} | {"a":7,"b":8}
+(2 rows)
+
+drop type two_ints cascade;
+NOTICE:  drop cascades to type two_ints_range
+--
+-- Check behavior when subtype lacks a hash function
+--
+set enable_sort = off;  -- try to make it pick a hash setop implementation
+select '{(2,5)}'::cashmultirange except select '{(5,6)}'::cashmultirange;
+ cashmultirange  
+-----------------
+ {($2.00,$5.00)}
+(1 row)
+
+reset enable_sort;
+--
+-- OUT/INOUT/TABLE functions
+--
+-- infer anymultirange from anymultirange
+create function mr_outparam_succeed(i anymultirange, out r anymultirange, out t text)
+  as $$ select $1, 'foo'::text $$ language sql;
+select * from mr_outparam_succeed(int4multirange(int4range(1,2)));
+    r    |  t  
+---------+-----
+ {[1,2)} | foo
+(1 row)
+
+-- infer anyarray from anymultirange
+create function mr_outparam_succeed2(i anymultirange, out r anyarray, out t text)
+  as $$ select ARRAY[upper($1)], 'foo'::text $$ language sql;
+select * from mr_outparam_succeed2(int4multirange(int4range(1,2)));
+  r  |  t  
+-----+-----
+ {2} | foo
+(1 row)
+
+-- infer anyrange from anymultirange
+create function mr_outparam_succeed3(i anymultirange, out r anyrange, out t text)
+  as $$ select range_merge($1), 'foo'::text $$ language sql;
+select * from mr_outparam_succeed3(int4multirange(int4range(1,2)));
+   r   |  t  
+-------+-----
+ [1,2) | foo
+(1 row)
+
+-- infer anymultirange from anyrange
+create function mr_outparam_succeed4(i anyrange, out r anymultirange, out t text)
+  as $$ select $1 @+ $1, 'foo'::text $$ language sql;
+select * from mr_outparam_succeed4(int4range(1,2));
+    r    |  t  
+---------+-----
+ {[1,2)} | foo
+(1 row)
+
+-- infer anyelement from anymultirange
+create function mr_inoutparam_succeed(out i anyelement, inout r anymultirange)
+  as $$ select upper($1), $1 $$ language sql;
+select * from mr_inoutparam_succeed(int4multirange(int4range(1,2)));
+ i |    r    
+---+---------
+ 2 | {[1,2)}
+(1 row)
+
+-- infer anyelement+anymultirange from anyelement+anymultirange
+create function mr_table_succeed(i anyelement, r anymultirange) returns table(i anyelement, r anymultirange)
+  as $$ select $1, $2 $$ language sql;
+select * from mr_table_succeed(123, int4multirange(int4range(1,11)));
+  i  |    r     
+-----+----------
+ 123 | {[1,11)}
+(1 row)
+
+-- should fail
+create function mr_outparam_fail(i anyelement, out r anymultirange, out t text)
+  as $$ select '[1,10]', 'foo' $$ language sql;
+ERROR:  cannot determine result data type
+DETAIL:  A function returning "anyrange" or "anymultirange" must have at least one "anyrange" or "anymultirange" argument.
+--should fail
+create function mr_inoutparam_fail(inout i anyelement, out r anymultirange)
+  as $$ select $1, '[1,10]' $$ language sql;
+ERROR:  cannot determine result data type
+DETAIL:  A function returning "anyrange" or "anymultirange" must have at least one "anyrange" or "anymultirange" argument.
+--should fail
+create function mr_table_fail(i anyelement) returns table(i anyelement, r anymultirange)
+  as $$ select $1, '[1,10]' $$ language sql;
+ERROR:  cannot determine result data type
+DETAIL:  A function returning "anyrange" or "anymultirange" must have at least one "anyrange" or "anymultirange" argument.
diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out
index 916e1016b5..e818eaea79 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -1101,13 +1101,15 @@ ORDER BY 1, 2;
  ?|   | ?|
  ?||  | ?||
  @    | ~
+ @*   | @*
+ @+   | @+
  @@   | @@
  @@@  | @@@
  |    | |
  ~<=~ | ~>=~
  ~<~  | ~>~
  ~=   | ~=
-(30 rows)
+(32 rows)
 
 -- Likewise for negator pairs.
 SELECT DISTINCT o1.oprname AS op1, o2.oprname AS op2
diff --git a/src/test/regress/expected/rangetypes.out b/src/test/regress/expected/rangetypes.out
index 60d875e898..d825c0db1a 100644
--- a/src/test/regress/expected/rangetypes.out
+++ b/src/test/regress/expected/rangetypes.out
@@ -518,6 +518,24 @@ select numrange(1.0, 2.0) * numrange(2.5, 3.0);
  empty
 (1 row)
 
+select range_intersect_agg(nr) from numrange_test;
+ range_intersect_agg 
+---------------------
+ empty
+(1 row)
+
+select range_intersect_agg(nr) from numrange_test where false;
+ range_intersect_agg 
+---------------------
+ 
+(1 row)
+
+select range_intersect_agg(nr) from numrange_test where nr @> 4.0;
+ range_intersect_agg 
+---------------------
+ [3,5)
+(1 row)
+
 create table numrange_test2(nr numrange);
 create index numrange_test2_hash_idx on numrange_test2 (nr);
 INSERT INTO numrange_test2 VALUES('[, 5)');
@@ -1284,7 +1302,7 @@ drop function anyarray_anyrange_func(anyarray, anyrange);
 create function bogus_func(anyelement)
   returns anyrange as 'select int4range(1,10)' language sql;
 ERROR:  cannot determine result data type
-DETAIL:  A function returning "anyrange" must have at least one "anyrange" argument.
+DETAIL:  A function returning "anyrange" or "anymultirange" must have at least one "anyrange" or "anymultirange" argument.
 -- should fail
 create function bogus_func(int)
   returns anyrange as 'select int4range(1,10)' language sql;
@@ -1392,6 +1410,7 @@ reset enable_sort;
 --
 -- OUT/INOUT/TABLE functions
 --
+-- infer anyrange from anyrange
 create function outparam_succeed(i anyrange, out r anyrange, out t text)
   as $$ select $1, 'foo'::text $$ language sql;
 select * from outparam_succeed(int4range(1,2));
@@ -1400,6 +1419,16 @@ select * from outparam_succeed(int4range(1,2));
  [1,2) | foo
 (1 row)
 
+-- infer anyarray from anyrange
+create function outparam_succeed2(i anyrange, out r anyarray, out t text)
+  as $$ select ARRAY[upper($1)], 'foo'::text $$ language sql;
+select * from outparam_succeed2(int4range(int4range(1,2)));
+  r  |  t  
+-----+-----
+ {2} | foo
+(1 row)
+
+-- infer anyelement from anyrange
 create function inoutparam_succeed(out i anyelement, inout r anyrange)
   as $$ select upper($1), $1 $$ language sql;
 select * from inoutparam_succeed(int4range(1,2));
@@ -1408,6 +1437,7 @@ select * from inoutparam_succeed(int4range(1,2));
  2 | [1,2)
 (1 row)
 
+-- infer anyelement+anyrange from anyelement+anyrange
 create function table_succeed(i anyelement, r anyrange) returns table(i anyelement, r anyrange)
   as $$ select $1, $2 $$ language sql;
 select * from table_succeed(123, int4range(1,11));
@@ -1420,14 +1450,14 @@ select * from table_succeed(123, int4range(1,11));
 create function outparam_fail(i anyelement, out r anyrange, out t text)
   as $$ select '[1,10]', 'foo' $$ language sql;
 ERROR:  cannot determine result data type
-DETAIL:  A function returning "anyrange" must have at least one "anyrange" argument.
+DETAIL:  A function returning "anyrange" or "anymultirange" must have at least one "anyrange" or "anymultirange" argument.
 --should fail
 create function inoutparam_fail(inout i anyelement, out r anyrange)
   as $$ select $1, '[1,10]' $$ language sql;
 ERROR:  cannot determine result data type
-DETAIL:  A function returning "anyrange" must have at least one "anyrange" argument.
+DETAIL:  A function returning "anyrange" or "anymultirange" must have at least one "anyrange" or "anymultirange" argument.
 --should fail
 create function table_fail(i anyelement) returns table(i anyelement, r anyrange)
   as $$ select $1, '[1,10]' $$ language sql;
 ERROR:  cannot determine result data type
-DETAIL:  A function returning "anyrange" must have at least one "anyrange" argument.
+DETAIL:  A function returning "anyrange" or "anymultirange" must have at least one "anyrange" or "anymultirange" argument.
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index d6e75ffce6..3d92541ebc 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -92,6 +92,7 @@ num_exp_sqrt|t
 num_exp_sub|t
 num_input_test|f
 num_result|f
+nummultirange_test|t
 onek|t
 onek2|t
 path_tbl|f
@@ -172,6 +173,7 @@ quad_poly_tbl|t
 radix_text_tbl|t
 ramp|f
 real_city|f
+reservations|f
 road|t
 shighway|t
 slow_emp4000|f
diff --git a/src/test/regress/sql/multirangetypes.sql b/src/test/regress/sql/multirangetypes.sql
index 8651ba3f3d..b3ede18f96 100644
--- a/src/test/regress/sql/multirangetypes.sql
+++ b/src/test/regress/sql/multirangetypes.sql
@@ -63,3 +63,610 @@ select textmultirange();
 select textmultirange(textrange('a', 'c'));
 select textmultirange(textrange('a', 'c'), textrange('f', 'g'));
 select textmultirange(textrange('a', 'c'), textrange('b', 'd'));
+
+--
+-- create some test data and test the operators
+--
+
+CREATE TABLE nummultirange_test (nmr NUMMULTIRANGE);
+CREATE INDEX nummultirange_test_btree ON nummultirange_test(nmr);
+
+INSERT INTO nummultirange_test VALUES('{}');
+INSERT INTO nummultirange_test VALUES('{[,)}');
+INSERT INTO nummultirange_test VALUES('{[3,]}');
+INSERT INTO nummultirange_test VALUES('{[,), [3,]}');
+INSERT INTO nummultirange_test VALUES('{[, 5)}');
+INSERT INTO nummultirange_test VALUES(nummultirange());
+INSERT INTO nummultirange_test VALUES(nummultirange(variadic '{}'::numrange[]));
+INSERT INTO nummultirange_test VALUES(nummultirange(numrange(1.1, 2.2)));
+INSERT INTO nummultirange_test VALUES('{empty}');
+INSERT INTO nummultirange_test VALUES(nummultirange(numrange(1.7, 1.7, '[]'), numrange(1.7, 1.9)));
+INSERT INTO nummultirange_test VALUES(nummultirange(numrange(1.7, 1.7, '[]'), numrange(1.9, 2.1)));
+
+SELECT nmr, isempty(nmr), lower(nmr), upper(nmr) FROM nummultirange_test ORDER BY nmr;
+SELECT nmr, lower_inc(nmr), lower_inf(nmr), upper_inc(nmr), upper_inf(nmr) FROM nummultirange_test ORDER BY nmr;
+
+SELECT * FROM nummultirange_test WHERE nmr = '{}';
+SELECT * FROM nummultirange_test WHERE nmr = '{(,5)}';
+SELECT * FROM nummultirange_test WHERE nmr = '{[3,)}';
+SELECT * FROM nummultirange_test WHERE nmr = '{[1.7,1.7]}';
+SELECT * FROM nummultirange_test WHERE nmr = '{[1.7,1.7],[1.9,2.1)}';
+SELECT * FROM nummultirange_test WHERE nmr < '{}';
+SELECT * FROM nummultirange_test WHERE nmr < '{[-1000.0, -1000.0]}';
+SELECT * FROM nummultirange_test WHERE nmr < '{[0.0, 1.0]}';
+SELECT * FROM nummultirange_test WHERE nmr < '{[1000.0, 1001.0]}';
+SELECT * FROM nummultirange_test WHERE nmr <= '{}';
+SELECT * FROM nummultirange_test WHERE nmr <= '{[3,)}';
+SELECT * FROM nummultirange_test WHERE nmr >= '{}';
+SELECT * FROM nummultirange_test WHERE nmr >= '{[3,)}';
+SELECT * FROM nummultirange_test WHERE nmr > '{}';
+SELECT * FROM nummultirange_test WHERE nmr > '{[-1000.0, -1000.0]}';
+SELECT * FROM nummultirange_test WHERE nmr > '{[0.0, 1.0]}';
+SELECT * FROM nummultirange_test WHERE nmr > '{[1000.0, 1001.0]}';
+SELECT * FROM nummultirange_test WHERE nmr <> '{}';
+SELECT * FROM nummultirange_test WHERE nmr <> '{(,5)}';
+
+select nummultirange(numrange(2.0, 1.0));
+select nummultirange(numrange(5.0, 6.0), numrange(1.0, 2.0));
+
+-- overlaps
+SELECT * FROM nummultirange_test WHERE range_overlaps_multirange(numrange(4.0, 4.2), nmr);
+SELECT * FROM nummultirange_test WHERE numrange(4.0, 4.2) && nmr;
+SELECT * FROM nummultirange_test WHERE multirange_overlaps_range(nmr, numrange(4.0, 4.2));
+SELECT * FROM nummultirange_test WHERE nmr && numrange(4.0, 4.2);
+SELECT * FROM nummultirange_test WHERE multirange_overlaps_multirange(nmr, nummultirange(numrange(4.0, 4.2), numrange(6.0, 7.0)));
+SELECT * FROM nummultirange_test WHERE nmr && nummultirange(numrange(4.0, 4.2), numrange(6.0, 7.0));
+SELECT * FROM nummultirange_test WHERE nmr && nummultirange(numrange(6.0, 7.0));
+SELECT * FROM nummultirange_test WHERE nmr && nummultirange(numrange(6.0, 7.0), numrange(8.0, 9.0));
+
+-- mr contains x
+SELECT * FROM nummultirange_test WHERE multirange_contains_elem(nmr, 4.0);
+SELECT * FROM nummultirange_test WHERE nmr @> 4.0;
+SELECT * FROM nummultirange_test WHERE multirange_contains_range(nmr, numrange(4.0, 4.2));
+SELECT * FROM nummultirange_test WHERE nmr @> numrange(4.0, 4.2);
+SELECT * FROM nummultirange_test WHERE multirange_contains_multirange(nmr, '{[4.0,4.2), [6.0, 8.0)}');
+SELECT * FROM nummultirange_test WHERE nmr @> '{[4.0,4.2), [6.0, 8.0)}'::nummultirange;
+
+-- x is contained by mr
+SELECT * FROM nummultirange_test WHERE elem_contained_by_multirange(4.0, nmr);
+SELECT * FROM nummultirange_test WHERE 4.0 <@ nmr;
+SELECT * FROM nummultirange_test WHERE range_contained_by_multirange(numrange(4.0, 4.2), nmr);
+SELECT * FROM nummultirange_test WHERE numrange(4.0, 4.2) <@ nmr;
+SELECT * FROM nummultirange_test WHERE multirange_contained_by_multirange('{[4.0,4.2), [6.0, 8.0)}', nmr);
+SELECT * FROM nummultirange_test WHERE '{[4.0,4.2), [6.0, 8.0)}'::nummultirange <@ nmr;
+
+-- overlaps
+SELECT 'empty'::numrange && nummultirange();
+SELECT 'empty'::numrange && nummultirange(numrange(1,2));
+SELECT nummultirange() && 'empty'::numrange;
+SELECT nummultirange(numrange(1,2)) && 'empty'::numrange;
+SELECT nummultirange() && nummultirange();
+SELECT nummultirange() && nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) && nummultirange();
+SELECT nummultirange(numrange(3,4)) && nummultirange(numrange(1,2), numrange(7,8));
+SELECT nummultirange(numrange(1,2), numrange(7,8)) && nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(3,4)) && nummultirange(numrange(1,2), numrange(3.5,8));
+SELECT nummultirange(numrange(1,2), numrange(3.5,8)) && numrange(3,4);
+SELECT nummultirange(numrange(1,2), numrange(3.5,8)) && nummultirange(numrange(3,4));
+
+-- contains
+SELECT nummultirange() @> nummultirange();
+SELECT nummultirange() @> 'empty'::numrange;
+SELECT nummultirange(numrange(null,null)) @> numrange(1,2);
+SELECT nummultirange(numrange(null,null)) @> numrange(null,2);
+SELECT nummultirange(numrange(null,null)) @> numrange(2,null);
+SELECT nummultirange(numrange(null,5)) @> numrange(null,3);
+SELECT nummultirange(numrange(null,5)) @> numrange(null,8);
+SELECT nummultirange(numrange(5,null)) @> numrange(8,null);
+SELECT nummultirange(numrange(5,null)) @> numrange(3,null);
+SELECT nummultirange(numrange(1,5)) @> numrange(8,9);
+SELECT nummultirange(numrange(1,5)) @> numrange(3,9);
+SELECT nummultirange(numrange(1,5)) @> numrange(1,4);
+SELECT nummultirange(numrange(1,5)) @> numrange(1,5);
+SELECT nummultirange(numrange(-4,-2), numrange(1,5)) @> numrange(1,5);
+SELECT nummultirange(numrange(1,5), numrange(8,9)) @> numrange(1,5);
+SELECT nummultirange(numrange(1,5), numrange(8,9)) @> numrange(6,7);
+SELECT nummultirange(numrange(1,5), numrange(6,9)) @> numrange(6,7);
+SELECT '{[1,5)}'::nummultirange @> '{[1,5)}';
+SELECT '{[-4,-2), [1,5)}'::nummultirange @> '{[1,5)}';
+SELECT '{[1,5), [8,9)}'::nummultirange @> '{[1,5)}';
+SELECT '{[1,5), [8,9)}'::nummultirange @> '{[6,7)}';
+SELECT '{[1,5), [6,9)}'::nummultirange @> '{[6,7)}';
+
+-- is contained by
+SELECT nummultirange() <@ nummultirange();
+SELECT 'empty'::numrange <@ nummultirange();
+SELECT numrange(1,2) <@ nummultirange(numrange(null,null));
+SELECT numrange(null,2) <@ nummultirange(numrange(null,null));
+SELECT numrange(2,null) <@ nummultirange(numrange(null,null));
+SELECT numrange(null,3) <@ nummultirange(numrange(null,5));
+SELECT numrange(null,8) <@ nummultirange(numrange(null,5));
+SELECT numrange(8,null) <@ nummultirange(numrange(5,null));
+SELECT numrange(3,null) <@ nummultirange(numrange(5,null));
+SELECT numrange(8,9) <@ nummultirange(numrange(1,5));
+SELECT numrange(3,9) <@ nummultirange(numrange(1,5));
+SELECT numrange(1,4) <@ nummultirange(numrange(1,5));
+SELECT numrange(1,5) <@ nummultirange(numrange(1,5));
+SELECT numrange(1,5) <@ nummultirange(numrange(-4,-2), numrange(1,5));
+SELECT numrange(1,5) <@ nummultirange(numrange(1,5), numrange(8,9));
+SELECT numrange(6,7) <@ nummultirange(numrange(1,5), numrange(8,9));
+SELECT numrange(6,7) <@ nummultirange(numrange(1,5), numrange(6,9));
+SELECT '{[1,5)}' <@ '{[1,5)}'::nummultirange;
+SELECT '{[1,5)}' <@ '{[-4,-2), [1,5)}'::nummultirange;
+SELECT '{[1,5)}' <@ '{[1,5), [8,9)}'::nummultirange;
+SELECT '{[6,7)}' <@ '{[1,5), [8,9)}'::nummultirange;
+SELECT '{[6,7)}' <@ '{[1,5), [6,9)}'::nummultirange;
+
+-- overleft
+SELECT 'empty'::numrange &< nummultirange();
+SELECT 'empty'::numrange &< nummultirange(numrange(1,2));
+SELECT nummultirange() &< 'empty'::numrange;
+SELECT nummultirange(numrange(1,2)) &< 'empty'::numrange;
+SELECT nummultirange() &< nummultirange();
+SELECT nummultirange(numrange(1,2)) &< nummultirange();
+SELECT nummultirange() &< nummultirange(numrange(1,2));
+SELECT numrange(6,7) &< nummultirange(numrange(3,4));
+SELECT numrange(1,2) &< nummultirange(numrange(3,4));
+SELECT numrange(1,4) &< nummultirange(numrange(3,4));
+SELECT numrange(1,6) &< nummultirange(numrange(3,4));
+SELECT numrange(3.5,6) &< nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(6,7)) &< numrange(3,4);
+SELECT nummultirange(numrange(1,2)) &< numrange(3,4);
+SELECT nummultirange(numrange(1,4)) &< numrange(3,4);
+SELECT nummultirange(numrange(1,6)) &< numrange(3,4);
+SELECT nummultirange(numrange(3.5,6)) &< numrange(3,4);
+SELECT nummultirange(numrange(6,7)) &< nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,2)) &< nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,4)) &< nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,6)) &< nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(3.5,6)) &< nummultirange(numrange(3,4));
+
+-- overright
+SELECT nummultirange() &> 'empty'::numrange;
+SELECT nummultirange(numrange(1,2)) &> 'empty'::numrange;
+SELECT 'empty'::numrange &> nummultirange();
+SELECT 'empty'::numrange &> nummultirange(numrange(1,2));
+SELECT nummultirange() &> nummultirange();
+SELECT nummultirange() &> nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) &> nummultirange();
+SELECT nummultirange(numrange(3,4)) &> numrange(6,7);
+SELECT nummultirange(numrange(3,4)) &> numrange(1,2);
+SELECT nummultirange(numrange(3,4)) &> numrange(1,4);
+SELECT nummultirange(numrange(3,4)) &> numrange(1,6);
+SELECT nummultirange(numrange(3,4)) &> numrange(3.5,6);
+SELECT numrange(3,4) &> nummultirange(numrange(6,7));
+SELECT numrange(3,4) &> nummultirange(numrange(1,2));
+SELECT numrange(3,4) &> nummultirange(numrange(1,4));
+SELECT numrange(3,4) &> nummultirange(numrange(1,6));
+SELECT numrange(3,4) &> nummultirange(numrange(3.5,6));
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(6,7));
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(1,4));
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(1,6));
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(3.5,6));
+
+-- meets
+SELECT 'empty'::numrange -|- nummultirange();
+SELECT 'empty'::numrange -|- nummultirange(numrange(1,2));
+SELECT nummultirange() -|- 'empty'::numrange;
+SELECT nummultirange(numrange(1,2)) -|- 'empty'::numrange;
+SELECT nummultirange() -|- nummultirange();
+SELECT nummultirange(numrange(1,2)) -|- nummultirange();
+SELECT nummultirange() -|- nummultirange(numrange(1,2));
+SELECT numrange(1,2) -|- nummultirange(numrange(2,4));
+SELECT numrange(1,2) -|- nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,2)) -|- numrange(2,4);
+SELECT nummultirange(numrange(1,2)) -|- numrange(3,4);
+SELECT nummultirange(numrange(1,2)) -|- nummultirange(numrange(2,4));
+SELECT nummultirange(numrange(1,2)) -|- nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,2), numrange(5,6)) -|- nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,2), numrange(5,6)) -|- nummultirange(numrange(6,7));
+SELECT nummultirange(numrange(1,2), numrange(5,6)) -|- nummultirange(numrange(8,9));
+SELECT nummultirange(numrange(1,2)) -|- nummultirange(numrange(2,4), numrange(6,7));
+
+-- strictly left
+select 'empty'::numrange << nummultirange();
+select numrange(1,2) << nummultirange();
+select numrange(1,2) << nummultirange(numrange(3,4));
+select numrange(1,2) << nummultirange(numrange(0,4));
+select numrange(1,2) << nummultirange(numrange(0,4), numrange(7,8));
+select nummultirange() << 'empty'::numrange;
+select nummultirange() << numrange(1,2);
+select nummultirange(numrange(3,4)) << numrange(3,6);
+select nummultirange(numrange(0,2)) << numrange(3,6);
+select nummultirange(numrange(0,2), numrange(7,8)) << numrange(3,6);
+select nummultirange(numrange(-4,-2), numrange(0,2)) << numrange(3,6);
+select nummultirange() << nummultirange();
+select nummultirange() << nummultirange(numrange(1,2));
+select nummultirange(numrange(1,2)) << nummultirange();
+select nummultirange(numrange(1,2)) << nummultirange(numrange(1,2));
+select nummultirange(numrange(1,2)) << nummultirange(numrange(3,4));
+select nummultirange(numrange(1,2)) << nummultirange(numrange(3,4), numrange(7,8));
+select nummultirange(numrange(1,2), numrange(4,5)) << nummultirange(numrange(3,4), numrange(7,8));
+
+-- strictly right
+select nummultirange() >> 'empty'::numrange;
+select nummultirange() >> numrange(1,2);
+select nummultirange(numrange(3,4)) >> numrange(1,2);
+select nummultirange(numrange(0,4)) >> numrange(1,2);
+select nummultirange(numrange(0,4), numrange(7,8)) >> numrange(1,2);
+select 'empty'::numrange >> nummultirange();
+select numrange(1,2) >> nummultirange();
+select numrange(3,6) >> nummultirange(numrange(3,4));
+select numrange(3,6) >> nummultirange(numrange(0,2));
+select numrange(3,6) >> nummultirange(numrange(0,2), numrange(7,8));
+select numrange(3,6) >> nummultirange(numrange(-4,-2), numrange(0,2));
+select nummultirange() >> nummultirange();
+select nummultirange(numrange(1,2)) >> nummultirange();
+select nummultirange() >> nummultirange(numrange(1,2));
+select nummultirange(numrange(1,2)) >> nummultirange(numrange(1,2));
+select nummultirange(numrange(3,4)) >> nummultirange(numrange(1,2));
+select nummultirange(numrange(3,4), numrange(7,8)) >> nummultirange(numrange(1,2));
+select nummultirange(numrange(3,4), numrange(7,8)) >> nummultirange(numrange(1,2), numrange(4,5));
+
+-- union
+SELECT 'empty'::numrange @+ 'empty'::numrange;
+SELECT 'empty'::numrange @+ nummultirange();
+SELECT nummultirange() @+ 'empty'::numrange;
+SELECT nummultirange() @+ nummultirange();
+SELECT 'empty'::numrange @+ numrange(1,2);
+SELECT 'empty'::numrange @+ nummultirange(numrange(1,2));
+SELECT numrange(1,2) @+ nummultirange();
+SELECT nummultirange(numrange(1,2)) @+ 'empty'::numrange;
+SELECT numrange(1,2) @+ 'empty'::numrange;
+SELECT nummultirange() @+ numrange(1,2);
+SELECT nummultirange() @+ nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) @+ nummultirange();
+SELECT numrange(1,2) @+ numrange(1,2);
+SELECT numrange(1,2) @+ numrange(2,4);
+SELECT numrange(1,2) @+ numrange(3,4);
+SELECT nummultirange(numrange(1,2)) @+ nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) @+ nummultirange(numrange(2,4));
+SELECT nummultirange(numrange(1,2)) @+ nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) @+ nummultirange(numrange(2,4));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) @+ nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) @+ nummultirange(numrange(0,9));
+
+-- merge
+SELECT range_merge(nummultirange());
+SELECT range_merge(nummultirange(numrange(1,2)));
+SELECT range_merge(nummultirange(numrange(1,2), numrange(7,8)));
+
+-- minus
+SELECT 'empty'::numrange @- 'empty'::numrange;
+SELECT 'empty'::numrange @- nummultirange();
+SELECT nummultirange() @- 'empty'::numrange;
+SELECT nummultirange() @- nummultirange();
+SELECT 'empty'::numrange @- numrange(1,2);
+SELECT 'empty'::numrange @- nummultirange(numrange(1,2));
+SELECT numrange(1,2) @- nummultirange();
+SELECT nummultirange(numrange(1,2)) @- 'empty'::numrange;
+SELECT 'empty'::numrange @- numrange(1,2);
+SELECT numrange(1,2) @- 'empty'::numrange;
+SELECT nummultirange() @- numrange(1,2);
+SELECT nummultirange() @- nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) @- nummultirange();
+SELECT numrange(1,3) @- numrange(1,3);
+SELECT numrange(1,3) @- numrange(1,2);
+SELECT numrange(1,3) @- numrange(2,4);
+SELECT numrange(1,3) @- numrange(3,4);
+SELECT numrange(1,3) @- nummultirange(numrange(1,3));
+SELECT numrange(1,3) @- nummultirange(numrange(1,2));
+SELECT numrange(1,3) @- nummultirange(numrange(2,4));
+SELECT numrange(1,3) @- nummultirange(numrange(3,4));
+SELECT numrange(1,10) @- nummultirange(numrange(1,2), numrange(4,5));
+SELECT nummultirange(numrange(1,10)) @- numrange(0,2);
+SELECT nummultirange(numrange(1,10)) @- numrange(1,2);
+SELECT nummultirange(numrange(1,10)) @- numrange(3,4);
+SELECT nummultirange(numrange(1,10)) @- numrange(13,18);
+SELECT nummultirange(numrange(1,2), numrange(3,4)) @- nummultirange();
+SELECT nummultirange(numrange(1,2)) @- nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) @- nummultirange(numrange(2,4));
+SELECT nummultirange(numrange(1,2)) @- nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,4)) @- nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,4)) @- nummultirange(numrange(2,3));
+SELECT nummultirange(numrange(1,4)) @- nummultirange(numrange(0,8));
+SELECT nummultirange(numrange(1,4)) @- nummultirange(numrange(0,2));
+SELECT nummultirange(numrange(1,8)) @- nummultirange(numrange(0,2), numrange(3,4));
+SELECT nummultirange(numrange(1,8)) @- nummultirange(numrange(2,3), numrange(5,null));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) @- nummultirange(numrange(-2,0));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) @- nummultirange(numrange(2,4));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) @- nummultirange(numrange(3,5));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) @- nummultirange(numrange(0,9));
+SELECT nummultirange(numrange(1,3), numrange(4,5)) @- nummultirange(numrange(2,9));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) @- nummultirange(numrange(8,9));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) @- nummultirange(numrange(-2,0), numrange(8,9));
+
+-- intersection
+SELECT 'empty'::numrange @* 'empty'::numrange;
+SELECT 'empty'::numrange @* nummultirange();
+SELECT nummultirange() @* 'empty'::numrange;
+SELECT nummultirange() @* nummultirange();
+SELECT 'empty'::numrange @* numrange(1,2);
+SELECT 'empty'::numrange @* nummultirange(numrange(1,2));
+SELECT numrange(1,2) @* 'empty'::numrange;
+SELECT numrange(1,2) @* nummultirange();
+SELECT nummultirange(numrange(1,2)) @* 'empty'::numrange;
+SELECT nummultirange() @* numrange(1,2);
+SELECT nummultirange() @* nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) @* nummultirange();
+SELECT numrange(1,3) @* numrange(1,3);
+SELECT numrange(1,3) @* numrange(1,2);
+SELECT numrange(1,3) @* numrange(1,5);
+SELECT numrange(1,3) @* numrange(2,5);
+SELECT numrange(1,5) @* numrange(2,3);
+SELECT '{[1,3)}'::nummultirange @* numrange(1,3);
+SELECT '{[1,3)}'::nummultirange @* numrange(1,2);
+SELECT '{[1,3)}'::nummultirange @* numrange(1,5);
+SELECT '{[1,3)}'::nummultirange @* numrange(2,5);
+SELECT '{[1,5)}'::nummultirange @* numrange(2,3);
+SELECT numrange(1,3) @* '{[1,3)}'::nummultirange;
+SELECT numrange(1,2) @* '{[1,3)}'::nummultirange;
+SELECT numrange(1,5) @* '{[1,3)}'::nummultirange;
+SELECT numrange(2,5) @* '{[1,3)}'::nummultirange;
+SELECT numrange(2,3) @* '{[1,5)}'::nummultirange;
+SELECT '{[1,3)}'::nummultirange @* '{[1,5)}'::nummultirange;
+SELECT '{[1,3)}'::nummultirange @* '{[0,5)}'::nummultirange;
+SELECT '{[1,3)}'::nummultirange @* '{[0,2)}'::nummultirange;
+SELECT '{[1,3)}'::nummultirange @* '{[2,5)}'::nummultirange;
+SELECT '{[1,4)}'::nummultirange @* '{[2,3)}'::nummultirange;
+SELECT '{[1,4)}'::nummultirange @* '{[0,2), [3,5)}'::nummultirange;
+SELECT '{[1,4), [7,10)}'::nummultirange @* '{[0,8), [9,12)}'::nummultirange;
+SELECT '{[1,4), [7,10)}'::nummultirange @* '{[9,12)}'::nummultirange;
+SELECT '{[1,4), [7,10)}'::nummultirange @* '{[-5,-4), [5,6), [9,12)}'::nummultirange;
+SELECT '{[1,4), [7,10)}'::nummultirange @* '{[0,2), [3,8), [9,12)}'::nummultirange;
+SELECT '{[1,4), [7,10)}'::nummultirange @* '{[0,2), [3,8), [9,12)}'::nummultirange;
+
+--
+-- range_agg function
+--
+create table reservations ( room_id integer not null, booked_during daterange );
+insert into reservations values
+-- 1: has a meets and a gap
+(1, daterange('2018-07-01', '2018-07-07')),
+(1, daterange('2018-07-07', '2018-07-14')),
+(1, daterange('2018-07-20', '2018-07-22')),
+-- 2: just a single row
+(2, daterange('2018-07-01', '2018-07-03')),
+-- 3: one null range
+(3, NULL),
+-- 4: two null ranges
+(4, NULL),
+(4, NULL),
+-- 5: a null range and a non-null range
+(5, NULL),
+(5, daterange('2018-07-01', '2018-07-03')),
+-- 6: has overlap
+(6, daterange('2018-07-01', '2018-07-07')),
+(6, daterange('2018-07-05', '2018-07-10')),
+-- 7: two ranges that meet: no gap or overlap
+(7, daterange('2018-07-01', '2018-07-07')),
+(7, daterange('2018-07-07', '2018-07-14')),
+-- 8: an empty range
+(8, 'empty'::daterange)
+;
+SELECT   room_id, range_agg(booked_during)
+FROM     reservations
+GROUP BY room_id
+ORDER BY room_id;
+
+-- range_agg on a custom range type too
+SELECT  range_agg(r)
+FROM    (VALUES
+          ('[a,c]'::textrange),
+          ('[b,b]'::textrange),
+          ('[c,f]'::textrange),
+          ('[g,h)'::textrange),
+          ('[h,j)'::textrange)
+        ) t(r);
+
+select range_intersect_agg(nmr) from nummultirange_test;
+select range_intersect_agg(nmr) from nummultirange_test where false;
+-- test with just one input:
+select range_intersect_agg(nmr) from (values ('{[1,2]}'::nummultirange)) t(nmr);
+select range_intersect_agg(nmr) from nummultirange_test where nmr @> 4.0;
+
+create table nummultirange_test2(nmr nummultirange);
+create index nummultirange_test2_hash_idx on nummultirange_test2 using hash (nmr);
+
+INSERT INTO nummultirange_test2 VALUES('{[, 5)}');
+INSERT INTO nummultirange_test2 VALUES(nummultirange(numrange(1.1, 2.2)));
+INSERT INTO nummultirange_test2 VALUES(nummultirange(numrange(1.1, 2.2)));
+INSERT INTO nummultirange_test2 VALUES(nummultirange(numrange(1.1, 2.2,'()')));
+INSERT INTO nummultirange_test2 VALUES('{}');
+
+select * from nummultirange_test2 where nmr = '{}';
+select * from nummultirange_test2 where nmr = nummultirange(numrange(1.1, 2.2));
+select * from nummultirange_test2 where nmr = nummultirange(numrange(1.1, 2.3));
+
+set enable_nestloop=t;
+set enable_hashjoin=f;
+set enable_mergejoin=f;
+select * from nummultirange_test natural join nummultirange_test2 order by nmr;
+set enable_nestloop=f;
+set enable_hashjoin=t;
+set enable_mergejoin=f;
+select * from nummultirange_test natural join nummultirange_test2 order by nmr;
+set enable_nestloop=f;
+set enable_hashjoin=f;
+set enable_mergejoin=t;
+select * from nummultirange_test natural join nummultirange_test2 order by nmr;
+
+set enable_nestloop to default;
+set enable_hashjoin to default;
+set enable_mergejoin to default;
+
+DROP TABLE nummultirange_test2;
+
+--
+-- Test user-defined multirange of floats
+--
+
+select '{[123.001, 5.e9)}'::float8multirange @> 888.882::float8;
+create table float8multirange_test(f8mr float8multirange, i int);
+insert into float8multirange_test values(float8multirange(float8range(-100.00007, '1.111113e9')), 42);
+select * from float8multirange_test;
+drop table float8multirange_test;
+
+--
+-- Test multirange types over domains
+--
+
+create domain mydomain as int4;
+create type mydomainrange as range(subtype=mydomain);
+select '{[4,50)}'::mydomainmultirange @> 7::mydomain;
+drop domain mydomain cascade;
+
+--
+-- Test domains over multirange types
+--
+
+create domain restrictedmultirange as int4multirange check (upper(value) < 10);
+select '{[4,5)}'::restrictedmultirange @> 7;
+select '{[4,50)}'::restrictedmultirange @> 7; -- should fail
+drop domain restrictedmultirange;
+
+--
+-- Test multiple multirange types over the same subtype
+--
+
+create type textrange1 as range(subtype=text, collation="C");
+create type textrange2 as range(subtype=text, collation="C");
+
+select textmultirange1(textrange2('a','Z'));  -- should fail
+select textmultirange1(textrange1('a','Z')) @> 'b'::text;
+select textmultirange2(textrange2('a','z')) @> 'b'::text;
+
+drop type textrange1;
+drop type textrange2;
+
+--
+-- Test polymorphic type system
+--
+
+create function anyarray_anymultirange_func(a anyarray, r anymultirange)
+  returns anyelement as 'select $1[1] + lower($2);' language sql;
+
+select anyarray_anymultirange_func(ARRAY[1,2], int4multirange(int4range(10,20)));
+
+-- should fail
+select anyarray_anymultirange_func(ARRAY[1,2], nummultirange(numrange(10,20)));
+
+drop function anyarray_anymultirange_func(anyarray, anymultirange);
+
+-- should fail
+create function bogus_func(anyelement)
+  returns anymultirange as 'select int4multirange(int4range(1,10))' language sql;
+
+-- should fail
+create function bogus_func(int)
+  returns anymultirange as 'select int4multirange(int4range(1,10))' language sql;
+
+create function range_add_bounds(anymultirange)
+  returns anyelement as 'select lower($1) + upper($1)' language sql;
+
+select range_add_bounds(int4multirange(int4range(1, 17)));
+select range_add_bounds(nummultirange(numrange(1.0001, 123.123)));
+
+create function multirangetypes_sql(q anymultirange, b anyarray, out c anyelement)
+  as $$ select upper($1) + $2[1] $$
+  language sql;
+
+select multirangetypes_sql(int4multirange(int4range(1,10)), ARRAY[2,20]);
+select multirangetypes_sql(nummultirange(numrange(1,10)), ARRAY[2,20]);  -- match failure
+
+--
+-- Arrays of multiranges
+--
+
+select ARRAY[nummultirange(numrange(1.1, 1.2)), nummultirange(numrange(12.3, 155.5))];
+
+create table i8mr_array (f1 int, f2 int8multirange[]);
+insert into i8mr_array values (42, array[int8multirange(int8range(1,10)), int8multirange(int8range(2,20))]);
+select * from i8mr_array;
+drop table i8mr_array;
+
+--
+-- Multiranges of arrays
+--
+
+select arraymultirange(arrayrange(ARRAY[1,2], ARRAY[2,1]));
+select arraymultirange(arrayrange(ARRAY[2,1], ARRAY[1,2]));  -- fail
+
+select array[1,1] <@ arraymultirange(arrayrange(array[1,2], array[2,1]));
+select array[1,3] <@ arraymultirange(arrayrange(array[1,2], array[2,1]));
+
+--
+-- Ranges of composites
+--
+
+create type two_ints as (a int, b int);
+create type two_ints_range as range (subtype = two_ints);
+
+-- with force_parallel_mode on, this exercises tqueue.c's range remapping
+select *, row_to_json(upper(t)) as u from
+  (values (two_ints_multirange(two_ints_range(row(1,2), row(3,4)))),
+          (two_ints_multirange(two_ints_range(row(5,6), row(7,8))))) v(t);
+
+drop type two_ints cascade;
+
+--
+-- Check behavior when subtype lacks a hash function
+--
+
+set enable_sort = off;  -- try to make it pick a hash setop implementation
+
+select '{(2,5)}'::cashmultirange except select '{(5,6)}'::cashmultirange;
+
+reset enable_sort;
+
+--
+-- OUT/INOUT/TABLE functions
+--
+
+-- infer anymultirange from anymultirange
+create function mr_outparam_succeed(i anymultirange, out r anymultirange, out t text)
+  as $$ select $1, 'foo'::text $$ language sql;
+
+select * from mr_outparam_succeed(int4multirange(int4range(1,2)));
+
+-- infer anyarray from anymultirange
+create function mr_outparam_succeed2(i anymultirange, out r anyarray, out t text)
+  as $$ select ARRAY[upper($1)], 'foo'::text $$ language sql;
+
+select * from mr_outparam_succeed2(int4multirange(int4range(1,2)));
+
+-- infer anyrange from anymultirange
+create function mr_outparam_succeed3(i anymultirange, out r anyrange, out t text)
+  as $$ select range_merge($1), 'foo'::text $$ language sql;
+select * from mr_outparam_succeed3(int4multirange(int4range(1,2)));
+
+-- infer anymultirange from anyrange
+create function mr_outparam_succeed4(i anyrange, out r anymultirange, out t text)
+  as $$ select $1 @+ $1, 'foo'::text $$ language sql;
+
+select * from mr_outparam_succeed4(int4range(1,2));
+
+-- infer anyelement from anymultirange
+create function mr_inoutparam_succeed(out i anyelement, inout r anymultirange)
+  as $$ select upper($1), $1 $$ language sql;
+
+select * from mr_inoutparam_succeed(int4multirange(int4range(1,2)));
+
+-- infer anyelement+anymultirange from anyelement+anymultirange
+create function mr_table_succeed(i anyelement, r anymultirange) returns table(i anyelement, r anymultirange)
+  as $$ select $1, $2 $$ language sql;
+
+select * from mr_table_succeed(123, int4multirange(int4range(1,11)));
+
+-- should fail
+create function mr_outparam_fail(i anyelement, out r anymultirange, out t text)
+  as $$ select '[1,10]', 'foo' $$ language sql;
+
+--should fail
+create function mr_inoutparam_fail(inout i anyelement, out r anymultirange)
+  as $$ select $1, '[1,10]' $$ language sql;
+
+--should fail
+create function mr_table_fail(i anyelement) returns table(i anyelement, r anymultirange)
+  as $$ select $1, '[1,10]' $$ language sql;
diff --git a/src/test/regress/sql/rangetypes.sql b/src/test/regress/sql/rangetypes.sql
index 9fdb1953df..d23855d08e 100644
--- a/src/test/regress/sql/rangetypes.sql
+++ b/src/test/regress/sql/rangetypes.sql
@@ -118,6 +118,10 @@ select numrange(1.0, 2.0) * numrange(2.0, 3.0);
 select numrange(1.0, 2.0) * numrange(1.5, 3.0);
 select numrange(1.0, 2.0) * numrange(2.5, 3.0);
 
+select range_intersect_agg(nr) from numrange_test;
+select range_intersect_agg(nr) from numrange_test where false;
+select range_intersect_agg(nr) from numrange_test where nr @> 4.0;
+
 create table numrange_test2(nr numrange);
 create index numrange_test2_hash_idx on numrange_test2 (nr);
 
@@ -481,16 +485,25 @@ reset enable_sort;
 -- OUT/INOUT/TABLE functions
 --
 
+-- infer anyrange from anyrange
 create function outparam_succeed(i anyrange, out r anyrange, out t text)
   as $$ select $1, 'foo'::text $$ language sql;
 
 select * from outparam_succeed(int4range(1,2));
 
+-- infer anyarray from anyrange
+create function outparam_succeed2(i anyrange, out r anyarray, out t text)
+  as $$ select ARRAY[upper($1)], 'foo'::text $$ language sql;
+
+select * from outparam_succeed2(int4range(int4range(1,2)));
+
+-- infer anyelement from anyrange
 create function inoutparam_succeed(out i anyelement, inout r anyrange)
   as $$ select upper($1), $1 $$ language sql;
 
 select * from inoutparam_succeed(int4range(1,2));
 
+-- infer anyelement+anyrange from anyelement+anyrange
 create function table_succeed(i anyelement, r anyrange) returns table(i anyelement, r anyrange)
   as $$ select $1, $2 $$ language sql;
 
-- 
2.11.0

#54Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Paul A Jungwirth (#53)
9 attachment(s)
Re: range_agg

Hello Paul, I've started to review this patch. Here's a few minor
things I ran across -- mostly compiler warnings (is my compiler too
ancient?). You don't have to agree with every fix -- feel free to use
different fixes if you have them. Also, feel free to squash them onto
whatever commit you like (I think they all belong onto 0001 except the
last which seems to be for 0002).

Did you not push your latest version to your github repo? I pulled from
there and branch 'multirange' does not seem to match what you posted.

--
�lvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

Attachments:

0003-Silence-mixed-declarations-and-code-compiler-warning.patchtext/x-diff; charset=us-asciiDownload
From c4e4b1a97136f7a2047f8be6608cac0782dd6d12 Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Thu, 26 Sep 2019 17:42:22 -0300
Subject: [PATCH 3/9] Silence 'mixed declarations and code' compiler warning

---
 src/backend/commands/typecmds.c | 8 ++------
 1 file changed, 2 insertions(+), 6 deletions(-)

diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index 14a6857062..26ed3e4c76 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -1782,14 +1782,10 @@ makeMultirangeConstructors(const char *name, Oid namespace,
 	static const char *const prosrc[2] = {"multirange_constructor0",
 	"multirange_constructor1"};
 	static const int pronargs[2] = {0, 1};
-
-	Oid			constructorArgTypes[0];
+	Oid			constructorArgTypes = rangeArrayOid;
 	ObjectAddress myself,
 				referenced;
 	int			i;
-
-	constructorArgTypes[0] = rangeArrayOid;
-
 	Datum		allParamTypes[1] = {ObjectIdGetDatum(rangeArrayOid)};
 	ArrayType  *allParameterTypes = construct_array(allParamTypes, 1, OIDOID,
 													sizeof(Oid), true, 'i');
@@ -1808,7 +1804,7 @@ makeMultirangeConstructors(const char *name, Oid namespace,
 	{
 		oidvector  *constructorArgTypesVector;
 
-		constructorArgTypesVector = buildoidvector(constructorArgTypes,
+		constructorArgTypesVector = buildoidvector(&constructorArgTypes,
 												   pronargs[i]);
 
 		myself = ProcedureCreate(name,	/* name: same as multirange type */
-- 
2.17.1

0004-Silence-mixed-declarations-and-code-compiler-warning.patchtext/x-diff; charset=us-asciiDownload
From af414286417d4fddd078c7ffac087482b3ba959d Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Thu, 26 Sep 2019 17:49:19 -0300
Subject: [PATCH 4/9] Silence 'mixed declarations and code' compiler warnings

---
 src/backend/utils/fmgr/funcapi.c | 98 +++++++++++++++++++-------------
 1 file changed, 59 insertions(+), 39 deletions(-)

diff --git a/src/backend/utils/fmgr/funcapi.c b/src/backend/utils/fmgr/funcapi.c
index e6e82fda63..701664bd3b 100644
--- a/src/backend/utils/fmgr/funcapi.c
+++ b/src/backend/utils/fmgr/funcapi.c
@@ -541,18 +541,21 @@ resolve_polymorphic_tupdesc(TupleDesc tupdesc, oidvector *declared_args,
 
 		if (OidIsValid(anymultirange_type))
 		{
-			Oid			rngtype = resolve_generic_type(ANYRANGEOID,
-													   anymultirange_type,
-													   ANYMULTIRANGEOID);
+			Oid			rngtype;
+			Oid			subtype;
+
+			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);
+			anyrange_type = rngtype;
+			subtype = resolve_generic_type(ANYELEMENTOID,
+										   anyrange_type,
+										   ANYRANGEOID);
 
 			/* check for inconsistent array and multirange results */
 			if (OidIsValid(anyelement_type) && anyelement_type != subtype)
@@ -596,18 +599,21 @@ resolve_polymorphic_tupdesc(TupleDesc tupdesc, oidvector *declared_args,
 	{
 		if (OidIsValid(anymultirange_type))
 		{
-			Oid			rngtype = resolve_generic_type(ANYRANGEOID,
-													   anymultirange_type,
-													   ANYMULTIRANGEOID);
+			Oid			rngtype;
+			Oid			subtype;
+
+			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);
+			subtype = resolve_generic_type(ANYELEMENTOID,
+										   anyrange_type,
+										   ANYRANGEOID);
 
 			/* check for inconsistent array and multirange results */
 			if (OidIsValid(anyelement_type) && anyelement_type != subtype)
@@ -628,21 +634,25 @@ resolve_polymorphic_tupdesc(TupleDesc tupdesc, oidvector *declared_args,
 	{
 		if (OidIsValid(anyrange_type))
 		{
-			Oid			subtype = resolve_generic_type(ANYELEMENTOID,
-													   anyrange_type,
-													   ANYRANGEOID);
+			Oid			subtype;
+			Oid			mltrngtype;
+			Oid			rngtype;
+
+			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);
+			mltrngtype = resolve_generic_type(ANYMULTIRANGEOID,
+											  anyrange_type,
+											  ANYRANGEOID);
 
 			/* check for inconsistent range and multirange results */
-			Oid			rngtype = get_multirange_subtype(mltrngtype);
+			rngtype = get_multirange_subtype(mltrngtype);
 
 			if (OidIsValid(anyrange_type) && anyrange_type != rngtype)
 				return false;
@@ -868,18 +878,21 @@ resolve_polymorphic_argtypes(int numargs, Oid *argtypes, char *argmodes,
 
 		if (OidIsValid(anymultirange_type))
 		{
-			Oid			rngtype = resolve_generic_type(ANYRANGEOID,
-													   anymultirange_type,
-													   ANYMULTIRANGEOID);
+			Oid			rngtype;
+			Oid			subtype;
+
+			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);
+			subtype = resolve_generic_type(ANYELEMENTOID,
+										   anyrange_type,
+										   ANYRANGEOID);
 
 			/* check for inconsistent array and multirange results */
 			if (OidIsValid(anyelement_type) && anyelement_type != subtype)
@@ -923,18 +936,21 @@ resolve_polymorphic_argtypes(int numargs, Oid *argtypes, char *argmodes,
 	{
 		if (OidIsValid(anymultirange_type))
 		{
-			Oid			rngtype = resolve_generic_type(ANYRANGEOID,
-													   anymultirange_type,
-													   ANYMULTIRANGEOID);
+			Oid			rngtype;
+			Oid			subtype;
+
+			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);
+			subtype = resolve_generic_type(ANYELEMENTOID,
+										   anyrange_type,
+										   ANYRANGEOID);
 
 			/* check for inconsistent array and multirange results */
 			if (OidIsValid(anyelement_type) && anyelement_type != subtype)
@@ -955,21 +971,25 @@ resolve_polymorphic_argtypes(int numargs, Oid *argtypes, char *argmodes,
 	{
 		if (OidIsValid(anyrange_type))
 		{
-			Oid			subtype = resolve_generic_type(ANYELEMENTOID,
-													   anyrange_type,
-													   ANYRANGEOID);
+			Oid			subtype;
+			Oid			mltrngtype;
+			Oid			rngtype;
+
+			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);
+			mltrngtype = resolve_generic_type(ANYMULTIRANGEOID,
+											  anyrange_type,
+											  ANYRANGEOID);
 
 			/* check for inconsistent range and multirange results */
-			Oid			rngtype = get_multirange_subtype(mltrngtype);
+			rngtype = get_multirange_subtype(mltrngtype);
 
 			if (OidIsValid(anyrange_type) && anyrange_type != rngtype)
 				return false;
-- 
2.17.1

0005-Silence-mixed-declarations-and-code-compiler-warning.patchtext/x-diff; charset=us-asciiDownload
From 43c82181760ad982cb6958817f34cb80f39ffd8c Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Thu, 26 Sep 2019 17:51:02 -0300
Subject: [PATCH 5/9] Silence 'mixed declarations and code' compiler warnings

---
 src/backend/utils/adt/multirangetypes.c | 25 ++++++++++++++-----------
 1 file changed, 14 insertions(+), 11 deletions(-)

diff --git a/src/backend/utils/adt/multirangetypes.c b/src/backend/utils/adt/multirangetypes.c
index c509994796..359b78d056 100644
--- a/src/backend/utils/adt/multirangetypes.c
+++ b/src/backend/utils/adt/multirangetypes.c
@@ -351,9 +351,12 @@ multirange_send(PG_FUNCTION_ARGS)
 	for (i = 0; i < range_count; i++)
 	{
 		Datum		range = RangeTypePGetDatum(ranges[i]);
+		uint32		range_len;
+		char	   *range_data;
+
 		range = PointerGetDatum(SendFunctionCall(&cache->proc, range));
-		uint32		range_len = VARSIZE(range) - VARHDRSZ;
-		char	   *range_data = VARDATA(range);
+		range_len = VARSIZE(range) - VARHDRSZ;
+		*range_data = VARDATA(range);
 
 		pq_sendint32(buf, range_len);
 		pq_sendbytes(buf, range_data, range_len);
@@ -2039,12 +2042,12 @@ bool
 range_before_multirange_internal(TypeCacheEntry *typcache, RangeType *r,
 								 MultirangeType * mr)
 {
-	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
-		return false;
-
 	int32		range_count;
 	RangeType **ranges;
 
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		return false;
+
 	multirange_deserialize(mr, &range_count, &ranges);
 
 	return range_before_internal(typcache->rngtype, r, ranges[0]);
@@ -2054,14 +2057,14 @@ bool
 multirange_before_multirange_internal(TypeCacheEntry *typcache, MultirangeType * mr1,
 									  MultirangeType * mr2)
 {
-	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
-		return false;
-
 	int32		range_count1;
 	int32		range_count2;
 	RangeType **ranges1;
 	RangeType **ranges2;
 
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		return false;
+
 	multirange_deserialize(mr1, &range_count1, &ranges1);
 	multirange_deserialize(mr2, &range_count2, &ranges2);
 
@@ -2074,12 +2077,12 @@ bool
 range_after_multirange_internal(TypeCacheEntry *typcache, RangeType *r,
 								MultirangeType * mr)
 {
-	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
-		return false;
-
 	int32		range_count;
 	RangeType **ranges;
 
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		return false;
+
 	multirange_deserialize(mr, &range_count, &ranges);
 
 	return range_after_internal(typcache->rngtype, r, ranges[range_count - 1]);
-- 
2.17.1

0001-Remove-unused-variable.patchtext/x-diff; charset=us-asciiDownload
From 6d849beafea1e4129f044f8dd038933d6ea72b54 Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Thu, 26 Sep 2019 16:56:26 -0300
Subject: [PATCH 1/9] Remove unused variable

---
 src/backend/catalog/pg_type.c | 2 --
 1 file changed, 2 deletions(-)

diff --git a/src/backend/catalog/pg_type.c b/src/backend/catalog/pg_type.c
index 08552850c8..ea5b161c4d 100644
--- a/src/backend/catalog/pg_type.c
+++ b/src/backend/catalog/pg_type.c
@@ -876,7 +876,6 @@ 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;
@@ -892,7 +891,6 @@ makeMultirangeTypeName(const char *rangeTypeName, Oid typeNamespace)
 	if (rangestr)
 	{
 		rangeoffset = rangestr - rangeTypeName;
-		rangelen = strlen(rangestr);
 		strlcpy(mltrng + rangeoffset, "multi", NAMEDATALEN - rangeoffset);
 		strlcpy(mltrng + rangeoffset + 5, rangestr, NAMEDATALEN - rangeoffset - 5);
 		namelen += 5;
-- 
2.17.1

0002-Silence-compiler-warning.patchtext/x-diff; charset=us-asciiDownload
From 63bd9723a45c33cccd11704817fd954af5abaf31 Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Thu, 26 Sep 2019 17:39:54 -0300
Subject: [PATCH 2/9] Silence compiler warning

---
 src/backend/parser/parse_coerce.c | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/src/backend/parser/parse_coerce.c b/src/backend/parser/parse_coerce.c
index f0b1f6f831..aab2348952 100644
--- a/src/backend/parser/parse_coerce.c
+++ b/src/backend/parser/parse_coerce.c
@@ -1919,6 +1919,8 @@ enforce_generic_type_consistency(const Oid *actual_arg_types,
 							   format_type_be(elem_typeid))));
 		}
 	}
+	else
+		range_typelem = InvalidOid;
 
 	/* Get the range type based on the multirange type, if we have one */
 	if (OidIsValid(multirange_typeid))
-- 
2.17.1

0006-silence-variable-set-but-not-used-compiler-warning.patchtext/x-diff; charset=us-asciiDownload
From a735dadde028785020b71e8aac40fc717f6c765c Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Thu, 26 Sep 2019 17:51:33 -0300
Subject: [PATCH 6/9] silence 'variable set but not used' compiler warning

---
 src/backend/utils/adt/multirangetypes.c | 2 --
 1 file changed, 2 deletions(-)

diff --git a/src/backend/utils/adt/multirangetypes.c b/src/backend/utils/adt/multirangetypes.c
index 359b78d056..cea7e7f97e 100644
--- a/src/backend/utils/adt/multirangetypes.c
+++ b/src/backend/utils/adt/multirangetypes.c
@@ -91,7 +91,6 @@ multirange_in(PG_FUNCTION_ARGS)
 	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;
@@ -107,7 +106,6 @@ multirange_in(PG_FUNCTION_ARGS)
 
 	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))
-- 
2.17.1

0007-Fix-uninitialized-variable-warning.patchtext/x-diff; charset=us-asciiDownload
From 670f80824c92dd9b36f823110182339dd8fc85b8 Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Thu, 26 Sep 2019 18:00:29 -0300
Subject: [PATCH 7/9] Fix uninitialized variable warning

---
 src/backend/utils/adt/multirangetypes.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/backend/utils/adt/multirangetypes.c b/src/backend/utils/adt/multirangetypes.c
index cea7e7f97e..ba7c2a23ae 100644
--- a/src/backend/utils/adt/multirangetypes.c
+++ b/src/backend/utils/adt/multirangetypes.c
@@ -100,7 +100,7 @@ multirange_in(PG_FUNCTION_ARGS)
 	MultirangeType *ret;
 	MultirangeParseState parse_state;
 	const char *ptr = input_str;
-	const char *range_str;
+	const char *range_str = NULL;
 	int32		range_str_len;
 	char	   *range_str_copy;
 
-- 
2.17.1

0008-Fix-wrong-type-assignment-warning.patchtext/x-diff; charset=us-asciiDownload
From edc5171168c804fe6127cb0ce51c5fad9a6cbb46 Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Thu, 26 Sep 2019 18:00:44 -0300
Subject: [PATCH 8/9] Fix wrong-type assignment warning

---
 src/backend/utils/adt/multirangetypes.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/backend/utils/adt/multirangetypes.c b/src/backend/utils/adt/multirangetypes.c
index ba7c2a23ae..c85f5dc90d 100644
--- a/src/backend/utils/adt/multirangetypes.c
+++ b/src/backend/utils/adt/multirangetypes.c
@@ -354,7 +354,7 @@ multirange_send(PG_FUNCTION_ARGS)
 
 		range = PointerGetDatum(SendFunctionCall(&cache->proc, range));
 		range_len = VARSIZE(range) - VARHDRSZ;
-		*range_data = VARDATA(range);
+		range_data = VARDATA(range);
 
 		pq_sendint32(buf, range_len);
 		pq_sendbytes(buf, range_data, range_len);
-- 
2.17.1

0009-Fix-crash-in-multirange_intersect_multirange_interna.patchtext/x-diff; charset=us-asciiDownload
From 1479908cd0df9cc77968dbb0def484f59ad9d64c Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Thu, 26 Sep 2019 18:01:04 -0300
Subject: [PATCH 9/9] Fix crash in multirange_intersect_multirange_internal

---
 src/backend/utils/adt/multirangetypes.c | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)

diff --git a/src/backend/utils/adt/multirangetypes.c b/src/backend/utils/adt/multirangetypes.c
index c85f5dc90d..7717acb3b1 100644
--- a/src/backend/utils/adt/multirangetypes.c
+++ b/src/backend/utils/adt/multirangetypes.c
@@ -1123,7 +1123,8 @@ multirange_intersect_multirange(PG_FUNCTION_ARGS)
 
 MultirangeType *
 multirange_intersect_multirange_internal(Oid mltrngtypoid, TypeCacheEntry *rangetyp,
-										 int32 range_count1, RangeType **ranges1, int32 range_count2, RangeType **ranges2)
+										 int32 range_count1, RangeType **ranges1,
+										 int32 range_count2, RangeType **ranges2)
 {
 	RangeType  *r1;
 	RangeType  *r2;
@@ -1132,6 +1133,9 @@ multirange_intersect_multirange_internal(Oid mltrngtypoid, TypeCacheEntry *range
 	int32		i1;
 	int32		i2;
 
+	if (range_count1 == 0 || range_count2 == 0)
+		return make_multirange(mltrngtypoid, rangetyp, 0, NULL);
+
 	/*
 	 * Worst case is a stitching pattern like this:
 	 *
-- 
2.17.1

#55Paul A Jungwirth
pj@illuminatedcomputing.com
In reply to: Alvaro Herrera (#54)
4 attachment(s)
Re: range_agg

On Thu, Sep 26, 2019 at 2:13 PM Alvaro Herrera <alvherre@2ndquadrant.com> wrote:

Hello Paul, I've started to review this patch. Here's a few minor
things I ran across -- mostly compiler warnings (is my compiler too
ancient?). You don't have to agree with every fix -- feel free to use
different fixes if you have them. Also, feel free to squash them onto
whatever commit you like (I think they all belong onto 0001 except the
last which seems to be for 0002).

Hi Alvaro, sorry, I missed your note from September. I really
appreciate your review and will take a look at your suggested changes!
I just opened this thread to post a rebased set patches (especially
because of the `const` additions to range functions). Maybe it's not
that helpful since they don't include your changes yet but here they
are anyway. I'll post some more with your changes shortly.

Did you not push your latest version to your github repo? I pulled from
there and branch 'multirange' does not seem to match what you posted.

Hmm, I'll take a look. Before I made the v3 files I switched to
multirange-patch so I could squash things and use git to generate one
patch file per commit. So `multirange` isn't rebased as currently as
`multirange-patch`. If you don't mind a force-push I can update
`multirange` to be the same.

Yours,
Paul

Attachments:

v4-0001-multirange-type-basics.patchapplication/octet-stream; name=v4-0001-multirange-type-basics.patchDownload
From fcf6446aff14bda7128995ee72eb8942420cefc6 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 v4 1/4] multirange type basics

---
 src/backend/catalog/pg_proc.c                 |  16 +-
 src/backend/catalog/pg_range.c                |  11 +-
 src/backend/catalog/pg_type.c                 | 134 +++-
 src/backend/commands/typecmds.c               | 171 ++++-
 src/backend/parser/parse_coerce.c             | 301 +++++++-
 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/catversion.h              |   2 +-
 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 +-
 42 files changed, 2895 insertions(+), 137 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 2a51501d8d..08552850c8 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,34 +788,10 @@ makeArrayTypeName(const char *typeName, Oid typeNamespace)
 {
 	char	   *arr = (char *) palloc(NAMEDATALEN);
 	int			namelen = strlen(typeName);
-	Relation	pg_type_desc;
-	int			i;
-
-	/*
-	 * The idea is to prepend underscores as needed until we make a name that
-	 * doesn't collide with anything...
-	 */
-	pg_type_desc = table_open(TypeRelationId, AccessShareLock);
-
-	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;
-	}
-
-	table_close(pg_type_desc, AccessShareLock);
+	int			underscores;
 
-	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\"",
@@ -883,3 +862,104 @@ 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)
+{
+	Relation	pg_type_desc;
+	int			i;
+
+	pg_type_desc = table_open(TypeRelationId, AccessShareLock);
+
+	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;
+		}
+	}
+
+	table_close(pg_type_desc, AccessShareLock);
+
+	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..f0b1f6f831 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			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"
 
 
 /*
@@ -185,6 +186,30 @@ anyrange_out(PG_FUNCTION_ARGS)
 }
 
 /*
+ * 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.
  *
  * We allow this so that PL functions can return VOID without any special
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));
 }
 
 /*
@@ -1938,6 +2006,45 @@ range_cmp_bound_values(TypeCacheEntry *typcache, const RangeBound *b1,
 }
 
 /*
+ * 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.
  */
 RangeType *
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
@@ -2469,6 +2469,16 @@ type_is_range(Oid typid)
 }
 
 /*
+ * 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
  *
  *		Given the type OID, fetch its category and preferred-type status.
@@ -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 16297a52a1..3f0e7f8576 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;
@@ -759,6 +777,16 @@ lookup_type_cache(Oid type_id, int flags)
 	}
 
 	/*
+	 * 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
 	 */
 	if ((flags & TYPECACHE_DOMAIN_BASE_INFO) &&
@@ -871,6 +899,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
  *
  * Note: we assume we're called in a relatively short-lived context, so it's
@@ -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/catversion.h b/src/include/catalog/catversion.h
index 1f6de76e9c..c265ee4c09 100644
--- a/src/include/catalog/catversion.h
+++ b/src/include/catalog/catversion.h
@@ -53,6 +53,6 @@
  */
 
 /*							yyyymmddN */
-#define CATALOG_VERSION_NO	201910251
+#define CATALOG_VERSION_NO	201911061
 
 #endif
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 fa7dc96ece..4d319bb5b3 100644
--- a/src/include/catalog/pg_operator.dat
+++ b/src/include/catalog/pg_operator.dat
@@ -3289,5 +3289,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 58ea5b982b..7ffa047969 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -9724,6 +9724,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 be49e00114..c95a38849a 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 2a584b4b13..6a25fcc9bf 100644
--- a/src/include/catalog/pg_type.h
+++ b/src/include/catalog/pg_type.h
@@ -259,6 +259,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 */
 
@@ -270,6 +271,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'
@@ -285,7 +287,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 */
 
@@ -344,4 +347,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
@@ -99,6 +99,11 @@ typedef struct TypeCacheEntry
 	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 fc0f14122b..c8c1cd0671 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 68ac56acdb..35155436f0 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.11.0

v4-0003-multirange-pg_dump.patchapplication/octet-stream; name=v4-0003-multirange-pg_dump.patchDownload
From 3d4609039bdc1ee9e6b8b50250116bf9ade60fc1 Mon Sep 17 00:00:00 2001
From: "Paul A. Jungwirth" <pj@illuminatedcomputing.com>
Date: Sat, 21 Sep 2019 21:28:37 -0700
Subject: [PATCH v4 3/4] multirange pg_dump

---
 src/bin/pg_dump/pg_dump.c | 7 ++++++-
 src/bin/pg_dump/pg_dump.h | 1 +
 2 files changed, 7 insertions(+), 1 deletion(-)

diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index bf69adc2f4..db84e79e11 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1583,7 +1583,7 @@ selectDumpableType(TypeInfo *tyinfo, Archive *fout)
 	}
 
 	/* skip auto-generated array types */
-	if (tyinfo->isArray)
+	if (tyinfo->isArray || tyinfo->isMultirange)
 	{
 		tyinfo->dobj.objType = DO_DUMMY_TYPE;
 
@@ -4923,6 +4923,11 @@ getTypes(Archive *fout, int *numTypes)
 		else
 			tyinfo[i].isArray = false;
 
+		if (tyinfo[i].typtype == 'm')
+			tyinfo[i].isMultirange = true;
+		else
+			tyinfo[i].isMultirange = false;
+
 		/* Decide whether we want to dump it */
 		selectDumpableType(&tyinfo[i], fout);
 
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 7b2c1524a5..e27a752b6d 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -175,6 +175,7 @@ typedef struct _typeInfo
 	char		typrelkind;		/* 'r', 'v', 'c', etc */
 	char		typtype;		/* 'b', 'c', etc */
 	bool		isArray;		/* true if auto-generated array type */
+	bool		isMultirange;	/* true if auto-generated multirange type */
 	bool		isDefined;		/* true if typisdefined */
 	/* If needed, we'll create a "shell type" entry for it; link that here: */
 	struct _shellTypeInfo *shellType;	/* shell-type entry, or NULL */
-- 
2.11.0

v4-0004-multirange-docs.patchapplication/octet-stream; name=v4-0004-multirange-docs.patchDownload
From f17f0d2710b22a25315c66b7b10fb43e8b6730ff Mon Sep 17 00:00:00 2001
From: "Paul A. Jungwirth" <pj@illuminatedcomputing.com>
Date: Sat, 21 Sep 2019 21:28:47 -0700
Subject: [PATCH v4 4/4] multirange docs

---
 doc/src/sgml/extend.sgml     |  28 +++-
 doc/src/sgml/func.sgml       | 302 +++++++++++++++++++++++++++++++++++++++----
 doc/src/sgml/rangetypes.sgml |  47 ++++++-
 3 files changed, 342 insertions(+), 35 deletions(-)

diff --git a/doc/src/sgml/extend.sgml b/doc/src/sgml/extend.sgml
index a3046f22d0..e72720cd68 100644
--- a/doc/src/sgml/extend.sgml
+++ b/doc/src/sgml/extend.sgml
@@ -229,9 +229,9 @@
    </indexterm>
 
     <para>
-     Five pseudo-types of special interest are <type>anyelement</type>,
+     Six pseudo-types of special interest are <type>anyelement</type>,
      <type>anyarray</type>, <type>anynonarray</type>, <type>anyenum</type>,
-     and <type>anyrange</type>,
+     <type>anyrange</type>, and <type>anymultirange</type>.
      which are collectively called <firstterm>polymorphic types</firstterm>.
      Any function declared using these types is said to be
      a <firstterm>polymorphic function</firstterm>.  A polymorphic function can
@@ -250,15 +250,15 @@
      position declared as <type>anyarray</type> can have any array data type,
      but similarly they must all be the same type.  And similarly,
      positions declared as <type>anyrange</type> must all be the same range
-     type.  Furthermore, if there are
+     type.  Likewise for <type>anymultirange</type>.
+    </para>
+
+    <para>
+     Furthermore, if there are
      positions declared <type>anyarray</type> and others declared
      <type>anyelement</type>, the actual array type in the
      <type>anyarray</type> positions must be an array whose elements are
      the same type appearing in the <type>anyelement</type> positions.
-     Similarly, if there are positions declared <type>anyrange</type>
-     and others declared <type>anyelement</type>, the actual range type in
-     the <type>anyrange</type> positions must be a range whose subtype is
-     the same type appearing in the <type>anyelement</type> positions.
      <type>anynonarray</type> is treated exactly the same as <type>anyelement</type>,
      but adds the additional constraint that the actual type must not be
      an array type.
@@ -268,6 +268,20 @@
     </para>
 
     <para>
+     Similarly, if there are positions declared <type>anyrange</type>
+     and others declared <type>anyelement</type>, the actual range type in
+     the <type>anyrange</type> positions must be a range whose subtype is
+     the same type appearing in the <type>anyelement</type> positions.
+     The type <type>anymultirange</type> accepts a multirange
+     whose contents match the other polymorphic types.
+     That is it must hold ranges matching <type>anyrange</type>
+     and base type elements matching <type>anyelement</type> (if either of
+     those are present). If <type>anyarray</type> is present its elements
+     must match the base type of the <type>anyrange</type> and/or the base
+     type of the ranges in an </type>anymultirange</type>.
+    </para>
+
+    <para>
      Thus, when more than one argument position is declared with a polymorphic
      type, the net effect is that only certain combinations of actual argument
      types are allowed.  For example, a function declared as
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 28eb322f3f..54b428eb39 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -14566,7 +14566,9 @@ NULL baz</literallayout>(3 rows)</entry>
 
   <para>
    <xref linkend="range-operators-table"/> shows the operators
-   available for range types.
+   available for range and multirange types.
+   Many of these operators will accept either a range or multirange
+   on either side.
   </para>
 
     <table id="range-operators-table">
@@ -14576,7 +14578,7 @@ NULL baz</literallayout>(3 rows)</entry>
        <row>
         <entry>Operator</entry>
         <entry>Description</entry>
-        <entry>Example</entry>
+        <entry>Examples</entry>
         <entry>Result</entry>
        </row>
       </thead>
@@ -14584,136 +14586,247 @@ NULL baz</literallayout>(3 rows)</entry>
        <row>
         <entry> <literal>=</literal> </entry>
         <entry>equal</entry>
-        <entry><literal>int4range(1,5) = '[1,4]'::int4range</literal></entry>
+        <entry><literallayout class="monospaced">int4range(1,5) = '[1,4]'::int4range
+'{[1,5)}'::int4multirange = '{[1,4]}'::int4multirange</literallayout></entry>
         <entry><literal>t</literal></entry>
        </row>
 
        <row>
         <entry> <literal>&lt;&gt;</literal> </entry>
         <entry>not equal</entry>
-        <entry><literal>numrange(1.1,2.2) &lt;&gt; numrange(1.1,2.3)</literal></entry>
+        <entry><literallayout class="monospaced">numrange(1.1,2.2) &lt;&gt; numrange(1.1,2.3)
+'{[1.1,2.2)}'::nummultirange &lt;&gt; '{[1.1,2.3)}'::nummultirange</literallayout></entry>
         <entry><literal>t</literal></entry>
        </row>
 
        <row>
         <entry> <literal>&lt;</literal> </entry>
         <entry>less than</entry>
-        <entry><literal>int4range(1,10) &lt; int4range(2,3)</literal></entry>
+        <entry><literallayout class="monospaced">int4range(1,10) &lt; int4range(2,3)
+'{[1,10)}'::int4multirange &lt; '{[2,3)}'::int4multirange</literallayout>
+        </entry>
         <entry><literal>t</literal></entry>
        </row>
 
        <row>
         <entry> <literal>&gt;</literal> </entry>
         <entry>greater than</entry>
-        <entry><literal>int4range(1,10) &gt; int4range(1,5)</literal></entry>
+        <entry>
+          <literallayout class="monospaced">int4range(1,10) &gt; int4range(1,5)
+'{[1,10)}'::int4multirange &gt; '{[1,5)}'::int4multirange</literallayout>
+        </entry>
         <entry><literal>t</literal></entry>
        </row>
 
        <row>
         <entry> <literal>&lt;=</literal> </entry>
         <entry>less than or equal</entry>
-        <entry><literal>numrange(1.1,2.2) &lt;= numrange(1.1,2.2)</literal></entry>
+        <entry>
+          <literallayout class="monospaced">numrange(1.1,2.2) &lt;= numrange(1.1,2.2)
+'{[1.1,2.2)}'::nummultirange &lt;= '{[1.1,2.2)}'::nummultirange</literallayout>
+        </entry>
         <entry><literal>t</literal></entry>
        </row>
 
        <row>
         <entry> <literal>&gt;=</literal> </entry>
         <entry>greater than or equal</entry>
-        <entry><literal>numrange(1.1,2.2) &gt;= numrange(1.1,2.0)</literal></entry>
+        <entry>
+          <literallayout class="monospaced">numrange(1.1,2.2) &gt;= numrange(1.1,2.0)
+'{[1.1,2.2)}'::nummultirange &gt;= '{[1.1,2.0)}'::nummultirange</literallayout>
+        </entry>
+        <entry><literal>t</literal></entry>
+       </row>
+
+       <row>
+        <entry> <literal>@&gt;</literal> </entry>
+        <entry>contains multirange</entry>
+        <entry>
+          <literallayout class="monospaced">int4range(2,4) @&gt; '{[2,3)}'::int4multirange
+'{[2,4)}'::int4multirange @&gt; '{[2,3)}'::int4multirange</literallayout>
+        </entry>
         <entry><literal>t</literal></entry>
        </row>
 
        <row>
         <entry> <literal>@&gt;</literal> </entry>
         <entry>contains range</entry>
-        <entry><literal>int4range(2,4) @&gt; int4range(2,3)</literal></entry>
+        <entry>
+          <literallayout class="monospaced">int4range(2,4) @&gt; int4range(2,3)
+'{[2,4)}'::int4multirange @&gt; int4range(2,3)</literallayout>
+        </entry>
         <entry><literal>t</literal></entry>
        </row>
 
        <row>
         <entry> <literal>@&gt;</literal> </entry>
         <entry>contains element</entry>
-        <entry><literal>'[2011-01-01,2011-03-01)'::tsrange @&gt; '2011-01-10'::timestamp</literal></entry>
+        <entry>
+          <literallayout class="monospaced">'[2011-01-01,2011-03-01)'::tsrange @&gt; '2011-01-10'::timestamp
+'{[2011-01-01,2011-03-01)}'::tsmultirange @&gt; '2011-01-10'::timestamp</literallayout>
+        </entry>
+        <entry><literal>t</literal></entry>
+       </row>
+
+       <row>
+        <entry> <literal>&lt;@</literal> </entry>
+        <entry>multirange is contained by</entry>
+        <entry>
+          <literallayout class="monospaced">'{[2,4)}'::int4multirange &lt;@ int4range(1,7)
+'{[2,4)}'::int4multirange &lt;@ '{[1,7)}'::int4multirange</literallayout>
+        </entry>
         <entry><literal>t</literal></entry>
        </row>
 
        <row>
         <entry> <literal>&lt;@</literal> </entry>
         <entry>range is contained by</entry>
-        <entry><literal>int4range(2,4) &lt;@ int4range(1,7)</literal></entry>
+        <entry>
+          <literallayout class="monospaced">int4range(2,4) &lt;@ int4range(1,7)
+int4range(2,4) &lt;@ '{[1,7)}'::int4multirange</literallayout>
+        </entry>
         <entry><literal>t</literal></entry>
        </row>
 
        <row>
         <entry> <literal>&lt;@</literal> </entry>
         <entry>element is contained by</entry>
-        <entry><literal>42 &lt;@ int4range(1,7)</literal></entry>
+        <entry>
+          <literallayout class="monospaced">42 &lt;@ int4range(1,7)
+42 &lt;@ '{[1,7)}'::int4multirange</literallayout>
+        </entry>
         <entry><literal>f</literal></entry>
        </row>
 
        <row>
         <entry> <literal>&amp;&amp;</literal> </entry>
         <entry>overlap (have points in common)</entry>
-        <entry><literal>int8range(3,7) &amp;&amp; int8range(4,12)</literal></entry>
+        <entry>
+          <literallayout class="monospaced">int8range(3,7) &amp;&amp; int8range(4,12)
+int8range(3,7) &amp;&amp; '{[4,12)}'::int8multirange
+'{[3,7)}'::int8multirange &amp;&amp; int8range(4,12)
+'{[3,7)}'::int8multirange &amp;&amp; '{[4,12)}'::int8multirange</literallayout>
+        </entry>
         <entry><literal>t</literal></entry>
        </row>
 
        <row>
         <entry> <literal>&lt;&lt;</literal> </entry>
         <entry>strictly left of</entry>
-        <entry><literal>int8range(1,10) &lt;&lt; int8range(100,110)</literal></entry>
+        <entry>
+          <literallayout class="monospaced">int8range(1,10) &lt;&lt; int8range(100,110)
+int8range(1,10) &lt;&lt; '{[100,110)}'::int8multirange
+'{[1,10)}'::int8multirange &lt;&lt; int8range(100,110)
+'{[1,10)}'::int8multirange &lt;&lt; '{[100,110)}'::int8multirange</literallayout>
+        </entry>
         <entry><literal>t</literal></entry>
        </row>
 
        <row>
         <entry> <literal>&gt;&gt;</literal> </entry>
         <entry>strictly right of</entry>
-        <entry><literal>int8range(50,60) &gt;&gt; int8range(20,30)</literal></entry>
+        <entry>
+          <literallayout class="monospaced">int8range(50,60) &gt;&gt; int8range(20,30)
+int8range(50,60) &gt;&gt; '{[20,30)}'::int8multirange
+'{[50,60)}'::int8multirange &gt;&gt; int8range(20,30)
+'{[50,60)}'::int8multirange &gt;&gt; '{[20,30)}'::int8multirange</literallayout>
+        </entry>
         <entry><literal>t</literal></entry>
        </row>
 
        <row>
         <entry> <literal>&amp;&lt;</literal> </entry>
         <entry>does not extend to the right of</entry>
-        <entry><literal>int8range(1,20) &amp;&lt; int8range(18,20)</literal></entry>
+        <entry>
+          <literallayout class="monospaced">int8range(1,20) &amp;&lt; int8range(18,20)
+int8range(1,20) &amp;&lt; '{[18,20)}'::int8multirange
+'{[1,20)}'::int8multirange &amp;&lt; int8range(18,20)
+'{[1,20)}'::int8multirange &amp;&lt; '{[18,20)}'::int8multirange</literallayout>
+        </entry>
         <entry><literal>t</literal></entry>
        </row>
 
        <row>
         <entry> <literal>&amp;&gt;</literal> </entry>
         <entry>does not extend to the left of</entry>
-        <entry><literal>int8range(7,20) &amp;&gt; int8range(5,10)</literal></entry>
+        <entry>
+          <literallayout class="monospaced">int8range(7,20) &amp;&gt; int8range(5,10)
+int8range(7,20) &amp;&gt; '{[5,10)}'::int8multirange
+'{[7,20)}'::int8multirange &amp;&gt; int8range(5,10)
+'{[7,20)}'::int8multirange &amp;&gt; '{[5,10)}'::int8multirange</literallayout>
+        </entry>
         <entry><literal>t</literal></entry>
        </row>
 
        <row>
         <entry> <literal>-|-</literal> </entry>
         <entry>is adjacent to</entry>
-        <entry><literal>numrange(1.1,2.2) -|- numrange(2.2,3.3)</literal></entry>
+        <entry>
+          <literallayout class="monospaced">numrange(1.1,2.2) -|- numrange(2.2,3.3)
+numrange(1.1,2.2) -|- '{[2.2,3.3)}'::nummultirange
+'{[1.1,2.2)}'::nummultirange -|- numrange(2.2,3.3)
+'{[1.1,2.2)}'::nummultirange -|- '{[2.2,3.3)}'::nummultirange</literallayout>
+        </entry>
         <entry><literal>t</literal></entry>
        </row>
 
        <row>
         <entry> <literal>+</literal> </entry>
         <entry>union</entry>
-        <entry><literal>numrange(5,15) + numrange(10,20)</literal></entry>
+        <entry><literallayout class="monospaced">numrange(5,15) + numrange(10,20)</literallayout></entry>
         <entry><literal>[5,20)</literal></entry>
        </row>
 
        <row>
+        <entry> <literal>@+</literal> </entry>
+        <entry>safe union</entry>
+        <entry>
+          <literallayout class="monospaced">numrange(5,10) @+ numrange(15,20)
+numrange(5,10) @+ '{[15,20)}'::nummultirange
+'{[5,10)}'::nummultirange @+ numrange(15,20)
+'{[5,10)}'::nummultirange @+ '{[15,20)}'::nummultirange</literallayout>
+        </entry>
+        <entry><literal>{[5,10), [15,20)}</literal></entry>
+       </row>
+
+       <row>
         <entry> <literal>*</literal> </entry>
         <entry>intersection</entry>
-        <entry><literal>int8range(5,15) * int8range(10,20)</literal></entry>
+        <entry><literallayout class="monospaced">int8range(5,15) * int8range(10,20)</literallayout></entry>
         <entry><literal>[10,15)</literal></entry>
        </row>
 
        <row>
+        <entry> <literal>@*</literal> </entry>
+        <entry>safe intersection</entry>
+        <entry>
+          <literallayout class="monospaced">int8range(5,15) @* int8range(10,20)
+int8range(5,15) @* '{[10,20)}'::int8multirange
+'{[5,15)}'::int8multirange @* int8range(10,20)
+'{[5,15)}'::int8multirange @* '{[10,20)}'::int8multirange</literallayout>
+        </entry>
+        <entry><literal>{[10,15)}</literal></entry>
+       </row>
+
+       <row>
         <entry> <literal>-</literal> </entry>
         <entry>difference</entry>
-        <entry><literal>int8range(5,15) - int8range(10,20)</literal></entry>
+        <entry><literallayout class="monospaced">int8range(5,15) - int8range(10,20)</literallayout></entry>
         <entry><literal>[5,10)</literal></entry>
        </row>
 
+       <row>
+        <entry> <literal>@-</literal> </entry>
+        <entry>safe difference</entry>
+        <entry>
+          <literallayout class="monospaced">int8range(5,20) @- int8range(10,15)
+int8range(5,20) @- '{[10,15)}'::int8multirange
+'{[5,20)}'::int8multirange @- int8range(10,15)
+'{[5,20)}'::int8multirange @- '{[10,15)}'::int8multirange</literallayout>
+        </entry>
+        <entry><literal>{[5,10), [15,20)}</literal></entry>
+       </row>
+
       </tbody>
      </tgroup>
     </table>
@@ -14729,19 +14842,28 @@ NULL baz</literallayout>(3 rows)</entry>
 
   <para>
    The left-of/right-of/adjacent operators always return false when an empty
-   range is involved; that is, an empty range is not considered to be either
-   before or after any other range.
+   range or multirange is involved; that is, an empty range is not considered to
+   be either before or after any other range.
+  </para>
+
+  <para>
+   Elsewhere empty ranges and multiranges are treated as the additive identity:
+   anything unioned with an empty value is itself. Anything minus an empty
+   value is itself. An empty multirange has exactly the same points as an empty
+   range. Every range contains the empty range. Every multirange contains as many
+   empty ranges as you like.
   </para>
 
   <para>
    The union and difference operators will fail if the resulting range would
    need to contain two disjoint sub-ranges, as such a range cannot be
-   represented.
+   represented. The safe versions of union, intersection, and difference
+   return multiranges, so they can handle gaps without failing.
   </para>
 
   <para>
    <xref linkend="range-functions-table"/> shows the functions
-   available for use with range types.
+   available for use with range and multirange types.
   </para>
 
   <indexterm>
@@ -14793,6 +14915,17 @@ NULL baz</literallayout>(3 rows)</entry>
        <row>
         <entry>
          <literal>
+          <function>lower</function>(<type>anymultirange</type>)
+         </literal>
+        </entry>
+        <entry>multirange's element type</entry>
+        <entry>lower bound of multirange</entry>
+        <entry><literal>lower('{[1.1,2.2)}'::nummultirange)</literal></entry>
+        <entry><literal>1.1</literal></entry>
+       </row>
+       <row>
+        <entry>
+         <literal>
           <function>upper</function>(<type>anyrange</type>)
          </literal>
         </entry>
@@ -14804,6 +14937,17 @@ NULL baz</literallayout>(3 rows)</entry>
        <row>
         <entry>
          <literal>
+          <function>upper</function>(<type>anymultirange</type>)
+         </literal>
+        </entry>
+        <entry>multirange's element type</entry>
+        <entry>upper bound of multirange</entry>
+        <entry><literal>upper('{[1.1,2.2)}'::nummultirange)</literal></entry>
+        <entry><literal>2.2</literal></entry>
+       </row>
+       <row>
+        <entry>
+         <literal>
           <function>isempty</function>(<type>anyrange</type>)
          </literal>
         </entry>
@@ -14815,6 +14959,17 @@ NULL baz</literallayout>(3 rows)</entry>
        <row>
         <entry>
          <literal>
+          <function>isempty</function>(<type>anymultirange</type>)
+         </literal>
+        </entry>
+        <entry><type>boolean</type></entry>
+        <entry>is the multirange empty?</entry>
+        <entry><literal>isempty('{[1.1,2.2)}'::nummultirange)</literal></entry>
+        <entry><literal>false</literal></entry>
+       </row>
+       <row>
+        <entry>
+         <literal>
           <function>lower_inc</function>(<type>anyrange</type>)
          </literal>
         </entry>
@@ -14826,6 +14981,17 @@ NULL baz</literallayout>(3 rows)</entry>
        <row>
         <entry>
          <literal>
+          <function>lower_inc</function>(<type>anymultirange</type>)
+         </literal>
+        </entry>
+        <entry><type>boolean</type></entry>
+        <entry>is the lower bound inclusive?</entry>
+        <entry><literal>lower_inc('{[1.1,2.2)}'::nummultirange)</literal></entry>
+        <entry><literal>true</literal></entry>
+       </row>
+       <row>
+        <entry>
+         <literal>
           <function>upper_inc</function>(<type>anyrange</type>)
          </literal>
         </entry>
@@ -14837,6 +15003,17 @@ NULL baz</literallayout>(3 rows)</entry>
        <row>
         <entry>
          <literal>
+          <function>upper_inc</function>(<type>anymultirange</type>)
+         </literal>
+        </entry>
+        <entry><type>boolean</type></entry>
+        <entry>is the upper bound inclusive?</entry>
+        <entry><literal>upper_inc('{[1.1,2.2)}'::nummultirange)</literal></entry>
+        <entry><literal>false</literal></entry>
+       </row>
+       <row>
+        <entry>
+         <literal>
           <function>lower_inf</function>(<type>anyrange</type>)
          </literal>
         </entry>
@@ -14848,6 +15025,17 @@ NULL baz</literallayout>(3 rows)</entry>
        <row>
         <entry>
          <literal>
+          <function>lower_inf</function>(<type>anymultirange</type>)
+         </literal>
+        </entry>
+        <entry><type>boolean</type></entry>
+        <entry>is the lower bound infinite?</entry>
+        <entry><literal>lower_inf('{(,)}'::datemultirange)</literal></entry>
+        <entry><literal>true</literal></entry>
+       </row>
+       <row>
+        <entry>
+         <literal>
           <function>upper_inf</function>(<type>anyrange</type>)
          </literal>
         </entry>
@@ -14859,6 +15047,17 @@ NULL baz</literallayout>(3 rows)</entry>
        <row>
         <entry>
          <literal>
+          <function>upper_inf</function>(<type>anymultirange</type>)
+         </literal>
+        </entry>
+        <entry><type>boolean</type></entry>
+        <entry>is the upper bound infinite?</entry>
+        <entry><literal>upper_inf('{(,)}'::datemultirange)</literal></entry>
+        <entry><literal>true</literal></entry>
+       </row>
+       <row>
+        <entry>
+         <literal>
           <function>range_merge</function>(<type>anyrange</type>, <type>anyrange</type>)
          </literal>
         </entry>
@@ -14867,16 +15066,27 @@ NULL baz</literallayout>(3 rows)</entry>
         <entry><literal>range_merge('[1,2)'::int4range, '[3,4)'::int4range)</literal></entry>
         <entry><literal>[1,4)</literal></entry>
        </row>
+       <row>
+        <entry>
+         <literal>
+          <function>range_merge</function>(<type>anymultirange</type>)
+         </literal>
+        </entry>
+        <entry><type>anyrange</type></entry>
+        <entry>the smallest range which includes the entire multirange</entry>
+        <entry><literal>range_merge('{[1,2), [3,4)}'::int4multirange)</literal></entry>
+        <entry><literal>[1,4)</literal></entry>
+       </row>
       </tbody>
      </tgroup>
     </table>
 
   <para>
    The <function>lower</function> and  <function>upper</function> functions return null
-   if the range is empty or the requested bound is infinite.
+   if the input range/multirange is empty or the requested bound is infinite.
    The <function>lower_inc</function>, <function>upper_inc</function>,
    <function>lower_inf</function>, and <function>upper_inf</function>
-   functions all return false for an empty range.
+   functions all return false for an empty range or multirange.
   </para>
   </sect1>
 
@@ -15198,6 +15408,44 @@ NULL baz</literallayout>(3 rows)</entry>
      <row>
       <entry>
        <indexterm>
+        <primary>range_agg</primary>
+       </indexterm>
+       <function>
+         range_agg(<replaceable class="parameter">expression</replaceable>)
+       </function>
+      </entry>
+      <entry>
+       <type>anyrange</type>
+      </entry>
+      <entry>
+       <type>multirange</type> of the argument range type
+      </entry>
+      <entry>Yes</entry>
+      <entry>union of the non-null input values</entry>
+     </row>
+
+     <row>
+      <entry>
+       <indexterm>
+        <primary>range_intersect_agg</primary>
+       </indexterm>
+       <function>
+         range_intersect_agg(<replaceable class="parameter">expression</replaceable>)
+       </function>
+      </entry>
+      <entry>
+       <type>anyrange</type>
+      </entry>
+      <entry>
+       <type>multirange</type> of the argument range type
+      </entry>
+      <entry>No</entry>
+      <entry>intersection of the non-null input values</entry>
+     </row>
+
+     <row>
+      <entry>
+       <indexterm>
         <primary>string_agg</primary>
        </indexterm>
        <function>
diff --git a/doc/src/sgml/rangetypes.sgml b/doc/src/sgml/rangetypes.sgml
index 3a034d9b06..3e37b352fc 100644
--- a/doc/src/sgml/rangetypes.sgml
+++ b/doc/src/sgml/rangetypes.sgml
@@ -27,6 +27,13 @@
   ranges from an instrument, and so forth can also be useful.
  </para>
 
+ <para>
+  Every range type has a corresponding multirange type. A multirange is
+  an ordered list of non-continguous, non-empty, non-null ranges. Most
+  range operators also work on multiranges, and they have a few functions
+  of their own.
+ </para>
+
  <sect2 id="rangetypes-builtin">
   <title>Built-in Range Types</title>
 
@@ -235,10 +242,30 @@ SELECT '[4,4]'::int4range;
 SELECT '[4,4)'::int4range;
 </programlisting>
   </para>
+
+  <para>
+   The input for a multirange is curly brackets (<literal>{</literal> and
+   <literal>}</literal>) containing zero or more valid ranges,
+   separated by commas. Whitespace is permitted around the brackets and
+   commas. This is intended to be reminiscent of array syntax, although
+   multiranges are much simpler: they have just one dimension and there is
+   no need to quote their contents. (The bounds of their ranges may be
+   quoted as above however.)
+  </para>
+
+  <para>
+  Examples:
+<programlisting>
+SELECT '{}'::int4multirange;
+SELECT '{[3,7)}'::int4multirange;
+SELECT '{[3,7), [8,9)}'::int4multirange;
+</programlisting>
+  </para>
+
  </sect2>
 
  <sect2 id="rangetypes-construct">
-  <title>Constructing Ranges</title>
+  <title>Constructing Ranges and Multiranges</title>
 
   <para>
    Each range type has a constructor function with the same name as the range
@@ -272,6 +299,19 @@ SELECT int8range(1, 14, '(]');
 SELECT numrange(NULL, 2.2);
 </programlisting>
   </para>
+
+  <para>
+   Each range type also has a multirange constructor with the same name as the
+   multirange type.  The constructor function takes zero or more arguments
+   which are all ranges of the appropriate type.
+   For example:
+
+<programlisting>
+SELECT nummultirange();
+SELECT nummultirange(numrange(1.0, 14.0));
+SELECT nummultirange(numrange(1.0, 14.0), numrange(20.0, 25.0));
+</programlisting>
+  </para>
  </sect2>
 
  <sect2 id="rangetypes-discrete">
@@ -345,6 +385,11 @@ SELECT '[1.234, 5.678]'::floatrange;
   </para>
 
   <para>
+   When you define your own range your automatically get a corresponding
+   multirange type.
+  </para>
+
+  <para>
    Defining your own range type also allows you to specify a different
    subtype B-tree operator class or collation to use, so as to change the sort
    ordering that determines which values fall into a given range.
-- 
2.11.0

v4-0002-multirange-operators-and-functions.patchapplication/octet-stream; name=v4-0002-multirange-operators-and-functions.patchDownload
From b51668e851bcdf402c0ac1945d51a8489c580457 Mon Sep 17 00:00:00 2001
From: "Paul A. Jungwirth" <pj@illuminatedcomputing.com>
Date: Sat, 21 Sep 2019 21:28:20 -0700
Subject: [PATCH v4 2/4] multirange operators and functions

---
 src/backend/utils/adt/multirangetypes.c       | 1422 ++++++++++++++-
 src/backend/utils/adt/rangetypes.c            |  238 ++-
 src/include/catalog/pg_aggregate.dat          |   11 +
 src/include/catalog/pg_amproc.dat             |    5 +-
 src/include/catalog/pg_operator.dat           |  169 ++
 src/include/catalog/pg_proc.dat               |  185 ++
 src/include/utils/multirangetypes.h           |   34 +
 src/include/utils/rangetypes.h                |   31 +-
 src/test/regress/expected/dependency.out      |    1 +
 src/test/regress/expected/multirangetypes.out | 2397 +++++++++++++++++++++++++
 src/test/regress/expected/opr_sanity.out      |    4 +-
 src/test/regress/expected/rangetypes.out      |   38 +-
 src/test/regress/expected/sanity_check.out    |    2 +
 src/test/regress/sql/multirangetypes.sql      |  607 +++++++
 src/test/regress/sql/rangetypes.sql           |   13 +
 15 files changed, 5048 insertions(+), 109 deletions(-)

diff --git a/src/backend/utils/adt/multirangetypes.c b/src/backend/utils/adt/multirangetypes.c
index 5323a6a635..c509994796 100644
--- a/src/backend/utils/adt/multirangetypes.c
+++ b/src/backend/utils/adt/multirangetypes.c
@@ -726,45 +726,1267 @@ multirange_constructor0(PG_FUNCTION_ARGS)
 }
 
 
+/* multirange, multirange -> multirange type functions */
+
+/* multirange union */
+Datum
+range_union_range(PG_FUNCTION_ARGS)
+{
+	RangeType  *r1 = PG_GETARG_RANGE_P(0);
+	RangeType  *r2 = PG_GETARG_RANGE_P(1);
+	Oid			mltrngtypoid = get_fn_expr_rettype(fcinfo->flinfo);
+	TypeCacheEntry *typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
+	MultirangeType *mr = make_multirange(mltrngtypoid, typcache->rngtype, 1, &r2);
+
+	PG_RETURN_MULTIRANGE_P(range_union_multirange_internal(typcache, r1, mr));
+}
+
+Datum
+range_union_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_MULTIRANGE_P(range_union_multirange_internal(typcache, r, mr));
+}
+
+Datum
+multirange_union_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_MULTIRANGE_P(range_union_multirange_internal(typcache, r, mr));
+}
+
+Datum
+multirange_union_multirange(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
+	MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count1;
+	int32		range_count2;
+	int32		range_count3;
+	RangeType **ranges1;
+	RangeType **ranges2;
+	RangeType **ranges3;
+
+	if (MultirangeIsEmpty(mr1))
+		PG_RETURN_MULTIRANGE_P(mr2);
+	if (MultirangeIsEmpty(mr2))
+		PG_RETURN_MULTIRANGE_P(mr1);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr1));
+
+	multirange_deserialize(mr1, &range_count1, &ranges1);
+	multirange_deserialize(mr2, &range_count2, &ranges2);
+
+	range_count3 = range_count1 + range_count2;
+	ranges3 = palloc0(range_count3 * sizeof(RangeType *));
+	memcpy(ranges3, ranges1, range_count1 * sizeof(RangeType *));
+	memcpy(ranges3 + range_count1, ranges2, range_count2 * sizeof(RangeType *));
+	PG_RETURN_MULTIRANGE_P(make_multirange(typcache->type_id, typcache->rngtype,
+										   range_count3, ranges3));
+}
+
+MultirangeType *
+range_union_multirange_internal(TypeCacheEntry *typcache, RangeType *r,
+								MultirangeType * mr)
+{
+	int32		range_count;
+	RangeType **ranges1;
+	RangeType **ranges2;
+
+	if (RangeIsEmpty(r))
+		return mr;
+	if (MultirangeIsEmpty(mr))
+		return make_multirange(typcache->type_id, typcache->rngtype, 1, &r);
+
+	multirange_deserialize(mr, &range_count, &ranges1);
+
+	ranges2 = palloc0((range_count + 1) * sizeof(RangeType *));
+
+	memcpy(ranges2, ranges1, range_count * sizeof(RangeType *));
+	ranges2[range_count] = r;
+	return make_multirange(typcache->type_id, typcache->rngtype, range_count + 1, ranges2);
+}
+
+/* multirange minus */
+Datum
+range_minus_range(PG_FUNCTION_ARGS)
+{
+	RangeType  *r1 = PG_GETARG_RANGE_P(0);
+	RangeType  *r2 = PG_GETARG_RANGE_P(1);
+	Oid			mltrngtypoid = get_fn_expr_rettype(fcinfo->flinfo);
+	TypeCacheEntry *typcache;
+	TypeCacheEntry *rangetyp;
+
+	typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
+	rangetyp = typcache->rngtype;
+
+	if (RangeIsEmpty(r1) || RangeIsEmpty(r2))
+		PG_RETURN_MULTIRANGE_P(make_multirange(mltrngtypoid, rangetyp, 1, &r1));
+
+	PG_RETURN_MULTIRANGE_P(multirange_minus_multirange_internal(mltrngtypoid,
+																rangetyp,
+																1,
+																&r1,
+																1,
+																&r2));
+}
+
+Datum
+range_minus_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	Oid			mltrngtypoid = MultirangeTypeGetOid(mr);
+	TypeCacheEntry *typcache;
+	TypeCacheEntry *rangetyp;
+	int32		range_count;
+	RangeType **ranges;
+
+	typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
+	rangetyp = typcache->rngtype;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		PG_RETURN_MULTIRANGE_P(make_multirange(mltrngtypoid, rangetyp, 1, &r));
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_MULTIRANGE_P(multirange_minus_multirange_internal(mltrngtypoid,
+																rangetyp,
+																1,
+																&r,
+																range_count,
+																ranges));
+}
+
+Datum
+multirange_minus_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	Oid			mltrngtypoid = MultirangeTypeGetOid(mr);
+	TypeCacheEntry *typcache;
+	TypeCacheEntry *rangetyp;
+	int32		range_count;
+	RangeType **ranges;
+
+	typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
+	rangetyp = typcache->rngtype;
+
+	if (MultirangeIsEmpty(mr) || RangeIsEmpty(r))
+		PG_RETURN_MULTIRANGE_P(mr);
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_MULTIRANGE_P(multirange_minus_multirange_internal(mltrngtypoid,
+																rangetyp,
+																range_count,
+																ranges,
+																1,
+																&r));
+}
+
+Datum
+multirange_minus_multirange(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
+	MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
+	Oid			mltrngtypoid = MultirangeTypeGetOid(mr1);
+	TypeCacheEntry *typcache;
+	TypeCacheEntry *rangetyp;
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+
+	typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
+	rangetyp = typcache->rngtype;
+
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		PG_RETURN_MULTIRANGE_P(mr1);
+
+	multirange_deserialize(mr1, &range_count1, &ranges1);
+	multirange_deserialize(mr2, &range_count2, &ranges2);
+
+	PG_RETURN_MULTIRANGE_P(multirange_minus_multirange_internal(mltrngtypoid,
+																rangetyp,
+																range_count1,
+																ranges1,
+																range_count2,
+																ranges2));
+}
+
+MultirangeType *
+multirange_minus_multirange_internal(Oid mltrngtypoid, TypeCacheEntry *rangetyp,
+									 int32 range_count1, RangeType **ranges1, int32 range_count2, RangeType **ranges2)
+{
+	RangeType  *r1;
+	RangeType  *r2;
+	RangeType **ranges3;
+	int32		range_count3;
+	int32		i1;
+	int32		i2;
+
+	/*
+	 * Worst case: every range in ranges1 makes a different cut to some range
+	 * in ranges2.
+	 */
+	ranges3 = palloc0((range_count1 + range_count2) * sizeof(RangeType *));
+	range_count3 = 0;
+
+	/*
+	 * For each range in mr1, keep subtracting until it's gone or the ranges
+	 * in mr2 have passed it. After a subtraction we assign what's left back
+	 * to r1. The parallel progress through mr1 and mr2 is similar to
+	 * multirange_overlaps_multirange_internal.
+	 */
+	r2 = ranges2[0];
+	for (i1 = 0, i2 = 0; i1 < range_count1; i1++)
+	{
+		r1 = ranges1[i1];
+
+		/* Discard r2s while r2 << r1 */
+		while (r2 != NULL && range_before_internal(rangetyp, r2, r1))
+		{
+			r2 = ++i2 >= range_count2 ? NULL : ranges2[i2];
+		}
+
+		while (r2 != NULL)
+		{
+			if (range_split_internal(rangetyp, r1, r2, &ranges3[range_count3], &r1))
+			{
+				/*
+				 * If r2 takes a bite out of the middle of r1, we need two
+				 * outputs
+				 */
+				range_count3++;
+				r2 = ++i2 >= range_count2 ? NULL : ranges2[i2];
+
+			}
+			else if (range_overlaps_internal(rangetyp, r1, r2))
+			{
+				/*
+				 * If r2 overlaps r1, replace r1 with r1 - r2.
+				 */
+				r1 = range_minus_internal(rangetyp, r1, r2);
+
+				/*
+				 * If r2 goes past r1, then we need to stay with it, in case
+				 * it hits future r1s. Otherwise we need to keep r1, in case
+				 * future r2s hit it. Since we already subtracted, there's no
+				 * point in using the overright/overleft calls.
+				 */
+				if (RangeIsEmpty(r1) || range_before_internal(rangetyp, r1, r2))
+					break;
+				else
+					r2 = ++i2 >= range_count2 ? NULL : ranges2[i2];
+
+			}
+			else
+			{
+				/*
+				 * This and all future r2s are past r1, so keep them. Also
+				 * assign whatever is left of r1 to the result.
+				 */
+				break;
+			}
+		}
+
+		/*
+		 * Nothing else can remove anything from r1, so keep it. Even if r1 is
+		 * empty here, make_multirange will remove it.
+		 */
+		ranges3[range_count3++] = r1;
+	}
+
+	return make_multirange(mltrngtypoid, rangetyp, range_count3, ranges3);
+}
+
+/* multirange intersection */
+Datum
+range_intersect_range(PG_FUNCTION_ARGS)
+{
+	RangeType  *r1 = PG_GETARG_RANGE_P(0);
+	RangeType  *r2 = PG_GETARG_RANGE_P(1);
+	Oid			mltrngtypoid = get_fn_expr_rettype(fcinfo->flinfo);
+	TypeCacheEntry *typcache;
+	TypeCacheEntry *rangetyp;
+
+	typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
+	rangetyp = typcache->rngtype;
+
+	if (RangeIsEmpty(r1) || RangeIsEmpty(r2))
+		PG_RETURN_MULTIRANGE_P(make_empty_multirange(mltrngtypoid, rangetyp));
+
+	PG_RETURN_MULTIRANGE_P(multirange_intersect_multirange_internal(mltrngtypoid,
+																	rangetyp,
+																	1,
+																	&r1,
+																	1,
+																	&r2));
+}
+
+Datum
+range_intersect_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r1 = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
+	Oid			mltrngtypoid = MultirangeTypeGetOid(mr2);
+	TypeCacheEntry *typcache;
+	TypeCacheEntry *rangetyp;
+	int32		range_count2;
+	RangeType **ranges2;
+
+	typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
+	rangetyp = typcache->rngtype;
+
+	if (RangeIsEmpty(r1) || MultirangeIsEmpty(mr2))
+		PG_RETURN_MULTIRANGE_P(make_empty_multirange(mltrngtypoid, rangetyp));
+
+	multirange_deserialize(mr2, &range_count2, &ranges2);
+
+	PG_RETURN_MULTIRANGE_P(multirange_intersect_multirange_internal(mltrngtypoid,
+																	rangetyp,
+																	1,
+																	&r1,
+																	range_count2,
+																	ranges2));
+}
+
+Datum
+multirange_intersect_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r2 = PG_GETARG_RANGE_P(1);
+	Oid			mltrngtypoid = MultirangeTypeGetOid(mr1);
+	TypeCacheEntry *typcache;
+	TypeCacheEntry *rangetyp;
+	int32		range_count1;
+	RangeType **ranges1;
+
+	typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
+	rangetyp = typcache->rngtype;
+
+	if (MultirangeIsEmpty(mr1) || RangeIsEmpty(r2))
+		PG_RETURN_MULTIRANGE_P(make_empty_multirange(mltrngtypoid, rangetyp));
+
+	multirange_deserialize(mr1, &range_count1, &ranges1);
+
+	PG_RETURN_MULTIRANGE_P(multirange_intersect_multirange_internal(mltrngtypoid,
+																	rangetyp,
+																	range_count1,
+																	ranges1,
+																	1,
+																	&r2));
+}
+
+Datum
+multirange_intersect_multirange(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
+	MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
+	Oid			mltrngtypoid = MultirangeTypeGetOid(mr1);
+	TypeCacheEntry *typcache;
+	TypeCacheEntry *rangetyp;
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+
+	typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
+	rangetyp = typcache->rngtype;
+
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		PG_RETURN_MULTIRANGE_P(make_empty_multirange(mltrngtypoid, rangetyp));
+
+	multirange_deserialize(mr1, &range_count1, &ranges1);
+	multirange_deserialize(mr2, &range_count2, &ranges2);
+
+	PG_RETURN_MULTIRANGE_P(multirange_intersect_multirange_internal(mltrngtypoid,
+																	rangetyp,
+																	range_count1,
+																	ranges1,
+																	range_count2,
+																	ranges2));
+}
+
+MultirangeType *
+multirange_intersect_multirange_internal(Oid mltrngtypoid, TypeCacheEntry *rangetyp,
+										 int32 range_count1, RangeType **ranges1, int32 range_count2, RangeType **ranges2)
+{
+	RangeType  *r1;
+	RangeType  *r2;
+	RangeType **ranges3;
+	int32		range_count3;
+	int32		i1;
+	int32		i2;
+
+	/*
+	 * Worst case is a stitching pattern like this:
+	 *
+	 * mr1: --- --- --- ---
+	 * mr2:   --- --- ---
+	 * mr3:   - - - - - -
+	 *
+	 * That seems to be range_count1 + range_count2 - 1, but one extra won't
+	 * hurt.
+	 */
+	ranges3 = palloc0((range_count1 + range_count2) * sizeof(RangeType *));
+	range_count3 = 0;
+
+	/*
+	 * For each range in mr1, keep intersecting until the ranges in mr2 have
+	 * passed it. The parallel progress through mr1 and mr2 is similar to
+	 * multirange_minus_multirange_internal, but we don't have to assign back
+	 * to r1.
+	 */
+	r2 = ranges2[0];
+	for (i1 = 0, i2 = 0; i1 < range_count1; i1++)
+	{
+		r1 = ranges1[i1];
+
+		/* Discard r2s while r2 << r1 */
+		while (r2 != NULL && range_before_internal(rangetyp, r2, r1))
+		{
+			r2 = ++i2 >= range_count2 ? NULL : ranges2[i2];
+		}
+
+		while (r2 != NULL)
+		{
+			if (range_overlaps_internal(rangetyp, r1, r2))
+			{
+				/* Keep the overlapping part */
+				ranges3[range_count3++] = range_intersect_internal(rangetyp, r1, r2);
+
+				/* If we "used up" all of r2, go to the next one... */
+				if (range_overleft_internal(rangetyp, r2, r1))
+					r2 = ++i2 >= range_count2 ? NULL : ranges2[i2];
+
+				/* ...otherwise go to the next r1 */
+				else
+					break;
+			}
+			else
+				/* We're past r1, so move to the next one */
+				break;
+		}
+
+		/* If we're out of r2s, there can be no more intersections */
+		if (r2 == NULL)
+			break;
+	}
+
+	return make_multirange(mltrngtypoid, rangetyp, range_count3, ranges3);
+}
+
+/*
+ * range_agg_transfn: combine adjacent/overlapping ranges.
+ *
+ * All we do here is gather the input ranges into an array
+ * so that the finalfn can sort and combine them.
+ */
+Datum
+range_agg_transfn(PG_FUNCTION_ARGS)
+{
+	MemoryContext aggContext;
+	Oid			rngtypoid;
+	ArrayBuildState *state;
+
+	if (!AggCheckCallContext(fcinfo, &aggContext))
+		elog(ERROR, "range_agg_transfn called in non-aggregate context");
+
+	rngtypoid = get_fn_expr_argtype(fcinfo->flinfo, 1);
+	if (!type_is_range(rngtypoid))
+		ereport(ERROR, (errmsg("range_agg must be called with a range")));
+
+	if (PG_ARGISNULL(0))
+		state = initArrayResult(rngtypoid, aggContext, false);
+	else
+		state = (ArrayBuildState *) PG_GETARG_POINTER(0);
+
+	/* skip NULLs */
+	if (!PG_ARGISNULL(1))
+		accumArrayResult(state, PG_GETARG_DATUM(1), false, rngtypoid, aggContext);
+
+	PG_RETURN_POINTER(state);
+}
+
+/*
+ * range_agg_finalfn: use our internal array to merge touching ranges.
+ */
+Datum
+range_agg_finalfn(PG_FUNCTION_ARGS)
+{
+	MemoryContext aggContext;
+	Oid			mltrngtypoid;
+	TypeCacheEntry *typcache;
+	ArrayBuildState *state;
+	int32		range_count;
+	RangeType **ranges;
+	int			i;
+
+	if (!AggCheckCallContext(fcinfo, &aggContext))
+		elog(ERROR, "range_agg_finalfn called in non-aggregate context");
+
+	state = PG_ARGISNULL(0) ? NULL : (ArrayBuildState *) PG_GETARG_POINTER(0);
+	if (state == NULL)
+		/* This shouldn't be possible, but just in case.... */
+		PG_RETURN_NULL();
+
+	/* Also return NULL if we had zero inputs, like other aggregates */
+	range_count = state->nelems;
+	if (range_count == 0)
+		PG_RETURN_NULL();
+
+	mltrngtypoid = get_fn_expr_rettype(fcinfo->flinfo);
+	typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
+
+	ranges = palloc0(range_count * sizeof(RangeType *));
+	for (i = 0; i < range_count; i++)
+		ranges[i] = DatumGetRangeTypeP(state->dvalues[i]);
+
+	PG_RETURN_MULTIRANGE_P(make_multirange(mltrngtypoid, typcache->rngtype, range_count, ranges));
+}
+
+Datum
+multirange_intersect_agg_transfn(PG_FUNCTION_ARGS)
+{
+	MemoryContext aggContext;
+	Oid			mltrngtypoid;
+	TypeCacheEntry *typcache;
+	MultirangeType *result;
+	MultirangeType *current;
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+
+	if (!AggCheckCallContext(fcinfo, &aggContext))
+		elog(ERROR, "multirange_intersect_agg_transfn called in non-aggregate context");
+
+	mltrngtypoid = get_fn_expr_argtype(fcinfo->flinfo, 1);
+	if (!type_is_multirange(mltrngtypoid))
+		ereport(ERROR, (errmsg("range_intersect_agg must be called with a multirange")));
+
+	typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
+
+	/* strictness ensures these are non-null */
+	result = PG_GETARG_MULTIRANGE_P(0);
+	current = PG_GETARG_MULTIRANGE_P(1);
+
+	multirange_deserialize(result, &range_count1, &ranges1);
+	multirange_deserialize(current, &range_count2, &ranges2);
+
+	result = multirange_intersect_multirange_internal(mltrngtypoid,
+													  typcache->rngtype,
+													  range_count1,
+													  ranges1,
+													  range_count2,
+													  ranges2);
+	PG_RETURN_RANGE_P(result);
+}
+
+
+/* multirange -> element type functions */
+
+/* extract lower bound value */
+Datum
+multirange_lower(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+	bool		isnull;
+	Datum		result;
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_NULL();
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	result = range_lower_internal(typcache->rngtype, ranges[0], &isnull);
+
+	if (isnull)
+		PG_RETURN_NULL();
+	else
+		PG_RETURN_DATUM(result);
+}
+
+/* extract upper bound value */
+Datum
+multirange_upper(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+	bool		isnull;
+	Datum		result;
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_NULL();
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	result = range_upper_internal(typcache->rngtype, ranges[range_count - 1], &isnull);
+
+	if (isnull)
+		PG_RETURN_NULL();
+	else
+		PG_RETURN_DATUM(result);
+}
+
+
+/* multirange -> bool functions */
+
+/* is multirange empty? */
+Datum
+multirange_empty(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+
+	PG_RETURN_BOOL(mr->rangeCount == 0);
+}
+
+/* is lower bound inclusive? */
+Datum
+multirange_lower_inc(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	int32		range_count;
+	RangeType **ranges;
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_has_flag(ranges[0], RANGE_LB_INC));
+}
+
+/* is upper bound inclusive? */
+Datum
+multirange_upper_inc(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	int32		range_count;
+	RangeType **ranges;
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_has_flag(ranges[range_count - 1], RANGE_UB_INC));
+}
+
+/* is lower bound infinite? */
+Datum
+multirange_lower_inf(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	int32		range_count;
+	RangeType **ranges;
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_has_flag(ranges[0], RANGE_LB_INF));
+}
+
+/* is upper bound infinite? */
+Datum
+multirange_upper_inf(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	int32		range_count;
+	RangeType **ranges;
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_has_flag(ranges[range_count - 1], RANGE_UB_INF));
+}
+
+
+
+/* multirange, element -> bool functions */
+
+/* contains? */
+Datum
+multirange_contains_elem(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	Datum		val = PG_GETARG_DATUM(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(multirange_contains_elem_internal(typcache, mr, val));
+}
+
+/* contained by? */
+Datum
+elem_contained_by_multirange(PG_FUNCTION_ARGS)
+{
+	Datum		val = PG_GETARG_DATUM(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(multirange_contains_elem_internal(typcache, mr, val));
+}
+
+/*
+ * Test whether multirange mr contains a specific element value.
+ */
+bool
+multirange_contains_elem_internal(TypeCacheEntry *typcache, MultirangeType * mr, Datum val)
+{
+	TypeCacheEntry *rangetyp;
+	int32		range_count;
+	RangeType **ranges;
+	RangeType  *r;
+	int			i;
+
+	rangetyp = typcache->rngtype;
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	for (i = 0; i < range_count; i++)
+	{
+		r = ranges[i];
+		if (range_contains_elem_internal(rangetyp, r, val))
+			return true;
+	}
+
+	return false;
+}
+
+/* multirange, range -> bool functions */
+
+/* contains? */
+Datum
+multirange_contains_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(multirange_contains_range_internal(typcache, mr, r));
+}
+
+/* contained by? */
+Datum
+range_contained_by_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(multirange_contains_range_internal(typcache, mr, r));
+}
+
+/*
+ * Test whether multirange mr contains a specific range r.
+ */
+bool
+multirange_contains_range_internal(TypeCacheEntry *typcache, MultirangeType * mr, RangeType *r)
+{
+	TypeCacheEntry *rangetyp;
+	int32		range_count;
+	RangeType **ranges;
+	RangeType  *mrr;
+	int			i;
+
+	rangetyp = typcache->rngtype;
+
+	/*
+	 * Every multirange contains an infinite number of empty ranges, even an
+	 * empty one.
+	 */
+	if (RangeIsEmpty(r))
+		return true;
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	for (i = 0; i < range_count; i++)
+	{
+		mrr = ranges[i];
+		if (range_contains_internal(rangetyp, mrr, r))
+			return true;
+	}
+
+	return false;
+}
+
+
 /* multirange, multirange -> bool functions */
 
-/* equality (internal version) */
+/* 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));
+}
+
+/* overlaps? */
+Datum
+range_overlaps_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(range_overlaps_multirange_internal(typcache, r, mr));
+}
+
+Datum
+multirange_overlaps_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(range_overlaps_multirange_internal(typcache, r, mr));
+}
+
+Datum
+multirange_overlaps_multirange(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_overlaps_multirange_internal(typcache, mr1, mr2));
+}
+
+bool
+range_overlaps_multirange_internal(TypeCacheEntry *typcache, RangeType *r, MultirangeType * mr)
+{
+	TypeCacheEntry *rangetyp;
+	int32		range_count;
+	int32		i;
+	RangeType **ranges;
+	RangeType  *mrr;
+
+	/*
+	 * Empties never overlap, even with empties. (This seems strange since
+	 * they *do* contain each other, but we want to follow how ranges work.)
+	 */
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		return false;
+
+	rangetyp = typcache->rngtype;
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	/* Scan through mrr and once it gets to r they either overlap or not */
+	mrr = ranges[0];
+
+	/* Discard mrrs while mrr << r */
+	i = 0;
+	while (range_before_internal(rangetyp, mrr, r))
+	{
+		if (++i >= range_count)
+			return false;
+		mrr = ranges[i];
+	}
+
+	/* Now either we overlap or we passed r */
+	return range_overlaps_internal(rangetyp, mrr, r);
+}
+
+bool
+multirange_overlaps_multirange_internal(TypeCacheEntry *typcache, MultirangeType * mr1,
+										MultirangeType * mr2)
+{
+	TypeCacheEntry *rangetyp;
+	int32		range_count1;
+	int32		range_count2;
+	int32		i1;
+	int32		i2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+	RangeType  *r1;
+	RangeType  *r2;
+
+	/*
+	 * Empties never overlap, even with empties. (This seems strange since
+	 * they *do* contain each other, but we want to follow how ranges work.)
+	 */
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		return false;
+
+	rangetyp = typcache->rngtype;
+
+	multirange_deserialize(mr1, &range_count1, &ranges1);
+	multirange_deserialize(mr2, &range_count2, &ranges2);
+
+	/*
+	 * Every range in mr1 gets a chance to overlap with the ranges in mr2, but
+	 * we can use their ordering to avoid O(n^2). This is similar to
+	 * range_overlaps_multirange where r1 : r2 :: mrr : r, but there if we
+	 * don't find an overlap with r we're done, and here if we don't find an
+	 * overlap with r2 we try the next r2.
+	 */
+	r1 = ranges1[0];
+	for (i1 = 0, i2 = 0; i2 < range_count2; i2++)
+	{
+		r2 = ranges2[i2];
+
+		/* Discard r1s while r1 << r2 */
+		while (range_before_internal(rangetyp, r1, r2))
+		{
+			if (++i1 >= range_count1)
+				return false;
+			r1 = ranges1[i1];
+		}
+
+		/*
+		 * If r1 && r2, we're done, otherwise we failed to find an overlap for
+		 * r2, so go to the next one.
+		 */
+		if (range_overlaps_internal(rangetyp, r1, r2))
+			return true;
+	}
+
+	/* We looked through all of mr2 without finding an overlap */
+	return false;
+}
+
+/* does not extend to right of? */
+Datum
+range_overleft_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_overleft_internal(typcache->rngtype, r, ranges[range_count - 1]));
+}
+
+Datum
+multirange_overleft_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_overleft_internal(typcache->rngtype, ranges[range_count - 1], r));
+}
+
+Datum
+multirange_overleft_multirange(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
+	MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr1));
+
+	multirange_deserialize(mr1, &range_count1, &ranges1);
+	multirange_deserialize(mr2, &range_count2, &ranges2);
+
+	PG_RETURN_BOOL(range_overleft_internal(typcache->rngtype, ranges1[range_count1 - 1], ranges2[range_count2 - 1]));
+}
+
+/* does not extend to left of? */
+Datum
+range_overright_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_overright_internal(typcache->rngtype, r, ranges[0]));
+}
+
+Datum
+multirange_overright_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_overright_internal(typcache->rngtype, ranges[0], r));
+}
+
+Datum
+multirange_overright_multirange(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
+	MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr1));
+
+	multirange_deserialize(mr1, &range_count1, &ranges1);
+	multirange_deserialize(mr2, &range_count2, &ranges2);
+
+	PG_RETURN_BOOL(range_overright_internal(typcache->rngtype, ranges1[0], ranges2[0]));
+}
+
+/* contains? */
+Datum
+multirange_contains_multirange(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_contains_multirange_internal(typcache, mr1, mr2));
+}
+
+/* contained by? */
+Datum
+multirange_contained_by_multirange(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_contains_multirange_internal(typcache, mr2, mr1));
+}
+
+/*
+ * Test whether multirange mr1 contains every range from another multirange mr2.
+ */
 bool
-multirange_eq_internal(TypeCacheEntry *typcache, MultirangeType * mr1, MultirangeType * mr2)
+multirange_contains_multirange_internal(TypeCacheEntry *typcache,
+										MultirangeType * mr1, MultirangeType * mr2)
 {
-	int32		range_count_1;
-	int32		range_count_2;
-	int32		i;
+	TypeCacheEntry *rangetyp;
+	int32		range_count1;
+	int32		range_count2;
 	RangeType **ranges1;
 	RangeType **ranges2;
 	RangeType  *r1;
 	RangeType  *r2;
+	int			i1,
+				i2;
 
-	/* Different types should be prevented by ANYMULTIRANGE matching rules */
-	if (MultirangeTypeGetOid(mr1) != MultirangeTypeGetOid(mr2))
-		elog(ERROR, "multirange types do not match");
+	rangetyp = typcache->rngtype;
 
-	multirange_deserialize(mr1, &range_count_1, &ranges1);
-	multirange_deserialize(mr2, &range_count_2, &ranges2);
+	multirange_deserialize(mr1, &range_count1, &ranges1);
+	multirange_deserialize(mr2, &range_count2, &ranges2);
 
-	if (range_count_1 != range_count_2)
+	/*
+	 * We follow the same logic for empties as ranges: - an empty multirange
+	 * contains an empty range/multirange. - an empty multirange can't contain
+	 * any other range/multirange. - an empty multirange is contained by any
+	 * other range/multirange.
+	 */
+
+	if (range_count2 == 0)
+		return true;
+	if (range_count1 == 0)
 		return false;
 
-	for (i = 0; i < range_count_1; i++)
+	/*
+	 * Every range in mr2 must be contained by some range in mr1. To avoid
+	 * O(n^2) we walk through both ranges in tandem.
+	 */
+	r1 = ranges1[0];
+	for (i1 = 0, i2 = 0; i2 < range_count2; i2++)
 	{
-		r1 = ranges1[i];
-		r2 = ranges2[i];
+		r2 = ranges2[i2];
 
-		if (!range_eq_internal(typcache->rngtype, r1, r2))
+		/* Discard r1s while r1 << r2 */
+		while (range_before_internal(rangetyp, r1, r2))
+		{
+			if (++i1 >= range_count1)
+				return false;
+			r1 = ranges1[i1];
+		}
+
+		/*
+		 * If r1 @> r2, go to the next r2, otherwise return false (since every
+		 * r1[n] and r1[n+1] must have a gap). Note this will give weird
+		 * answers if you don't canonicalize, e.g. with a custom
+		 * int2multirange {[1,1], [2,2]} there is a "gap". But that is
+		 * consistent with other range operators, e.g. '[1,1]'::int2range -|-
+		 * '[2,2]'::int2range is false.
+		 */
+		if (!range_contains_internal(rangetyp, r1, r2))
 			return false;
 	}
 
+	/* All ranges in mr2 are satisfied */
 	return true;
 }
 
-/* equality */
+/* strictly left of? */
 Datum
-multirange_eq(PG_FUNCTION_ARGS)
+range_before_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(range_before_multirange_internal(typcache, r, mr));
+}
+
+Datum
+multirange_before_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(range_after_multirange_internal(typcache, r, mr));
+}
+
+Datum
+multirange_before_multirange(PG_FUNCTION_ARGS)
 {
 	MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
 	MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
@@ -772,27 +1994,156 @@ multirange_eq(PG_FUNCTION_ARGS)
 
 	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr1));
 
-	PG_RETURN_BOOL(multirange_eq_internal(typcache, mr1, mr2));
+	PG_RETURN_BOOL(multirange_before_multirange_internal(typcache, mr1, mr2));
 }
 
-/* inequality (internal version) */
+/* strictly right of? */
+Datum
+range_after_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(range_after_multirange_internal(typcache, r, mr));
+}
+
+Datum
+multirange_after_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(range_before_multirange_internal(typcache, r, mr));
+}
+
+Datum
+multirange_after_multirange(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_before_multirange_internal(typcache, mr2, mr1));
+}
+
+/* strictly left of? (internal version) */
 bool
-multirange_ne_internal(TypeCacheEntry *typcache, MultirangeType * mr1, MultirangeType * mr2)
+range_before_multirange_internal(TypeCacheEntry *typcache, RangeType *r,
+								 MultirangeType * mr)
 {
-	return (!multirange_eq_internal(typcache, mr1, mr2));
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		return false;
+
+	int32		range_count;
+	RangeType **ranges;
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	return range_before_internal(typcache->rngtype, r, ranges[0]);
 }
 
-/* inequality */
+bool
+multirange_before_multirange_internal(TypeCacheEntry *typcache, MultirangeType * mr1,
+									  MultirangeType * mr2)
+{
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		return false;
+
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+
+	multirange_deserialize(mr1, &range_count1, &ranges1);
+	multirange_deserialize(mr2, &range_count2, &ranges2);
+
+	return range_before_internal(typcache->rngtype, ranges1[range_count1 - 1],
+								 ranges2[0]);
+}
+
+/* strictly right of? (internal version) */
+bool
+range_after_multirange_internal(TypeCacheEntry *typcache, RangeType *r,
+								MultirangeType * mr)
+{
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		return false;
+
+	int32		range_count;
+	RangeType **ranges;
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	return range_after_internal(typcache->rngtype, r, ranges[range_count - 1]);
+}
+
+/* adjacent to? */
 Datum
-multirange_ne(PG_FUNCTION_ARGS)
+range_adjacent_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		return false;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_adjacent_internal(typcache->rngtype, r, ranges[0]));
+}
+
+Datum
+multirange_adjacent_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		return false;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_adjacent_internal(typcache->rngtype, ranges[range_count - 1], r));
+}
+
+Datum
+multirange_adjacent_multirange(PG_FUNCTION_ARGS)
 {
 	MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
 	MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
 	TypeCacheEntry *typcache;
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		return false;
 
 	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr1));
 
-	PG_RETURN_BOOL(multirange_ne_internal(typcache, mr1, mr2));
+	multirange_deserialize(mr1, &range_count1, &ranges1);
+	multirange_deserialize(mr2, &range_count2, &ranges2);
+
+	PG_RETURN_BOOL(range_adjacent_internal(typcache->rngtype, ranges1[range_count1 - 1], ranges2[0]));
 }
 
 /* Btree support */
@@ -891,6 +2242,29 @@ multirange_gt(PG_FUNCTION_ARGS)
 	PG_RETURN_BOOL(cmp > 0);
 }
 
+/* multirange -> range functions */
+
+/* Find the smallest range that includes everything in the multirange */
+Datum
+range_merge_from_multirange(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	Oid			mltrngtypoid = MultirangeTypeGetOid(mr);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_RANGE_P(make_empty_range(typcache->rngtype));
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_RANGE_P(range_union_internal(typcache->rngtype, ranges[0],
+										   ranges[range_count - 1], false));
+}
+
 /* Hash support */
 
 /* hash a multirange value */
diff --git a/src/backend/utils/adt/rangetypes.c b/src/backend/utils/adt/rangetypes.c
index 65f68614c1..ece2215704 100644
--- a/src/backend/utils/adt/rangetypes.c
+++ b/src/backend/utils/adt/rangetypes.c
@@ -429,19 +429,37 @@ range_lower(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
 	TypeCacheEntry *typcache;
+	bool		isnull;
+	Datum		result;
+
+	typcache = range_get_typcache(fcinfo, RangeTypeGetOid(r1));
+
+	result = range_lower_internal(typcache, r1, &isnull);
+
+	if (isnull)
+		PG_RETURN_NULL();
+	else
+		PG_RETURN_DATUM(result);
+}
+
+Datum
+range_lower_internal(TypeCacheEntry *typcache, const RangeType *r1, bool *isnull)
+{
 	RangeBound	lower;
 	RangeBound	upper;
 	bool		empty;
 
-	typcache = range_get_typcache(fcinfo, RangeTypeGetOid(r1));
-
 	range_deserialize(typcache, r1, &lower, &upper, &empty);
 
 	/* Return NULL if there's no finite lower bound */
 	if (empty || lower.infinite)
-		PG_RETURN_NULL();
+	{
+		*isnull = true;
+		return 0;
+	}
 
-	PG_RETURN_DATUM(lower.val);
+	*isnull = false;
+	return lower.val;
 }
 
 /* extract upper bound value */
@@ -450,19 +468,37 @@ range_upper(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
 	TypeCacheEntry *typcache;
+	bool		isnull;
+	Datum		result;
+
+	typcache = range_get_typcache(fcinfo, RangeTypeGetOid(r1));
+
+	result = range_upper_internal(typcache, r1, &isnull);
+
+	if (isnull)
+		PG_RETURN_NULL();
+	else
+		PG_RETURN_DATUM(result);
+}
+
+Datum
+range_upper_internal(TypeCacheEntry *typcache, const RangeType *r1, bool *isnull)
+{
 	RangeBound	lower;
 	RangeBound	upper;
 	bool		empty;
 
-	typcache = range_get_typcache(fcinfo, RangeTypeGetOid(r1));
-
 	range_deserialize(typcache, r1, &lower, &upper, &empty);
 
 	/* Return NULL if there's no finite upper bound */
 	if (empty || upper.infinite)
-		PG_RETURN_NULL();
+	{
+		*isnull = true;
+		return 0;
+	}
 
-	PG_RETURN_DATUM(upper.val);
+	*isnull = false;
+	return upper.val;
 }
 
 
@@ -473,9 +509,8 @@ Datum
 range_empty(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
-	char		flags = range_get_flags(r1);
 
-	PG_RETURN_BOOL(flags & RANGE_EMPTY);
+	PG_RETURN_BOOL(range_has_flag(r1, RANGE_EMPTY));
 }
 
 /* is lower bound inclusive? */
@@ -483,9 +518,8 @@ Datum
 range_lower_inc(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
-	char		flags = range_get_flags(r1);
 
-	PG_RETURN_BOOL(flags & RANGE_LB_INC);
+	PG_RETURN_BOOL(range_has_flag(r1, RANGE_LB_INC));
 }
 
 /* is upper bound inclusive? */
@@ -493,9 +527,8 @@ Datum
 range_upper_inc(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
-	char		flags = range_get_flags(r1);
 
-	PG_RETURN_BOOL(flags & RANGE_UB_INC);
+	PG_RETURN_BOOL(range_has_flag(r1, RANGE_UB_INC));
 }
 
 /* is lower bound infinite? */
@@ -503,9 +536,8 @@ Datum
 range_lower_inf(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
-	char		flags = range_get_flags(r1);
 
-	PG_RETURN_BOOL(flags & RANGE_LB_INF);
+	PG_RETURN_BOOL(range_has_flag(r1, RANGE_LB_INF));
 }
 
 /* is upper bound infinite? */
@@ -513,9 +545,8 @@ Datum
 range_upper_inf(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
-	char		flags = range_get_flags(r1);
 
-	PG_RETURN_BOOL(flags & RANGE_UB_INF);
+	PG_RETURN_BOOL(range_has_flag(r1, RANGE_UB_INF));
 }
 
 
@@ -955,7 +986,25 @@ range_minus(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
 	RangeType  *r2 = PG_GETARG_RANGE_P(1);
+	RangeType  *ret;
 	TypeCacheEntry *typcache;
+
+	/* 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));
+
+	ret = range_minus_internal(typcache, r1, r2);
+	if (ret)
+		PG_RETURN_RANGE_P(ret);
+	else
+		PG_RETURN_NULL();
+}
+
+RangeType *
+range_minus_internal(TypeCacheEntry *typcache, const RangeType *r1, const RangeType *r2)
+{
 	RangeBound	lower1,
 				lower2;
 	RangeBound	upper1,
@@ -967,18 +1016,12 @@ range_minus(PG_FUNCTION_ARGS)
 				cmp_u1l2,
 				cmp_u1u2;
 
-	/* 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));
-
 	range_deserialize(typcache, r1, &lower1, &upper1, &empty1);
 	range_deserialize(typcache, r2, &lower2, &upper2, &empty2);
 
 	/* if either is empty, r1 is the correct answer */
 	if (empty1 || empty2)
-		PG_RETURN_RANGE_P(r1);
+		return r1;
 
 	cmp_l1l2 = range_cmp_bounds(typcache, &lower1, &lower2);
 	cmp_l1u2 = range_cmp_bounds(typcache, &lower1, &upper2);
@@ -991,35 +1034,35 @@ range_minus(PG_FUNCTION_ARGS)
 				 errmsg("result of range difference would not be contiguous")));
 
 	if (cmp_l1u2 > 0 || cmp_u1l2 < 0)
-		PG_RETURN_RANGE_P(r1);
+		return r1;
 
 	if (cmp_l1l2 >= 0 && cmp_u1u2 <= 0)
-		PG_RETURN_RANGE_P(make_empty_range(typcache));
+		return make_empty_range(typcache);
 
 	if (cmp_l1l2 <= 0 && cmp_u1l2 >= 0 && cmp_u1u2 <= 0)
 	{
 		lower2.inclusive = !lower2.inclusive;
 		lower2.lower = false;	/* it will become the upper bound */
-		PG_RETURN_RANGE_P(make_range(typcache, &lower1, &lower2, false));
+		return make_range(typcache, &lower1, &lower2, false);
 	}
 
 	if (cmp_l1l2 >= 0 && cmp_u1u2 >= 0 && cmp_l1u2 <= 0)
 	{
 		upper2.inclusive = !upper2.inclusive;
 		upper2.lower = true;	/* it will become the lower bound */
-		PG_RETURN_RANGE_P(make_range(typcache, &upper2, &upper1, false));
+		return make_range(typcache, &upper2, &upper1, false);
 	}
 
 	elog(ERROR, "unexpected case in range_minus");
-	PG_RETURN_NULL();
+	return NULL;
 }
 
 /*
  * Set union.  If strict is true, it is an error that the two input ranges
  * are not adjacent or overlapping.
  */
-static RangeType *
-range_union_internal(TypeCacheEntry *typcache, RangeType *r1, RangeType *r2,
+RangeType *
+range_union_internal(TypeCacheEntry *typcache, const RangeType *r1, const RangeType *r2,
 					 bool strict)
 {
 	RangeBound	lower1,
@@ -1099,6 +1142,19 @@ range_intersect(PG_FUNCTION_ARGS)
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
 	RangeType  *r2 = PG_GETARG_RANGE_P(1);
 	TypeCacheEntry *typcache;
+
+	/* 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));
+
+	PG_RETURN_RANGE_P(range_intersect_internal(typcache, r1, r2));
+}
+
+RangeType *
+range_intersect_internal(TypeCacheEntry *typcache, const RangeType *r1, const RangeType *r2)
+{
 	RangeBound	lower1,
 				lower2;
 	RangeBound	upper1,
@@ -1108,17 +1164,11 @@ range_intersect(PG_FUNCTION_ARGS)
 	RangeBound *result_lower;
 	RangeBound *result_upper;
 
-	/* 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));
-
 	range_deserialize(typcache, r1, &lower1, &upper1, &empty1);
 	range_deserialize(typcache, r2, &lower2, &upper2, &empty2);
 
-	if (empty1 || empty2 || !DatumGetBool(range_overlaps(fcinfo)))
-		PG_RETURN_RANGE_P(make_empty_range(typcache));
+	if (empty1 || empty2 || !range_overlaps_internal(typcache, r1, r2))
+		return make_empty_range(typcache);
 
 	if (range_cmp_bounds(typcache, &lower1, &lower2) >= 0)
 		result_lower = &lower1;
@@ -1130,54 +1180,83 @@ range_intersect(PG_FUNCTION_ARGS)
 	else
 		result_upper = &upper2;
 
-	PG_RETURN_RANGE_P(make_range(typcache, result_lower, result_upper, false));
+	return make_range(typcache, result_lower, result_upper, false);
 }
 
-/* Btree support */
+/* range, range -> range, range functions */
 
-/* btree comparator */
-Datum
-range_cmp(PG_FUNCTION_ARGS)
+/*
+ * range_split_internal - if r2 intersects the middle of r1, leaving non-empty
+ * ranges on both sides, then return true and set output1 and output2 to the
+ * results of r1 - r2 (in order). Otherwise return false and don't set output1
+ * or output2. Neither input range should be empty.
+ */
+bool
+range_split_internal(TypeCacheEntry *typcache, const RangeType *r1, const RangeType *r2,
+					 RangeType **output1, RangeType **output2)
 {
-	RangeType  *r1 = PG_GETARG_RANGE_P(0);
-	RangeType  *r2 = PG_GETARG_RANGE_P(1);
-	TypeCacheEntry *typcache;
 	RangeBound	lower1,
 				lower2;
 	RangeBound	upper1,
 				upper2;
 	bool		empty1,
 				empty2;
-	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));
 
 	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
+	if (range_cmp_bounds(typcache, &lower1, &lower2) < 0 &&
+		range_cmp_bounds(typcache, &upper1, &upper2) > 0)
 	{
-		cmp = range_cmp_bounds(typcache, &lower1, &lower2);
-		if (cmp == 0)
-			cmp = range_cmp_bounds(typcache, &upper1, &upper2);
+		/*
+		 * Need to invert inclusive/exclusive for the lower2 and upper2
+		 * points. They can't be infinite though. We're allowed to overwrite
+		 * these RangeBounds since they only exist locally.
+		 */
+		lower2.inclusive = !lower2.inclusive;
+		lower2.lower = false;
+		upper2.inclusive = !upper2.inclusive;
+		upper2.lower = true;
+
+		*output1 = make_range(typcache, &lower1, &lower2, false);
+		*output2 = make_range(typcache, &upper2, &upper1, false);
+		return true;
 	}
 
-	return cmp;
+	return false;
+}
+
+/* range -> range aggregate functions */
+
+Datum
+range_intersect_agg_transfn(PG_FUNCTION_ARGS)
+{
+	MemoryContext aggContext;
+	Oid			rngtypoid;
+	TypeCacheEntry *typcache;
+	RangeType  *result;
+	RangeType  *current;
+
+	if (!AggCheckCallContext(fcinfo, &aggContext))
+		elog(ERROR, "range_intersect_agg_transfn called in non-aggregate context");
+
+	rngtypoid = get_fn_expr_argtype(fcinfo->flinfo, 1);
+	if (!type_is_range(rngtypoid))
+		ereport(ERROR, (errmsg("range_intersect_agg must be called with a range")));
+
+	typcache = range_get_typcache(fcinfo, rngtypoid);
+
+	/* strictness ensures these are non-null */
+	result = PG_GETARG_RANGE_P(0);
+	current = PG_GETARG_RANGE_P(1);
+
+	result = range_intersect_internal(typcache, result, current);
+	PG_RETURN_RANGE_P(result);
 }
 
+
+/* Btree support */
+
 /* btree comparator */
 Datum
 range_cmp(PG_FUNCTION_ARGS)
@@ -1207,7 +1286,7 @@ range_cmp(PG_FUNCTION_ARGS)
  * Internal version of range_cmp
  */
 int
-range_cmp_internal(TypeCacheEntry *typcache, RangeType *r1, RangeType *r2)
+range_cmp_internal(TypeCacheEntry *typcache, const RangeType *r1, const RangeType *r2)
 {
 	RangeBound	lower1,
 				lower2;
@@ -1273,7 +1352,7 @@ range_gt(PG_FUNCTION_ARGS)
 /* Hash support */
 
 uint32
-hash_range_internal(TypeCacheEntry *typcache, RangeType *r)
+hash_range_internal(TypeCacheEntry *typcache, const RangeType *r)
 {
 	uint32		result;
 	TypeCacheEntry *scache;
@@ -1343,7 +1422,7 @@ hash_range(PG_FUNCTION_ARGS)
 }
 
 uint64
-hash_range_extended_internal(TypeCacheEntry *typcache, RangeType *r, Datum seed)
+hash_range_extended_internal(TypeCacheEntry *typcache, const RangeType *r, Datum seed)
 {
 	uint64		result;
 	TypeCacheEntry *scache;
@@ -1838,6 +1917,21 @@ range_get_flags(const RangeType *range)
 }
 
 /*
+ * range_has_flag: set whether a range has a specific flag.
+ *
+ * This lets expose some of our functions that just check flags
+ * to the rest of the code base (like to multiranges)
+ * without writing full-fledged *_internal versions.
+ */
+bool
+range_has_flag(const RangeType *r1, char flag)
+{
+	char		flags = range_get_flags(r1);
+
+	return flags & flag;
+}
+
+/*
  * range_set_contain_empty: set the RANGE_CONTAIN_EMPTY bit in the value.
  *
  * This is only needed in GiST operations, so we don't include a provision
diff --git a/src/include/catalog/pg_aggregate.dat b/src/include/catalog/pg_aggregate.dat
index 242d843347..07a7211c15 100644
--- a/src/include/catalog/pg_aggregate.dat
+++ b/src/include/catalog/pg_aggregate.dat
@@ -538,6 +538,17 @@
   aggtransfn => 'bytea_string_agg_transfn',
   aggfinalfn => 'bytea_string_agg_finalfn', aggtranstype => 'internal' },
 
+# range
+{ aggfnoid => 'range_intersect_agg(anyrange)',
+  aggtransfn => 'range_intersect_agg_transfn',
+  aggcombinefn => 'range_intersect_agg_transfn', aggtranstype => 'anyrange' },
+{ aggfnoid => 'range_intersect_agg(anymultirange)',
+  aggtransfn => 'multirange_intersect_agg_transfn',
+  aggcombinefn => 'multirange_intersect_agg_transfn', aggtranstype => 'anymultirange' },
+{ aggfnoid => 'range_agg(anyrange)', aggtransfn => 'range_agg_transfn',
+  aggfinalfn => 'range_agg_finalfn', aggfinalextra => 't',
+  aggtranstype => 'internal' },
+
 # json
 { aggfnoid => 'json_agg', aggtransfn => 'json_agg_transfn',
   aggfinalfn => 'json_agg_finalfn', aggtranstype => 'internal' },
diff --git a/src/include/catalog/pg_amproc.dat b/src/include/catalog/pg_amproc.dat
index b7cb2cb000..32965d6b3d 100644
--- a/src/include/catalog/pg_amproc.dat
+++ b/src/include/catalog/pg_amproc.dat
@@ -1210,12 +1210,13 @@
   amprocrighttype => 'anyrange', amprocnum => '4',
   amproc => 'brin_inclusion_union' },
 { amprocfamily => 'brin/range_inclusion_ops', amproclefttype => 'anyrange',
-  amprocrighttype => 'anyrange', amprocnum => '11', amproc => 'range_merge' },
+  amprocrighttype => 'anyrange', amprocnum => '11',
+  amproc => 'range_merge(anyrange,anyrange)' },
 { amprocfamily => 'brin/range_inclusion_ops', amproclefttype => 'anyrange',
   amprocrighttype => 'anyrange', amprocnum => '13',
   amproc => 'range_contains' },
 { amprocfamily => 'brin/range_inclusion_ops', amproclefttype => 'anyrange',
-  amprocrighttype => 'anyrange', amprocnum => '14', amproc => 'isempty' },
+  amprocrighttype => 'anyrange', amprocnum => '14', amproc => 'isempty(anyrange)' },
 
 # minmax pg_lsn
 { amprocfamily => 'brin/pg_lsn_minmax_ops', amproclefttype => 'pg_lsn',
diff --git a/src/include/catalog/pg_operator.dat b/src/include/catalog/pg_operator.dat
index 4d319bb5b3..ade223b6f3 100644
--- a/src/include/catalog/pg_operator.dat
+++ b/src/include/catalog/pg_operator.dat
@@ -3322,5 +3322,174 @@
   oprresult => 'bool', oprcom => '<(anymultirange,anymultirange)',
   oprnegate => '<=(anymultirange,anymultirange)', oprcode => 'multirange_gt',
   oprrest => 'scalargtsel', oprjoin => 'scalargtjoinsel' },
+{ oid => '8073', oid_symbol => 'OID_RANGE_OVERLAPS_MULTIRANGE_OP',
+  descr => 'overlaps',
+  oprname => '&&', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '&&(anymultirange,anyrange)',
+  oprcode => 'range_overlaps_multirange', oprrest => 'areasel',
+  oprjoin => 'areajoinsel' },
+{ oid => '8074', oid_symbol => 'OID_MULTIRANGE_OVERLAPS_RANGE_OP', descr => 'contains',
+  oprname => '&&', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcom => '&&(anyrange,anymultirange)',
+  oprcode => 'multirange_overlaps_range', oprrest => 'areasel',
+  oprjoin => 'areajoinsel' },
+{ oid => '8075', oid_symbol => 'OID_MULTIRANGE_OVERLAPS_MULTIRANGE_OP', descr => 'contains',
+  oprname => '&&', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '&&(anymultirange,anymultirange)',
+  oprcode => 'multirange_overlaps_multirange', oprrest => 'areasel',
+  oprjoin => 'areajoinsel' },
+{ oid => '8064', oid_symbol => 'OID_MULTIRANGE_CONTAINS_ELEM_OP',
+  descr => 'contains',
+  oprname => '@>', oprleft => 'anymultirange', oprright => 'anyelement',
+  oprresult => 'bool', oprcom => '<@(anyelement,anymultirange)',
+  oprcode => 'multirange_contains_elem', oprrest => 'contsel',
+  oprjoin => 'contjoinsel' },
+{ oid => '8065', oid_symbol => 'OID_MULTIRANGE_CONTAINS_RANGE_OP', descr => 'contains',
+  oprname => '@>', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcom => '<@(anyrange,anymultirange)',
+  oprcode => 'multirange_contains_range', oprrest => 'contsel',
+  oprjoin => 'contjoinsel' },
+{ oid => '8066', oid_symbol => 'OID_MULTIRANGE_CONTAINS_MULTIRANGE_OP', descr => 'contains',
+  oprname => '@>', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '<@(anymultirange,anymultirange)',
+  oprcode => 'multirange_contains_multirange', oprrest => 'contsel',
+  oprjoin => 'contjoinsel' },
+{ oid => '8067', oid_symbol => 'OID_MULTIRANGE_ELEM_CONTAINED_OP',
+  descr => 'is contained by',
+  oprname => '<@', oprleft => 'anyelement', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '@>(anymultirange,anyelement)',
+  oprcode => 'elem_contained_by_multirange', oprrest => 'contsel',
+  oprjoin => 'contjoinsel' },
+{ oid => '8068', oid_symbol => 'OID_MULTIRANGE_RANGE_CONTAINED_OP',
+  descr => 'is contained by',
+  oprname => '<@', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '@>(anymultirange,anyrange)',
+  oprcode => 'range_contained_by_multirange', oprrest => 'contsel',
+  oprjoin => 'contjoinsel' },
+{ oid => '8069', oid_symbol => 'OID_MULTIRANGE_MULTIRANGE_CONTAINED_OP',
+  descr => 'is contained by',
+  oprname => '<@', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '@>(anymultirange,anymultirange)',
+  oprcode => 'multirange_contained_by_multirange', oprrest => 'contsel',
+  oprjoin => 'contjoinsel' },
+{ oid => '8106', oid_symbol => 'OID_RANGE_OVERLAPS_LEFT_MULTIRANGE_OP',
+  descr => 'overlaps or is left of',
+  oprname => '&<', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcode => 'range_overleft_multirange', oprrest => 'scalarltsel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8107', oid_symbol => 'OID_MULTIRANGE_OVERLAPS_LEFT_RANGE_OP',
+  descr => 'overlaps or is left of',
+  oprname => '&<', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcode => 'multirange_overleft_range', oprrest => 'scalarltsel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8108', oid_symbol => 'OID_MULTIRANGE_OVERLAPS_LEFT_MULTIRANGE_OP',
+  descr => 'overlaps or is left of',
+  oprname => '&<', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcode => 'multirange_overleft_multirange', oprrest => 'scalarltsel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8109', oid_symbol => 'OID_RANGE_OVERLAPS_RIGHT_MULTIRANGE_OP',
+  descr => 'overlaps or is right of',
+  oprname => '&>', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcode => 'range_overright_multirange', oprrest => 'scalargtsel',
+  oprjoin => 'scalargtjoinsel' },
+{ oid => '8110', oid_symbol => 'OID_MULTIRANGE_OVERLAPS_RIGHT_RANGE_OP',
+  descr => 'overlaps or is right of',
+  oprname => '&>', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcode => 'multirange_overright_range', oprrest => 'scalargtsel',
+  oprjoin => 'scalargtjoinsel' },
+{ oid => '8111', oid_symbol => 'OID_MULTIRANGE_OVERLAPS_RIGHT_MULTIRANGE_OP',
+  descr => 'overlaps or is right of',
+  oprname => '&>', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcode => 'multirange_overright_multirange', oprrest => 'scalargtsel',
+  oprjoin => 'scalargtjoinsel' },
+{ oid => '8091', oid_symbol => 'OID_RANGE_ADJACENT_MULTIRANGE_OP', descr => 'is adjacent to',
+  oprname => '-|-', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '-|-(anymultirange,anyrange)',
+  oprcode => 'range_adjacent_multirange', oprrest => 'scalarltsel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8092', oid_symbol => 'OID_MULTIRANGE_ADJACENT_RANGE_OP', descr => 'is adjacent to',
+  oprname => '-|-', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcom => '-|-(anyrange,anymultirange)',
+  oprcode => 'multirange_adjacent_range', oprrest => 'scalarltsel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8093', oid_symbol => 'OID_MULTIRANGE_ADJACENT_MULTIRANGE_OP', descr => 'is adjacent to',
+  oprname => '-|-', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '-|-(anymultirange,anymultirange)',
+  oprcode => 'multirange_adjacent_multirange', oprrest => 'scalarltsel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8138', descr => 'multirange union',
+  oprname => '@+', oprleft => 'anyrange', oprright => 'anyrange',
+  oprresult => 'anymultirange', oprcom => '@+(anyrange,anyrange)',
+  oprcode => 'range_union_range' },
+{ oid => '8115', descr => 'multirange union',
+  oprname => '@+', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'anymultirange', oprcom => '@+(anymultirange,anyrange)',
+  oprcode => 'range_union_multirange' },
+{ oid => '8116', descr => 'multirange union',
+  oprname => '@+', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'anymultirange', oprcom => '@+(anyrange,anymultirange)',
+  oprcode => 'multirange_union_range' },
+{ oid => '8117', descr => 'multirange union',
+  oprname => '@+', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'anymultirange', oprcom => '@+(anymultirange,anymultirange)',
+  oprcode => 'multirange_union_multirange' },
+{ oid => '8140', descr => 'multirange minus',
+  oprname => '@-', oprleft => 'anyrange', oprright => 'anyrange',
+  oprresult => 'anymultirange', oprcode => 'range_minus_range' },
+{ oid => '8121', descr => 'multirange minus',
+  oprname => '@-', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'anymultirange', oprcode => 'range_minus_multirange' },
+{ oid => '8122', descr => 'multirange minus',
+  oprname => '@-', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'anymultirange', oprcode => 'multirange_minus_range' },
+{ oid => '8123', descr => 'multirange minus',
+  oprname => '@-', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'anymultirange', oprcode => 'multirange_minus_multirange' },
+{ oid => '8142', descr => 'multirange intersect',
+  oprname => '@*', oprleft => 'anyrange', oprright => 'anyrange',
+  oprresult => 'anymultirange', oprcom => '@*(anyrange,anyrange)',
+  oprcode => 'range_intersect_range' },
+{ oid => '8127', descr => 'multirange intersect',
+  oprname => '@*', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'anymultirange', oprcom => '@*(anymultirange,anyrange)',
+  oprcode => 'range_intersect_multirange' },
+{ oid => '8128', descr => 'multirange intersect',
+  oprname => '@*', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'anymultirange', oprcom => '@*(anyrange,anymultirange)',
+  oprcode => 'multirange_intersect_range' },
+{ oid => '8129', descr => 'multirange intersect',
+  oprname => '@*', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'anymultirange', oprcom => '@*(anymultirange,anymultirange)',
+  oprcode => 'multirange_intersect_multirange' },
+{ oid => '8082', oid_symbol => 'OID_RANGE_BEFORE_MULTIRANGE_OP', descr => 'is left of',
+  oprname => '<<', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '>>(anymultirange,anyrange)',
+  oprcode => 'range_before_multirange', oprrest => 'scalarltsel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8083', oid_symbol => 'OID_MULTIRANGE_BEFORE_RANGE_OP', descr => 'is left of',
+  oprname => '<<', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcom => '>>(anyrange,anymultirange)',
+  oprcode => 'multirange_before_range', oprrest => 'scalarltsel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8084', oid_symbol => 'OID_MULTIRANGE_BEFORE_MULTIRANGE_OP', descr => 'is left of',
+  oprname => '<<', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '>>(anymultirange,anymultirange)',
+  oprcode => 'multirange_before_multirange', oprrest => 'scalarltsel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8085', oid_symbol => 'OID_RANGE_AFTER_MULTIRANGE_OP', descr => 'is right of',
+  oprname => '>>', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '<<(anymultirange,anyrange)',
+  oprcode => 'range_after_multirange', oprrest => 'scalargtsel',
+  oprjoin => 'scalargtjoinsel' },
+{ oid => '8086', oid_symbol => 'OID_MULTIRANGE_AFTER_RANGE_OP', descr => 'is right of',
+  oprname => '>>', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcom => '<<(anyrange,anymultirange)',
+  oprcode => 'multirange_after_range', oprrest => 'scalargtsel',
+  oprjoin => 'scalargtjoinsel' },
+{ oid => '8087', oid_symbol => 'OID_MULTIRANGE_AFTER_MULTIRANGE_OP', descr => 'is right of',
+  oprname => '>>', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '<<(anymultirange,anymultirange)',
+  oprcode => 'multirange_after_multirange', oprrest => 'scalargtsel',
+  oprjoin => 'scalargtjoinsel' },
 
 ]
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 7ffa047969..13ea94c024 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -9607,6 +9607,10 @@
   descr => 'the smallest range which includes both of the given ranges',
   proname => 'range_merge', prorettype => 'anyrange',
   proargtypes => 'anyrange anyrange', prosrc => 'range_merge' },
+{ oid => '8143',
+  descr => 'the smallest range which includes the whole multirange',
+  proname => 'range_merge', prorettype => 'anyrange',
+  proargtypes => 'anymultirange', prosrc => 'range_merge_from_multirange' },
 { oid => '3868',
   proname => 'range_intersect', prorettype => 'anyrange',
   proargtypes => 'anyrange anyrange', prosrc => 'range_intersect' },
@@ -9656,6 +9660,13 @@
 { oid => '3169', descr => 'restriction selectivity for range operators',
   proname => 'rangesel', provolatile => 's', prorettype => 'float8',
   proargtypes => 'internal oid internal int4', prosrc => 'rangesel' },
+{ oid => '8133', descr => 'range aggregate by intersecting',
+  proname => 'range_intersect_agg_transfn', prorettype => 'anyrange',
+  proargtypes => 'anyrange anyrange', prosrc => 'range_intersect_agg_transfn'},
+{ oid => '8134', descr => 'range aggregate by intersecting',
+  proname => 'range_intersect_agg', prokind => 'a', proisstrict => 'f',
+  prorettype => 'anyrange', proargtypes => 'anyrange',
+  prosrc => 'aggregate_dummy'},
 
 { oid => '3914', descr => 'convert an int4 range to canonical form',
   proname => 'int4range_canonical', prorettype => 'int4range',
@@ -9743,9 +9754,165 @@
 { oid => '8007', descr => 'I/O',
   proname => 'multirange_send', provolatile => 's', prorettype => 'bytea',
   proargtypes => 'anymultirange', prosrc => 'multirange_send' },
+{ oid => '8094', descr => 'lower bound of multirange',
+  proname => 'lower', prorettype => 'anyelement', proargtypes => 'anymultirange',
+  prosrc => 'multirange_lower' },
+{ oid => '8095', descr => 'upper bound of multirange',
+  proname => 'upper', prorettype => 'anyelement', proargtypes => 'anymultirange',
+  prosrc => 'multirange_upper' },
+{ oid => '8057', descr => 'is the multirange empty?',
+  proname => 'isempty', prorettype => 'bool',
+  proargtypes => 'anymultirange', prosrc => 'multirange_empty' },
+{ oid => '8096', descr => 'is the multirange\'s lower bound inclusive?',
+  proname => 'lower_inc', prorettype => 'bool', proargtypes => 'anymultirange',
+  prosrc => 'multirange_lower_inc' },
+{ oid => '8097', descr => 'is the multirange\'s upper bound inclusive?',
+  proname => 'upper_inc', prorettype => 'bool', proargtypes => 'anymultirange',
+  prosrc => 'multirange_upper_inc' },
+{ oid => '8098', descr => 'is the multirange\'s lower bound infinite?',
+  proname => 'lower_inf', prorettype => 'bool', proargtypes => 'anymultirange',
+  prosrc => 'multirange_lower_inf' },
+{ oid => '8099', descr => 'is the multirange\'s upper bound infinite?',
+  proname => 'upper_inf', prorettype => 'bool', proargtypes => 'anymultirange',
+  prosrc => 'multirange_upper_inf' },
 { oid => '8008', descr => 'multirange typanalyze',
   proname => 'multirange_typanalyze', provolatile => 's', prorettype => 'bool',
   proargtypes => 'internal', prosrc => 'multirange_typanalyze' },
+{ oid => '8033',
+  proname => 'multirange_eq', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_eq' },
+{ oid => '8034',
+  proname => 'multirange_ne', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_ne' },
+{ oid => '8070',
+  proname => 'range_overlaps_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_overlaps_multirange' },
+{ oid => '8071',
+  proname => 'multirange_overlaps_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_overlaps_range' },
+{ oid => '8072',
+  proname => 'multirange_overlaps_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_overlaps_multirange' },
+{ oid => '8058',
+  proname => 'multirange_contains_elem', prorettype => 'bool',
+  proargtypes => 'anymultirange anyelement', prosrc => 'multirange_contains_elem' },
+{ oid => '8059',
+  proname => 'multirange_contains_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_contains_range' },
+{ oid => '8060',
+  proname => 'multirange_contains_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_contains_multirange' },
+{ oid => '8061',
+  proname => 'elem_contained_by_multirange', prorettype => 'bool',
+  proargtypes => 'anyelement anymultirange', prosrc => 'elem_contained_by_multirange' },
+{ oid => '8062',
+  proname => 'range_contained_by_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_contained_by_multirange' },
+{ oid => '8063',
+  proname => 'multirange_contained_by_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_contained_by_multirange' },
+{ oid => '8088',
+  proname => 'range_adjacent_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_adjacent_multirange' },
+{ oid => '8089',
+  proname => 'multirange_adjacent_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_adjacent_multirange' },
+{ oid => '8090',
+  proname => 'multirange_adjacent_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_adjacent_range' },
+{ oid => '8076',
+  proname => 'range_before_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_before_multirange' },
+{ oid => '8077',
+  proname => 'multirange_before_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_before_range' },
+{ oid => '8078',
+  proname => 'multirange_before_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_before_multirange' },
+{ oid => '8079',
+  proname => 'range_after_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_after_multirange' },
+{ oid => '8080',
+  proname => 'multirange_after_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_after_range' },
+{ oid => '8081',
+  proname => 'multirange_after_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_after_multirange' },
+{ oid => '8100',
+  proname => 'range_overleft_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_overleft_multirange' },
+{ oid => '8101',
+  proname => 'multirange_overleft_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_overleft_range' },
+{ oid => '8102',
+  proname => 'multirange_overleft_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_overleft_multirange' },
+{ oid => '8103',
+  proname => 'range_overright_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_overright_multirange' },
+{ oid => '8104',
+  proname => 'multirange_overright_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_overright_range' },
+{ oid => '8105',
+  proname => 'multirange_overright_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_overright_multirange' },
+{ oid => '8137',
+  proname => 'range_union_range', prorettype => 'anymultirange',
+  proargtypes => 'anyrange anyrange', prosrc => 'range_union_range' },
+{ oid => '8112',
+  proname => 'range_union_multirange', prorettype => 'anymultirange',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_union_multirange' },
+{ oid => '8113',
+  proname => 'multirange_union_range', prorettype => 'anymultirange',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_union_range' },
+{ oid => '8114',
+  proname => 'multirange_union_multirange', prorettype => 'anymultirange',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_union_multirange' },
+{ oid => '8139',
+  proname => 'range_minus_range', prorettype => 'anymultirange',
+  proargtypes => 'anyrange anyrange', prosrc => 'range_minus_range' },
+{ oid => '8118',
+  proname => 'range_minus_multirange', prorettype => 'anymultirange',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_minus_multirange' },
+{ oid => '8119',
+  proname => 'multirange_minus_range', prorettype => 'anymultirange',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_minus_range' },
+{ oid => '8120',
+  proname => 'multirange_minus_multirange', prorettype => 'anymultirange',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_minus_multirange' },
+{ oid => '8141',
+  proname => 'range_intersect_range', prorettype => 'anymultirange',
+  proargtypes => 'anyrange anyrange', prosrc => 'range_intersect_range' },
+{ oid => '8124',
+  proname => 'range_intersect_multirange', prorettype => 'anymultirange',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_intersect_multirange' },
+{ oid => '8125',
+  proname => 'multirange_intersect_range', prorettype => 'anymultirange',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_intersect_range' },
+{ oid => '8126',
+  proname => 'multirange_intersect_multirange', prorettype => 'anymultirange',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_intersect_multirange' },
+{ oid => '8022', descr => 'less-equal-greater',
+  proname => 'multirange_cmp', prorettype => 'int4',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_cmp' },
+{ oid => '8023',
+  proname => 'multirange_lt', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_lt' },
+{ oid => '8024',
+  proname => 'multirange_le', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_le' },
+{ oid => '8025',
+  proname => 'multirange_ge', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_ge' },
+{ oid => '8026',
+  proname => 'multirange_gt', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_gt' },
+{ oid => '8038', descr => 'hash a multirange',
+  proname => 'hash_multirange', prorettype => 'int4', proargtypes => 'anymultirange',
+  prosrc => 'hash_multirange' },
+{ oid => '8039', descr => 'hash a multirange',
+  proname => 'hash_multirange_extended', prorettype => 'int8',
+  proargtypes => 'anymultirange int8', prosrc => 'hash_multirange_extended' },
 
 { oid => '8045', descr => 'int4multirange constructor',
   proname => 'int4multirange', proisstrict => 'f',
@@ -9801,6 +9968,24 @@
   prorettype => 'int8multirange', proargtypes => '_int8range',
   proallargtypes => '{_int8range}', proargmodes => '{v}',
   prosrc => 'multirange_constructor1' },
+{ oid => '8130', descr => 'aggregate transition function',
+  proname => 'range_agg_transfn', proisstrict => 'f', prorettype => 'internal',
+  proargtypes => 'internal anyrange', prosrc => 'range_agg_transfn' },
+{ oid => '8131', descr => 'aggregate final function',
+  proname => 'range_agg_finalfn', proisstrict => 'f', prorettype => 'anymultirange',
+  proargtypes => 'internal anyrange', prosrc => 'range_agg_finalfn' },
+{ oid => '8132', descr => 'combine aggregate input into a multirange',
+  proname => 'range_agg', prokind => 'a', proisstrict => 'f',
+  prorettype => 'anymultirange', proargtypes => 'anyrange',
+  prosrc => 'aggregate_dummy' },
+{ oid => '8135', descr => 'range aggregate by intersecting',
+  proname => 'multirange_intersect_agg_transfn', prorettype => 'anymultirange',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_intersect_agg_transfn'},
+{ oid => '8136', descr => 'range aggregate by intersecting',
+  proname => 'range_intersect_agg', prokind => 'a', proisstrict => 'f',
+  prorettype => 'anymultirange', proargtypes => 'anymultirange',
+  prosrc => 'aggregate_dummy'},
+
 # date, time, timestamp constructors
 { oid => '3846', descr => 'construct date',
   proname => 'make_date', prorettype => 'date', proargtypes => 'int4 int4 int4',
diff --git a/src/include/utils/multirangetypes.h b/src/include/utils/multirangetypes.h
index ed2e19aafa..cc4f82b0ba 100644
--- a/src/include/utils/multirangetypes.h
+++ b/src/include/utils/multirangetypes.h
@@ -59,6 +59,40 @@ extern bool multirange_eq_internal(TypeCacheEntry *typcache, MultirangeType * mr
 								   MultirangeType * mr2);
 extern bool multirange_ne_internal(TypeCacheEntry *typcache, MultirangeType * mr1,
 								   MultirangeType * mr2);
+extern bool multirange_contains_elem_internal(TypeCacheEntry *typcache, MultirangeType * mr,
+											  Datum elem);
+extern bool multirange_contains_range_internal(TypeCacheEntry *typcache, MultirangeType * mr,
+											   RangeType *r);
+extern bool multirange_contains_multirange_internal(TypeCacheEntry *typcache,
+													MultirangeType * mr1,
+													MultirangeType * mr2);
+extern bool range_overlaps_multirange_internal(TypeCacheEntry *typcache, RangeType *r,
+											   MultirangeType * mr);
+extern bool multirange_overlaps_multirange_internal(TypeCacheEntry *typcache,
+													MultirangeType * mr1,
+													MultirangeType * mr2);
+extern bool range_before_multirange_internal(TypeCacheEntry *typcache, RangeType *r,
+											 MultirangeType * mr);
+extern bool range_after_multirange_internal(TypeCacheEntry *typcache, RangeType *r,
+											MultirangeType * mr);
+extern bool multirange_before_multirange_internal(TypeCacheEntry *typcache,
+												  MultirangeType * mr1,
+												  MultirangeType * mr2);
+extern MultirangeType * range_union_multirange_internal(TypeCacheEntry *typcache,
+														RangeType *r,
+														MultirangeType * mr);
+extern MultirangeType * multirange_minus_multirange_internal(Oid mltrngtypoid,
+															 TypeCacheEntry *rangetyp,
+															 int32 range_count1,
+															 RangeType **ranges1,
+															 int32 range_count2,
+															 RangeType **ranges2);
+extern MultirangeType * multirange_intersect_multirange_internal(Oid mltrngtypoid,
+																 TypeCacheEntry *rangetyp,
+																 int32 range_count1,
+																 RangeType **ranges1,
+																 int32 range_count2,
+																 RangeType **ranges2);
 
 /* assorted support functions */
 extern TypeCacheEntry *multirange_get_typcache(FunctionCallInfo fcinfo,
diff --git a/src/include/utils/rangetypes.h b/src/include/utils/rangetypes.h
index 521710d71f..b801f20fe1 100644
--- a/src/include/utils/rangetypes.h
+++ b/src/include/utils/rangetypes.h
@@ -94,13 +94,21 @@ typedef struct
  * prototypes for functions defined in rangetypes.c
  */
 
-extern bool range_contains_elem_internal(TypeCacheEntry *typcache, const RangeType *r, Datum val);
-
 /* internal versions of the above */
+extern int	range_cmp_internal(TypeCacheEntry *typcache, const RangeType *r1,
+							   const RangeType *r2);
+extern uint32 hash_range_internal(TypeCacheEntry *typcache, const RangeType *r);
+extern uint64 hash_range_extended_internal(TypeCacheEntry *typcache, const RangeType *r,
+										   Datum seed);
+extern Datum range_lower_internal(TypeCacheEntry *typcache, const RangeType *r1,
+								  bool *isnull);
+extern Datum range_upper_internal(TypeCacheEntry *typcache, const RangeType *r1,
+								  bool *isnull);
 extern bool range_eq_internal(TypeCacheEntry *typcache, const RangeType *r1,
 							  const RangeType *r2);
 extern bool range_ne_internal(TypeCacheEntry *typcache, const RangeType *r1,
 							  const RangeType *r2);
+extern bool range_contains_elem_internal(TypeCacheEntry *typcache, const RangeType *r, Datum val);
 extern bool range_contains_internal(TypeCacheEntry *typcache, const RangeType *r1,
 									const RangeType *r2);
 extern bool range_contained_by_internal(TypeCacheEntry *typcache, const RangeType *r1,
@@ -117,6 +125,12 @@ extern bool range_overleft_internal(TypeCacheEntry *typcache, const RangeType *r
 									const RangeType *r2);
 extern bool range_overright_internal(TypeCacheEntry *typcache, const RangeType *r1,
 									 const RangeType *r2);
+extern RangeType *range_union_internal(TypeCacheEntry *typcache, const RangeType *r1,
+									   const RangeType *r2, bool strict);
+extern RangeType *range_minus_internal(TypeCacheEntry *typcache, const RangeType *r1,
+									   const RangeType *r2);
+extern RangeType *range_intersect_internal(TypeCacheEntry *typcache, const RangeType *r1,
+										   const RangeType *r2);
 
 /* assorted support functions */
 extern TypeCacheEntry *range_get_typcache(FunctionCallInfo fcinfo,
@@ -127,15 +141,20 @@ extern void range_deserialize(TypeCacheEntry *typcache, const RangeType *range,
 							  RangeBound *lower, RangeBound *upper,
 							  bool *empty);
 extern char range_get_flags(const RangeType *range);
+extern bool range_has_flag(const RangeType *range, char flag);
 extern void range_set_contain_empty(RangeType *range);
 extern RangeType *make_range(TypeCacheEntry *typcache, RangeBound *lower,
 							 RangeBound *upper, bool empty);
 extern int range_cmp_bounds(TypeCacheEntry *typcache, const RangeBound *b1,
-							const RangeBound *b2);
+							 const RangeBound *b2);
 extern int range_cmp_bound_values(TypeCacheEntry *typcache, const RangeBound *b1,
-								  const RangeBound *b2);
-extern bool bounds_adjacent(TypeCacheEntry *typcache, RangeBound bound1,
-							RangeBound bound2);
+								   const RangeBound *b2);
+extern int range_compare(const void *key1, const void *key2, void *arg);
+extern bool bounds_adjacent(TypeCacheEntry *typcache, const RangeBound bound1,
+							const RangeBound bound2);
 extern RangeType *make_empty_range(TypeCacheEntry *typcache);
+extern bool range_split_internal(TypeCacheEntry *typcache, const RangeType *r1,
+								 const RangeType *r2, RangeType **output1,
+								 RangeType **output2);
 
 #endif							/* RANGETYPES_H */
diff --git a/src/test/regress/expected/dependency.out b/src/test/regress/expected/dependency.out
index b4a11b8aa9..778699a961 100644
--- a/src/test/regress/expected/dependency.out
+++ b/src/test/regress/expected/dependency.out
@@ -141,6 +141,7 @@ owner of table deptest
 owner of function deptest_func()
 owner of type deptest_enum
 owner of type deptest_range
+owner of type deptest_multirange
 owner of table deptest2
 owner of sequence ss1
 owner of type deptest_t
diff --git a/src/test/regress/expected/multirangetypes.out b/src/test/regress/expected/multirangetypes.out
index 5749537635..2276d6d002 100644
--- a/src/test/regress/expected/multirangetypes.out
+++ b/src/test/regress/expected/multirangetypes.out
@@ -290,3 +290,2400 @@ select textmultirange(textrange('a', 'c'), textrange('b', 'd'));
  {[a,d)}
 (1 row)
 
+--
+-- create some test data and test the operators
+--
+CREATE TABLE nummultirange_test (nmr NUMMULTIRANGE);
+CREATE INDEX nummultirange_test_btree ON nummultirange_test(nmr);
+INSERT INTO nummultirange_test VALUES('{}');
+INSERT INTO nummultirange_test VALUES('{[,)}');
+INSERT INTO nummultirange_test VALUES('{[3,]}');
+INSERT INTO nummultirange_test VALUES('{[,), [3,]}');
+INSERT INTO nummultirange_test VALUES('{[, 5)}');
+INSERT INTO nummultirange_test VALUES(nummultirange());
+INSERT INTO nummultirange_test VALUES(nummultirange(variadic '{}'::numrange[]));
+INSERT INTO nummultirange_test VALUES(nummultirange(numrange(1.1, 2.2)));
+INSERT INTO nummultirange_test VALUES('{empty}');
+INSERT INTO nummultirange_test VALUES(nummultirange(numrange(1.7, 1.7, '[]'), numrange(1.7, 1.9)));
+INSERT INTO nummultirange_test VALUES(nummultirange(numrange(1.7, 1.7, '[]'), numrange(1.9, 2.1)));
+SELECT nmr, isempty(nmr), lower(nmr), upper(nmr) FROM nummultirange_test ORDER BY nmr;
+          nmr          | isempty | lower | upper 
+-----------------------+---------+-------+-------
+ {}                    | t       |       |      
+ {}                    | t       |       |      
+ {}                    | t       |       |      
+ {}                    | t       |       |      
+ {(,5)}                | f       |       |     5
+ {(,)}                 | f       |       |      
+ {(,)}                 | f       |       |      
+ {[1.1,2.2)}           | f       |   1.1 |   2.2
+ {[1.7,1.7],[1.9,2.1)} | f       |   1.7 |   2.1
+ {[1.7,1.9)}           | f       |   1.7 |   1.9
+ {[3,)}                | f       |     3 |      
+(11 rows)
+
+SELECT nmr, lower_inc(nmr), lower_inf(nmr), upper_inc(nmr), upper_inf(nmr) FROM nummultirange_test ORDER BY nmr;
+          nmr          | lower_inc | lower_inf | upper_inc | upper_inf 
+-----------------------+-----------+-----------+-----------+-----------
+ {}                    | f         | f         | f         | f
+ {}                    | f         | f         | f         | f
+ {}                    | f         | f         | f         | f
+ {}                    | f         | f         | f         | f
+ {(,5)}                | f         | t         | f         | f
+ {(,)}                 | f         | t         | f         | t
+ {(,)}                 | f         | t         | f         | t
+ {[1.1,2.2)}           | t         | f         | f         | f
+ {[1.7,1.7],[1.9,2.1)} | t         | f         | f         | f
+ {[1.7,1.9)}           | t         | f         | f         | f
+ {[3,)}                | t         | f         | f         | t
+(11 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr = '{}';
+ nmr 
+-----
+ {}
+ {}
+ {}
+ {}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr = '{(,5)}';
+  nmr   
+--------
+ {(,5)}
+(1 row)
+
+SELECT * FROM nummultirange_test WHERE nmr = '{[3,)}';
+  nmr   
+--------
+ {[3,)}
+(1 row)
+
+SELECT * FROM nummultirange_test WHERE nmr = '{[1.7,1.7]}';
+ nmr 
+-----
+(0 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr = '{[1.7,1.7],[1.9,2.1)}';
+          nmr          
+-----------------------
+ {[1.7,1.7],[1.9,2.1)}
+(1 row)
+
+SELECT * FROM nummultirange_test WHERE nmr < '{}';
+ nmr 
+-----
+(0 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr < '{[-1000.0, -1000.0]}';
+  nmr   
+--------
+ {}
+ {(,)}
+ {(,)}
+ {(,5)}
+ {}
+ {}
+ {}
+(7 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr < '{[0.0, 1.0]}';
+  nmr   
+--------
+ {}
+ {(,)}
+ {(,)}
+ {(,5)}
+ {}
+ {}
+ {}
+(7 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr < '{[1000.0, 1001.0]}';
+          nmr          
+-----------------------
+ {}
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+ {}
+ {}
+ {[1.1,2.2)}
+ {}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(11 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr <= '{}';
+ nmr 
+-----
+ {}
+ {}
+ {}
+ {}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr <= '{[3,)}';
+          nmr          
+-----------------------
+ {}
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+ {}
+ {}
+ {[1.1,2.2)}
+ {}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(11 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr >= '{}';
+          nmr          
+-----------------------
+ {}
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+ {}
+ {}
+ {[1.1,2.2)}
+ {}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(11 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr >= '{[3,)}';
+  nmr   
+--------
+ {[3,)}
+(1 row)
+
+SELECT * FROM nummultirange_test WHERE nmr > '{}';
+          nmr          
+-----------------------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+ {[1.1,2.2)}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(7 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr > '{[-1000.0, -1000.0]}';
+          nmr          
+-----------------------
+ {[3,)}
+ {[1.1,2.2)}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr > '{[0.0, 1.0]}';
+          nmr          
+-----------------------
+ {[3,)}
+ {[1.1,2.2)}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr > '{[1000.0, 1001.0]}';
+ nmr 
+-----
+(0 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr <> '{}';
+          nmr          
+-----------------------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+ {[1.1,2.2)}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(7 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr <> '{(,5)}';
+          nmr          
+-----------------------
+ {}
+ {(,)}
+ {[3,)}
+ {(,)}
+ {}
+ {}
+ {[1.1,2.2)}
+ {}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(10 rows)
+
+select nummultirange(numrange(2.0, 1.0));
+ERROR:  range lower bound must be less than or equal to range upper bound
+select nummultirange(numrange(5.0, 6.0), numrange(1.0, 2.0));
+     nummultirange     
+-----------------------
+ {[1.0,2.0),[5.0,6.0)}
+(1 row)
+
+-- overlaps
+SELECT * FROM nummultirange_test WHERE range_overlaps_multirange(numrange(4.0, 4.2), nmr);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE numrange(4.0, 4.2) && nmr;
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE multirange_overlaps_range(nmr, numrange(4.0, 4.2));
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr && numrange(4.0, 4.2);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE multirange_overlaps_multirange(nmr, nummultirange(numrange(4.0, 4.2), numrange(6.0, 7.0)));
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr && nummultirange(numrange(4.0, 4.2), numrange(6.0, 7.0));
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr && nummultirange(numrange(6.0, 7.0));
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+(3 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr && nummultirange(numrange(6.0, 7.0), numrange(8.0, 9.0));
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+(3 rows)
+
+-- mr contains x
+SELECT * FROM nummultirange_test WHERE multirange_contains_elem(nmr, 4.0);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr @> 4.0;
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE multirange_contains_range(nmr, numrange(4.0, 4.2));
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr @> numrange(4.0, 4.2);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE multirange_contains_multirange(nmr, '{[4.0,4.2), [6.0, 8.0)}');
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+(3 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr @> '{[4.0,4.2), [6.0, 8.0)}'::nummultirange;
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+(3 rows)
+
+-- x is contained by mr
+SELECT * FROM nummultirange_test WHERE elem_contained_by_multirange(4.0, nmr);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE 4.0 <@ nmr;
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE range_contained_by_multirange(numrange(4.0, 4.2), nmr);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE numrange(4.0, 4.2) <@ nmr;
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE multirange_contained_by_multirange('{[4.0,4.2), [6.0, 8.0)}', nmr);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+(3 rows)
+
+SELECT * FROM nummultirange_test WHERE '{[4.0,4.2), [6.0, 8.0)}'::nummultirange <@ nmr;
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+(3 rows)
+
+-- overlaps
+SELECT 'empty'::numrange && nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT 'empty'::numrange && nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() && 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) && 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() && nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() && nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) && nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) && nummultirange(numrange(1,2), numrange(7,8));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(7,8)) && nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) && nummultirange(numrange(1,2), numrange(3.5,8));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(3.5,8)) && numrange(3,4);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(3.5,8)) && nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+-- contains
+SELECT nummultirange() @> nummultirange();
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange() @> 'empty'::numrange;
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(null,null)) @> numrange(1,2);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(null,null)) @> numrange(null,2);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(null,null)) @> numrange(2,null);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(null,5)) @> numrange(null,3);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(null,5)) @> numrange(null,8);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(5,null)) @> numrange(8,null);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(5,null)) @> numrange(3,null);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,5)) @> numrange(8,9);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,5)) @> numrange(3,9);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,5)) @> numrange(1,4);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,5)) @> numrange(1,5);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(-4,-2), numrange(1,5)) @> numrange(1,5);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,5), numrange(8,9)) @> numrange(1,5);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,5), numrange(8,9)) @> numrange(6,7);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,5), numrange(6,9)) @> numrange(6,7);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[1,5)}'::nummultirange @> '{[1,5)}';
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[-4,-2), [1,5)}'::nummultirange @> '{[1,5)}';
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[1,5), [8,9)}'::nummultirange @> '{[1,5)}';
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[1,5), [8,9)}'::nummultirange @> '{[6,7)}';
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT '{[1,5), [6,9)}'::nummultirange @> '{[6,7)}';
+ ?column? 
+----------
+ t
+(1 row)
+
+-- is contained by
+SELECT nummultirange() <@ nummultirange();
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT 'empty'::numrange <@ nummultirange();
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,2) <@ nummultirange(numrange(null,null));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(null,2) <@ nummultirange(numrange(null,null));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(2,null) <@ nummultirange(numrange(null,null));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(null,3) <@ nummultirange(numrange(null,5));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(null,8) <@ nummultirange(numrange(null,5));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(8,null) <@ nummultirange(numrange(5,null));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(3,null) <@ nummultirange(numrange(5,null));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(8,9) <@ nummultirange(numrange(1,5));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(3,9) <@ nummultirange(numrange(1,5));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(1,4) <@ nummultirange(numrange(1,5));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,5) <@ nummultirange(numrange(1,5));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,5) <@ nummultirange(numrange(-4,-2), numrange(1,5));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,5) <@ nummultirange(numrange(1,5), numrange(8,9));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(6,7) <@ nummultirange(numrange(1,5), numrange(8,9));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(6,7) <@ nummultirange(numrange(1,5), numrange(6,9));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[1,5)}' <@ '{[1,5)}'::nummultirange;
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[1,5)}' <@ '{[-4,-2), [1,5)}'::nummultirange;
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[1,5)}' <@ '{[1,5), [8,9)}'::nummultirange;
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[6,7)}' <@ '{[1,5), [8,9)}'::nummultirange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT '{[6,7)}' <@ '{[1,5), [6,9)}'::nummultirange;
+ ?column? 
+----------
+ t
+(1 row)
+
+-- overleft
+SELECT 'empty'::numrange &< nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT 'empty'::numrange &< nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() &< 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) &< 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() &< nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) &< nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() &< nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(6,7) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(1,2) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,4) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,6) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(3.5,6) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(6,7)) &< numrange(3,4);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) &< numrange(3,4);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,4)) &< numrange(3,4);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,6)) &< numrange(3,4);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3.5,6)) &< numrange(3,4);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(6,7)) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,4)) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,6)) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3.5,6)) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+-- overright
+SELECT nummultirange() &> 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) &> 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT 'empty'::numrange &> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT 'empty'::numrange &> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() &> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() &> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) &> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> numrange(6,7);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> numrange(1,2);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> numrange(1,4);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> numrange(1,6);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> numrange(3.5,6);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(3,4) &> nummultirange(numrange(6,7));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(3,4) &> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(3,4) &> nummultirange(numrange(1,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(3,4) &> nummultirange(numrange(1,6));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(3,4) &> nummultirange(numrange(3.5,6));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(6,7));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(1,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(1,6));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(3.5,6));
+ ?column? 
+----------
+ f
+(1 row)
+
+-- meets
+SELECT 'empty'::numrange -|- nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT 'empty'::numrange -|- nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() -|- 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() -|- nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() -|- nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(1,2) -|- nummultirange(numrange(2,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,2) -|- nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- numrange(2,4);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- numrange(3,4);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- nummultirange(numrange(2,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(5,6)) -|- nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(5,6)) -|- nummultirange(numrange(6,7));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(5,6)) -|- nummultirange(numrange(8,9));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- nummultirange(numrange(2,4), numrange(6,7));
+ ?column? 
+----------
+ t
+(1 row)
+
+-- strictly left
+select 'empty'::numrange << nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(1,2) << nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(1,2) << nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+select numrange(1,2) << nummultirange(numrange(0,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(1,2) << nummultirange(numrange(0,4), numrange(7,8));
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange() << 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange() << numrange(1,2);
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(3,4)) << numrange(3,6);
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(0,2)) << numrange(3,6);
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange(numrange(0,2), numrange(7,8)) << numrange(3,6);
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(-4,-2), numrange(0,2)) << numrange(3,6);
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange() << nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange() << nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(1,2)) << nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(1,2)) << nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(1,2)) << nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange(numrange(1,2)) << nummultirange(numrange(3,4), numrange(7,8));
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange(numrange(1,2), numrange(4,5)) << nummultirange(numrange(3,4), numrange(7,8));
+ ?column? 
+----------
+ f
+(1 row)
+
+-- strictly right
+select nummultirange() >> 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange() >> numrange(1,2);
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(3,4)) >> numrange(1,2);
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange(numrange(0,4)) >> numrange(1,2);
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(0,4), numrange(7,8)) >> numrange(1,2);
+ ?column? 
+----------
+ f
+(1 row)
+
+select 'empty'::numrange >> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(1,2) >> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(3,6) >> nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(3,6) >> nummultirange(numrange(0,2));
+ ?column? 
+----------
+ t
+(1 row)
+
+select numrange(3,6) >> nummultirange(numrange(0,2), numrange(7,8));
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(3,6) >> nummultirange(numrange(-4,-2), numrange(0,2));
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange() >> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(1,2)) >> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange() >> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(1,2)) >> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(3,4)) >> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange(numrange(3,4), numrange(7,8)) >> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange(numrange(3,4), numrange(7,8)) >> nummultirange(numrange(1,2), numrange(4,5));
+ ?column? 
+----------
+ f
+(1 row)
+
+-- union
+SELECT 'empty'::numrange @+ 'empty'::numrange;
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT 'empty'::numrange @+ nummultirange();
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange() @+ 'empty'::numrange;
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange() @+ nummultirange();
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT 'empty'::numrange @+ numrange(1,2);
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT 'empty'::numrange @+ nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT numrange(1,2) @+ nummultirange();
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) @+ 'empty'::numrange;
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT numrange(1,2) @+ 'empty'::numrange;
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange() @+ numrange(1,2);
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange() @+ nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) @+ nummultirange();
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT numrange(1,2) @+ numrange(1,2);
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT numrange(1,2) @+ numrange(2,4);
+ ?column? 
+----------
+ {[1,4)}
+(1 row)
+
+SELECT numrange(1,2) @+ numrange(3,4);
+   ?column?    
+---------------
+ {[1,2),[3,4)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) @+ nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) @+ nummultirange(numrange(2,4));
+ ?column? 
+----------
+ {[1,4)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) @+ nummultirange(numrange(3,4));
+   ?column?    
+---------------
+ {[1,2),[3,4)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) @+ nummultirange(numrange(2,4));
+ ?column? 
+----------
+ {[1,5)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) @+ nummultirange(numrange(3,4));
+   ?column?    
+---------------
+ {[1,2),[3,5)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) @+ nummultirange(numrange(0,9));
+ ?column? 
+----------
+ {[0,9)}
+(1 row)
+
+-- merge
+SELECT range_merge(nummultirange());
+ range_merge 
+-------------
+ empty
+(1 row)
+
+SELECT range_merge(nummultirange(numrange(1,2)));
+ range_merge 
+-------------
+ [1,2)
+(1 row)
+
+SELECT range_merge(nummultirange(numrange(1,2), numrange(7,8)));
+ range_merge 
+-------------
+ [1,8)
+(1 row)
+
+-- minus
+SELECT 'empty'::numrange @- 'empty'::numrange;
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT 'empty'::numrange @- nummultirange();
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange() @- 'empty'::numrange;
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange() @- nummultirange();
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT 'empty'::numrange @- numrange(1,2);
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT 'empty'::numrange @- nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT numrange(1,2) @- nummultirange();
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) @- 'empty'::numrange;
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT 'empty'::numrange @- numrange(1,2);
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT numrange(1,2) @- 'empty'::numrange;
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange() @- numrange(1,2);
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange() @- nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) @- nummultirange();
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT numrange(1,3) @- numrange(1,3);
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT numrange(1,3) @- numrange(1,2);
+ ?column? 
+----------
+ {[2,3)}
+(1 row)
+
+SELECT numrange(1,3) @- numrange(2,4);
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT numrange(1,3) @- numrange(3,4);
+ ?column? 
+----------
+ {[1,3)}
+(1 row)
+
+SELECT numrange(1,3) @- nummultirange(numrange(1,3));
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT numrange(1,3) @- nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {[2,3)}
+(1 row)
+
+SELECT numrange(1,3) @- nummultirange(numrange(2,4));
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT numrange(1,3) @- nummultirange(numrange(3,4));
+ ?column? 
+----------
+ {[1,3)}
+(1 row)
+
+SELECT numrange(1,10) @- nummultirange(numrange(1,2), numrange(4,5));
+    ?column?    
+----------------
+ {[2,4),[5,10)}
+(1 row)
+
+SELECT nummultirange(numrange(1,10)) @- numrange(0,2);
+ ?column? 
+----------
+ {[2,10)}
+(1 row)
+
+SELECT nummultirange(numrange(1,10)) @- numrange(1,2);
+ ?column? 
+----------
+ {[2,10)}
+(1 row)
+
+SELECT nummultirange(numrange(1,10)) @- numrange(3,4);
+    ?column?    
+----------------
+ {[1,3),[4,10)}
+(1 row)
+
+SELECT nummultirange(numrange(1,10)) @- numrange(13,18);
+ ?column? 
+----------
+ {[1,10)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(3,4)) @- nummultirange();
+   ?column?    
+---------------
+ {[1,2),[3,4)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) @- nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) @- nummultirange(numrange(2,4));
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) @- nummultirange(numrange(3,4));
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,4)) @- nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {[2,4)}
+(1 row)
+
+SELECT nummultirange(numrange(1,4)) @- nummultirange(numrange(2,3));
+   ?column?    
+---------------
+ {[1,2),[3,4)}
+(1 row)
+
+SELECT nummultirange(numrange(1,4)) @- nummultirange(numrange(0,8));
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange(numrange(1,4)) @- nummultirange(numrange(0,2));
+ ?column? 
+----------
+ {[2,4)}
+(1 row)
+
+SELECT nummultirange(numrange(1,8)) @- nummultirange(numrange(0,2), numrange(3,4));
+   ?column?    
+---------------
+ {[2,3),[4,8)}
+(1 row)
+
+SELECT nummultirange(numrange(1,8)) @- nummultirange(numrange(2,3), numrange(5,null));
+   ?column?    
+---------------
+ {[1,2),[3,5)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) @- nummultirange(numrange(-2,0));
+   ?column?    
+---------------
+ {[1,2),[4,5)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) @- nummultirange(numrange(2,4));
+   ?column?    
+---------------
+ {[1,2),[4,5)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) @- nummultirange(numrange(3,5));
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) @- nummultirange(numrange(0,9));
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange(numrange(1,3), numrange(4,5)) @- nummultirange(numrange(2,9));
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) @- nummultirange(numrange(8,9));
+   ?column?    
+---------------
+ {[1,2),[4,5)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) @- nummultirange(numrange(-2,0), numrange(8,9));
+   ?column?    
+---------------
+ {[1,2),[4,5)}
+(1 row)
+
+-- intersection
+SELECT 'empty'::numrange @* 'empty'::numrange;
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT 'empty'::numrange @* nummultirange();
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange() @* 'empty'::numrange;
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange() @* nummultirange();
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT 'empty'::numrange @* numrange(1,2);
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT 'empty'::numrange @* nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT numrange(1,2) @* 'empty'::numrange;
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT numrange(1,2) @* nummultirange();
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) @* 'empty'::numrange;
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange() @* numrange(1,2);
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange() @* nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) @* nummultirange();
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT numrange(1,3) @* numrange(1,3);
+ ?column? 
+----------
+ {[1,3)}
+(1 row)
+
+SELECT numrange(1,3) @* numrange(1,2);
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT numrange(1,3) @* numrange(1,5);
+ ?column? 
+----------
+ {[1,3)}
+(1 row)
+
+SELECT numrange(1,3) @* numrange(2,5);
+ ?column? 
+----------
+ {[2,3)}
+(1 row)
+
+SELECT numrange(1,5) @* numrange(2,3);
+ ?column? 
+----------
+ {[2,3)}
+(1 row)
+
+SELECT '{[1,3)}'::nummultirange @* numrange(1,3);
+ ?column? 
+----------
+ {[1,3)}
+(1 row)
+
+SELECT '{[1,3)}'::nummultirange @* numrange(1,2);
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT '{[1,3)}'::nummultirange @* numrange(1,5);
+ ?column? 
+----------
+ {[1,3)}
+(1 row)
+
+SELECT '{[1,3)}'::nummultirange @* numrange(2,5);
+ ?column? 
+----------
+ {[2,3)}
+(1 row)
+
+SELECT '{[1,5)}'::nummultirange @* numrange(2,3);
+ ?column? 
+----------
+ {[2,3)}
+(1 row)
+
+SELECT numrange(1,3) @* '{[1,3)}'::nummultirange;
+ ?column? 
+----------
+ {[1,3)}
+(1 row)
+
+SELECT numrange(1,2) @* '{[1,3)}'::nummultirange;
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT numrange(1,5) @* '{[1,3)}'::nummultirange;
+ ?column? 
+----------
+ {[1,3)}
+(1 row)
+
+SELECT numrange(2,5) @* '{[1,3)}'::nummultirange;
+ ?column? 
+----------
+ {[2,3)}
+(1 row)
+
+SELECT numrange(2,3) @* '{[1,5)}'::nummultirange;
+ ?column? 
+----------
+ {[2,3)}
+(1 row)
+
+SELECT '{[1,3)}'::nummultirange @* '{[1,5)}'::nummultirange;
+ ?column? 
+----------
+ {[1,3)}
+(1 row)
+
+SELECT '{[1,3)}'::nummultirange @* '{[0,5)}'::nummultirange;
+ ?column? 
+----------
+ {[1,3)}
+(1 row)
+
+SELECT '{[1,3)}'::nummultirange @* '{[0,2)}'::nummultirange;
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT '{[1,3)}'::nummultirange @* '{[2,5)}'::nummultirange;
+ ?column? 
+----------
+ {[2,3)}
+(1 row)
+
+SELECT '{[1,4)}'::nummultirange @* '{[2,3)}'::nummultirange;
+ ?column? 
+----------
+ {[2,3)}
+(1 row)
+
+SELECT '{[1,4)}'::nummultirange @* '{[0,2), [3,5)}'::nummultirange;
+   ?column?    
+---------------
+ {[1,2),[3,4)}
+(1 row)
+
+SELECT '{[1,4), [7,10)}'::nummultirange @* '{[0,8), [9,12)}'::nummultirange;
+       ?column?       
+----------------------
+ {[1,4),[7,8),[9,10)}
+(1 row)
+
+SELECT '{[1,4), [7,10)}'::nummultirange @* '{[9,12)}'::nummultirange;
+ ?column? 
+----------
+ {[9,10)}
+(1 row)
+
+SELECT '{[1,4), [7,10)}'::nummultirange @* '{[-5,-4), [5,6), [9,12)}'::nummultirange;
+ ?column? 
+----------
+ {[9,10)}
+(1 row)
+
+SELECT '{[1,4), [7,10)}'::nummultirange @* '{[0,2), [3,8), [9,12)}'::nummultirange;
+          ?column?          
+----------------------------
+ {[1,2),[3,4),[7,8),[9,10)}
+(1 row)
+
+SELECT '{[1,4), [7,10)}'::nummultirange @* '{[0,2), [3,8), [9,12)}'::nummultirange;
+          ?column?          
+----------------------------
+ {[1,2),[3,4),[7,8),[9,10)}
+(1 row)
+
+--
+-- range_agg function
+--
+create table reservations ( room_id integer not null, booked_during daterange );
+insert into reservations values
+-- 1: has a meets and a gap
+(1, daterange('2018-07-01', '2018-07-07')),
+(1, daterange('2018-07-07', '2018-07-14')),
+(1, daterange('2018-07-20', '2018-07-22')),
+-- 2: just a single row
+(2, daterange('2018-07-01', '2018-07-03')),
+-- 3: one null range
+(3, NULL),
+-- 4: two null ranges
+(4, NULL),
+(4, NULL),
+-- 5: a null range and a non-null range
+(5, NULL),
+(5, daterange('2018-07-01', '2018-07-03')),
+-- 6: has overlap
+(6, daterange('2018-07-01', '2018-07-07')),
+(6, daterange('2018-07-05', '2018-07-10')),
+-- 7: two ranges that meet: no gap or overlap
+(7, daterange('2018-07-01', '2018-07-07')),
+(7, daterange('2018-07-07', '2018-07-14')),
+-- 8: an empty range
+(8, 'empty'::daterange)
+;
+SELECT   room_id, range_agg(booked_during)
+FROM     reservations
+GROUP BY room_id
+ORDER BY room_id;
+ room_id |                     range_agg                     
+---------+---------------------------------------------------
+       1 | {[07-01-2018,07-14-2018),[07-20-2018,07-22-2018)}
+       2 | {[07-01-2018,07-03-2018)}
+       3 | 
+       4 | 
+       5 | {[07-01-2018,07-03-2018)}
+       6 | {[07-01-2018,07-10-2018)}
+       7 | {[07-01-2018,07-14-2018)}
+       8 | {}
+(8 rows)
+
+-- range_agg on a custom range type too
+SELECT  range_agg(r)
+FROM    (VALUES
+          ('[a,c]'::textrange),
+          ('[b,b]'::textrange),
+          ('[c,f]'::textrange),
+          ('[g,h)'::textrange),
+          ('[h,j)'::textrange)
+        ) t(r);
+   range_agg   
+---------------
+ {[a,f],[g,j)}
+(1 row)
+
+select range_intersect_agg(nmr) from nummultirange_test;
+ range_intersect_agg 
+---------------------
+ {}
+(1 row)
+
+select range_intersect_agg(nmr) from nummultirange_test where false;
+ range_intersect_agg 
+---------------------
+ 
+(1 row)
+
+-- test with just one input:
+select range_intersect_agg(nmr) from (values ('{[1,2]}'::nummultirange)) t(nmr);
+ range_intersect_agg 
+---------------------
+ {[1,2]}
+(1 row)
+
+select range_intersect_agg(nmr) from nummultirange_test where nmr @> 4.0;
+ range_intersect_agg 
+---------------------
+ {[3,5)}
+(1 row)
+
+create table nummultirange_test2(nmr nummultirange);
+create index nummultirange_test2_hash_idx on nummultirange_test2 using hash (nmr);
+INSERT INTO nummultirange_test2 VALUES('{[, 5)}');
+INSERT INTO nummultirange_test2 VALUES(nummultirange(numrange(1.1, 2.2)));
+INSERT INTO nummultirange_test2 VALUES(nummultirange(numrange(1.1, 2.2)));
+INSERT INTO nummultirange_test2 VALUES(nummultirange(numrange(1.1, 2.2,'()')));
+INSERT INTO nummultirange_test2 VALUES('{}');
+select * from nummultirange_test2 where nmr = '{}';
+ nmr 
+-----
+ {}
+(1 row)
+
+select * from nummultirange_test2 where nmr = nummultirange(numrange(1.1, 2.2));
+     nmr     
+-------------
+ {[1.1,2.2)}
+ {[1.1,2.2)}
+(2 rows)
+
+select * from nummultirange_test2 where nmr = nummultirange(numrange(1.1, 2.3));
+ nmr 
+-----
+(0 rows)
+
+set enable_nestloop=t;
+set enable_hashjoin=f;
+set enable_mergejoin=f;
+select * from nummultirange_test natural join nummultirange_test2 order by nmr;
+     nmr     
+-------------
+ {}
+ {}
+ {}
+ {}
+ {(,5)}
+ {[1.1,2.2)}
+ {[1.1,2.2)}
+(7 rows)
+
+set enable_nestloop=f;
+set enable_hashjoin=t;
+set enable_mergejoin=f;
+select * from nummultirange_test natural join nummultirange_test2 order by nmr;
+     nmr     
+-------------
+ {}
+ {}
+ {}
+ {}
+ {(,5)}
+ {[1.1,2.2)}
+ {[1.1,2.2)}
+(7 rows)
+
+set enable_nestloop=f;
+set enable_hashjoin=f;
+set enable_mergejoin=t;
+select * from nummultirange_test natural join nummultirange_test2 order by nmr;
+     nmr     
+-------------
+ {}
+ {}
+ {}
+ {}
+ {(,5)}
+ {[1.1,2.2)}
+ {[1.1,2.2)}
+(7 rows)
+
+set enable_nestloop to default;
+set enable_hashjoin to default;
+set enable_mergejoin to default;
+DROP TABLE nummultirange_test2;
+--
+-- Test user-defined multirange of floats
+--
+select '{[123.001, 5.e9)}'::float8multirange @> 888.882::float8;
+ ?column? 
+----------
+ t
+(1 row)
+
+create table float8multirange_test(f8mr float8multirange, i int);
+insert into float8multirange_test values(float8multirange(float8range(-100.00007, '1.111113e9')), 42);
+select * from float8multirange_test;
+           f8mr            | i  
+---------------------------+----
+ {[-100.00007,1111113000)} | 42
+(1 row)
+
+drop table float8multirange_test;
+--
+-- Test multirange types over domains
+--
+create domain mydomain as int4;
+create type mydomainrange as range(subtype=mydomain);
+select '{[4,50)}'::mydomainmultirange @> 7::mydomain;
+ ?column? 
+----------
+ t
+(1 row)
+
+drop domain mydomain cascade;
+NOTICE:  drop cascades to type mydomainrange
+--
+-- Test domains over multirange types
+--
+create domain restrictedmultirange as int4multirange check (upper(value) < 10);
+select '{[4,5)}'::restrictedmultirange @> 7;
+ ?column? 
+----------
+ f
+(1 row)
+
+select '{[4,50)}'::restrictedmultirange @> 7; -- should fail
+ERROR:  value for domain restrictedmultirange violates check constraint "restrictedmultirange_check"
+drop domain restrictedmultirange;
+--
+-- Test multiple multirange types over the same subtype
+--
+create type textrange1 as range(subtype=text, collation="C");
+create type textrange2 as range(subtype=text, collation="C");
+select textmultirange1(textrange2('a','Z'));  -- should fail
+ERROR:  function textmultirange1(textrange2) does not exist
+LINE 1: select textmultirange1(textrange2('a','Z'));
+               ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+select textmultirange1(textrange1('a','Z')) @> 'b'::text;
+ERROR:  range lower bound must be less than or equal to range upper bound
+select textmultirange2(textrange2('a','z')) @> 'b'::text;
+ ?column? 
+----------
+ t
+(1 row)
+
+drop type textrange1;
+drop type textrange2;
+--
+-- Test polymorphic type system
+--
+create function anyarray_anymultirange_func(a anyarray, r anymultirange)
+  returns anyelement as 'select $1[1] + lower($2);' language sql;
+select anyarray_anymultirange_func(ARRAY[1,2], int4multirange(int4range(10,20)));
+ anyarray_anymultirange_func 
+-----------------------------
+                          11
+(1 row)
+
+-- should fail
+select anyarray_anymultirange_func(ARRAY[1,2], nummultirange(numrange(10,20)));
+ERROR:  function anyarray_anymultirange_func(integer[], nummultirange) does not exist
+LINE 1: select anyarray_anymultirange_func(ARRAY[1,2], nummultirange...
+               ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+drop function anyarray_anymultirange_func(anyarray, anymultirange);
+-- should fail
+create function bogus_func(anyelement)
+  returns anymultirange as 'select int4multirange(int4range(1,10))' language sql;
+ERROR:  cannot determine result data type
+DETAIL:  A function returning "anyrange" or "anymultirange" must have at least one "anyrange" or "anymultirange" argument.
+-- should fail
+create function bogus_func(int)
+  returns anymultirange as 'select int4multirange(int4range(1,10))' language sql;
+ERROR:  cannot determine result data type
+DETAIL:  A function returning a polymorphic type must have at least one polymorphic argument.
+create function range_add_bounds(anymultirange)
+  returns anyelement as 'select lower($1) + upper($1)' language sql;
+select range_add_bounds(int4multirange(int4range(1, 17)));
+ range_add_bounds 
+------------------
+               18
+(1 row)
+
+select range_add_bounds(nummultirange(numrange(1.0001, 123.123)));
+ range_add_bounds 
+------------------
+         124.1231
+(1 row)
+
+create function multirangetypes_sql(q anymultirange, b anyarray, out c anyelement)
+  as $$ select upper($1) + $2[1] $$
+  language sql;
+select multirangetypes_sql(int4multirange(int4range(1,10)), ARRAY[2,20]);
+ multirangetypes_sql 
+---------------------
+                  12
+(1 row)
+
+select multirangetypes_sql(nummultirange(numrange(1,10)), ARRAY[2,20]);  -- match failure
+ERROR:  function multirangetypes_sql(nummultirange, integer[]) does not exist
+LINE 1: select multirangetypes_sql(nummultirange(numrange(1,10)), AR...
+               ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+--
+-- Arrays of multiranges
+--
+select ARRAY[nummultirange(numrange(1.1, 1.2)), nummultirange(numrange(12.3, 155.5))];
+              array               
+----------------------------------
+ {"{[1.1,1.2)}","{[12.3,155.5)}"}
+(1 row)
+
+create table i8mr_array (f1 int, f2 int8multirange[]);
+insert into i8mr_array values (42, array[int8multirange(int8range(1,10)), int8multirange(int8range(2,20))]);
+select * from i8mr_array;
+ f1 |           f2            
+----+-------------------------
+ 42 | {"{[1,10)}","{[2,20)}"}
+(1 row)
+
+drop table i8mr_array;
+--
+-- Multiranges of arrays
+--
+select arraymultirange(arrayrange(ARRAY[1,2], ARRAY[2,1]));
+   arraymultirange   
+---------------------
+ {["{1,2}","{2,1}")}
+(1 row)
+
+select arraymultirange(arrayrange(ARRAY[2,1], ARRAY[1,2]));  -- fail
+ERROR:  range lower bound must be less than or equal to range upper bound
+select array[1,1] <@ arraymultirange(arrayrange(array[1,2], array[2,1]));
+ ?column? 
+----------
+ f
+(1 row)
+
+select array[1,3] <@ arraymultirange(arrayrange(array[1,2], array[2,1]));
+ ?column? 
+----------
+ t
+(1 row)
+
+--
+-- Ranges of composites
+--
+create type two_ints as (a int, b int);
+create type two_ints_range as range (subtype = two_ints);
+-- with force_parallel_mode on, this exercises tqueue.c's range remapping
+select *, row_to_json(upper(t)) as u from
+  (values (two_ints_multirange(two_ints_range(row(1,2), row(3,4)))),
+          (two_ints_multirange(two_ints_range(row(5,6), row(7,8))))) v(t);
+          t          |       u       
+---------------------+---------------
+ {["(1,2)","(3,4)")} | {"a":3,"b":4}
+ {["(5,6)","(7,8)")} | {"a":7,"b":8}
+(2 rows)
+
+drop type two_ints cascade;
+NOTICE:  drop cascades to type two_ints_range
+--
+-- Check behavior when subtype lacks a hash function
+--
+set enable_sort = off;  -- try to make it pick a hash setop implementation
+select '{(2,5)}'::cashmultirange except select '{(5,6)}'::cashmultirange;
+ cashmultirange  
+-----------------
+ {($2.00,$5.00)}
+(1 row)
+
+reset enable_sort;
+--
+-- OUT/INOUT/TABLE functions
+--
+-- infer anymultirange from anymultirange
+create function mr_outparam_succeed(i anymultirange, out r anymultirange, out t text)
+  as $$ select $1, 'foo'::text $$ language sql;
+select * from mr_outparam_succeed(int4multirange(int4range(1,2)));
+    r    |  t  
+---------+-----
+ {[1,2)} | foo
+(1 row)
+
+-- infer anyarray from anymultirange
+create function mr_outparam_succeed2(i anymultirange, out r anyarray, out t text)
+  as $$ select ARRAY[upper($1)], 'foo'::text $$ language sql;
+select * from mr_outparam_succeed2(int4multirange(int4range(1,2)));
+  r  |  t  
+-----+-----
+ {2} | foo
+(1 row)
+
+-- infer anyrange from anymultirange
+create function mr_outparam_succeed3(i anymultirange, out r anyrange, out t text)
+  as $$ select range_merge($1), 'foo'::text $$ language sql;
+select * from mr_outparam_succeed3(int4multirange(int4range(1,2)));
+   r   |  t  
+-------+-----
+ [1,2) | foo
+(1 row)
+
+-- infer anymultirange from anyrange
+create function mr_outparam_succeed4(i anyrange, out r anymultirange, out t text)
+  as $$ select $1 @+ $1, 'foo'::text $$ language sql;
+select * from mr_outparam_succeed4(int4range(1,2));
+    r    |  t  
+---------+-----
+ {[1,2)} | foo
+(1 row)
+
+-- infer anyelement from anymultirange
+create function mr_inoutparam_succeed(out i anyelement, inout r anymultirange)
+  as $$ select upper($1), $1 $$ language sql;
+select * from mr_inoutparam_succeed(int4multirange(int4range(1,2)));
+ i |    r    
+---+---------
+ 2 | {[1,2)}
+(1 row)
+
+-- infer anyelement+anymultirange from anyelement+anymultirange
+create function mr_table_succeed(i anyelement, r anymultirange) returns table(i anyelement, r anymultirange)
+  as $$ select $1, $2 $$ language sql;
+select * from mr_table_succeed(123, int4multirange(int4range(1,11)));
+  i  |    r     
+-----+----------
+ 123 | {[1,11)}
+(1 row)
+
+-- should fail
+create function mr_outparam_fail(i anyelement, out r anymultirange, out t text)
+  as $$ select '[1,10]', 'foo' $$ language sql;
+ERROR:  cannot determine result data type
+DETAIL:  A function returning "anyrange" or "anymultirange" must have at least one "anyrange" or "anymultirange" argument.
+--should fail
+create function mr_inoutparam_fail(inout i anyelement, out r anymultirange)
+  as $$ select $1, '[1,10]' $$ language sql;
+ERROR:  cannot determine result data type
+DETAIL:  A function returning "anyrange" or "anymultirange" must have at least one "anyrange" or "anymultirange" argument.
+--should fail
+create function mr_table_fail(i anyelement) returns table(i anyelement, r anymultirange)
+  as $$ select $1, '[1,10]' $$ language sql;
+ERROR:  cannot determine result data type
+DETAIL:  A function returning "anyrange" or "anymultirange" must have at least one "anyrange" or "anymultirange" argument.
diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out
index 916e1016b5..e818eaea79 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -1101,13 +1101,15 @@ ORDER BY 1, 2;
  ?|   | ?|
  ?||  | ?||
  @    | ~
+ @*   | @*
+ @+   | @+
  @@   | @@
  @@@  | @@@
  |    | |
  ~<=~ | ~>=~
  ~<~  | ~>~
  ~=   | ~=
-(30 rows)
+(32 rows)
 
 -- Likewise for negator pairs.
 SELECT DISTINCT o1.oprname AS op1, o2.oprname AS op2
diff --git a/src/test/regress/expected/rangetypes.out b/src/test/regress/expected/rangetypes.out
index 6fd16bddd1..1bfe426be6 100644
--- a/src/test/regress/expected/rangetypes.out
+++ b/src/test/regress/expected/rangetypes.out
@@ -518,6 +518,24 @@ select numrange(1.0, 2.0) * numrange(2.5, 3.0);
  empty
 (1 row)
 
+select range_intersect_agg(nr) from numrange_test;
+ range_intersect_agg 
+---------------------
+ empty
+(1 row)
+
+select range_intersect_agg(nr) from numrange_test where false;
+ range_intersect_agg 
+---------------------
+ 
+(1 row)
+
+select range_intersect_agg(nr) from numrange_test where nr @> 4.0;
+ range_intersect_agg 
+---------------------
+ [3,5)
+(1 row)
+
 create table numrange_test2(nr numrange);
 create index numrange_test2_hash_idx on numrange_test2 using hash (nr);
 INSERT INTO numrange_test2 VALUES('[, 5)');
@@ -1284,7 +1302,7 @@ drop function anyarray_anyrange_func(anyarray, anyrange);
 create function bogus_func(anyelement)
   returns anyrange as 'select int4range(1,10)' language sql;
 ERROR:  cannot determine result data type
-DETAIL:  A function returning "anyrange" must have at least one "anyrange" argument.
+DETAIL:  A function returning "anyrange" or "anymultirange" must have at least one "anyrange" or "anymultirange" argument.
 -- should fail
 create function bogus_func(int)
   returns anyrange as 'select int4range(1,10)' language sql;
@@ -1392,6 +1410,7 @@ reset enable_sort;
 --
 -- OUT/INOUT/TABLE functions
 --
+-- infer anyrange from anyrange
 create function outparam_succeed(i anyrange, out r anyrange, out t text)
   as $$ select $1, 'foo'::text $$ language sql;
 select * from outparam_succeed(int4range(1,2));
@@ -1400,6 +1419,16 @@ select * from outparam_succeed(int4range(1,2));
  [1,2) | foo
 (1 row)
 
+-- infer anyarray from anyrange
+create function outparam_succeed2(i anyrange, out r anyarray, out t text)
+  as $$ select ARRAY[upper($1)], 'foo'::text $$ language sql;
+select * from outparam_succeed2(int4range(int4range(1,2)));
+  r  |  t  
+-----+-----
+ {2} | foo
+(1 row)
+
+-- infer anyelement from anyrange
 create function inoutparam_succeed(out i anyelement, inout r anyrange)
   as $$ select upper($1), $1 $$ language sql;
 select * from inoutparam_succeed(int4range(1,2));
@@ -1408,6 +1437,7 @@ select * from inoutparam_succeed(int4range(1,2));
  2 | [1,2)
 (1 row)
 
+-- infer anyelement+anyrange from anyelement+anyrange
 create function table_succeed(i anyelement, r anyrange) returns table(i anyelement, r anyrange)
   as $$ select $1, $2 $$ language sql;
 select * from table_succeed(123, int4range(1,11));
@@ -1420,14 +1450,14 @@ select * from table_succeed(123, int4range(1,11));
 create function outparam_fail(i anyelement, out r anyrange, out t text)
   as $$ select '[1,10]', 'foo' $$ language sql;
 ERROR:  cannot determine result data type
-DETAIL:  A function returning "anyrange" must have at least one "anyrange" argument.
+DETAIL:  A function returning "anyrange" or "anymultirange" must have at least one "anyrange" or "anymultirange" argument.
 --should fail
 create function inoutparam_fail(inout i anyelement, out r anyrange)
   as $$ select $1, '[1,10]' $$ language sql;
 ERROR:  cannot determine result data type
-DETAIL:  A function returning "anyrange" must have at least one "anyrange" argument.
+DETAIL:  A function returning "anyrange" or "anymultirange" must have at least one "anyrange" or "anymultirange" argument.
 --should fail
 create function table_fail(i anyelement) returns table(i anyelement, r anyrange)
   as $$ select $1, '[1,10]' $$ language sql;
 ERROR:  cannot determine result data type
-DETAIL:  A function returning "anyrange" must have at least one "anyrange" argument.
+DETAIL:  A function returning "anyrange" or "anymultirange" must have at least one "anyrange" or "anymultirange" argument.
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index 070de78e85..6f7fcf6326 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -92,6 +92,7 @@ num_exp_sqrt|t
 num_exp_sub|t
 num_input_test|f
 num_result|f
+nummultirange_test|t
 onek|t
 onek2|t
 path_tbl|f
@@ -172,6 +173,7 @@ quad_poly_tbl|t
 radix_text_tbl|t
 ramp|f
 real_city|f
+reservations|f
 road|t
 shighway|t
 slow_emp4000|f
diff --git a/src/test/regress/sql/multirangetypes.sql b/src/test/regress/sql/multirangetypes.sql
index 8651ba3f3d..b3ede18f96 100644
--- a/src/test/regress/sql/multirangetypes.sql
+++ b/src/test/regress/sql/multirangetypes.sql
@@ -63,3 +63,610 @@ select textmultirange();
 select textmultirange(textrange('a', 'c'));
 select textmultirange(textrange('a', 'c'), textrange('f', 'g'));
 select textmultirange(textrange('a', 'c'), textrange('b', 'd'));
+
+--
+-- create some test data and test the operators
+--
+
+CREATE TABLE nummultirange_test (nmr NUMMULTIRANGE);
+CREATE INDEX nummultirange_test_btree ON nummultirange_test(nmr);
+
+INSERT INTO nummultirange_test VALUES('{}');
+INSERT INTO nummultirange_test VALUES('{[,)}');
+INSERT INTO nummultirange_test VALUES('{[3,]}');
+INSERT INTO nummultirange_test VALUES('{[,), [3,]}');
+INSERT INTO nummultirange_test VALUES('{[, 5)}');
+INSERT INTO nummultirange_test VALUES(nummultirange());
+INSERT INTO nummultirange_test VALUES(nummultirange(variadic '{}'::numrange[]));
+INSERT INTO nummultirange_test VALUES(nummultirange(numrange(1.1, 2.2)));
+INSERT INTO nummultirange_test VALUES('{empty}');
+INSERT INTO nummultirange_test VALUES(nummultirange(numrange(1.7, 1.7, '[]'), numrange(1.7, 1.9)));
+INSERT INTO nummultirange_test VALUES(nummultirange(numrange(1.7, 1.7, '[]'), numrange(1.9, 2.1)));
+
+SELECT nmr, isempty(nmr), lower(nmr), upper(nmr) FROM nummultirange_test ORDER BY nmr;
+SELECT nmr, lower_inc(nmr), lower_inf(nmr), upper_inc(nmr), upper_inf(nmr) FROM nummultirange_test ORDER BY nmr;
+
+SELECT * FROM nummultirange_test WHERE nmr = '{}';
+SELECT * FROM nummultirange_test WHERE nmr = '{(,5)}';
+SELECT * FROM nummultirange_test WHERE nmr = '{[3,)}';
+SELECT * FROM nummultirange_test WHERE nmr = '{[1.7,1.7]}';
+SELECT * FROM nummultirange_test WHERE nmr = '{[1.7,1.7],[1.9,2.1)}';
+SELECT * FROM nummultirange_test WHERE nmr < '{}';
+SELECT * FROM nummultirange_test WHERE nmr < '{[-1000.0, -1000.0]}';
+SELECT * FROM nummultirange_test WHERE nmr < '{[0.0, 1.0]}';
+SELECT * FROM nummultirange_test WHERE nmr < '{[1000.0, 1001.0]}';
+SELECT * FROM nummultirange_test WHERE nmr <= '{}';
+SELECT * FROM nummultirange_test WHERE nmr <= '{[3,)}';
+SELECT * FROM nummultirange_test WHERE nmr >= '{}';
+SELECT * FROM nummultirange_test WHERE nmr >= '{[3,)}';
+SELECT * FROM nummultirange_test WHERE nmr > '{}';
+SELECT * FROM nummultirange_test WHERE nmr > '{[-1000.0, -1000.0]}';
+SELECT * FROM nummultirange_test WHERE nmr > '{[0.0, 1.0]}';
+SELECT * FROM nummultirange_test WHERE nmr > '{[1000.0, 1001.0]}';
+SELECT * FROM nummultirange_test WHERE nmr <> '{}';
+SELECT * FROM nummultirange_test WHERE nmr <> '{(,5)}';
+
+select nummultirange(numrange(2.0, 1.0));
+select nummultirange(numrange(5.0, 6.0), numrange(1.0, 2.0));
+
+-- overlaps
+SELECT * FROM nummultirange_test WHERE range_overlaps_multirange(numrange(4.0, 4.2), nmr);
+SELECT * FROM nummultirange_test WHERE numrange(4.0, 4.2) && nmr;
+SELECT * FROM nummultirange_test WHERE multirange_overlaps_range(nmr, numrange(4.0, 4.2));
+SELECT * FROM nummultirange_test WHERE nmr && numrange(4.0, 4.2);
+SELECT * FROM nummultirange_test WHERE multirange_overlaps_multirange(nmr, nummultirange(numrange(4.0, 4.2), numrange(6.0, 7.0)));
+SELECT * FROM nummultirange_test WHERE nmr && nummultirange(numrange(4.0, 4.2), numrange(6.0, 7.0));
+SELECT * FROM nummultirange_test WHERE nmr && nummultirange(numrange(6.0, 7.0));
+SELECT * FROM nummultirange_test WHERE nmr && nummultirange(numrange(6.0, 7.0), numrange(8.0, 9.0));
+
+-- mr contains x
+SELECT * FROM nummultirange_test WHERE multirange_contains_elem(nmr, 4.0);
+SELECT * FROM nummultirange_test WHERE nmr @> 4.0;
+SELECT * FROM nummultirange_test WHERE multirange_contains_range(nmr, numrange(4.0, 4.2));
+SELECT * FROM nummultirange_test WHERE nmr @> numrange(4.0, 4.2);
+SELECT * FROM nummultirange_test WHERE multirange_contains_multirange(nmr, '{[4.0,4.2), [6.0, 8.0)}');
+SELECT * FROM nummultirange_test WHERE nmr @> '{[4.0,4.2), [6.0, 8.0)}'::nummultirange;
+
+-- x is contained by mr
+SELECT * FROM nummultirange_test WHERE elem_contained_by_multirange(4.0, nmr);
+SELECT * FROM nummultirange_test WHERE 4.0 <@ nmr;
+SELECT * FROM nummultirange_test WHERE range_contained_by_multirange(numrange(4.0, 4.2), nmr);
+SELECT * FROM nummultirange_test WHERE numrange(4.0, 4.2) <@ nmr;
+SELECT * FROM nummultirange_test WHERE multirange_contained_by_multirange('{[4.0,4.2), [6.0, 8.0)}', nmr);
+SELECT * FROM nummultirange_test WHERE '{[4.0,4.2), [6.0, 8.0)}'::nummultirange <@ nmr;
+
+-- overlaps
+SELECT 'empty'::numrange && nummultirange();
+SELECT 'empty'::numrange && nummultirange(numrange(1,2));
+SELECT nummultirange() && 'empty'::numrange;
+SELECT nummultirange(numrange(1,2)) && 'empty'::numrange;
+SELECT nummultirange() && nummultirange();
+SELECT nummultirange() && nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) && nummultirange();
+SELECT nummultirange(numrange(3,4)) && nummultirange(numrange(1,2), numrange(7,8));
+SELECT nummultirange(numrange(1,2), numrange(7,8)) && nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(3,4)) && nummultirange(numrange(1,2), numrange(3.5,8));
+SELECT nummultirange(numrange(1,2), numrange(3.5,8)) && numrange(3,4);
+SELECT nummultirange(numrange(1,2), numrange(3.5,8)) && nummultirange(numrange(3,4));
+
+-- contains
+SELECT nummultirange() @> nummultirange();
+SELECT nummultirange() @> 'empty'::numrange;
+SELECT nummultirange(numrange(null,null)) @> numrange(1,2);
+SELECT nummultirange(numrange(null,null)) @> numrange(null,2);
+SELECT nummultirange(numrange(null,null)) @> numrange(2,null);
+SELECT nummultirange(numrange(null,5)) @> numrange(null,3);
+SELECT nummultirange(numrange(null,5)) @> numrange(null,8);
+SELECT nummultirange(numrange(5,null)) @> numrange(8,null);
+SELECT nummultirange(numrange(5,null)) @> numrange(3,null);
+SELECT nummultirange(numrange(1,5)) @> numrange(8,9);
+SELECT nummultirange(numrange(1,5)) @> numrange(3,9);
+SELECT nummultirange(numrange(1,5)) @> numrange(1,4);
+SELECT nummultirange(numrange(1,5)) @> numrange(1,5);
+SELECT nummultirange(numrange(-4,-2), numrange(1,5)) @> numrange(1,5);
+SELECT nummultirange(numrange(1,5), numrange(8,9)) @> numrange(1,5);
+SELECT nummultirange(numrange(1,5), numrange(8,9)) @> numrange(6,7);
+SELECT nummultirange(numrange(1,5), numrange(6,9)) @> numrange(6,7);
+SELECT '{[1,5)}'::nummultirange @> '{[1,5)}';
+SELECT '{[-4,-2), [1,5)}'::nummultirange @> '{[1,5)}';
+SELECT '{[1,5), [8,9)}'::nummultirange @> '{[1,5)}';
+SELECT '{[1,5), [8,9)}'::nummultirange @> '{[6,7)}';
+SELECT '{[1,5), [6,9)}'::nummultirange @> '{[6,7)}';
+
+-- is contained by
+SELECT nummultirange() <@ nummultirange();
+SELECT 'empty'::numrange <@ nummultirange();
+SELECT numrange(1,2) <@ nummultirange(numrange(null,null));
+SELECT numrange(null,2) <@ nummultirange(numrange(null,null));
+SELECT numrange(2,null) <@ nummultirange(numrange(null,null));
+SELECT numrange(null,3) <@ nummultirange(numrange(null,5));
+SELECT numrange(null,8) <@ nummultirange(numrange(null,5));
+SELECT numrange(8,null) <@ nummultirange(numrange(5,null));
+SELECT numrange(3,null) <@ nummultirange(numrange(5,null));
+SELECT numrange(8,9) <@ nummultirange(numrange(1,5));
+SELECT numrange(3,9) <@ nummultirange(numrange(1,5));
+SELECT numrange(1,4) <@ nummultirange(numrange(1,5));
+SELECT numrange(1,5) <@ nummultirange(numrange(1,5));
+SELECT numrange(1,5) <@ nummultirange(numrange(-4,-2), numrange(1,5));
+SELECT numrange(1,5) <@ nummultirange(numrange(1,5), numrange(8,9));
+SELECT numrange(6,7) <@ nummultirange(numrange(1,5), numrange(8,9));
+SELECT numrange(6,7) <@ nummultirange(numrange(1,5), numrange(6,9));
+SELECT '{[1,5)}' <@ '{[1,5)}'::nummultirange;
+SELECT '{[1,5)}' <@ '{[-4,-2), [1,5)}'::nummultirange;
+SELECT '{[1,5)}' <@ '{[1,5), [8,9)}'::nummultirange;
+SELECT '{[6,7)}' <@ '{[1,5), [8,9)}'::nummultirange;
+SELECT '{[6,7)}' <@ '{[1,5), [6,9)}'::nummultirange;
+
+-- overleft
+SELECT 'empty'::numrange &< nummultirange();
+SELECT 'empty'::numrange &< nummultirange(numrange(1,2));
+SELECT nummultirange() &< 'empty'::numrange;
+SELECT nummultirange(numrange(1,2)) &< 'empty'::numrange;
+SELECT nummultirange() &< nummultirange();
+SELECT nummultirange(numrange(1,2)) &< nummultirange();
+SELECT nummultirange() &< nummultirange(numrange(1,2));
+SELECT numrange(6,7) &< nummultirange(numrange(3,4));
+SELECT numrange(1,2) &< nummultirange(numrange(3,4));
+SELECT numrange(1,4) &< nummultirange(numrange(3,4));
+SELECT numrange(1,6) &< nummultirange(numrange(3,4));
+SELECT numrange(3.5,6) &< nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(6,7)) &< numrange(3,4);
+SELECT nummultirange(numrange(1,2)) &< numrange(3,4);
+SELECT nummultirange(numrange(1,4)) &< numrange(3,4);
+SELECT nummultirange(numrange(1,6)) &< numrange(3,4);
+SELECT nummultirange(numrange(3.5,6)) &< numrange(3,4);
+SELECT nummultirange(numrange(6,7)) &< nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,2)) &< nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,4)) &< nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,6)) &< nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(3.5,6)) &< nummultirange(numrange(3,4));
+
+-- overright
+SELECT nummultirange() &> 'empty'::numrange;
+SELECT nummultirange(numrange(1,2)) &> 'empty'::numrange;
+SELECT 'empty'::numrange &> nummultirange();
+SELECT 'empty'::numrange &> nummultirange(numrange(1,2));
+SELECT nummultirange() &> nummultirange();
+SELECT nummultirange() &> nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) &> nummultirange();
+SELECT nummultirange(numrange(3,4)) &> numrange(6,7);
+SELECT nummultirange(numrange(3,4)) &> numrange(1,2);
+SELECT nummultirange(numrange(3,4)) &> numrange(1,4);
+SELECT nummultirange(numrange(3,4)) &> numrange(1,6);
+SELECT nummultirange(numrange(3,4)) &> numrange(3.5,6);
+SELECT numrange(3,4) &> nummultirange(numrange(6,7));
+SELECT numrange(3,4) &> nummultirange(numrange(1,2));
+SELECT numrange(3,4) &> nummultirange(numrange(1,4));
+SELECT numrange(3,4) &> nummultirange(numrange(1,6));
+SELECT numrange(3,4) &> nummultirange(numrange(3.5,6));
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(6,7));
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(1,4));
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(1,6));
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(3.5,6));
+
+-- meets
+SELECT 'empty'::numrange -|- nummultirange();
+SELECT 'empty'::numrange -|- nummultirange(numrange(1,2));
+SELECT nummultirange() -|- 'empty'::numrange;
+SELECT nummultirange(numrange(1,2)) -|- 'empty'::numrange;
+SELECT nummultirange() -|- nummultirange();
+SELECT nummultirange(numrange(1,2)) -|- nummultirange();
+SELECT nummultirange() -|- nummultirange(numrange(1,2));
+SELECT numrange(1,2) -|- nummultirange(numrange(2,4));
+SELECT numrange(1,2) -|- nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,2)) -|- numrange(2,4);
+SELECT nummultirange(numrange(1,2)) -|- numrange(3,4);
+SELECT nummultirange(numrange(1,2)) -|- nummultirange(numrange(2,4));
+SELECT nummultirange(numrange(1,2)) -|- nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,2), numrange(5,6)) -|- nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,2), numrange(5,6)) -|- nummultirange(numrange(6,7));
+SELECT nummultirange(numrange(1,2), numrange(5,6)) -|- nummultirange(numrange(8,9));
+SELECT nummultirange(numrange(1,2)) -|- nummultirange(numrange(2,4), numrange(6,7));
+
+-- strictly left
+select 'empty'::numrange << nummultirange();
+select numrange(1,2) << nummultirange();
+select numrange(1,2) << nummultirange(numrange(3,4));
+select numrange(1,2) << nummultirange(numrange(0,4));
+select numrange(1,2) << nummultirange(numrange(0,4), numrange(7,8));
+select nummultirange() << 'empty'::numrange;
+select nummultirange() << numrange(1,2);
+select nummultirange(numrange(3,4)) << numrange(3,6);
+select nummultirange(numrange(0,2)) << numrange(3,6);
+select nummultirange(numrange(0,2), numrange(7,8)) << numrange(3,6);
+select nummultirange(numrange(-4,-2), numrange(0,2)) << numrange(3,6);
+select nummultirange() << nummultirange();
+select nummultirange() << nummultirange(numrange(1,2));
+select nummultirange(numrange(1,2)) << nummultirange();
+select nummultirange(numrange(1,2)) << nummultirange(numrange(1,2));
+select nummultirange(numrange(1,2)) << nummultirange(numrange(3,4));
+select nummultirange(numrange(1,2)) << nummultirange(numrange(3,4), numrange(7,8));
+select nummultirange(numrange(1,2), numrange(4,5)) << nummultirange(numrange(3,4), numrange(7,8));
+
+-- strictly right
+select nummultirange() >> 'empty'::numrange;
+select nummultirange() >> numrange(1,2);
+select nummultirange(numrange(3,4)) >> numrange(1,2);
+select nummultirange(numrange(0,4)) >> numrange(1,2);
+select nummultirange(numrange(0,4), numrange(7,8)) >> numrange(1,2);
+select 'empty'::numrange >> nummultirange();
+select numrange(1,2) >> nummultirange();
+select numrange(3,6) >> nummultirange(numrange(3,4));
+select numrange(3,6) >> nummultirange(numrange(0,2));
+select numrange(3,6) >> nummultirange(numrange(0,2), numrange(7,8));
+select numrange(3,6) >> nummultirange(numrange(-4,-2), numrange(0,2));
+select nummultirange() >> nummultirange();
+select nummultirange(numrange(1,2)) >> nummultirange();
+select nummultirange() >> nummultirange(numrange(1,2));
+select nummultirange(numrange(1,2)) >> nummultirange(numrange(1,2));
+select nummultirange(numrange(3,4)) >> nummultirange(numrange(1,2));
+select nummultirange(numrange(3,4), numrange(7,8)) >> nummultirange(numrange(1,2));
+select nummultirange(numrange(3,4), numrange(7,8)) >> nummultirange(numrange(1,2), numrange(4,5));
+
+-- union
+SELECT 'empty'::numrange @+ 'empty'::numrange;
+SELECT 'empty'::numrange @+ nummultirange();
+SELECT nummultirange() @+ 'empty'::numrange;
+SELECT nummultirange() @+ nummultirange();
+SELECT 'empty'::numrange @+ numrange(1,2);
+SELECT 'empty'::numrange @+ nummultirange(numrange(1,2));
+SELECT numrange(1,2) @+ nummultirange();
+SELECT nummultirange(numrange(1,2)) @+ 'empty'::numrange;
+SELECT numrange(1,2) @+ 'empty'::numrange;
+SELECT nummultirange() @+ numrange(1,2);
+SELECT nummultirange() @+ nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) @+ nummultirange();
+SELECT numrange(1,2) @+ numrange(1,2);
+SELECT numrange(1,2) @+ numrange(2,4);
+SELECT numrange(1,2) @+ numrange(3,4);
+SELECT nummultirange(numrange(1,2)) @+ nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) @+ nummultirange(numrange(2,4));
+SELECT nummultirange(numrange(1,2)) @+ nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) @+ nummultirange(numrange(2,4));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) @+ nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) @+ nummultirange(numrange(0,9));
+
+-- merge
+SELECT range_merge(nummultirange());
+SELECT range_merge(nummultirange(numrange(1,2)));
+SELECT range_merge(nummultirange(numrange(1,2), numrange(7,8)));
+
+-- minus
+SELECT 'empty'::numrange @- 'empty'::numrange;
+SELECT 'empty'::numrange @- nummultirange();
+SELECT nummultirange() @- 'empty'::numrange;
+SELECT nummultirange() @- nummultirange();
+SELECT 'empty'::numrange @- numrange(1,2);
+SELECT 'empty'::numrange @- nummultirange(numrange(1,2));
+SELECT numrange(1,2) @- nummultirange();
+SELECT nummultirange(numrange(1,2)) @- 'empty'::numrange;
+SELECT 'empty'::numrange @- numrange(1,2);
+SELECT numrange(1,2) @- 'empty'::numrange;
+SELECT nummultirange() @- numrange(1,2);
+SELECT nummultirange() @- nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) @- nummultirange();
+SELECT numrange(1,3) @- numrange(1,3);
+SELECT numrange(1,3) @- numrange(1,2);
+SELECT numrange(1,3) @- numrange(2,4);
+SELECT numrange(1,3) @- numrange(3,4);
+SELECT numrange(1,3) @- nummultirange(numrange(1,3));
+SELECT numrange(1,3) @- nummultirange(numrange(1,2));
+SELECT numrange(1,3) @- nummultirange(numrange(2,4));
+SELECT numrange(1,3) @- nummultirange(numrange(3,4));
+SELECT numrange(1,10) @- nummultirange(numrange(1,2), numrange(4,5));
+SELECT nummultirange(numrange(1,10)) @- numrange(0,2);
+SELECT nummultirange(numrange(1,10)) @- numrange(1,2);
+SELECT nummultirange(numrange(1,10)) @- numrange(3,4);
+SELECT nummultirange(numrange(1,10)) @- numrange(13,18);
+SELECT nummultirange(numrange(1,2), numrange(3,4)) @- nummultirange();
+SELECT nummultirange(numrange(1,2)) @- nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) @- nummultirange(numrange(2,4));
+SELECT nummultirange(numrange(1,2)) @- nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,4)) @- nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,4)) @- nummultirange(numrange(2,3));
+SELECT nummultirange(numrange(1,4)) @- nummultirange(numrange(0,8));
+SELECT nummultirange(numrange(1,4)) @- nummultirange(numrange(0,2));
+SELECT nummultirange(numrange(1,8)) @- nummultirange(numrange(0,2), numrange(3,4));
+SELECT nummultirange(numrange(1,8)) @- nummultirange(numrange(2,3), numrange(5,null));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) @- nummultirange(numrange(-2,0));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) @- nummultirange(numrange(2,4));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) @- nummultirange(numrange(3,5));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) @- nummultirange(numrange(0,9));
+SELECT nummultirange(numrange(1,3), numrange(4,5)) @- nummultirange(numrange(2,9));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) @- nummultirange(numrange(8,9));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) @- nummultirange(numrange(-2,0), numrange(8,9));
+
+-- intersection
+SELECT 'empty'::numrange @* 'empty'::numrange;
+SELECT 'empty'::numrange @* nummultirange();
+SELECT nummultirange() @* 'empty'::numrange;
+SELECT nummultirange() @* nummultirange();
+SELECT 'empty'::numrange @* numrange(1,2);
+SELECT 'empty'::numrange @* nummultirange(numrange(1,2));
+SELECT numrange(1,2) @* 'empty'::numrange;
+SELECT numrange(1,2) @* nummultirange();
+SELECT nummultirange(numrange(1,2)) @* 'empty'::numrange;
+SELECT nummultirange() @* numrange(1,2);
+SELECT nummultirange() @* nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) @* nummultirange();
+SELECT numrange(1,3) @* numrange(1,3);
+SELECT numrange(1,3) @* numrange(1,2);
+SELECT numrange(1,3) @* numrange(1,5);
+SELECT numrange(1,3) @* numrange(2,5);
+SELECT numrange(1,5) @* numrange(2,3);
+SELECT '{[1,3)}'::nummultirange @* numrange(1,3);
+SELECT '{[1,3)}'::nummultirange @* numrange(1,2);
+SELECT '{[1,3)}'::nummultirange @* numrange(1,5);
+SELECT '{[1,3)}'::nummultirange @* numrange(2,5);
+SELECT '{[1,5)}'::nummultirange @* numrange(2,3);
+SELECT numrange(1,3) @* '{[1,3)}'::nummultirange;
+SELECT numrange(1,2) @* '{[1,3)}'::nummultirange;
+SELECT numrange(1,5) @* '{[1,3)}'::nummultirange;
+SELECT numrange(2,5) @* '{[1,3)}'::nummultirange;
+SELECT numrange(2,3) @* '{[1,5)}'::nummultirange;
+SELECT '{[1,3)}'::nummultirange @* '{[1,5)}'::nummultirange;
+SELECT '{[1,3)}'::nummultirange @* '{[0,5)}'::nummultirange;
+SELECT '{[1,3)}'::nummultirange @* '{[0,2)}'::nummultirange;
+SELECT '{[1,3)}'::nummultirange @* '{[2,5)}'::nummultirange;
+SELECT '{[1,4)}'::nummultirange @* '{[2,3)}'::nummultirange;
+SELECT '{[1,4)}'::nummultirange @* '{[0,2), [3,5)}'::nummultirange;
+SELECT '{[1,4), [7,10)}'::nummultirange @* '{[0,8), [9,12)}'::nummultirange;
+SELECT '{[1,4), [7,10)}'::nummultirange @* '{[9,12)}'::nummultirange;
+SELECT '{[1,4), [7,10)}'::nummultirange @* '{[-5,-4), [5,6), [9,12)}'::nummultirange;
+SELECT '{[1,4), [7,10)}'::nummultirange @* '{[0,2), [3,8), [9,12)}'::nummultirange;
+SELECT '{[1,4), [7,10)}'::nummultirange @* '{[0,2), [3,8), [9,12)}'::nummultirange;
+
+--
+-- range_agg function
+--
+create table reservations ( room_id integer not null, booked_during daterange );
+insert into reservations values
+-- 1: has a meets and a gap
+(1, daterange('2018-07-01', '2018-07-07')),
+(1, daterange('2018-07-07', '2018-07-14')),
+(1, daterange('2018-07-20', '2018-07-22')),
+-- 2: just a single row
+(2, daterange('2018-07-01', '2018-07-03')),
+-- 3: one null range
+(3, NULL),
+-- 4: two null ranges
+(4, NULL),
+(4, NULL),
+-- 5: a null range and a non-null range
+(5, NULL),
+(5, daterange('2018-07-01', '2018-07-03')),
+-- 6: has overlap
+(6, daterange('2018-07-01', '2018-07-07')),
+(6, daterange('2018-07-05', '2018-07-10')),
+-- 7: two ranges that meet: no gap or overlap
+(7, daterange('2018-07-01', '2018-07-07')),
+(7, daterange('2018-07-07', '2018-07-14')),
+-- 8: an empty range
+(8, 'empty'::daterange)
+;
+SELECT   room_id, range_agg(booked_during)
+FROM     reservations
+GROUP BY room_id
+ORDER BY room_id;
+
+-- range_agg on a custom range type too
+SELECT  range_agg(r)
+FROM    (VALUES
+          ('[a,c]'::textrange),
+          ('[b,b]'::textrange),
+          ('[c,f]'::textrange),
+          ('[g,h)'::textrange),
+          ('[h,j)'::textrange)
+        ) t(r);
+
+select range_intersect_agg(nmr) from nummultirange_test;
+select range_intersect_agg(nmr) from nummultirange_test where false;
+-- test with just one input:
+select range_intersect_agg(nmr) from (values ('{[1,2]}'::nummultirange)) t(nmr);
+select range_intersect_agg(nmr) from nummultirange_test where nmr @> 4.0;
+
+create table nummultirange_test2(nmr nummultirange);
+create index nummultirange_test2_hash_idx on nummultirange_test2 using hash (nmr);
+
+INSERT INTO nummultirange_test2 VALUES('{[, 5)}');
+INSERT INTO nummultirange_test2 VALUES(nummultirange(numrange(1.1, 2.2)));
+INSERT INTO nummultirange_test2 VALUES(nummultirange(numrange(1.1, 2.2)));
+INSERT INTO nummultirange_test2 VALUES(nummultirange(numrange(1.1, 2.2,'()')));
+INSERT INTO nummultirange_test2 VALUES('{}');
+
+select * from nummultirange_test2 where nmr = '{}';
+select * from nummultirange_test2 where nmr = nummultirange(numrange(1.1, 2.2));
+select * from nummultirange_test2 where nmr = nummultirange(numrange(1.1, 2.3));
+
+set enable_nestloop=t;
+set enable_hashjoin=f;
+set enable_mergejoin=f;
+select * from nummultirange_test natural join nummultirange_test2 order by nmr;
+set enable_nestloop=f;
+set enable_hashjoin=t;
+set enable_mergejoin=f;
+select * from nummultirange_test natural join nummultirange_test2 order by nmr;
+set enable_nestloop=f;
+set enable_hashjoin=f;
+set enable_mergejoin=t;
+select * from nummultirange_test natural join nummultirange_test2 order by nmr;
+
+set enable_nestloop to default;
+set enable_hashjoin to default;
+set enable_mergejoin to default;
+
+DROP TABLE nummultirange_test2;
+
+--
+-- Test user-defined multirange of floats
+--
+
+select '{[123.001, 5.e9)}'::float8multirange @> 888.882::float8;
+create table float8multirange_test(f8mr float8multirange, i int);
+insert into float8multirange_test values(float8multirange(float8range(-100.00007, '1.111113e9')), 42);
+select * from float8multirange_test;
+drop table float8multirange_test;
+
+--
+-- Test multirange types over domains
+--
+
+create domain mydomain as int4;
+create type mydomainrange as range(subtype=mydomain);
+select '{[4,50)}'::mydomainmultirange @> 7::mydomain;
+drop domain mydomain cascade;
+
+--
+-- Test domains over multirange types
+--
+
+create domain restrictedmultirange as int4multirange check (upper(value) < 10);
+select '{[4,5)}'::restrictedmultirange @> 7;
+select '{[4,50)}'::restrictedmultirange @> 7; -- should fail
+drop domain restrictedmultirange;
+
+--
+-- Test multiple multirange types over the same subtype
+--
+
+create type textrange1 as range(subtype=text, collation="C");
+create type textrange2 as range(subtype=text, collation="C");
+
+select textmultirange1(textrange2('a','Z'));  -- should fail
+select textmultirange1(textrange1('a','Z')) @> 'b'::text;
+select textmultirange2(textrange2('a','z')) @> 'b'::text;
+
+drop type textrange1;
+drop type textrange2;
+
+--
+-- Test polymorphic type system
+--
+
+create function anyarray_anymultirange_func(a anyarray, r anymultirange)
+  returns anyelement as 'select $1[1] + lower($2);' language sql;
+
+select anyarray_anymultirange_func(ARRAY[1,2], int4multirange(int4range(10,20)));
+
+-- should fail
+select anyarray_anymultirange_func(ARRAY[1,2], nummultirange(numrange(10,20)));
+
+drop function anyarray_anymultirange_func(anyarray, anymultirange);
+
+-- should fail
+create function bogus_func(anyelement)
+  returns anymultirange as 'select int4multirange(int4range(1,10))' language sql;
+
+-- should fail
+create function bogus_func(int)
+  returns anymultirange as 'select int4multirange(int4range(1,10))' language sql;
+
+create function range_add_bounds(anymultirange)
+  returns anyelement as 'select lower($1) + upper($1)' language sql;
+
+select range_add_bounds(int4multirange(int4range(1, 17)));
+select range_add_bounds(nummultirange(numrange(1.0001, 123.123)));
+
+create function multirangetypes_sql(q anymultirange, b anyarray, out c anyelement)
+  as $$ select upper($1) + $2[1] $$
+  language sql;
+
+select multirangetypes_sql(int4multirange(int4range(1,10)), ARRAY[2,20]);
+select multirangetypes_sql(nummultirange(numrange(1,10)), ARRAY[2,20]);  -- match failure
+
+--
+-- Arrays of multiranges
+--
+
+select ARRAY[nummultirange(numrange(1.1, 1.2)), nummultirange(numrange(12.3, 155.5))];
+
+create table i8mr_array (f1 int, f2 int8multirange[]);
+insert into i8mr_array values (42, array[int8multirange(int8range(1,10)), int8multirange(int8range(2,20))]);
+select * from i8mr_array;
+drop table i8mr_array;
+
+--
+-- Multiranges of arrays
+--
+
+select arraymultirange(arrayrange(ARRAY[1,2], ARRAY[2,1]));
+select arraymultirange(arrayrange(ARRAY[2,1], ARRAY[1,2]));  -- fail
+
+select array[1,1] <@ arraymultirange(arrayrange(array[1,2], array[2,1]));
+select array[1,3] <@ arraymultirange(arrayrange(array[1,2], array[2,1]));
+
+--
+-- Ranges of composites
+--
+
+create type two_ints as (a int, b int);
+create type two_ints_range as range (subtype = two_ints);
+
+-- with force_parallel_mode on, this exercises tqueue.c's range remapping
+select *, row_to_json(upper(t)) as u from
+  (values (two_ints_multirange(two_ints_range(row(1,2), row(3,4)))),
+          (two_ints_multirange(two_ints_range(row(5,6), row(7,8))))) v(t);
+
+drop type two_ints cascade;
+
+--
+-- Check behavior when subtype lacks a hash function
+--
+
+set enable_sort = off;  -- try to make it pick a hash setop implementation
+
+select '{(2,5)}'::cashmultirange except select '{(5,6)}'::cashmultirange;
+
+reset enable_sort;
+
+--
+-- OUT/INOUT/TABLE functions
+--
+
+-- infer anymultirange from anymultirange
+create function mr_outparam_succeed(i anymultirange, out r anymultirange, out t text)
+  as $$ select $1, 'foo'::text $$ language sql;
+
+select * from mr_outparam_succeed(int4multirange(int4range(1,2)));
+
+-- infer anyarray from anymultirange
+create function mr_outparam_succeed2(i anymultirange, out r anyarray, out t text)
+  as $$ select ARRAY[upper($1)], 'foo'::text $$ language sql;
+
+select * from mr_outparam_succeed2(int4multirange(int4range(1,2)));
+
+-- infer anyrange from anymultirange
+create function mr_outparam_succeed3(i anymultirange, out r anyrange, out t text)
+  as $$ select range_merge($1), 'foo'::text $$ language sql;
+select * from mr_outparam_succeed3(int4multirange(int4range(1,2)));
+
+-- infer anymultirange from anyrange
+create function mr_outparam_succeed4(i anyrange, out r anymultirange, out t text)
+  as $$ select $1 @+ $1, 'foo'::text $$ language sql;
+
+select * from mr_outparam_succeed4(int4range(1,2));
+
+-- infer anyelement from anymultirange
+create function mr_inoutparam_succeed(out i anyelement, inout r anymultirange)
+  as $$ select upper($1), $1 $$ language sql;
+
+select * from mr_inoutparam_succeed(int4multirange(int4range(1,2)));
+
+-- infer anyelement+anymultirange from anyelement+anymultirange
+create function mr_table_succeed(i anyelement, r anymultirange) returns table(i anyelement, r anymultirange)
+  as $$ select $1, $2 $$ language sql;
+
+select * from mr_table_succeed(123, int4multirange(int4range(1,11)));
+
+-- should fail
+create function mr_outparam_fail(i anyelement, out r anymultirange, out t text)
+  as $$ select '[1,10]', 'foo' $$ language sql;
+
+--should fail
+create function mr_inoutparam_fail(inout i anyelement, out r anymultirange)
+  as $$ select $1, '[1,10]' $$ language sql;
+
+--should fail
+create function mr_table_fail(i anyelement) returns table(i anyelement, r anymultirange)
+  as $$ select $1, '[1,10]' $$ language sql;
diff --git a/src/test/regress/sql/rangetypes.sql b/src/test/regress/sql/rangetypes.sql
index 8960add976..32e18661d0 100644
--- a/src/test/regress/sql/rangetypes.sql
+++ b/src/test/regress/sql/rangetypes.sql
@@ -118,6 +118,10 @@ select numrange(1.0, 2.0) * numrange(2.0, 3.0);
 select numrange(1.0, 2.0) * numrange(1.5, 3.0);
 select numrange(1.0, 2.0) * numrange(2.5, 3.0);
 
+select range_intersect_agg(nr) from numrange_test;
+select range_intersect_agg(nr) from numrange_test where false;
+select range_intersect_agg(nr) from numrange_test where nr @> 4.0;
+
 create table numrange_test2(nr numrange);
 create index numrange_test2_hash_idx on numrange_test2 using hash (nr);
 
@@ -481,16 +485,25 @@ reset enable_sort;
 -- OUT/INOUT/TABLE functions
 --
 
+-- infer anyrange from anyrange
 create function outparam_succeed(i anyrange, out r anyrange, out t text)
   as $$ select $1, 'foo'::text $$ language sql;
 
 select * from outparam_succeed(int4range(1,2));
 
+-- infer anyarray from anyrange
+create function outparam_succeed2(i anyrange, out r anyarray, out t text)
+  as $$ select ARRAY[upper($1)], 'foo'::text $$ language sql;
+
+select * from outparam_succeed2(int4range(int4range(1,2)));
+
+-- infer anyelement from anyrange
 create function inoutparam_succeed(out i anyelement, inout r anyrange)
   as $$ select upper($1), $1 $$ language sql;
 
 select * from inoutparam_succeed(int4range(1,2));
 
+-- infer anyelement+anyrange from anyelement+anyrange
 create function table_succeed(i anyelement, r anyrange) returns table(i anyelement, r anyrange)
   as $$ select $1, $2 $$ language sql;
 
-- 
2.11.0

#56Paul A Jungwirth
pj@illuminatedcomputing.com
In reply to: Paul A Jungwirth (#55)
4 attachment(s)
Re: range_agg

On Wed, Nov 6, 2019 at 3:02 PM Paul A Jungwirth
<pj@illuminatedcomputing.com> wrote:

On Thu, Sep 26, 2019 at 2:13 PM Alvaro Herrera <alvherre@2ndquadrant.com> wrote:

Hello Paul, I've started to review this patch. Here's a few minor
things I ran across -- mostly compiler warnings (is my compiler too
ancient?).

I just opened this thread to post a rebased set patches (especially
because of the `const` additions to range functions). Maybe it's not
that helpful since they don't include your changes yet but here they
are anyway. I'll post some more with your changes shortly.

Here is another batch of patches incorporating your improvements. It
seems like almost all the warnings were about moving variable
declarations above any other statements. For some reason I don't get
warnings about that on my end (compiling on OS X):

platter:postgres paul$ gcc --version
Configured with:
--prefix=/Applications/Xcode.app/Contents/Developer/usr
--with-gxx-include-dir=/usr/include/c++/4.2.1
Apple clang version 11.0.0 (clang-1100.0.33.12)
Target: x86_64-apple-darwin18.6.0
Thread model: posix
InstalledDir: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin

For configure I'm saying this:

./configure CFLAGS=-ggdb 5-Og -g3 -fno-omit-frame-pointer
--enable-tap-tests --enable-cassert --enable-debug
--prefix=/Users/paul/local

Any suggestions to get better warnings? On my other patch I got
feedback about the very same kind. I could just compile on Linux but
it's nice to work on this away from my desk on the laptop. Maybe
installing a real gcc is the way to go.

Thanks,
Paul

Attachments:

v5-0004-multirange-docs.patchapplication/octet-stream; name=v5-0004-multirange-docs.patchDownload
From 0a662c1d27358477e69615bdd6d14df9ba2ce297 Mon Sep 17 00:00:00 2001
From: "Paul A. Jungwirth" <pj@illuminatedcomputing.com>
Date: Sat, 21 Sep 2019 21:28:47 -0700
Subject: [PATCH v5 4/4] multirange docs

---
 doc/src/sgml/extend.sgml     |  28 +++-
 doc/src/sgml/func.sgml       | 302 +++++++++++++++++++++++++++++++++++++++----
 doc/src/sgml/rangetypes.sgml |  47 ++++++-
 3 files changed, 342 insertions(+), 35 deletions(-)

diff --git a/doc/src/sgml/extend.sgml b/doc/src/sgml/extend.sgml
index a3046f22d0..e72720cd68 100644
--- a/doc/src/sgml/extend.sgml
+++ b/doc/src/sgml/extend.sgml
@@ -229,9 +229,9 @@
    </indexterm>
 
     <para>
-     Five pseudo-types of special interest are <type>anyelement</type>,
+     Six pseudo-types of special interest are <type>anyelement</type>,
      <type>anyarray</type>, <type>anynonarray</type>, <type>anyenum</type>,
-     and <type>anyrange</type>,
+     <type>anyrange</type>, and <type>anymultirange</type>.
      which are collectively called <firstterm>polymorphic types</firstterm>.
      Any function declared using these types is said to be
      a <firstterm>polymorphic function</firstterm>.  A polymorphic function can
@@ -250,15 +250,15 @@
      position declared as <type>anyarray</type> can have any array data type,
      but similarly they must all be the same type.  And similarly,
      positions declared as <type>anyrange</type> must all be the same range
-     type.  Furthermore, if there are
+     type.  Likewise for <type>anymultirange</type>.
+    </para>
+
+    <para>
+     Furthermore, if there are
      positions declared <type>anyarray</type> and others declared
      <type>anyelement</type>, the actual array type in the
      <type>anyarray</type> positions must be an array whose elements are
      the same type appearing in the <type>anyelement</type> positions.
-     Similarly, if there are positions declared <type>anyrange</type>
-     and others declared <type>anyelement</type>, the actual range type in
-     the <type>anyrange</type> positions must be a range whose subtype is
-     the same type appearing in the <type>anyelement</type> positions.
      <type>anynonarray</type> is treated exactly the same as <type>anyelement</type>,
      but adds the additional constraint that the actual type must not be
      an array type.
@@ -268,6 +268,20 @@
     </para>
 
     <para>
+     Similarly, if there are positions declared <type>anyrange</type>
+     and others declared <type>anyelement</type>, the actual range type in
+     the <type>anyrange</type> positions must be a range whose subtype is
+     the same type appearing in the <type>anyelement</type> positions.
+     The type <type>anymultirange</type> accepts a multirange
+     whose contents match the other polymorphic types.
+     That is it must hold ranges matching <type>anyrange</type>
+     and base type elements matching <type>anyelement</type> (if either of
+     those are present). If <type>anyarray</type> is present its elements
+     must match the base type of the <type>anyrange</type> and/or the base
+     type of the ranges in an </type>anymultirange</type>.
+    </para>
+
+    <para>
      Thus, when more than one argument position is declared with a polymorphic
      type, the net effect is that only certain combinations of actual argument
      types are allowed.  For example, a function declared as
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 28eb322f3f..54b428eb39 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -14566,7 +14566,9 @@ NULL baz</literallayout>(3 rows)</entry>
 
   <para>
    <xref linkend="range-operators-table"/> shows the operators
-   available for range types.
+   available for range and multirange types.
+   Many of these operators will accept either a range or multirange
+   on either side.
   </para>
 
     <table id="range-operators-table">
@@ -14576,7 +14578,7 @@ NULL baz</literallayout>(3 rows)</entry>
        <row>
         <entry>Operator</entry>
         <entry>Description</entry>
-        <entry>Example</entry>
+        <entry>Examples</entry>
         <entry>Result</entry>
        </row>
       </thead>
@@ -14584,136 +14586,247 @@ NULL baz</literallayout>(3 rows)</entry>
        <row>
         <entry> <literal>=</literal> </entry>
         <entry>equal</entry>
-        <entry><literal>int4range(1,5) = '[1,4]'::int4range</literal></entry>
+        <entry><literallayout class="monospaced">int4range(1,5) = '[1,4]'::int4range
+'{[1,5)}'::int4multirange = '{[1,4]}'::int4multirange</literallayout></entry>
         <entry><literal>t</literal></entry>
        </row>
 
        <row>
         <entry> <literal>&lt;&gt;</literal> </entry>
         <entry>not equal</entry>
-        <entry><literal>numrange(1.1,2.2) &lt;&gt; numrange(1.1,2.3)</literal></entry>
+        <entry><literallayout class="monospaced">numrange(1.1,2.2) &lt;&gt; numrange(1.1,2.3)
+'{[1.1,2.2)}'::nummultirange &lt;&gt; '{[1.1,2.3)}'::nummultirange</literallayout></entry>
         <entry><literal>t</literal></entry>
        </row>
 
        <row>
         <entry> <literal>&lt;</literal> </entry>
         <entry>less than</entry>
-        <entry><literal>int4range(1,10) &lt; int4range(2,3)</literal></entry>
+        <entry><literallayout class="monospaced">int4range(1,10) &lt; int4range(2,3)
+'{[1,10)}'::int4multirange &lt; '{[2,3)}'::int4multirange</literallayout>
+        </entry>
         <entry><literal>t</literal></entry>
        </row>
 
        <row>
         <entry> <literal>&gt;</literal> </entry>
         <entry>greater than</entry>
-        <entry><literal>int4range(1,10) &gt; int4range(1,5)</literal></entry>
+        <entry>
+          <literallayout class="monospaced">int4range(1,10) &gt; int4range(1,5)
+'{[1,10)}'::int4multirange &gt; '{[1,5)}'::int4multirange</literallayout>
+        </entry>
         <entry><literal>t</literal></entry>
        </row>
 
        <row>
         <entry> <literal>&lt;=</literal> </entry>
         <entry>less than or equal</entry>
-        <entry><literal>numrange(1.1,2.2) &lt;= numrange(1.1,2.2)</literal></entry>
+        <entry>
+          <literallayout class="monospaced">numrange(1.1,2.2) &lt;= numrange(1.1,2.2)
+'{[1.1,2.2)}'::nummultirange &lt;= '{[1.1,2.2)}'::nummultirange</literallayout>
+        </entry>
         <entry><literal>t</literal></entry>
        </row>
 
        <row>
         <entry> <literal>&gt;=</literal> </entry>
         <entry>greater than or equal</entry>
-        <entry><literal>numrange(1.1,2.2) &gt;= numrange(1.1,2.0)</literal></entry>
+        <entry>
+          <literallayout class="monospaced">numrange(1.1,2.2) &gt;= numrange(1.1,2.0)
+'{[1.1,2.2)}'::nummultirange &gt;= '{[1.1,2.0)}'::nummultirange</literallayout>
+        </entry>
+        <entry><literal>t</literal></entry>
+       </row>
+
+       <row>
+        <entry> <literal>@&gt;</literal> </entry>
+        <entry>contains multirange</entry>
+        <entry>
+          <literallayout class="monospaced">int4range(2,4) @&gt; '{[2,3)}'::int4multirange
+'{[2,4)}'::int4multirange @&gt; '{[2,3)}'::int4multirange</literallayout>
+        </entry>
         <entry><literal>t</literal></entry>
        </row>
 
        <row>
         <entry> <literal>@&gt;</literal> </entry>
         <entry>contains range</entry>
-        <entry><literal>int4range(2,4) @&gt; int4range(2,3)</literal></entry>
+        <entry>
+          <literallayout class="monospaced">int4range(2,4) @&gt; int4range(2,3)
+'{[2,4)}'::int4multirange @&gt; int4range(2,3)</literallayout>
+        </entry>
         <entry><literal>t</literal></entry>
        </row>
 
        <row>
         <entry> <literal>@&gt;</literal> </entry>
         <entry>contains element</entry>
-        <entry><literal>'[2011-01-01,2011-03-01)'::tsrange @&gt; '2011-01-10'::timestamp</literal></entry>
+        <entry>
+          <literallayout class="monospaced">'[2011-01-01,2011-03-01)'::tsrange @&gt; '2011-01-10'::timestamp
+'{[2011-01-01,2011-03-01)}'::tsmultirange @&gt; '2011-01-10'::timestamp</literallayout>
+        </entry>
+        <entry><literal>t</literal></entry>
+       </row>
+
+       <row>
+        <entry> <literal>&lt;@</literal> </entry>
+        <entry>multirange is contained by</entry>
+        <entry>
+          <literallayout class="monospaced">'{[2,4)}'::int4multirange &lt;@ int4range(1,7)
+'{[2,4)}'::int4multirange &lt;@ '{[1,7)}'::int4multirange</literallayout>
+        </entry>
         <entry><literal>t</literal></entry>
        </row>
 
        <row>
         <entry> <literal>&lt;@</literal> </entry>
         <entry>range is contained by</entry>
-        <entry><literal>int4range(2,4) &lt;@ int4range(1,7)</literal></entry>
+        <entry>
+          <literallayout class="monospaced">int4range(2,4) &lt;@ int4range(1,7)
+int4range(2,4) &lt;@ '{[1,7)}'::int4multirange</literallayout>
+        </entry>
         <entry><literal>t</literal></entry>
        </row>
 
        <row>
         <entry> <literal>&lt;@</literal> </entry>
         <entry>element is contained by</entry>
-        <entry><literal>42 &lt;@ int4range(1,7)</literal></entry>
+        <entry>
+          <literallayout class="monospaced">42 &lt;@ int4range(1,7)
+42 &lt;@ '{[1,7)}'::int4multirange</literallayout>
+        </entry>
         <entry><literal>f</literal></entry>
        </row>
 
        <row>
         <entry> <literal>&amp;&amp;</literal> </entry>
         <entry>overlap (have points in common)</entry>
-        <entry><literal>int8range(3,7) &amp;&amp; int8range(4,12)</literal></entry>
+        <entry>
+          <literallayout class="monospaced">int8range(3,7) &amp;&amp; int8range(4,12)
+int8range(3,7) &amp;&amp; '{[4,12)}'::int8multirange
+'{[3,7)}'::int8multirange &amp;&amp; int8range(4,12)
+'{[3,7)}'::int8multirange &amp;&amp; '{[4,12)}'::int8multirange</literallayout>
+        </entry>
         <entry><literal>t</literal></entry>
        </row>
 
        <row>
         <entry> <literal>&lt;&lt;</literal> </entry>
         <entry>strictly left of</entry>
-        <entry><literal>int8range(1,10) &lt;&lt; int8range(100,110)</literal></entry>
+        <entry>
+          <literallayout class="monospaced">int8range(1,10) &lt;&lt; int8range(100,110)
+int8range(1,10) &lt;&lt; '{[100,110)}'::int8multirange
+'{[1,10)}'::int8multirange &lt;&lt; int8range(100,110)
+'{[1,10)}'::int8multirange &lt;&lt; '{[100,110)}'::int8multirange</literallayout>
+        </entry>
         <entry><literal>t</literal></entry>
        </row>
 
        <row>
         <entry> <literal>&gt;&gt;</literal> </entry>
         <entry>strictly right of</entry>
-        <entry><literal>int8range(50,60) &gt;&gt; int8range(20,30)</literal></entry>
+        <entry>
+          <literallayout class="monospaced">int8range(50,60) &gt;&gt; int8range(20,30)
+int8range(50,60) &gt;&gt; '{[20,30)}'::int8multirange
+'{[50,60)}'::int8multirange &gt;&gt; int8range(20,30)
+'{[50,60)}'::int8multirange &gt;&gt; '{[20,30)}'::int8multirange</literallayout>
+        </entry>
         <entry><literal>t</literal></entry>
        </row>
 
        <row>
         <entry> <literal>&amp;&lt;</literal> </entry>
         <entry>does not extend to the right of</entry>
-        <entry><literal>int8range(1,20) &amp;&lt; int8range(18,20)</literal></entry>
+        <entry>
+          <literallayout class="monospaced">int8range(1,20) &amp;&lt; int8range(18,20)
+int8range(1,20) &amp;&lt; '{[18,20)}'::int8multirange
+'{[1,20)}'::int8multirange &amp;&lt; int8range(18,20)
+'{[1,20)}'::int8multirange &amp;&lt; '{[18,20)}'::int8multirange</literallayout>
+        </entry>
         <entry><literal>t</literal></entry>
        </row>
 
        <row>
         <entry> <literal>&amp;&gt;</literal> </entry>
         <entry>does not extend to the left of</entry>
-        <entry><literal>int8range(7,20) &amp;&gt; int8range(5,10)</literal></entry>
+        <entry>
+          <literallayout class="monospaced">int8range(7,20) &amp;&gt; int8range(5,10)
+int8range(7,20) &amp;&gt; '{[5,10)}'::int8multirange
+'{[7,20)}'::int8multirange &amp;&gt; int8range(5,10)
+'{[7,20)}'::int8multirange &amp;&gt; '{[5,10)}'::int8multirange</literallayout>
+        </entry>
         <entry><literal>t</literal></entry>
        </row>
 
        <row>
         <entry> <literal>-|-</literal> </entry>
         <entry>is adjacent to</entry>
-        <entry><literal>numrange(1.1,2.2) -|- numrange(2.2,3.3)</literal></entry>
+        <entry>
+          <literallayout class="monospaced">numrange(1.1,2.2) -|- numrange(2.2,3.3)
+numrange(1.1,2.2) -|- '{[2.2,3.3)}'::nummultirange
+'{[1.1,2.2)}'::nummultirange -|- numrange(2.2,3.3)
+'{[1.1,2.2)}'::nummultirange -|- '{[2.2,3.3)}'::nummultirange</literallayout>
+        </entry>
         <entry><literal>t</literal></entry>
        </row>
 
        <row>
         <entry> <literal>+</literal> </entry>
         <entry>union</entry>
-        <entry><literal>numrange(5,15) + numrange(10,20)</literal></entry>
+        <entry><literallayout class="monospaced">numrange(5,15) + numrange(10,20)</literallayout></entry>
         <entry><literal>[5,20)</literal></entry>
        </row>
 
        <row>
+        <entry> <literal>@+</literal> </entry>
+        <entry>safe union</entry>
+        <entry>
+          <literallayout class="monospaced">numrange(5,10) @+ numrange(15,20)
+numrange(5,10) @+ '{[15,20)}'::nummultirange
+'{[5,10)}'::nummultirange @+ numrange(15,20)
+'{[5,10)}'::nummultirange @+ '{[15,20)}'::nummultirange</literallayout>
+        </entry>
+        <entry><literal>{[5,10), [15,20)}</literal></entry>
+       </row>
+
+       <row>
         <entry> <literal>*</literal> </entry>
         <entry>intersection</entry>
-        <entry><literal>int8range(5,15) * int8range(10,20)</literal></entry>
+        <entry><literallayout class="monospaced">int8range(5,15) * int8range(10,20)</literallayout></entry>
         <entry><literal>[10,15)</literal></entry>
        </row>
 
        <row>
+        <entry> <literal>@*</literal> </entry>
+        <entry>safe intersection</entry>
+        <entry>
+          <literallayout class="monospaced">int8range(5,15) @* int8range(10,20)
+int8range(5,15) @* '{[10,20)}'::int8multirange
+'{[5,15)}'::int8multirange @* int8range(10,20)
+'{[5,15)}'::int8multirange @* '{[10,20)}'::int8multirange</literallayout>
+        </entry>
+        <entry><literal>{[10,15)}</literal></entry>
+       </row>
+
+       <row>
         <entry> <literal>-</literal> </entry>
         <entry>difference</entry>
-        <entry><literal>int8range(5,15) - int8range(10,20)</literal></entry>
+        <entry><literallayout class="monospaced">int8range(5,15) - int8range(10,20)</literallayout></entry>
         <entry><literal>[5,10)</literal></entry>
        </row>
 
+       <row>
+        <entry> <literal>@-</literal> </entry>
+        <entry>safe difference</entry>
+        <entry>
+          <literallayout class="monospaced">int8range(5,20) @- int8range(10,15)
+int8range(5,20) @- '{[10,15)}'::int8multirange
+'{[5,20)}'::int8multirange @- int8range(10,15)
+'{[5,20)}'::int8multirange @- '{[10,15)}'::int8multirange</literallayout>
+        </entry>
+        <entry><literal>{[5,10), [15,20)}</literal></entry>
+       </row>
+
       </tbody>
      </tgroup>
     </table>
@@ -14729,19 +14842,28 @@ NULL baz</literallayout>(3 rows)</entry>
 
   <para>
    The left-of/right-of/adjacent operators always return false when an empty
-   range is involved; that is, an empty range is not considered to be either
-   before or after any other range.
+   range or multirange is involved; that is, an empty range is not considered to
+   be either before or after any other range.
+  </para>
+
+  <para>
+   Elsewhere empty ranges and multiranges are treated as the additive identity:
+   anything unioned with an empty value is itself. Anything minus an empty
+   value is itself. An empty multirange has exactly the same points as an empty
+   range. Every range contains the empty range. Every multirange contains as many
+   empty ranges as you like.
   </para>
 
   <para>
    The union and difference operators will fail if the resulting range would
    need to contain two disjoint sub-ranges, as such a range cannot be
-   represented.
+   represented. The safe versions of union, intersection, and difference
+   return multiranges, so they can handle gaps without failing.
   </para>
 
   <para>
    <xref linkend="range-functions-table"/> shows the functions
-   available for use with range types.
+   available for use with range and multirange types.
   </para>
 
   <indexterm>
@@ -14793,6 +14915,17 @@ NULL baz</literallayout>(3 rows)</entry>
        <row>
         <entry>
          <literal>
+          <function>lower</function>(<type>anymultirange</type>)
+         </literal>
+        </entry>
+        <entry>multirange's element type</entry>
+        <entry>lower bound of multirange</entry>
+        <entry><literal>lower('{[1.1,2.2)}'::nummultirange)</literal></entry>
+        <entry><literal>1.1</literal></entry>
+       </row>
+       <row>
+        <entry>
+         <literal>
           <function>upper</function>(<type>anyrange</type>)
          </literal>
         </entry>
@@ -14804,6 +14937,17 @@ NULL baz</literallayout>(3 rows)</entry>
        <row>
         <entry>
          <literal>
+          <function>upper</function>(<type>anymultirange</type>)
+         </literal>
+        </entry>
+        <entry>multirange's element type</entry>
+        <entry>upper bound of multirange</entry>
+        <entry><literal>upper('{[1.1,2.2)}'::nummultirange)</literal></entry>
+        <entry><literal>2.2</literal></entry>
+       </row>
+       <row>
+        <entry>
+         <literal>
           <function>isempty</function>(<type>anyrange</type>)
          </literal>
         </entry>
@@ -14815,6 +14959,17 @@ NULL baz</literallayout>(3 rows)</entry>
        <row>
         <entry>
          <literal>
+          <function>isempty</function>(<type>anymultirange</type>)
+         </literal>
+        </entry>
+        <entry><type>boolean</type></entry>
+        <entry>is the multirange empty?</entry>
+        <entry><literal>isempty('{[1.1,2.2)}'::nummultirange)</literal></entry>
+        <entry><literal>false</literal></entry>
+       </row>
+       <row>
+        <entry>
+         <literal>
           <function>lower_inc</function>(<type>anyrange</type>)
          </literal>
         </entry>
@@ -14826,6 +14981,17 @@ NULL baz</literallayout>(3 rows)</entry>
        <row>
         <entry>
          <literal>
+          <function>lower_inc</function>(<type>anymultirange</type>)
+         </literal>
+        </entry>
+        <entry><type>boolean</type></entry>
+        <entry>is the lower bound inclusive?</entry>
+        <entry><literal>lower_inc('{[1.1,2.2)}'::nummultirange)</literal></entry>
+        <entry><literal>true</literal></entry>
+       </row>
+       <row>
+        <entry>
+         <literal>
           <function>upper_inc</function>(<type>anyrange</type>)
          </literal>
         </entry>
@@ -14837,6 +15003,17 @@ NULL baz</literallayout>(3 rows)</entry>
        <row>
         <entry>
          <literal>
+          <function>upper_inc</function>(<type>anymultirange</type>)
+         </literal>
+        </entry>
+        <entry><type>boolean</type></entry>
+        <entry>is the upper bound inclusive?</entry>
+        <entry><literal>upper_inc('{[1.1,2.2)}'::nummultirange)</literal></entry>
+        <entry><literal>false</literal></entry>
+       </row>
+       <row>
+        <entry>
+         <literal>
           <function>lower_inf</function>(<type>anyrange</type>)
          </literal>
         </entry>
@@ -14848,6 +15025,17 @@ NULL baz</literallayout>(3 rows)</entry>
        <row>
         <entry>
          <literal>
+          <function>lower_inf</function>(<type>anymultirange</type>)
+         </literal>
+        </entry>
+        <entry><type>boolean</type></entry>
+        <entry>is the lower bound infinite?</entry>
+        <entry><literal>lower_inf('{(,)}'::datemultirange)</literal></entry>
+        <entry><literal>true</literal></entry>
+       </row>
+       <row>
+        <entry>
+         <literal>
           <function>upper_inf</function>(<type>anyrange</type>)
          </literal>
         </entry>
@@ -14859,6 +15047,17 @@ NULL baz</literallayout>(3 rows)</entry>
        <row>
         <entry>
          <literal>
+          <function>upper_inf</function>(<type>anymultirange</type>)
+         </literal>
+        </entry>
+        <entry><type>boolean</type></entry>
+        <entry>is the upper bound infinite?</entry>
+        <entry><literal>upper_inf('{(,)}'::datemultirange)</literal></entry>
+        <entry><literal>true</literal></entry>
+       </row>
+       <row>
+        <entry>
+         <literal>
           <function>range_merge</function>(<type>anyrange</type>, <type>anyrange</type>)
          </literal>
         </entry>
@@ -14867,16 +15066,27 @@ NULL baz</literallayout>(3 rows)</entry>
         <entry><literal>range_merge('[1,2)'::int4range, '[3,4)'::int4range)</literal></entry>
         <entry><literal>[1,4)</literal></entry>
        </row>
+       <row>
+        <entry>
+         <literal>
+          <function>range_merge</function>(<type>anymultirange</type>)
+         </literal>
+        </entry>
+        <entry><type>anyrange</type></entry>
+        <entry>the smallest range which includes the entire multirange</entry>
+        <entry><literal>range_merge('{[1,2), [3,4)}'::int4multirange)</literal></entry>
+        <entry><literal>[1,4)</literal></entry>
+       </row>
       </tbody>
      </tgroup>
     </table>
 
   <para>
    The <function>lower</function> and  <function>upper</function> functions return null
-   if the range is empty or the requested bound is infinite.
+   if the input range/multirange is empty or the requested bound is infinite.
    The <function>lower_inc</function>, <function>upper_inc</function>,
    <function>lower_inf</function>, and <function>upper_inf</function>
-   functions all return false for an empty range.
+   functions all return false for an empty range or multirange.
   </para>
   </sect1>
 
@@ -15198,6 +15408,44 @@ NULL baz</literallayout>(3 rows)</entry>
      <row>
       <entry>
        <indexterm>
+        <primary>range_agg</primary>
+       </indexterm>
+       <function>
+         range_agg(<replaceable class="parameter">expression</replaceable>)
+       </function>
+      </entry>
+      <entry>
+       <type>anyrange</type>
+      </entry>
+      <entry>
+       <type>multirange</type> of the argument range type
+      </entry>
+      <entry>Yes</entry>
+      <entry>union of the non-null input values</entry>
+     </row>
+
+     <row>
+      <entry>
+       <indexterm>
+        <primary>range_intersect_agg</primary>
+       </indexterm>
+       <function>
+         range_intersect_agg(<replaceable class="parameter">expression</replaceable>)
+       </function>
+      </entry>
+      <entry>
+       <type>anyrange</type>
+      </entry>
+      <entry>
+       <type>multirange</type> of the argument range type
+      </entry>
+      <entry>No</entry>
+      <entry>intersection of the non-null input values</entry>
+     </row>
+
+     <row>
+      <entry>
+       <indexterm>
         <primary>string_agg</primary>
        </indexterm>
        <function>
diff --git a/doc/src/sgml/rangetypes.sgml b/doc/src/sgml/rangetypes.sgml
index 3a034d9b06..3e37b352fc 100644
--- a/doc/src/sgml/rangetypes.sgml
+++ b/doc/src/sgml/rangetypes.sgml
@@ -27,6 +27,13 @@
   ranges from an instrument, and so forth can also be useful.
  </para>
 
+ <para>
+  Every range type has a corresponding multirange type. A multirange is
+  an ordered list of non-continguous, non-empty, non-null ranges. Most
+  range operators also work on multiranges, and they have a few functions
+  of their own.
+ </para>
+
  <sect2 id="rangetypes-builtin">
   <title>Built-in Range Types</title>
 
@@ -235,10 +242,30 @@ SELECT '[4,4]'::int4range;
 SELECT '[4,4)'::int4range;
 </programlisting>
   </para>
+
+  <para>
+   The input for a multirange is curly brackets (<literal>{</literal> and
+   <literal>}</literal>) containing zero or more valid ranges,
+   separated by commas. Whitespace is permitted around the brackets and
+   commas. This is intended to be reminiscent of array syntax, although
+   multiranges are much simpler: they have just one dimension and there is
+   no need to quote their contents. (The bounds of their ranges may be
+   quoted as above however.)
+  </para>
+
+  <para>
+  Examples:
+<programlisting>
+SELECT '{}'::int4multirange;
+SELECT '{[3,7)}'::int4multirange;
+SELECT '{[3,7), [8,9)}'::int4multirange;
+</programlisting>
+  </para>
+
  </sect2>
 
  <sect2 id="rangetypes-construct">
-  <title>Constructing Ranges</title>
+  <title>Constructing Ranges and Multiranges</title>
 
   <para>
    Each range type has a constructor function with the same name as the range
@@ -272,6 +299,19 @@ SELECT int8range(1, 14, '(]');
 SELECT numrange(NULL, 2.2);
 </programlisting>
   </para>
+
+  <para>
+   Each range type also has a multirange constructor with the same name as the
+   multirange type.  The constructor function takes zero or more arguments
+   which are all ranges of the appropriate type.
+   For example:
+
+<programlisting>
+SELECT nummultirange();
+SELECT nummultirange(numrange(1.0, 14.0));
+SELECT nummultirange(numrange(1.0, 14.0), numrange(20.0, 25.0));
+</programlisting>
+  </para>
  </sect2>
 
  <sect2 id="rangetypes-discrete">
@@ -345,6 +385,11 @@ SELECT '[1.234, 5.678]'::floatrange;
   </para>
 
   <para>
+   When you define your own range your automatically get a corresponding
+   multirange type.
+  </para>
+
+  <para>
    Defining your own range type also allows you to specify a different
    subtype B-tree operator class or collation to use, so as to change the sort
    ordering that determines which values fall into a given range.
-- 
2.11.0

v5-0002-multirange-operators-and-functions.patchapplication/octet-stream; name=v5-0002-multirange-operators-and-functions.patchDownload
From a54a44248f043f60f4aee5fc7b5f8d5e1cbd5100 Mon Sep 17 00:00:00 2001
From: "Paul A. Jungwirth" <pj@illuminatedcomputing.com>
Date: Sat, 21 Sep 2019 21:28:20 -0700
Subject: [PATCH v5 2/4] multirange operators and functions

---
 src/backend/catalog/pg_type.c                 |    2 -
 src/backend/commands/typecmds.c               |    6 +-
 src/backend/parser/parse_coerce.c             |    2 +
 src/backend/utils/adt/multirangetypes.c       | 1575 +++++++++++++++-
 src/backend/utils/adt/rangetypes.c            |  236 ++-
 src/backend/utils/fmgr/funcapi.c              |   97 +-
 src/include/catalog/pg_aggregate.dat          |   11 +
 src/include/catalog/pg_amproc.dat             |    5 +-
 src/include/catalog/pg_operator.dat           |  169 ++
 src/include/catalog/pg_proc.dat               |  185 ++
 src/include/utils/multirangetypes.h           |   34 +
 src/include/utils/rangetypes.h                |   31 +-
 src/test/regress/expected/dependency.out      |    1 +
 src/test/regress/expected/multirangetypes.out | 2397 +++++++++++++++++++++++++
 src/test/regress/expected/opr_sanity.out      |    4 +-
 src/test/regress/expected/rangetypes.out      |   38 +-
 src/test/regress/expected/sanity_check.out    |    2 +
 src/test/regress/sql/multirangetypes.sql      |  607 +++++++
 src/test/regress/sql/rangetypes.sql           |   13 +
 19 files changed, 5188 insertions(+), 227 deletions(-)

diff --git a/src/backend/catalog/pg_type.c b/src/backend/catalog/pg_type.c
index 08552850c8..ea5b161c4d 100644
--- a/src/backend/catalog/pg_type.c
+++ b/src/backend/catalog/pg_type.c
@@ -876,7 +876,6 @@ 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;
@@ -892,7 +891,6 @@ makeMultirangeTypeName(const char *rangeTypeName, Oid typeNamespace)
 	if (rangestr)
 	{
 		rangeoffset = rangestr - rangeTypeName;
-		rangelen = strlen(rangestr);
 		strlcpy(mltrng + rangeoffset, "multi", NAMEDATALEN - rangeoffset);
 		strlcpy(mltrng + rangeoffset + 5, rangestr, NAMEDATALEN - rangeoffset - 5);
 		namelen += 5;
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index 14a6857062..7a440cafd1 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -1783,13 +1783,11 @@ makeMultirangeConstructors(const char *name, Oid namespace,
 	"multirange_constructor1"};
 	static const int pronargs[2] = {0, 1};
 
-	Oid			constructorArgTypes[0];
+	Oid			constructorArgTypes = rangeArrayOid;
 	ObjectAddress myself,
 				referenced;
 	int			i;
 
-	constructorArgTypes[0] = rangeArrayOid;
-
 	Datum		allParamTypes[1] = {ObjectIdGetDatum(rangeArrayOid)};
 	ArrayType  *allParameterTypes = construct_array(allParamTypes, 1, OIDOID,
 													sizeof(Oid), true, 'i');
@@ -1808,7 +1806,7 @@ makeMultirangeConstructors(const char *name, Oid namespace,
 	{
 		oidvector  *constructorArgTypesVector;
 
-		constructorArgTypesVector = buildoidvector(constructorArgTypes,
+		constructorArgTypesVector = buildoidvector(&constructorArgTypes,
 												   pronargs[i]);
 
 		myself = ProcedureCreate(name,	/* name: same as multirange type */
diff --git a/src/backend/parser/parse_coerce.c b/src/backend/parser/parse_coerce.c
index f0b1f6f831..2ff039ab6c 100644
--- a/src/backend/parser/parse_coerce.c
+++ b/src/backend/parser/parse_coerce.c
@@ -1918,6 +1918,8 @@ enforce_generic_type_consistency(const Oid *actual_arg_types,
 							   format_type_be(range_typeid),
 							   format_type_be(elem_typeid))));
 		}
+		else
+			range_typelem = InvalidOid;
 	}
 
 	/* Get the range type based on the multirange type, if we have one */
diff --git a/src/backend/utils/adt/multirangetypes.c b/src/backend/utils/adt/multirangetypes.c
index 5323a6a635..7717acb3b1 100644
--- a/src/backend/utils/adt/multirangetypes.c
+++ b/src/backend/utils/adt/multirangetypes.c
@@ -91,7 +91,6 @@ multirange_in(PG_FUNCTION_ARGS)
 	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;
@@ -101,13 +100,12 @@ multirange_in(PG_FUNCTION_ARGS)
 	MultirangeType *ret;
 	MultirangeParseState parse_state;
 	const char *ptr = input_str;
-	const char *range_str;
+	const char *range_str = NULL;
 	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))
@@ -351,9 +349,12 @@ multirange_send(PG_FUNCTION_ARGS)
 	for (i = 0; i < range_count; i++)
 	{
 		Datum		range = RangeTypePGetDatum(ranges[i]);
+		uint32		range_len;
+		char	   *range_data;
+
 		range = PointerGetDatum(SendFunctionCall(&cache->proc, range));
-		uint32		range_len = VARSIZE(range) - VARHDRSZ;
-		char	   *range_data = VARDATA(range);
+		range_len = VARSIZE(range) - VARHDRSZ;
+		range_data = VARDATA(range);
 
 		pq_sendint32(buf, range_len);
 		pq_sendbytes(buf, range_data, range_len);
@@ -726,169 +727,1547 @@ multirange_constructor0(PG_FUNCTION_ARGS)
 }
 
 
-/* multirange, multirange -> bool functions */
+/* multirange, multirange -> multirange type functions */
 
-/* equality (internal version) */
-bool
-multirange_eq_internal(TypeCacheEntry *typcache, MultirangeType * mr1, MultirangeType * mr2)
+/* multirange union */
+Datum
+range_union_range(PG_FUNCTION_ARGS)
 {
-	int32		range_count_1;
-	int32		range_count_2;
-	int32		i;
-	RangeType **ranges1;
-	RangeType **ranges2;
-	RangeType  *r1;
-	RangeType  *r2;
+	RangeType  *r1 = PG_GETARG_RANGE_P(0);
+	RangeType  *r2 = PG_GETARG_RANGE_P(1);
+	Oid			mltrngtypoid = get_fn_expr_rettype(fcinfo->flinfo);
+	TypeCacheEntry *typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
+	MultirangeType *mr = make_multirange(mltrngtypoid, typcache->rngtype, 1, &r2);
 
-	/* Different types should be prevented by ANYMULTIRANGE matching rules */
-	if (MultirangeTypeGetOid(mr1) != MultirangeTypeGetOid(mr2))
-		elog(ERROR, "multirange types do not match");
+	PG_RETURN_MULTIRANGE_P(range_union_multirange_internal(typcache, r1, mr));
+}
 
-	multirange_deserialize(mr1, &range_count_1, &ranges1);
-	multirange_deserialize(mr2, &range_count_2, &ranges2);
+Datum
+range_union_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
 
-	if (range_count_1 != range_count_2)
-		return false;
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
 
-	for (i = 0; i < range_count_1; i++)
-	{
-		r1 = ranges1[i];
-		r2 = ranges2[i];
+	PG_RETURN_MULTIRANGE_P(range_union_multirange_internal(typcache, r, mr));
+}
 
-		if (!range_eq_internal(typcache->rngtype, r1, r2))
-			return false;
-	}
+Datum
+multirange_union_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
 
-	return true;
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_MULTIRANGE_P(range_union_multirange_internal(typcache, r, mr));
 }
 
-/* equality */
 Datum
-multirange_eq(PG_FUNCTION_ARGS)
+multirange_union_multirange(PG_FUNCTION_ARGS)
 {
 	MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
 	MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
 	TypeCacheEntry *typcache;
+	int32		range_count1;
+	int32		range_count2;
+	int32		range_count3;
+	RangeType **ranges1;
+	RangeType **ranges2;
+	RangeType **ranges3;
+
+	if (MultirangeIsEmpty(mr1))
+		PG_RETURN_MULTIRANGE_P(mr2);
+	if (MultirangeIsEmpty(mr2))
+		PG_RETURN_MULTIRANGE_P(mr1);
 
 	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr1));
 
-	PG_RETURN_BOOL(multirange_eq_internal(typcache, mr1, mr2));
+	multirange_deserialize(mr1, &range_count1, &ranges1);
+	multirange_deserialize(mr2, &range_count2, &ranges2);
+
+	range_count3 = range_count1 + range_count2;
+	ranges3 = palloc0(range_count3 * sizeof(RangeType *));
+	memcpy(ranges3, ranges1, range_count1 * sizeof(RangeType *));
+	memcpy(ranges3 + range_count1, ranges2, range_count2 * sizeof(RangeType *));
+	PG_RETURN_MULTIRANGE_P(make_multirange(typcache->type_id, typcache->rngtype,
+										   range_count3, ranges3));
 }
 
-/* inequality (internal version) */
-bool
-multirange_ne_internal(TypeCacheEntry *typcache, MultirangeType * mr1, MultirangeType * mr2)
+MultirangeType *
+range_union_multirange_internal(TypeCacheEntry *typcache, RangeType *r,
+								MultirangeType * mr)
 {
-	return (!multirange_eq_internal(typcache, mr1, mr2));
+	int32		range_count;
+	RangeType **ranges1;
+	RangeType **ranges2;
+
+	if (RangeIsEmpty(r))
+		return mr;
+	if (MultirangeIsEmpty(mr))
+		return make_multirange(typcache->type_id, typcache->rngtype, 1, &r);
+
+	multirange_deserialize(mr, &range_count, &ranges1);
+
+	ranges2 = palloc0((range_count + 1) * sizeof(RangeType *));
+
+	memcpy(ranges2, ranges1, range_count * sizeof(RangeType *));
+	ranges2[range_count] = r;
+	return make_multirange(typcache->type_id, typcache->rngtype, range_count + 1, ranges2);
 }
 
-/* inequality */
+/* multirange minus */
 Datum
-multirange_ne(PG_FUNCTION_ARGS)
+range_minus_range(PG_FUNCTION_ARGS)
 {
-	MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
-	MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
+	RangeType  *r1 = PG_GETARG_RANGE_P(0);
+	RangeType  *r2 = PG_GETARG_RANGE_P(1);
+	Oid			mltrngtypoid = get_fn_expr_rettype(fcinfo->flinfo);
 	TypeCacheEntry *typcache;
+	TypeCacheEntry *rangetyp;
 
-	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr1));
+	typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
+	rangetyp = typcache->rngtype;
 
-	PG_RETURN_BOOL(multirange_ne_internal(typcache, mr1, mr2));
+	if (RangeIsEmpty(r1) || RangeIsEmpty(r2))
+		PG_RETURN_MULTIRANGE_P(make_multirange(mltrngtypoid, rangetyp, 1, &r1));
+
+	PG_RETURN_MULTIRANGE_P(multirange_minus_multirange_internal(mltrngtypoid,
+																rangetyp,
+																1,
+																&r1,
+																1,
+																&r2));
 }
 
-/* Btree support */
+Datum
+range_minus_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	Oid			mltrngtypoid = MultirangeTypeGetOid(mr);
+	TypeCacheEntry *typcache;
+	TypeCacheEntry *rangetyp;
+	int32		range_count;
+	RangeType **ranges;
+
+	typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
+	rangetyp = typcache->rngtype;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		PG_RETURN_MULTIRANGE_P(make_multirange(mltrngtypoid, rangetyp, 1, &r));
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_MULTIRANGE_P(multirange_minus_multirange_internal(mltrngtypoid,
+																rangetyp,
+																1,
+																&r,
+																range_count,
+																ranges));
+}
 
-/* btree comparator */
 Datum
-multirange_cmp(PG_FUNCTION_ARGS)
+multirange_minus_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	Oid			mltrngtypoid = MultirangeTypeGetOid(mr);
+	TypeCacheEntry *typcache;
+	TypeCacheEntry *rangetyp;
+	int32		range_count;
+	RangeType **ranges;
+
+	typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
+	rangetyp = typcache->rngtype;
+
+	if (MultirangeIsEmpty(mr) || RangeIsEmpty(r))
+		PG_RETURN_MULTIRANGE_P(mr);
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_MULTIRANGE_P(multirange_minus_multirange_internal(mltrngtypoid,
+																rangetyp,
+																range_count,
+																ranges,
+																1,
+																&r));
+}
+
+Datum
+multirange_minus_multirange(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;
+	Oid			mltrngtypoid = MultirangeTypeGetOid(mr1);
+	TypeCacheEntry *typcache;
+	TypeCacheEntry *rangetyp;
+	int32		range_count1;
+	int32		range_count2;
 	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, mltrngtypoid);
+	rangetyp = typcache->rngtype;
 
-	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr1));
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		PG_RETURN_MULTIRANGE_P(mr1);
 
-	multirange_deserialize(mr1, &range_count_1, &ranges1);
-	multirange_deserialize(mr2, &range_count_2, &ranges2);
+	multirange_deserialize(mr1, &range_count1, &ranges1);
+	multirange_deserialize(mr2, &range_count2, &ranges2);
 
-	/* Loop over source data */
-	range_count_max = Max(range_count_1, range_count_2);
-	for (i = 0; i < range_count_max; i++)
+	PG_RETURN_MULTIRANGE_P(multirange_minus_multirange_internal(mltrngtypoid,
+																rangetyp,
+																range_count1,
+																ranges1,
+																range_count2,
+																ranges2));
+}
+
+MultirangeType *
+multirange_minus_multirange_internal(Oid mltrngtypoid, TypeCacheEntry *rangetyp,
+									 int32 range_count1, RangeType **ranges1, int32 range_count2, RangeType **ranges2)
+{
+	RangeType  *r1;
+	RangeType  *r2;
+	RangeType **ranges3;
+	int32		range_count3;
+	int32		i1;
+	int32		i2;
+
+	/*
+	 * Worst case: every range in ranges1 makes a different cut to some range
+	 * in ranges2.
+	 */
+	ranges3 = palloc0((range_count1 + range_count2) * sizeof(RangeType *));
+	range_count3 = 0;
+
+	/*
+	 * For each range in mr1, keep subtracting until it's gone or the ranges
+	 * in mr2 have passed it. After a subtraction we assign what's left back
+	 * to r1. The parallel progress through mr1 and mr2 is similar to
+	 * multirange_overlaps_multirange_internal.
+	 */
+	r2 = ranges2[0];
+	for (i1 = 0, i2 = 0; i1 < range_count1; i1++)
 	{
-		/*
-		 * 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)
+		r1 = ranges1[i1];
+
+		/* Discard r2s while r2 << r1 */
+		while (r2 != NULL && range_before_internal(rangetyp, r2, r1))
 		{
-			cmp = -1;
-			break;
+			r2 = ++i2 >= range_count2 ? NULL : ranges2[i2];
 		}
-		if (i >= range_count_2)
+
+		while (r2 != NULL)
 		{
-			cmp = 1;
-			break;
+			if (range_split_internal(rangetyp, r1, r2, &ranges3[range_count3], &r1))
+			{
+				/*
+				 * If r2 takes a bite out of the middle of r1, we need two
+				 * outputs
+				 */
+				range_count3++;
+				r2 = ++i2 >= range_count2 ? NULL : ranges2[i2];
+
+			}
+			else if (range_overlaps_internal(rangetyp, r1, r2))
+			{
+				/*
+				 * If r2 overlaps r1, replace r1 with r1 - r2.
+				 */
+				r1 = range_minus_internal(rangetyp, r1, r2);
+
+				/*
+				 * If r2 goes past r1, then we need to stay with it, in case
+				 * it hits future r1s. Otherwise we need to keep r1, in case
+				 * future r2s hit it. Since we already subtracted, there's no
+				 * point in using the overright/overleft calls.
+				 */
+				if (RangeIsEmpty(r1) || range_before_internal(rangetyp, r1, r2))
+					break;
+				else
+					r2 = ++i2 >= range_count2 ? NULL : ranges2[i2];
+
+			}
+			else
+			{
+				/*
+				 * This and all future r2s are past r1, so keep them. Also
+				 * assign whatever is left of r1 to the result.
+				 */
+				break;
+			}
 		}
-		r1 = ranges1[i];
-		r2 = ranges2[i];
 
-		cmp = range_cmp_internal(typcache->rngtype, r1, r2);
-		if (cmp != 0)
-			break;
+		/*
+		 * Nothing else can remove anything from r1, so keep it. Even if r1 is
+		 * empty here, make_multirange will remove it.
+		 */
+		ranges3[range_count3++] = r1;
 	}
 
-	PG_FREE_IF_COPY(mr1, 0);
-	PG_FREE_IF_COPY(mr2, 1);
+	return make_multirange(mltrngtypoid, rangetyp, range_count3, ranges3);
+}
 
-	PG_RETURN_INT32(cmp);
+/* multirange intersection */
+Datum
+range_intersect_range(PG_FUNCTION_ARGS)
+{
+	RangeType  *r1 = PG_GETARG_RANGE_P(0);
+	RangeType  *r2 = PG_GETARG_RANGE_P(1);
+	Oid			mltrngtypoid = get_fn_expr_rettype(fcinfo->flinfo);
+	TypeCacheEntry *typcache;
+	TypeCacheEntry *rangetyp;
+
+	typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
+	rangetyp = typcache->rngtype;
+
+	if (RangeIsEmpty(r1) || RangeIsEmpty(r2))
+		PG_RETURN_MULTIRANGE_P(make_empty_multirange(mltrngtypoid, rangetyp));
+
+	PG_RETURN_MULTIRANGE_P(multirange_intersect_multirange_internal(mltrngtypoid,
+																	rangetyp,
+																	1,
+																	&r1,
+																	1,
+																	&r2));
 }
 
-/* inequality operators using the multirange_cmp function */
 Datum
-multirange_lt(PG_FUNCTION_ARGS)
+range_intersect_multirange(PG_FUNCTION_ARGS)
 {
-	int			cmp = multirange_cmp(fcinfo);
+	RangeType  *r1 = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
+	Oid			mltrngtypoid = MultirangeTypeGetOid(mr2);
+	TypeCacheEntry *typcache;
+	TypeCacheEntry *rangetyp;
+	int32		range_count2;
+	RangeType **ranges2;
 
-	PG_RETURN_BOOL(cmp < 0);
+	typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
+	rangetyp = typcache->rngtype;
+
+	if (RangeIsEmpty(r1) || MultirangeIsEmpty(mr2))
+		PG_RETURN_MULTIRANGE_P(make_empty_multirange(mltrngtypoid, rangetyp));
+
+	multirange_deserialize(mr2, &range_count2, &ranges2);
+
+	PG_RETURN_MULTIRANGE_P(multirange_intersect_multirange_internal(mltrngtypoid,
+																	rangetyp,
+																	1,
+																	&r1,
+																	range_count2,
+																	ranges2));
 }
 
 Datum
-multirange_le(PG_FUNCTION_ARGS)
+multirange_intersect_range(PG_FUNCTION_ARGS)
 {
-	int			cmp = multirange_cmp(fcinfo);
+	MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r2 = PG_GETARG_RANGE_P(1);
+	Oid			mltrngtypoid = MultirangeTypeGetOid(mr1);
+	TypeCacheEntry *typcache;
+	TypeCacheEntry *rangetyp;
+	int32		range_count1;
+	RangeType **ranges1;
 
-	PG_RETURN_BOOL(cmp <= 0);
+	typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
+	rangetyp = typcache->rngtype;
+
+	if (MultirangeIsEmpty(mr1) || RangeIsEmpty(r2))
+		PG_RETURN_MULTIRANGE_P(make_empty_multirange(mltrngtypoid, rangetyp));
+
+	multirange_deserialize(mr1, &range_count1, &ranges1);
+
+	PG_RETURN_MULTIRANGE_P(multirange_intersect_multirange_internal(mltrngtypoid,
+																	rangetyp,
+																	range_count1,
+																	ranges1,
+																	1,
+																	&r2));
 }
 
 Datum
-multirange_ge(PG_FUNCTION_ARGS)
+multirange_intersect_multirange(PG_FUNCTION_ARGS)
 {
-	int			cmp = multirange_cmp(fcinfo);
+	MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
+	MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
+	Oid			mltrngtypoid = MultirangeTypeGetOid(mr1);
+	TypeCacheEntry *typcache;
+	TypeCacheEntry *rangetyp;
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
 
-	PG_RETURN_BOOL(cmp >= 0);
+	typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
+	rangetyp = typcache->rngtype;
+
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		PG_RETURN_MULTIRANGE_P(make_empty_multirange(mltrngtypoid, rangetyp));
+
+	multirange_deserialize(mr1, &range_count1, &ranges1);
+	multirange_deserialize(mr2, &range_count2, &ranges2);
+
+	PG_RETURN_MULTIRANGE_P(multirange_intersect_multirange_internal(mltrngtypoid,
+																	rangetyp,
+																	range_count1,
+																	ranges1,
+																	range_count2,
+																	ranges2));
+}
+
+MultirangeType *
+multirange_intersect_multirange_internal(Oid mltrngtypoid, TypeCacheEntry *rangetyp,
+										 int32 range_count1, RangeType **ranges1,
+										 int32 range_count2, RangeType **ranges2)
+{
+	RangeType  *r1;
+	RangeType  *r2;
+	RangeType **ranges3;
+	int32		range_count3;
+	int32		i1;
+	int32		i2;
+
+	if (range_count1 == 0 || range_count2 == 0)
+		return make_multirange(mltrngtypoid, rangetyp, 0, NULL);
+
+	/*
+	 * Worst case is a stitching pattern like this:
+	 *
+	 * mr1: --- --- --- ---
+	 * mr2:   --- --- ---
+	 * mr3:   - - - - - -
+	 *
+	 * That seems to be range_count1 + range_count2 - 1, but one extra won't
+	 * hurt.
+	 */
+	ranges3 = palloc0((range_count1 + range_count2) * sizeof(RangeType *));
+	range_count3 = 0;
+
+	/*
+	 * For each range in mr1, keep intersecting until the ranges in mr2 have
+	 * passed it. The parallel progress through mr1 and mr2 is similar to
+	 * multirange_minus_multirange_internal, but we don't have to assign back
+	 * to r1.
+	 */
+	r2 = ranges2[0];
+	for (i1 = 0, i2 = 0; i1 < range_count1; i1++)
+	{
+		r1 = ranges1[i1];
+
+		/* Discard r2s while r2 << r1 */
+		while (r2 != NULL && range_before_internal(rangetyp, r2, r1))
+		{
+			r2 = ++i2 >= range_count2 ? NULL : ranges2[i2];
+		}
+
+		while (r2 != NULL)
+		{
+			if (range_overlaps_internal(rangetyp, r1, r2))
+			{
+				/* Keep the overlapping part */
+				ranges3[range_count3++] = range_intersect_internal(rangetyp, r1, r2);
+
+				/* If we "used up" all of r2, go to the next one... */
+				if (range_overleft_internal(rangetyp, r2, r1))
+					r2 = ++i2 >= range_count2 ? NULL : ranges2[i2];
+
+				/* ...otherwise go to the next r1 */
+				else
+					break;
+			}
+			else
+				/* We're past r1, so move to the next one */
+				break;
+		}
+
+		/* If we're out of r2s, there can be no more intersections */
+		if (r2 == NULL)
+			break;
+	}
+
+	return make_multirange(mltrngtypoid, rangetyp, range_count3, ranges3);
 }
 
+/*
+ * range_agg_transfn: combine adjacent/overlapping ranges.
+ *
+ * All we do here is gather the input ranges into an array
+ * so that the finalfn can sort and combine them.
+ */
 Datum
-multirange_gt(PG_FUNCTION_ARGS)
+range_agg_transfn(PG_FUNCTION_ARGS)
 {
-	int			cmp = multirange_cmp(fcinfo);
+	MemoryContext aggContext;
+	Oid			rngtypoid;
+	ArrayBuildState *state;
 
-	PG_RETURN_BOOL(cmp > 0);
+	if (!AggCheckCallContext(fcinfo, &aggContext))
+		elog(ERROR, "range_agg_transfn called in non-aggregate context");
+
+	rngtypoid = get_fn_expr_argtype(fcinfo->flinfo, 1);
+	if (!type_is_range(rngtypoid))
+		ereport(ERROR, (errmsg("range_agg must be called with a range")));
+
+	if (PG_ARGISNULL(0))
+		state = initArrayResult(rngtypoid, aggContext, false);
+	else
+		state = (ArrayBuildState *) PG_GETARG_POINTER(0);
+
+	/* skip NULLs */
+	if (!PG_ARGISNULL(1))
+		accumArrayResult(state, PG_GETARG_DATUM(1), false, rngtypoid, aggContext);
+
+	PG_RETURN_POINTER(state);
+}
+
+/*
+ * range_agg_finalfn: use our internal array to merge touching ranges.
+ */
+Datum
+range_agg_finalfn(PG_FUNCTION_ARGS)
+{
+	MemoryContext aggContext;
+	Oid			mltrngtypoid;
+	TypeCacheEntry *typcache;
+	ArrayBuildState *state;
+	int32		range_count;
+	RangeType **ranges;
+	int			i;
+
+	if (!AggCheckCallContext(fcinfo, &aggContext))
+		elog(ERROR, "range_agg_finalfn called in non-aggregate context");
+
+	state = PG_ARGISNULL(0) ? NULL : (ArrayBuildState *) PG_GETARG_POINTER(0);
+	if (state == NULL)
+		/* This shouldn't be possible, but just in case.... */
+		PG_RETURN_NULL();
+
+	/* Also return NULL if we had zero inputs, like other aggregates */
+	range_count = state->nelems;
+	if (range_count == 0)
+		PG_RETURN_NULL();
+
+	mltrngtypoid = get_fn_expr_rettype(fcinfo->flinfo);
+	typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
+
+	ranges = palloc0(range_count * sizeof(RangeType *));
+	for (i = 0; i < range_count; i++)
+		ranges[i] = DatumGetRangeTypeP(state->dvalues[i]);
+
+	PG_RETURN_MULTIRANGE_P(make_multirange(mltrngtypoid, typcache->rngtype, range_count, ranges));
+}
+
+Datum
+multirange_intersect_agg_transfn(PG_FUNCTION_ARGS)
+{
+	MemoryContext aggContext;
+	Oid			mltrngtypoid;
+	TypeCacheEntry *typcache;
+	MultirangeType *result;
+	MultirangeType *current;
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+
+	if (!AggCheckCallContext(fcinfo, &aggContext))
+		elog(ERROR, "multirange_intersect_agg_transfn called in non-aggregate context");
+
+	mltrngtypoid = get_fn_expr_argtype(fcinfo->flinfo, 1);
+	if (!type_is_multirange(mltrngtypoid))
+		ereport(ERROR, (errmsg("range_intersect_agg must be called with a multirange")));
+
+	typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
+
+	/* strictness ensures these are non-null */
+	result = PG_GETARG_MULTIRANGE_P(0);
+	current = PG_GETARG_MULTIRANGE_P(1);
+
+	multirange_deserialize(result, &range_count1, &ranges1);
+	multirange_deserialize(current, &range_count2, &ranges2);
+
+	result = multirange_intersect_multirange_internal(mltrngtypoid,
+													  typcache->rngtype,
+													  range_count1,
+													  ranges1,
+													  range_count2,
+													  ranges2);
+	PG_RETURN_RANGE_P(result);
+}
+
+
+/* multirange -> element type functions */
+
+/* extract lower bound value */
+Datum
+multirange_lower(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+	bool		isnull;
+	Datum		result;
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_NULL();
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	result = range_lower_internal(typcache->rngtype, ranges[0], &isnull);
+
+	if (isnull)
+		PG_RETURN_NULL();
+	else
+		PG_RETURN_DATUM(result);
+}
+
+/* extract upper bound value */
+Datum
+multirange_upper(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+	bool		isnull;
+	Datum		result;
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_NULL();
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	result = range_upper_internal(typcache->rngtype, ranges[range_count - 1], &isnull);
+
+	if (isnull)
+		PG_RETURN_NULL();
+	else
+		PG_RETURN_DATUM(result);
+}
+
+
+/* multirange -> bool functions */
+
+/* is multirange empty? */
+Datum
+multirange_empty(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+
+	PG_RETURN_BOOL(mr->rangeCount == 0);
+}
+
+/* is lower bound inclusive? */
+Datum
+multirange_lower_inc(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	int32		range_count;
+	RangeType **ranges;
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_has_flag(ranges[0], RANGE_LB_INC));
+}
+
+/* is upper bound inclusive? */
+Datum
+multirange_upper_inc(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	int32		range_count;
+	RangeType **ranges;
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_has_flag(ranges[range_count - 1], RANGE_UB_INC));
+}
+
+/* is lower bound infinite? */
+Datum
+multirange_lower_inf(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	int32		range_count;
+	RangeType **ranges;
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_has_flag(ranges[0], RANGE_LB_INF));
+}
+
+/* is upper bound infinite? */
+Datum
+multirange_upper_inf(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	int32		range_count;
+	RangeType **ranges;
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_has_flag(ranges[range_count - 1], RANGE_UB_INF));
+}
+
+
+
+/* multirange, element -> bool functions */
+
+/* contains? */
+Datum
+multirange_contains_elem(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	Datum		val = PG_GETARG_DATUM(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(multirange_contains_elem_internal(typcache, mr, val));
+}
+
+/* contained by? */
+Datum
+elem_contained_by_multirange(PG_FUNCTION_ARGS)
+{
+	Datum		val = PG_GETARG_DATUM(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(multirange_contains_elem_internal(typcache, mr, val));
+}
+
+/*
+ * Test whether multirange mr contains a specific element value.
+ */
+bool
+multirange_contains_elem_internal(TypeCacheEntry *typcache, MultirangeType * mr, Datum val)
+{
+	TypeCacheEntry *rangetyp;
+	int32		range_count;
+	RangeType **ranges;
+	RangeType  *r;
+	int			i;
+
+	rangetyp = typcache->rngtype;
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	for (i = 0; i < range_count; i++)
+	{
+		r = ranges[i];
+		if (range_contains_elem_internal(rangetyp, r, val))
+			return true;
+	}
+
+	return false;
+}
+
+/* multirange, range -> bool functions */
+
+/* contains? */
+Datum
+multirange_contains_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(multirange_contains_range_internal(typcache, mr, r));
+}
+
+/* contained by? */
+Datum
+range_contained_by_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(multirange_contains_range_internal(typcache, mr, r));
+}
+
+/*
+ * Test whether multirange mr contains a specific range r.
+ */
+bool
+multirange_contains_range_internal(TypeCacheEntry *typcache, MultirangeType * mr, RangeType *r)
+{
+	TypeCacheEntry *rangetyp;
+	int32		range_count;
+	RangeType **ranges;
+	RangeType  *mrr;
+	int			i;
+
+	rangetyp = typcache->rngtype;
+
+	/*
+	 * Every multirange contains an infinite number of empty ranges, even an
+	 * empty one.
+	 */
+	if (RangeIsEmpty(r))
+		return true;
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	for (i = 0; i < range_count; i++)
+	{
+		mrr = ranges[i];
+		if (range_contains_internal(rangetyp, mrr, r))
+			return true;
+	}
+
+	return false;
+}
+
+
+/* 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));
+}
+
+/* overlaps? */
+Datum
+range_overlaps_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(range_overlaps_multirange_internal(typcache, r, mr));
+}
+
+Datum
+multirange_overlaps_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(range_overlaps_multirange_internal(typcache, r, mr));
+}
+
+Datum
+multirange_overlaps_multirange(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_overlaps_multirange_internal(typcache, mr1, mr2));
+}
+
+bool
+range_overlaps_multirange_internal(TypeCacheEntry *typcache, RangeType *r, MultirangeType * mr)
+{
+	TypeCacheEntry *rangetyp;
+	int32		range_count;
+	int32		i;
+	RangeType **ranges;
+	RangeType  *mrr;
+
+	/*
+	 * Empties never overlap, even with empties. (This seems strange since
+	 * they *do* contain each other, but we want to follow how ranges work.)
+	 */
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		return false;
+
+	rangetyp = typcache->rngtype;
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	/* Scan through mrr and once it gets to r they either overlap or not */
+	mrr = ranges[0];
+
+	/* Discard mrrs while mrr << r */
+	i = 0;
+	while (range_before_internal(rangetyp, mrr, r))
+	{
+		if (++i >= range_count)
+			return false;
+		mrr = ranges[i];
+	}
+
+	/* Now either we overlap or we passed r */
+	return range_overlaps_internal(rangetyp, mrr, r);
+}
+
+bool
+multirange_overlaps_multirange_internal(TypeCacheEntry *typcache, MultirangeType * mr1,
+										MultirangeType * mr2)
+{
+	TypeCacheEntry *rangetyp;
+	int32		range_count1;
+	int32		range_count2;
+	int32		i1;
+	int32		i2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+	RangeType  *r1;
+	RangeType  *r2;
+
+	/*
+	 * Empties never overlap, even with empties. (This seems strange since
+	 * they *do* contain each other, but we want to follow how ranges work.)
+	 */
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		return false;
+
+	rangetyp = typcache->rngtype;
+
+	multirange_deserialize(mr1, &range_count1, &ranges1);
+	multirange_deserialize(mr2, &range_count2, &ranges2);
+
+	/*
+	 * Every range in mr1 gets a chance to overlap with the ranges in mr2, but
+	 * we can use their ordering to avoid O(n^2). This is similar to
+	 * range_overlaps_multirange where r1 : r2 :: mrr : r, but there if we
+	 * don't find an overlap with r we're done, and here if we don't find an
+	 * overlap with r2 we try the next r2.
+	 */
+	r1 = ranges1[0];
+	for (i1 = 0, i2 = 0; i2 < range_count2; i2++)
+	{
+		r2 = ranges2[i2];
+
+		/* Discard r1s while r1 << r2 */
+		while (range_before_internal(rangetyp, r1, r2))
+		{
+			if (++i1 >= range_count1)
+				return false;
+			r1 = ranges1[i1];
+		}
+
+		/*
+		 * If r1 && r2, we're done, otherwise we failed to find an overlap for
+		 * r2, so go to the next one.
+		 */
+		if (range_overlaps_internal(rangetyp, r1, r2))
+			return true;
+	}
+
+	/* We looked through all of mr2 without finding an overlap */
+	return false;
+}
+
+/* does not extend to right of? */
+Datum
+range_overleft_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_overleft_internal(typcache->rngtype, r, ranges[range_count - 1]));
+}
+
+Datum
+multirange_overleft_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_overleft_internal(typcache->rngtype, ranges[range_count - 1], r));
+}
+
+Datum
+multirange_overleft_multirange(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
+	MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr1));
+
+	multirange_deserialize(mr1, &range_count1, &ranges1);
+	multirange_deserialize(mr2, &range_count2, &ranges2);
+
+	PG_RETURN_BOOL(range_overleft_internal(typcache->rngtype, ranges1[range_count1 - 1], ranges2[range_count2 - 1]));
+}
+
+/* does not extend to left of? */
+Datum
+range_overright_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_overright_internal(typcache->rngtype, r, ranges[0]));
+}
+
+Datum
+multirange_overright_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_overright_internal(typcache->rngtype, ranges[0], r));
+}
+
+Datum
+multirange_overright_multirange(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
+	MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr1));
+
+	multirange_deserialize(mr1, &range_count1, &ranges1);
+	multirange_deserialize(mr2, &range_count2, &ranges2);
+
+	PG_RETURN_BOOL(range_overright_internal(typcache->rngtype, ranges1[0], ranges2[0]));
+}
+
+/* contains? */
+Datum
+multirange_contains_multirange(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_contains_multirange_internal(typcache, mr1, mr2));
+}
+
+/* contained by? */
+Datum
+multirange_contained_by_multirange(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_contains_multirange_internal(typcache, mr2, mr1));
+}
+
+/*
+ * Test whether multirange mr1 contains every range from another multirange mr2.
+ */
+bool
+multirange_contains_multirange_internal(TypeCacheEntry *typcache,
+										MultirangeType * mr1, MultirangeType * mr2)
+{
+	TypeCacheEntry *rangetyp;
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+	RangeType  *r1;
+	RangeType  *r2;
+	int			i1,
+				i2;
+
+	rangetyp = typcache->rngtype;
+
+	multirange_deserialize(mr1, &range_count1, &ranges1);
+	multirange_deserialize(mr2, &range_count2, &ranges2);
+
+	/*
+	 * We follow the same logic for empties as ranges: - an empty multirange
+	 * contains an empty range/multirange. - an empty multirange can't contain
+	 * any other range/multirange. - an empty multirange is contained by any
+	 * other range/multirange.
+	 */
+
+	if (range_count2 == 0)
+		return true;
+	if (range_count1 == 0)
+		return false;
+
+	/*
+	 * Every range in mr2 must be contained by some range in mr1. To avoid
+	 * O(n^2) we walk through both ranges in tandem.
+	 */
+	r1 = ranges1[0];
+	for (i1 = 0, i2 = 0; i2 < range_count2; i2++)
+	{
+		r2 = ranges2[i2];
+
+		/* Discard r1s while r1 << r2 */
+		while (range_before_internal(rangetyp, r1, r2))
+		{
+			if (++i1 >= range_count1)
+				return false;
+			r1 = ranges1[i1];
+		}
+
+		/*
+		 * If r1 @> r2, go to the next r2, otherwise return false (since every
+		 * r1[n] and r1[n+1] must have a gap). Note this will give weird
+		 * answers if you don't canonicalize, e.g. with a custom
+		 * int2multirange {[1,1], [2,2]} there is a "gap". But that is
+		 * consistent with other range operators, e.g. '[1,1]'::int2range -|-
+		 * '[2,2]'::int2range is false.
+		 */
+		if (!range_contains_internal(rangetyp, r1, r2))
+			return false;
+	}
+
+	/* All ranges in mr2 are satisfied */
+	return true;
+}
+
+/* strictly left of? */
+Datum
+range_before_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(range_before_multirange_internal(typcache, r, mr));
+}
+
+Datum
+multirange_before_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(range_after_multirange_internal(typcache, r, mr));
+}
+
+Datum
+multirange_before_multirange(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_before_multirange_internal(typcache, mr1, mr2));
+}
+
+/* strictly right of? */
+Datum
+range_after_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(range_after_multirange_internal(typcache, r, mr));
+}
+
+Datum
+multirange_after_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(range_before_multirange_internal(typcache, r, mr));
+}
+
+Datum
+multirange_after_multirange(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_before_multirange_internal(typcache, mr2, mr1));
+}
+
+/* strictly left of? (internal version) */
+bool
+range_before_multirange_internal(TypeCacheEntry *typcache, RangeType *r,
+								 MultirangeType * mr)
+{
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		return false;
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	return range_before_internal(typcache->rngtype, r, ranges[0]);
+}
+
+bool
+multirange_before_multirange_internal(TypeCacheEntry *typcache, MultirangeType * mr1,
+									  MultirangeType * mr2)
+{
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		return false;
+
+	multirange_deserialize(mr1, &range_count1, &ranges1);
+	multirange_deserialize(mr2, &range_count2, &ranges2);
+
+	return range_before_internal(typcache->rngtype, ranges1[range_count1 - 1],
+								 ranges2[0]);
+}
+
+/* strictly right of? (internal version) */
+bool
+range_after_multirange_internal(TypeCacheEntry *typcache, RangeType *r,
+								MultirangeType * mr)
+{
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		return false;
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	return range_after_internal(typcache->rngtype, r, ranges[range_count - 1]);
+}
+
+/* adjacent to? */
+Datum
+range_adjacent_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		return false;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_adjacent_internal(typcache->rngtype, r, ranges[0]));
+}
+
+Datum
+multirange_adjacent_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		return false;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_adjacent_internal(typcache->rngtype, ranges[range_count - 1], r));
+}
+
+Datum
+multirange_adjacent_multirange(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
+	MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		return false;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr1));
+
+	multirange_deserialize(mr1, &range_count1, &ranges1);
+	multirange_deserialize(mr2, &range_count2, &ranges2);
+
+	PG_RETURN_BOOL(range_adjacent_internal(typcache->rngtype, ranges1[range_count1 - 1], ranges2[0]));
+}
+
+/* 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);
+}
+
+/* multirange -> range functions */
+
+/* Find the smallest range that includes everything in the multirange */
+Datum
+range_merge_from_multirange(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	Oid			mltrngtypoid = MultirangeTypeGetOid(mr);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_RANGE_P(make_empty_range(typcache->rngtype));
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_RANGE_P(range_union_internal(typcache->rngtype, ranges[0],
+										   ranges[range_count - 1], false));
 }
 
 /* Hash support */
diff --git a/src/backend/utils/adt/rangetypes.c b/src/backend/utils/adt/rangetypes.c
index 65f68614c1..0651618363 100644
--- a/src/backend/utils/adt/rangetypes.c
+++ b/src/backend/utils/adt/rangetypes.c
@@ -429,19 +429,37 @@ range_lower(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
 	TypeCacheEntry *typcache;
+	bool		isnull;
+	Datum		result;
+
+	typcache = range_get_typcache(fcinfo, RangeTypeGetOid(r1));
+
+	result = range_lower_internal(typcache, r1, &isnull);
+
+	if (isnull)
+		PG_RETURN_NULL();
+	else
+		PG_RETURN_DATUM(result);
+}
+
+Datum
+range_lower_internal(TypeCacheEntry *typcache, const RangeType *r1, bool *isnull)
+{
 	RangeBound	lower;
 	RangeBound	upper;
 	bool		empty;
 
-	typcache = range_get_typcache(fcinfo, RangeTypeGetOid(r1));
-
 	range_deserialize(typcache, r1, &lower, &upper, &empty);
 
 	/* Return NULL if there's no finite lower bound */
 	if (empty || lower.infinite)
-		PG_RETURN_NULL();
+	{
+		*isnull = true;
+		return 0;
+	}
 
-	PG_RETURN_DATUM(lower.val);
+	*isnull = false;
+	return lower.val;
 }
 
 /* extract upper bound value */
@@ -450,19 +468,37 @@ range_upper(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
 	TypeCacheEntry *typcache;
+	bool		isnull;
+	Datum		result;
+
+	typcache = range_get_typcache(fcinfo, RangeTypeGetOid(r1));
+
+	result = range_upper_internal(typcache, r1, &isnull);
+
+	if (isnull)
+		PG_RETURN_NULL();
+	else
+		PG_RETURN_DATUM(result);
+}
+
+Datum
+range_upper_internal(TypeCacheEntry *typcache, const RangeType *r1, bool *isnull)
+{
 	RangeBound	lower;
 	RangeBound	upper;
 	bool		empty;
 
-	typcache = range_get_typcache(fcinfo, RangeTypeGetOid(r1));
-
 	range_deserialize(typcache, r1, &lower, &upper, &empty);
 
 	/* Return NULL if there's no finite upper bound */
 	if (empty || upper.infinite)
-		PG_RETURN_NULL();
+	{
+		*isnull = true;
+		return 0;
+	}
 
-	PG_RETURN_DATUM(upper.val);
+	*isnull = false;
+	return upper.val;
 }
 
 
@@ -473,9 +509,8 @@ Datum
 range_empty(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
-	char		flags = range_get_flags(r1);
 
-	PG_RETURN_BOOL(flags & RANGE_EMPTY);
+	PG_RETURN_BOOL(range_has_flag(r1, RANGE_EMPTY));
 }
 
 /* is lower bound inclusive? */
@@ -483,9 +518,8 @@ Datum
 range_lower_inc(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
-	char		flags = range_get_flags(r1);
 
-	PG_RETURN_BOOL(flags & RANGE_LB_INC);
+	PG_RETURN_BOOL(range_has_flag(r1, RANGE_LB_INC));
 }
 
 /* is upper bound inclusive? */
@@ -493,9 +527,8 @@ Datum
 range_upper_inc(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
-	char		flags = range_get_flags(r1);
 
-	PG_RETURN_BOOL(flags & RANGE_UB_INC);
+	PG_RETURN_BOOL(range_has_flag(r1, RANGE_UB_INC));
 }
 
 /* is lower bound infinite? */
@@ -503,9 +536,8 @@ Datum
 range_lower_inf(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
-	char		flags = range_get_flags(r1);
 
-	PG_RETURN_BOOL(flags & RANGE_LB_INF);
+	PG_RETURN_BOOL(range_has_flag(r1, RANGE_LB_INF));
 }
 
 /* is upper bound infinite? */
@@ -513,9 +545,8 @@ Datum
 range_upper_inf(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
-	char		flags = range_get_flags(r1);
 
-	PG_RETURN_BOOL(flags & RANGE_UB_INF);
+	PG_RETURN_BOOL(range_has_flag(r1, RANGE_UB_INF));
 }
 
 
@@ -955,7 +986,25 @@ range_minus(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
 	RangeType  *r2 = PG_GETARG_RANGE_P(1);
+	RangeType  *ret;
 	TypeCacheEntry *typcache;
+
+	/* 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));
+
+	ret = range_minus_internal(typcache, r1, r2);
+	if (ret)
+		PG_RETURN_RANGE_P(ret);
+	else
+		PG_RETURN_NULL();
+}
+
+RangeType *
+range_minus_internal(TypeCacheEntry *typcache, RangeType *r1, RangeType *r2)
+{
 	RangeBound	lower1,
 				lower2;
 	RangeBound	upper1,
@@ -967,18 +1016,12 @@ range_minus(PG_FUNCTION_ARGS)
 				cmp_u1l2,
 				cmp_u1u2;
 
-	/* 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));
-
 	range_deserialize(typcache, r1, &lower1, &upper1, &empty1);
 	range_deserialize(typcache, r2, &lower2, &upper2, &empty2);
 
 	/* if either is empty, r1 is the correct answer */
 	if (empty1 || empty2)
-		PG_RETURN_RANGE_P(r1);
+		return r1;
 
 	cmp_l1l2 = range_cmp_bounds(typcache, &lower1, &lower2);
 	cmp_l1u2 = range_cmp_bounds(typcache, &lower1, &upper2);
@@ -991,34 +1034,34 @@ range_minus(PG_FUNCTION_ARGS)
 				 errmsg("result of range difference would not be contiguous")));
 
 	if (cmp_l1u2 > 0 || cmp_u1l2 < 0)
-		PG_RETURN_RANGE_P(r1);
+		return r1;
 
 	if (cmp_l1l2 >= 0 && cmp_u1u2 <= 0)
-		PG_RETURN_RANGE_P(make_empty_range(typcache));
+		return make_empty_range(typcache);
 
 	if (cmp_l1l2 <= 0 && cmp_u1l2 >= 0 && cmp_u1u2 <= 0)
 	{
 		lower2.inclusive = !lower2.inclusive;
 		lower2.lower = false;	/* it will become the upper bound */
-		PG_RETURN_RANGE_P(make_range(typcache, &lower1, &lower2, false));
+		return make_range(typcache, &lower1, &lower2, false);
 	}
 
 	if (cmp_l1l2 >= 0 && cmp_u1u2 >= 0 && cmp_l1u2 <= 0)
 	{
 		upper2.inclusive = !upper2.inclusive;
 		upper2.lower = true;	/* it will become the lower bound */
-		PG_RETURN_RANGE_P(make_range(typcache, &upper2, &upper1, false));
+		return make_range(typcache, &upper2, &upper1, false);
 	}
 
 	elog(ERROR, "unexpected case in range_minus");
-	PG_RETURN_NULL();
+	return NULL;
 }
 
 /*
  * Set union.  If strict is true, it is an error that the two input ranges
  * are not adjacent or overlapping.
  */
-static RangeType *
+RangeType *
 range_union_internal(TypeCacheEntry *typcache, RangeType *r1, RangeType *r2,
 					 bool strict)
 {
@@ -1099,6 +1142,19 @@ range_intersect(PG_FUNCTION_ARGS)
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
 	RangeType  *r2 = PG_GETARG_RANGE_P(1);
 	TypeCacheEntry *typcache;
+
+	/* 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));
+
+	PG_RETURN_RANGE_P(range_intersect_internal(typcache, r1, r2));
+}
+
+RangeType *
+range_intersect_internal(TypeCacheEntry *typcache, const RangeType *r1, const RangeType *r2)
+{
 	RangeBound	lower1,
 				lower2;
 	RangeBound	upper1,
@@ -1108,17 +1164,11 @@ range_intersect(PG_FUNCTION_ARGS)
 	RangeBound *result_lower;
 	RangeBound *result_upper;
 
-	/* 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));
-
 	range_deserialize(typcache, r1, &lower1, &upper1, &empty1);
 	range_deserialize(typcache, r2, &lower2, &upper2, &empty2);
 
-	if (empty1 || empty2 || !DatumGetBool(range_overlaps(fcinfo)))
-		PG_RETURN_RANGE_P(make_empty_range(typcache));
+	if (empty1 || empty2 || !range_overlaps_internal(typcache, r1, r2))
+		return make_empty_range(typcache);
 
 	if (range_cmp_bounds(typcache, &lower1, &lower2) >= 0)
 		result_lower = &lower1;
@@ -1130,54 +1180,83 @@ range_intersect(PG_FUNCTION_ARGS)
 	else
 		result_upper = &upper2;
 
-	PG_RETURN_RANGE_P(make_range(typcache, result_lower, result_upper, false));
+	return make_range(typcache, result_lower, result_upper, false);
 }
 
-/* Btree support */
+/* range, range -> range, range functions */
 
-/* btree comparator */
-Datum
-range_cmp(PG_FUNCTION_ARGS)
+/*
+ * range_split_internal - if r2 intersects the middle of r1, leaving non-empty
+ * ranges on both sides, then return true and set output1 and output2 to the
+ * results of r1 - r2 (in order). Otherwise return false and don't set output1
+ * or output2. Neither input range should be empty.
+ */
+bool
+range_split_internal(TypeCacheEntry *typcache, const RangeType *r1, const RangeType *r2,
+					 RangeType **output1, RangeType **output2)
 {
-	RangeType  *r1 = PG_GETARG_RANGE_P(0);
-	RangeType  *r2 = PG_GETARG_RANGE_P(1);
-	TypeCacheEntry *typcache;
 	RangeBound	lower1,
 				lower2;
 	RangeBound	upper1,
 				upper2;
 	bool		empty1,
 				empty2;
-	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));
 
 	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
+	if (range_cmp_bounds(typcache, &lower1, &lower2) < 0 &&
+		range_cmp_bounds(typcache, &upper1, &upper2) > 0)
 	{
-		cmp = range_cmp_bounds(typcache, &lower1, &lower2);
-		if (cmp == 0)
-			cmp = range_cmp_bounds(typcache, &upper1, &upper2);
+		/*
+		 * Need to invert inclusive/exclusive for the lower2 and upper2
+		 * points. They can't be infinite though. We're allowed to overwrite
+		 * these RangeBounds since they only exist locally.
+		 */
+		lower2.inclusive = !lower2.inclusive;
+		lower2.lower = false;
+		upper2.inclusive = !upper2.inclusive;
+		upper2.lower = true;
+
+		*output1 = make_range(typcache, &lower1, &lower2, false);
+		*output2 = make_range(typcache, &upper2, &upper1, false);
+		return true;
 	}
 
-	return cmp;
+	return false;
+}
+
+/* range -> range aggregate functions */
+
+Datum
+range_intersect_agg_transfn(PG_FUNCTION_ARGS)
+{
+	MemoryContext aggContext;
+	Oid			rngtypoid;
+	TypeCacheEntry *typcache;
+	RangeType  *result;
+	RangeType  *current;
+
+	if (!AggCheckCallContext(fcinfo, &aggContext))
+		elog(ERROR, "range_intersect_agg_transfn called in non-aggregate context");
+
+	rngtypoid = get_fn_expr_argtype(fcinfo->flinfo, 1);
+	if (!type_is_range(rngtypoid))
+		ereport(ERROR, (errmsg("range_intersect_agg must be called with a range")));
+
+	typcache = range_get_typcache(fcinfo, rngtypoid);
+
+	/* strictness ensures these are non-null */
+	result = PG_GETARG_RANGE_P(0);
+	current = PG_GETARG_RANGE_P(1);
+
+	result = range_intersect_internal(typcache, result, current);
+	PG_RETURN_RANGE_P(result);
 }
 
+
+/* Btree support */
+
 /* btree comparator */
 Datum
 range_cmp(PG_FUNCTION_ARGS)
@@ -1207,7 +1286,7 @@ range_cmp(PG_FUNCTION_ARGS)
  * Internal version of range_cmp
  */
 int
-range_cmp_internal(TypeCacheEntry *typcache, RangeType *r1, RangeType *r2)
+range_cmp_internal(TypeCacheEntry *typcache, const RangeType *r1, const RangeType *r2)
 {
 	RangeBound	lower1,
 				lower2;
@@ -1273,7 +1352,7 @@ range_gt(PG_FUNCTION_ARGS)
 /* Hash support */
 
 uint32
-hash_range_internal(TypeCacheEntry *typcache, RangeType *r)
+hash_range_internal(TypeCacheEntry *typcache, const RangeType *r)
 {
 	uint32		result;
 	TypeCacheEntry *scache;
@@ -1343,7 +1422,7 @@ hash_range(PG_FUNCTION_ARGS)
 }
 
 uint64
-hash_range_extended_internal(TypeCacheEntry *typcache, RangeType *r, Datum seed)
+hash_range_extended_internal(TypeCacheEntry *typcache, const RangeType *r, Datum seed)
 {
 	uint64		result;
 	TypeCacheEntry *scache;
@@ -1838,6 +1917,21 @@ range_get_flags(const RangeType *range)
 }
 
 /*
+ * range_has_flag: set whether a range has a specific flag.
+ *
+ * This lets expose some of our functions that just check flags
+ * to the rest of the code base (like to multiranges)
+ * without writing full-fledged *_internal versions.
+ */
+bool
+range_has_flag(const RangeType *r1, char flag)
+{
+	char		flags = range_get_flags(r1);
+
+	return flags & flag;
+}
+
+/*
  * range_set_contain_empty: set the RANGE_CONTAIN_EMPTY bit in the value.
  *
  * This is only needed in GiST operations, so we don't include a provision
diff --git a/src/backend/utils/fmgr/funcapi.c b/src/backend/utils/fmgr/funcapi.c
index d65968d4eb..25623461bb 100644
--- a/src/backend/utils/fmgr/funcapi.c
+++ b/src/backend/utils/fmgr/funcapi.c
@@ -573,18 +573,20 @@ resolve_polymorphic_tupdesc(TupleDesc tupdesc, oidvector *declared_args,
 
 		if (OidIsValid(anymultirange_type))
 		{
-			Oid			rngtype = resolve_generic_type(ANYRANGEOID,
-													   anymultirange_type,
-													   ANYMULTIRANGEOID);
+			Oid			rngtype;
+			Oid			subtype;
+
+			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);
+			subtype = resolve_generic_type(ANYELEMENTOID,
+										   anyrange_type,
+										   ANYRANGEOID);
 
 			/* check for inconsistent array and multirange results */
 			if (OidIsValid(anyelement_type) && anyelement_type != subtype)
@@ -628,18 +630,21 @@ resolve_polymorphic_tupdesc(TupleDesc tupdesc, oidvector *declared_args,
 	{
 		if (OidIsValid(anymultirange_type))
 		{
-			Oid			rngtype = resolve_generic_type(ANYRANGEOID,
-													   anymultirange_type,
-													   ANYMULTIRANGEOID);
+			Oid		rngtype;
+			Oid		subtype;
+
+			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);
+			subtype = resolve_generic_type(ANYELEMENTOID,
+										   anyrange_type,
+										   ANYRANGEOID);
 
 			/* check for inconsistent array and multirange results */
 			if (OidIsValid(anyelement_type) && anyelement_type != subtype)
@@ -660,21 +665,25 @@ resolve_polymorphic_tupdesc(TupleDesc tupdesc, oidvector *declared_args,
 	{
 		if (OidIsValid(anyrange_type))
 		{
-			Oid			subtype = resolve_generic_type(ANYELEMENTOID,
-													   anyrange_type,
-													   ANYRANGEOID);
+			Oid		subtype;
+			Oid		mltrngtype;
+			Oid		rngtype;
+
+			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);
+			mltrngtype = resolve_generic_type(ANYMULTIRANGEOID,
+											  anyrange_type,
+											  ANYRANGEOID);
 
 			/* check for inconsistent range and multirange results */
-			Oid			rngtype = get_multirange_subtype(mltrngtype);
+			rngtype = get_multirange_subtype(mltrngtype);
 
 			if (OidIsValid(anyrange_type) && anyrange_type != rngtype)
 				return false;
@@ -900,18 +909,21 @@ resolve_polymorphic_argtypes(int numargs, Oid *argtypes, char *argmodes,
 
 		if (OidIsValid(anymultirange_type))
 		{
-			Oid			rngtype = resolve_generic_type(ANYRANGEOID,
-													   anymultirange_type,
-													   ANYMULTIRANGEOID);
+			Oid			rngtype;
+			Oid			subtype;
+			
+			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);
+			subtype = resolve_generic_type(ANYELEMENTOID,
+										   anyrange_type,
+										   ANYRANGEOID);
 
 			/* check for inconsistent array and multirange results */
 			if (OidIsValid(anyelement_type) && anyelement_type != subtype)
@@ -955,18 +967,21 @@ resolve_polymorphic_argtypes(int numargs, Oid *argtypes, char *argmodes,
 	{
 		if (OidIsValid(anymultirange_type))
 		{
-			Oid			rngtype = resolve_generic_type(ANYRANGEOID,
-													   anymultirange_type,
-													   ANYMULTIRANGEOID);
+			Oid			rngtype;
+			Oid			subtype;
+
+			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);
+			subtype = resolve_generic_type(ANYELEMENTOID,
+										   anyrange_type,
+										   ANYRANGEOID);
 
 			/* check for inconsistent array and multirange results */
 			if (OidIsValid(anyelement_type) && anyelement_type != subtype)
@@ -987,21 +1002,25 @@ resolve_polymorphic_argtypes(int numargs, Oid *argtypes, char *argmodes,
 	{
 		if (OidIsValid(anyrange_type))
 		{
-			Oid			subtype = resolve_generic_type(ANYELEMENTOID,
-													   anyrange_type,
-													   ANYRANGEOID);
+			Oid			subtype;
+			Oid			mltrngtype;
+			Oid			rngtype;
+
+			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);
+			mltrngtype = resolve_generic_type(ANYMULTIRANGEOID,
+											  anyrange_type,
+											  ANYRANGEOID);
 
 			/* check for inconsistent range and multirange results */
-			Oid			rngtype = get_multirange_subtype(mltrngtype);
+			rngtype = get_multirange_subtype(mltrngtype);
 
 			if (OidIsValid(anyrange_type) && anyrange_type != rngtype)
 				return false;
diff --git a/src/include/catalog/pg_aggregate.dat b/src/include/catalog/pg_aggregate.dat
index 242d843347..07a7211c15 100644
--- a/src/include/catalog/pg_aggregate.dat
+++ b/src/include/catalog/pg_aggregate.dat
@@ -538,6 +538,17 @@
   aggtransfn => 'bytea_string_agg_transfn',
   aggfinalfn => 'bytea_string_agg_finalfn', aggtranstype => 'internal' },
 
+# range
+{ aggfnoid => 'range_intersect_agg(anyrange)',
+  aggtransfn => 'range_intersect_agg_transfn',
+  aggcombinefn => 'range_intersect_agg_transfn', aggtranstype => 'anyrange' },
+{ aggfnoid => 'range_intersect_agg(anymultirange)',
+  aggtransfn => 'multirange_intersect_agg_transfn',
+  aggcombinefn => 'multirange_intersect_agg_transfn', aggtranstype => 'anymultirange' },
+{ aggfnoid => 'range_agg(anyrange)', aggtransfn => 'range_agg_transfn',
+  aggfinalfn => 'range_agg_finalfn', aggfinalextra => 't',
+  aggtranstype => 'internal' },
+
 # json
 { aggfnoid => 'json_agg', aggtransfn => 'json_agg_transfn',
   aggfinalfn => 'json_agg_finalfn', aggtranstype => 'internal' },
diff --git a/src/include/catalog/pg_amproc.dat b/src/include/catalog/pg_amproc.dat
index b7cb2cb000..32965d6b3d 100644
--- a/src/include/catalog/pg_amproc.dat
+++ b/src/include/catalog/pg_amproc.dat
@@ -1210,12 +1210,13 @@
   amprocrighttype => 'anyrange', amprocnum => '4',
   amproc => 'brin_inclusion_union' },
 { amprocfamily => 'brin/range_inclusion_ops', amproclefttype => 'anyrange',
-  amprocrighttype => 'anyrange', amprocnum => '11', amproc => 'range_merge' },
+  amprocrighttype => 'anyrange', amprocnum => '11',
+  amproc => 'range_merge(anyrange,anyrange)' },
 { amprocfamily => 'brin/range_inclusion_ops', amproclefttype => 'anyrange',
   amprocrighttype => 'anyrange', amprocnum => '13',
   amproc => 'range_contains' },
 { amprocfamily => 'brin/range_inclusion_ops', amproclefttype => 'anyrange',
-  amprocrighttype => 'anyrange', amprocnum => '14', amproc => 'isempty' },
+  amprocrighttype => 'anyrange', amprocnum => '14', amproc => 'isempty(anyrange)' },
 
 # minmax pg_lsn
 { amprocfamily => 'brin/pg_lsn_minmax_ops', amproclefttype => 'pg_lsn',
diff --git a/src/include/catalog/pg_operator.dat b/src/include/catalog/pg_operator.dat
index 4d319bb5b3..ade223b6f3 100644
--- a/src/include/catalog/pg_operator.dat
+++ b/src/include/catalog/pg_operator.dat
@@ -3322,5 +3322,174 @@
   oprresult => 'bool', oprcom => '<(anymultirange,anymultirange)',
   oprnegate => '<=(anymultirange,anymultirange)', oprcode => 'multirange_gt',
   oprrest => 'scalargtsel', oprjoin => 'scalargtjoinsel' },
+{ oid => '8073', oid_symbol => 'OID_RANGE_OVERLAPS_MULTIRANGE_OP',
+  descr => 'overlaps',
+  oprname => '&&', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '&&(anymultirange,anyrange)',
+  oprcode => 'range_overlaps_multirange', oprrest => 'areasel',
+  oprjoin => 'areajoinsel' },
+{ oid => '8074', oid_symbol => 'OID_MULTIRANGE_OVERLAPS_RANGE_OP', descr => 'contains',
+  oprname => '&&', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcom => '&&(anyrange,anymultirange)',
+  oprcode => 'multirange_overlaps_range', oprrest => 'areasel',
+  oprjoin => 'areajoinsel' },
+{ oid => '8075', oid_symbol => 'OID_MULTIRANGE_OVERLAPS_MULTIRANGE_OP', descr => 'contains',
+  oprname => '&&', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '&&(anymultirange,anymultirange)',
+  oprcode => 'multirange_overlaps_multirange', oprrest => 'areasel',
+  oprjoin => 'areajoinsel' },
+{ oid => '8064', oid_symbol => 'OID_MULTIRANGE_CONTAINS_ELEM_OP',
+  descr => 'contains',
+  oprname => '@>', oprleft => 'anymultirange', oprright => 'anyelement',
+  oprresult => 'bool', oprcom => '<@(anyelement,anymultirange)',
+  oprcode => 'multirange_contains_elem', oprrest => 'contsel',
+  oprjoin => 'contjoinsel' },
+{ oid => '8065', oid_symbol => 'OID_MULTIRANGE_CONTAINS_RANGE_OP', descr => 'contains',
+  oprname => '@>', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcom => '<@(anyrange,anymultirange)',
+  oprcode => 'multirange_contains_range', oprrest => 'contsel',
+  oprjoin => 'contjoinsel' },
+{ oid => '8066', oid_symbol => 'OID_MULTIRANGE_CONTAINS_MULTIRANGE_OP', descr => 'contains',
+  oprname => '@>', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '<@(anymultirange,anymultirange)',
+  oprcode => 'multirange_contains_multirange', oprrest => 'contsel',
+  oprjoin => 'contjoinsel' },
+{ oid => '8067', oid_symbol => 'OID_MULTIRANGE_ELEM_CONTAINED_OP',
+  descr => 'is contained by',
+  oprname => '<@', oprleft => 'anyelement', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '@>(anymultirange,anyelement)',
+  oprcode => 'elem_contained_by_multirange', oprrest => 'contsel',
+  oprjoin => 'contjoinsel' },
+{ oid => '8068', oid_symbol => 'OID_MULTIRANGE_RANGE_CONTAINED_OP',
+  descr => 'is contained by',
+  oprname => '<@', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '@>(anymultirange,anyrange)',
+  oprcode => 'range_contained_by_multirange', oprrest => 'contsel',
+  oprjoin => 'contjoinsel' },
+{ oid => '8069', oid_symbol => 'OID_MULTIRANGE_MULTIRANGE_CONTAINED_OP',
+  descr => 'is contained by',
+  oprname => '<@', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '@>(anymultirange,anymultirange)',
+  oprcode => 'multirange_contained_by_multirange', oprrest => 'contsel',
+  oprjoin => 'contjoinsel' },
+{ oid => '8106', oid_symbol => 'OID_RANGE_OVERLAPS_LEFT_MULTIRANGE_OP',
+  descr => 'overlaps or is left of',
+  oprname => '&<', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcode => 'range_overleft_multirange', oprrest => 'scalarltsel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8107', oid_symbol => 'OID_MULTIRANGE_OVERLAPS_LEFT_RANGE_OP',
+  descr => 'overlaps or is left of',
+  oprname => '&<', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcode => 'multirange_overleft_range', oprrest => 'scalarltsel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8108', oid_symbol => 'OID_MULTIRANGE_OVERLAPS_LEFT_MULTIRANGE_OP',
+  descr => 'overlaps or is left of',
+  oprname => '&<', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcode => 'multirange_overleft_multirange', oprrest => 'scalarltsel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8109', oid_symbol => 'OID_RANGE_OVERLAPS_RIGHT_MULTIRANGE_OP',
+  descr => 'overlaps or is right of',
+  oprname => '&>', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcode => 'range_overright_multirange', oprrest => 'scalargtsel',
+  oprjoin => 'scalargtjoinsel' },
+{ oid => '8110', oid_symbol => 'OID_MULTIRANGE_OVERLAPS_RIGHT_RANGE_OP',
+  descr => 'overlaps or is right of',
+  oprname => '&>', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcode => 'multirange_overright_range', oprrest => 'scalargtsel',
+  oprjoin => 'scalargtjoinsel' },
+{ oid => '8111', oid_symbol => 'OID_MULTIRANGE_OVERLAPS_RIGHT_MULTIRANGE_OP',
+  descr => 'overlaps or is right of',
+  oprname => '&>', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcode => 'multirange_overright_multirange', oprrest => 'scalargtsel',
+  oprjoin => 'scalargtjoinsel' },
+{ oid => '8091', oid_symbol => 'OID_RANGE_ADJACENT_MULTIRANGE_OP', descr => 'is adjacent to',
+  oprname => '-|-', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '-|-(anymultirange,anyrange)',
+  oprcode => 'range_adjacent_multirange', oprrest => 'scalarltsel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8092', oid_symbol => 'OID_MULTIRANGE_ADJACENT_RANGE_OP', descr => 'is adjacent to',
+  oprname => '-|-', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcom => '-|-(anyrange,anymultirange)',
+  oprcode => 'multirange_adjacent_range', oprrest => 'scalarltsel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8093', oid_symbol => 'OID_MULTIRANGE_ADJACENT_MULTIRANGE_OP', descr => 'is adjacent to',
+  oprname => '-|-', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '-|-(anymultirange,anymultirange)',
+  oprcode => 'multirange_adjacent_multirange', oprrest => 'scalarltsel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8138', descr => 'multirange union',
+  oprname => '@+', oprleft => 'anyrange', oprright => 'anyrange',
+  oprresult => 'anymultirange', oprcom => '@+(anyrange,anyrange)',
+  oprcode => 'range_union_range' },
+{ oid => '8115', descr => 'multirange union',
+  oprname => '@+', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'anymultirange', oprcom => '@+(anymultirange,anyrange)',
+  oprcode => 'range_union_multirange' },
+{ oid => '8116', descr => 'multirange union',
+  oprname => '@+', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'anymultirange', oprcom => '@+(anyrange,anymultirange)',
+  oprcode => 'multirange_union_range' },
+{ oid => '8117', descr => 'multirange union',
+  oprname => '@+', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'anymultirange', oprcom => '@+(anymultirange,anymultirange)',
+  oprcode => 'multirange_union_multirange' },
+{ oid => '8140', descr => 'multirange minus',
+  oprname => '@-', oprleft => 'anyrange', oprright => 'anyrange',
+  oprresult => 'anymultirange', oprcode => 'range_minus_range' },
+{ oid => '8121', descr => 'multirange minus',
+  oprname => '@-', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'anymultirange', oprcode => 'range_minus_multirange' },
+{ oid => '8122', descr => 'multirange minus',
+  oprname => '@-', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'anymultirange', oprcode => 'multirange_minus_range' },
+{ oid => '8123', descr => 'multirange minus',
+  oprname => '@-', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'anymultirange', oprcode => 'multirange_minus_multirange' },
+{ oid => '8142', descr => 'multirange intersect',
+  oprname => '@*', oprleft => 'anyrange', oprright => 'anyrange',
+  oprresult => 'anymultirange', oprcom => '@*(anyrange,anyrange)',
+  oprcode => 'range_intersect_range' },
+{ oid => '8127', descr => 'multirange intersect',
+  oprname => '@*', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'anymultirange', oprcom => '@*(anymultirange,anyrange)',
+  oprcode => 'range_intersect_multirange' },
+{ oid => '8128', descr => 'multirange intersect',
+  oprname => '@*', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'anymultirange', oprcom => '@*(anyrange,anymultirange)',
+  oprcode => 'multirange_intersect_range' },
+{ oid => '8129', descr => 'multirange intersect',
+  oprname => '@*', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'anymultirange', oprcom => '@*(anymultirange,anymultirange)',
+  oprcode => 'multirange_intersect_multirange' },
+{ oid => '8082', oid_symbol => 'OID_RANGE_BEFORE_MULTIRANGE_OP', descr => 'is left of',
+  oprname => '<<', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '>>(anymultirange,anyrange)',
+  oprcode => 'range_before_multirange', oprrest => 'scalarltsel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8083', oid_symbol => 'OID_MULTIRANGE_BEFORE_RANGE_OP', descr => 'is left of',
+  oprname => '<<', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcom => '>>(anyrange,anymultirange)',
+  oprcode => 'multirange_before_range', oprrest => 'scalarltsel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8084', oid_symbol => 'OID_MULTIRANGE_BEFORE_MULTIRANGE_OP', descr => 'is left of',
+  oprname => '<<', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '>>(anymultirange,anymultirange)',
+  oprcode => 'multirange_before_multirange', oprrest => 'scalarltsel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8085', oid_symbol => 'OID_RANGE_AFTER_MULTIRANGE_OP', descr => 'is right of',
+  oprname => '>>', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '<<(anymultirange,anyrange)',
+  oprcode => 'range_after_multirange', oprrest => 'scalargtsel',
+  oprjoin => 'scalargtjoinsel' },
+{ oid => '8086', oid_symbol => 'OID_MULTIRANGE_AFTER_RANGE_OP', descr => 'is right of',
+  oprname => '>>', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcom => '<<(anyrange,anymultirange)',
+  oprcode => 'multirange_after_range', oprrest => 'scalargtsel',
+  oprjoin => 'scalargtjoinsel' },
+{ oid => '8087', oid_symbol => 'OID_MULTIRANGE_AFTER_MULTIRANGE_OP', descr => 'is right of',
+  oprname => '>>', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '<<(anymultirange,anymultirange)',
+  oprcode => 'multirange_after_multirange', oprrest => 'scalargtsel',
+  oprjoin => 'scalargtjoinsel' },
 
 ]
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 7ffa047969..13ea94c024 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -9607,6 +9607,10 @@
   descr => 'the smallest range which includes both of the given ranges',
   proname => 'range_merge', prorettype => 'anyrange',
   proargtypes => 'anyrange anyrange', prosrc => 'range_merge' },
+{ oid => '8143',
+  descr => 'the smallest range which includes the whole multirange',
+  proname => 'range_merge', prorettype => 'anyrange',
+  proargtypes => 'anymultirange', prosrc => 'range_merge_from_multirange' },
 { oid => '3868',
   proname => 'range_intersect', prorettype => 'anyrange',
   proargtypes => 'anyrange anyrange', prosrc => 'range_intersect' },
@@ -9656,6 +9660,13 @@
 { oid => '3169', descr => 'restriction selectivity for range operators',
   proname => 'rangesel', provolatile => 's', prorettype => 'float8',
   proargtypes => 'internal oid internal int4', prosrc => 'rangesel' },
+{ oid => '8133', descr => 'range aggregate by intersecting',
+  proname => 'range_intersect_agg_transfn', prorettype => 'anyrange',
+  proargtypes => 'anyrange anyrange', prosrc => 'range_intersect_agg_transfn'},
+{ oid => '8134', descr => 'range aggregate by intersecting',
+  proname => 'range_intersect_agg', prokind => 'a', proisstrict => 'f',
+  prorettype => 'anyrange', proargtypes => 'anyrange',
+  prosrc => 'aggregate_dummy'},
 
 { oid => '3914', descr => 'convert an int4 range to canonical form',
   proname => 'int4range_canonical', prorettype => 'int4range',
@@ -9743,9 +9754,165 @@
 { oid => '8007', descr => 'I/O',
   proname => 'multirange_send', provolatile => 's', prorettype => 'bytea',
   proargtypes => 'anymultirange', prosrc => 'multirange_send' },
+{ oid => '8094', descr => 'lower bound of multirange',
+  proname => 'lower', prorettype => 'anyelement', proargtypes => 'anymultirange',
+  prosrc => 'multirange_lower' },
+{ oid => '8095', descr => 'upper bound of multirange',
+  proname => 'upper', prorettype => 'anyelement', proargtypes => 'anymultirange',
+  prosrc => 'multirange_upper' },
+{ oid => '8057', descr => 'is the multirange empty?',
+  proname => 'isempty', prorettype => 'bool',
+  proargtypes => 'anymultirange', prosrc => 'multirange_empty' },
+{ oid => '8096', descr => 'is the multirange\'s lower bound inclusive?',
+  proname => 'lower_inc', prorettype => 'bool', proargtypes => 'anymultirange',
+  prosrc => 'multirange_lower_inc' },
+{ oid => '8097', descr => 'is the multirange\'s upper bound inclusive?',
+  proname => 'upper_inc', prorettype => 'bool', proargtypes => 'anymultirange',
+  prosrc => 'multirange_upper_inc' },
+{ oid => '8098', descr => 'is the multirange\'s lower bound infinite?',
+  proname => 'lower_inf', prorettype => 'bool', proargtypes => 'anymultirange',
+  prosrc => 'multirange_lower_inf' },
+{ oid => '8099', descr => 'is the multirange\'s upper bound infinite?',
+  proname => 'upper_inf', prorettype => 'bool', proargtypes => 'anymultirange',
+  prosrc => 'multirange_upper_inf' },
 { oid => '8008', descr => 'multirange typanalyze',
   proname => 'multirange_typanalyze', provolatile => 's', prorettype => 'bool',
   proargtypes => 'internal', prosrc => 'multirange_typanalyze' },
+{ oid => '8033',
+  proname => 'multirange_eq', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_eq' },
+{ oid => '8034',
+  proname => 'multirange_ne', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_ne' },
+{ oid => '8070',
+  proname => 'range_overlaps_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_overlaps_multirange' },
+{ oid => '8071',
+  proname => 'multirange_overlaps_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_overlaps_range' },
+{ oid => '8072',
+  proname => 'multirange_overlaps_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_overlaps_multirange' },
+{ oid => '8058',
+  proname => 'multirange_contains_elem', prorettype => 'bool',
+  proargtypes => 'anymultirange anyelement', prosrc => 'multirange_contains_elem' },
+{ oid => '8059',
+  proname => 'multirange_contains_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_contains_range' },
+{ oid => '8060',
+  proname => 'multirange_contains_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_contains_multirange' },
+{ oid => '8061',
+  proname => 'elem_contained_by_multirange', prorettype => 'bool',
+  proargtypes => 'anyelement anymultirange', prosrc => 'elem_contained_by_multirange' },
+{ oid => '8062',
+  proname => 'range_contained_by_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_contained_by_multirange' },
+{ oid => '8063',
+  proname => 'multirange_contained_by_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_contained_by_multirange' },
+{ oid => '8088',
+  proname => 'range_adjacent_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_adjacent_multirange' },
+{ oid => '8089',
+  proname => 'multirange_adjacent_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_adjacent_multirange' },
+{ oid => '8090',
+  proname => 'multirange_adjacent_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_adjacent_range' },
+{ oid => '8076',
+  proname => 'range_before_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_before_multirange' },
+{ oid => '8077',
+  proname => 'multirange_before_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_before_range' },
+{ oid => '8078',
+  proname => 'multirange_before_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_before_multirange' },
+{ oid => '8079',
+  proname => 'range_after_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_after_multirange' },
+{ oid => '8080',
+  proname => 'multirange_after_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_after_range' },
+{ oid => '8081',
+  proname => 'multirange_after_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_after_multirange' },
+{ oid => '8100',
+  proname => 'range_overleft_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_overleft_multirange' },
+{ oid => '8101',
+  proname => 'multirange_overleft_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_overleft_range' },
+{ oid => '8102',
+  proname => 'multirange_overleft_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_overleft_multirange' },
+{ oid => '8103',
+  proname => 'range_overright_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_overright_multirange' },
+{ oid => '8104',
+  proname => 'multirange_overright_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_overright_range' },
+{ oid => '8105',
+  proname => 'multirange_overright_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_overright_multirange' },
+{ oid => '8137',
+  proname => 'range_union_range', prorettype => 'anymultirange',
+  proargtypes => 'anyrange anyrange', prosrc => 'range_union_range' },
+{ oid => '8112',
+  proname => 'range_union_multirange', prorettype => 'anymultirange',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_union_multirange' },
+{ oid => '8113',
+  proname => 'multirange_union_range', prorettype => 'anymultirange',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_union_range' },
+{ oid => '8114',
+  proname => 'multirange_union_multirange', prorettype => 'anymultirange',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_union_multirange' },
+{ oid => '8139',
+  proname => 'range_minus_range', prorettype => 'anymultirange',
+  proargtypes => 'anyrange anyrange', prosrc => 'range_minus_range' },
+{ oid => '8118',
+  proname => 'range_minus_multirange', prorettype => 'anymultirange',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_minus_multirange' },
+{ oid => '8119',
+  proname => 'multirange_minus_range', prorettype => 'anymultirange',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_minus_range' },
+{ oid => '8120',
+  proname => 'multirange_minus_multirange', prorettype => 'anymultirange',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_minus_multirange' },
+{ oid => '8141',
+  proname => 'range_intersect_range', prorettype => 'anymultirange',
+  proargtypes => 'anyrange anyrange', prosrc => 'range_intersect_range' },
+{ oid => '8124',
+  proname => 'range_intersect_multirange', prorettype => 'anymultirange',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_intersect_multirange' },
+{ oid => '8125',
+  proname => 'multirange_intersect_range', prorettype => 'anymultirange',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_intersect_range' },
+{ oid => '8126',
+  proname => 'multirange_intersect_multirange', prorettype => 'anymultirange',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_intersect_multirange' },
+{ oid => '8022', descr => 'less-equal-greater',
+  proname => 'multirange_cmp', prorettype => 'int4',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_cmp' },
+{ oid => '8023',
+  proname => 'multirange_lt', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_lt' },
+{ oid => '8024',
+  proname => 'multirange_le', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_le' },
+{ oid => '8025',
+  proname => 'multirange_ge', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_ge' },
+{ oid => '8026',
+  proname => 'multirange_gt', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_gt' },
+{ oid => '8038', descr => 'hash a multirange',
+  proname => 'hash_multirange', prorettype => 'int4', proargtypes => 'anymultirange',
+  prosrc => 'hash_multirange' },
+{ oid => '8039', descr => 'hash a multirange',
+  proname => 'hash_multirange_extended', prorettype => 'int8',
+  proargtypes => 'anymultirange int8', prosrc => 'hash_multirange_extended' },
 
 { oid => '8045', descr => 'int4multirange constructor',
   proname => 'int4multirange', proisstrict => 'f',
@@ -9801,6 +9968,24 @@
   prorettype => 'int8multirange', proargtypes => '_int8range',
   proallargtypes => '{_int8range}', proargmodes => '{v}',
   prosrc => 'multirange_constructor1' },
+{ oid => '8130', descr => 'aggregate transition function',
+  proname => 'range_agg_transfn', proisstrict => 'f', prorettype => 'internal',
+  proargtypes => 'internal anyrange', prosrc => 'range_agg_transfn' },
+{ oid => '8131', descr => 'aggregate final function',
+  proname => 'range_agg_finalfn', proisstrict => 'f', prorettype => 'anymultirange',
+  proargtypes => 'internal anyrange', prosrc => 'range_agg_finalfn' },
+{ oid => '8132', descr => 'combine aggregate input into a multirange',
+  proname => 'range_agg', prokind => 'a', proisstrict => 'f',
+  prorettype => 'anymultirange', proargtypes => 'anyrange',
+  prosrc => 'aggregate_dummy' },
+{ oid => '8135', descr => 'range aggregate by intersecting',
+  proname => 'multirange_intersect_agg_transfn', prorettype => 'anymultirange',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_intersect_agg_transfn'},
+{ oid => '8136', descr => 'range aggregate by intersecting',
+  proname => 'range_intersect_agg', prokind => 'a', proisstrict => 'f',
+  prorettype => 'anymultirange', proargtypes => 'anymultirange',
+  prosrc => 'aggregate_dummy'},
+
 # date, time, timestamp constructors
 { oid => '3846', descr => 'construct date',
   proname => 'make_date', prorettype => 'date', proargtypes => 'int4 int4 int4',
diff --git a/src/include/utils/multirangetypes.h b/src/include/utils/multirangetypes.h
index ed2e19aafa..cc4f82b0ba 100644
--- a/src/include/utils/multirangetypes.h
+++ b/src/include/utils/multirangetypes.h
@@ -59,6 +59,40 @@ extern bool multirange_eq_internal(TypeCacheEntry *typcache, MultirangeType * mr
 								   MultirangeType * mr2);
 extern bool multirange_ne_internal(TypeCacheEntry *typcache, MultirangeType * mr1,
 								   MultirangeType * mr2);
+extern bool multirange_contains_elem_internal(TypeCacheEntry *typcache, MultirangeType * mr,
+											  Datum elem);
+extern bool multirange_contains_range_internal(TypeCacheEntry *typcache, MultirangeType * mr,
+											   RangeType *r);
+extern bool multirange_contains_multirange_internal(TypeCacheEntry *typcache,
+													MultirangeType * mr1,
+													MultirangeType * mr2);
+extern bool range_overlaps_multirange_internal(TypeCacheEntry *typcache, RangeType *r,
+											   MultirangeType * mr);
+extern bool multirange_overlaps_multirange_internal(TypeCacheEntry *typcache,
+													MultirangeType * mr1,
+													MultirangeType * mr2);
+extern bool range_before_multirange_internal(TypeCacheEntry *typcache, RangeType *r,
+											 MultirangeType * mr);
+extern bool range_after_multirange_internal(TypeCacheEntry *typcache, RangeType *r,
+											MultirangeType * mr);
+extern bool multirange_before_multirange_internal(TypeCacheEntry *typcache,
+												  MultirangeType * mr1,
+												  MultirangeType * mr2);
+extern MultirangeType * range_union_multirange_internal(TypeCacheEntry *typcache,
+														RangeType *r,
+														MultirangeType * mr);
+extern MultirangeType * multirange_minus_multirange_internal(Oid mltrngtypoid,
+															 TypeCacheEntry *rangetyp,
+															 int32 range_count1,
+															 RangeType **ranges1,
+															 int32 range_count2,
+															 RangeType **ranges2);
+extern MultirangeType * multirange_intersect_multirange_internal(Oid mltrngtypoid,
+																 TypeCacheEntry *rangetyp,
+																 int32 range_count1,
+																 RangeType **ranges1,
+																 int32 range_count2,
+																 RangeType **ranges2);
 
 /* assorted support functions */
 extern TypeCacheEntry *multirange_get_typcache(FunctionCallInfo fcinfo,
diff --git a/src/include/utils/rangetypes.h b/src/include/utils/rangetypes.h
index 521710d71f..0bee724886 100644
--- a/src/include/utils/rangetypes.h
+++ b/src/include/utils/rangetypes.h
@@ -94,13 +94,21 @@ typedef struct
  * prototypes for functions defined in rangetypes.c
  */
 
-extern bool range_contains_elem_internal(TypeCacheEntry *typcache, const RangeType *r, Datum val);
-
 /* internal versions of the above */
+extern int	range_cmp_internal(TypeCacheEntry *typcache, const RangeType *r1,
+							   const RangeType *r2);
+extern uint32 hash_range_internal(TypeCacheEntry *typcache, const RangeType *r);
+extern uint64 hash_range_extended_internal(TypeCacheEntry *typcache, const RangeType *r,
+										   Datum seed);
+extern Datum range_lower_internal(TypeCacheEntry *typcache, const RangeType *r1,
+								  bool *isnull);
+extern Datum range_upper_internal(TypeCacheEntry *typcache, const RangeType *r1,
+								  bool *isnull);
 extern bool range_eq_internal(TypeCacheEntry *typcache, const RangeType *r1,
 							  const RangeType *r2);
 extern bool range_ne_internal(TypeCacheEntry *typcache, const RangeType *r1,
 							  const RangeType *r2);
+extern bool range_contains_elem_internal(TypeCacheEntry *typcache, const RangeType *r, Datum val);
 extern bool range_contains_internal(TypeCacheEntry *typcache, const RangeType *r1,
 									const RangeType *r2);
 extern bool range_contained_by_internal(TypeCacheEntry *typcache, const RangeType *r1,
@@ -117,6 +125,12 @@ extern bool range_overleft_internal(TypeCacheEntry *typcache, const RangeType *r
 									const RangeType *r2);
 extern bool range_overright_internal(TypeCacheEntry *typcache, const RangeType *r1,
 									 const RangeType *r2);
+extern RangeType *range_union_internal(TypeCacheEntry *typcache, RangeType *r1,
+									   RangeType *r2, bool strict);
+extern RangeType *range_minus_internal(TypeCacheEntry *typcache, RangeType *r1,
+									   RangeType *r2);
+extern RangeType *range_intersect_internal(TypeCacheEntry *typcache, const RangeType *r1,
+										   const RangeType *r2);
 
 /* assorted support functions */
 extern TypeCacheEntry *range_get_typcache(FunctionCallInfo fcinfo,
@@ -127,15 +141,20 @@ extern void range_deserialize(TypeCacheEntry *typcache, const RangeType *range,
 							  RangeBound *lower, RangeBound *upper,
 							  bool *empty);
 extern char range_get_flags(const RangeType *range);
+extern bool range_has_flag(const RangeType *range, char flag);
 extern void range_set_contain_empty(RangeType *range);
 extern RangeType *make_range(TypeCacheEntry *typcache, RangeBound *lower,
 							 RangeBound *upper, bool empty);
 extern int range_cmp_bounds(TypeCacheEntry *typcache, const RangeBound *b1,
-							const RangeBound *b2);
+							 const RangeBound *b2);
 extern int range_cmp_bound_values(TypeCacheEntry *typcache, const RangeBound *b1,
-								  const RangeBound *b2);
-extern bool bounds_adjacent(TypeCacheEntry *typcache, RangeBound bound1,
-							RangeBound bound2);
+								   const RangeBound *b2);
+extern int range_compare(const void *key1, const void *key2, void *arg);
+extern bool bounds_adjacent(TypeCacheEntry *typcache, const RangeBound bound1,
+							const RangeBound bound2);
 extern RangeType *make_empty_range(TypeCacheEntry *typcache);
+extern bool range_split_internal(TypeCacheEntry *typcache, const RangeType *r1,
+								 const RangeType *r2, RangeType **output1,
+								 RangeType **output2);
 
 #endif							/* RANGETYPES_H */
diff --git a/src/test/regress/expected/dependency.out b/src/test/regress/expected/dependency.out
index b4a11b8aa9..778699a961 100644
--- a/src/test/regress/expected/dependency.out
+++ b/src/test/regress/expected/dependency.out
@@ -141,6 +141,7 @@ owner of table deptest
 owner of function deptest_func()
 owner of type deptest_enum
 owner of type deptest_range
+owner of type deptest_multirange
 owner of table deptest2
 owner of sequence ss1
 owner of type deptest_t
diff --git a/src/test/regress/expected/multirangetypes.out b/src/test/regress/expected/multirangetypes.out
index 5749537635..2276d6d002 100644
--- a/src/test/regress/expected/multirangetypes.out
+++ b/src/test/regress/expected/multirangetypes.out
@@ -290,3 +290,2400 @@ select textmultirange(textrange('a', 'c'), textrange('b', 'd'));
  {[a,d)}
 (1 row)
 
+--
+-- create some test data and test the operators
+--
+CREATE TABLE nummultirange_test (nmr NUMMULTIRANGE);
+CREATE INDEX nummultirange_test_btree ON nummultirange_test(nmr);
+INSERT INTO nummultirange_test VALUES('{}');
+INSERT INTO nummultirange_test VALUES('{[,)}');
+INSERT INTO nummultirange_test VALUES('{[3,]}');
+INSERT INTO nummultirange_test VALUES('{[,), [3,]}');
+INSERT INTO nummultirange_test VALUES('{[, 5)}');
+INSERT INTO nummultirange_test VALUES(nummultirange());
+INSERT INTO nummultirange_test VALUES(nummultirange(variadic '{}'::numrange[]));
+INSERT INTO nummultirange_test VALUES(nummultirange(numrange(1.1, 2.2)));
+INSERT INTO nummultirange_test VALUES('{empty}');
+INSERT INTO nummultirange_test VALUES(nummultirange(numrange(1.7, 1.7, '[]'), numrange(1.7, 1.9)));
+INSERT INTO nummultirange_test VALUES(nummultirange(numrange(1.7, 1.7, '[]'), numrange(1.9, 2.1)));
+SELECT nmr, isempty(nmr), lower(nmr), upper(nmr) FROM nummultirange_test ORDER BY nmr;
+          nmr          | isempty | lower | upper 
+-----------------------+---------+-------+-------
+ {}                    | t       |       |      
+ {}                    | t       |       |      
+ {}                    | t       |       |      
+ {}                    | t       |       |      
+ {(,5)}                | f       |       |     5
+ {(,)}                 | f       |       |      
+ {(,)}                 | f       |       |      
+ {[1.1,2.2)}           | f       |   1.1 |   2.2
+ {[1.7,1.7],[1.9,2.1)} | f       |   1.7 |   2.1
+ {[1.7,1.9)}           | f       |   1.7 |   1.9
+ {[3,)}                | f       |     3 |      
+(11 rows)
+
+SELECT nmr, lower_inc(nmr), lower_inf(nmr), upper_inc(nmr), upper_inf(nmr) FROM nummultirange_test ORDER BY nmr;
+          nmr          | lower_inc | lower_inf | upper_inc | upper_inf 
+-----------------------+-----------+-----------+-----------+-----------
+ {}                    | f         | f         | f         | f
+ {}                    | f         | f         | f         | f
+ {}                    | f         | f         | f         | f
+ {}                    | f         | f         | f         | f
+ {(,5)}                | f         | t         | f         | f
+ {(,)}                 | f         | t         | f         | t
+ {(,)}                 | f         | t         | f         | t
+ {[1.1,2.2)}           | t         | f         | f         | f
+ {[1.7,1.7],[1.9,2.1)} | t         | f         | f         | f
+ {[1.7,1.9)}           | t         | f         | f         | f
+ {[3,)}                | t         | f         | f         | t
+(11 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr = '{}';
+ nmr 
+-----
+ {}
+ {}
+ {}
+ {}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr = '{(,5)}';
+  nmr   
+--------
+ {(,5)}
+(1 row)
+
+SELECT * FROM nummultirange_test WHERE nmr = '{[3,)}';
+  nmr   
+--------
+ {[3,)}
+(1 row)
+
+SELECT * FROM nummultirange_test WHERE nmr = '{[1.7,1.7]}';
+ nmr 
+-----
+(0 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr = '{[1.7,1.7],[1.9,2.1)}';
+          nmr          
+-----------------------
+ {[1.7,1.7],[1.9,2.1)}
+(1 row)
+
+SELECT * FROM nummultirange_test WHERE nmr < '{}';
+ nmr 
+-----
+(0 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr < '{[-1000.0, -1000.0]}';
+  nmr   
+--------
+ {}
+ {(,)}
+ {(,)}
+ {(,5)}
+ {}
+ {}
+ {}
+(7 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr < '{[0.0, 1.0]}';
+  nmr   
+--------
+ {}
+ {(,)}
+ {(,)}
+ {(,5)}
+ {}
+ {}
+ {}
+(7 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr < '{[1000.0, 1001.0]}';
+          nmr          
+-----------------------
+ {}
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+ {}
+ {}
+ {[1.1,2.2)}
+ {}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(11 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr <= '{}';
+ nmr 
+-----
+ {}
+ {}
+ {}
+ {}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr <= '{[3,)}';
+          nmr          
+-----------------------
+ {}
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+ {}
+ {}
+ {[1.1,2.2)}
+ {}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(11 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr >= '{}';
+          nmr          
+-----------------------
+ {}
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+ {}
+ {}
+ {[1.1,2.2)}
+ {}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(11 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr >= '{[3,)}';
+  nmr   
+--------
+ {[3,)}
+(1 row)
+
+SELECT * FROM nummultirange_test WHERE nmr > '{}';
+          nmr          
+-----------------------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+ {[1.1,2.2)}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(7 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr > '{[-1000.0, -1000.0]}';
+          nmr          
+-----------------------
+ {[3,)}
+ {[1.1,2.2)}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr > '{[0.0, 1.0]}';
+          nmr          
+-----------------------
+ {[3,)}
+ {[1.1,2.2)}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr > '{[1000.0, 1001.0]}';
+ nmr 
+-----
+(0 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr <> '{}';
+          nmr          
+-----------------------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+ {[1.1,2.2)}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(7 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr <> '{(,5)}';
+          nmr          
+-----------------------
+ {}
+ {(,)}
+ {[3,)}
+ {(,)}
+ {}
+ {}
+ {[1.1,2.2)}
+ {}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(10 rows)
+
+select nummultirange(numrange(2.0, 1.0));
+ERROR:  range lower bound must be less than or equal to range upper bound
+select nummultirange(numrange(5.0, 6.0), numrange(1.0, 2.0));
+     nummultirange     
+-----------------------
+ {[1.0,2.0),[5.0,6.0)}
+(1 row)
+
+-- overlaps
+SELECT * FROM nummultirange_test WHERE range_overlaps_multirange(numrange(4.0, 4.2), nmr);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE numrange(4.0, 4.2) && nmr;
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE multirange_overlaps_range(nmr, numrange(4.0, 4.2));
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr && numrange(4.0, 4.2);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE multirange_overlaps_multirange(nmr, nummultirange(numrange(4.0, 4.2), numrange(6.0, 7.0)));
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr && nummultirange(numrange(4.0, 4.2), numrange(6.0, 7.0));
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr && nummultirange(numrange(6.0, 7.0));
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+(3 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr && nummultirange(numrange(6.0, 7.0), numrange(8.0, 9.0));
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+(3 rows)
+
+-- mr contains x
+SELECT * FROM nummultirange_test WHERE multirange_contains_elem(nmr, 4.0);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr @> 4.0;
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE multirange_contains_range(nmr, numrange(4.0, 4.2));
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr @> numrange(4.0, 4.2);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE multirange_contains_multirange(nmr, '{[4.0,4.2), [6.0, 8.0)}');
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+(3 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr @> '{[4.0,4.2), [6.0, 8.0)}'::nummultirange;
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+(3 rows)
+
+-- x is contained by mr
+SELECT * FROM nummultirange_test WHERE elem_contained_by_multirange(4.0, nmr);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE 4.0 <@ nmr;
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE range_contained_by_multirange(numrange(4.0, 4.2), nmr);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE numrange(4.0, 4.2) <@ nmr;
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE multirange_contained_by_multirange('{[4.0,4.2), [6.0, 8.0)}', nmr);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+(3 rows)
+
+SELECT * FROM nummultirange_test WHERE '{[4.0,4.2), [6.0, 8.0)}'::nummultirange <@ nmr;
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+(3 rows)
+
+-- overlaps
+SELECT 'empty'::numrange && nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT 'empty'::numrange && nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() && 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) && 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() && nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() && nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) && nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) && nummultirange(numrange(1,2), numrange(7,8));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(7,8)) && nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) && nummultirange(numrange(1,2), numrange(3.5,8));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(3.5,8)) && numrange(3,4);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(3.5,8)) && nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+-- contains
+SELECT nummultirange() @> nummultirange();
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange() @> 'empty'::numrange;
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(null,null)) @> numrange(1,2);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(null,null)) @> numrange(null,2);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(null,null)) @> numrange(2,null);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(null,5)) @> numrange(null,3);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(null,5)) @> numrange(null,8);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(5,null)) @> numrange(8,null);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(5,null)) @> numrange(3,null);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,5)) @> numrange(8,9);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,5)) @> numrange(3,9);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,5)) @> numrange(1,4);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,5)) @> numrange(1,5);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(-4,-2), numrange(1,5)) @> numrange(1,5);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,5), numrange(8,9)) @> numrange(1,5);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,5), numrange(8,9)) @> numrange(6,7);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,5), numrange(6,9)) @> numrange(6,7);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[1,5)}'::nummultirange @> '{[1,5)}';
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[-4,-2), [1,5)}'::nummultirange @> '{[1,5)}';
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[1,5), [8,9)}'::nummultirange @> '{[1,5)}';
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[1,5), [8,9)}'::nummultirange @> '{[6,7)}';
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT '{[1,5), [6,9)}'::nummultirange @> '{[6,7)}';
+ ?column? 
+----------
+ t
+(1 row)
+
+-- is contained by
+SELECT nummultirange() <@ nummultirange();
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT 'empty'::numrange <@ nummultirange();
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,2) <@ nummultirange(numrange(null,null));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(null,2) <@ nummultirange(numrange(null,null));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(2,null) <@ nummultirange(numrange(null,null));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(null,3) <@ nummultirange(numrange(null,5));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(null,8) <@ nummultirange(numrange(null,5));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(8,null) <@ nummultirange(numrange(5,null));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(3,null) <@ nummultirange(numrange(5,null));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(8,9) <@ nummultirange(numrange(1,5));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(3,9) <@ nummultirange(numrange(1,5));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(1,4) <@ nummultirange(numrange(1,5));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,5) <@ nummultirange(numrange(1,5));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,5) <@ nummultirange(numrange(-4,-2), numrange(1,5));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,5) <@ nummultirange(numrange(1,5), numrange(8,9));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(6,7) <@ nummultirange(numrange(1,5), numrange(8,9));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(6,7) <@ nummultirange(numrange(1,5), numrange(6,9));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[1,5)}' <@ '{[1,5)}'::nummultirange;
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[1,5)}' <@ '{[-4,-2), [1,5)}'::nummultirange;
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[1,5)}' <@ '{[1,5), [8,9)}'::nummultirange;
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[6,7)}' <@ '{[1,5), [8,9)}'::nummultirange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT '{[6,7)}' <@ '{[1,5), [6,9)}'::nummultirange;
+ ?column? 
+----------
+ t
+(1 row)
+
+-- overleft
+SELECT 'empty'::numrange &< nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT 'empty'::numrange &< nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() &< 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) &< 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() &< nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) &< nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() &< nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(6,7) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(1,2) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,4) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,6) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(3.5,6) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(6,7)) &< numrange(3,4);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) &< numrange(3,4);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,4)) &< numrange(3,4);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,6)) &< numrange(3,4);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3.5,6)) &< numrange(3,4);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(6,7)) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,4)) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,6)) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3.5,6)) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+-- overright
+SELECT nummultirange() &> 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) &> 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT 'empty'::numrange &> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT 'empty'::numrange &> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() &> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() &> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) &> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> numrange(6,7);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> numrange(1,2);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> numrange(1,4);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> numrange(1,6);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> numrange(3.5,6);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(3,4) &> nummultirange(numrange(6,7));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(3,4) &> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(3,4) &> nummultirange(numrange(1,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(3,4) &> nummultirange(numrange(1,6));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(3,4) &> nummultirange(numrange(3.5,6));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(6,7));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(1,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(1,6));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(3.5,6));
+ ?column? 
+----------
+ f
+(1 row)
+
+-- meets
+SELECT 'empty'::numrange -|- nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT 'empty'::numrange -|- nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() -|- 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() -|- nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() -|- nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(1,2) -|- nummultirange(numrange(2,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,2) -|- nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- numrange(2,4);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- numrange(3,4);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- nummultirange(numrange(2,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(5,6)) -|- nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(5,6)) -|- nummultirange(numrange(6,7));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(5,6)) -|- nummultirange(numrange(8,9));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- nummultirange(numrange(2,4), numrange(6,7));
+ ?column? 
+----------
+ t
+(1 row)
+
+-- strictly left
+select 'empty'::numrange << nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(1,2) << nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(1,2) << nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+select numrange(1,2) << nummultirange(numrange(0,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(1,2) << nummultirange(numrange(0,4), numrange(7,8));
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange() << 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange() << numrange(1,2);
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(3,4)) << numrange(3,6);
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(0,2)) << numrange(3,6);
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange(numrange(0,2), numrange(7,8)) << numrange(3,6);
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(-4,-2), numrange(0,2)) << numrange(3,6);
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange() << nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange() << nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(1,2)) << nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(1,2)) << nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(1,2)) << nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange(numrange(1,2)) << nummultirange(numrange(3,4), numrange(7,8));
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange(numrange(1,2), numrange(4,5)) << nummultirange(numrange(3,4), numrange(7,8));
+ ?column? 
+----------
+ f
+(1 row)
+
+-- strictly right
+select nummultirange() >> 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange() >> numrange(1,2);
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(3,4)) >> numrange(1,2);
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange(numrange(0,4)) >> numrange(1,2);
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(0,4), numrange(7,8)) >> numrange(1,2);
+ ?column? 
+----------
+ f
+(1 row)
+
+select 'empty'::numrange >> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(1,2) >> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(3,6) >> nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(3,6) >> nummultirange(numrange(0,2));
+ ?column? 
+----------
+ t
+(1 row)
+
+select numrange(3,6) >> nummultirange(numrange(0,2), numrange(7,8));
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(3,6) >> nummultirange(numrange(-4,-2), numrange(0,2));
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange() >> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(1,2)) >> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange() >> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(1,2)) >> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(3,4)) >> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange(numrange(3,4), numrange(7,8)) >> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange(numrange(3,4), numrange(7,8)) >> nummultirange(numrange(1,2), numrange(4,5));
+ ?column? 
+----------
+ f
+(1 row)
+
+-- union
+SELECT 'empty'::numrange @+ 'empty'::numrange;
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT 'empty'::numrange @+ nummultirange();
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange() @+ 'empty'::numrange;
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange() @+ nummultirange();
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT 'empty'::numrange @+ numrange(1,2);
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT 'empty'::numrange @+ nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT numrange(1,2) @+ nummultirange();
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) @+ 'empty'::numrange;
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT numrange(1,2) @+ 'empty'::numrange;
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange() @+ numrange(1,2);
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange() @+ nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) @+ nummultirange();
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT numrange(1,2) @+ numrange(1,2);
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT numrange(1,2) @+ numrange(2,4);
+ ?column? 
+----------
+ {[1,4)}
+(1 row)
+
+SELECT numrange(1,2) @+ numrange(3,4);
+   ?column?    
+---------------
+ {[1,2),[3,4)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) @+ nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) @+ nummultirange(numrange(2,4));
+ ?column? 
+----------
+ {[1,4)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) @+ nummultirange(numrange(3,4));
+   ?column?    
+---------------
+ {[1,2),[3,4)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) @+ nummultirange(numrange(2,4));
+ ?column? 
+----------
+ {[1,5)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) @+ nummultirange(numrange(3,4));
+   ?column?    
+---------------
+ {[1,2),[3,5)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) @+ nummultirange(numrange(0,9));
+ ?column? 
+----------
+ {[0,9)}
+(1 row)
+
+-- merge
+SELECT range_merge(nummultirange());
+ range_merge 
+-------------
+ empty
+(1 row)
+
+SELECT range_merge(nummultirange(numrange(1,2)));
+ range_merge 
+-------------
+ [1,2)
+(1 row)
+
+SELECT range_merge(nummultirange(numrange(1,2), numrange(7,8)));
+ range_merge 
+-------------
+ [1,8)
+(1 row)
+
+-- minus
+SELECT 'empty'::numrange @- 'empty'::numrange;
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT 'empty'::numrange @- nummultirange();
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange() @- 'empty'::numrange;
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange() @- nummultirange();
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT 'empty'::numrange @- numrange(1,2);
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT 'empty'::numrange @- nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT numrange(1,2) @- nummultirange();
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) @- 'empty'::numrange;
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT 'empty'::numrange @- numrange(1,2);
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT numrange(1,2) @- 'empty'::numrange;
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange() @- numrange(1,2);
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange() @- nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) @- nummultirange();
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT numrange(1,3) @- numrange(1,3);
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT numrange(1,3) @- numrange(1,2);
+ ?column? 
+----------
+ {[2,3)}
+(1 row)
+
+SELECT numrange(1,3) @- numrange(2,4);
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT numrange(1,3) @- numrange(3,4);
+ ?column? 
+----------
+ {[1,3)}
+(1 row)
+
+SELECT numrange(1,3) @- nummultirange(numrange(1,3));
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT numrange(1,3) @- nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {[2,3)}
+(1 row)
+
+SELECT numrange(1,3) @- nummultirange(numrange(2,4));
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT numrange(1,3) @- nummultirange(numrange(3,4));
+ ?column? 
+----------
+ {[1,3)}
+(1 row)
+
+SELECT numrange(1,10) @- nummultirange(numrange(1,2), numrange(4,5));
+    ?column?    
+----------------
+ {[2,4),[5,10)}
+(1 row)
+
+SELECT nummultirange(numrange(1,10)) @- numrange(0,2);
+ ?column? 
+----------
+ {[2,10)}
+(1 row)
+
+SELECT nummultirange(numrange(1,10)) @- numrange(1,2);
+ ?column? 
+----------
+ {[2,10)}
+(1 row)
+
+SELECT nummultirange(numrange(1,10)) @- numrange(3,4);
+    ?column?    
+----------------
+ {[1,3),[4,10)}
+(1 row)
+
+SELECT nummultirange(numrange(1,10)) @- numrange(13,18);
+ ?column? 
+----------
+ {[1,10)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(3,4)) @- nummultirange();
+   ?column?    
+---------------
+ {[1,2),[3,4)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) @- nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) @- nummultirange(numrange(2,4));
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) @- nummultirange(numrange(3,4));
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,4)) @- nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {[2,4)}
+(1 row)
+
+SELECT nummultirange(numrange(1,4)) @- nummultirange(numrange(2,3));
+   ?column?    
+---------------
+ {[1,2),[3,4)}
+(1 row)
+
+SELECT nummultirange(numrange(1,4)) @- nummultirange(numrange(0,8));
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange(numrange(1,4)) @- nummultirange(numrange(0,2));
+ ?column? 
+----------
+ {[2,4)}
+(1 row)
+
+SELECT nummultirange(numrange(1,8)) @- nummultirange(numrange(0,2), numrange(3,4));
+   ?column?    
+---------------
+ {[2,3),[4,8)}
+(1 row)
+
+SELECT nummultirange(numrange(1,8)) @- nummultirange(numrange(2,3), numrange(5,null));
+   ?column?    
+---------------
+ {[1,2),[3,5)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) @- nummultirange(numrange(-2,0));
+   ?column?    
+---------------
+ {[1,2),[4,5)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) @- nummultirange(numrange(2,4));
+   ?column?    
+---------------
+ {[1,2),[4,5)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) @- nummultirange(numrange(3,5));
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) @- nummultirange(numrange(0,9));
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange(numrange(1,3), numrange(4,5)) @- nummultirange(numrange(2,9));
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) @- nummultirange(numrange(8,9));
+   ?column?    
+---------------
+ {[1,2),[4,5)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) @- nummultirange(numrange(-2,0), numrange(8,9));
+   ?column?    
+---------------
+ {[1,2),[4,5)}
+(1 row)
+
+-- intersection
+SELECT 'empty'::numrange @* 'empty'::numrange;
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT 'empty'::numrange @* nummultirange();
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange() @* 'empty'::numrange;
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange() @* nummultirange();
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT 'empty'::numrange @* numrange(1,2);
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT 'empty'::numrange @* nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT numrange(1,2) @* 'empty'::numrange;
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT numrange(1,2) @* nummultirange();
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) @* 'empty'::numrange;
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange() @* numrange(1,2);
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange() @* nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) @* nummultirange();
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT numrange(1,3) @* numrange(1,3);
+ ?column? 
+----------
+ {[1,3)}
+(1 row)
+
+SELECT numrange(1,3) @* numrange(1,2);
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT numrange(1,3) @* numrange(1,5);
+ ?column? 
+----------
+ {[1,3)}
+(1 row)
+
+SELECT numrange(1,3) @* numrange(2,5);
+ ?column? 
+----------
+ {[2,3)}
+(1 row)
+
+SELECT numrange(1,5) @* numrange(2,3);
+ ?column? 
+----------
+ {[2,3)}
+(1 row)
+
+SELECT '{[1,3)}'::nummultirange @* numrange(1,3);
+ ?column? 
+----------
+ {[1,3)}
+(1 row)
+
+SELECT '{[1,3)}'::nummultirange @* numrange(1,2);
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT '{[1,3)}'::nummultirange @* numrange(1,5);
+ ?column? 
+----------
+ {[1,3)}
+(1 row)
+
+SELECT '{[1,3)}'::nummultirange @* numrange(2,5);
+ ?column? 
+----------
+ {[2,3)}
+(1 row)
+
+SELECT '{[1,5)}'::nummultirange @* numrange(2,3);
+ ?column? 
+----------
+ {[2,3)}
+(1 row)
+
+SELECT numrange(1,3) @* '{[1,3)}'::nummultirange;
+ ?column? 
+----------
+ {[1,3)}
+(1 row)
+
+SELECT numrange(1,2) @* '{[1,3)}'::nummultirange;
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT numrange(1,5) @* '{[1,3)}'::nummultirange;
+ ?column? 
+----------
+ {[1,3)}
+(1 row)
+
+SELECT numrange(2,5) @* '{[1,3)}'::nummultirange;
+ ?column? 
+----------
+ {[2,3)}
+(1 row)
+
+SELECT numrange(2,3) @* '{[1,5)}'::nummultirange;
+ ?column? 
+----------
+ {[2,3)}
+(1 row)
+
+SELECT '{[1,3)}'::nummultirange @* '{[1,5)}'::nummultirange;
+ ?column? 
+----------
+ {[1,3)}
+(1 row)
+
+SELECT '{[1,3)}'::nummultirange @* '{[0,5)}'::nummultirange;
+ ?column? 
+----------
+ {[1,3)}
+(1 row)
+
+SELECT '{[1,3)}'::nummultirange @* '{[0,2)}'::nummultirange;
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT '{[1,3)}'::nummultirange @* '{[2,5)}'::nummultirange;
+ ?column? 
+----------
+ {[2,3)}
+(1 row)
+
+SELECT '{[1,4)}'::nummultirange @* '{[2,3)}'::nummultirange;
+ ?column? 
+----------
+ {[2,3)}
+(1 row)
+
+SELECT '{[1,4)}'::nummultirange @* '{[0,2), [3,5)}'::nummultirange;
+   ?column?    
+---------------
+ {[1,2),[3,4)}
+(1 row)
+
+SELECT '{[1,4), [7,10)}'::nummultirange @* '{[0,8), [9,12)}'::nummultirange;
+       ?column?       
+----------------------
+ {[1,4),[7,8),[9,10)}
+(1 row)
+
+SELECT '{[1,4), [7,10)}'::nummultirange @* '{[9,12)}'::nummultirange;
+ ?column? 
+----------
+ {[9,10)}
+(1 row)
+
+SELECT '{[1,4), [7,10)}'::nummultirange @* '{[-5,-4), [5,6), [9,12)}'::nummultirange;
+ ?column? 
+----------
+ {[9,10)}
+(1 row)
+
+SELECT '{[1,4), [7,10)}'::nummultirange @* '{[0,2), [3,8), [9,12)}'::nummultirange;
+          ?column?          
+----------------------------
+ {[1,2),[3,4),[7,8),[9,10)}
+(1 row)
+
+SELECT '{[1,4), [7,10)}'::nummultirange @* '{[0,2), [3,8), [9,12)}'::nummultirange;
+          ?column?          
+----------------------------
+ {[1,2),[3,4),[7,8),[9,10)}
+(1 row)
+
+--
+-- range_agg function
+--
+create table reservations ( room_id integer not null, booked_during daterange );
+insert into reservations values
+-- 1: has a meets and a gap
+(1, daterange('2018-07-01', '2018-07-07')),
+(1, daterange('2018-07-07', '2018-07-14')),
+(1, daterange('2018-07-20', '2018-07-22')),
+-- 2: just a single row
+(2, daterange('2018-07-01', '2018-07-03')),
+-- 3: one null range
+(3, NULL),
+-- 4: two null ranges
+(4, NULL),
+(4, NULL),
+-- 5: a null range and a non-null range
+(5, NULL),
+(5, daterange('2018-07-01', '2018-07-03')),
+-- 6: has overlap
+(6, daterange('2018-07-01', '2018-07-07')),
+(6, daterange('2018-07-05', '2018-07-10')),
+-- 7: two ranges that meet: no gap or overlap
+(7, daterange('2018-07-01', '2018-07-07')),
+(7, daterange('2018-07-07', '2018-07-14')),
+-- 8: an empty range
+(8, 'empty'::daterange)
+;
+SELECT   room_id, range_agg(booked_during)
+FROM     reservations
+GROUP BY room_id
+ORDER BY room_id;
+ room_id |                     range_agg                     
+---------+---------------------------------------------------
+       1 | {[07-01-2018,07-14-2018),[07-20-2018,07-22-2018)}
+       2 | {[07-01-2018,07-03-2018)}
+       3 | 
+       4 | 
+       5 | {[07-01-2018,07-03-2018)}
+       6 | {[07-01-2018,07-10-2018)}
+       7 | {[07-01-2018,07-14-2018)}
+       8 | {}
+(8 rows)
+
+-- range_agg on a custom range type too
+SELECT  range_agg(r)
+FROM    (VALUES
+          ('[a,c]'::textrange),
+          ('[b,b]'::textrange),
+          ('[c,f]'::textrange),
+          ('[g,h)'::textrange),
+          ('[h,j)'::textrange)
+        ) t(r);
+   range_agg   
+---------------
+ {[a,f],[g,j)}
+(1 row)
+
+select range_intersect_agg(nmr) from nummultirange_test;
+ range_intersect_agg 
+---------------------
+ {}
+(1 row)
+
+select range_intersect_agg(nmr) from nummultirange_test where false;
+ range_intersect_agg 
+---------------------
+ 
+(1 row)
+
+-- test with just one input:
+select range_intersect_agg(nmr) from (values ('{[1,2]}'::nummultirange)) t(nmr);
+ range_intersect_agg 
+---------------------
+ {[1,2]}
+(1 row)
+
+select range_intersect_agg(nmr) from nummultirange_test where nmr @> 4.0;
+ range_intersect_agg 
+---------------------
+ {[3,5)}
+(1 row)
+
+create table nummultirange_test2(nmr nummultirange);
+create index nummultirange_test2_hash_idx on nummultirange_test2 using hash (nmr);
+INSERT INTO nummultirange_test2 VALUES('{[, 5)}');
+INSERT INTO nummultirange_test2 VALUES(nummultirange(numrange(1.1, 2.2)));
+INSERT INTO nummultirange_test2 VALUES(nummultirange(numrange(1.1, 2.2)));
+INSERT INTO nummultirange_test2 VALUES(nummultirange(numrange(1.1, 2.2,'()')));
+INSERT INTO nummultirange_test2 VALUES('{}');
+select * from nummultirange_test2 where nmr = '{}';
+ nmr 
+-----
+ {}
+(1 row)
+
+select * from nummultirange_test2 where nmr = nummultirange(numrange(1.1, 2.2));
+     nmr     
+-------------
+ {[1.1,2.2)}
+ {[1.1,2.2)}
+(2 rows)
+
+select * from nummultirange_test2 where nmr = nummultirange(numrange(1.1, 2.3));
+ nmr 
+-----
+(0 rows)
+
+set enable_nestloop=t;
+set enable_hashjoin=f;
+set enable_mergejoin=f;
+select * from nummultirange_test natural join nummultirange_test2 order by nmr;
+     nmr     
+-------------
+ {}
+ {}
+ {}
+ {}
+ {(,5)}
+ {[1.1,2.2)}
+ {[1.1,2.2)}
+(7 rows)
+
+set enable_nestloop=f;
+set enable_hashjoin=t;
+set enable_mergejoin=f;
+select * from nummultirange_test natural join nummultirange_test2 order by nmr;
+     nmr     
+-------------
+ {}
+ {}
+ {}
+ {}
+ {(,5)}
+ {[1.1,2.2)}
+ {[1.1,2.2)}
+(7 rows)
+
+set enable_nestloop=f;
+set enable_hashjoin=f;
+set enable_mergejoin=t;
+select * from nummultirange_test natural join nummultirange_test2 order by nmr;
+     nmr     
+-------------
+ {}
+ {}
+ {}
+ {}
+ {(,5)}
+ {[1.1,2.2)}
+ {[1.1,2.2)}
+(7 rows)
+
+set enable_nestloop to default;
+set enable_hashjoin to default;
+set enable_mergejoin to default;
+DROP TABLE nummultirange_test2;
+--
+-- Test user-defined multirange of floats
+--
+select '{[123.001, 5.e9)}'::float8multirange @> 888.882::float8;
+ ?column? 
+----------
+ t
+(1 row)
+
+create table float8multirange_test(f8mr float8multirange, i int);
+insert into float8multirange_test values(float8multirange(float8range(-100.00007, '1.111113e9')), 42);
+select * from float8multirange_test;
+           f8mr            | i  
+---------------------------+----
+ {[-100.00007,1111113000)} | 42
+(1 row)
+
+drop table float8multirange_test;
+--
+-- Test multirange types over domains
+--
+create domain mydomain as int4;
+create type mydomainrange as range(subtype=mydomain);
+select '{[4,50)}'::mydomainmultirange @> 7::mydomain;
+ ?column? 
+----------
+ t
+(1 row)
+
+drop domain mydomain cascade;
+NOTICE:  drop cascades to type mydomainrange
+--
+-- Test domains over multirange types
+--
+create domain restrictedmultirange as int4multirange check (upper(value) < 10);
+select '{[4,5)}'::restrictedmultirange @> 7;
+ ?column? 
+----------
+ f
+(1 row)
+
+select '{[4,50)}'::restrictedmultirange @> 7; -- should fail
+ERROR:  value for domain restrictedmultirange violates check constraint "restrictedmultirange_check"
+drop domain restrictedmultirange;
+--
+-- Test multiple multirange types over the same subtype
+--
+create type textrange1 as range(subtype=text, collation="C");
+create type textrange2 as range(subtype=text, collation="C");
+select textmultirange1(textrange2('a','Z'));  -- should fail
+ERROR:  function textmultirange1(textrange2) does not exist
+LINE 1: select textmultirange1(textrange2('a','Z'));
+               ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+select textmultirange1(textrange1('a','Z')) @> 'b'::text;
+ERROR:  range lower bound must be less than or equal to range upper bound
+select textmultirange2(textrange2('a','z')) @> 'b'::text;
+ ?column? 
+----------
+ t
+(1 row)
+
+drop type textrange1;
+drop type textrange2;
+--
+-- Test polymorphic type system
+--
+create function anyarray_anymultirange_func(a anyarray, r anymultirange)
+  returns anyelement as 'select $1[1] + lower($2);' language sql;
+select anyarray_anymultirange_func(ARRAY[1,2], int4multirange(int4range(10,20)));
+ anyarray_anymultirange_func 
+-----------------------------
+                          11
+(1 row)
+
+-- should fail
+select anyarray_anymultirange_func(ARRAY[1,2], nummultirange(numrange(10,20)));
+ERROR:  function anyarray_anymultirange_func(integer[], nummultirange) does not exist
+LINE 1: select anyarray_anymultirange_func(ARRAY[1,2], nummultirange...
+               ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+drop function anyarray_anymultirange_func(anyarray, anymultirange);
+-- should fail
+create function bogus_func(anyelement)
+  returns anymultirange as 'select int4multirange(int4range(1,10))' language sql;
+ERROR:  cannot determine result data type
+DETAIL:  A function returning "anyrange" or "anymultirange" must have at least one "anyrange" or "anymultirange" argument.
+-- should fail
+create function bogus_func(int)
+  returns anymultirange as 'select int4multirange(int4range(1,10))' language sql;
+ERROR:  cannot determine result data type
+DETAIL:  A function returning a polymorphic type must have at least one polymorphic argument.
+create function range_add_bounds(anymultirange)
+  returns anyelement as 'select lower($1) + upper($1)' language sql;
+select range_add_bounds(int4multirange(int4range(1, 17)));
+ range_add_bounds 
+------------------
+               18
+(1 row)
+
+select range_add_bounds(nummultirange(numrange(1.0001, 123.123)));
+ range_add_bounds 
+------------------
+         124.1231
+(1 row)
+
+create function multirangetypes_sql(q anymultirange, b anyarray, out c anyelement)
+  as $$ select upper($1) + $2[1] $$
+  language sql;
+select multirangetypes_sql(int4multirange(int4range(1,10)), ARRAY[2,20]);
+ multirangetypes_sql 
+---------------------
+                  12
+(1 row)
+
+select multirangetypes_sql(nummultirange(numrange(1,10)), ARRAY[2,20]);  -- match failure
+ERROR:  function multirangetypes_sql(nummultirange, integer[]) does not exist
+LINE 1: select multirangetypes_sql(nummultirange(numrange(1,10)), AR...
+               ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+--
+-- Arrays of multiranges
+--
+select ARRAY[nummultirange(numrange(1.1, 1.2)), nummultirange(numrange(12.3, 155.5))];
+              array               
+----------------------------------
+ {"{[1.1,1.2)}","{[12.3,155.5)}"}
+(1 row)
+
+create table i8mr_array (f1 int, f2 int8multirange[]);
+insert into i8mr_array values (42, array[int8multirange(int8range(1,10)), int8multirange(int8range(2,20))]);
+select * from i8mr_array;
+ f1 |           f2            
+----+-------------------------
+ 42 | {"{[1,10)}","{[2,20)}"}
+(1 row)
+
+drop table i8mr_array;
+--
+-- Multiranges of arrays
+--
+select arraymultirange(arrayrange(ARRAY[1,2], ARRAY[2,1]));
+   arraymultirange   
+---------------------
+ {["{1,2}","{2,1}")}
+(1 row)
+
+select arraymultirange(arrayrange(ARRAY[2,1], ARRAY[1,2]));  -- fail
+ERROR:  range lower bound must be less than or equal to range upper bound
+select array[1,1] <@ arraymultirange(arrayrange(array[1,2], array[2,1]));
+ ?column? 
+----------
+ f
+(1 row)
+
+select array[1,3] <@ arraymultirange(arrayrange(array[1,2], array[2,1]));
+ ?column? 
+----------
+ t
+(1 row)
+
+--
+-- Ranges of composites
+--
+create type two_ints as (a int, b int);
+create type two_ints_range as range (subtype = two_ints);
+-- with force_parallel_mode on, this exercises tqueue.c's range remapping
+select *, row_to_json(upper(t)) as u from
+  (values (two_ints_multirange(two_ints_range(row(1,2), row(3,4)))),
+          (two_ints_multirange(two_ints_range(row(5,6), row(7,8))))) v(t);
+          t          |       u       
+---------------------+---------------
+ {["(1,2)","(3,4)")} | {"a":3,"b":4}
+ {["(5,6)","(7,8)")} | {"a":7,"b":8}
+(2 rows)
+
+drop type two_ints cascade;
+NOTICE:  drop cascades to type two_ints_range
+--
+-- Check behavior when subtype lacks a hash function
+--
+set enable_sort = off;  -- try to make it pick a hash setop implementation
+select '{(2,5)}'::cashmultirange except select '{(5,6)}'::cashmultirange;
+ cashmultirange  
+-----------------
+ {($2.00,$5.00)}
+(1 row)
+
+reset enable_sort;
+--
+-- OUT/INOUT/TABLE functions
+--
+-- infer anymultirange from anymultirange
+create function mr_outparam_succeed(i anymultirange, out r anymultirange, out t text)
+  as $$ select $1, 'foo'::text $$ language sql;
+select * from mr_outparam_succeed(int4multirange(int4range(1,2)));
+    r    |  t  
+---------+-----
+ {[1,2)} | foo
+(1 row)
+
+-- infer anyarray from anymultirange
+create function mr_outparam_succeed2(i anymultirange, out r anyarray, out t text)
+  as $$ select ARRAY[upper($1)], 'foo'::text $$ language sql;
+select * from mr_outparam_succeed2(int4multirange(int4range(1,2)));
+  r  |  t  
+-----+-----
+ {2} | foo
+(1 row)
+
+-- infer anyrange from anymultirange
+create function mr_outparam_succeed3(i anymultirange, out r anyrange, out t text)
+  as $$ select range_merge($1), 'foo'::text $$ language sql;
+select * from mr_outparam_succeed3(int4multirange(int4range(1,2)));
+   r   |  t  
+-------+-----
+ [1,2) | foo
+(1 row)
+
+-- infer anymultirange from anyrange
+create function mr_outparam_succeed4(i anyrange, out r anymultirange, out t text)
+  as $$ select $1 @+ $1, 'foo'::text $$ language sql;
+select * from mr_outparam_succeed4(int4range(1,2));
+    r    |  t  
+---------+-----
+ {[1,2)} | foo
+(1 row)
+
+-- infer anyelement from anymultirange
+create function mr_inoutparam_succeed(out i anyelement, inout r anymultirange)
+  as $$ select upper($1), $1 $$ language sql;
+select * from mr_inoutparam_succeed(int4multirange(int4range(1,2)));
+ i |    r    
+---+---------
+ 2 | {[1,2)}
+(1 row)
+
+-- infer anyelement+anymultirange from anyelement+anymultirange
+create function mr_table_succeed(i anyelement, r anymultirange) returns table(i anyelement, r anymultirange)
+  as $$ select $1, $2 $$ language sql;
+select * from mr_table_succeed(123, int4multirange(int4range(1,11)));
+  i  |    r     
+-----+----------
+ 123 | {[1,11)}
+(1 row)
+
+-- should fail
+create function mr_outparam_fail(i anyelement, out r anymultirange, out t text)
+  as $$ select '[1,10]', 'foo' $$ language sql;
+ERROR:  cannot determine result data type
+DETAIL:  A function returning "anyrange" or "anymultirange" must have at least one "anyrange" or "anymultirange" argument.
+--should fail
+create function mr_inoutparam_fail(inout i anyelement, out r anymultirange)
+  as $$ select $1, '[1,10]' $$ language sql;
+ERROR:  cannot determine result data type
+DETAIL:  A function returning "anyrange" or "anymultirange" must have at least one "anyrange" or "anymultirange" argument.
+--should fail
+create function mr_table_fail(i anyelement) returns table(i anyelement, r anymultirange)
+  as $$ select $1, '[1,10]' $$ language sql;
+ERROR:  cannot determine result data type
+DETAIL:  A function returning "anyrange" or "anymultirange" must have at least one "anyrange" or "anymultirange" argument.
diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out
index 916e1016b5..e818eaea79 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -1101,13 +1101,15 @@ ORDER BY 1, 2;
  ?|   | ?|
  ?||  | ?||
  @    | ~
+ @*   | @*
+ @+   | @+
  @@   | @@
  @@@  | @@@
  |    | |
  ~<=~ | ~>=~
  ~<~  | ~>~
  ~=   | ~=
-(30 rows)
+(32 rows)
 
 -- Likewise for negator pairs.
 SELECT DISTINCT o1.oprname AS op1, o2.oprname AS op2
diff --git a/src/test/regress/expected/rangetypes.out b/src/test/regress/expected/rangetypes.out
index 6fd16bddd1..1bfe426be6 100644
--- a/src/test/regress/expected/rangetypes.out
+++ b/src/test/regress/expected/rangetypes.out
@@ -518,6 +518,24 @@ select numrange(1.0, 2.0) * numrange(2.5, 3.0);
  empty
 (1 row)
 
+select range_intersect_agg(nr) from numrange_test;
+ range_intersect_agg 
+---------------------
+ empty
+(1 row)
+
+select range_intersect_agg(nr) from numrange_test where false;
+ range_intersect_agg 
+---------------------
+ 
+(1 row)
+
+select range_intersect_agg(nr) from numrange_test where nr @> 4.0;
+ range_intersect_agg 
+---------------------
+ [3,5)
+(1 row)
+
 create table numrange_test2(nr numrange);
 create index numrange_test2_hash_idx on numrange_test2 using hash (nr);
 INSERT INTO numrange_test2 VALUES('[, 5)');
@@ -1284,7 +1302,7 @@ drop function anyarray_anyrange_func(anyarray, anyrange);
 create function bogus_func(anyelement)
   returns anyrange as 'select int4range(1,10)' language sql;
 ERROR:  cannot determine result data type
-DETAIL:  A function returning "anyrange" must have at least one "anyrange" argument.
+DETAIL:  A function returning "anyrange" or "anymultirange" must have at least one "anyrange" or "anymultirange" argument.
 -- should fail
 create function bogus_func(int)
   returns anyrange as 'select int4range(1,10)' language sql;
@@ -1392,6 +1410,7 @@ reset enable_sort;
 --
 -- OUT/INOUT/TABLE functions
 --
+-- infer anyrange from anyrange
 create function outparam_succeed(i anyrange, out r anyrange, out t text)
   as $$ select $1, 'foo'::text $$ language sql;
 select * from outparam_succeed(int4range(1,2));
@@ -1400,6 +1419,16 @@ select * from outparam_succeed(int4range(1,2));
  [1,2) | foo
 (1 row)
 
+-- infer anyarray from anyrange
+create function outparam_succeed2(i anyrange, out r anyarray, out t text)
+  as $$ select ARRAY[upper($1)], 'foo'::text $$ language sql;
+select * from outparam_succeed2(int4range(int4range(1,2)));
+  r  |  t  
+-----+-----
+ {2} | foo
+(1 row)
+
+-- infer anyelement from anyrange
 create function inoutparam_succeed(out i anyelement, inout r anyrange)
   as $$ select upper($1), $1 $$ language sql;
 select * from inoutparam_succeed(int4range(1,2));
@@ -1408,6 +1437,7 @@ select * from inoutparam_succeed(int4range(1,2));
  2 | [1,2)
 (1 row)
 
+-- infer anyelement+anyrange from anyelement+anyrange
 create function table_succeed(i anyelement, r anyrange) returns table(i anyelement, r anyrange)
   as $$ select $1, $2 $$ language sql;
 select * from table_succeed(123, int4range(1,11));
@@ -1420,14 +1450,14 @@ select * from table_succeed(123, int4range(1,11));
 create function outparam_fail(i anyelement, out r anyrange, out t text)
   as $$ select '[1,10]', 'foo' $$ language sql;
 ERROR:  cannot determine result data type
-DETAIL:  A function returning "anyrange" must have at least one "anyrange" argument.
+DETAIL:  A function returning "anyrange" or "anymultirange" must have at least one "anyrange" or "anymultirange" argument.
 --should fail
 create function inoutparam_fail(inout i anyelement, out r anyrange)
   as $$ select $1, '[1,10]' $$ language sql;
 ERROR:  cannot determine result data type
-DETAIL:  A function returning "anyrange" must have at least one "anyrange" argument.
+DETAIL:  A function returning "anyrange" or "anymultirange" must have at least one "anyrange" or "anymultirange" argument.
 --should fail
 create function table_fail(i anyelement) returns table(i anyelement, r anyrange)
   as $$ select $1, '[1,10]' $$ language sql;
 ERROR:  cannot determine result data type
-DETAIL:  A function returning "anyrange" must have at least one "anyrange" argument.
+DETAIL:  A function returning "anyrange" or "anymultirange" must have at least one "anyrange" or "anymultirange" argument.
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index 070de78e85..6f7fcf6326 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -92,6 +92,7 @@ num_exp_sqrt|t
 num_exp_sub|t
 num_input_test|f
 num_result|f
+nummultirange_test|t
 onek|t
 onek2|t
 path_tbl|f
@@ -172,6 +173,7 @@ quad_poly_tbl|t
 radix_text_tbl|t
 ramp|f
 real_city|f
+reservations|f
 road|t
 shighway|t
 slow_emp4000|f
diff --git a/src/test/regress/sql/multirangetypes.sql b/src/test/regress/sql/multirangetypes.sql
index 8651ba3f3d..b3ede18f96 100644
--- a/src/test/regress/sql/multirangetypes.sql
+++ b/src/test/regress/sql/multirangetypes.sql
@@ -63,3 +63,610 @@ select textmultirange();
 select textmultirange(textrange('a', 'c'));
 select textmultirange(textrange('a', 'c'), textrange('f', 'g'));
 select textmultirange(textrange('a', 'c'), textrange('b', 'd'));
+
+--
+-- create some test data and test the operators
+--
+
+CREATE TABLE nummultirange_test (nmr NUMMULTIRANGE);
+CREATE INDEX nummultirange_test_btree ON nummultirange_test(nmr);
+
+INSERT INTO nummultirange_test VALUES('{}');
+INSERT INTO nummultirange_test VALUES('{[,)}');
+INSERT INTO nummultirange_test VALUES('{[3,]}');
+INSERT INTO nummultirange_test VALUES('{[,), [3,]}');
+INSERT INTO nummultirange_test VALUES('{[, 5)}');
+INSERT INTO nummultirange_test VALUES(nummultirange());
+INSERT INTO nummultirange_test VALUES(nummultirange(variadic '{}'::numrange[]));
+INSERT INTO nummultirange_test VALUES(nummultirange(numrange(1.1, 2.2)));
+INSERT INTO nummultirange_test VALUES('{empty}');
+INSERT INTO nummultirange_test VALUES(nummultirange(numrange(1.7, 1.7, '[]'), numrange(1.7, 1.9)));
+INSERT INTO nummultirange_test VALUES(nummultirange(numrange(1.7, 1.7, '[]'), numrange(1.9, 2.1)));
+
+SELECT nmr, isempty(nmr), lower(nmr), upper(nmr) FROM nummultirange_test ORDER BY nmr;
+SELECT nmr, lower_inc(nmr), lower_inf(nmr), upper_inc(nmr), upper_inf(nmr) FROM nummultirange_test ORDER BY nmr;
+
+SELECT * FROM nummultirange_test WHERE nmr = '{}';
+SELECT * FROM nummultirange_test WHERE nmr = '{(,5)}';
+SELECT * FROM nummultirange_test WHERE nmr = '{[3,)}';
+SELECT * FROM nummultirange_test WHERE nmr = '{[1.7,1.7]}';
+SELECT * FROM nummultirange_test WHERE nmr = '{[1.7,1.7],[1.9,2.1)}';
+SELECT * FROM nummultirange_test WHERE nmr < '{}';
+SELECT * FROM nummultirange_test WHERE nmr < '{[-1000.0, -1000.0]}';
+SELECT * FROM nummultirange_test WHERE nmr < '{[0.0, 1.0]}';
+SELECT * FROM nummultirange_test WHERE nmr < '{[1000.0, 1001.0]}';
+SELECT * FROM nummultirange_test WHERE nmr <= '{}';
+SELECT * FROM nummultirange_test WHERE nmr <= '{[3,)}';
+SELECT * FROM nummultirange_test WHERE nmr >= '{}';
+SELECT * FROM nummultirange_test WHERE nmr >= '{[3,)}';
+SELECT * FROM nummultirange_test WHERE nmr > '{}';
+SELECT * FROM nummultirange_test WHERE nmr > '{[-1000.0, -1000.0]}';
+SELECT * FROM nummultirange_test WHERE nmr > '{[0.0, 1.0]}';
+SELECT * FROM nummultirange_test WHERE nmr > '{[1000.0, 1001.0]}';
+SELECT * FROM nummultirange_test WHERE nmr <> '{}';
+SELECT * FROM nummultirange_test WHERE nmr <> '{(,5)}';
+
+select nummultirange(numrange(2.0, 1.0));
+select nummultirange(numrange(5.0, 6.0), numrange(1.0, 2.0));
+
+-- overlaps
+SELECT * FROM nummultirange_test WHERE range_overlaps_multirange(numrange(4.0, 4.2), nmr);
+SELECT * FROM nummultirange_test WHERE numrange(4.0, 4.2) && nmr;
+SELECT * FROM nummultirange_test WHERE multirange_overlaps_range(nmr, numrange(4.0, 4.2));
+SELECT * FROM nummultirange_test WHERE nmr && numrange(4.0, 4.2);
+SELECT * FROM nummultirange_test WHERE multirange_overlaps_multirange(nmr, nummultirange(numrange(4.0, 4.2), numrange(6.0, 7.0)));
+SELECT * FROM nummultirange_test WHERE nmr && nummultirange(numrange(4.0, 4.2), numrange(6.0, 7.0));
+SELECT * FROM nummultirange_test WHERE nmr && nummultirange(numrange(6.0, 7.0));
+SELECT * FROM nummultirange_test WHERE nmr && nummultirange(numrange(6.0, 7.0), numrange(8.0, 9.0));
+
+-- mr contains x
+SELECT * FROM nummultirange_test WHERE multirange_contains_elem(nmr, 4.0);
+SELECT * FROM nummultirange_test WHERE nmr @> 4.0;
+SELECT * FROM nummultirange_test WHERE multirange_contains_range(nmr, numrange(4.0, 4.2));
+SELECT * FROM nummultirange_test WHERE nmr @> numrange(4.0, 4.2);
+SELECT * FROM nummultirange_test WHERE multirange_contains_multirange(nmr, '{[4.0,4.2), [6.0, 8.0)}');
+SELECT * FROM nummultirange_test WHERE nmr @> '{[4.0,4.2), [6.0, 8.0)}'::nummultirange;
+
+-- x is contained by mr
+SELECT * FROM nummultirange_test WHERE elem_contained_by_multirange(4.0, nmr);
+SELECT * FROM nummultirange_test WHERE 4.0 <@ nmr;
+SELECT * FROM nummultirange_test WHERE range_contained_by_multirange(numrange(4.0, 4.2), nmr);
+SELECT * FROM nummultirange_test WHERE numrange(4.0, 4.2) <@ nmr;
+SELECT * FROM nummultirange_test WHERE multirange_contained_by_multirange('{[4.0,4.2), [6.0, 8.0)}', nmr);
+SELECT * FROM nummultirange_test WHERE '{[4.0,4.2), [6.0, 8.0)}'::nummultirange <@ nmr;
+
+-- overlaps
+SELECT 'empty'::numrange && nummultirange();
+SELECT 'empty'::numrange && nummultirange(numrange(1,2));
+SELECT nummultirange() && 'empty'::numrange;
+SELECT nummultirange(numrange(1,2)) && 'empty'::numrange;
+SELECT nummultirange() && nummultirange();
+SELECT nummultirange() && nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) && nummultirange();
+SELECT nummultirange(numrange(3,4)) && nummultirange(numrange(1,2), numrange(7,8));
+SELECT nummultirange(numrange(1,2), numrange(7,8)) && nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(3,4)) && nummultirange(numrange(1,2), numrange(3.5,8));
+SELECT nummultirange(numrange(1,2), numrange(3.5,8)) && numrange(3,4);
+SELECT nummultirange(numrange(1,2), numrange(3.5,8)) && nummultirange(numrange(3,4));
+
+-- contains
+SELECT nummultirange() @> nummultirange();
+SELECT nummultirange() @> 'empty'::numrange;
+SELECT nummultirange(numrange(null,null)) @> numrange(1,2);
+SELECT nummultirange(numrange(null,null)) @> numrange(null,2);
+SELECT nummultirange(numrange(null,null)) @> numrange(2,null);
+SELECT nummultirange(numrange(null,5)) @> numrange(null,3);
+SELECT nummultirange(numrange(null,5)) @> numrange(null,8);
+SELECT nummultirange(numrange(5,null)) @> numrange(8,null);
+SELECT nummultirange(numrange(5,null)) @> numrange(3,null);
+SELECT nummultirange(numrange(1,5)) @> numrange(8,9);
+SELECT nummultirange(numrange(1,5)) @> numrange(3,9);
+SELECT nummultirange(numrange(1,5)) @> numrange(1,4);
+SELECT nummultirange(numrange(1,5)) @> numrange(1,5);
+SELECT nummultirange(numrange(-4,-2), numrange(1,5)) @> numrange(1,5);
+SELECT nummultirange(numrange(1,5), numrange(8,9)) @> numrange(1,5);
+SELECT nummultirange(numrange(1,5), numrange(8,9)) @> numrange(6,7);
+SELECT nummultirange(numrange(1,5), numrange(6,9)) @> numrange(6,7);
+SELECT '{[1,5)}'::nummultirange @> '{[1,5)}';
+SELECT '{[-4,-2), [1,5)}'::nummultirange @> '{[1,5)}';
+SELECT '{[1,5), [8,9)}'::nummultirange @> '{[1,5)}';
+SELECT '{[1,5), [8,9)}'::nummultirange @> '{[6,7)}';
+SELECT '{[1,5), [6,9)}'::nummultirange @> '{[6,7)}';
+
+-- is contained by
+SELECT nummultirange() <@ nummultirange();
+SELECT 'empty'::numrange <@ nummultirange();
+SELECT numrange(1,2) <@ nummultirange(numrange(null,null));
+SELECT numrange(null,2) <@ nummultirange(numrange(null,null));
+SELECT numrange(2,null) <@ nummultirange(numrange(null,null));
+SELECT numrange(null,3) <@ nummultirange(numrange(null,5));
+SELECT numrange(null,8) <@ nummultirange(numrange(null,5));
+SELECT numrange(8,null) <@ nummultirange(numrange(5,null));
+SELECT numrange(3,null) <@ nummultirange(numrange(5,null));
+SELECT numrange(8,9) <@ nummultirange(numrange(1,5));
+SELECT numrange(3,9) <@ nummultirange(numrange(1,5));
+SELECT numrange(1,4) <@ nummultirange(numrange(1,5));
+SELECT numrange(1,5) <@ nummultirange(numrange(1,5));
+SELECT numrange(1,5) <@ nummultirange(numrange(-4,-2), numrange(1,5));
+SELECT numrange(1,5) <@ nummultirange(numrange(1,5), numrange(8,9));
+SELECT numrange(6,7) <@ nummultirange(numrange(1,5), numrange(8,9));
+SELECT numrange(6,7) <@ nummultirange(numrange(1,5), numrange(6,9));
+SELECT '{[1,5)}' <@ '{[1,5)}'::nummultirange;
+SELECT '{[1,5)}' <@ '{[-4,-2), [1,5)}'::nummultirange;
+SELECT '{[1,5)}' <@ '{[1,5), [8,9)}'::nummultirange;
+SELECT '{[6,7)}' <@ '{[1,5), [8,9)}'::nummultirange;
+SELECT '{[6,7)}' <@ '{[1,5), [6,9)}'::nummultirange;
+
+-- overleft
+SELECT 'empty'::numrange &< nummultirange();
+SELECT 'empty'::numrange &< nummultirange(numrange(1,2));
+SELECT nummultirange() &< 'empty'::numrange;
+SELECT nummultirange(numrange(1,2)) &< 'empty'::numrange;
+SELECT nummultirange() &< nummultirange();
+SELECT nummultirange(numrange(1,2)) &< nummultirange();
+SELECT nummultirange() &< nummultirange(numrange(1,2));
+SELECT numrange(6,7) &< nummultirange(numrange(3,4));
+SELECT numrange(1,2) &< nummultirange(numrange(3,4));
+SELECT numrange(1,4) &< nummultirange(numrange(3,4));
+SELECT numrange(1,6) &< nummultirange(numrange(3,4));
+SELECT numrange(3.5,6) &< nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(6,7)) &< numrange(3,4);
+SELECT nummultirange(numrange(1,2)) &< numrange(3,4);
+SELECT nummultirange(numrange(1,4)) &< numrange(3,4);
+SELECT nummultirange(numrange(1,6)) &< numrange(3,4);
+SELECT nummultirange(numrange(3.5,6)) &< numrange(3,4);
+SELECT nummultirange(numrange(6,7)) &< nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,2)) &< nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,4)) &< nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,6)) &< nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(3.5,6)) &< nummultirange(numrange(3,4));
+
+-- overright
+SELECT nummultirange() &> 'empty'::numrange;
+SELECT nummultirange(numrange(1,2)) &> 'empty'::numrange;
+SELECT 'empty'::numrange &> nummultirange();
+SELECT 'empty'::numrange &> nummultirange(numrange(1,2));
+SELECT nummultirange() &> nummultirange();
+SELECT nummultirange() &> nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) &> nummultirange();
+SELECT nummultirange(numrange(3,4)) &> numrange(6,7);
+SELECT nummultirange(numrange(3,4)) &> numrange(1,2);
+SELECT nummultirange(numrange(3,4)) &> numrange(1,4);
+SELECT nummultirange(numrange(3,4)) &> numrange(1,6);
+SELECT nummultirange(numrange(3,4)) &> numrange(3.5,6);
+SELECT numrange(3,4) &> nummultirange(numrange(6,7));
+SELECT numrange(3,4) &> nummultirange(numrange(1,2));
+SELECT numrange(3,4) &> nummultirange(numrange(1,4));
+SELECT numrange(3,4) &> nummultirange(numrange(1,6));
+SELECT numrange(3,4) &> nummultirange(numrange(3.5,6));
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(6,7));
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(1,4));
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(1,6));
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(3.5,6));
+
+-- meets
+SELECT 'empty'::numrange -|- nummultirange();
+SELECT 'empty'::numrange -|- nummultirange(numrange(1,2));
+SELECT nummultirange() -|- 'empty'::numrange;
+SELECT nummultirange(numrange(1,2)) -|- 'empty'::numrange;
+SELECT nummultirange() -|- nummultirange();
+SELECT nummultirange(numrange(1,2)) -|- nummultirange();
+SELECT nummultirange() -|- nummultirange(numrange(1,2));
+SELECT numrange(1,2) -|- nummultirange(numrange(2,4));
+SELECT numrange(1,2) -|- nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,2)) -|- numrange(2,4);
+SELECT nummultirange(numrange(1,2)) -|- numrange(3,4);
+SELECT nummultirange(numrange(1,2)) -|- nummultirange(numrange(2,4));
+SELECT nummultirange(numrange(1,2)) -|- nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,2), numrange(5,6)) -|- nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,2), numrange(5,6)) -|- nummultirange(numrange(6,7));
+SELECT nummultirange(numrange(1,2), numrange(5,6)) -|- nummultirange(numrange(8,9));
+SELECT nummultirange(numrange(1,2)) -|- nummultirange(numrange(2,4), numrange(6,7));
+
+-- strictly left
+select 'empty'::numrange << nummultirange();
+select numrange(1,2) << nummultirange();
+select numrange(1,2) << nummultirange(numrange(3,4));
+select numrange(1,2) << nummultirange(numrange(0,4));
+select numrange(1,2) << nummultirange(numrange(0,4), numrange(7,8));
+select nummultirange() << 'empty'::numrange;
+select nummultirange() << numrange(1,2);
+select nummultirange(numrange(3,4)) << numrange(3,6);
+select nummultirange(numrange(0,2)) << numrange(3,6);
+select nummultirange(numrange(0,2), numrange(7,8)) << numrange(3,6);
+select nummultirange(numrange(-4,-2), numrange(0,2)) << numrange(3,6);
+select nummultirange() << nummultirange();
+select nummultirange() << nummultirange(numrange(1,2));
+select nummultirange(numrange(1,2)) << nummultirange();
+select nummultirange(numrange(1,2)) << nummultirange(numrange(1,2));
+select nummultirange(numrange(1,2)) << nummultirange(numrange(3,4));
+select nummultirange(numrange(1,2)) << nummultirange(numrange(3,4), numrange(7,8));
+select nummultirange(numrange(1,2), numrange(4,5)) << nummultirange(numrange(3,4), numrange(7,8));
+
+-- strictly right
+select nummultirange() >> 'empty'::numrange;
+select nummultirange() >> numrange(1,2);
+select nummultirange(numrange(3,4)) >> numrange(1,2);
+select nummultirange(numrange(0,4)) >> numrange(1,2);
+select nummultirange(numrange(0,4), numrange(7,8)) >> numrange(1,2);
+select 'empty'::numrange >> nummultirange();
+select numrange(1,2) >> nummultirange();
+select numrange(3,6) >> nummultirange(numrange(3,4));
+select numrange(3,6) >> nummultirange(numrange(0,2));
+select numrange(3,6) >> nummultirange(numrange(0,2), numrange(7,8));
+select numrange(3,6) >> nummultirange(numrange(-4,-2), numrange(0,2));
+select nummultirange() >> nummultirange();
+select nummultirange(numrange(1,2)) >> nummultirange();
+select nummultirange() >> nummultirange(numrange(1,2));
+select nummultirange(numrange(1,2)) >> nummultirange(numrange(1,2));
+select nummultirange(numrange(3,4)) >> nummultirange(numrange(1,2));
+select nummultirange(numrange(3,4), numrange(7,8)) >> nummultirange(numrange(1,2));
+select nummultirange(numrange(3,4), numrange(7,8)) >> nummultirange(numrange(1,2), numrange(4,5));
+
+-- union
+SELECT 'empty'::numrange @+ 'empty'::numrange;
+SELECT 'empty'::numrange @+ nummultirange();
+SELECT nummultirange() @+ 'empty'::numrange;
+SELECT nummultirange() @+ nummultirange();
+SELECT 'empty'::numrange @+ numrange(1,2);
+SELECT 'empty'::numrange @+ nummultirange(numrange(1,2));
+SELECT numrange(1,2) @+ nummultirange();
+SELECT nummultirange(numrange(1,2)) @+ 'empty'::numrange;
+SELECT numrange(1,2) @+ 'empty'::numrange;
+SELECT nummultirange() @+ numrange(1,2);
+SELECT nummultirange() @+ nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) @+ nummultirange();
+SELECT numrange(1,2) @+ numrange(1,2);
+SELECT numrange(1,2) @+ numrange(2,4);
+SELECT numrange(1,2) @+ numrange(3,4);
+SELECT nummultirange(numrange(1,2)) @+ nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) @+ nummultirange(numrange(2,4));
+SELECT nummultirange(numrange(1,2)) @+ nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) @+ nummultirange(numrange(2,4));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) @+ nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) @+ nummultirange(numrange(0,9));
+
+-- merge
+SELECT range_merge(nummultirange());
+SELECT range_merge(nummultirange(numrange(1,2)));
+SELECT range_merge(nummultirange(numrange(1,2), numrange(7,8)));
+
+-- minus
+SELECT 'empty'::numrange @- 'empty'::numrange;
+SELECT 'empty'::numrange @- nummultirange();
+SELECT nummultirange() @- 'empty'::numrange;
+SELECT nummultirange() @- nummultirange();
+SELECT 'empty'::numrange @- numrange(1,2);
+SELECT 'empty'::numrange @- nummultirange(numrange(1,2));
+SELECT numrange(1,2) @- nummultirange();
+SELECT nummultirange(numrange(1,2)) @- 'empty'::numrange;
+SELECT 'empty'::numrange @- numrange(1,2);
+SELECT numrange(1,2) @- 'empty'::numrange;
+SELECT nummultirange() @- numrange(1,2);
+SELECT nummultirange() @- nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) @- nummultirange();
+SELECT numrange(1,3) @- numrange(1,3);
+SELECT numrange(1,3) @- numrange(1,2);
+SELECT numrange(1,3) @- numrange(2,4);
+SELECT numrange(1,3) @- numrange(3,4);
+SELECT numrange(1,3) @- nummultirange(numrange(1,3));
+SELECT numrange(1,3) @- nummultirange(numrange(1,2));
+SELECT numrange(1,3) @- nummultirange(numrange(2,4));
+SELECT numrange(1,3) @- nummultirange(numrange(3,4));
+SELECT numrange(1,10) @- nummultirange(numrange(1,2), numrange(4,5));
+SELECT nummultirange(numrange(1,10)) @- numrange(0,2);
+SELECT nummultirange(numrange(1,10)) @- numrange(1,2);
+SELECT nummultirange(numrange(1,10)) @- numrange(3,4);
+SELECT nummultirange(numrange(1,10)) @- numrange(13,18);
+SELECT nummultirange(numrange(1,2), numrange(3,4)) @- nummultirange();
+SELECT nummultirange(numrange(1,2)) @- nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) @- nummultirange(numrange(2,4));
+SELECT nummultirange(numrange(1,2)) @- nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,4)) @- nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,4)) @- nummultirange(numrange(2,3));
+SELECT nummultirange(numrange(1,4)) @- nummultirange(numrange(0,8));
+SELECT nummultirange(numrange(1,4)) @- nummultirange(numrange(0,2));
+SELECT nummultirange(numrange(1,8)) @- nummultirange(numrange(0,2), numrange(3,4));
+SELECT nummultirange(numrange(1,8)) @- nummultirange(numrange(2,3), numrange(5,null));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) @- nummultirange(numrange(-2,0));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) @- nummultirange(numrange(2,4));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) @- nummultirange(numrange(3,5));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) @- nummultirange(numrange(0,9));
+SELECT nummultirange(numrange(1,3), numrange(4,5)) @- nummultirange(numrange(2,9));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) @- nummultirange(numrange(8,9));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) @- nummultirange(numrange(-2,0), numrange(8,9));
+
+-- intersection
+SELECT 'empty'::numrange @* 'empty'::numrange;
+SELECT 'empty'::numrange @* nummultirange();
+SELECT nummultirange() @* 'empty'::numrange;
+SELECT nummultirange() @* nummultirange();
+SELECT 'empty'::numrange @* numrange(1,2);
+SELECT 'empty'::numrange @* nummultirange(numrange(1,2));
+SELECT numrange(1,2) @* 'empty'::numrange;
+SELECT numrange(1,2) @* nummultirange();
+SELECT nummultirange(numrange(1,2)) @* 'empty'::numrange;
+SELECT nummultirange() @* numrange(1,2);
+SELECT nummultirange() @* nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) @* nummultirange();
+SELECT numrange(1,3) @* numrange(1,3);
+SELECT numrange(1,3) @* numrange(1,2);
+SELECT numrange(1,3) @* numrange(1,5);
+SELECT numrange(1,3) @* numrange(2,5);
+SELECT numrange(1,5) @* numrange(2,3);
+SELECT '{[1,3)}'::nummultirange @* numrange(1,3);
+SELECT '{[1,3)}'::nummultirange @* numrange(1,2);
+SELECT '{[1,3)}'::nummultirange @* numrange(1,5);
+SELECT '{[1,3)}'::nummultirange @* numrange(2,5);
+SELECT '{[1,5)}'::nummultirange @* numrange(2,3);
+SELECT numrange(1,3) @* '{[1,3)}'::nummultirange;
+SELECT numrange(1,2) @* '{[1,3)}'::nummultirange;
+SELECT numrange(1,5) @* '{[1,3)}'::nummultirange;
+SELECT numrange(2,5) @* '{[1,3)}'::nummultirange;
+SELECT numrange(2,3) @* '{[1,5)}'::nummultirange;
+SELECT '{[1,3)}'::nummultirange @* '{[1,5)}'::nummultirange;
+SELECT '{[1,3)}'::nummultirange @* '{[0,5)}'::nummultirange;
+SELECT '{[1,3)}'::nummultirange @* '{[0,2)}'::nummultirange;
+SELECT '{[1,3)}'::nummultirange @* '{[2,5)}'::nummultirange;
+SELECT '{[1,4)}'::nummultirange @* '{[2,3)}'::nummultirange;
+SELECT '{[1,4)}'::nummultirange @* '{[0,2), [3,5)}'::nummultirange;
+SELECT '{[1,4), [7,10)}'::nummultirange @* '{[0,8), [9,12)}'::nummultirange;
+SELECT '{[1,4), [7,10)}'::nummultirange @* '{[9,12)}'::nummultirange;
+SELECT '{[1,4), [7,10)}'::nummultirange @* '{[-5,-4), [5,6), [9,12)}'::nummultirange;
+SELECT '{[1,4), [7,10)}'::nummultirange @* '{[0,2), [3,8), [9,12)}'::nummultirange;
+SELECT '{[1,4), [7,10)}'::nummultirange @* '{[0,2), [3,8), [9,12)}'::nummultirange;
+
+--
+-- range_agg function
+--
+create table reservations ( room_id integer not null, booked_during daterange );
+insert into reservations values
+-- 1: has a meets and a gap
+(1, daterange('2018-07-01', '2018-07-07')),
+(1, daterange('2018-07-07', '2018-07-14')),
+(1, daterange('2018-07-20', '2018-07-22')),
+-- 2: just a single row
+(2, daterange('2018-07-01', '2018-07-03')),
+-- 3: one null range
+(3, NULL),
+-- 4: two null ranges
+(4, NULL),
+(4, NULL),
+-- 5: a null range and a non-null range
+(5, NULL),
+(5, daterange('2018-07-01', '2018-07-03')),
+-- 6: has overlap
+(6, daterange('2018-07-01', '2018-07-07')),
+(6, daterange('2018-07-05', '2018-07-10')),
+-- 7: two ranges that meet: no gap or overlap
+(7, daterange('2018-07-01', '2018-07-07')),
+(7, daterange('2018-07-07', '2018-07-14')),
+-- 8: an empty range
+(8, 'empty'::daterange)
+;
+SELECT   room_id, range_agg(booked_during)
+FROM     reservations
+GROUP BY room_id
+ORDER BY room_id;
+
+-- range_agg on a custom range type too
+SELECT  range_agg(r)
+FROM    (VALUES
+          ('[a,c]'::textrange),
+          ('[b,b]'::textrange),
+          ('[c,f]'::textrange),
+          ('[g,h)'::textrange),
+          ('[h,j)'::textrange)
+        ) t(r);
+
+select range_intersect_agg(nmr) from nummultirange_test;
+select range_intersect_agg(nmr) from nummultirange_test where false;
+-- test with just one input:
+select range_intersect_agg(nmr) from (values ('{[1,2]}'::nummultirange)) t(nmr);
+select range_intersect_agg(nmr) from nummultirange_test where nmr @> 4.0;
+
+create table nummultirange_test2(nmr nummultirange);
+create index nummultirange_test2_hash_idx on nummultirange_test2 using hash (nmr);
+
+INSERT INTO nummultirange_test2 VALUES('{[, 5)}');
+INSERT INTO nummultirange_test2 VALUES(nummultirange(numrange(1.1, 2.2)));
+INSERT INTO nummultirange_test2 VALUES(nummultirange(numrange(1.1, 2.2)));
+INSERT INTO nummultirange_test2 VALUES(nummultirange(numrange(1.1, 2.2,'()')));
+INSERT INTO nummultirange_test2 VALUES('{}');
+
+select * from nummultirange_test2 where nmr = '{}';
+select * from nummultirange_test2 where nmr = nummultirange(numrange(1.1, 2.2));
+select * from nummultirange_test2 where nmr = nummultirange(numrange(1.1, 2.3));
+
+set enable_nestloop=t;
+set enable_hashjoin=f;
+set enable_mergejoin=f;
+select * from nummultirange_test natural join nummultirange_test2 order by nmr;
+set enable_nestloop=f;
+set enable_hashjoin=t;
+set enable_mergejoin=f;
+select * from nummultirange_test natural join nummultirange_test2 order by nmr;
+set enable_nestloop=f;
+set enable_hashjoin=f;
+set enable_mergejoin=t;
+select * from nummultirange_test natural join nummultirange_test2 order by nmr;
+
+set enable_nestloop to default;
+set enable_hashjoin to default;
+set enable_mergejoin to default;
+
+DROP TABLE nummultirange_test2;
+
+--
+-- Test user-defined multirange of floats
+--
+
+select '{[123.001, 5.e9)}'::float8multirange @> 888.882::float8;
+create table float8multirange_test(f8mr float8multirange, i int);
+insert into float8multirange_test values(float8multirange(float8range(-100.00007, '1.111113e9')), 42);
+select * from float8multirange_test;
+drop table float8multirange_test;
+
+--
+-- Test multirange types over domains
+--
+
+create domain mydomain as int4;
+create type mydomainrange as range(subtype=mydomain);
+select '{[4,50)}'::mydomainmultirange @> 7::mydomain;
+drop domain mydomain cascade;
+
+--
+-- Test domains over multirange types
+--
+
+create domain restrictedmultirange as int4multirange check (upper(value) < 10);
+select '{[4,5)}'::restrictedmultirange @> 7;
+select '{[4,50)}'::restrictedmultirange @> 7; -- should fail
+drop domain restrictedmultirange;
+
+--
+-- Test multiple multirange types over the same subtype
+--
+
+create type textrange1 as range(subtype=text, collation="C");
+create type textrange2 as range(subtype=text, collation="C");
+
+select textmultirange1(textrange2('a','Z'));  -- should fail
+select textmultirange1(textrange1('a','Z')) @> 'b'::text;
+select textmultirange2(textrange2('a','z')) @> 'b'::text;
+
+drop type textrange1;
+drop type textrange2;
+
+--
+-- Test polymorphic type system
+--
+
+create function anyarray_anymultirange_func(a anyarray, r anymultirange)
+  returns anyelement as 'select $1[1] + lower($2);' language sql;
+
+select anyarray_anymultirange_func(ARRAY[1,2], int4multirange(int4range(10,20)));
+
+-- should fail
+select anyarray_anymultirange_func(ARRAY[1,2], nummultirange(numrange(10,20)));
+
+drop function anyarray_anymultirange_func(anyarray, anymultirange);
+
+-- should fail
+create function bogus_func(anyelement)
+  returns anymultirange as 'select int4multirange(int4range(1,10))' language sql;
+
+-- should fail
+create function bogus_func(int)
+  returns anymultirange as 'select int4multirange(int4range(1,10))' language sql;
+
+create function range_add_bounds(anymultirange)
+  returns anyelement as 'select lower($1) + upper($1)' language sql;
+
+select range_add_bounds(int4multirange(int4range(1, 17)));
+select range_add_bounds(nummultirange(numrange(1.0001, 123.123)));
+
+create function multirangetypes_sql(q anymultirange, b anyarray, out c anyelement)
+  as $$ select upper($1) + $2[1] $$
+  language sql;
+
+select multirangetypes_sql(int4multirange(int4range(1,10)), ARRAY[2,20]);
+select multirangetypes_sql(nummultirange(numrange(1,10)), ARRAY[2,20]);  -- match failure
+
+--
+-- Arrays of multiranges
+--
+
+select ARRAY[nummultirange(numrange(1.1, 1.2)), nummultirange(numrange(12.3, 155.5))];
+
+create table i8mr_array (f1 int, f2 int8multirange[]);
+insert into i8mr_array values (42, array[int8multirange(int8range(1,10)), int8multirange(int8range(2,20))]);
+select * from i8mr_array;
+drop table i8mr_array;
+
+--
+-- Multiranges of arrays
+--
+
+select arraymultirange(arrayrange(ARRAY[1,2], ARRAY[2,1]));
+select arraymultirange(arrayrange(ARRAY[2,1], ARRAY[1,2]));  -- fail
+
+select array[1,1] <@ arraymultirange(arrayrange(array[1,2], array[2,1]));
+select array[1,3] <@ arraymultirange(arrayrange(array[1,2], array[2,1]));
+
+--
+-- Ranges of composites
+--
+
+create type two_ints as (a int, b int);
+create type two_ints_range as range (subtype = two_ints);
+
+-- with force_parallel_mode on, this exercises tqueue.c's range remapping
+select *, row_to_json(upper(t)) as u from
+  (values (two_ints_multirange(two_ints_range(row(1,2), row(3,4)))),
+          (two_ints_multirange(two_ints_range(row(5,6), row(7,8))))) v(t);
+
+drop type two_ints cascade;
+
+--
+-- Check behavior when subtype lacks a hash function
+--
+
+set enable_sort = off;  -- try to make it pick a hash setop implementation
+
+select '{(2,5)}'::cashmultirange except select '{(5,6)}'::cashmultirange;
+
+reset enable_sort;
+
+--
+-- OUT/INOUT/TABLE functions
+--
+
+-- infer anymultirange from anymultirange
+create function mr_outparam_succeed(i anymultirange, out r anymultirange, out t text)
+  as $$ select $1, 'foo'::text $$ language sql;
+
+select * from mr_outparam_succeed(int4multirange(int4range(1,2)));
+
+-- infer anyarray from anymultirange
+create function mr_outparam_succeed2(i anymultirange, out r anyarray, out t text)
+  as $$ select ARRAY[upper($1)], 'foo'::text $$ language sql;
+
+select * from mr_outparam_succeed2(int4multirange(int4range(1,2)));
+
+-- infer anyrange from anymultirange
+create function mr_outparam_succeed3(i anymultirange, out r anyrange, out t text)
+  as $$ select range_merge($1), 'foo'::text $$ language sql;
+select * from mr_outparam_succeed3(int4multirange(int4range(1,2)));
+
+-- infer anymultirange from anyrange
+create function mr_outparam_succeed4(i anyrange, out r anymultirange, out t text)
+  as $$ select $1 @+ $1, 'foo'::text $$ language sql;
+
+select * from mr_outparam_succeed4(int4range(1,2));
+
+-- infer anyelement from anymultirange
+create function mr_inoutparam_succeed(out i anyelement, inout r anymultirange)
+  as $$ select upper($1), $1 $$ language sql;
+
+select * from mr_inoutparam_succeed(int4multirange(int4range(1,2)));
+
+-- infer anyelement+anymultirange from anyelement+anymultirange
+create function mr_table_succeed(i anyelement, r anymultirange) returns table(i anyelement, r anymultirange)
+  as $$ select $1, $2 $$ language sql;
+
+select * from mr_table_succeed(123, int4multirange(int4range(1,11)));
+
+-- should fail
+create function mr_outparam_fail(i anyelement, out r anymultirange, out t text)
+  as $$ select '[1,10]', 'foo' $$ language sql;
+
+--should fail
+create function mr_inoutparam_fail(inout i anyelement, out r anymultirange)
+  as $$ select $1, '[1,10]' $$ language sql;
+
+--should fail
+create function mr_table_fail(i anyelement) returns table(i anyelement, r anymultirange)
+  as $$ select $1, '[1,10]' $$ language sql;
diff --git a/src/test/regress/sql/rangetypes.sql b/src/test/regress/sql/rangetypes.sql
index 8960add976..32e18661d0 100644
--- a/src/test/regress/sql/rangetypes.sql
+++ b/src/test/regress/sql/rangetypes.sql
@@ -118,6 +118,10 @@ select numrange(1.0, 2.0) * numrange(2.0, 3.0);
 select numrange(1.0, 2.0) * numrange(1.5, 3.0);
 select numrange(1.0, 2.0) * numrange(2.5, 3.0);
 
+select range_intersect_agg(nr) from numrange_test;
+select range_intersect_agg(nr) from numrange_test where false;
+select range_intersect_agg(nr) from numrange_test where nr @> 4.0;
+
 create table numrange_test2(nr numrange);
 create index numrange_test2_hash_idx on numrange_test2 using hash (nr);
 
@@ -481,16 +485,25 @@ reset enable_sort;
 -- OUT/INOUT/TABLE functions
 --
 
+-- infer anyrange from anyrange
 create function outparam_succeed(i anyrange, out r anyrange, out t text)
   as $$ select $1, 'foo'::text $$ language sql;
 
 select * from outparam_succeed(int4range(1,2));
 
+-- infer anyarray from anyrange
+create function outparam_succeed2(i anyrange, out r anyarray, out t text)
+  as $$ select ARRAY[upper($1)], 'foo'::text $$ language sql;
+
+select * from outparam_succeed2(int4range(int4range(1,2)));
+
+-- infer anyelement from anyrange
 create function inoutparam_succeed(out i anyelement, inout r anyrange)
   as $$ select upper($1), $1 $$ language sql;
 
 select * from inoutparam_succeed(int4range(1,2));
 
+-- infer anyelement+anyrange from anyelement+anyrange
 create function table_succeed(i anyelement, r anyrange) returns table(i anyelement, r anyrange)
   as $$ select $1, $2 $$ language sql;
 
-- 
2.11.0

v5-0001-multirange-type-basics.patchapplication/octet-stream; name=v5-0001-multirange-type-basics.patchDownload
From fcf6446aff14bda7128995ee72eb8942420cefc6 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 v5 1/4] multirange type basics

---
 src/backend/catalog/pg_proc.c                 |  16 +-
 src/backend/catalog/pg_range.c                |  11 +-
 src/backend/catalog/pg_type.c                 | 134 +++-
 src/backend/commands/typecmds.c               | 171 ++++-
 src/backend/parser/parse_coerce.c             | 301 +++++++-
 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/catversion.h              |   2 +-
 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 +-
 42 files changed, 2895 insertions(+), 137 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 2a51501d8d..08552850c8 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,34 +788,10 @@ makeArrayTypeName(const char *typeName, Oid typeNamespace)
 {
 	char	   *arr = (char *) palloc(NAMEDATALEN);
 	int			namelen = strlen(typeName);
-	Relation	pg_type_desc;
-	int			i;
-
-	/*
-	 * The idea is to prepend underscores as needed until we make a name that
-	 * doesn't collide with anything...
-	 */
-	pg_type_desc = table_open(TypeRelationId, AccessShareLock);
-
-	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;
-	}
-
-	table_close(pg_type_desc, AccessShareLock);
+	int			underscores;
 
-	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\"",
@@ -883,3 +862,104 @@ 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)
+{
+	Relation	pg_type_desc;
+	int			i;
+
+	pg_type_desc = table_open(TypeRelationId, AccessShareLock);
+
+	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;
+		}
+	}
+
+	table_close(pg_type_desc, AccessShareLock);
+
+	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..f0b1f6f831 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			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"
 
 
 /*
@@ -185,6 +186,30 @@ anyrange_out(PG_FUNCTION_ARGS)
 }
 
 /*
+ * 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.
  *
  * We allow this so that PL functions can return VOID without any special
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));
 }
 
 /*
@@ -1938,6 +2006,45 @@ range_cmp_bound_values(TypeCacheEntry *typcache, const RangeBound *b1,
 }
 
 /*
+ * 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.
  */
 RangeType *
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
@@ -2469,6 +2469,16 @@ type_is_range(Oid typid)
 }
 
 /*
+ * 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
  *
  *		Given the type OID, fetch its category and preferred-type status.
@@ -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 16297a52a1..3f0e7f8576 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;
@@ -759,6 +777,16 @@ lookup_type_cache(Oid type_id, int flags)
 	}
 
 	/*
+	 * 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
 	 */
 	if ((flags & TYPECACHE_DOMAIN_BASE_INFO) &&
@@ -871,6 +899,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
  *
  * Note: we assume we're called in a relatively short-lived context, so it's
@@ -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/catversion.h b/src/include/catalog/catversion.h
index 1f6de76e9c..c265ee4c09 100644
--- a/src/include/catalog/catversion.h
+++ b/src/include/catalog/catversion.h
@@ -53,6 +53,6 @@
  */
 
 /*							yyyymmddN */
-#define CATALOG_VERSION_NO	201910251
+#define CATALOG_VERSION_NO	201911061
 
 #endif
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 fa7dc96ece..4d319bb5b3 100644
--- a/src/include/catalog/pg_operator.dat
+++ b/src/include/catalog/pg_operator.dat
@@ -3289,5 +3289,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 58ea5b982b..7ffa047969 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -9724,6 +9724,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 be49e00114..c95a38849a 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 2a584b4b13..6a25fcc9bf 100644
--- a/src/include/catalog/pg_type.h
+++ b/src/include/catalog/pg_type.h
@@ -259,6 +259,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 */
 
@@ -270,6 +271,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'
@@ -285,7 +287,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 */
 
@@ -344,4 +347,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
@@ -99,6 +99,11 @@ typedef struct TypeCacheEntry
 	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 fc0f14122b..c8c1cd0671 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 68ac56acdb..35155436f0 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.11.0

v5-0003-multirange-pg_dump.patchapplication/octet-stream; name=v5-0003-multirange-pg_dump.patchDownload
From 24297040a8209b11eb533baa63cb6f2b4fc7c51a Mon Sep 17 00:00:00 2001
From: "Paul A. Jungwirth" <pj@illuminatedcomputing.com>
Date: Sat, 21 Sep 2019 21:28:37 -0700
Subject: [PATCH v5 3/4] multirange pg_dump

---
 src/bin/pg_dump/pg_dump.c | 7 ++++++-
 src/bin/pg_dump/pg_dump.h | 1 +
 2 files changed, 7 insertions(+), 1 deletion(-)

diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index bf69adc2f4..db84e79e11 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1583,7 +1583,7 @@ selectDumpableType(TypeInfo *tyinfo, Archive *fout)
 	}
 
 	/* skip auto-generated array types */
-	if (tyinfo->isArray)
+	if (tyinfo->isArray || tyinfo->isMultirange)
 	{
 		tyinfo->dobj.objType = DO_DUMMY_TYPE;
 
@@ -4923,6 +4923,11 @@ getTypes(Archive *fout, int *numTypes)
 		else
 			tyinfo[i].isArray = false;
 
+		if (tyinfo[i].typtype == 'm')
+			tyinfo[i].isMultirange = true;
+		else
+			tyinfo[i].isMultirange = false;
+
 		/* Decide whether we want to dump it */
 		selectDumpableType(&tyinfo[i], fout);
 
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 7b2c1524a5..e27a752b6d 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -175,6 +175,7 @@ typedef struct _typeInfo
 	char		typrelkind;		/* 'r', 'v', 'c', etc */
 	char		typtype;		/* 'b', 'c', etc */
 	bool		isArray;		/* true if auto-generated array type */
+	bool		isMultirange;	/* true if auto-generated multirange type */
 	bool		isDefined;		/* true if typisdefined */
 	/* If needed, we'll create a "shell type" entry for it; link that here: */
 	struct _shellTypeInfo *shellType;	/* shell-type entry, or NULL */
-- 
2.11.0

#57Pavel Stehule
pavel.stehule@gmail.com
In reply to: Paul A Jungwirth (#56)
Re: range_agg

Hi

čt 7. 11. 2019 v 3:36 odesílatel Paul A Jungwirth <
pj@illuminatedcomputing.com> napsal:

On Wed, Nov 6, 2019 at 3:02 PM Paul A Jungwirth
<pj@illuminatedcomputing.com> wrote:

On Thu, Sep 26, 2019 at 2:13 PM Alvaro Herrera <alvherre@2ndquadrant.com>

wrote:

Hello Paul, I've started to review this patch. Here's a few minor
things I ran across -- mostly compiler warnings (is my compiler too
ancient?).

I just opened this thread to post a rebased set patches (especially
because of the `const` additions to range functions). Maybe it's not
that helpful since they don't include your changes yet but here they
are anyway. I'll post some more with your changes shortly.

Here is another batch of patches incorporating your improvements. It
seems like almost all the warnings were about moving variable
declarations above any other statements. For some reason I don't get
warnings about that on my end (compiling on OS X):

platter:postgres paul$ gcc --version
Configured with:
--prefix=/Applications/Xcode.app/Contents/Developer/usr
--with-gxx-include-dir=/usr/include/c++/4.2.1
Apple clang version 11.0.0 (clang-1100.0.33.12)
Target: x86_64-apple-darwin18.6.0
Thread model: posix
InstalledDir:
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin

For configure I'm saying this:

./configure CFLAGS=-ggdb 5-Og -g3 -fno-omit-frame-pointer
--enable-tap-tests --enable-cassert --enable-debug
--prefix=/Users/paul/local

Any suggestions to get better warnings? On my other patch I got
feedback about the very same kind. I could just compile on Linux but
it's nice to work on this away from my desk on the laptop. Maybe
installing a real gcc is the way to go.

I tested last patches. I found some issues

1. you should not to try patch catversion.

2. there is warning

parse_coerce.c: In function ‘enforce_generic_type_consistency’:
parse_coerce.c:1975:11: warning: ‘range_typelem’ may be used uninitialized
in this function [-Wmaybe-uninitialized]
1975 | else if (range_typelem != elem_typeid)

3. there are problems with pg_upgrade. Regress tests fails

command:
"/home/pavel/src/postgresql.master/tmp_install/usr/local/pgsql/bin/pg_restore"
--host /home/pavel/src/postgresql.master/src/b
pg_restore: connecting to database for restore
pg_restore: creating DATABASE "regression"
pg_restore: connecting to new database "regression"
pg_restore: connecting to database "regression" as user "pavel"
pg_restore: creating DATABASE PROPERTIES "regression"
pg_restore: connecting to new database "regression"
pg_restore: connecting to database "regression" as user "pavel"
pg_restore: creating pg_largeobject "pg_largeobject"
pg_restore: creating SCHEMA "fkpart3"
pg_restore: creating SCHEMA "fkpart4"
pg_restore: creating SCHEMA "fkpart5"
pg_restore: creating SCHEMA "fkpart6"
pg_restore: creating SCHEMA "mvtest_mvschema"
pg_restore: creating SCHEMA "regress_indexing"
pg_restore: creating SCHEMA "regress_rls_schema"
pg_restore: creating SCHEMA "regress_schema_2"
pg_restore: creating SCHEMA "testxmlschema"
pg_restore: creating TRANSFORM "TRANSFORM FOR integer LANGUAGE "sql""
pg_restore: creating TYPE "public.aggtype"
pg_restore: creating TYPE "public.arrayrange"
pg_restore: while PROCESSING TOC:
pg_restore: from TOC entry 1653; 1247 17044 TYPE arrayrange pavel
pg_restore: error: could not execute query: ERROR: pg_type array OID value
not set when in binary upgrade mode
Command was:.
-- For binary upgrade, must preserve pg_type oid
SELECT
pg_catalog.binary_upgrade_set_next_pg_type_oid('17044'::pg_catalog.oid);

-- For binary upgrade, must preserve pg_type array oid
SELECT
pg_catalog.binary_upgrade_set_next_array_pg_type_oid('17045'::pg_catalog.oid);

CREATE TYPE "public"."arrayrange" AS RANGE (
subtype = integer[]
);

4. there is a problem with doc

echo "<!ENTITY version \"13devel\">"; \
echo "<!ENTITY majorversion \"13\">"; \
} > version.sgml
'/usr/bin/perl' ./mk_feature_tables.pl YES
../../../src/backend/catalog/sql_feature_packages.txt
../../../src/backend/catalog/sql_features.txt > features-supported.sgml
'/usr/bin/perl' ./mk_feature_tables.pl NO
../../../src/backend/catalog/sql_feature_packages.txt
../../../src/backend/catalog/sql_features.txt > features-unsupported.sgml
'/usr/bin/perl' ./generate-errcodes-table.pl
../../../src/backend/utils/errcodes.txt > errcodes-table.sgml
'/usr/bin/perl' ./generate-keywords-table.pl . > keywords-table.sgml
/usr/bin/xmllint --path . --noout --valid postgres.sgml
extend.sgml:281: parser error : Opening and ending tag mismatch: para line
270 and type
type of the ranges in an </type>anymultirange</type>.
^
extend.sgml:281: parser error : Opening and ending tag mismatch: sect2 line
270 and type
type of the ranges in an </type>anymultirange</type>.
^
extend.sgml:282: parser error : Opening and ending tag mismatch: sect1 line
270 and para
</para>
^
extend.sgml:324: parser error : Opening and ending tag mismatch: chapter
line 270 and sect2
</sect2>
^

I am not sure how much is correct to use <literallayout class="monospaced">
in doc. It is used for ranges, and multiranges, but no in other places

All other looks well

Pavel

Show quoted text

Thanks,
Paul

#58Paul A Jungwirth
pj@illuminatedcomputing.com
In reply to: Pavel Stehule (#57)
Re: range_agg

On Tue, Nov 19, 2019 at 1:17 AM Pavel Stehule <pavel.stehule@gmail.com> wrote:

Hi
I tested last patches. I found some issues

Thank you for the review!

1. you should not to try patch catversion.

I've seen discussion on pgsql-hackers going both ways, but I'll leave
it out of future patches. :-)

2. there is warning

parse_coerce.c: In function ‘enforce_generic_type_consistency’:
parse_coerce.c:1975:11: warning: ‘range_typelem’ may be used uninitialized in this function [-Wmaybe-uninitialized]
1975 | else if (range_typelem != elem_typeid)

Fixed locally, will include in my next patch.

3. there are problems with pg_upgrade. Regress tests fails
. . .
pg_restore: while PROCESSING TOC:
pg_restore: from TOC entry 1653; 1247 17044 TYPE arrayrange pavel
pg_restore: error: could not execute query: ERROR: pg_type array OID value not set when in binary upgrade mode

I see what's going on here. (Sorry if this verbose explanation is
obvious; it's as much for me as for anyone.) With pg_upgrade the
values of pg_type.oid and pg_type.typarray must be the same before &
after. For built-in types there's no problem, because those are fixed
by pg_type.dat. But for user-defined types we have to take extra steps
to make sure they don't change. CREATE TYPE always uses two oids: one
for the type and one for the type's array type. But now when you
create a range type we use *four*: the range type, the array of that
range, the multirange type, and the array of that multirange.
Currently when you run pg_dump in "binary mode" (i.e. as part of
pg_upgrade) it includes calls to special functions to set the next oid
to use for pg_type.oid and pg_type.typarray. Then CREATE TYPE also has
special "binary mode" code to check those variables and use those oids
(e.g. AssignTypeArrayOid). After using them once it sets them back to
InvalidOid so it doesn't keep using them. So I guess I need to add
code to pg_dump so that it also outputs calls to two new special
functions that similarly set the oid to use for the next multirange
and multirange[]. For v12->v13 it will chose high-enough oids like we
do already for arrays of domains. (For other upgrades it will use the
existing value.) And then I can change the CREATE TYPE code to check
those pre-set values when obtaining the next oid. Does that sound like
the right approach here?

4. there is a problem with doc

extend.sgml:281: parser error : Opening and ending tag mismatch: para line 270 and type
type of the ranges in an </type>anymultirange</type>.

Hmm, yikes, I'll fix that!

I am not sure how much is correct to use <literallayout class="monospaced"> in doc. It is used for ranges, and multiranges, but no in other places

I could use some advice here. Many operators seem best presented in
groups of four, where only their parameter types change, for example:

int8range < int8range
int8range < int8multirange
int8multirange < int8range
int8multirange < int8multirange

All I really want is to show those separated by line breaks. I
couldn't find any other examples of that happening inside a table cell
though (i.e. inside <row><entry></entry></row>). What is the best way
to do that?

Thanks,
Paul

#59Paul A Jungwirth
pj@illuminatedcomputing.com
In reply to: Paul A Jungwirth (#58)
4 attachment(s)
Re: range_agg

On Tue, Nov 19, 2019 at 9:49 PM Paul A Jungwirth
<pj@illuminatedcomputing.com> wrote:

On Tue, Nov 19, 2019 at 1:17 AM Pavel Stehule <pavel.stehule@gmail.com> wrote:

Hi
I tested last patches. I found some issues

Thank you for the review!

Here is an updated patch series fixing the problems from that last
review. I would still like some direction about the doc formatting:

I am not sure how much is correct to use <literallayout class="monospaced"> in doc. It is used for ranges, and multiranges, but no in other places

I could use some advice here. Many operators seem best presented in
groups of four, where only their parameter types change, for example:

int8range < int8range
int8range < int8multirange
int8multirange < int8range
int8multirange < int8multirange

All I really want is to show those separated by line breaks. I
couldn't find any other examples of that happening inside a table cell
though (i.e. inside <row><entry></entry></row>). What is the best way
to do that?

Thanks,
Paul

Attachments:

v6-0003-multirange-pg_dump.patchapplication/octet-stream; name=v6-0003-multirange-pg_dump.patchDownload
From b0f1a6ca6bf0981b905c1c647357f7cffbb1c7c2 Mon Sep 17 00:00:00 2001
From: "Paul A. Jungwirth" <pj@illuminatedcomputing.com>
Date: Sat, 21 Sep 2019 21:28:37 -0700
Subject: [PATCH v6 3/4] multirange pg_dump

---
 src/backend/commands/typecmds.c            |  85 +++++++++++++++++--
 src/backend/utils/adt/pg_upgrade_support.c |  22 +++++
 src/bin/pg_dump/pg_dump.c                  | 130 ++++++++++++++++++++---------
 src/bin/pg_dump/pg_dump.h                  |   1 +
 src/include/catalog/binary_upgrade.h       |   2 +
 src/include/catalog/pg_proc.dat            |   8 ++
 src/include/commands/typecmds.h            |   2 +
 7 files changed, 205 insertions(+), 45 deletions(-)

diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index 7a440cafd1..38948a049b 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -85,6 +85,8 @@ typedef struct
 
 /* Potentially set by pg_upgrade_support functions */
 Oid			binary_upgrade_next_array_pg_type_oid = InvalidOid;
+Oid			binary_upgrade_next_multirange_pg_type_oid = InvalidOid;
+Oid			binary_upgrade_next_multirange_array_pg_type_oid = InvalidOid;
 
 static void makeRangeConstructors(const char *name, Oid namespace,
 								  Oid rangeOid, Oid subtype);
@@ -1356,11 +1358,11 @@ DefineRange(CreateRangeStmt *stmt)
 	char	   *typeName;
 	Oid			typeNamespace;
 	Oid			typoid;
-	Oid			mltrngtypoid;
 	char	   *rangeArrayName;
 	char	   *multirangeTypeName;
 	char	   *multirangeArrayName;
 	Oid			rangeArrayOid;
+	Oid			multirangeOid;
 	Oid			multirangeArrayOid;
 	Oid			rangeSubtype = InvalidOid;
 	List	   *rangeSubOpclassName = NIL;
@@ -1530,8 +1532,11 @@ DefineRange(CreateRangeStmt *stmt)
 	/* Allocate OID for array type */
 	rangeArrayOid = AssignTypeArrayOid();
 
+	/* Allocate OID for multirange type */
+	multirangeOid = AssignTypeMultirangeOid();
+
 	/* Allocate OID for multirange array type */
-	multirangeArrayOid = AssignTypeArrayOid();
+	multirangeArrayOid = AssignTypeMultirangeArrayOid();
 
 	/* Create the pg_type entry */
 	address =
@@ -1573,7 +1578,7 @@ DefineRange(CreateRangeStmt *stmt)
 	multirangeTypeName = makeMultirangeTypeName(typeName, typeNamespace);
 
 	mltrngaddress =
-		TypeCreate(InvalidOid,	/* no predetermined type OID */
+		TypeCreate(multirangeOid,	/* force assignment of this type OID */
 				   multirangeTypeName,	/* type name */
 				   typeNamespace,	/* namespace */
 				   InvalidOid,	/* relation oid (n/a here) */
@@ -1604,11 +1609,11 @@ DefineRange(CreateRangeStmt *stmt)
 				   0,			/* Array dimensions of typbasetype */
 				   false,		/* Type NOT NULL */
 				   InvalidOid); /* type's collation (ranges never have one) */
-	mltrngtypoid = mltrngaddress.objectId;
+	Assert(multirangeOid == mltrngaddress.objectId);
 
 	/* Create the entry in pg_range */
 	RangeCreate(typoid, rangeSubtype, rangeCollation, rangeSubOpclass,
-				rangeCanonical, rangeSubtypeDiff, mltrngtypoid);
+				rangeCanonical, rangeSubtypeDiff, multirangeOid);
 
 	/*
 	 * Create the array type that goes with it.
@@ -1671,7 +1676,7 @@ DefineRange(CreateRangeStmt *stmt)
 			   InvalidOid,		/* typmodin procedure - none */
 			   InvalidOid,		/* typmodout procedure - none */
 			   F_ARRAY_TYPANALYZE,	/* analyze procedure */
-			   mltrngtypoid,	/* element type ID */
+			   multirangeOid,	/* element type ID */
 			   true,			/* yes this is an array type */
 			   InvalidOid,		/* no further array type */
 			   InvalidOid,		/* base type ID */
@@ -1688,7 +1693,7 @@ DefineRange(CreateRangeStmt *stmt)
 	/* And create the constructor functions for this range type */
 	makeRangeConstructors(typeName, typeNamespace, typoid, rangeSubtype);
 	makeMultirangeConstructors(multirangeTypeName, typeNamespace,
-							   mltrngtypoid, rangeArrayOid);
+							   multirangeOid, rangeArrayOid);
 
 	pfree(multirangeTypeName);
 	pfree(multirangeArrayName);
@@ -2269,6 +2274,72 @@ AssignTypeArrayOid(void)
 	return type_array_oid;
 }
 
+/*
+ *	AssignTypeMultirangeOid
+ *
+ *	Pre-assign the range type's multirange OID for use in pg_type.oid
+ */
+Oid
+AssignTypeMultirangeOid(void)
+{
+	Oid			type_multirange_oid;
+
+	/* Use binary-upgrade override for pg_type.oid? */
+	if (IsBinaryUpgrade)
+	{
+		if (!OidIsValid(binary_upgrade_next_multirange_pg_type_oid))
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+					 errmsg("pg_type multirange OID value not set when in binary upgrade mode")));
+
+		type_multirange_oid = binary_upgrade_next_multirange_pg_type_oid;
+		binary_upgrade_next_multirange_pg_type_oid = InvalidOid;
+	}
+	else
+	{
+		Relation	pg_type = table_open(TypeRelationId, AccessShareLock);
+
+		type_multirange_oid = GetNewOidWithIndex(pg_type, TypeOidIndexId,
+												 Anum_pg_type_oid);
+		table_close(pg_type, AccessShareLock);
+	}
+
+	return type_multirange_oid;
+}
+
+/*
+ *	AssignTypeMultirangeArrayOid
+ *
+ *	Pre-assign the range type's multirange array OID for use in pg_type.typarray
+ */
+Oid
+AssignTypeMultirangeArrayOid(void)
+{
+	Oid			type_multirange_array_oid;
+
+	/* Use binary-upgrade override for pg_type.oid? */
+	if (IsBinaryUpgrade)
+	{
+		if (!OidIsValid(binary_upgrade_next_multirange_array_pg_type_oid))
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+					 errmsg("pg_type multirange array OID value not set when in binary upgrade mode")));
+
+		type_multirange_array_oid = binary_upgrade_next_multirange_array_pg_type_oid;
+		binary_upgrade_next_multirange_array_pg_type_oid = InvalidOid;
+	}
+	else
+	{
+		Relation	pg_type = table_open(TypeRelationId, AccessShareLock);
+
+		type_multirange_array_oid = GetNewOidWithIndex(pg_type, TypeOidIndexId,
+													   Anum_pg_type_oid);
+		table_close(pg_type, AccessShareLock);
+	}
+
+	return type_multirange_array_oid;
+}
+
 
 /*-------------------------------------------------------------------
  * DefineCompositeType
diff --git a/src/backend/utils/adt/pg_upgrade_support.c b/src/backend/utils/adt/pg_upgrade_support.c
index 99db5ba389..d980b96f48 100644
--- a/src/backend/utils/adt/pg_upgrade_support.c
+++ b/src/backend/utils/adt/pg_upgrade_support.c
@@ -52,6 +52,28 @@ binary_upgrade_set_next_array_pg_type_oid(PG_FUNCTION_ARGS)
 }
 
 Datum
+binary_upgrade_set_next_multirange_pg_type_oid(PG_FUNCTION_ARGS)
+{
+	Oid			typoid = PG_GETARG_OID(0);
+
+	CHECK_IS_BINARY_UPGRADE;
+	binary_upgrade_next_multirange_pg_type_oid = typoid;
+
+	PG_RETURN_VOID();
+}
+
+Datum
+binary_upgrade_set_next_multirange_array_pg_type_oid(PG_FUNCTION_ARGS)
+{
+	Oid			typoid = PG_GETARG_OID(0);
+
+	CHECK_IS_BINARY_UPGRADE;
+	binary_upgrade_next_multirange_array_pg_type_oid = typoid;
+
+	PG_RETURN_VOID();
+}
+
+Datum
 binary_upgrade_set_next_toast_pg_type_oid(PG_FUNCTION_ARGS)
 {
 	Oid			typoid = PG_GETARG_OID(0);
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index bf69adc2f4..4c65b047d2 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -274,7 +274,8 @@ static void dumpSearchPath(Archive *AH);
 static void binary_upgrade_set_type_oids_by_type_oid(Archive *fout,
 													 PQExpBuffer upgrade_buffer,
 													 Oid pg_type_oid,
-													 bool force_array_type);
+													 bool force_array_type,
+													 bool include_multirange_type);
 static bool binary_upgrade_set_type_oids_by_rel_oid(Archive *fout,
 													PQExpBuffer upgrade_buffer, Oid pg_rel_oid);
 static void binary_upgrade_set_pg_class_oids(Archive *fout,
@@ -1583,7 +1584,7 @@ selectDumpableType(TypeInfo *tyinfo, Archive *fout)
 	}
 
 	/* skip auto-generated array types */
-	if (tyinfo->isArray)
+	if (tyinfo->isArray || tyinfo->isMultirange)
 	{
 		tyinfo->dobj.objType = DO_DUMMY_TYPE;
 
@@ -4275,15 +4276,48 @@ dumpSubscription(Archive *fout, SubscriptionInfo *subinfo)
 	free(qsubname);
 }
 
+static Oid get_next_possible_free_pg_type_oid(Archive *fout, PQExpBuffer upgrade_query)
+{
+	/*
+	 * If the old version didn't assign an array type, but the new version
+	 * does, we must select an unused type OID to assign.  This currently
+	 * only happens for domains, when upgrading pre-v11 to v11 and up.
+	 *
+	 * Note: local state here is kind of ugly, but we must have some,
+	 * since we mustn't choose the same unused OID more than once.
+	 */
+	static Oid	next_possible_free_oid = FirstNormalObjectId;
+	PGresult   *res;
+	bool		is_dup;
+
+	do
+	{
+		++next_possible_free_oid;
+		printfPQExpBuffer(upgrade_query,
+						  "SELECT EXISTS(SELECT 1 "
+						  "FROM pg_catalog.pg_type "
+						  "WHERE oid = '%u'::pg_catalog.oid);",
+						  next_possible_free_oid);
+		res = ExecuteSqlQueryForSingleRow(fout, upgrade_query->data);
+		is_dup = (PQgetvalue(res, 0, 0)[0] == 't');
+		PQclear(res);
+	} while (is_dup);
+
+	return next_possible_free_oid;
+}
+
 static void
 binary_upgrade_set_type_oids_by_type_oid(Archive *fout,
 										 PQExpBuffer upgrade_buffer,
 										 Oid pg_type_oid,
-										 bool force_array_type)
+										 bool force_array_type,
+										 bool include_multirange_type)
 {
 	PQExpBuffer upgrade_query = createPQExpBuffer();
 	PGresult   *res;
 	Oid			pg_type_array_oid;
+	Oid			pg_type_multirange_oid;
+	Oid			pg_type_multirange_array_oid;
 
 	appendPQExpBufferStr(upgrade_buffer, "\n-- For binary upgrade, must preserve pg_type oid\n");
 	appendPQExpBuffer(upgrade_buffer,
@@ -4304,33 +4338,7 @@ binary_upgrade_set_type_oids_by_type_oid(Archive *fout,
 	PQclear(res);
 
 	if (!OidIsValid(pg_type_array_oid) && force_array_type)
-	{
-		/*
-		 * If the old version didn't assign an array type, but the new version
-		 * does, we must select an unused type OID to assign.  This currently
-		 * only happens for domains, when upgrading pre-v11 to v11 and up.
-		 *
-		 * Note: local state here is kind of ugly, but we must have some,
-		 * since we mustn't choose the same unused OID more than once.
-		 */
-		static Oid	next_possible_free_oid = FirstNormalObjectId;
-		bool		is_dup;
-
-		do
-		{
-			++next_possible_free_oid;
-			printfPQExpBuffer(upgrade_query,
-							  "SELECT EXISTS(SELECT 1 "
-							  "FROM pg_catalog.pg_type "
-							  "WHERE oid = '%u'::pg_catalog.oid);",
-							  next_possible_free_oid);
-			res = ExecuteSqlQueryForSingleRow(fout, upgrade_query->data);
-			is_dup = (PQgetvalue(res, 0, 0)[0] == 't');
-			PQclear(res);
-		} while (is_dup);
-
-		pg_type_array_oid = next_possible_free_oid;
-	}
+		pg_type_array_oid = get_next_possible_free_pg_type_oid(fout, upgrade_query);
 
 	if (OidIsValid(pg_type_array_oid))
 	{
@@ -4341,6 +4349,46 @@ binary_upgrade_set_type_oids_by_type_oid(Archive *fout,
 						  pg_type_array_oid);
 	}
 
+	/*
+	 * Pre-set the multirange type oid and its own array type oid.
+	 */
+	if (include_multirange_type)
+	{
+		if (fout->remoteVersion >= 130000)
+		{
+			appendPQExpBuffer(upgrade_query,
+							  "SELECT t.oid, t.typarray "
+							  "FROM pg_catalog.pg_type t "
+							  "JOIN pg_catalog.pg_range r "
+							  "ON t.oid = r.mltrngtypid "
+							  "WHERE r.rngtypid = '%u'::pg_catalog.oid;",
+							  pg_type_oid);
+
+			res = ExecuteSqlQueryForSingleRow(fout, upgrade_query->data);
+
+			pg_type_multirange_oid = atooid(PQgetvalue(res, 0, PQfnumber(res, "oid")));
+			pg_type_multirange_array_oid = atooid(PQgetvalue(res, 0, PQfnumber(res, "typarray")));
+
+			PQclear(res);
+		}
+		else
+		{
+			pg_type_multirange_oid = get_next_possible_free_pg_type_oid(fout, upgrade_query);
+			pg_type_multirange_array_oid = get_next_possible_free_pg_type_oid(fout, upgrade_query);
+		}
+
+		appendPQExpBufferStr(upgrade_buffer,
+							 "\n-- For binary upgrade, must preserve multirange pg_type oid\n");
+		appendPQExpBuffer(upgrade_buffer,
+						  "SELECT pg_catalog.binary_upgrade_set_next_multirange_pg_type_oid('%u'::pg_catalog.oid);\n\n",
+						  pg_type_multirange_oid);
+		appendPQExpBufferStr(upgrade_buffer,
+							 "\n-- For binary upgrade, must preserve multirange pg_type array oid\n");
+		appendPQExpBuffer(upgrade_buffer,
+						  "SELECT pg_catalog.binary_upgrade_set_next_multirange_array_pg_type_oid('%u'::pg_catalog.oid);\n\n",
+						  pg_type_multirange_array_oid);
+	}
+
 	destroyPQExpBuffer(upgrade_query);
 }
 
@@ -4374,7 +4422,7 @@ binary_upgrade_set_type_oids_by_rel_oid(Archive *fout,
 	pg_type_oid = atooid(PQgetvalue(upgrade_res, 0, PQfnumber(upgrade_res, "crel")));
 
 	binary_upgrade_set_type_oids_by_type_oid(fout, upgrade_buffer,
-											 pg_type_oid, false);
+											 pg_type_oid, false, false);
 
 	if (!PQgetisnull(upgrade_res, 0, PQfnumber(upgrade_res, "trel")))
 	{
@@ -4923,6 +4971,11 @@ getTypes(Archive *fout, int *numTypes)
 		else
 			tyinfo[i].isArray = false;
 
+		if (tyinfo[i].typtype == 'm')
+			tyinfo[i].isMultirange = true;
+		else
+			tyinfo[i].isMultirange = false;
+
 		/* Decide whether we want to dump it */
 		selectDumpableType(&tyinfo[i], fout);
 
@@ -10245,7 +10298,7 @@ dumpEnumType(Archive *fout, TypeInfo *tyinfo)
 	if (dopt->binary_upgrade)
 		binary_upgrade_set_type_oids_by_type_oid(fout, q,
 												 tyinfo->dobj.catId.oid,
-												 false);
+												 false, false);
 
 	appendPQExpBuffer(q, "CREATE TYPE %s AS ENUM (",
 					  qualtypname);
@@ -10371,7 +10424,7 @@ dumpRangeType(Archive *fout, TypeInfo *tyinfo)
 	if (dopt->binary_upgrade)
 		binary_upgrade_set_type_oids_by_type_oid(fout, q,
 												 tyinfo->dobj.catId.oid,
-												 false);
+												 false, true);
 
 	appendPQExpBuffer(q, "CREATE TYPE %s AS RANGE (",
 					  qualtypname);
@@ -10477,7 +10530,7 @@ dumpUndefinedType(Archive *fout, TypeInfo *tyinfo)
 	if (dopt->binary_upgrade)
 		binary_upgrade_set_type_oids_by_type_oid(fout, q,
 												 tyinfo->dobj.catId.oid,
-												 false);
+												 false, false);
 
 	appendPQExpBuffer(q, "CREATE TYPE %s;\n",
 					  qualtypname);
@@ -10682,7 +10735,7 @@ dumpBaseType(Archive *fout, TypeInfo *tyinfo)
 	if (dopt->binary_upgrade)
 		binary_upgrade_set_type_oids_by_type_oid(fout, q,
 												 tyinfo->dobj.catId.oid,
-												 false);
+												 false, false);
 
 	appendPQExpBuffer(q,
 					  "CREATE TYPE %s (\n"
@@ -10869,7 +10922,8 @@ dumpDomain(Archive *fout, TypeInfo *tyinfo)
 	if (dopt->binary_upgrade)
 		binary_upgrade_set_type_oids_by_type_oid(fout, q,
 												 tyinfo->dobj.catId.oid,
-												 true); /* force array type */
+												 true,		/* force array type */
+												 false);	/* force multirange type */
 
 	qtypname = pg_strdup(fmtId(tyinfo->dobj.name));
 	qualtypname = pg_strdup(fmtQualifiedDumpable(tyinfo));
@@ -11057,7 +11111,7 @@ dumpCompositeType(Archive *fout, TypeInfo *tyinfo)
 	{
 		binary_upgrade_set_type_oids_by_type_oid(fout, q,
 												 tyinfo->dobj.catId.oid,
-												 false);
+												 false, false);
 		binary_upgrade_set_pg_class_oids(fout, q, tyinfo->typrelid, false);
 	}
 
@@ -11331,7 +11385,7 @@ dumpShellType(Archive *fout, ShellTypeInfo *stinfo)
 	if (dopt->binary_upgrade)
 		binary_upgrade_set_type_oids_by_type_oid(fout, q,
 												 stinfo->baseType->dobj.catId.oid,
-												 false);
+												 false, false);
 
 	appendPQExpBuffer(q, "CREATE TYPE %s;\n",
 					  fmtQualifiedDumpable(stinfo));
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 7b2c1524a5..e27a752b6d 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -175,6 +175,7 @@ typedef struct _typeInfo
 	char		typrelkind;		/* 'r', 'v', 'c', etc */
 	char		typtype;		/* 'b', 'c', etc */
 	bool		isArray;		/* true if auto-generated array type */
+	bool		isMultirange;	/* true if auto-generated multirange type */
 	bool		isDefined;		/* true if typisdefined */
 	/* If needed, we'll create a "shell type" entry for it; link that here: */
 	struct _shellTypeInfo *shellType;	/* shell-type entry, or NULL */
diff --git a/src/include/catalog/binary_upgrade.h b/src/include/catalog/binary_upgrade.h
index 2927b7a4d3..2b6e87bb84 100644
--- a/src/include/catalog/binary_upgrade.h
+++ b/src/include/catalog/binary_upgrade.h
@@ -16,6 +16,8 @@
 
 extern PGDLLIMPORT Oid binary_upgrade_next_pg_type_oid;
 extern PGDLLIMPORT Oid binary_upgrade_next_array_pg_type_oid;
+extern PGDLLIMPORT Oid binary_upgrade_next_multirange_pg_type_oid;
+extern PGDLLIMPORT Oid binary_upgrade_next_multirange_array_pg_type_oid;
 extern PGDLLIMPORT Oid binary_upgrade_next_toast_pg_type_oid;
 
 extern PGDLLIMPORT Oid binary_upgrade_next_heap_pg_class_oid;
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 13ea94c024..d3cb8c8cb4 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10368,6 +10368,14 @@
   proname => 'binary_upgrade_set_next_array_pg_type_oid', provolatile => 'v',
   proparallel => 'r', prorettype => 'void', proargtypes => 'oid',
   prosrc => 'binary_upgrade_set_next_array_pg_type_oid' },
+{ oid => '8144', descr => 'for use by pg_upgrade',
+  proname => 'binary_upgrade_set_next_multirange_pg_type_oid', provolatile => 'v',
+  proparallel => 'r', prorettype => 'void', proargtypes => 'oid',
+  prosrc => 'binary_upgrade_set_next_multirange_pg_type_oid' },
+{ oid => '8145', descr => 'for use by pg_upgrade',
+  proname => 'binary_upgrade_set_next_multirange_array_pg_type_oid', provolatile => 'v',
+  proparallel => 'r', prorettype => 'void', proargtypes => 'oid',
+  prosrc => 'binary_upgrade_set_next_multirange_array_pg_type_oid' },
 { oid => '3585', descr => 'for use by pg_upgrade',
   proname => 'binary_upgrade_set_next_toast_pg_type_oid', provolatile => 'v',
   proparallel => 'r', prorettype => 'void', proargtypes => 'oid',
diff --git a/src/include/commands/typecmds.h b/src/include/commands/typecmds.h
index 8af0665f29..2b9115b6a0 100644
--- a/src/include/commands/typecmds.h
+++ b/src/include/commands/typecmds.h
@@ -29,6 +29,8 @@ extern ObjectAddress DefineRange(CreateRangeStmt *stmt);
 extern ObjectAddress AlterEnum(AlterEnumStmt *stmt);
 extern ObjectAddress DefineCompositeType(RangeVar *typevar, List *coldeflist);
 extern Oid	AssignTypeArrayOid(void);
+extern Oid	AssignTypeMultirangeOid(void);
+extern Oid	AssignTypeMultirangeArrayOid(void);
 
 extern ObjectAddress AlterDomainDefault(List *names, Node *defaultRaw);
 extern ObjectAddress AlterDomainNotNull(List *names, bool notNull);
-- 
2.11.0

v6-0001-multirange-type-basics.patchapplication/octet-stream; name=v6-0001-multirange-type-basics.patchDownload
From f3e68d611f5df06a6eaeed192ce0d388cba1b192 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 v6 1/4] multirange type basics

---
 src/backend/catalog/pg_proc.c                 |  16 +-
 src/backend/catalog/pg_range.c                |  11 +-
 src/backend/catalog/pg_type.c                 | 134 +++-
 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, 2895 insertions(+), 137 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 a8c1de511f..5d962ed302 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,34 +788,10 @@ makeArrayTypeName(const char *typeName, Oid typeNamespace)
 {
 	char	   *arr = (char *) palloc(NAMEDATALEN);
 	int			namelen = strlen(typeName);
-	Relation	pg_type_desc;
-	int			i;
-
-	/*
-	 * The idea is to prepend underscores as needed until we make a name that
-	 * doesn't collide with anything...
-	 */
-	pg_type_desc = table_open(TypeRelationId, AccessShareLock);
-
-	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;
-	}
-
-	table_close(pg_type_desc, AccessShareLock);
+	int			underscores;
 
-	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\"",
@@ -883,3 +862,104 @@ 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)
+{
+	Relation	pg_type_desc;
+	int			i;
+
+	pg_type_desc = table_open(TypeRelationId, AccessShareLock);
+
+	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;
+		}
+	}
+
+	table_close(pg_type_desc, AccessShareLock);
+
+	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"
 
 
 /*
@@ -185,6 +186,30 @@ anyrange_out(PG_FUNCTION_ARGS)
 }
 
 /*
+ * 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.
  *
  * We allow this so that PL functions can return VOID without any special
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));
 }
 
 /*
@@ -1938,6 +2006,45 @@ range_cmp_bound_values(TypeCacheEntry *typcache, const RangeBound *b1,
 }
 
 /*
+ * 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.
  */
 RangeType *
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
@@ -2469,6 +2469,16 @@ type_is_range(Oid typid)
 }
 
 /*
+ * 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
  *
  *		Given the type OID, fetch its category and preferred-type status.
@@ -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;
@@ -759,6 +777,16 @@ lookup_type_cache(Oid type_id, int flags)
 	}
 
 	/*
+	 * 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
 	 */
 	if ((flags & TYPECACHE_DOMAIN_BASE_INFO) &&
@@ -871,6 +899,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
  *
  * Note: we assume we're called in a relatively short-lived context, so it's
@@ -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 fa7dc96ece..4d319bb5b3 100644
--- a/src/include/catalog/pg_operator.dat
+++ b/src/include/catalog/pg_operator.dat
@@ -3289,5 +3289,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 58ea5b982b..7ffa047969 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -9724,6 +9724,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 be49e00114..c95a38849a 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 2a584b4b13..6a25fcc9bf 100644
--- a/src/include/catalog/pg_type.h
+++ b/src/include/catalog/pg_type.h
@@ -259,6 +259,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 */
 
@@ -270,6 +271,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'
@@ -285,7 +287,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 */
 
@@ -344,4 +347,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
@@ -99,6 +99,11 @@ typedef struct TypeCacheEntry
 	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.11.0

v6-0004-multirange-docs.patchapplication/octet-stream; name=v6-0004-multirange-docs.patchDownload
From fcc5d11d3f2176ab2a859af7c62a3208e52da909 Mon Sep 17 00:00:00 2001
From: "Paul A. Jungwirth" <pj@illuminatedcomputing.com>
Date: Sat, 21 Sep 2019 21:28:47 -0700
Subject: [PATCH v6 4/4] multirange docs

---
 doc/src/sgml/extend.sgml     |  28 +++-
 doc/src/sgml/func.sgml       | 302 +++++++++++++++++++++++++++++++++++++++----
 doc/src/sgml/rangetypes.sgml |  47 ++++++-
 3 files changed, 342 insertions(+), 35 deletions(-)

diff --git a/doc/src/sgml/extend.sgml b/doc/src/sgml/extend.sgml
index a3046f22d0..7ea2f65778 100644
--- a/doc/src/sgml/extend.sgml
+++ b/doc/src/sgml/extend.sgml
@@ -229,9 +229,9 @@
    </indexterm>
 
     <para>
-     Five pseudo-types of special interest are <type>anyelement</type>,
+     Six pseudo-types of special interest are <type>anyelement</type>,
      <type>anyarray</type>, <type>anynonarray</type>, <type>anyenum</type>,
-     and <type>anyrange</type>,
+     <type>anyrange</type>, and <type>anymultirange</type>.
      which are collectively called <firstterm>polymorphic types</firstterm>.
      Any function declared using these types is said to be
      a <firstterm>polymorphic function</firstterm>.  A polymorphic function can
@@ -250,15 +250,15 @@
      position declared as <type>anyarray</type> can have any array data type,
      but similarly they must all be the same type.  And similarly,
      positions declared as <type>anyrange</type> must all be the same range
-     type.  Furthermore, if there are
+     type.  Likewise for <type>anymultirange</type>.
+    </para>
+
+    <para>
+     Furthermore, if there are
      positions declared <type>anyarray</type> and others declared
      <type>anyelement</type>, the actual array type in the
      <type>anyarray</type> positions must be an array whose elements are
      the same type appearing in the <type>anyelement</type> positions.
-     Similarly, if there are positions declared <type>anyrange</type>
-     and others declared <type>anyelement</type>, the actual range type in
-     the <type>anyrange</type> positions must be a range whose subtype is
-     the same type appearing in the <type>anyelement</type> positions.
      <type>anynonarray</type> is treated exactly the same as <type>anyelement</type>,
      but adds the additional constraint that the actual type must not be
      an array type.
@@ -268,6 +268,20 @@
     </para>
 
     <para>
+     Similarly, if there are positions declared <type>anyrange</type>
+     and others declared <type>anyelement</type>, the actual range type in
+     the <type>anyrange</type> positions must be a range whose subtype is
+     the same type appearing in the <type>anyelement</type> positions.
+     The type <type>anymultirange</type> accepts a multirange
+     whose contents match the other polymorphic types.
+     That is it must hold ranges matching <type>anyrange</type>
+     and base type elements matching <type>anyelement</type> (if either of
+     those are present). If <type>anyarray</type> is present its elements
+     must match the base type of the <type>anyrange</type> and/or the base
+     type of the ranges in an <type>anymultirange</type>.
+    </para>
+
+    <para>
      Thus, when more than one argument position is declared with a polymorphic
      type, the net effect is that only certain combinations of actual argument
      types are allowed.  For example, a function declared as
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 28eb322f3f..54b428eb39 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -14566,7 +14566,9 @@ NULL baz</literallayout>(3 rows)</entry>
 
   <para>
    <xref linkend="range-operators-table"/> shows the operators
-   available for range types.
+   available for range and multirange types.
+   Many of these operators will accept either a range or multirange
+   on either side.
   </para>
 
     <table id="range-operators-table">
@@ -14576,7 +14578,7 @@ NULL baz</literallayout>(3 rows)</entry>
        <row>
         <entry>Operator</entry>
         <entry>Description</entry>
-        <entry>Example</entry>
+        <entry>Examples</entry>
         <entry>Result</entry>
        </row>
       </thead>
@@ -14584,136 +14586,247 @@ NULL baz</literallayout>(3 rows)</entry>
        <row>
         <entry> <literal>=</literal> </entry>
         <entry>equal</entry>
-        <entry><literal>int4range(1,5) = '[1,4]'::int4range</literal></entry>
+        <entry><literallayout class="monospaced">int4range(1,5) = '[1,4]'::int4range
+'{[1,5)}'::int4multirange = '{[1,4]}'::int4multirange</literallayout></entry>
         <entry><literal>t</literal></entry>
        </row>
 
        <row>
         <entry> <literal>&lt;&gt;</literal> </entry>
         <entry>not equal</entry>
-        <entry><literal>numrange(1.1,2.2) &lt;&gt; numrange(1.1,2.3)</literal></entry>
+        <entry><literallayout class="monospaced">numrange(1.1,2.2) &lt;&gt; numrange(1.1,2.3)
+'{[1.1,2.2)}'::nummultirange &lt;&gt; '{[1.1,2.3)}'::nummultirange</literallayout></entry>
         <entry><literal>t</literal></entry>
        </row>
 
        <row>
         <entry> <literal>&lt;</literal> </entry>
         <entry>less than</entry>
-        <entry><literal>int4range(1,10) &lt; int4range(2,3)</literal></entry>
+        <entry><literallayout class="monospaced">int4range(1,10) &lt; int4range(2,3)
+'{[1,10)}'::int4multirange &lt; '{[2,3)}'::int4multirange</literallayout>
+        </entry>
         <entry><literal>t</literal></entry>
        </row>
 
        <row>
         <entry> <literal>&gt;</literal> </entry>
         <entry>greater than</entry>
-        <entry><literal>int4range(1,10) &gt; int4range(1,5)</literal></entry>
+        <entry>
+          <literallayout class="monospaced">int4range(1,10) &gt; int4range(1,5)
+'{[1,10)}'::int4multirange &gt; '{[1,5)}'::int4multirange</literallayout>
+        </entry>
         <entry><literal>t</literal></entry>
        </row>
 
        <row>
         <entry> <literal>&lt;=</literal> </entry>
         <entry>less than or equal</entry>
-        <entry><literal>numrange(1.1,2.2) &lt;= numrange(1.1,2.2)</literal></entry>
+        <entry>
+          <literallayout class="monospaced">numrange(1.1,2.2) &lt;= numrange(1.1,2.2)
+'{[1.1,2.2)}'::nummultirange &lt;= '{[1.1,2.2)}'::nummultirange</literallayout>
+        </entry>
         <entry><literal>t</literal></entry>
        </row>
 
        <row>
         <entry> <literal>&gt;=</literal> </entry>
         <entry>greater than or equal</entry>
-        <entry><literal>numrange(1.1,2.2) &gt;= numrange(1.1,2.0)</literal></entry>
+        <entry>
+          <literallayout class="monospaced">numrange(1.1,2.2) &gt;= numrange(1.1,2.0)
+'{[1.1,2.2)}'::nummultirange &gt;= '{[1.1,2.0)}'::nummultirange</literallayout>
+        </entry>
+        <entry><literal>t</literal></entry>
+       </row>
+
+       <row>
+        <entry> <literal>@&gt;</literal> </entry>
+        <entry>contains multirange</entry>
+        <entry>
+          <literallayout class="monospaced">int4range(2,4) @&gt; '{[2,3)}'::int4multirange
+'{[2,4)}'::int4multirange @&gt; '{[2,3)}'::int4multirange</literallayout>
+        </entry>
         <entry><literal>t</literal></entry>
        </row>
 
        <row>
         <entry> <literal>@&gt;</literal> </entry>
         <entry>contains range</entry>
-        <entry><literal>int4range(2,4) @&gt; int4range(2,3)</literal></entry>
+        <entry>
+          <literallayout class="monospaced">int4range(2,4) @&gt; int4range(2,3)
+'{[2,4)}'::int4multirange @&gt; int4range(2,3)</literallayout>
+        </entry>
         <entry><literal>t</literal></entry>
        </row>
 
        <row>
         <entry> <literal>@&gt;</literal> </entry>
         <entry>contains element</entry>
-        <entry><literal>'[2011-01-01,2011-03-01)'::tsrange @&gt; '2011-01-10'::timestamp</literal></entry>
+        <entry>
+          <literallayout class="monospaced">'[2011-01-01,2011-03-01)'::tsrange @&gt; '2011-01-10'::timestamp
+'{[2011-01-01,2011-03-01)}'::tsmultirange @&gt; '2011-01-10'::timestamp</literallayout>
+        </entry>
+        <entry><literal>t</literal></entry>
+       </row>
+
+       <row>
+        <entry> <literal>&lt;@</literal> </entry>
+        <entry>multirange is contained by</entry>
+        <entry>
+          <literallayout class="monospaced">'{[2,4)}'::int4multirange &lt;@ int4range(1,7)
+'{[2,4)}'::int4multirange &lt;@ '{[1,7)}'::int4multirange</literallayout>
+        </entry>
         <entry><literal>t</literal></entry>
        </row>
 
        <row>
         <entry> <literal>&lt;@</literal> </entry>
         <entry>range is contained by</entry>
-        <entry><literal>int4range(2,4) &lt;@ int4range(1,7)</literal></entry>
+        <entry>
+          <literallayout class="monospaced">int4range(2,4) &lt;@ int4range(1,7)
+int4range(2,4) &lt;@ '{[1,7)}'::int4multirange</literallayout>
+        </entry>
         <entry><literal>t</literal></entry>
        </row>
 
        <row>
         <entry> <literal>&lt;@</literal> </entry>
         <entry>element is contained by</entry>
-        <entry><literal>42 &lt;@ int4range(1,7)</literal></entry>
+        <entry>
+          <literallayout class="monospaced">42 &lt;@ int4range(1,7)
+42 &lt;@ '{[1,7)}'::int4multirange</literallayout>
+        </entry>
         <entry><literal>f</literal></entry>
        </row>
 
        <row>
         <entry> <literal>&amp;&amp;</literal> </entry>
         <entry>overlap (have points in common)</entry>
-        <entry><literal>int8range(3,7) &amp;&amp; int8range(4,12)</literal></entry>
+        <entry>
+          <literallayout class="monospaced">int8range(3,7) &amp;&amp; int8range(4,12)
+int8range(3,7) &amp;&amp; '{[4,12)}'::int8multirange
+'{[3,7)}'::int8multirange &amp;&amp; int8range(4,12)
+'{[3,7)}'::int8multirange &amp;&amp; '{[4,12)}'::int8multirange</literallayout>
+        </entry>
         <entry><literal>t</literal></entry>
        </row>
 
        <row>
         <entry> <literal>&lt;&lt;</literal> </entry>
         <entry>strictly left of</entry>
-        <entry><literal>int8range(1,10) &lt;&lt; int8range(100,110)</literal></entry>
+        <entry>
+          <literallayout class="monospaced">int8range(1,10) &lt;&lt; int8range(100,110)
+int8range(1,10) &lt;&lt; '{[100,110)}'::int8multirange
+'{[1,10)}'::int8multirange &lt;&lt; int8range(100,110)
+'{[1,10)}'::int8multirange &lt;&lt; '{[100,110)}'::int8multirange</literallayout>
+        </entry>
         <entry><literal>t</literal></entry>
        </row>
 
        <row>
         <entry> <literal>&gt;&gt;</literal> </entry>
         <entry>strictly right of</entry>
-        <entry><literal>int8range(50,60) &gt;&gt; int8range(20,30)</literal></entry>
+        <entry>
+          <literallayout class="monospaced">int8range(50,60) &gt;&gt; int8range(20,30)
+int8range(50,60) &gt;&gt; '{[20,30)}'::int8multirange
+'{[50,60)}'::int8multirange &gt;&gt; int8range(20,30)
+'{[50,60)}'::int8multirange &gt;&gt; '{[20,30)}'::int8multirange</literallayout>
+        </entry>
         <entry><literal>t</literal></entry>
        </row>
 
        <row>
         <entry> <literal>&amp;&lt;</literal> </entry>
         <entry>does not extend to the right of</entry>
-        <entry><literal>int8range(1,20) &amp;&lt; int8range(18,20)</literal></entry>
+        <entry>
+          <literallayout class="monospaced">int8range(1,20) &amp;&lt; int8range(18,20)
+int8range(1,20) &amp;&lt; '{[18,20)}'::int8multirange
+'{[1,20)}'::int8multirange &amp;&lt; int8range(18,20)
+'{[1,20)}'::int8multirange &amp;&lt; '{[18,20)}'::int8multirange</literallayout>
+        </entry>
         <entry><literal>t</literal></entry>
        </row>
 
        <row>
         <entry> <literal>&amp;&gt;</literal> </entry>
         <entry>does not extend to the left of</entry>
-        <entry><literal>int8range(7,20) &amp;&gt; int8range(5,10)</literal></entry>
+        <entry>
+          <literallayout class="monospaced">int8range(7,20) &amp;&gt; int8range(5,10)
+int8range(7,20) &amp;&gt; '{[5,10)}'::int8multirange
+'{[7,20)}'::int8multirange &amp;&gt; int8range(5,10)
+'{[7,20)}'::int8multirange &amp;&gt; '{[5,10)}'::int8multirange</literallayout>
+        </entry>
         <entry><literal>t</literal></entry>
        </row>
 
        <row>
         <entry> <literal>-|-</literal> </entry>
         <entry>is adjacent to</entry>
-        <entry><literal>numrange(1.1,2.2) -|- numrange(2.2,3.3)</literal></entry>
+        <entry>
+          <literallayout class="monospaced">numrange(1.1,2.2) -|- numrange(2.2,3.3)
+numrange(1.1,2.2) -|- '{[2.2,3.3)}'::nummultirange
+'{[1.1,2.2)}'::nummultirange -|- numrange(2.2,3.3)
+'{[1.1,2.2)}'::nummultirange -|- '{[2.2,3.3)}'::nummultirange</literallayout>
+        </entry>
         <entry><literal>t</literal></entry>
        </row>
 
        <row>
         <entry> <literal>+</literal> </entry>
         <entry>union</entry>
-        <entry><literal>numrange(5,15) + numrange(10,20)</literal></entry>
+        <entry><literallayout class="monospaced">numrange(5,15) + numrange(10,20)</literallayout></entry>
         <entry><literal>[5,20)</literal></entry>
        </row>
 
        <row>
+        <entry> <literal>@+</literal> </entry>
+        <entry>safe union</entry>
+        <entry>
+          <literallayout class="monospaced">numrange(5,10) @+ numrange(15,20)
+numrange(5,10) @+ '{[15,20)}'::nummultirange
+'{[5,10)}'::nummultirange @+ numrange(15,20)
+'{[5,10)}'::nummultirange @+ '{[15,20)}'::nummultirange</literallayout>
+        </entry>
+        <entry><literal>{[5,10), [15,20)}</literal></entry>
+       </row>
+
+       <row>
         <entry> <literal>*</literal> </entry>
         <entry>intersection</entry>
-        <entry><literal>int8range(5,15) * int8range(10,20)</literal></entry>
+        <entry><literallayout class="monospaced">int8range(5,15) * int8range(10,20)</literallayout></entry>
         <entry><literal>[10,15)</literal></entry>
        </row>
 
        <row>
+        <entry> <literal>@*</literal> </entry>
+        <entry>safe intersection</entry>
+        <entry>
+          <literallayout class="monospaced">int8range(5,15) @* int8range(10,20)
+int8range(5,15) @* '{[10,20)}'::int8multirange
+'{[5,15)}'::int8multirange @* int8range(10,20)
+'{[5,15)}'::int8multirange @* '{[10,20)}'::int8multirange</literallayout>
+        </entry>
+        <entry><literal>{[10,15)}</literal></entry>
+       </row>
+
+       <row>
         <entry> <literal>-</literal> </entry>
         <entry>difference</entry>
-        <entry><literal>int8range(5,15) - int8range(10,20)</literal></entry>
+        <entry><literallayout class="monospaced">int8range(5,15) - int8range(10,20)</literallayout></entry>
         <entry><literal>[5,10)</literal></entry>
        </row>
 
+       <row>
+        <entry> <literal>@-</literal> </entry>
+        <entry>safe difference</entry>
+        <entry>
+          <literallayout class="monospaced">int8range(5,20) @- int8range(10,15)
+int8range(5,20) @- '{[10,15)}'::int8multirange
+'{[5,20)}'::int8multirange @- int8range(10,15)
+'{[5,20)}'::int8multirange @- '{[10,15)}'::int8multirange</literallayout>
+        </entry>
+        <entry><literal>{[5,10), [15,20)}</literal></entry>
+       </row>
+
       </tbody>
      </tgroup>
     </table>
@@ -14729,19 +14842,28 @@ NULL baz</literallayout>(3 rows)</entry>
 
   <para>
    The left-of/right-of/adjacent operators always return false when an empty
-   range is involved; that is, an empty range is not considered to be either
-   before or after any other range.
+   range or multirange is involved; that is, an empty range is not considered to
+   be either before or after any other range.
+  </para>
+
+  <para>
+   Elsewhere empty ranges and multiranges are treated as the additive identity:
+   anything unioned with an empty value is itself. Anything minus an empty
+   value is itself. An empty multirange has exactly the same points as an empty
+   range. Every range contains the empty range. Every multirange contains as many
+   empty ranges as you like.
   </para>
 
   <para>
    The union and difference operators will fail if the resulting range would
    need to contain two disjoint sub-ranges, as such a range cannot be
-   represented.
+   represented. The safe versions of union, intersection, and difference
+   return multiranges, so they can handle gaps without failing.
   </para>
 
   <para>
    <xref linkend="range-functions-table"/> shows the functions
-   available for use with range types.
+   available for use with range and multirange types.
   </para>
 
   <indexterm>
@@ -14793,6 +14915,17 @@ NULL baz</literallayout>(3 rows)</entry>
        <row>
         <entry>
          <literal>
+          <function>lower</function>(<type>anymultirange</type>)
+         </literal>
+        </entry>
+        <entry>multirange's element type</entry>
+        <entry>lower bound of multirange</entry>
+        <entry><literal>lower('{[1.1,2.2)}'::nummultirange)</literal></entry>
+        <entry><literal>1.1</literal></entry>
+       </row>
+       <row>
+        <entry>
+         <literal>
           <function>upper</function>(<type>anyrange</type>)
          </literal>
         </entry>
@@ -14804,6 +14937,17 @@ NULL baz</literallayout>(3 rows)</entry>
        <row>
         <entry>
          <literal>
+          <function>upper</function>(<type>anymultirange</type>)
+         </literal>
+        </entry>
+        <entry>multirange's element type</entry>
+        <entry>upper bound of multirange</entry>
+        <entry><literal>upper('{[1.1,2.2)}'::nummultirange)</literal></entry>
+        <entry><literal>2.2</literal></entry>
+       </row>
+       <row>
+        <entry>
+         <literal>
           <function>isempty</function>(<type>anyrange</type>)
          </literal>
         </entry>
@@ -14815,6 +14959,17 @@ NULL baz</literallayout>(3 rows)</entry>
        <row>
         <entry>
          <literal>
+          <function>isempty</function>(<type>anymultirange</type>)
+         </literal>
+        </entry>
+        <entry><type>boolean</type></entry>
+        <entry>is the multirange empty?</entry>
+        <entry><literal>isempty('{[1.1,2.2)}'::nummultirange)</literal></entry>
+        <entry><literal>false</literal></entry>
+       </row>
+       <row>
+        <entry>
+         <literal>
           <function>lower_inc</function>(<type>anyrange</type>)
          </literal>
         </entry>
@@ -14826,6 +14981,17 @@ NULL baz</literallayout>(3 rows)</entry>
        <row>
         <entry>
          <literal>
+          <function>lower_inc</function>(<type>anymultirange</type>)
+         </literal>
+        </entry>
+        <entry><type>boolean</type></entry>
+        <entry>is the lower bound inclusive?</entry>
+        <entry><literal>lower_inc('{[1.1,2.2)}'::nummultirange)</literal></entry>
+        <entry><literal>true</literal></entry>
+       </row>
+       <row>
+        <entry>
+         <literal>
           <function>upper_inc</function>(<type>anyrange</type>)
          </literal>
         </entry>
@@ -14837,6 +15003,17 @@ NULL baz</literallayout>(3 rows)</entry>
        <row>
         <entry>
          <literal>
+          <function>upper_inc</function>(<type>anymultirange</type>)
+         </literal>
+        </entry>
+        <entry><type>boolean</type></entry>
+        <entry>is the upper bound inclusive?</entry>
+        <entry><literal>upper_inc('{[1.1,2.2)}'::nummultirange)</literal></entry>
+        <entry><literal>false</literal></entry>
+       </row>
+       <row>
+        <entry>
+         <literal>
           <function>lower_inf</function>(<type>anyrange</type>)
          </literal>
         </entry>
@@ -14848,6 +15025,17 @@ NULL baz</literallayout>(3 rows)</entry>
        <row>
         <entry>
          <literal>
+          <function>lower_inf</function>(<type>anymultirange</type>)
+         </literal>
+        </entry>
+        <entry><type>boolean</type></entry>
+        <entry>is the lower bound infinite?</entry>
+        <entry><literal>lower_inf('{(,)}'::datemultirange)</literal></entry>
+        <entry><literal>true</literal></entry>
+       </row>
+       <row>
+        <entry>
+         <literal>
           <function>upper_inf</function>(<type>anyrange</type>)
          </literal>
         </entry>
@@ -14859,6 +15047,17 @@ NULL baz</literallayout>(3 rows)</entry>
        <row>
         <entry>
          <literal>
+          <function>upper_inf</function>(<type>anymultirange</type>)
+         </literal>
+        </entry>
+        <entry><type>boolean</type></entry>
+        <entry>is the upper bound infinite?</entry>
+        <entry><literal>upper_inf('{(,)}'::datemultirange)</literal></entry>
+        <entry><literal>true</literal></entry>
+       </row>
+       <row>
+        <entry>
+         <literal>
           <function>range_merge</function>(<type>anyrange</type>, <type>anyrange</type>)
          </literal>
         </entry>
@@ -14867,16 +15066,27 @@ NULL baz</literallayout>(3 rows)</entry>
         <entry><literal>range_merge('[1,2)'::int4range, '[3,4)'::int4range)</literal></entry>
         <entry><literal>[1,4)</literal></entry>
        </row>
+       <row>
+        <entry>
+         <literal>
+          <function>range_merge</function>(<type>anymultirange</type>)
+         </literal>
+        </entry>
+        <entry><type>anyrange</type></entry>
+        <entry>the smallest range which includes the entire multirange</entry>
+        <entry><literal>range_merge('{[1,2), [3,4)}'::int4multirange)</literal></entry>
+        <entry><literal>[1,4)</literal></entry>
+       </row>
       </tbody>
      </tgroup>
     </table>
 
   <para>
    The <function>lower</function> and  <function>upper</function> functions return null
-   if the range is empty or the requested bound is infinite.
+   if the input range/multirange is empty or the requested bound is infinite.
    The <function>lower_inc</function>, <function>upper_inc</function>,
    <function>lower_inf</function>, and <function>upper_inf</function>
-   functions all return false for an empty range.
+   functions all return false for an empty range or multirange.
   </para>
   </sect1>
 
@@ -15198,6 +15408,44 @@ NULL baz</literallayout>(3 rows)</entry>
      <row>
       <entry>
        <indexterm>
+        <primary>range_agg</primary>
+       </indexterm>
+       <function>
+         range_agg(<replaceable class="parameter">expression</replaceable>)
+       </function>
+      </entry>
+      <entry>
+       <type>anyrange</type>
+      </entry>
+      <entry>
+       <type>multirange</type> of the argument range type
+      </entry>
+      <entry>Yes</entry>
+      <entry>union of the non-null input values</entry>
+     </row>
+
+     <row>
+      <entry>
+       <indexterm>
+        <primary>range_intersect_agg</primary>
+       </indexterm>
+       <function>
+         range_intersect_agg(<replaceable class="parameter">expression</replaceable>)
+       </function>
+      </entry>
+      <entry>
+       <type>anyrange</type>
+      </entry>
+      <entry>
+       <type>multirange</type> of the argument range type
+      </entry>
+      <entry>No</entry>
+      <entry>intersection of the non-null input values</entry>
+     </row>
+
+     <row>
+      <entry>
+       <indexterm>
         <primary>string_agg</primary>
        </indexterm>
        <function>
diff --git a/doc/src/sgml/rangetypes.sgml b/doc/src/sgml/rangetypes.sgml
index 3a034d9b06..3e37b352fc 100644
--- a/doc/src/sgml/rangetypes.sgml
+++ b/doc/src/sgml/rangetypes.sgml
@@ -27,6 +27,13 @@
   ranges from an instrument, and so forth can also be useful.
  </para>
 
+ <para>
+  Every range type has a corresponding multirange type. A multirange is
+  an ordered list of non-continguous, non-empty, non-null ranges. Most
+  range operators also work on multiranges, and they have a few functions
+  of their own.
+ </para>
+
  <sect2 id="rangetypes-builtin">
   <title>Built-in Range Types</title>
 
@@ -235,10 +242,30 @@ SELECT '[4,4]'::int4range;
 SELECT '[4,4)'::int4range;
 </programlisting>
   </para>
+
+  <para>
+   The input for a multirange is curly brackets (<literal>{</literal> and
+   <literal>}</literal>) containing zero or more valid ranges,
+   separated by commas. Whitespace is permitted around the brackets and
+   commas. This is intended to be reminiscent of array syntax, although
+   multiranges are much simpler: they have just one dimension and there is
+   no need to quote their contents. (The bounds of their ranges may be
+   quoted as above however.)
+  </para>
+
+  <para>
+  Examples:
+<programlisting>
+SELECT '{}'::int4multirange;
+SELECT '{[3,7)}'::int4multirange;
+SELECT '{[3,7), [8,9)}'::int4multirange;
+</programlisting>
+  </para>
+
  </sect2>
 
  <sect2 id="rangetypes-construct">
-  <title>Constructing Ranges</title>
+  <title>Constructing Ranges and Multiranges</title>
 
   <para>
    Each range type has a constructor function with the same name as the range
@@ -272,6 +299,19 @@ SELECT int8range(1, 14, '(]');
 SELECT numrange(NULL, 2.2);
 </programlisting>
   </para>
+
+  <para>
+   Each range type also has a multirange constructor with the same name as the
+   multirange type.  The constructor function takes zero or more arguments
+   which are all ranges of the appropriate type.
+   For example:
+
+<programlisting>
+SELECT nummultirange();
+SELECT nummultirange(numrange(1.0, 14.0));
+SELECT nummultirange(numrange(1.0, 14.0), numrange(20.0, 25.0));
+</programlisting>
+  </para>
  </sect2>
 
  <sect2 id="rangetypes-discrete">
@@ -345,6 +385,11 @@ SELECT '[1.234, 5.678]'::floatrange;
   </para>
 
   <para>
+   When you define your own range your automatically get a corresponding
+   multirange type.
+  </para>
+
+  <para>
    Defining your own range type also allows you to specify a different
    subtype B-tree operator class or collation to use, so as to change the sort
    ordering that determines which values fall into a given range.
-- 
2.11.0

v6-0002-multirange-operators-and-functions.patchapplication/octet-stream; name=v6-0002-multirange-operators-and-functions.patchDownload
From 10ee59a513c2982423f5163931e9d9e63d5028d3 Mon Sep 17 00:00:00 2001
From: "Paul A. Jungwirth" <pj@illuminatedcomputing.com>
Date: Sat, 21 Sep 2019 21:28:20 -0700
Subject: [PATCH v6 2/4] multirange operators and functions

---
 src/backend/catalog/pg_type.c                 |    2 -
 src/backend/commands/typecmds.c               |    6 +-
 src/backend/parser/parse_coerce.c             |    2 +
 src/backend/utils/adt/multirangetypes.c       | 1575 +++++++++++++++-
 src/backend/utils/adt/rangetypes.c            |  236 ++-
 src/backend/utils/fmgr/funcapi.c              |   97 +-
 src/include/catalog/pg_aggregate.dat          |   11 +
 src/include/catalog/pg_amproc.dat             |    5 +-
 src/include/catalog/pg_operator.dat           |  169 ++
 src/include/catalog/pg_proc.dat               |  185 ++
 src/include/utils/multirangetypes.h           |   34 +
 src/include/utils/rangetypes.h                |   31 +-
 src/test/regress/expected/dependency.out      |    1 +
 src/test/regress/expected/multirangetypes.out | 2397 +++++++++++++++++++++++++
 src/test/regress/expected/opr_sanity.out      |    4 +-
 src/test/regress/expected/rangetypes.out      |   38 +-
 src/test/regress/expected/sanity_check.out    |    2 +
 src/test/regress/sql/multirangetypes.sql      |  607 +++++++
 src/test/regress/sql/rangetypes.sql           |   13 +
 19 files changed, 5188 insertions(+), 227 deletions(-)

diff --git a/src/backend/catalog/pg_type.c b/src/backend/catalog/pg_type.c
index 5d962ed302..de0bb24649 100644
--- a/src/backend/catalog/pg_type.c
+++ b/src/backend/catalog/pg_type.c
@@ -876,7 +876,6 @@ 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;
@@ -892,7 +891,6 @@ makeMultirangeTypeName(const char *rangeTypeName, Oid typeNamespace)
 	if (rangestr)
 	{
 		rangeoffset = rangestr - rangeTypeName;
-		rangelen = strlen(rangestr);
 		strlcpy(mltrng + rangeoffset, "multi", NAMEDATALEN - rangeoffset);
 		strlcpy(mltrng + rangeoffset + 5, rangestr, NAMEDATALEN - rangeoffset - 5);
 		namelen += 5;
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index 14a6857062..7a440cafd1 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -1783,13 +1783,11 @@ makeMultirangeConstructors(const char *name, Oid namespace,
 	"multirange_constructor1"};
 	static const int pronargs[2] = {0, 1};
 
-	Oid			constructorArgTypes[0];
+	Oid			constructorArgTypes = rangeArrayOid;
 	ObjectAddress myself,
 				referenced;
 	int			i;
 
-	constructorArgTypes[0] = rangeArrayOid;
-
 	Datum		allParamTypes[1] = {ObjectIdGetDatum(rangeArrayOid)};
 	ArrayType  *allParameterTypes = construct_array(allParamTypes, 1, OIDOID,
 													sizeof(Oid), true, 'i');
@@ -1808,7 +1806,7 @@ makeMultirangeConstructors(const char *name, Oid namespace,
 	{
 		oidvector  *constructorArgTypesVector;
 
-		constructorArgTypesVector = buildoidvector(constructorArgTypes,
+		constructorArgTypesVector = buildoidvector(&constructorArgTypes,
 												   pronargs[i]);
 
 		myself = ProcedureCreate(name,	/* name: same as multirange type */
diff --git a/src/backend/parser/parse_coerce.c b/src/backend/parser/parse_coerce.c
index 4c7a5f7fbe..49c75dc12d 100644
--- a/src/backend/parser/parse_coerce.c
+++ b/src/backend/parser/parse_coerce.c
@@ -1918,6 +1918,8 @@ enforce_generic_type_consistency(const Oid *actual_arg_types,
 							   format_type_be(range_typeid),
 							   format_type_be(elem_typeid))));
 		}
+		else
+			range_typelem = InvalidOid;
 	}
 
 	/* Get the range type based on the multirange type, if we have one */
diff --git a/src/backend/utils/adt/multirangetypes.c b/src/backend/utils/adt/multirangetypes.c
index 5323a6a635..7717acb3b1 100644
--- a/src/backend/utils/adt/multirangetypes.c
+++ b/src/backend/utils/adt/multirangetypes.c
@@ -91,7 +91,6 @@ multirange_in(PG_FUNCTION_ARGS)
 	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;
@@ -101,13 +100,12 @@ multirange_in(PG_FUNCTION_ARGS)
 	MultirangeType *ret;
 	MultirangeParseState parse_state;
 	const char *ptr = input_str;
-	const char *range_str;
+	const char *range_str = NULL;
 	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))
@@ -351,9 +349,12 @@ multirange_send(PG_FUNCTION_ARGS)
 	for (i = 0; i < range_count; i++)
 	{
 		Datum		range = RangeTypePGetDatum(ranges[i]);
+		uint32		range_len;
+		char	   *range_data;
+
 		range = PointerGetDatum(SendFunctionCall(&cache->proc, range));
-		uint32		range_len = VARSIZE(range) - VARHDRSZ;
-		char	   *range_data = VARDATA(range);
+		range_len = VARSIZE(range) - VARHDRSZ;
+		range_data = VARDATA(range);
 
 		pq_sendint32(buf, range_len);
 		pq_sendbytes(buf, range_data, range_len);
@@ -726,169 +727,1547 @@ multirange_constructor0(PG_FUNCTION_ARGS)
 }
 
 
-/* multirange, multirange -> bool functions */
+/* multirange, multirange -> multirange type functions */
 
-/* equality (internal version) */
-bool
-multirange_eq_internal(TypeCacheEntry *typcache, MultirangeType * mr1, MultirangeType * mr2)
+/* multirange union */
+Datum
+range_union_range(PG_FUNCTION_ARGS)
 {
-	int32		range_count_1;
-	int32		range_count_2;
-	int32		i;
-	RangeType **ranges1;
-	RangeType **ranges2;
-	RangeType  *r1;
-	RangeType  *r2;
+	RangeType  *r1 = PG_GETARG_RANGE_P(0);
+	RangeType  *r2 = PG_GETARG_RANGE_P(1);
+	Oid			mltrngtypoid = get_fn_expr_rettype(fcinfo->flinfo);
+	TypeCacheEntry *typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
+	MultirangeType *mr = make_multirange(mltrngtypoid, typcache->rngtype, 1, &r2);
 
-	/* Different types should be prevented by ANYMULTIRANGE matching rules */
-	if (MultirangeTypeGetOid(mr1) != MultirangeTypeGetOid(mr2))
-		elog(ERROR, "multirange types do not match");
+	PG_RETURN_MULTIRANGE_P(range_union_multirange_internal(typcache, r1, mr));
+}
 
-	multirange_deserialize(mr1, &range_count_1, &ranges1);
-	multirange_deserialize(mr2, &range_count_2, &ranges2);
+Datum
+range_union_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
 
-	if (range_count_1 != range_count_2)
-		return false;
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
 
-	for (i = 0; i < range_count_1; i++)
-	{
-		r1 = ranges1[i];
-		r2 = ranges2[i];
+	PG_RETURN_MULTIRANGE_P(range_union_multirange_internal(typcache, r, mr));
+}
 
-		if (!range_eq_internal(typcache->rngtype, r1, r2))
-			return false;
-	}
+Datum
+multirange_union_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
 
-	return true;
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_MULTIRANGE_P(range_union_multirange_internal(typcache, r, mr));
 }
 
-/* equality */
 Datum
-multirange_eq(PG_FUNCTION_ARGS)
+multirange_union_multirange(PG_FUNCTION_ARGS)
 {
 	MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
 	MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
 	TypeCacheEntry *typcache;
+	int32		range_count1;
+	int32		range_count2;
+	int32		range_count3;
+	RangeType **ranges1;
+	RangeType **ranges2;
+	RangeType **ranges3;
+
+	if (MultirangeIsEmpty(mr1))
+		PG_RETURN_MULTIRANGE_P(mr2);
+	if (MultirangeIsEmpty(mr2))
+		PG_RETURN_MULTIRANGE_P(mr1);
 
 	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr1));
 
-	PG_RETURN_BOOL(multirange_eq_internal(typcache, mr1, mr2));
+	multirange_deserialize(mr1, &range_count1, &ranges1);
+	multirange_deserialize(mr2, &range_count2, &ranges2);
+
+	range_count3 = range_count1 + range_count2;
+	ranges3 = palloc0(range_count3 * sizeof(RangeType *));
+	memcpy(ranges3, ranges1, range_count1 * sizeof(RangeType *));
+	memcpy(ranges3 + range_count1, ranges2, range_count2 * sizeof(RangeType *));
+	PG_RETURN_MULTIRANGE_P(make_multirange(typcache->type_id, typcache->rngtype,
+										   range_count3, ranges3));
 }
 
-/* inequality (internal version) */
-bool
-multirange_ne_internal(TypeCacheEntry *typcache, MultirangeType * mr1, MultirangeType * mr2)
+MultirangeType *
+range_union_multirange_internal(TypeCacheEntry *typcache, RangeType *r,
+								MultirangeType * mr)
 {
-	return (!multirange_eq_internal(typcache, mr1, mr2));
+	int32		range_count;
+	RangeType **ranges1;
+	RangeType **ranges2;
+
+	if (RangeIsEmpty(r))
+		return mr;
+	if (MultirangeIsEmpty(mr))
+		return make_multirange(typcache->type_id, typcache->rngtype, 1, &r);
+
+	multirange_deserialize(mr, &range_count, &ranges1);
+
+	ranges2 = palloc0((range_count + 1) * sizeof(RangeType *));
+
+	memcpy(ranges2, ranges1, range_count * sizeof(RangeType *));
+	ranges2[range_count] = r;
+	return make_multirange(typcache->type_id, typcache->rngtype, range_count + 1, ranges2);
 }
 
-/* inequality */
+/* multirange minus */
 Datum
-multirange_ne(PG_FUNCTION_ARGS)
+range_minus_range(PG_FUNCTION_ARGS)
 {
-	MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
-	MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
+	RangeType  *r1 = PG_GETARG_RANGE_P(0);
+	RangeType  *r2 = PG_GETARG_RANGE_P(1);
+	Oid			mltrngtypoid = get_fn_expr_rettype(fcinfo->flinfo);
 	TypeCacheEntry *typcache;
+	TypeCacheEntry *rangetyp;
 
-	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr1));
+	typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
+	rangetyp = typcache->rngtype;
 
-	PG_RETURN_BOOL(multirange_ne_internal(typcache, mr1, mr2));
+	if (RangeIsEmpty(r1) || RangeIsEmpty(r2))
+		PG_RETURN_MULTIRANGE_P(make_multirange(mltrngtypoid, rangetyp, 1, &r1));
+
+	PG_RETURN_MULTIRANGE_P(multirange_minus_multirange_internal(mltrngtypoid,
+																rangetyp,
+																1,
+																&r1,
+																1,
+																&r2));
 }
 
-/* Btree support */
+Datum
+range_minus_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	Oid			mltrngtypoid = MultirangeTypeGetOid(mr);
+	TypeCacheEntry *typcache;
+	TypeCacheEntry *rangetyp;
+	int32		range_count;
+	RangeType **ranges;
+
+	typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
+	rangetyp = typcache->rngtype;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		PG_RETURN_MULTIRANGE_P(make_multirange(mltrngtypoid, rangetyp, 1, &r));
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_MULTIRANGE_P(multirange_minus_multirange_internal(mltrngtypoid,
+																rangetyp,
+																1,
+																&r,
+																range_count,
+																ranges));
+}
 
-/* btree comparator */
 Datum
-multirange_cmp(PG_FUNCTION_ARGS)
+multirange_minus_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	Oid			mltrngtypoid = MultirangeTypeGetOid(mr);
+	TypeCacheEntry *typcache;
+	TypeCacheEntry *rangetyp;
+	int32		range_count;
+	RangeType **ranges;
+
+	typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
+	rangetyp = typcache->rngtype;
+
+	if (MultirangeIsEmpty(mr) || RangeIsEmpty(r))
+		PG_RETURN_MULTIRANGE_P(mr);
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_MULTIRANGE_P(multirange_minus_multirange_internal(mltrngtypoid,
+																rangetyp,
+																range_count,
+																ranges,
+																1,
+																&r));
+}
+
+Datum
+multirange_minus_multirange(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;
+	Oid			mltrngtypoid = MultirangeTypeGetOid(mr1);
+	TypeCacheEntry *typcache;
+	TypeCacheEntry *rangetyp;
+	int32		range_count1;
+	int32		range_count2;
 	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, mltrngtypoid);
+	rangetyp = typcache->rngtype;
 
-	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr1));
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		PG_RETURN_MULTIRANGE_P(mr1);
 
-	multirange_deserialize(mr1, &range_count_1, &ranges1);
-	multirange_deserialize(mr2, &range_count_2, &ranges2);
+	multirange_deserialize(mr1, &range_count1, &ranges1);
+	multirange_deserialize(mr2, &range_count2, &ranges2);
 
-	/* Loop over source data */
-	range_count_max = Max(range_count_1, range_count_2);
-	for (i = 0; i < range_count_max; i++)
+	PG_RETURN_MULTIRANGE_P(multirange_minus_multirange_internal(mltrngtypoid,
+																rangetyp,
+																range_count1,
+																ranges1,
+																range_count2,
+																ranges2));
+}
+
+MultirangeType *
+multirange_minus_multirange_internal(Oid mltrngtypoid, TypeCacheEntry *rangetyp,
+									 int32 range_count1, RangeType **ranges1, int32 range_count2, RangeType **ranges2)
+{
+	RangeType  *r1;
+	RangeType  *r2;
+	RangeType **ranges3;
+	int32		range_count3;
+	int32		i1;
+	int32		i2;
+
+	/*
+	 * Worst case: every range in ranges1 makes a different cut to some range
+	 * in ranges2.
+	 */
+	ranges3 = palloc0((range_count1 + range_count2) * sizeof(RangeType *));
+	range_count3 = 0;
+
+	/*
+	 * For each range in mr1, keep subtracting until it's gone or the ranges
+	 * in mr2 have passed it. After a subtraction we assign what's left back
+	 * to r1. The parallel progress through mr1 and mr2 is similar to
+	 * multirange_overlaps_multirange_internal.
+	 */
+	r2 = ranges2[0];
+	for (i1 = 0, i2 = 0; i1 < range_count1; i1++)
 	{
-		/*
-		 * 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)
+		r1 = ranges1[i1];
+
+		/* Discard r2s while r2 << r1 */
+		while (r2 != NULL && range_before_internal(rangetyp, r2, r1))
 		{
-			cmp = -1;
-			break;
+			r2 = ++i2 >= range_count2 ? NULL : ranges2[i2];
 		}
-		if (i >= range_count_2)
+
+		while (r2 != NULL)
 		{
-			cmp = 1;
-			break;
+			if (range_split_internal(rangetyp, r1, r2, &ranges3[range_count3], &r1))
+			{
+				/*
+				 * If r2 takes a bite out of the middle of r1, we need two
+				 * outputs
+				 */
+				range_count3++;
+				r2 = ++i2 >= range_count2 ? NULL : ranges2[i2];
+
+			}
+			else if (range_overlaps_internal(rangetyp, r1, r2))
+			{
+				/*
+				 * If r2 overlaps r1, replace r1 with r1 - r2.
+				 */
+				r1 = range_minus_internal(rangetyp, r1, r2);
+
+				/*
+				 * If r2 goes past r1, then we need to stay with it, in case
+				 * it hits future r1s. Otherwise we need to keep r1, in case
+				 * future r2s hit it. Since we already subtracted, there's no
+				 * point in using the overright/overleft calls.
+				 */
+				if (RangeIsEmpty(r1) || range_before_internal(rangetyp, r1, r2))
+					break;
+				else
+					r2 = ++i2 >= range_count2 ? NULL : ranges2[i2];
+
+			}
+			else
+			{
+				/*
+				 * This and all future r2s are past r1, so keep them. Also
+				 * assign whatever is left of r1 to the result.
+				 */
+				break;
+			}
 		}
-		r1 = ranges1[i];
-		r2 = ranges2[i];
 
-		cmp = range_cmp_internal(typcache->rngtype, r1, r2);
-		if (cmp != 0)
-			break;
+		/*
+		 * Nothing else can remove anything from r1, so keep it. Even if r1 is
+		 * empty here, make_multirange will remove it.
+		 */
+		ranges3[range_count3++] = r1;
 	}
 
-	PG_FREE_IF_COPY(mr1, 0);
-	PG_FREE_IF_COPY(mr2, 1);
+	return make_multirange(mltrngtypoid, rangetyp, range_count3, ranges3);
+}
 
-	PG_RETURN_INT32(cmp);
+/* multirange intersection */
+Datum
+range_intersect_range(PG_FUNCTION_ARGS)
+{
+	RangeType  *r1 = PG_GETARG_RANGE_P(0);
+	RangeType  *r2 = PG_GETARG_RANGE_P(1);
+	Oid			mltrngtypoid = get_fn_expr_rettype(fcinfo->flinfo);
+	TypeCacheEntry *typcache;
+	TypeCacheEntry *rangetyp;
+
+	typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
+	rangetyp = typcache->rngtype;
+
+	if (RangeIsEmpty(r1) || RangeIsEmpty(r2))
+		PG_RETURN_MULTIRANGE_P(make_empty_multirange(mltrngtypoid, rangetyp));
+
+	PG_RETURN_MULTIRANGE_P(multirange_intersect_multirange_internal(mltrngtypoid,
+																	rangetyp,
+																	1,
+																	&r1,
+																	1,
+																	&r2));
 }
 
-/* inequality operators using the multirange_cmp function */
 Datum
-multirange_lt(PG_FUNCTION_ARGS)
+range_intersect_multirange(PG_FUNCTION_ARGS)
 {
-	int			cmp = multirange_cmp(fcinfo);
+	RangeType  *r1 = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
+	Oid			mltrngtypoid = MultirangeTypeGetOid(mr2);
+	TypeCacheEntry *typcache;
+	TypeCacheEntry *rangetyp;
+	int32		range_count2;
+	RangeType **ranges2;
 
-	PG_RETURN_BOOL(cmp < 0);
+	typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
+	rangetyp = typcache->rngtype;
+
+	if (RangeIsEmpty(r1) || MultirangeIsEmpty(mr2))
+		PG_RETURN_MULTIRANGE_P(make_empty_multirange(mltrngtypoid, rangetyp));
+
+	multirange_deserialize(mr2, &range_count2, &ranges2);
+
+	PG_RETURN_MULTIRANGE_P(multirange_intersect_multirange_internal(mltrngtypoid,
+																	rangetyp,
+																	1,
+																	&r1,
+																	range_count2,
+																	ranges2));
 }
 
 Datum
-multirange_le(PG_FUNCTION_ARGS)
+multirange_intersect_range(PG_FUNCTION_ARGS)
 {
-	int			cmp = multirange_cmp(fcinfo);
+	MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r2 = PG_GETARG_RANGE_P(1);
+	Oid			mltrngtypoid = MultirangeTypeGetOid(mr1);
+	TypeCacheEntry *typcache;
+	TypeCacheEntry *rangetyp;
+	int32		range_count1;
+	RangeType **ranges1;
 
-	PG_RETURN_BOOL(cmp <= 0);
+	typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
+	rangetyp = typcache->rngtype;
+
+	if (MultirangeIsEmpty(mr1) || RangeIsEmpty(r2))
+		PG_RETURN_MULTIRANGE_P(make_empty_multirange(mltrngtypoid, rangetyp));
+
+	multirange_deserialize(mr1, &range_count1, &ranges1);
+
+	PG_RETURN_MULTIRANGE_P(multirange_intersect_multirange_internal(mltrngtypoid,
+																	rangetyp,
+																	range_count1,
+																	ranges1,
+																	1,
+																	&r2));
 }
 
 Datum
-multirange_ge(PG_FUNCTION_ARGS)
+multirange_intersect_multirange(PG_FUNCTION_ARGS)
 {
-	int			cmp = multirange_cmp(fcinfo);
+	MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
+	MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
+	Oid			mltrngtypoid = MultirangeTypeGetOid(mr1);
+	TypeCacheEntry *typcache;
+	TypeCacheEntry *rangetyp;
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
 
-	PG_RETURN_BOOL(cmp >= 0);
+	typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
+	rangetyp = typcache->rngtype;
+
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		PG_RETURN_MULTIRANGE_P(make_empty_multirange(mltrngtypoid, rangetyp));
+
+	multirange_deserialize(mr1, &range_count1, &ranges1);
+	multirange_deserialize(mr2, &range_count2, &ranges2);
+
+	PG_RETURN_MULTIRANGE_P(multirange_intersect_multirange_internal(mltrngtypoid,
+																	rangetyp,
+																	range_count1,
+																	ranges1,
+																	range_count2,
+																	ranges2));
+}
+
+MultirangeType *
+multirange_intersect_multirange_internal(Oid mltrngtypoid, TypeCacheEntry *rangetyp,
+										 int32 range_count1, RangeType **ranges1,
+										 int32 range_count2, RangeType **ranges2)
+{
+	RangeType  *r1;
+	RangeType  *r2;
+	RangeType **ranges3;
+	int32		range_count3;
+	int32		i1;
+	int32		i2;
+
+	if (range_count1 == 0 || range_count2 == 0)
+		return make_multirange(mltrngtypoid, rangetyp, 0, NULL);
+
+	/*
+	 * Worst case is a stitching pattern like this:
+	 *
+	 * mr1: --- --- --- ---
+	 * mr2:   --- --- ---
+	 * mr3:   - - - - - -
+	 *
+	 * That seems to be range_count1 + range_count2 - 1, but one extra won't
+	 * hurt.
+	 */
+	ranges3 = palloc0((range_count1 + range_count2) * sizeof(RangeType *));
+	range_count3 = 0;
+
+	/*
+	 * For each range in mr1, keep intersecting until the ranges in mr2 have
+	 * passed it. The parallel progress through mr1 and mr2 is similar to
+	 * multirange_minus_multirange_internal, but we don't have to assign back
+	 * to r1.
+	 */
+	r2 = ranges2[0];
+	for (i1 = 0, i2 = 0; i1 < range_count1; i1++)
+	{
+		r1 = ranges1[i1];
+
+		/* Discard r2s while r2 << r1 */
+		while (r2 != NULL && range_before_internal(rangetyp, r2, r1))
+		{
+			r2 = ++i2 >= range_count2 ? NULL : ranges2[i2];
+		}
+
+		while (r2 != NULL)
+		{
+			if (range_overlaps_internal(rangetyp, r1, r2))
+			{
+				/* Keep the overlapping part */
+				ranges3[range_count3++] = range_intersect_internal(rangetyp, r1, r2);
+
+				/* If we "used up" all of r2, go to the next one... */
+				if (range_overleft_internal(rangetyp, r2, r1))
+					r2 = ++i2 >= range_count2 ? NULL : ranges2[i2];
+
+				/* ...otherwise go to the next r1 */
+				else
+					break;
+			}
+			else
+				/* We're past r1, so move to the next one */
+				break;
+		}
+
+		/* If we're out of r2s, there can be no more intersections */
+		if (r2 == NULL)
+			break;
+	}
+
+	return make_multirange(mltrngtypoid, rangetyp, range_count3, ranges3);
 }
 
+/*
+ * range_agg_transfn: combine adjacent/overlapping ranges.
+ *
+ * All we do here is gather the input ranges into an array
+ * so that the finalfn can sort and combine them.
+ */
 Datum
-multirange_gt(PG_FUNCTION_ARGS)
+range_agg_transfn(PG_FUNCTION_ARGS)
 {
-	int			cmp = multirange_cmp(fcinfo);
+	MemoryContext aggContext;
+	Oid			rngtypoid;
+	ArrayBuildState *state;
 
-	PG_RETURN_BOOL(cmp > 0);
+	if (!AggCheckCallContext(fcinfo, &aggContext))
+		elog(ERROR, "range_agg_transfn called in non-aggregate context");
+
+	rngtypoid = get_fn_expr_argtype(fcinfo->flinfo, 1);
+	if (!type_is_range(rngtypoid))
+		ereport(ERROR, (errmsg("range_agg must be called with a range")));
+
+	if (PG_ARGISNULL(0))
+		state = initArrayResult(rngtypoid, aggContext, false);
+	else
+		state = (ArrayBuildState *) PG_GETARG_POINTER(0);
+
+	/* skip NULLs */
+	if (!PG_ARGISNULL(1))
+		accumArrayResult(state, PG_GETARG_DATUM(1), false, rngtypoid, aggContext);
+
+	PG_RETURN_POINTER(state);
+}
+
+/*
+ * range_agg_finalfn: use our internal array to merge touching ranges.
+ */
+Datum
+range_agg_finalfn(PG_FUNCTION_ARGS)
+{
+	MemoryContext aggContext;
+	Oid			mltrngtypoid;
+	TypeCacheEntry *typcache;
+	ArrayBuildState *state;
+	int32		range_count;
+	RangeType **ranges;
+	int			i;
+
+	if (!AggCheckCallContext(fcinfo, &aggContext))
+		elog(ERROR, "range_agg_finalfn called in non-aggregate context");
+
+	state = PG_ARGISNULL(0) ? NULL : (ArrayBuildState *) PG_GETARG_POINTER(0);
+	if (state == NULL)
+		/* This shouldn't be possible, but just in case.... */
+		PG_RETURN_NULL();
+
+	/* Also return NULL if we had zero inputs, like other aggregates */
+	range_count = state->nelems;
+	if (range_count == 0)
+		PG_RETURN_NULL();
+
+	mltrngtypoid = get_fn_expr_rettype(fcinfo->flinfo);
+	typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
+
+	ranges = palloc0(range_count * sizeof(RangeType *));
+	for (i = 0; i < range_count; i++)
+		ranges[i] = DatumGetRangeTypeP(state->dvalues[i]);
+
+	PG_RETURN_MULTIRANGE_P(make_multirange(mltrngtypoid, typcache->rngtype, range_count, ranges));
+}
+
+Datum
+multirange_intersect_agg_transfn(PG_FUNCTION_ARGS)
+{
+	MemoryContext aggContext;
+	Oid			mltrngtypoid;
+	TypeCacheEntry *typcache;
+	MultirangeType *result;
+	MultirangeType *current;
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+
+	if (!AggCheckCallContext(fcinfo, &aggContext))
+		elog(ERROR, "multirange_intersect_agg_transfn called in non-aggregate context");
+
+	mltrngtypoid = get_fn_expr_argtype(fcinfo->flinfo, 1);
+	if (!type_is_multirange(mltrngtypoid))
+		ereport(ERROR, (errmsg("range_intersect_agg must be called with a multirange")));
+
+	typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
+
+	/* strictness ensures these are non-null */
+	result = PG_GETARG_MULTIRANGE_P(0);
+	current = PG_GETARG_MULTIRANGE_P(1);
+
+	multirange_deserialize(result, &range_count1, &ranges1);
+	multirange_deserialize(current, &range_count2, &ranges2);
+
+	result = multirange_intersect_multirange_internal(mltrngtypoid,
+													  typcache->rngtype,
+													  range_count1,
+													  ranges1,
+													  range_count2,
+													  ranges2);
+	PG_RETURN_RANGE_P(result);
+}
+
+
+/* multirange -> element type functions */
+
+/* extract lower bound value */
+Datum
+multirange_lower(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+	bool		isnull;
+	Datum		result;
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_NULL();
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	result = range_lower_internal(typcache->rngtype, ranges[0], &isnull);
+
+	if (isnull)
+		PG_RETURN_NULL();
+	else
+		PG_RETURN_DATUM(result);
+}
+
+/* extract upper bound value */
+Datum
+multirange_upper(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+	bool		isnull;
+	Datum		result;
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_NULL();
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	result = range_upper_internal(typcache->rngtype, ranges[range_count - 1], &isnull);
+
+	if (isnull)
+		PG_RETURN_NULL();
+	else
+		PG_RETURN_DATUM(result);
+}
+
+
+/* multirange -> bool functions */
+
+/* is multirange empty? */
+Datum
+multirange_empty(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+
+	PG_RETURN_BOOL(mr->rangeCount == 0);
+}
+
+/* is lower bound inclusive? */
+Datum
+multirange_lower_inc(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	int32		range_count;
+	RangeType **ranges;
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_has_flag(ranges[0], RANGE_LB_INC));
+}
+
+/* is upper bound inclusive? */
+Datum
+multirange_upper_inc(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	int32		range_count;
+	RangeType **ranges;
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_has_flag(ranges[range_count - 1], RANGE_UB_INC));
+}
+
+/* is lower bound infinite? */
+Datum
+multirange_lower_inf(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	int32		range_count;
+	RangeType **ranges;
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_has_flag(ranges[0], RANGE_LB_INF));
+}
+
+/* is upper bound infinite? */
+Datum
+multirange_upper_inf(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	int32		range_count;
+	RangeType **ranges;
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_has_flag(ranges[range_count - 1], RANGE_UB_INF));
+}
+
+
+
+/* multirange, element -> bool functions */
+
+/* contains? */
+Datum
+multirange_contains_elem(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	Datum		val = PG_GETARG_DATUM(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(multirange_contains_elem_internal(typcache, mr, val));
+}
+
+/* contained by? */
+Datum
+elem_contained_by_multirange(PG_FUNCTION_ARGS)
+{
+	Datum		val = PG_GETARG_DATUM(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(multirange_contains_elem_internal(typcache, mr, val));
+}
+
+/*
+ * Test whether multirange mr contains a specific element value.
+ */
+bool
+multirange_contains_elem_internal(TypeCacheEntry *typcache, MultirangeType * mr, Datum val)
+{
+	TypeCacheEntry *rangetyp;
+	int32		range_count;
+	RangeType **ranges;
+	RangeType  *r;
+	int			i;
+
+	rangetyp = typcache->rngtype;
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	for (i = 0; i < range_count; i++)
+	{
+		r = ranges[i];
+		if (range_contains_elem_internal(rangetyp, r, val))
+			return true;
+	}
+
+	return false;
+}
+
+/* multirange, range -> bool functions */
+
+/* contains? */
+Datum
+multirange_contains_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(multirange_contains_range_internal(typcache, mr, r));
+}
+
+/* contained by? */
+Datum
+range_contained_by_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(multirange_contains_range_internal(typcache, mr, r));
+}
+
+/*
+ * Test whether multirange mr contains a specific range r.
+ */
+bool
+multirange_contains_range_internal(TypeCacheEntry *typcache, MultirangeType * mr, RangeType *r)
+{
+	TypeCacheEntry *rangetyp;
+	int32		range_count;
+	RangeType **ranges;
+	RangeType  *mrr;
+	int			i;
+
+	rangetyp = typcache->rngtype;
+
+	/*
+	 * Every multirange contains an infinite number of empty ranges, even an
+	 * empty one.
+	 */
+	if (RangeIsEmpty(r))
+		return true;
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	for (i = 0; i < range_count; i++)
+	{
+		mrr = ranges[i];
+		if (range_contains_internal(rangetyp, mrr, r))
+			return true;
+	}
+
+	return false;
+}
+
+
+/* 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));
+}
+
+/* overlaps? */
+Datum
+range_overlaps_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(range_overlaps_multirange_internal(typcache, r, mr));
+}
+
+Datum
+multirange_overlaps_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(range_overlaps_multirange_internal(typcache, r, mr));
+}
+
+Datum
+multirange_overlaps_multirange(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_overlaps_multirange_internal(typcache, mr1, mr2));
+}
+
+bool
+range_overlaps_multirange_internal(TypeCacheEntry *typcache, RangeType *r, MultirangeType * mr)
+{
+	TypeCacheEntry *rangetyp;
+	int32		range_count;
+	int32		i;
+	RangeType **ranges;
+	RangeType  *mrr;
+
+	/*
+	 * Empties never overlap, even with empties. (This seems strange since
+	 * they *do* contain each other, but we want to follow how ranges work.)
+	 */
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		return false;
+
+	rangetyp = typcache->rngtype;
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	/* Scan through mrr and once it gets to r they either overlap or not */
+	mrr = ranges[0];
+
+	/* Discard mrrs while mrr << r */
+	i = 0;
+	while (range_before_internal(rangetyp, mrr, r))
+	{
+		if (++i >= range_count)
+			return false;
+		mrr = ranges[i];
+	}
+
+	/* Now either we overlap or we passed r */
+	return range_overlaps_internal(rangetyp, mrr, r);
+}
+
+bool
+multirange_overlaps_multirange_internal(TypeCacheEntry *typcache, MultirangeType * mr1,
+										MultirangeType * mr2)
+{
+	TypeCacheEntry *rangetyp;
+	int32		range_count1;
+	int32		range_count2;
+	int32		i1;
+	int32		i2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+	RangeType  *r1;
+	RangeType  *r2;
+
+	/*
+	 * Empties never overlap, even with empties. (This seems strange since
+	 * they *do* contain each other, but we want to follow how ranges work.)
+	 */
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		return false;
+
+	rangetyp = typcache->rngtype;
+
+	multirange_deserialize(mr1, &range_count1, &ranges1);
+	multirange_deserialize(mr2, &range_count2, &ranges2);
+
+	/*
+	 * Every range in mr1 gets a chance to overlap with the ranges in mr2, but
+	 * we can use their ordering to avoid O(n^2). This is similar to
+	 * range_overlaps_multirange where r1 : r2 :: mrr : r, but there if we
+	 * don't find an overlap with r we're done, and here if we don't find an
+	 * overlap with r2 we try the next r2.
+	 */
+	r1 = ranges1[0];
+	for (i1 = 0, i2 = 0; i2 < range_count2; i2++)
+	{
+		r2 = ranges2[i2];
+
+		/* Discard r1s while r1 << r2 */
+		while (range_before_internal(rangetyp, r1, r2))
+		{
+			if (++i1 >= range_count1)
+				return false;
+			r1 = ranges1[i1];
+		}
+
+		/*
+		 * If r1 && r2, we're done, otherwise we failed to find an overlap for
+		 * r2, so go to the next one.
+		 */
+		if (range_overlaps_internal(rangetyp, r1, r2))
+			return true;
+	}
+
+	/* We looked through all of mr2 without finding an overlap */
+	return false;
+}
+
+/* does not extend to right of? */
+Datum
+range_overleft_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_overleft_internal(typcache->rngtype, r, ranges[range_count - 1]));
+}
+
+Datum
+multirange_overleft_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_overleft_internal(typcache->rngtype, ranges[range_count - 1], r));
+}
+
+Datum
+multirange_overleft_multirange(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
+	MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr1));
+
+	multirange_deserialize(mr1, &range_count1, &ranges1);
+	multirange_deserialize(mr2, &range_count2, &ranges2);
+
+	PG_RETURN_BOOL(range_overleft_internal(typcache->rngtype, ranges1[range_count1 - 1], ranges2[range_count2 - 1]));
+}
+
+/* does not extend to left of? */
+Datum
+range_overright_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_overright_internal(typcache->rngtype, r, ranges[0]));
+}
+
+Datum
+multirange_overright_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_overright_internal(typcache->rngtype, ranges[0], r));
+}
+
+Datum
+multirange_overright_multirange(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
+	MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr1));
+
+	multirange_deserialize(mr1, &range_count1, &ranges1);
+	multirange_deserialize(mr2, &range_count2, &ranges2);
+
+	PG_RETURN_BOOL(range_overright_internal(typcache->rngtype, ranges1[0], ranges2[0]));
+}
+
+/* contains? */
+Datum
+multirange_contains_multirange(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_contains_multirange_internal(typcache, mr1, mr2));
+}
+
+/* contained by? */
+Datum
+multirange_contained_by_multirange(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_contains_multirange_internal(typcache, mr2, mr1));
+}
+
+/*
+ * Test whether multirange mr1 contains every range from another multirange mr2.
+ */
+bool
+multirange_contains_multirange_internal(TypeCacheEntry *typcache,
+										MultirangeType * mr1, MultirangeType * mr2)
+{
+	TypeCacheEntry *rangetyp;
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+	RangeType  *r1;
+	RangeType  *r2;
+	int			i1,
+				i2;
+
+	rangetyp = typcache->rngtype;
+
+	multirange_deserialize(mr1, &range_count1, &ranges1);
+	multirange_deserialize(mr2, &range_count2, &ranges2);
+
+	/*
+	 * We follow the same logic for empties as ranges: - an empty multirange
+	 * contains an empty range/multirange. - an empty multirange can't contain
+	 * any other range/multirange. - an empty multirange is contained by any
+	 * other range/multirange.
+	 */
+
+	if (range_count2 == 0)
+		return true;
+	if (range_count1 == 0)
+		return false;
+
+	/*
+	 * Every range in mr2 must be contained by some range in mr1. To avoid
+	 * O(n^2) we walk through both ranges in tandem.
+	 */
+	r1 = ranges1[0];
+	for (i1 = 0, i2 = 0; i2 < range_count2; i2++)
+	{
+		r2 = ranges2[i2];
+
+		/* Discard r1s while r1 << r2 */
+		while (range_before_internal(rangetyp, r1, r2))
+		{
+			if (++i1 >= range_count1)
+				return false;
+			r1 = ranges1[i1];
+		}
+
+		/*
+		 * If r1 @> r2, go to the next r2, otherwise return false (since every
+		 * r1[n] and r1[n+1] must have a gap). Note this will give weird
+		 * answers if you don't canonicalize, e.g. with a custom
+		 * int2multirange {[1,1], [2,2]} there is a "gap". But that is
+		 * consistent with other range operators, e.g. '[1,1]'::int2range -|-
+		 * '[2,2]'::int2range is false.
+		 */
+		if (!range_contains_internal(rangetyp, r1, r2))
+			return false;
+	}
+
+	/* All ranges in mr2 are satisfied */
+	return true;
+}
+
+/* strictly left of? */
+Datum
+range_before_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(range_before_multirange_internal(typcache, r, mr));
+}
+
+Datum
+multirange_before_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(range_after_multirange_internal(typcache, r, mr));
+}
+
+Datum
+multirange_before_multirange(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_before_multirange_internal(typcache, mr1, mr2));
+}
+
+/* strictly right of? */
+Datum
+range_after_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(range_after_multirange_internal(typcache, r, mr));
+}
+
+Datum
+multirange_after_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(range_before_multirange_internal(typcache, r, mr));
+}
+
+Datum
+multirange_after_multirange(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_before_multirange_internal(typcache, mr2, mr1));
+}
+
+/* strictly left of? (internal version) */
+bool
+range_before_multirange_internal(TypeCacheEntry *typcache, RangeType *r,
+								 MultirangeType * mr)
+{
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		return false;
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	return range_before_internal(typcache->rngtype, r, ranges[0]);
+}
+
+bool
+multirange_before_multirange_internal(TypeCacheEntry *typcache, MultirangeType * mr1,
+									  MultirangeType * mr2)
+{
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		return false;
+
+	multirange_deserialize(mr1, &range_count1, &ranges1);
+	multirange_deserialize(mr2, &range_count2, &ranges2);
+
+	return range_before_internal(typcache->rngtype, ranges1[range_count1 - 1],
+								 ranges2[0]);
+}
+
+/* strictly right of? (internal version) */
+bool
+range_after_multirange_internal(TypeCacheEntry *typcache, RangeType *r,
+								MultirangeType * mr)
+{
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		return false;
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	return range_after_internal(typcache->rngtype, r, ranges[range_count - 1]);
+}
+
+/* adjacent to? */
+Datum
+range_adjacent_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		return false;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_adjacent_internal(typcache->rngtype, r, ranges[0]));
+}
+
+Datum
+multirange_adjacent_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		return false;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_adjacent_internal(typcache->rngtype, ranges[range_count - 1], r));
+}
+
+Datum
+multirange_adjacent_multirange(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
+	MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		return false;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr1));
+
+	multirange_deserialize(mr1, &range_count1, &ranges1);
+	multirange_deserialize(mr2, &range_count2, &ranges2);
+
+	PG_RETURN_BOOL(range_adjacent_internal(typcache->rngtype, ranges1[range_count1 - 1], ranges2[0]));
+}
+
+/* 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);
+}
+
+/* multirange -> range functions */
+
+/* Find the smallest range that includes everything in the multirange */
+Datum
+range_merge_from_multirange(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	Oid			mltrngtypoid = MultirangeTypeGetOid(mr);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_RANGE_P(make_empty_range(typcache->rngtype));
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_RANGE_P(range_union_internal(typcache->rngtype, ranges[0],
+										   ranges[range_count - 1], false));
 }
 
 /* Hash support */
diff --git a/src/backend/utils/adt/rangetypes.c b/src/backend/utils/adt/rangetypes.c
index 65f68614c1..0651618363 100644
--- a/src/backend/utils/adt/rangetypes.c
+++ b/src/backend/utils/adt/rangetypes.c
@@ -429,19 +429,37 @@ range_lower(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
 	TypeCacheEntry *typcache;
+	bool		isnull;
+	Datum		result;
+
+	typcache = range_get_typcache(fcinfo, RangeTypeGetOid(r1));
+
+	result = range_lower_internal(typcache, r1, &isnull);
+
+	if (isnull)
+		PG_RETURN_NULL();
+	else
+		PG_RETURN_DATUM(result);
+}
+
+Datum
+range_lower_internal(TypeCacheEntry *typcache, const RangeType *r1, bool *isnull)
+{
 	RangeBound	lower;
 	RangeBound	upper;
 	bool		empty;
 
-	typcache = range_get_typcache(fcinfo, RangeTypeGetOid(r1));
-
 	range_deserialize(typcache, r1, &lower, &upper, &empty);
 
 	/* Return NULL if there's no finite lower bound */
 	if (empty || lower.infinite)
-		PG_RETURN_NULL();
+	{
+		*isnull = true;
+		return 0;
+	}
 
-	PG_RETURN_DATUM(lower.val);
+	*isnull = false;
+	return lower.val;
 }
 
 /* extract upper bound value */
@@ -450,19 +468,37 @@ range_upper(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
 	TypeCacheEntry *typcache;
+	bool		isnull;
+	Datum		result;
+
+	typcache = range_get_typcache(fcinfo, RangeTypeGetOid(r1));
+
+	result = range_upper_internal(typcache, r1, &isnull);
+
+	if (isnull)
+		PG_RETURN_NULL();
+	else
+		PG_RETURN_DATUM(result);
+}
+
+Datum
+range_upper_internal(TypeCacheEntry *typcache, const RangeType *r1, bool *isnull)
+{
 	RangeBound	lower;
 	RangeBound	upper;
 	bool		empty;
 
-	typcache = range_get_typcache(fcinfo, RangeTypeGetOid(r1));
-
 	range_deserialize(typcache, r1, &lower, &upper, &empty);
 
 	/* Return NULL if there's no finite upper bound */
 	if (empty || upper.infinite)
-		PG_RETURN_NULL();
+	{
+		*isnull = true;
+		return 0;
+	}
 
-	PG_RETURN_DATUM(upper.val);
+	*isnull = false;
+	return upper.val;
 }
 
 
@@ -473,9 +509,8 @@ Datum
 range_empty(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
-	char		flags = range_get_flags(r1);
 
-	PG_RETURN_BOOL(flags & RANGE_EMPTY);
+	PG_RETURN_BOOL(range_has_flag(r1, RANGE_EMPTY));
 }
 
 /* is lower bound inclusive? */
@@ -483,9 +518,8 @@ Datum
 range_lower_inc(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
-	char		flags = range_get_flags(r1);
 
-	PG_RETURN_BOOL(flags & RANGE_LB_INC);
+	PG_RETURN_BOOL(range_has_flag(r1, RANGE_LB_INC));
 }
 
 /* is upper bound inclusive? */
@@ -493,9 +527,8 @@ Datum
 range_upper_inc(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
-	char		flags = range_get_flags(r1);
 
-	PG_RETURN_BOOL(flags & RANGE_UB_INC);
+	PG_RETURN_BOOL(range_has_flag(r1, RANGE_UB_INC));
 }
 
 /* is lower bound infinite? */
@@ -503,9 +536,8 @@ Datum
 range_lower_inf(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
-	char		flags = range_get_flags(r1);
 
-	PG_RETURN_BOOL(flags & RANGE_LB_INF);
+	PG_RETURN_BOOL(range_has_flag(r1, RANGE_LB_INF));
 }
 
 /* is upper bound infinite? */
@@ -513,9 +545,8 @@ Datum
 range_upper_inf(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
-	char		flags = range_get_flags(r1);
 
-	PG_RETURN_BOOL(flags & RANGE_UB_INF);
+	PG_RETURN_BOOL(range_has_flag(r1, RANGE_UB_INF));
 }
 
 
@@ -955,7 +986,25 @@ range_minus(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
 	RangeType  *r2 = PG_GETARG_RANGE_P(1);
+	RangeType  *ret;
 	TypeCacheEntry *typcache;
+
+	/* 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));
+
+	ret = range_minus_internal(typcache, r1, r2);
+	if (ret)
+		PG_RETURN_RANGE_P(ret);
+	else
+		PG_RETURN_NULL();
+}
+
+RangeType *
+range_minus_internal(TypeCacheEntry *typcache, RangeType *r1, RangeType *r2)
+{
 	RangeBound	lower1,
 				lower2;
 	RangeBound	upper1,
@@ -967,18 +1016,12 @@ range_minus(PG_FUNCTION_ARGS)
 				cmp_u1l2,
 				cmp_u1u2;
 
-	/* 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));
-
 	range_deserialize(typcache, r1, &lower1, &upper1, &empty1);
 	range_deserialize(typcache, r2, &lower2, &upper2, &empty2);
 
 	/* if either is empty, r1 is the correct answer */
 	if (empty1 || empty2)
-		PG_RETURN_RANGE_P(r1);
+		return r1;
 
 	cmp_l1l2 = range_cmp_bounds(typcache, &lower1, &lower2);
 	cmp_l1u2 = range_cmp_bounds(typcache, &lower1, &upper2);
@@ -991,34 +1034,34 @@ range_minus(PG_FUNCTION_ARGS)
 				 errmsg("result of range difference would not be contiguous")));
 
 	if (cmp_l1u2 > 0 || cmp_u1l2 < 0)
-		PG_RETURN_RANGE_P(r1);
+		return r1;
 
 	if (cmp_l1l2 >= 0 && cmp_u1u2 <= 0)
-		PG_RETURN_RANGE_P(make_empty_range(typcache));
+		return make_empty_range(typcache);
 
 	if (cmp_l1l2 <= 0 && cmp_u1l2 >= 0 && cmp_u1u2 <= 0)
 	{
 		lower2.inclusive = !lower2.inclusive;
 		lower2.lower = false;	/* it will become the upper bound */
-		PG_RETURN_RANGE_P(make_range(typcache, &lower1, &lower2, false));
+		return make_range(typcache, &lower1, &lower2, false);
 	}
 
 	if (cmp_l1l2 >= 0 && cmp_u1u2 >= 0 && cmp_l1u2 <= 0)
 	{
 		upper2.inclusive = !upper2.inclusive;
 		upper2.lower = true;	/* it will become the lower bound */
-		PG_RETURN_RANGE_P(make_range(typcache, &upper2, &upper1, false));
+		return make_range(typcache, &upper2, &upper1, false);
 	}
 
 	elog(ERROR, "unexpected case in range_minus");
-	PG_RETURN_NULL();
+	return NULL;
 }
 
 /*
  * Set union.  If strict is true, it is an error that the two input ranges
  * are not adjacent or overlapping.
  */
-static RangeType *
+RangeType *
 range_union_internal(TypeCacheEntry *typcache, RangeType *r1, RangeType *r2,
 					 bool strict)
 {
@@ -1099,6 +1142,19 @@ range_intersect(PG_FUNCTION_ARGS)
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
 	RangeType  *r2 = PG_GETARG_RANGE_P(1);
 	TypeCacheEntry *typcache;
+
+	/* 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));
+
+	PG_RETURN_RANGE_P(range_intersect_internal(typcache, r1, r2));
+}
+
+RangeType *
+range_intersect_internal(TypeCacheEntry *typcache, const RangeType *r1, const RangeType *r2)
+{
 	RangeBound	lower1,
 				lower2;
 	RangeBound	upper1,
@@ -1108,17 +1164,11 @@ range_intersect(PG_FUNCTION_ARGS)
 	RangeBound *result_lower;
 	RangeBound *result_upper;
 
-	/* 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));
-
 	range_deserialize(typcache, r1, &lower1, &upper1, &empty1);
 	range_deserialize(typcache, r2, &lower2, &upper2, &empty2);
 
-	if (empty1 || empty2 || !DatumGetBool(range_overlaps(fcinfo)))
-		PG_RETURN_RANGE_P(make_empty_range(typcache));
+	if (empty1 || empty2 || !range_overlaps_internal(typcache, r1, r2))
+		return make_empty_range(typcache);
 
 	if (range_cmp_bounds(typcache, &lower1, &lower2) >= 0)
 		result_lower = &lower1;
@@ -1130,54 +1180,83 @@ range_intersect(PG_FUNCTION_ARGS)
 	else
 		result_upper = &upper2;
 
-	PG_RETURN_RANGE_P(make_range(typcache, result_lower, result_upper, false));
+	return make_range(typcache, result_lower, result_upper, false);
 }
 
-/* Btree support */
+/* range, range -> range, range functions */
 
-/* btree comparator */
-Datum
-range_cmp(PG_FUNCTION_ARGS)
+/*
+ * range_split_internal - if r2 intersects the middle of r1, leaving non-empty
+ * ranges on both sides, then return true and set output1 and output2 to the
+ * results of r1 - r2 (in order). Otherwise return false and don't set output1
+ * or output2. Neither input range should be empty.
+ */
+bool
+range_split_internal(TypeCacheEntry *typcache, const RangeType *r1, const RangeType *r2,
+					 RangeType **output1, RangeType **output2)
 {
-	RangeType  *r1 = PG_GETARG_RANGE_P(0);
-	RangeType  *r2 = PG_GETARG_RANGE_P(1);
-	TypeCacheEntry *typcache;
 	RangeBound	lower1,
 				lower2;
 	RangeBound	upper1,
 				upper2;
 	bool		empty1,
 				empty2;
-	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));
 
 	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
+	if (range_cmp_bounds(typcache, &lower1, &lower2) < 0 &&
+		range_cmp_bounds(typcache, &upper1, &upper2) > 0)
 	{
-		cmp = range_cmp_bounds(typcache, &lower1, &lower2);
-		if (cmp == 0)
-			cmp = range_cmp_bounds(typcache, &upper1, &upper2);
+		/*
+		 * Need to invert inclusive/exclusive for the lower2 and upper2
+		 * points. They can't be infinite though. We're allowed to overwrite
+		 * these RangeBounds since they only exist locally.
+		 */
+		lower2.inclusive = !lower2.inclusive;
+		lower2.lower = false;
+		upper2.inclusive = !upper2.inclusive;
+		upper2.lower = true;
+
+		*output1 = make_range(typcache, &lower1, &lower2, false);
+		*output2 = make_range(typcache, &upper2, &upper1, false);
+		return true;
 	}
 
-	return cmp;
+	return false;
+}
+
+/* range -> range aggregate functions */
+
+Datum
+range_intersect_agg_transfn(PG_FUNCTION_ARGS)
+{
+	MemoryContext aggContext;
+	Oid			rngtypoid;
+	TypeCacheEntry *typcache;
+	RangeType  *result;
+	RangeType  *current;
+
+	if (!AggCheckCallContext(fcinfo, &aggContext))
+		elog(ERROR, "range_intersect_agg_transfn called in non-aggregate context");
+
+	rngtypoid = get_fn_expr_argtype(fcinfo->flinfo, 1);
+	if (!type_is_range(rngtypoid))
+		ereport(ERROR, (errmsg("range_intersect_agg must be called with a range")));
+
+	typcache = range_get_typcache(fcinfo, rngtypoid);
+
+	/* strictness ensures these are non-null */
+	result = PG_GETARG_RANGE_P(0);
+	current = PG_GETARG_RANGE_P(1);
+
+	result = range_intersect_internal(typcache, result, current);
+	PG_RETURN_RANGE_P(result);
 }
 
+
+/* Btree support */
+
 /* btree comparator */
 Datum
 range_cmp(PG_FUNCTION_ARGS)
@@ -1207,7 +1286,7 @@ range_cmp(PG_FUNCTION_ARGS)
  * Internal version of range_cmp
  */
 int
-range_cmp_internal(TypeCacheEntry *typcache, RangeType *r1, RangeType *r2)
+range_cmp_internal(TypeCacheEntry *typcache, const RangeType *r1, const RangeType *r2)
 {
 	RangeBound	lower1,
 				lower2;
@@ -1273,7 +1352,7 @@ range_gt(PG_FUNCTION_ARGS)
 /* Hash support */
 
 uint32
-hash_range_internal(TypeCacheEntry *typcache, RangeType *r)
+hash_range_internal(TypeCacheEntry *typcache, const RangeType *r)
 {
 	uint32		result;
 	TypeCacheEntry *scache;
@@ -1343,7 +1422,7 @@ hash_range(PG_FUNCTION_ARGS)
 }
 
 uint64
-hash_range_extended_internal(TypeCacheEntry *typcache, RangeType *r, Datum seed)
+hash_range_extended_internal(TypeCacheEntry *typcache, const RangeType *r, Datum seed)
 {
 	uint64		result;
 	TypeCacheEntry *scache;
@@ -1838,6 +1917,21 @@ range_get_flags(const RangeType *range)
 }
 
 /*
+ * range_has_flag: set whether a range has a specific flag.
+ *
+ * This lets expose some of our functions that just check flags
+ * to the rest of the code base (like to multiranges)
+ * without writing full-fledged *_internal versions.
+ */
+bool
+range_has_flag(const RangeType *r1, char flag)
+{
+	char		flags = range_get_flags(r1);
+
+	return flags & flag;
+}
+
+/*
  * range_set_contain_empty: set the RANGE_CONTAIN_EMPTY bit in the value.
  *
  * This is only needed in GiST operations, so we don't include a provision
diff --git a/src/backend/utils/fmgr/funcapi.c b/src/backend/utils/fmgr/funcapi.c
index d65968d4eb..25623461bb 100644
--- a/src/backend/utils/fmgr/funcapi.c
+++ b/src/backend/utils/fmgr/funcapi.c
@@ -573,18 +573,20 @@ resolve_polymorphic_tupdesc(TupleDesc tupdesc, oidvector *declared_args,
 
 		if (OidIsValid(anymultirange_type))
 		{
-			Oid			rngtype = resolve_generic_type(ANYRANGEOID,
-													   anymultirange_type,
-													   ANYMULTIRANGEOID);
+			Oid			rngtype;
+			Oid			subtype;
+
+			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);
+			subtype = resolve_generic_type(ANYELEMENTOID,
+										   anyrange_type,
+										   ANYRANGEOID);
 
 			/* check for inconsistent array and multirange results */
 			if (OidIsValid(anyelement_type) && anyelement_type != subtype)
@@ -628,18 +630,21 @@ resolve_polymorphic_tupdesc(TupleDesc tupdesc, oidvector *declared_args,
 	{
 		if (OidIsValid(anymultirange_type))
 		{
-			Oid			rngtype = resolve_generic_type(ANYRANGEOID,
-													   anymultirange_type,
-													   ANYMULTIRANGEOID);
+			Oid		rngtype;
+			Oid		subtype;
+
+			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);
+			subtype = resolve_generic_type(ANYELEMENTOID,
+										   anyrange_type,
+										   ANYRANGEOID);
 
 			/* check for inconsistent array and multirange results */
 			if (OidIsValid(anyelement_type) && anyelement_type != subtype)
@@ -660,21 +665,25 @@ resolve_polymorphic_tupdesc(TupleDesc tupdesc, oidvector *declared_args,
 	{
 		if (OidIsValid(anyrange_type))
 		{
-			Oid			subtype = resolve_generic_type(ANYELEMENTOID,
-													   anyrange_type,
-													   ANYRANGEOID);
+			Oid		subtype;
+			Oid		mltrngtype;
+			Oid		rngtype;
+
+			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);
+			mltrngtype = resolve_generic_type(ANYMULTIRANGEOID,
+											  anyrange_type,
+											  ANYRANGEOID);
 
 			/* check for inconsistent range and multirange results */
-			Oid			rngtype = get_multirange_subtype(mltrngtype);
+			rngtype = get_multirange_subtype(mltrngtype);
 
 			if (OidIsValid(anyrange_type) && anyrange_type != rngtype)
 				return false;
@@ -900,18 +909,21 @@ resolve_polymorphic_argtypes(int numargs, Oid *argtypes, char *argmodes,
 
 		if (OidIsValid(anymultirange_type))
 		{
-			Oid			rngtype = resolve_generic_type(ANYRANGEOID,
-													   anymultirange_type,
-													   ANYMULTIRANGEOID);
+			Oid			rngtype;
+			Oid			subtype;
+			
+			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);
+			subtype = resolve_generic_type(ANYELEMENTOID,
+										   anyrange_type,
+										   ANYRANGEOID);
 
 			/* check for inconsistent array and multirange results */
 			if (OidIsValid(anyelement_type) && anyelement_type != subtype)
@@ -955,18 +967,21 @@ resolve_polymorphic_argtypes(int numargs, Oid *argtypes, char *argmodes,
 	{
 		if (OidIsValid(anymultirange_type))
 		{
-			Oid			rngtype = resolve_generic_type(ANYRANGEOID,
-													   anymultirange_type,
-													   ANYMULTIRANGEOID);
+			Oid			rngtype;
+			Oid			subtype;
+
+			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);
+			subtype = resolve_generic_type(ANYELEMENTOID,
+										   anyrange_type,
+										   ANYRANGEOID);
 
 			/* check for inconsistent array and multirange results */
 			if (OidIsValid(anyelement_type) && anyelement_type != subtype)
@@ -987,21 +1002,25 @@ resolve_polymorphic_argtypes(int numargs, Oid *argtypes, char *argmodes,
 	{
 		if (OidIsValid(anyrange_type))
 		{
-			Oid			subtype = resolve_generic_type(ANYELEMENTOID,
-													   anyrange_type,
-													   ANYRANGEOID);
+			Oid			subtype;
+			Oid			mltrngtype;
+			Oid			rngtype;
+
+			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);
+			mltrngtype = resolve_generic_type(ANYMULTIRANGEOID,
+											  anyrange_type,
+											  ANYRANGEOID);
 
 			/* check for inconsistent range and multirange results */
-			Oid			rngtype = get_multirange_subtype(mltrngtype);
+			rngtype = get_multirange_subtype(mltrngtype);
 
 			if (OidIsValid(anyrange_type) && anyrange_type != rngtype)
 				return false;
diff --git a/src/include/catalog/pg_aggregate.dat b/src/include/catalog/pg_aggregate.dat
index 242d843347..07a7211c15 100644
--- a/src/include/catalog/pg_aggregate.dat
+++ b/src/include/catalog/pg_aggregate.dat
@@ -538,6 +538,17 @@
   aggtransfn => 'bytea_string_agg_transfn',
   aggfinalfn => 'bytea_string_agg_finalfn', aggtranstype => 'internal' },
 
+# range
+{ aggfnoid => 'range_intersect_agg(anyrange)',
+  aggtransfn => 'range_intersect_agg_transfn',
+  aggcombinefn => 'range_intersect_agg_transfn', aggtranstype => 'anyrange' },
+{ aggfnoid => 'range_intersect_agg(anymultirange)',
+  aggtransfn => 'multirange_intersect_agg_transfn',
+  aggcombinefn => 'multirange_intersect_agg_transfn', aggtranstype => 'anymultirange' },
+{ aggfnoid => 'range_agg(anyrange)', aggtransfn => 'range_agg_transfn',
+  aggfinalfn => 'range_agg_finalfn', aggfinalextra => 't',
+  aggtranstype => 'internal' },
+
 # json
 { aggfnoid => 'json_agg', aggtransfn => 'json_agg_transfn',
   aggfinalfn => 'json_agg_finalfn', aggtranstype => 'internal' },
diff --git a/src/include/catalog/pg_amproc.dat b/src/include/catalog/pg_amproc.dat
index b7cb2cb000..32965d6b3d 100644
--- a/src/include/catalog/pg_amproc.dat
+++ b/src/include/catalog/pg_amproc.dat
@@ -1210,12 +1210,13 @@
   amprocrighttype => 'anyrange', amprocnum => '4',
   amproc => 'brin_inclusion_union' },
 { amprocfamily => 'brin/range_inclusion_ops', amproclefttype => 'anyrange',
-  amprocrighttype => 'anyrange', amprocnum => '11', amproc => 'range_merge' },
+  amprocrighttype => 'anyrange', amprocnum => '11',
+  amproc => 'range_merge(anyrange,anyrange)' },
 { amprocfamily => 'brin/range_inclusion_ops', amproclefttype => 'anyrange',
   amprocrighttype => 'anyrange', amprocnum => '13',
   amproc => 'range_contains' },
 { amprocfamily => 'brin/range_inclusion_ops', amproclefttype => 'anyrange',
-  amprocrighttype => 'anyrange', amprocnum => '14', amproc => 'isempty' },
+  amprocrighttype => 'anyrange', amprocnum => '14', amproc => 'isempty(anyrange)' },
 
 # minmax pg_lsn
 { amprocfamily => 'brin/pg_lsn_minmax_ops', amproclefttype => 'pg_lsn',
diff --git a/src/include/catalog/pg_operator.dat b/src/include/catalog/pg_operator.dat
index 4d319bb5b3..ade223b6f3 100644
--- a/src/include/catalog/pg_operator.dat
+++ b/src/include/catalog/pg_operator.dat
@@ -3322,5 +3322,174 @@
   oprresult => 'bool', oprcom => '<(anymultirange,anymultirange)',
   oprnegate => '<=(anymultirange,anymultirange)', oprcode => 'multirange_gt',
   oprrest => 'scalargtsel', oprjoin => 'scalargtjoinsel' },
+{ oid => '8073', oid_symbol => 'OID_RANGE_OVERLAPS_MULTIRANGE_OP',
+  descr => 'overlaps',
+  oprname => '&&', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '&&(anymultirange,anyrange)',
+  oprcode => 'range_overlaps_multirange', oprrest => 'areasel',
+  oprjoin => 'areajoinsel' },
+{ oid => '8074', oid_symbol => 'OID_MULTIRANGE_OVERLAPS_RANGE_OP', descr => 'contains',
+  oprname => '&&', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcom => '&&(anyrange,anymultirange)',
+  oprcode => 'multirange_overlaps_range', oprrest => 'areasel',
+  oprjoin => 'areajoinsel' },
+{ oid => '8075', oid_symbol => 'OID_MULTIRANGE_OVERLAPS_MULTIRANGE_OP', descr => 'contains',
+  oprname => '&&', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '&&(anymultirange,anymultirange)',
+  oprcode => 'multirange_overlaps_multirange', oprrest => 'areasel',
+  oprjoin => 'areajoinsel' },
+{ oid => '8064', oid_symbol => 'OID_MULTIRANGE_CONTAINS_ELEM_OP',
+  descr => 'contains',
+  oprname => '@>', oprleft => 'anymultirange', oprright => 'anyelement',
+  oprresult => 'bool', oprcom => '<@(anyelement,anymultirange)',
+  oprcode => 'multirange_contains_elem', oprrest => 'contsel',
+  oprjoin => 'contjoinsel' },
+{ oid => '8065', oid_symbol => 'OID_MULTIRANGE_CONTAINS_RANGE_OP', descr => 'contains',
+  oprname => '@>', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcom => '<@(anyrange,anymultirange)',
+  oprcode => 'multirange_contains_range', oprrest => 'contsel',
+  oprjoin => 'contjoinsel' },
+{ oid => '8066', oid_symbol => 'OID_MULTIRANGE_CONTAINS_MULTIRANGE_OP', descr => 'contains',
+  oprname => '@>', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '<@(anymultirange,anymultirange)',
+  oprcode => 'multirange_contains_multirange', oprrest => 'contsel',
+  oprjoin => 'contjoinsel' },
+{ oid => '8067', oid_symbol => 'OID_MULTIRANGE_ELEM_CONTAINED_OP',
+  descr => 'is contained by',
+  oprname => '<@', oprleft => 'anyelement', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '@>(anymultirange,anyelement)',
+  oprcode => 'elem_contained_by_multirange', oprrest => 'contsel',
+  oprjoin => 'contjoinsel' },
+{ oid => '8068', oid_symbol => 'OID_MULTIRANGE_RANGE_CONTAINED_OP',
+  descr => 'is contained by',
+  oprname => '<@', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '@>(anymultirange,anyrange)',
+  oprcode => 'range_contained_by_multirange', oprrest => 'contsel',
+  oprjoin => 'contjoinsel' },
+{ oid => '8069', oid_symbol => 'OID_MULTIRANGE_MULTIRANGE_CONTAINED_OP',
+  descr => 'is contained by',
+  oprname => '<@', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '@>(anymultirange,anymultirange)',
+  oprcode => 'multirange_contained_by_multirange', oprrest => 'contsel',
+  oprjoin => 'contjoinsel' },
+{ oid => '8106', oid_symbol => 'OID_RANGE_OVERLAPS_LEFT_MULTIRANGE_OP',
+  descr => 'overlaps or is left of',
+  oprname => '&<', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcode => 'range_overleft_multirange', oprrest => 'scalarltsel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8107', oid_symbol => 'OID_MULTIRANGE_OVERLAPS_LEFT_RANGE_OP',
+  descr => 'overlaps or is left of',
+  oprname => '&<', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcode => 'multirange_overleft_range', oprrest => 'scalarltsel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8108', oid_symbol => 'OID_MULTIRANGE_OVERLAPS_LEFT_MULTIRANGE_OP',
+  descr => 'overlaps or is left of',
+  oprname => '&<', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcode => 'multirange_overleft_multirange', oprrest => 'scalarltsel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8109', oid_symbol => 'OID_RANGE_OVERLAPS_RIGHT_MULTIRANGE_OP',
+  descr => 'overlaps or is right of',
+  oprname => '&>', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcode => 'range_overright_multirange', oprrest => 'scalargtsel',
+  oprjoin => 'scalargtjoinsel' },
+{ oid => '8110', oid_symbol => 'OID_MULTIRANGE_OVERLAPS_RIGHT_RANGE_OP',
+  descr => 'overlaps or is right of',
+  oprname => '&>', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcode => 'multirange_overright_range', oprrest => 'scalargtsel',
+  oprjoin => 'scalargtjoinsel' },
+{ oid => '8111', oid_symbol => 'OID_MULTIRANGE_OVERLAPS_RIGHT_MULTIRANGE_OP',
+  descr => 'overlaps or is right of',
+  oprname => '&>', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcode => 'multirange_overright_multirange', oprrest => 'scalargtsel',
+  oprjoin => 'scalargtjoinsel' },
+{ oid => '8091', oid_symbol => 'OID_RANGE_ADJACENT_MULTIRANGE_OP', descr => 'is adjacent to',
+  oprname => '-|-', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '-|-(anymultirange,anyrange)',
+  oprcode => 'range_adjacent_multirange', oprrest => 'scalarltsel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8092', oid_symbol => 'OID_MULTIRANGE_ADJACENT_RANGE_OP', descr => 'is adjacent to',
+  oprname => '-|-', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcom => '-|-(anyrange,anymultirange)',
+  oprcode => 'multirange_adjacent_range', oprrest => 'scalarltsel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8093', oid_symbol => 'OID_MULTIRANGE_ADJACENT_MULTIRANGE_OP', descr => 'is adjacent to',
+  oprname => '-|-', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '-|-(anymultirange,anymultirange)',
+  oprcode => 'multirange_adjacent_multirange', oprrest => 'scalarltsel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8138', descr => 'multirange union',
+  oprname => '@+', oprleft => 'anyrange', oprright => 'anyrange',
+  oprresult => 'anymultirange', oprcom => '@+(anyrange,anyrange)',
+  oprcode => 'range_union_range' },
+{ oid => '8115', descr => 'multirange union',
+  oprname => '@+', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'anymultirange', oprcom => '@+(anymultirange,anyrange)',
+  oprcode => 'range_union_multirange' },
+{ oid => '8116', descr => 'multirange union',
+  oprname => '@+', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'anymultirange', oprcom => '@+(anyrange,anymultirange)',
+  oprcode => 'multirange_union_range' },
+{ oid => '8117', descr => 'multirange union',
+  oprname => '@+', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'anymultirange', oprcom => '@+(anymultirange,anymultirange)',
+  oprcode => 'multirange_union_multirange' },
+{ oid => '8140', descr => 'multirange minus',
+  oprname => '@-', oprleft => 'anyrange', oprright => 'anyrange',
+  oprresult => 'anymultirange', oprcode => 'range_minus_range' },
+{ oid => '8121', descr => 'multirange minus',
+  oprname => '@-', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'anymultirange', oprcode => 'range_minus_multirange' },
+{ oid => '8122', descr => 'multirange minus',
+  oprname => '@-', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'anymultirange', oprcode => 'multirange_minus_range' },
+{ oid => '8123', descr => 'multirange minus',
+  oprname => '@-', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'anymultirange', oprcode => 'multirange_minus_multirange' },
+{ oid => '8142', descr => 'multirange intersect',
+  oprname => '@*', oprleft => 'anyrange', oprright => 'anyrange',
+  oprresult => 'anymultirange', oprcom => '@*(anyrange,anyrange)',
+  oprcode => 'range_intersect_range' },
+{ oid => '8127', descr => 'multirange intersect',
+  oprname => '@*', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'anymultirange', oprcom => '@*(anymultirange,anyrange)',
+  oprcode => 'range_intersect_multirange' },
+{ oid => '8128', descr => 'multirange intersect',
+  oprname => '@*', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'anymultirange', oprcom => '@*(anyrange,anymultirange)',
+  oprcode => 'multirange_intersect_range' },
+{ oid => '8129', descr => 'multirange intersect',
+  oprname => '@*', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'anymultirange', oprcom => '@*(anymultirange,anymultirange)',
+  oprcode => 'multirange_intersect_multirange' },
+{ oid => '8082', oid_symbol => 'OID_RANGE_BEFORE_MULTIRANGE_OP', descr => 'is left of',
+  oprname => '<<', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '>>(anymultirange,anyrange)',
+  oprcode => 'range_before_multirange', oprrest => 'scalarltsel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8083', oid_symbol => 'OID_MULTIRANGE_BEFORE_RANGE_OP', descr => 'is left of',
+  oprname => '<<', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcom => '>>(anyrange,anymultirange)',
+  oprcode => 'multirange_before_range', oprrest => 'scalarltsel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8084', oid_symbol => 'OID_MULTIRANGE_BEFORE_MULTIRANGE_OP', descr => 'is left of',
+  oprname => '<<', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '>>(anymultirange,anymultirange)',
+  oprcode => 'multirange_before_multirange', oprrest => 'scalarltsel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8085', oid_symbol => 'OID_RANGE_AFTER_MULTIRANGE_OP', descr => 'is right of',
+  oprname => '>>', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '<<(anymultirange,anyrange)',
+  oprcode => 'range_after_multirange', oprrest => 'scalargtsel',
+  oprjoin => 'scalargtjoinsel' },
+{ oid => '8086', oid_symbol => 'OID_MULTIRANGE_AFTER_RANGE_OP', descr => 'is right of',
+  oprname => '>>', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcom => '<<(anyrange,anymultirange)',
+  oprcode => 'multirange_after_range', oprrest => 'scalargtsel',
+  oprjoin => 'scalargtjoinsel' },
+{ oid => '8087', oid_symbol => 'OID_MULTIRANGE_AFTER_MULTIRANGE_OP', descr => 'is right of',
+  oprname => '>>', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '<<(anymultirange,anymultirange)',
+  oprcode => 'multirange_after_multirange', oprrest => 'scalargtsel',
+  oprjoin => 'scalargtjoinsel' },
 
 ]
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 7ffa047969..13ea94c024 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -9607,6 +9607,10 @@
   descr => 'the smallest range which includes both of the given ranges',
   proname => 'range_merge', prorettype => 'anyrange',
   proargtypes => 'anyrange anyrange', prosrc => 'range_merge' },
+{ oid => '8143',
+  descr => 'the smallest range which includes the whole multirange',
+  proname => 'range_merge', prorettype => 'anyrange',
+  proargtypes => 'anymultirange', prosrc => 'range_merge_from_multirange' },
 { oid => '3868',
   proname => 'range_intersect', prorettype => 'anyrange',
   proargtypes => 'anyrange anyrange', prosrc => 'range_intersect' },
@@ -9656,6 +9660,13 @@
 { oid => '3169', descr => 'restriction selectivity for range operators',
   proname => 'rangesel', provolatile => 's', prorettype => 'float8',
   proargtypes => 'internal oid internal int4', prosrc => 'rangesel' },
+{ oid => '8133', descr => 'range aggregate by intersecting',
+  proname => 'range_intersect_agg_transfn', prorettype => 'anyrange',
+  proargtypes => 'anyrange anyrange', prosrc => 'range_intersect_agg_transfn'},
+{ oid => '8134', descr => 'range aggregate by intersecting',
+  proname => 'range_intersect_agg', prokind => 'a', proisstrict => 'f',
+  prorettype => 'anyrange', proargtypes => 'anyrange',
+  prosrc => 'aggregate_dummy'},
 
 { oid => '3914', descr => 'convert an int4 range to canonical form',
   proname => 'int4range_canonical', prorettype => 'int4range',
@@ -9743,9 +9754,165 @@
 { oid => '8007', descr => 'I/O',
   proname => 'multirange_send', provolatile => 's', prorettype => 'bytea',
   proargtypes => 'anymultirange', prosrc => 'multirange_send' },
+{ oid => '8094', descr => 'lower bound of multirange',
+  proname => 'lower', prorettype => 'anyelement', proargtypes => 'anymultirange',
+  prosrc => 'multirange_lower' },
+{ oid => '8095', descr => 'upper bound of multirange',
+  proname => 'upper', prorettype => 'anyelement', proargtypes => 'anymultirange',
+  prosrc => 'multirange_upper' },
+{ oid => '8057', descr => 'is the multirange empty?',
+  proname => 'isempty', prorettype => 'bool',
+  proargtypes => 'anymultirange', prosrc => 'multirange_empty' },
+{ oid => '8096', descr => 'is the multirange\'s lower bound inclusive?',
+  proname => 'lower_inc', prorettype => 'bool', proargtypes => 'anymultirange',
+  prosrc => 'multirange_lower_inc' },
+{ oid => '8097', descr => 'is the multirange\'s upper bound inclusive?',
+  proname => 'upper_inc', prorettype => 'bool', proargtypes => 'anymultirange',
+  prosrc => 'multirange_upper_inc' },
+{ oid => '8098', descr => 'is the multirange\'s lower bound infinite?',
+  proname => 'lower_inf', prorettype => 'bool', proargtypes => 'anymultirange',
+  prosrc => 'multirange_lower_inf' },
+{ oid => '8099', descr => 'is the multirange\'s upper bound infinite?',
+  proname => 'upper_inf', prorettype => 'bool', proargtypes => 'anymultirange',
+  prosrc => 'multirange_upper_inf' },
 { oid => '8008', descr => 'multirange typanalyze',
   proname => 'multirange_typanalyze', provolatile => 's', prorettype => 'bool',
   proargtypes => 'internal', prosrc => 'multirange_typanalyze' },
+{ oid => '8033',
+  proname => 'multirange_eq', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_eq' },
+{ oid => '8034',
+  proname => 'multirange_ne', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_ne' },
+{ oid => '8070',
+  proname => 'range_overlaps_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_overlaps_multirange' },
+{ oid => '8071',
+  proname => 'multirange_overlaps_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_overlaps_range' },
+{ oid => '8072',
+  proname => 'multirange_overlaps_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_overlaps_multirange' },
+{ oid => '8058',
+  proname => 'multirange_contains_elem', prorettype => 'bool',
+  proargtypes => 'anymultirange anyelement', prosrc => 'multirange_contains_elem' },
+{ oid => '8059',
+  proname => 'multirange_contains_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_contains_range' },
+{ oid => '8060',
+  proname => 'multirange_contains_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_contains_multirange' },
+{ oid => '8061',
+  proname => 'elem_contained_by_multirange', prorettype => 'bool',
+  proargtypes => 'anyelement anymultirange', prosrc => 'elem_contained_by_multirange' },
+{ oid => '8062',
+  proname => 'range_contained_by_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_contained_by_multirange' },
+{ oid => '8063',
+  proname => 'multirange_contained_by_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_contained_by_multirange' },
+{ oid => '8088',
+  proname => 'range_adjacent_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_adjacent_multirange' },
+{ oid => '8089',
+  proname => 'multirange_adjacent_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_adjacent_multirange' },
+{ oid => '8090',
+  proname => 'multirange_adjacent_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_adjacent_range' },
+{ oid => '8076',
+  proname => 'range_before_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_before_multirange' },
+{ oid => '8077',
+  proname => 'multirange_before_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_before_range' },
+{ oid => '8078',
+  proname => 'multirange_before_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_before_multirange' },
+{ oid => '8079',
+  proname => 'range_after_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_after_multirange' },
+{ oid => '8080',
+  proname => 'multirange_after_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_after_range' },
+{ oid => '8081',
+  proname => 'multirange_after_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_after_multirange' },
+{ oid => '8100',
+  proname => 'range_overleft_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_overleft_multirange' },
+{ oid => '8101',
+  proname => 'multirange_overleft_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_overleft_range' },
+{ oid => '8102',
+  proname => 'multirange_overleft_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_overleft_multirange' },
+{ oid => '8103',
+  proname => 'range_overright_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_overright_multirange' },
+{ oid => '8104',
+  proname => 'multirange_overright_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_overright_range' },
+{ oid => '8105',
+  proname => 'multirange_overright_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_overright_multirange' },
+{ oid => '8137',
+  proname => 'range_union_range', prorettype => 'anymultirange',
+  proargtypes => 'anyrange anyrange', prosrc => 'range_union_range' },
+{ oid => '8112',
+  proname => 'range_union_multirange', prorettype => 'anymultirange',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_union_multirange' },
+{ oid => '8113',
+  proname => 'multirange_union_range', prorettype => 'anymultirange',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_union_range' },
+{ oid => '8114',
+  proname => 'multirange_union_multirange', prorettype => 'anymultirange',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_union_multirange' },
+{ oid => '8139',
+  proname => 'range_minus_range', prorettype => 'anymultirange',
+  proargtypes => 'anyrange anyrange', prosrc => 'range_minus_range' },
+{ oid => '8118',
+  proname => 'range_minus_multirange', prorettype => 'anymultirange',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_minus_multirange' },
+{ oid => '8119',
+  proname => 'multirange_minus_range', prorettype => 'anymultirange',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_minus_range' },
+{ oid => '8120',
+  proname => 'multirange_minus_multirange', prorettype => 'anymultirange',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_minus_multirange' },
+{ oid => '8141',
+  proname => 'range_intersect_range', prorettype => 'anymultirange',
+  proargtypes => 'anyrange anyrange', prosrc => 'range_intersect_range' },
+{ oid => '8124',
+  proname => 'range_intersect_multirange', prorettype => 'anymultirange',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_intersect_multirange' },
+{ oid => '8125',
+  proname => 'multirange_intersect_range', prorettype => 'anymultirange',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_intersect_range' },
+{ oid => '8126',
+  proname => 'multirange_intersect_multirange', prorettype => 'anymultirange',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_intersect_multirange' },
+{ oid => '8022', descr => 'less-equal-greater',
+  proname => 'multirange_cmp', prorettype => 'int4',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_cmp' },
+{ oid => '8023',
+  proname => 'multirange_lt', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_lt' },
+{ oid => '8024',
+  proname => 'multirange_le', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_le' },
+{ oid => '8025',
+  proname => 'multirange_ge', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_ge' },
+{ oid => '8026',
+  proname => 'multirange_gt', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_gt' },
+{ oid => '8038', descr => 'hash a multirange',
+  proname => 'hash_multirange', prorettype => 'int4', proargtypes => 'anymultirange',
+  prosrc => 'hash_multirange' },
+{ oid => '8039', descr => 'hash a multirange',
+  proname => 'hash_multirange_extended', prorettype => 'int8',
+  proargtypes => 'anymultirange int8', prosrc => 'hash_multirange_extended' },
 
 { oid => '8045', descr => 'int4multirange constructor',
   proname => 'int4multirange', proisstrict => 'f',
@@ -9801,6 +9968,24 @@
   prorettype => 'int8multirange', proargtypes => '_int8range',
   proallargtypes => '{_int8range}', proargmodes => '{v}',
   prosrc => 'multirange_constructor1' },
+{ oid => '8130', descr => 'aggregate transition function',
+  proname => 'range_agg_transfn', proisstrict => 'f', prorettype => 'internal',
+  proargtypes => 'internal anyrange', prosrc => 'range_agg_transfn' },
+{ oid => '8131', descr => 'aggregate final function',
+  proname => 'range_agg_finalfn', proisstrict => 'f', prorettype => 'anymultirange',
+  proargtypes => 'internal anyrange', prosrc => 'range_agg_finalfn' },
+{ oid => '8132', descr => 'combine aggregate input into a multirange',
+  proname => 'range_agg', prokind => 'a', proisstrict => 'f',
+  prorettype => 'anymultirange', proargtypes => 'anyrange',
+  prosrc => 'aggregate_dummy' },
+{ oid => '8135', descr => 'range aggregate by intersecting',
+  proname => 'multirange_intersect_agg_transfn', prorettype => 'anymultirange',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_intersect_agg_transfn'},
+{ oid => '8136', descr => 'range aggregate by intersecting',
+  proname => 'range_intersect_agg', prokind => 'a', proisstrict => 'f',
+  prorettype => 'anymultirange', proargtypes => 'anymultirange',
+  prosrc => 'aggregate_dummy'},
+
 # date, time, timestamp constructors
 { oid => '3846', descr => 'construct date',
   proname => 'make_date', prorettype => 'date', proargtypes => 'int4 int4 int4',
diff --git a/src/include/utils/multirangetypes.h b/src/include/utils/multirangetypes.h
index ed2e19aafa..cc4f82b0ba 100644
--- a/src/include/utils/multirangetypes.h
+++ b/src/include/utils/multirangetypes.h
@@ -59,6 +59,40 @@ extern bool multirange_eq_internal(TypeCacheEntry *typcache, MultirangeType * mr
 								   MultirangeType * mr2);
 extern bool multirange_ne_internal(TypeCacheEntry *typcache, MultirangeType * mr1,
 								   MultirangeType * mr2);
+extern bool multirange_contains_elem_internal(TypeCacheEntry *typcache, MultirangeType * mr,
+											  Datum elem);
+extern bool multirange_contains_range_internal(TypeCacheEntry *typcache, MultirangeType * mr,
+											   RangeType *r);
+extern bool multirange_contains_multirange_internal(TypeCacheEntry *typcache,
+													MultirangeType * mr1,
+													MultirangeType * mr2);
+extern bool range_overlaps_multirange_internal(TypeCacheEntry *typcache, RangeType *r,
+											   MultirangeType * mr);
+extern bool multirange_overlaps_multirange_internal(TypeCacheEntry *typcache,
+													MultirangeType * mr1,
+													MultirangeType * mr2);
+extern bool range_before_multirange_internal(TypeCacheEntry *typcache, RangeType *r,
+											 MultirangeType * mr);
+extern bool range_after_multirange_internal(TypeCacheEntry *typcache, RangeType *r,
+											MultirangeType * mr);
+extern bool multirange_before_multirange_internal(TypeCacheEntry *typcache,
+												  MultirangeType * mr1,
+												  MultirangeType * mr2);
+extern MultirangeType * range_union_multirange_internal(TypeCacheEntry *typcache,
+														RangeType *r,
+														MultirangeType * mr);
+extern MultirangeType * multirange_minus_multirange_internal(Oid mltrngtypoid,
+															 TypeCacheEntry *rangetyp,
+															 int32 range_count1,
+															 RangeType **ranges1,
+															 int32 range_count2,
+															 RangeType **ranges2);
+extern MultirangeType * multirange_intersect_multirange_internal(Oid mltrngtypoid,
+																 TypeCacheEntry *rangetyp,
+																 int32 range_count1,
+																 RangeType **ranges1,
+																 int32 range_count2,
+																 RangeType **ranges2);
 
 /* assorted support functions */
 extern TypeCacheEntry *multirange_get_typcache(FunctionCallInfo fcinfo,
diff --git a/src/include/utils/rangetypes.h b/src/include/utils/rangetypes.h
index 521710d71f..0bee724886 100644
--- a/src/include/utils/rangetypes.h
+++ b/src/include/utils/rangetypes.h
@@ -94,13 +94,21 @@ typedef struct
  * prototypes for functions defined in rangetypes.c
  */
 
-extern bool range_contains_elem_internal(TypeCacheEntry *typcache, const RangeType *r, Datum val);
-
 /* internal versions of the above */
+extern int	range_cmp_internal(TypeCacheEntry *typcache, const RangeType *r1,
+							   const RangeType *r2);
+extern uint32 hash_range_internal(TypeCacheEntry *typcache, const RangeType *r);
+extern uint64 hash_range_extended_internal(TypeCacheEntry *typcache, const RangeType *r,
+										   Datum seed);
+extern Datum range_lower_internal(TypeCacheEntry *typcache, const RangeType *r1,
+								  bool *isnull);
+extern Datum range_upper_internal(TypeCacheEntry *typcache, const RangeType *r1,
+								  bool *isnull);
 extern bool range_eq_internal(TypeCacheEntry *typcache, const RangeType *r1,
 							  const RangeType *r2);
 extern bool range_ne_internal(TypeCacheEntry *typcache, const RangeType *r1,
 							  const RangeType *r2);
+extern bool range_contains_elem_internal(TypeCacheEntry *typcache, const RangeType *r, Datum val);
 extern bool range_contains_internal(TypeCacheEntry *typcache, const RangeType *r1,
 									const RangeType *r2);
 extern bool range_contained_by_internal(TypeCacheEntry *typcache, const RangeType *r1,
@@ -117,6 +125,12 @@ extern bool range_overleft_internal(TypeCacheEntry *typcache, const RangeType *r
 									const RangeType *r2);
 extern bool range_overright_internal(TypeCacheEntry *typcache, const RangeType *r1,
 									 const RangeType *r2);
+extern RangeType *range_union_internal(TypeCacheEntry *typcache, RangeType *r1,
+									   RangeType *r2, bool strict);
+extern RangeType *range_minus_internal(TypeCacheEntry *typcache, RangeType *r1,
+									   RangeType *r2);
+extern RangeType *range_intersect_internal(TypeCacheEntry *typcache, const RangeType *r1,
+										   const RangeType *r2);
 
 /* assorted support functions */
 extern TypeCacheEntry *range_get_typcache(FunctionCallInfo fcinfo,
@@ -127,15 +141,20 @@ extern void range_deserialize(TypeCacheEntry *typcache, const RangeType *range,
 							  RangeBound *lower, RangeBound *upper,
 							  bool *empty);
 extern char range_get_flags(const RangeType *range);
+extern bool range_has_flag(const RangeType *range, char flag);
 extern void range_set_contain_empty(RangeType *range);
 extern RangeType *make_range(TypeCacheEntry *typcache, RangeBound *lower,
 							 RangeBound *upper, bool empty);
 extern int range_cmp_bounds(TypeCacheEntry *typcache, const RangeBound *b1,
-							const RangeBound *b2);
+							 const RangeBound *b2);
 extern int range_cmp_bound_values(TypeCacheEntry *typcache, const RangeBound *b1,
-								  const RangeBound *b2);
-extern bool bounds_adjacent(TypeCacheEntry *typcache, RangeBound bound1,
-							RangeBound bound2);
+								   const RangeBound *b2);
+extern int range_compare(const void *key1, const void *key2, void *arg);
+extern bool bounds_adjacent(TypeCacheEntry *typcache, const RangeBound bound1,
+							const RangeBound bound2);
 extern RangeType *make_empty_range(TypeCacheEntry *typcache);
+extern bool range_split_internal(TypeCacheEntry *typcache, const RangeType *r1,
+								 const RangeType *r2, RangeType **output1,
+								 RangeType **output2);
 
 #endif							/* RANGETYPES_H */
diff --git a/src/test/regress/expected/dependency.out b/src/test/regress/expected/dependency.out
index b4a11b8aa9..778699a961 100644
--- a/src/test/regress/expected/dependency.out
+++ b/src/test/regress/expected/dependency.out
@@ -141,6 +141,7 @@ owner of table deptest
 owner of function deptest_func()
 owner of type deptest_enum
 owner of type deptest_range
+owner of type deptest_multirange
 owner of table deptest2
 owner of sequence ss1
 owner of type deptest_t
diff --git a/src/test/regress/expected/multirangetypes.out b/src/test/regress/expected/multirangetypes.out
index 5749537635..2276d6d002 100644
--- a/src/test/regress/expected/multirangetypes.out
+++ b/src/test/regress/expected/multirangetypes.out
@@ -290,3 +290,2400 @@ select textmultirange(textrange('a', 'c'), textrange('b', 'd'));
  {[a,d)}
 (1 row)
 
+--
+-- create some test data and test the operators
+--
+CREATE TABLE nummultirange_test (nmr NUMMULTIRANGE);
+CREATE INDEX nummultirange_test_btree ON nummultirange_test(nmr);
+INSERT INTO nummultirange_test VALUES('{}');
+INSERT INTO nummultirange_test VALUES('{[,)}');
+INSERT INTO nummultirange_test VALUES('{[3,]}');
+INSERT INTO nummultirange_test VALUES('{[,), [3,]}');
+INSERT INTO nummultirange_test VALUES('{[, 5)}');
+INSERT INTO nummultirange_test VALUES(nummultirange());
+INSERT INTO nummultirange_test VALUES(nummultirange(variadic '{}'::numrange[]));
+INSERT INTO nummultirange_test VALUES(nummultirange(numrange(1.1, 2.2)));
+INSERT INTO nummultirange_test VALUES('{empty}');
+INSERT INTO nummultirange_test VALUES(nummultirange(numrange(1.7, 1.7, '[]'), numrange(1.7, 1.9)));
+INSERT INTO nummultirange_test VALUES(nummultirange(numrange(1.7, 1.7, '[]'), numrange(1.9, 2.1)));
+SELECT nmr, isempty(nmr), lower(nmr), upper(nmr) FROM nummultirange_test ORDER BY nmr;
+          nmr          | isempty | lower | upper 
+-----------------------+---------+-------+-------
+ {}                    | t       |       |      
+ {}                    | t       |       |      
+ {}                    | t       |       |      
+ {}                    | t       |       |      
+ {(,5)}                | f       |       |     5
+ {(,)}                 | f       |       |      
+ {(,)}                 | f       |       |      
+ {[1.1,2.2)}           | f       |   1.1 |   2.2
+ {[1.7,1.7],[1.9,2.1)} | f       |   1.7 |   2.1
+ {[1.7,1.9)}           | f       |   1.7 |   1.9
+ {[3,)}                | f       |     3 |      
+(11 rows)
+
+SELECT nmr, lower_inc(nmr), lower_inf(nmr), upper_inc(nmr), upper_inf(nmr) FROM nummultirange_test ORDER BY nmr;
+          nmr          | lower_inc | lower_inf | upper_inc | upper_inf 
+-----------------------+-----------+-----------+-----------+-----------
+ {}                    | f         | f         | f         | f
+ {}                    | f         | f         | f         | f
+ {}                    | f         | f         | f         | f
+ {}                    | f         | f         | f         | f
+ {(,5)}                | f         | t         | f         | f
+ {(,)}                 | f         | t         | f         | t
+ {(,)}                 | f         | t         | f         | t
+ {[1.1,2.2)}           | t         | f         | f         | f
+ {[1.7,1.7],[1.9,2.1)} | t         | f         | f         | f
+ {[1.7,1.9)}           | t         | f         | f         | f
+ {[3,)}                | t         | f         | f         | t
+(11 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr = '{}';
+ nmr 
+-----
+ {}
+ {}
+ {}
+ {}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr = '{(,5)}';
+  nmr   
+--------
+ {(,5)}
+(1 row)
+
+SELECT * FROM nummultirange_test WHERE nmr = '{[3,)}';
+  nmr   
+--------
+ {[3,)}
+(1 row)
+
+SELECT * FROM nummultirange_test WHERE nmr = '{[1.7,1.7]}';
+ nmr 
+-----
+(0 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr = '{[1.7,1.7],[1.9,2.1)}';
+          nmr          
+-----------------------
+ {[1.7,1.7],[1.9,2.1)}
+(1 row)
+
+SELECT * FROM nummultirange_test WHERE nmr < '{}';
+ nmr 
+-----
+(0 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr < '{[-1000.0, -1000.0]}';
+  nmr   
+--------
+ {}
+ {(,)}
+ {(,)}
+ {(,5)}
+ {}
+ {}
+ {}
+(7 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr < '{[0.0, 1.0]}';
+  nmr   
+--------
+ {}
+ {(,)}
+ {(,)}
+ {(,5)}
+ {}
+ {}
+ {}
+(7 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr < '{[1000.0, 1001.0]}';
+          nmr          
+-----------------------
+ {}
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+ {}
+ {}
+ {[1.1,2.2)}
+ {}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(11 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr <= '{}';
+ nmr 
+-----
+ {}
+ {}
+ {}
+ {}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr <= '{[3,)}';
+          nmr          
+-----------------------
+ {}
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+ {}
+ {}
+ {[1.1,2.2)}
+ {}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(11 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr >= '{}';
+          nmr          
+-----------------------
+ {}
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+ {}
+ {}
+ {[1.1,2.2)}
+ {}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(11 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr >= '{[3,)}';
+  nmr   
+--------
+ {[3,)}
+(1 row)
+
+SELECT * FROM nummultirange_test WHERE nmr > '{}';
+          nmr          
+-----------------------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+ {[1.1,2.2)}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(7 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr > '{[-1000.0, -1000.0]}';
+          nmr          
+-----------------------
+ {[3,)}
+ {[1.1,2.2)}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr > '{[0.0, 1.0]}';
+          nmr          
+-----------------------
+ {[3,)}
+ {[1.1,2.2)}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr > '{[1000.0, 1001.0]}';
+ nmr 
+-----
+(0 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr <> '{}';
+          nmr          
+-----------------------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+ {[1.1,2.2)}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(7 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr <> '{(,5)}';
+          nmr          
+-----------------------
+ {}
+ {(,)}
+ {[3,)}
+ {(,)}
+ {}
+ {}
+ {[1.1,2.2)}
+ {}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(10 rows)
+
+select nummultirange(numrange(2.0, 1.0));
+ERROR:  range lower bound must be less than or equal to range upper bound
+select nummultirange(numrange(5.0, 6.0), numrange(1.0, 2.0));
+     nummultirange     
+-----------------------
+ {[1.0,2.0),[5.0,6.0)}
+(1 row)
+
+-- overlaps
+SELECT * FROM nummultirange_test WHERE range_overlaps_multirange(numrange(4.0, 4.2), nmr);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE numrange(4.0, 4.2) && nmr;
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE multirange_overlaps_range(nmr, numrange(4.0, 4.2));
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr && numrange(4.0, 4.2);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE multirange_overlaps_multirange(nmr, nummultirange(numrange(4.0, 4.2), numrange(6.0, 7.0)));
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr && nummultirange(numrange(4.0, 4.2), numrange(6.0, 7.0));
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr && nummultirange(numrange(6.0, 7.0));
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+(3 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr && nummultirange(numrange(6.0, 7.0), numrange(8.0, 9.0));
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+(3 rows)
+
+-- mr contains x
+SELECT * FROM nummultirange_test WHERE multirange_contains_elem(nmr, 4.0);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr @> 4.0;
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE multirange_contains_range(nmr, numrange(4.0, 4.2));
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr @> numrange(4.0, 4.2);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE multirange_contains_multirange(nmr, '{[4.0,4.2), [6.0, 8.0)}');
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+(3 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr @> '{[4.0,4.2), [6.0, 8.0)}'::nummultirange;
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+(3 rows)
+
+-- x is contained by mr
+SELECT * FROM nummultirange_test WHERE elem_contained_by_multirange(4.0, nmr);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE 4.0 <@ nmr;
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE range_contained_by_multirange(numrange(4.0, 4.2), nmr);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE numrange(4.0, 4.2) <@ nmr;
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE multirange_contained_by_multirange('{[4.0,4.2), [6.0, 8.0)}', nmr);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+(3 rows)
+
+SELECT * FROM nummultirange_test WHERE '{[4.0,4.2), [6.0, 8.0)}'::nummultirange <@ nmr;
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+(3 rows)
+
+-- overlaps
+SELECT 'empty'::numrange && nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT 'empty'::numrange && nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() && 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) && 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() && nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() && nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) && nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) && nummultirange(numrange(1,2), numrange(7,8));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(7,8)) && nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) && nummultirange(numrange(1,2), numrange(3.5,8));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(3.5,8)) && numrange(3,4);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(3.5,8)) && nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+-- contains
+SELECT nummultirange() @> nummultirange();
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange() @> 'empty'::numrange;
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(null,null)) @> numrange(1,2);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(null,null)) @> numrange(null,2);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(null,null)) @> numrange(2,null);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(null,5)) @> numrange(null,3);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(null,5)) @> numrange(null,8);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(5,null)) @> numrange(8,null);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(5,null)) @> numrange(3,null);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,5)) @> numrange(8,9);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,5)) @> numrange(3,9);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,5)) @> numrange(1,4);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,5)) @> numrange(1,5);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(-4,-2), numrange(1,5)) @> numrange(1,5);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,5), numrange(8,9)) @> numrange(1,5);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,5), numrange(8,9)) @> numrange(6,7);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,5), numrange(6,9)) @> numrange(6,7);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[1,5)}'::nummultirange @> '{[1,5)}';
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[-4,-2), [1,5)}'::nummultirange @> '{[1,5)}';
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[1,5), [8,9)}'::nummultirange @> '{[1,5)}';
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[1,5), [8,9)}'::nummultirange @> '{[6,7)}';
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT '{[1,5), [6,9)}'::nummultirange @> '{[6,7)}';
+ ?column? 
+----------
+ t
+(1 row)
+
+-- is contained by
+SELECT nummultirange() <@ nummultirange();
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT 'empty'::numrange <@ nummultirange();
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,2) <@ nummultirange(numrange(null,null));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(null,2) <@ nummultirange(numrange(null,null));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(2,null) <@ nummultirange(numrange(null,null));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(null,3) <@ nummultirange(numrange(null,5));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(null,8) <@ nummultirange(numrange(null,5));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(8,null) <@ nummultirange(numrange(5,null));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(3,null) <@ nummultirange(numrange(5,null));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(8,9) <@ nummultirange(numrange(1,5));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(3,9) <@ nummultirange(numrange(1,5));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(1,4) <@ nummultirange(numrange(1,5));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,5) <@ nummultirange(numrange(1,5));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,5) <@ nummultirange(numrange(-4,-2), numrange(1,5));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,5) <@ nummultirange(numrange(1,5), numrange(8,9));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(6,7) <@ nummultirange(numrange(1,5), numrange(8,9));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(6,7) <@ nummultirange(numrange(1,5), numrange(6,9));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[1,5)}' <@ '{[1,5)}'::nummultirange;
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[1,5)}' <@ '{[-4,-2), [1,5)}'::nummultirange;
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[1,5)}' <@ '{[1,5), [8,9)}'::nummultirange;
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[6,7)}' <@ '{[1,5), [8,9)}'::nummultirange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT '{[6,7)}' <@ '{[1,5), [6,9)}'::nummultirange;
+ ?column? 
+----------
+ t
+(1 row)
+
+-- overleft
+SELECT 'empty'::numrange &< nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT 'empty'::numrange &< nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() &< 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) &< 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() &< nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) &< nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() &< nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(6,7) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(1,2) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,4) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,6) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(3.5,6) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(6,7)) &< numrange(3,4);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) &< numrange(3,4);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,4)) &< numrange(3,4);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,6)) &< numrange(3,4);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3.5,6)) &< numrange(3,4);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(6,7)) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,4)) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,6)) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3.5,6)) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+-- overright
+SELECT nummultirange() &> 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) &> 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT 'empty'::numrange &> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT 'empty'::numrange &> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() &> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() &> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) &> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> numrange(6,7);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> numrange(1,2);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> numrange(1,4);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> numrange(1,6);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> numrange(3.5,6);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(3,4) &> nummultirange(numrange(6,7));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(3,4) &> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(3,4) &> nummultirange(numrange(1,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(3,4) &> nummultirange(numrange(1,6));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(3,4) &> nummultirange(numrange(3.5,6));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(6,7));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(1,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(1,6));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(3.5,6));
+ ?column? 
+----------
+ f
+(1 row)
+
+-- meets
+SELECT 'empty'::numrange -|- nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT 'empty'::numrange -|- nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() -|- 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() -|- nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() -|- nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(1,2) -|- nummultirange(numrange(2,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,2) -|- nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- numrange(2,4);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- numrange(3,4);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- nummultirange(numrange(2,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(5,6)) -|- nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(5,6)) -|- nummultirange(numrange(6,7));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(5,6)) -|- nummultirange(numrange(8,9));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- nummultirange(numrange(2,4), numrange(6,7));
+ ?column? 
+----------
+ t
+(1 row)
+
+-- strictly left
+select 'empty'::numrange << nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(1,2) << nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(1,2) << nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+select numrange(1,2) << nummultirange(numrange(0,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(1,2) << nummultirange(numrange(0,4), numrange(7,8));
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange() << 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange() << numrange(1,2);
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(3,4)) << numrange(3,6);
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(0,2)) << numrange(3,6);
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange(numrange(0,2), numrange(7,8)) << numrange(3,6);
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(-4,-2), numrange(0,2)) << numrange(3,6);
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange() << nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange() << nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(1,2)) << nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(1,2)) << nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(1,2)) << nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange(numrange(1,2)) << nummultirange(numrange(3,4), numrange(7,8));
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange(numrange(1,2), numrange(4,5)) << nummultirange(numrange(3,4), numrange(7,8));
+ ?column? 
+----------
+ f
+(1 row)
+
+-- strictly right
+select nummultirange() >> 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange() >> numrange(1,2);
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(3,4)) >> numrange(1,2);
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange(numrange(0,4)) >> numrange(1,2);
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(0,4), numrange(7,8)) >> numrange(1,2);
+ ?column? 
+----------
+ f
+(1 row)
+
+select 'empty'::numrange >> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(1,2) >> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(3,6) >> nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(3,6) >> nummultirange(numrange(0,2));
+ ?column? 
+----------
+ t
+(1 row)
+
+select numrange(3,6) >> nummultirange(numrange(0,2), numrange(7,8));
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(3,6) >> nummultirange(numrange(-4,-2), numrange(0,2));
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange() >> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(1,2)) >> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange() >> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(1,2)) >> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(3,4)) >> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange(numrange(3,4), numrange(7,8)) >> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange(numrange(3,4), numrange(7,8)) >> nummultirange(numrange(1,2), numrange(4,5));
+ ?column? 
+----------
+ f
+(1 row)
+
+-- union
+SELECT 'empty'::numrange @+ 'empty'::numrange;
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT 'empty'::numrange @+ nummultirange();
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange() @+ 'empty'::numrange;
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange() @+ nummultirange();
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT 'empty'::numrange @+ numrange(1,2);
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT 'empty'::numrange @+ nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT numrange(1,2) @+ nummultirange();
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) @+ 'empty'::numrange;
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT numrange(1,2) @+ 'empty'::numrange;
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange() @+ numrange(1,2);
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange() @+ nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) @+ nummultirange();
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT numrange(1,2) @+ numrange(1,2);
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT numrange(1,2) @+ numrange(2,4);
+ ?column? 
+----------
+ {[1,4)}
+(1 row)
+
+SELECT numrange(1,2) @+ numrange(3,4);
+   ?column?    
+---------------
+ {[1,2),[3,4)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) @+ nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) @+ nummultirange(numrange(2,4));
+ ?column? 
+----------
+ {[1,4)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) @+ nummultirange(numrange(3,4));
+   ?column?    
+---------------
+ {[1,2),[3,4)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) @+ nummultirange(numrange(2,4));
+ ?column? 
+----------
+ {[1,5)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) @+ nummultirange(numrange(3,4));
+   ?column?    
+---------------
+ {[1,2),[3,5)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) @+ nummultirange(numrange(0,9));
+ ?column? 
+----------
+ {[0,9)}
+(1 row)
+
+-- merge
+SELECT range_merge(nummultirange());
+ range_merge 
+-------------
+ empty
+(1 row)
+
+SELECT range_merge(nummultirange(numrange(1,2)));
+ range_merge 
+-------------
+ [1,2)
+(1 row)
+
+SELECT range_merge(nummultirange(numrange(1,2), numrange(7,8)));
+ range_merge 
+-------------
+ [1,8)
+(1 row)
+
+-- minus
+SELECT 'empty'::numrange @- 'empty'::numrange;
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT 'empty'::numrange @- nummultirange();
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange() @- 'empty'::numrange;
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange() @- nummultirange();
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT 'empty'::numrange @- numrange(1,2);
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT 'empty'::numrange @- nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT numrange(1,2) @- nummultirange();
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) @- 'empty'::numrange;
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT 'empty'::numrange @- numrange(1,2);
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT numrange(1,2) @- 'empty'::numrange;
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange() @- numrange(1,2);
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange() @- nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) @- nummultirange();
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT numrange(1,3) @- numrange(1,3);
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT numrange(1,3) @- numrange(1,2);
+ ?column? 
+----------
+ {[2,3)}
+(1 row)
+
+SELECT numrange(1,3) @- numrange(2,4);
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT numrange(1,3) @- numrange(3,4);
+ ?column? 
+----------
+ {[1,3)}
+(1 row)
+
+SELECT numrange(1,3) @- nummultirange(numrange(1,3));
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT numrange(1,3) @- nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {[2,3)}
+(1 row)
+
+SELECT numrange(1,3) @- nummultirange(numrange(2,4));
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT numrange(1,3) @- nummultirange(numrange(3,4));
+ ?column? 
+----------
+ {[1,3)}
+(1 row)
+
+SELECT numrange(1,10) @- nummultirange(numrange(1,2), numrange(4,5));
+    ?column?    
+----------------
+ {[2,4),[5,10)}
+(1 row)
+
+SELECT nummultirange(numrange(1,10)) @- numrange(0,2);
+ ?column? 
+----------
+ {[2,10)}
+(1 row)
+
+SELECT nummultirange(numrange(1,10)) @- numrange(1,2);
+ ?column? 
+----------
+ {[2,10)}
+(1 row)
+
+SELECT nummultirange(numrange(1,10)) @- numrange(3,4);
+    ?column?    
+----------------
+ {[1,3),[4,10)}
+(1 row)
+
+SELECT nummultirange(numrange(1,10)) @- numrange(13,18);
+ ?column? 
+----------
+ {[1,10)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(3,4)) @- nummultirange();
+   ?column?    
+---------------
+ {[1,2),[3,4)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) @- nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) @- nummultirange(numrange(2,4));
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) @- nummultirange(numrange(3,4));
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,4)) @- nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {[2,4)}
+(1 row)
+
+SELECT nummultirange(numrange(1,4)) @- nummultirange(numrange(2,3));
+   ?column?    
+---------------
+ {[1,2),[3,4)}
+(1 row)
+
+SELECT nummultirange(numrange(1,4)) @- nummultirange(numrange(0,8));
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange(numrange(1,4)) @- nummultirange(numrange(0,2));
+ ?column? 
+----------
+ {[2,4)}
+(1 row)
+
+SELECT nummultirange(numrange(1,8)) @- nummultirange(numrange(0,2), numrange(3,4));
+   ?column?    
+---------------
+ {[2,3),[4,8)}
+(1 row)
+
+SELECT nummultirange(numrange(1,8)) @- nummultirange(numrange(2,3), numrange(5,null));
+   ?column?    
+---------------
+ {[1,2),[3,5)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) @- nummultirange(numrange(-2,0));
+   ?column?    
+---------------
+ {[1,2),[4,5)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) @- nummultirange(numrange(2,4));
+   ?column?    
+---------------
+ {[1,2),[4,5)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) @- nummultirange(numrange(3,5));
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) @- nummultirange(numrange(0,9));
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange(numrange(1,3), numrange(4,5)) @- nummultirange(numrange(2,9));
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) @- nummultirange(numrange(8,9));
+   ?column?    
+---------------
+ {[1,2),[4,5)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) @- nummultirange(numrange(-2,0), numrange(8,9));
+   ?column?    
+---------------
+ {[1,2),[4,5)}
+(1 row)
+
+-- intersection
+SELECT 'empty'::numrange @* 'empty'::numrange;
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT 'empty'::numrange @* nummultirange();
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange() @* 'empty'::numrange;
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange() @* nummultirange();
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT 'empty'::numrange @* numrange(1,2);
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT 'empty'::numrange @* nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT numrange(1,2) @* 'empty'::numrange;
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT numrange(1,2) @* nummultirange();
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) @* 'empty'::numrange;
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange() @* numrange(1,2);
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange() @* nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) @* nummultirange();
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT numrange(1,3) @* numrange(1,3);
+ ?column? 
+----------
+ {[1,3)}
+(1 row)
+
+SELECT numrange(1,3) @* numrange(1,2);
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT numrange(1,3) @* numrange(1,5);
+ ?column? 
+----------
+ {[1,3)}
+(1 row)
+
+SELECT numrange(1,3) @* numrange(2,5);
+ ?column? 
+----------
+ {[2,3)}
+(1 row)
+
+SELECT numrange(1,5) @* numrange(2,3);
+ ?column? 
+----------
+ {[2,3)}
+(1 row)
+
+SELECT '{[1,3)}'::nummultirange @* numrange(1,3);
+ ?column? 
+----------
+ {[1,3)}
+(1 row)
+
+SELECT '{[1,3)}'::nummultirange @* numrange(1,2);
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT '{[1,3)}'::nummultirange @* numrange(1,5);
+ ?column? 
+----------
+ {[1,3)}
+(1 row)
+
+SELECT '{[1,3)}'::nummultirange @* numrange(2,5);
+ ?column? 
+----------
+ {[2,3)}
+(1 row)
+
+SELECT '{[1,5)}'::nummultirange @* numrange(2,3);
+ ?column? 
+----------
+ {[2,3)}
+(1 row)
+
+SELECT numrange(1,3) @* '{[1,3)}'::nummultirange;
+ ?column? 
+----------
+ {[1,3)}
+(1 row)
+
+SELECT numrange(1,2) @* '{[1,3)}'::nummultirange;
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT numrange(1,5) @* '{[1,3)}'::nummultirange;
+ ?column? 
+----------
+ {[1,3)}
+(1 row)
+
+SELECT numrange(2,5) @* '{[1,3)}'::nummultirange;
+ ?column? 
+----------
+ {[2,3)}
+(1 row)
+
+SELECT numrange(2,3) @* '{[1,5)}'::nummultirange;
+ ?column? 
+----------
+ {[2,3)}
+(1 row)
+
+SELECT '{[1,3)}'::nummultirange @* '{[1,5)}'::nummultirange;
+ ?column? 
+----------
+ {[1,3)}
+(1 row)
+
+SELECT '{[1,3)}'::nummultirange @* '{[0,5)}'::nummultirange;
+ ?column? 
+----------
+ {[1,3)}
+(1 row)
+
+SELECT '{[1,3)}'::nummultirange @* '{[0,2)}'::nummultirange;
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT '{[1,3)}'::nummultirange @* '{[2,5)}'::nummultirange;
+ ?column? 
+----------
+ {[2,3)}
+(1 row)
+
+SELECT '{[1,4)}'::nummultirange @* '{[2,3)}'::nummultirange;
+ ?column? 
+----------
+ {[2,3)}
+(1 row)
+
+SELECT '{[1,4)}'::nummultirange @* '{[0,2), [3,5)}'::nummultirange;
+   ?column?    
+---------------
+ {[1,2),[3,4)}
+(1 row)
+
+SELECT '{[1,4), [7,10)}'::nummultirange @* '{[0,8), [9,12)}'::nummultirange;
+       ?column?       
+----------------------
+ {[1,4),[7,8),[9,10)}
+(1 row)
+
+SELECT '{[1,4), [7,10)}'::nummultirange @* '{[9,12)}'::nummultirange;
+ ?column? 
+----------
+ {[9,10)}
+(1 row)
+
+SELECT '{[1,4), [7,10)}'::nummultirange @* '{[-5,-4), [5,6), [9,12)}'::nummultirange;
+ ?column? 
+----------
+ {[9,10)}
+(1 row)
+
+SELECT '{[1,4), [7,10)}'::nummultirange @* '{[0,2), [3,8), [9,12)}'::nummultirange;
+          ?column?          
+----------------------------
+ {[1,2),[3,4),[7,8),[9,10)}
+(1 row)
+
+SELECT '{[1,4), [7,10)}'::nummultirange @* '{[0,2), [3,8), [9,12)}'::nummultirange;
+          ?column?          
+----------------------------
+ {[1,2),[3,4),[7,8),[9,10)}
+(1 row)
+
+--
+-- range_agg function
+--
+create table reservations ( room_id integer not null, booked_during daterange );
+insert into reservations values
+-- 1: has a meets and a gap
+(1, daterange('2018-07-01', '2018-07-07')),
+(1, daterange('2018-07-07', '2018-07-14')),
+(1, daterange('2018-07-20', '2018-07-22')),
+-- 2: just a single row
+(2, daterange('2018-07-01', '2018-07-03')),
+-- 3: one null range
+(3, NULL),
+-- 4: two null ranges
+(4, NULL),
+(4, NULL),
+-- 5: a null range and a non-null range
+(5, NULL),
+(5, daterange('2018-07-01', '2018-07-03')),
+-- 6: has overlap
+(6, daterange('2018-07-01', '2018-07-07')),
+(6, daterange('2018-07-05', '2018-07-10')),
+-- 7: two ranges that meet: no gap or overlap
+(7, daterange('2018-07-01', '2018-07-07')),
+(7, daterange('2018-07-07', '2018-07-14')),
+-- 8: an empty range
+(8, 'empty'::daterange)
+;
+SELECT   room_id, range_agg(booked_during)
+FROM     reservations
+GROUP BY room_id
+ORDER BY room_id;
+ room_id |                     range_agg                     
+---------+---------------------------------------------------
+       1 | {[07-01-2018,07-14-2018),[07-20-2018,07-22-2018)}
+       2 | {[07-01-2018,07-03-2018)}
+       3 | 
+       4 | 
+       5 | {[07-01-2018,07-03-2018)}
+       6 | {[07-01-2018,07-10-2018)}
+       7 | {[07-01-2018,07-14-2018)}
+       8 | {}
+(8 rows)
+
+-- range_agg on a custom range type too
+SELECT  range_agg(r)
+FROM    (VALUES
+          ('[a,c]'::textrange),
+          ('[b,b]'::textrange),
+          ('[c,f]'::textrange),
+          ('[g,h)'::textrange),
+          ('[h,j)'::textrange)
+        ) t(r);
+   range_agg   
+---------------
+ {[a,f],[g,j)}
+(1 row)
+
+select range_intersect_agg(nmr) from nummultirange_test;
+ range_intersect_agg 
+---------------------
+ {}
+(1 row)
+
+select range_intersect_agg(nmr) from nummultirange_test where false;
+ range_intersect_agg 
+---------------------
+ 
+(1 row)
+
+-- test with just one input:
+select range_intersect_agg(nmr) from (values ('{[1,2]}'::nummultirange)) t(nmr);
+ range_intersect_agg 
+---------------------
+ {[1,2]}
+(1 row)
+
+select range_intersect_agg(nmr) from nummultirange_test where nmr @> 4.0;
+ range_intersect_agg 
+---------------------
+ {[3,5)}
+(1 row)
+
+create table nummultirange_test2(nmr nummultirange);
+create index nummultirange_test2_hash_idx on nummultirange_test2 using hash (nmr);
+INSERT INTO nummultirange_test2 VALUES('{[, 5)}');
+INSERT INTO nummultirange_test2 VALUES(nummultirange(numrange(1.1, 2.2)));
+INSERT INTO nummultirange_test2 VALUES(nummultirange(numrange(1.1, 2.2)));
+INSERT INTO nummultirange_test2 VALUES(nummultirange(numrange(1.1, 2.2,'()')));
+INSERT INTO nummultirange_test2 VALUES('{}');
+select * from nummultirange_test2 where nmr = '{}';
+ nmr 
+-----
+ {}
+(1 row)
+
+select * from nummultirange_test2 where nmr = nummultirange(numrange(1.1, 2.2));
+     nmr     
+-------------
+ {[1.1,2.2)}
+ {[1.1,2.2)}
+(2 rows)
+
+select * from nummultirange_test2 where nmr = nummultirange(numrange(1.1, 2.3));
+ nmr 
+-----
+(0 rows)
+
+set enable_nestloop=t;
+set enable_hashjoin=f;
+set enable_mergejoin=f;
+select * from nummultirange_test natural join nummultirange_test2 order by nmr;
+     nmr     
+-------------
+ {}
+ {}
+ {}
+ {}
+ {(,5)}
+ {[1.1,2.2)}
+ {[1.1,2.2)}
+(7 rows)
+
+set enable_nestloop=f;
+set enable_hashjoin=t;
+set enable_mergejoin=f;
+select * from nummultirange_test natural join nummultirange_test2 order by nmr;
+     nmr     
+-------------
+ {}
+ {}
+ {}
+ {}
+ {(,5)}
+ {[1.1,2.2)}
+ {[1.1,2.2)}
+(7 rows)
+
+set enable_nestloop=f;
+set enable_hashjoin=f;
+set enable_mergejoin=t;
+select * from nummultirange_test natural join nummultirange_test2 order by nmr;
+     nmr     
+-------------
+ {}
+ {}
+ {}
+ {}
+ {(,5)}
+ {[1.1,2.2)}
+ {[1.1,2.2)}
+(7 rows)
+
+set enable_nestloop to default;
+set enable_hashjoin to default;
+set enable_mergejoin to default;
+DROP TABLE nummultirange_test2;
+--
+-- Test user-defined multirange of floats
+--
+select '{[123.001, 5.e9)}'::float8multirange @> 888.882::float8;
+ ?column? 
+----------
+ t
+(1 row)
+
+create table float8multirange_test(f8mr float8multirange, i int);
+insert into float8multirange_test values(float8multirange(float8range(-100.00007, '1.111113e9')), 42);
+select * from float8multirange_test;
+           f8mr            | i  
+---------------------------+----
+ {[-100.00007,1111113000)} | 42
+(1 row)
+
+drop table float8multirange_test;
+--
+-- Test multirange types over domains
+--
+create domain mydomain as int4;
+create type mydomainrange as range(subtype=mydomain);
+select '{[4,50)}'::mydomainmultirange @> 7::mydomain;
+ ?column? 
+----------
+ t
+(1 row)
+
+drop domain mydomain cascade;
+NOTICE:  drop cascades to type mydomainrange
+--
+-- Test domains over multirange types
+--
+create domain restrictedmultirange as int4multirange check (upper(value) < 10);
+select '{[4,5)}'::restrictedmultirange @> 7;
+ ?column? 
+----------
+ f
+(1 row)
+
+select '{[4,50)}'::restrictedmultirange @> 7; -- should fail
+ERROR:  value for domain restrictedmultirange violates check constraint "restrictedmultirange_check"
+drop domain restrictedmultirange;
+--
+-- Test multiple multirange types over the same subtype
+--
+create type textrange1 as range(subtype=text, collation="C");
+create type textrange2 as range(subtype=text, collation="C");
+select textmultirange1(textrange2('a','Z'));  -- should fail
+ERROR:  function textmultirange1(textrange2) does not exist
+LINE 1: select textmultirange1(textrange2('a','Z'));
+               ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+select textmultirange1(textrange1('a','Z')) @> 'b'::text;
+ERROR:  range lower bound must be less than or equal to range upper bound
+select textmultirange2(textrange2('a','z')) @> 'b'::text;
+ ?column? 
+----------
+ t
+(1 row)
+
+drop type textrange1;
+drop type textrange2;
+--
+-- Test polymorphic type system
+--
+create function anyarray_anymultirange_func(a anyarray, r anymultirange)
+  returns anyelement as 'select $1[1] + lower($2);' language sql;
+select anyarray_anymultirange_func(ARRAY[1,2], int4multirange(int4range(10,20)));
+ anyarray_anymultirange_func 
+-----------------------------
+                          11
+(1 row)
+
+-- should fail
+select anyarray_anymultirange_func(ARRAY[1,2], nummultirange(numrange(10,20)));
+ERROR:  function anyarray_anymultirange_func(integer[], nummultirange) does not exist
+LINE 1: select anyarray_anymultirange_func(ARRAY[1,2], nummultirange...
+               ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+drop function anyarray_anymultirange_func(anyarray, anymultirange);
+-- should fail
+create function bogus_func(anyelement)
+  returns anymultirange as 'select int4multirange(int4range(1,10))' language sql;
+ERROR:  cannot determine result data type
+DETAIL:  A function returning "anyrange" or "anymultirange" must have at least one "anyrange" or "anymultirange" argument.
+-- should fail
+create function bogus_func(int)
+  returns anymultirange as 'select int4multirange(int4range(1,10))' language sql;
+ERROR:  cannot determine result data type
+DETAIL:  A function returning a polymorphic type must have at least one polymorphic argument.
+create function range_add_bounds(anymultirange)
+  returns anyelement as 'select lower($1) + upper($1)' language sql;
+select range_add_bounds(int4multirange(int4range(1, 17)));
+ range_add_bounds 
+------------------
+               18
+(1 row)
+
+select range_add_bounds(nummultirange(numrange(1.0001, 123.123)));
+ range_add_bounds 
+------------------
+         124.1231
+(1 row)
+
+create function multirangetypes_sql(q anymultirange, b anyarray, out c anyelement)
+  as $$ select upper($1) + $2[1] $$
+  language sql;
+select multirangetypes_sql(int4multirange(int4range(1,10)), ARRAY[2,20]);
+ multirangetypes_sql 
+---------------------
+                  12
+(1 row)
+
+select multirangetypes_sql(nummultirange(numrange(1,10)), ARRAY[2,20]);  -- match failure
+ERROR:  function multirangetypes_sql(nummultirange, integer[]) does not exist
+LINE 1: select multirangetypes_sql(nummultirange(numrange(1,10)), AR...
+               ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+--
+-- Arrays of multiranges
+--
+select ARRAY[nummultirange(numrange(1.1, 1.2)), nummultirange(numrange(12.3, 155.5))];
+              array               
+----------------------------------
+ {"{[1.1,1.2)}","{[12.3,155.5)}"}
+(1 row)
+
+create table i8mr_array (f1 int, f2 int8multirange[]);
+insert into i8mr_array values (42, array[int8multirange(int8range(1,10)), int8multirange(int8range(2,20))]);
+select * from i8mr_array;
+ f1 |           f2            
+----+-------------------------
+ 42 | {"{[1,10)}","{[2,20)}"}
+(1 row)
+
+drop table i8mr_array;
+--
+-- Multiranges of arrays
+--
+select arraymultirange(arrayrange(ARRAY[1,2], ARRAY[2,1]));
+   arraymultirange   
+---------------------
+ {["{1,2}","{2,1}")}
+(1 row)
+
+select arraymultirange(arrayrange(ARRAY[2,1], ARRAY[1,2]));  -- fail
+ERROR:  range lower bound must be less than or equal to range upper bound
+select array[1,1] <@ arraymultirange(arrayrange(array[1,2], array[2,1]));
+ ?column? 
+----------
+ f
+(1 row)
+
+select array[1,3] <@ arraymultirange(arrayrange(array[1,2], array[2,1]));
+ ?column? 
+----------
+ t
+(1 row)
+
+--
+-- Ranges of composites
+--
+create type two_ints as (a int, b int);
+create type two_ints_range as range (subtype = two_ints);
+-- with force_parallel_mode on, this exercises tqueue.c's range remapping
+select *, row_to_json(upper(t)) as u from
+  (values (two_ints_multirange(two_ints_range(row(1,2), row(3,4)))),
+          (two_ints_multirange(two_ints_range(row(5,6), row(7,8))))) v(t);
+          t          |       u       
+---------------------+---------------
+ {["(1,2)","(3,4)")} | {"a":3,"b":4}
+ {["(5,6)","(7,8)")} | {"a":7,"b":8}
+(2 rows)
+
+drop type two_ints cascade;
+NOTICE:  drop cascades to type two_ints_range
+--
+-- Check behavior when subtype lacks a hash function
+--
+set enable_sort = off;  -- try to make it pick a hash setop implementation
+select '{(2,5)}'::cashmultirange except select '{(5,6)}'::cashmultirange;
+ cashmultirange  
+-----------------
+ {($2.00,$5.00)}
+(1 row)
+
+reset enable_sort;
+--
+-- OUT/INOUT/TABLE functions
+--
+-- infer anymultirange from anymultirange
+create function mr_outparam_succeed(i anymultirange, out r anymultirange, out t text)
+  as $$ select $1, 'foo'::text $$ language sql;
+select * from mr_outparam_succeed(int4multirange(int4range(1,2)));
+    r    |  t  
+---------+-----
+ {[1,2)} | foo
+(1 row)
+
+-- infer anyarray from anymultirange
+create function mr_outparam_succeed2(i anymultirange, out r anyarray, out t text)
+  as $$ select ARRAY[upper($1)], 'foo'::text $$ language sql;
+select * from mr_outparam_succeed2(int4multirange(int4range(1,2)));
+  r  |  t  
+-----+-----
+ {2} | foo
+(1 row)
+
+-- infer anyrange from anymultirange
+create function mr_outparam_succeed3(i anymultirange, out r anyrange, out t text)
+  as $$ select range_merge($1), 'foo'::text $$ language sql;
+select * from mr_outparam_succeed3(int4multirange(int4range(1,2)));
+   r   |  t  
+-------+-----
+ [1,2) | foo
+(1 row)
+
+-- infer anymultirange from anyrange
+create function mr_outparam_succeed4(i anyrange, out r anymultirange, out t text)
+  as $$ select $1 @+ $1, 'foo'::text $$ language sql;
+select * from mr_outparam_succeed4(int4range(1,2));
+    r    |  t  
+---------+-----
+ {[1,2)} | foo
+(1 row)
+
+-- infer anyelement from anymultirange
+create function mr_inoutparam_succeed(out i anyelement, inout r anymultirange)
+  as $$ select upper($1), $1 $$ language sql;
+select * from mr_inoutparam_succeed(int4multirange(int4range(1,2)));
+ i |    r    
+---+---------
+ 2 | {[1,2)}
+(1 row)
+
+-- infer anyelement+anymultirange from anyelement+anymultirange
+create function mr_table_succeed(i anyelement, r anymultirange) returns table(i anyelement, r anymultirange)
+  as $$ select $1, $2 $$ language sql;
+select * from mr_table_succeed(123, int4multirange(int4range(1,11)));
+  i  |    r     
+-----+----------
+ 123 | {[1,11)}
+(1 row)
+
+-- should fail
+create function mr_outparam_fail(i anyelement, out r anymultirange, out t text)
+  as $$ select '[1,10]', 'foo' $$ language sql;
+ERROR:  cannot determine result data type
+DETAIL:  A function returning "anyrange" or "anymultirange" must have at least one "anyrange" or "anymultirange" argument.
+--should fail
+create function mr_inoutparam_fail(inout i anyelement, out r anymultirange)
+  as $$ select $1, '[1,10]' $$ language sql;
+ERROR:  cannot determine result data type
+DETAIL:  A function returning "anyrange" or "anymultirange" must have at least one "anyrange" or "anymultirange" argument.
+--should fail
+create function mr_table_fail(i anyelement) returns table(i anyelement, r anymultirange)
+  as $$ select $1, '[1,10]' $$ language sql;
+ERROR:  cannot determine result data type
+DETAIL:  A function returning "anyrange" or "anymultirange" must have at least one "anyrange" or "anymultirange" argument.
diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out
index 916e1016b5..e818eaea79 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -1101,13 +1101,15 @@ ORDER BY 1, 2;
  ?|   | ?|
  ?||  | ?||
  @    | ~
+ @*   | @*
+ @+   | @+
  @@   | @@
  @@@  | @@@
  |    | |
  ~<=~ | ~>=~
  ~<~  | ~>~
  ~=   | ~=
-(30 rows)
+(32 rows)
 
 -- Likewise for negator pairs.
 SELECT DISTINCT o1.oprname AS op1, o2.oprname AS op2
diff --git a/src/test/regress/expected/rangetypes.out b/src/test/regress/expected/rangetypes.out
index 6fd16bddd1..1bfe426be6 100644
--- a/src/test/regress/expected/rangetypes.out
+++ b/src/test/regress/expected/rangetypes.out
@@ -518,6 +518,24 @@ select numrange(1.0, 2.0) * numrange(2.5, 3.0);
  empty
 (1 row)
 
+select range_intersect_agg(nr) from numrange_test;
+ range_intersect_agg 
+---------------------
+ empty
+(1 row)
+
+select range_intersect_agg(nr) from numrange_test where false;
+ range_intersect_agg 
+---------------------
+ 
+(1 row)
+
+select range_intersect_agg(nr) from numrange_test where nr @> 4.0;
+ range_intersect_agg 
+---------------------
+ [3,5)
+(1 row)
+
 create table numrange_test2(nr numrange);
 create index numrange_test2_hash_idx on numrange_test2 using hash (nr);
 INSERT INTO numrange_test2 VALUES('[, 5)');
@@ -1284,7 +1302,7 @@ drop function anyarray_anyrange_func(anyarray, anyrange);
 create function bogus_func(anyelement)
   returns anyrange as 'select int4range(1,10)' language sql;
 ERROR:  cannot determine result data type
-DETAIL:  A function returning "anyrange" must have at least one "anyrange" argument.
+DETAIL:  A function returning "anyrange" or "anymultirange" must have at least one "anyrange" or "anymultirange" argument.
 -- should fail
 create function bogus_func(int)
   returns anyrange as 'select int4range(1,10)' language sql;
@@ -1392,6 +1410,7 @@ reset enable_sort;
 --
 -- OUT/INOUT/TABLE functions
 --
+-- infer anyrange from anyrange
 create function outparam_succeed(i anyrange, out r anyrange, out t text)
   as $$ select $1, 'foo'::text $$ language sql;
 select * from outparam_succeed(int4range(1,2));
@@ -1400,6 +1419,16 @@ select * from outparam_succeed(int4range(1,2));
  [1,2) | foo
 (1 row)
 
+-- infer anyarray from anyrange
+create function outparam_succeed2(i anyrange, out r anyarray, out t text)
+  as $$ select ARRAY[upper($1)], 'foo'::text $$ language sql;
+select * from outparam_succeed2(int4range(int4range(1,2)));
+  r  |  t  
+-----+-----
+ {2} | foo
+(1 row)
+
+-- infer anyelement from anyrange
 create function inoutparam_succeed(out i anyelement, inout r anyrange)
   as $$ select upper($1), $1 $$ language sql;
 select * from inoutparam_succeed(int4range(1,2));
@@ -1408,6 +1437,7 @@ select * from inoutparam_succeed(int4range(1,2));
  2 | [1,2)
 (1 row)
 
+-- infer anyelement+anyrange from anyelement+anyrange
 create function table_succeed(i anyelement, r anyrange) returns table(i anyelement, r anyrange)
   as $$ select $1, $2 $$ language sql;
 select * from table_succeed(123, int4range(1,11));
@@ -1420,14 +1450,14 @@ select * from table_succeed(123, int4range(1,11));
 create function outparam_fail(i anyelement, out r anyrange, out t text)
   as $$ select '[1,10]', 'foo' $$ language sql;
 ERROR:  cannot determine result data type
-DETAIL:  A function returning "anyrange" must have at least one "anyrange" argument.
+DETAIL:  A function returning "anyrange" or "anymultirange" must have at least one "anyrange" or "anymultirange" argument.
 --should fail
 create function inoutparam_fail(inout i anyelement, out r anyrange)
   as $$ select $1, '[1,10]' $$ language sql;
 ERROR:  cannot determine result data type
-DETAIL:  A function returning "anyrange" must have at least one "anyrange" argument.
+DETAIL:  A function returning "anyrange" or "anymultirange" must have at least one "anyrange" or "anymultirange" argument.
 --should fail
 create function table_fail(i anyelement) returns table(i anyelement, r anyrange)
   as $$ select $1, '[1,10]' $$ language sql;
 ERROR:  cannot determine result data type
-DETAIL:  A function returning "anyrange" must have at least one "anyrange" argument.
+DETAIL:  A function returning "anyrange" or "anymultirange" must have at least one "anyrange" or "anymultirange" argument.
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index 070de78e85..6f7fcf6326 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -92,6 +92,7 @@ num_exp_sqrt|t
 num_exp_sub|t
 num_input_test|f
 num_result|f
+nummultirange_test|t
 onek|t
 onek2|t
 path_tbl|f
@@ -172,6 +173,7 @@ quad_poly_tbl|t
 radix_text_tbl|t
 ramp|f
 real_city|f
+reservations|f
 road|t
 shighway|t
 slow_emp4000|f
diff --git a/src/test/regress/sql/multirangetypes.sql b/src/test/regress/sql/multirangetypes.sql
index 8651ba3f3d..b3ede18f96 100644
--- a/src/test/regress/sql/multirangetypes.sql
+++ b/src/test/regress/sql/multirangetypes.sql
@@ -63,3 +63,610 @@ select textmultirange();
 select textmultirange(textrange('a', 'c'));
 select textmultirange(textrange('a', 'c'), textrange('f', 'g'));
 select textmultirange(textrange('a', 'c'), textrange('b', 'd'));
+
+--
+-- create some test data and test the operators
+--
+
+CREATE TABLE nummultirange_test (nmr NUMMULTIRANGE);
+CREATE INDEX nummultirange_test_btree ON nummultirange_test(nmr);
+
+INSERT INTO nummultirange_test VALUES('{}');
+INSERT INTO nummultirange_test VALUES('{[,)}');
+INSERT INTO nummultirange_test VALUES('{[3,]}');
+INSERT INTO nummultirange_test VALUES('{[,), [3,]}');
+INSERT INTO nummultirange_test VALUES('{[, 5)}');
+INSERT INTO nummultirange_test VALUES(nummultirange());
+INSERT INTO nummultirange_test VALUES(nummultirange(variadic '{}'::numrange[]));
+INSERT INTO nummultirange_test VALUES(nummultirange(numrange(1.1, 2.2)));
+INSERT INTO nummultirange_test VALUES('{empty}');
+INSERT INTO nummultirange_test VALUES(nummultirange(numrange(1.7, 1.7, '[]'), numrange(1.7, 1.9)));
+INSERT INTO nummultirange_test VALUES(nummultirange(numrange(1.7, 1.7, '[]'), numrange(1.9, 2.1)));
+
+SELECT nmr, isempty(nmr), lower(nmr), upper(nmr) FROM nummultirange_test ORDER BY nmr;
+SELECT nmr, lower_inc(nmr), lower_inf(nmr), upper_inc(nmr), upper_inf(nmr) FROM nummultirange_test ORDER BY nmr;
+
+SELECT * FROM nummultirange_test WHERE nmr = '{}';
+SELECT * FROM nummultirange_test WHERE nmr = '{(,5)}';
+SELECT * FROM nummultirange_test WHERE nmr = '{[3,)}';
+SELECT * FROM nummultirange_test WHERE nmr = '{[1.7,1.7]}';
+SELECT * FROM nummultirange_test WHERE nmr = '{[1.7,1.7],[1.9,2.1)}';
+SELECT * FROM nummultirange_test WHERE nmr < '{}';
+SELECT * FROM nummultirange_test WHERE nmr < '{[-1000.0, -1000.0]}';
+SELECT * FROM nummultirange_test WHERE nmr < '{[0.0, 1.0]}';
+SELECT * FROM nummultirange_test WHERE nmr < '{[1000.0, 1001.0]}';
+SELECT * FROM nummultirange_test WHERE nmr <= '{}';
+SELECT * FROM nummultirange_test WHERE nmr <= '{[3,)}';
+SELECT * FROM nummultirange_test WHERE nmr >= '{}';
+SELECT * FROM nummultirange_test WHERE nmr >= '{[3,)}';
+SELECT * FROM nummultirange_test WHERE nmr > '{}';
+SELECT * FROM nummultirange_test WHERE nmr > '{[-1000.0, -1000.0]}';
+SELECT * FROM nummultirange_test WHERE nmr > '{[0.0, 1.0]}';
+SELECT * FROM nummultirange_test WHERE nmr > '{[1000.0, 1001.0]}';
+SELECT * FROM nummultirange_test WHERE nmr <> '{}';
+SELECT * FROM nummultirange_test WHERE nmr <> '{(,5)}';
+
+select nummultirange(numrange(2.0, 1.0));
+select nummultirange(numrange(5.0, 6.0), numrange(1.0, 2.0));
+
+-- overlaps
+SELECT * FROM nummultirange_test WHERE range_overlaps_multirange(numrange(4.0, 4.2), nmr);
+SELECT * FROM nummultirange_test WHERE numrange(4.0, 4.2) && nmr;
+SELECT * FROM nummultirange_test WHERE multirange_overlaps_range(nmr, numrange(4.0, 4.2));
+SELECT * FROM nummultirange_test WHERE nmr && numrange(4.0, 4.2);
+SELECT * FROM nummultirange_test WHERE multirange_overlaps_multirange(nmr, nummultirange(numrange(4.0, 4.2), numrange(6.0, 7.0)));
+SELECT * FROM nummultirange_test WHERE nmr && nummultirange(numrange(4.0, 4.2), numrange(6.0, 7.0));
+SELECT * FROM nummultirange_test WHERE nmr && nummultirange(numrange(6.0, 7.0));
+SELECT * FROM nummultirange_test WHERE nmr && nummultirange(numrange(6.0, 7.0), numrange(8.0, 9.0));
+
+-- mr contains x
+SELECT * FROM nummultirange_test WHERE multirange_contains_elem(nmr, 4.0);
+SELECT * FROM nummultirange_test WHERE nmr @> 4.0;
+SELECT * FROM nummultirange_test WHERE multirange_contains_range(nmr, numrange(4.0, 4.2));
+SELECT * FROM nummultirange_test WHERE nmr @> numrange(4.0, 4.2);
+SELECT * FROM nummultirange_test WHERE multirange_contains_multirange(nmr, '{[4.0,4.2), [6.0, 8.0)}');
+SELECT * FROM nummultirange_test WHERE nmr @> '{[4.0,4.2), [6.0, 8.0)}'::nummultirange;
+
+-- x is contained by mr
+SELECT * FROM nummultirange_test WHERE elem_contained_by_multirange(4.0, nmr);
+SELECT * FROM nummultirange_test WHERE 4.0 <@ nmr;
+SELECT * FROM nummultirange_test WHERE range_contained_by_multirange(numrange(4.0, 4.2), nmr);
+SELECT * FROM nummultirange_test WHERE numrange(4.0, 4.2) <@ nmr;
+SELECT * FROM nummultirange_test WHERE multirange_contained_by_multirange('{[4.0,4.2), [6.0, 8.0)}', nmr);
+SELECT * FROM nummultirange_test WHERE '{[4.0,4.2), [6.0, 8.0)}'::nummultirange <@ nmr;
+
+-- overlaps
+SELECT 'empty'::numrange && nummultirange();
+SELECT 'empty'::numrange && nummultirange(numrange(1,2));
+SELECT nummultirange() && 'empty'::numrange;
+SELECT nummultirange(numrange(1,2)) && 'empty'::numrange;
+SELECT nummultirange() && nummultirange();
+SELECT nummultirange() && nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) && nummultirange();
+SELECT nummultirange(numrange(3,4)) && nummultirange(numrange(1,2), numrange(7,8));
+SELECT nummultirange(numrange(1,2), numrange(7,8)) && nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(3,4)) && nummultirange(numrange(1,2), numrange(3.5,8));
+SELECT nummultirange(numrange(1,2), numrange(3.5,8)) && numrange(3,4);
+SELECT nummultirange(numrange(1,2), numrange(3.5,8)) && nummultirange(numrange(3,4));
+
+-- contains
+SELECT nummultirange() @> nummultirange();
+SELECT nummultirange() @> 'empty'::numrange;
+SELECT nummultirange(numrange(null,null)) @> numrange(1,2);
+SELECT nummultirange(numrange(null,null)) @> numrange(null,2);
+SELECT nummultirange(numrange(null,null)) @> numrange(2,null);
+SELECT nummultirange(numrange(null,5)) @> numrange(null,3);
+SELECT nummultirange(numrange(null,5)) @> numrange(null,8);
+SELECT nummultirange(numrange(5,null)) @> numrange(8,null);
+SELECT nummultirange(numrange(5,null)) @> numrange(3,null);
+SELECT nummultirange(numrange(1,5)) @> numrange(8,9);
+SELECT nummultirange(numrange(1,5)) @> numrange(3,9);
+SELECT nummultirange(numrange(1,5)) @> numrange(1,4);
+SELECT nummultirange(numrange(1,5)) @> numrange(1,5);
+SELECT nummultirange(numrange(-4,-2), numrange(1,5)) @> numrange(1,5);
+SELECT nummultirange(numrange(1,5), numrange(8,9)) @> numrange(1,5);
+SELECT nummultirange(numrange(1,5), numrange(8,9)) @> numrange(6,7);
+SELECT nummultirange(numrange(1,5), numrange(6,9)) @> numrange(6,7);
+SELECT '{[1,5)}'::nummultirange @> '{[1,5)}';
+SELECT '{[-4,-2), [1,5)}'::nummultirange @> '{[1,5)}';
+SELECT '{[1,5), [8,9)}'::nummultirange @> '{[1,5)}';
+SELECT '{[1,5), [8,9)}'::nummultirange @> '{[6,7)}';
+SELECT '{[1,5), [6,9)}'::nummultirange @> '{[6,7)}';
+
+-- is contained by
+SELECT nummultirange() <@ nummultirange();
+SELECT 'empty'::numrange <@ nummultirange();
+SELECT numrange(1,2) <@ nummultirange(numrange(null,null));
+SELECT numrange(null,2) <@ nummultirange(numrange(null,null));
+SELECT numrange(2,null) <@ nummultirange(numrange(null,null));
+SELECT numrange(null,3) <@ nummultirange(numrange(null,5));
+SELECT numrange(null,8) <@ nummultirange(numrange(null,5));
+SELECT numrange(8,null) <@ nummultirange(numrange(5,null));
+SELECT numrange(3,null) <@ nummultirange(numrange(5,null));
+SELECT numrange(8,9) <@ nummultirange(numrange(1,5));
+SELECT numrange(3,9) <@ nummultirange(numrange(1,5));
+SELECT numrange(1,4) <@ nummultirange(numrange(1,5));
+SELECT numrange(1,5) <@ nummultirange(numrange(1,5));
+SELECT numrange(1,5) <@ nummultirange(numrange(-4,-2), numrange(1,5));
+SELECT numrange(1,5) <@ nummultirange(numrange(1,5), numrange(8,9));
+SELECT numrange(6,7) <@ nummultirange(numrange(1,5), numrange(8,9));
+SELECT numrange(6,7) <@ nummultirange(numrange(1,5), numrange(6,9));
+SELECT '{[1,5)}' <@ '{[1,5)}'::nummultirange;
+SELECT '{[1,5)}' <@ '{[-4,-2), [1,5)}'::nummultirange;
+SELECT '{[1,5)}' <@ '{[1,5), [8,9)}'::nummultirange;
+SELECT '{[6,7)}' <@ '{[1,5), [8,9)}'::nummultirange;
+SELECT '{[6,7)}' <@ '{[1,5), [6,9)}'::nummultirange;
+
+-- overleft
+SELECT 'empty'::numrange &< nummultirange();
+SELECT 'empty'::numrange &< nummultirange(numrange(1,2));
+SELECT nummultirange() &< 'empty'::numrange;
+SELECT nummultirange(numrange(1,2)) &< 'empty'::numrange;
+SELECT nummultirange() &< nummultirange();
+SELECT nummultirange(numrange(1,2)) &< nummultirange();
+SELECT nummultirange() &< nummultirange(numrange(1,2));
+SELECT numrange(6,7) &< nummultirange(numrange(3,4));
+SELECT numrange(1,2) &< nummultirange(numrange(3,4));
+SELECT numrange(1,4) &< nummultirange(numrange(3,4));
+SELECT numrange(1,6) &< nummultirange(numrange(3,4));
+SELECT numrange(3.5,6) &< nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(6,7)) &< numrange(3,4);
+SELECT nummultirange(numrange(1,2)) &< numrange(3,4);
+SELECT nummultirange(numrange(1,4)) &< numrange(3,4);
+SELECT nummultirange(numrange(1,6)) &< numrange(3,4);
+SELECT nummultirange(numrange(3.5,6)) &< numrange(3,4);
+SELECT nummultirange(numrange(6,7)) &< nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,2)) &< nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,4)) &< nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,6)) &< nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(3.5,6)) &< nummultirange(numrange(3,4));
+
+-- overright
+SELECT nummultirange() &> 'empty'::numrange;
+SELECT nummultirange(numrange(1,2)) &> 'empty'::numrange;
+SELECT 'empty'::numrange &> nummultirange();
+SELECT 'empty'::numrange &> nummultirange(numrange(1,2));
+SELECT nummultirange() &> nummultirange();
+SELECT nummultirange() &> nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) &> nummultirange();
+SELECT nummultirange(numrange(3,4)) &> numrange(6,7);
+SELECT nummultirange(numrange(3,4)) &> numrange(1,2);
+SELECT nummultirange(numrange(3,4)) &> numrange(1,4);
+SELECT nummultirange(numrange(3,4)) &> numrange(1,6);
+SELECT nummultirange(numrange(3,4)) &> numrange(3.5,6);
+SELECT numrange(3,4) &> nummultirange(numrange(6,7));
+SELECT numrange(3,4) &> nummultirange(numrange(1,2));
+SELECT numrange(3,4) &> nummultirange(numrange(1,4));
+SELECT numrange(3,4) &> nummultirange(numrange(1,6));
+SELECT numrange(3,4) &> nummultirange(numrange(3.5,6));
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(6,7));
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(1,4));
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(1,6));
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(3.5,6));
+
+-- meets
+SELECT 'empty'::numrange -|- nummultirange();
+SELECT 'empty'::numrange -|- nummultirange(numrange(1,2));
+SELECT nummultirange() -|- 'empty'::numrange;
+SELECT nummultirange(numrange(1,2)) -|- 'empty'::numrange;
+SELECT nummultirange() -|- nummultirange();
+SELECT nummultirange(numrange(1,2)) -|- nummultirange();
+SELECT nummultirange() -|- nummultirange(numrange(1,2));
+SELECT numrange(1,2) -|- nummultirange(numrange(2,4));
+SELECT numrange(1,2) -|- nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,2)) -|- numrange(2,4);
+SELECT nummultirange(numrange(1,2)) -|- numrange(3,4);
+SELECT nummultirange(numrange(1,2)) -|- nummultirange(numrange(2,4));
+SELECT nummultirange(numrange(1,2)) -|- nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,2), numrange(5,6)) -|- nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,2), numrange(5,6)) -|- nummultirange(numrange(6,7));
+SELECT nummultirange(numrange(1,2), numrange(5,6)) -|- nummultirange(numrange(8,9));
+SELECT nummultirange(numrange(1,2)) -|- nummultirange(numrange(2,4), numrange(6,7));
+
+-- strictly left
+select 'empty'::numrange << nummultirange();
+select numrange(1,2) << nummultirange();
+select numrange(1,2) << nummultirange(numrange(3,4));
+select numrange(1,2) << nummultirange(numrange(0,4));
+select numrange(1,2) << nummultirange(numrange(0,4), numrange(7,8));
+select nummultirange() << 'empty'::numrange;
+select nummultirange() << numrange(1,2);
+select nummultirange(numrange(3,4)) << numrange(3,6);
+select nummultirange(numrange(0,2)) << numrange(3,6);
+select nummultirange(numrange(0,2), numrange(7,8)) << numrange(3,6);
+select nummultirange(numrange(-4,-2), numrange(0,2)) << numrange(3,6);
+select nummultirange() << nummultirange();
+select nummultirange() << nummultirange(numrange(1,2));
+select nummultirange(numrange(1,2)) << nummultirange();
+select nummultirange(numrange(1,2)) << nummultirange(numrange(1,2));
+select nummultirange(numrange(1,2)) << nummultirange(numrange(3,4));
+select nummultirange(numrange(1,2)) << nummultirange(numrange(3,4), numrange(7,8));
+select nummultirange(numrange(1,2), numrange(4,5)) << nummultirange(numrange(3,4), numrange(7,8));
+
+-- strictly right
+select nummultirange() >> 'empty'::numrange;
+select nummultirange() >> numrange(1,2);
+select nummultirange(numrange(3,4)) >> numrange(1,2);
+select nummultirange(numrange(0,4)) >> numrange(1,2);
+select nummultirange(numrange(0,4), numrange(7,8)) >> numrange(1,2);
+select 'empty'::numrange >> nummultirange();
+select numrange(1,2) >> nummultirange();
+select numrange(3,6) >> nummultirange(numrange(3,4));
+select numrange(3,6) >> nummultirange(numrange(0,2));
+select numrange(3,6) >> nummultirange(numrange(0,2), numrange(7,8));
+select numrange(3,6) >> nummultirange(numrange(-4,-2), numrange(0,2));
+select nummultirange() >> nummultirange();
+select nummultirange(numrange(1,2)) >> nummultirange();
+select nummultirange() >> nummultirange(numrange(1,2));
+select nummultirange(numrange(1,2)) >> nummultirange(numrange(1,2));
+select nummultirange(numrange(3,4)) >> nummultirange(numrange(1,2));
+select nummultirange(numrange(3,4), numrange(7,8)) >> nummultirange(numrange(1,2));
+select nummultirange(numrange(3,4), numrange(7,8)) >> nummultirange(numrange(1,2), numrange(4,5));
+
+-- union
+SELECT 'empty'::numrange @+ 'empty'::numrange;
+SELECT 'empty'::numrange @+ nummultirange();
+SELECT nummultirange() @+ 'empty'::numrange;
+SELECT nummultirange() @+ nummultirange();
+SELECT 'empty'::numrange @+ numrange(1,2);
+SELECT 'empty'::numrange @+ nummultirange(numrange(1,2));
+SELECT numrange(1,2) @+ nummultirange();
+SELECT nummultirange(numrange(1,2)) @+ 'empty'::numrange;
+SELECT numrange(1,2) @+ 'empty'::numrange;
+SELECT nummultirange() @+ numrange(1,2);
+SELECT nummultirange() @+ nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) @+ nummultirange();
+SELECT numrange(1,2) @+ numrange(1,2);
+SELECT numrange(1,2) @+ numrange(2,4);
+SELECT numrange(1,2) @+ numrange(3,4);
+SELECT nummultirange(numrange(1,2)) @+ nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) @+ nummultirange(numrange(2,4));
+SELECT nummultirange(numrange(1,2)) @+ nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) @+ nummultirange(numrange(2,4));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) @+ nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) @+ nummultirange(numrange(0,9));
+
+-- merge
+SELECT range_merge(nummultirange());
+SELECT range_merge(nummultirange(numrange(1,2)));
+SELECT range_merge(nummultirange(numrange(1,2), numrange(7,8)));
+
+-- minus
+SELECT 'empty'::numrange @- 'empty'::numrange;
+SELECT 'empty'::numrange @- nummultirange();
+SELECT nummultirange() @- 'empty'::numrange;
+SELECT nummultirange() @- nummultirange();
+SELECT 'empty'::numrange @- numrange(1,2);
+SELECT 'empty'::numrange @- nummultirange(numrange(1,2));
+SELECT numrange(1,2) @- nummultirange();
+SELECT nummultirange(numrange(1,2)) @- 'empty'::numrange;
+SELECT 'empty'::numrange @- numrange(1,2);
+SELECT numrange(1,2) @- 'empty'::numrange;
+SELECT nummultirange() @- numrange(1,2);
+SELECT nummultirange() @- nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) @- nummultirange();
+SELECT numrange(1,3) @- numrange(1,3);
+SELECT numrange(1,3) @- numrange(1,2);
+SELECT numrange(1,3) @- numrange(2,4);
+SELECT numrange(1,3) @- numrange(3,4);
+SELECT numrange(1,3) @- nummultirange(numrange(1,3));
+SELECT numrange(1,3) @- nummultirange(numrange(1,2));
+SELECT numrange(1,3) @- nummultirange(numrange(2,4));
+SELECT numrange(1,3) @- nummultirange(numrange(3,4));
+SELECT numrange(1,10) @- nummultirange(numrange(1,2), numrange(4,5));
+SELECT nummultirange(numrange(1,10)) @- numrange(0,2);
+SELECT nummultirange(numrange(1,10)) @- numrange(1,2);
+SELECT nummultirange(numrange(1,10)) @- numrange(3,4);
+SELECT nummultirange(numrange(1,10)) @- numrange(13,18);
+SELECT nummultirange(numrange(1,2), numrange(3,4)) @- nummultirange();
+SELECT nummultirange(numrange(1,2)) @- nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) @- nummultirange(numrange(2,4));
+SELECT nummultirange(numrange(1,2)) @- nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,4)) @- nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,4)) @- nummultirange(numrange(2,3));
+SELECT nummultirange(numrange(1,4)) @- nummultirange(numrange(0,8));
+SELECT nummultirange(numrange(1,4)) @- nummultirange(numrange(0,2));
+SELECT nummultirange(numrange(1,8)) @- nummultirange(numrange(0,2), numrange(3,4));
+SELECT nummultirange(numrange(1,8)) @- nummultirange(numrange(2,3), numrange(5,null));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) @- nummultirange(numrange(-2,0));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) @- nummultirange(numrange(2,4));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) @- nummultirange(numrange(3,5));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) @- nummultirange(numrange(0,9));
+SELECT nummultirange(numrange(1,3), numrange(4,5)) @- nummultirange(numrange(2,9));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) @- nummultirange(numrange(8,9));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) @- nummultirange(numrange(-2,0), numrange(8,9));
+
+-- intersection
+SELECT 'empty'::numrange @* 'empty'::numrange;
+SELECT 'empty'::numrange @* nummultirange();
+SELECT nummultirange() @* 'empty'::numrange;
+SELECT nummultirange() @* nummultirange();
+SELECT 'empty'::numrange @* numrange(1,2);
+SELECT 'empty'::numrange @* nummultirange(numrange(1,2));
+SELECT numrange(1,2) @* 'empty'::numrange;
+SELECT numrange(1,2) @* nummultirange();
+SELECT nummultirange(numrange(1,2)) @* 'empty'::numrange;
+SELECT nummultirange() @* numrange(1,2);
+SELECT nummultirange() @* nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) @* nummultirange();
+SELECT numrange(1,3) @* numrange(1,3);
+SELECT numrange(1,3) @* numrange(1,2);
+SELECT numrange(1,3) @* numrange(1,5);
+SELECT numrange(1,3) @* numrange(2,5);
+SELECT numrange(1,5) @* numrange(2,3);
+SELECT '{[1,3)}'::nummultirange @* numrange(1,3);
+SELECT '{[1,3)}'::nummultirange @* numrange(1,2);
+SELECT '{[1,3)}'::nummultirange @* numrange(1,5);
+SELECT '{[1,3)}'::nummultirange @* numrange(2,5);
+SELECT '{[1,5)}'::nummultirange @* numrange(2,3);
+SELECT numrange(1,3) @* '{[1,3)}'::nummultirange;
+SELECT numrange(1,2) @* '{[1,3)}'::nummultirange;
+SELECT numrange(1,5) @* '{[1,3)}'::nummultirange;
+SELECT numrange(2,5) @* '{[1,3)}'::nummultirange;
+SELECT numrange(2,3) @* '{[1,5)}'::nummultirange;
+SELECT '{[1,3)}'::nummultirange @* '{[1,5)}'::nummultirange;
+SELECT '{[1,3)}'::nummultirange @* '{[0,5)}'::nummultirange;
+SELECT '{[1,3)}'::nummultirange @* '{[0,2)}'::nummultirange;
+SELECT '{[1,3)}'::nummultirange @* '{[2,5)}'::nummultirange;
+SELECT '{[1,4)}'::nummultirange @* '{[2,3)}'::nummultirange;
+SELECT '{[1,4)}'::nummultirange @* '{[0,2), [3,5)}'::nummultirange;
+SELECT '{[1,4), [7,10)}'::nummultirange @* '{[0,8), [9,12)}'::nummultirange;
+SELECT '{[1,4), [7,10)}'::nummultirange @* '{[9,12)}'::nummultirange;
+SELECT '{[1,4), [7,10)}'::nummultirange @* '{[-5,-4), [5,6), [9,12)}'::nummultirange;
+SELECT '{[1,4), [7,10)}'::nummultirange @* '{[0,2), [3,8), [9,12)}'::nummultirange;
+SELECT '{[1,4), [7,10)}'::nummultirange @* '{[0,2), [3,8), [9,12)}'::nummultirange;
+
+--
+-- range_agg function
+--
+create table reservations ( room_id integer not null, booked_during daterange );
+insert into reservations values
+-- 1: has a meets and a gap
+(1, daterange('2018-07-01', '2018-07-07')),
+(1, daterange('2018-07-07', '2018-07-14')),
+(1, daterange('2018-07-20', '2018-07-22')),
+-- 2: just a single row
+(2, daterange('2018-07-01', '2018-07-03')),
+-- 3: one null range
+(3, NULL),
+-- 4: two null ranges
+(4, NULL),
+(4, NULL),
+-- 5: a null range and a non-null range
+(5, NULL),
+(5, daterange('2018-07-01', '2018-07-03')),
+-- 6: has overlap
+(6, daterange('2018-07-01', '2018-07-07')),
+(6, daterange('2018-07-05', '2018-07-10')),
+-- 7: two ranges that meet: no gap or overlap
+(7, daterange('2018-07-01', '2018-07-07')),
+(7, daterange('2018-07-07', '2018-07-14')),
+-- 8: an empty range
+(8, 'empty'::daterange)
+;
+SELECT   room_id, range_agg(booked_during)
+FROM     reservations
+GROUP BY room_id
+ORDER BY room_id;
+
+-- range_agg on a custom range type too
+SELECT  range_agg(r)
+FROM    (VALUES
+          ('[a,c]'::textrange),
+          ('[b,b]'::textrange),
+          ('[c,f]'::textrange),
+          ('[g,h)'::textrange),
+          ('[h,j)'::textrange)
+        ) t(r);
+
+select range_intersect_agg(nmr) from nummultirange_test;
+select range_intersect_agg(nmr) from nummultirange_test where false;
+-- test with just one input:
+select range_intersect_agg(nmr) from (values ('{[1,2]}'::nummultirange)) t(nmr);
+select range_intersect_agg(nmr) from nummultirange_test where nmr @> 4.0;
+
+create table nummultirange_test2(nmr nummultirange);
+create index nummultirange_test2_hash_idx on nummultirange_test2 using hash (nmr);
+
+INSERT INTO nummultirange_test2 VALUES('{[, 5)}');
+INSERT INTO nummultirange_test2 VALUES(nummultirange(numrange(1.1, 2.2)));
+INSERT INTO nummultirange_test2 VALUES(nummultirange(numrange(1.1, 2.2)));
+INSERT INTO nummultirange_test2 VALUES(nummultirange(numrange(1.1, 2.2,'()')));
+INSERT INTO nummultirange_test2 VALUES('{}');
+
+select * from nummultirange_test2 where nmr = '{}';
+select * from nummultirange_test2 where nmr = nummultirange(numrange(1.1, 2.2));
+select * from nummultirange_test2 where nmr = nummultirange(numrange(1.1, 2.3));
+
+set enable_nestloop=t;
+set enable_hashjoin=f;
+set enable_mergejoin=f;
+select * from nummultirange_test natural join nummultirange_test2 order by nmr;
+set enable_nestloop=f;
+set enable_hashjoin=t;
+set enable_mergejoin=f;
+select * from nummultirange_test natural join nummultirange_test2 order by nmr;
+set enable_nestloop=f;
+set enable_hashjoin=f;
+set enable_mergejoin=t;
+select * from nummultirange_test natural join nummultirange_test2 order by nmr;
+
+set enable_nestloop to default;
+set enable_hashjoin to default;
+set enable_mergejoin to default;
+
+DROP TABLE nummultirange_test2;
+
+--
+-- Test user-defined multirange of floats
+--
+
+select '{[123.001, 5.e9)}'::float8multirange @> 888.882::float8;
+create table float8multirange_test(f8mr float8multirange, i int);
+insert into float8multirange_test values(float8multirange(float8range(-100.00007, '1.111113e9')), 42);
+select * from float8multirange_test;
+drop table float8multirange_test;
+
+--
+-- Test multirange types over domains
+--
+
+create domain mydomain as int4;
+create type mydomainrange as range(subtype=mydomain);
+select '{[4,50)}'::mydomainmultirange @> 7::mydomain;
+drop domain mydomain cascade;
+
+--
+-- Test domains over multirange types
+--
+
+create domain restrictedmultirange as int4multirange check (upper(value) < 10);
+select '{[4,5)}'::restrictedmultirange @> 7;
+select '{[4,50)}'::restrictedmultirange @> 7; -- should fail
+drop domain restrictedmultirange;
+
+--
+-- Test multiple multirange types over the same subtype
+--
+
+create type textrange1 as range(subtype=text, collation="C");
+create type textrange2 as range(subtype=text, collation="C");
+
+select textmultirange1(textrange2('a','Z'));  -- should fail
+select textmultirange1(textrange1('a','Z')) @> 'b'::text;
+select textmultirange2(textrange2('a','z')) @> 'b'::text;
+
+drop type textrange1;
+drop type textrange2;
+
+--
+-- Test polymorphic type system
+--
+
+create function anyarray_anymultirange_func(a anyarray, r anymultirange)
+  returns anyelement as 'select $1[1] + lower($2);' language sql;
+
+select anyarray_anymultirange_func(ARRAY[1,2], int4multirange(int4range(10,20)));
+
+-- should fail
+select anyarray_anymultirange_func(ARRAY[1,2], nummultirange(numrange(10,20)));
+
+drop function anyarray_anymultirange_func(anyarray, anymultirange);
+
+-- should fail
+create function bogus_func(anyelement)
+  returns anymultirange as 'select int4multirange(int4range(1,10))' language sql;
+
+-- should fail
+create function bogus_func(int)
+  returns anymultirange as 'select int4multirange(int4range(1,10))' language sql;
+
+create function range_add_bounds(anymultirange)
+  returns anyelement as 'select lower($1) + upper($1)' language sql;
+
+select range_add_bounds(int4multirange(int4range(1, 17)));
+select range_add_bounds(nummultirange(numrange(1.0001, 123.123)));
+
+create function multirangetypes_sql(q anymultirange, b anyarray, out c anyelement)
+  as $$ select upper($1) + $2[1] $$
+  language sql;
+
+select multirangetypes_sql(int4multirange(int4range(1,10)), ARRAY[2,20]);
+select multirangetypes_sql(nummultirange(numrange(1,10)), ARRAY[2,20]);  -- match failure
+
+--
+-- Arrays of multiranges
+--
+
+select ARRAY[nummultirange(numrange(1.1, 1.2)), nummultirange(numrange(12.3, 155.5))];
+
+create table i8mr_array (f1 int, f2 int8multirange[]);
+insert into i8mr_array values (42, array[int8multirange(int8range(1,10)), int8multirange(int8range(2,20))]);
+select * from i8mr_array;
+drop table i8mr_array;
+
+--
+-- Multiranges of arrays
+--
+
+select arraymultirange(arrayrange(ARRAY[1,2], ARRAY[2,1]));
+select arraymultirange(arrayrange(ARRAY[2,1], ARRAY[1,2]));  -- fail
+
+select array[1,1] <@ arraymultirange(arrayrange(array[1,2], array[2,1]));
+select array[1,3] <@ arraymultirange(arrayrange(array[1,2], array[2,1]));
+
+--
+-- Ranges of composites
+--
+
+create type two_ints as (a int, b int);
+create type two_ints_range as range (subtype = two_ints);
+
+-- with force_parallel_mode on, this exercises tqueue.c's range remapping
+select *, row_to_json(upper(t)) as u from
+  (values (two_ints_multirange(two_ints_range(row(1,2), row(3,4)))),
+          (two_ints_multirange(two_ints_range(row(5,6), row(7,8))))) v(t);
+
+drop type two_ints cascade;
+
+--
+-- Check behavior when subtype lacks a hash function
+--
+
+set enable_sort = off;  -- try to make it pick a hash setop implementation
+
+select '{(2,5)}'::cashmultirange except select '{(5,6)}'::cashmultirange;
+
+reset enable_sort;
+
+--
+-- OUT/INOUT/TABLE functions
+--
+
+-- infer anymultirange from anymultirange
+create function mr_outparam_succeed(i anymultirange, out r anymultirange, out t text)
+  as $$ select $1, 'foo'::text $$ language sql;
+
+select * from mr_outparam_succeed(int4multirange(int4range(1,2)));
+
+-- infer anyarray from anymultirange
+create function mr_outparam_succeed2(i anymultirange, out r anyarray, out t text)
+  as $$ select ARRAY[upper($1)], 'foo'::text $$ language sql;
+
+select * from mr_outparam_succeed2(int4multirange(int4range(1,2)));
+
+-- infer anyrange from anymultirange
+create function mr_outparam_succeed3(i anymultirange, out r anyrange, out t text)
+  as $$ select range_merge($1), 'foo'::text $$ language sql;
+select * from mr_outparam_succeed3(int4multirange(int4range(1,2)));
+
+-- infer anymultirange from anyrange
+create function mr_outparam_succeed4(i anyrange, out r anymultirange, out t text)
+  as $$ select $1 @+ $1, 'foo'::text $$ language sql;
+
+select * from mr_outparam_succeed4(int4range(1,2));
+
+-- infer anyelement from anymultirange
+create function mr_inoutparam_succeed(out i anyelement, inout r anymultirange)
+  as $$ select upper($1), $1 $$ language sql;
+
+select * from mr_inoutparam_succeed(int4multirange(int4range(1,2)));
+
+-- infer anyelement+anymultirange from anyelement+anymultirange
+create function mr_table_succeed(i anyelement, r anymultirange) returns table(i anyelement, r anymultirange)
+  as $$ select $1, $2 $$ language sql;
+
+select * from mr_table_succeed(123, int4multirange(int4range(1,11)));
+
+-- should fail
+create function mr_outparam_fail(i anyelement, out r anymultirange, out t text)
+  as $$ select '[1,10]', 'foo' $$ language sql;
+
+--should fail
+create function mr_inoutparam_fail(inout i anyelement, out r anymultirange)
+  as $$ select $1, '[1,10]' $$ language sql;
+
+--should fail
+create function mr_table_fail(i anyelement) returns table(i anyelement, r anymultirange)
+  as $$ select $1, '[1,10]' $$ language sql;
diff --git a/src/test/regress/sql/rangetypes.sql b/src/test/regress/sql/rangetypes.sql
index 8960add976..32e18661d0 100644
--- a/src/test/regress/sql/rangetypes.sql
+++ b/src/test/regress/sql/rangetypes.sql
@@ -118,6 +118,10 @@ select numrange(1.0, 2.0) * numrange(2.0, 3.0);
 select numrange(1.0, 2.0) * numrange(1.5, 3.0);
 select numrange(1.0, 2.0) * numrange(2.5, 3.0);
 
+select range_intersect_agg(nr) from numrange_test;
+select range_intersect_agg(nr) from numrange_test where false;
+select range_intersect_agg(nr) from numrange_test where nr @> 4.0;
+
 create table numrange_test2(nr numrange);
 create index numrange_test2_hash_idx on numrange_test2 using hash (nr);
 
@@ -481,16 +485,25 @@ reset enable_sort;
 -- OUT/INOUT/TABLE functions
 --
 
+-- infer anyrange from anyrange
 create function outparam_succeed(i anyrange, out r anyrange, out t text)
   as $$ select $1, 'foo'::text $$ language sql;
 
 select * from outparam_succeed(int4range(1,2));
 
+-- infer anyarray from anyrange
+create function outparam_succeed2(i anyrange, out r anyarray, out t text)
+  as $$ select ARRAY[upper($1)], 'foo'::text $$ language sql;
+
+select * from outparam_succeed2(int4range(int4range(1,2)));
+
+-- infer anyelement from anyrange
 create function inoutparam_succeed(out i anyelement, inout r anyrange)
   as $$ select upper($1), $1 $$ language sql;
 
 select * from inoutparam_succeed(int4range(1,2));
 
+-- infer anyelement+anyrange from anyelement+anyrange
 create function table_succeed(i anyelement, r anyrange) returns table(i anyelement, r anyrange)
   as $$ select $1, $2 $$ language sql;
 
-- 
2.11.0

#60Pavel Stehule
pavel.stehule@gmail.com
In reply to: Paul A Jungwirth (#59)
Re: range_agg

st 20. 11. 2019 v 20:32 odesílatel Paul A Jungwirth <
pj@illuminatedcomputing.com> napsal:

On Tue, Nov 19, 2019 at 9:49 PM Paul A Jungwirth
<pj@illuminatedcomputing.com> wrote:

On Tue, Nov 19, 2019 at 1:17 AM Pavel Stehule <pavel.stehule@gmail.com>

wrote:

Hi
I tested last patches. I found some issues

Thank you for the review!

Here is an updated patch series fixing the problems from that last
review. I would still like some direction about the doc formatting:

yes, these bugs are fixed

there are not compilation's issues
tests passed
doc is ok

I have notes

1. the chapter should be renamed to "Range Functions and Operators" to
"Range and Multirange Functions and Operators"

But now the doc is not well readable - there is not clean, what functions
are for range type, what for multirange and what for both

2. I don't like introduction "safe" operators - now the basic operators are
doubled, and nobody without documentation will use @* operators.

It is not intuitive. I think is better to map this functionality to basic
operators +- * and implement it just for pairs (Multirange, Multirange) and
(Multirange, Range) if it is possible

It's same relation line Numeric X integer. There should not be introduced
new operators. If somebody need it for ranges, then he can use cast to
multirange, and can continue.

The "safe" operators can be implement on user space - but should not be
default solution.

3. There are not prepared casts -

postgres=# select int8range(10,15)::int8multirange;
ERROR: cannot cast type int8range to int8multirange
LINE 1: select int8range(10,15)::int8multirange;
^
There should be some a) fully generic solution, or b) possibility to build
implicit cast when any multirange type is created.

Regards

Pavel

I am not sure how much is correct to use <literallayout

class="monospaced"> in doc. It is used for ranges, and multiranges, but no
in other places

I could use some advice here. Many operators seem best presented in
groups of four, where only their parameter types change, for example:

int8range < int8range
int8range < int8multirange
int8multirange < int8range
int8multirange < int8multirange

All I really want is to show those separated by line breaks. I
couldn't find any other examples of that happening inside a table cell
though (i.e. inside <row><entry></entry></row>). What is the best way
to do that?

Personally I think it should be cleaned. Mainly if there is not visible
differences. But range related doc it uses, so it is consistent with it.
And then this is not big issue.

Show quoted text

Thanks,
Paul

#61Paul Jungwirth
pj@illuminatedcomputing.com
In reply to: Pavel Stehule (#60)
Re: range_agg

On 11/21/19 1:06 AM, Pavel Stehule wrote:

2. I don't like introduction "safe" operators - now the basic operators
are doubled, and nobody without documentation will use @* operators.

It is not intuitive. I think is better to map this functionality to
basic operators +- * and implement it just for pairs (Multirange,
Multirange) and (Multirange, Range) if it is possible

It's same relation line Numeric X integer. There should not be
introduced new operators. If somebody need it for ranges, then he can
use cast to multirange, and can continue.
[snip]
3. There are not prepared casts -

postgres=# select int8range(10,15)::int8multirange;
ERROR:  cannot cast type int8range to int8multirange
LINE 1: select int8range(10,15)::int8multirange;
                               ^
There should be some a) fully generic solution, or b) possibility to
build implicit cast when any multirange type is created.

Okay, I like the idea of just having `range + range` and `multirange +
multirange`, then letting you cast between ranges and multiranges. The
analogy to int/numeric seems strong. I guess if you cast a multirange
with more than one element to a range it will raise an error. That will
let me clean up the docs a lot too.

Thanks!

--
Paul ~{:-)
pj@illuminatedcomputing.com

#62Pavel Stehule
pavel.stehule@gmail.com
In reply to: Paul Jungwirth (#61)
Re: range_agg

čt 21. 11. 2019 v 21:15 odesílatel Paul Jungwirth <
pj@illuminatedcomputing.com> napsal:

On 11/21/19 1:06 AM, Pavel Stehule wrote:

2. I don't like introduction "safe" operators - now the basic operators
are doubled, and nobody without documentation will use @* operators.

It is not intuitive. I think is better to map this functionality to
basic operators +- * and implement it just for pairs (Multirange,
Multirange) and (Multirange, Range) if it is possible

It's same relation line Numeric X integer. There should not be
introduced new operators. If somebody need it for ranges, then he can
use cast to multirange, and can continue.
[snip]
3. There are not prepared casts -

postgres=# select int8range(10,15)::int8multirange;
ERROR: cannot cast type int8range to int8multirange
LINE 1: select int8range(10,15)::int8multirange;
^
There should be some a) fully generic solution, or b) possibility to
build implicit cast when any multirange type is created.

Okay, I like the idea of just having `range + range` and `multirange +
multirange`, then letting you cast between ranges and multiranges. The
analogy to int/numeric seems strong. I guess if you cast a multirange
with more than one element to a range it will raise an error. That will
let me clean up the docs a lot too.

I though about it, and I think so cast from multirange to range is useless,
minimally it should be explicit.

On second hand - from range to multirange should be implicit.

The original patch did

1. MR @x MR = MR
2. R @x R = MR
3. MR @x R = MR

I think so @1 & @3 has sense, but without introduction of special operator.
@2 is bad and can be solved by cast one or second operand.

Pavel

Show quoted text

Thanks!

--
Paul ~{:-)
pj@illuminatedcomputing.com

#63Paul A Jungwirth
pj@illuminatedcomputing.com
In reply to: Pavel Stehule (#62)
Re: range_agg

On Thu, Nov 21, 2019 at 9:21 PM Pavel Stehule <pavel.stehule@gmail.com> wrote:

I though about it, and I think so cast from multirange to range is useless, minimally it should be explicit.

I agree: definitely not implicit. If I think of a good reason for it
I'll add it, but otherwise I'll leave it out.

On second hand - from range to multirange should be implicit.

Okay.

The original patch did

1. MR @x MR = MR
2. R @x R = MR
3. MR @x R = MR

I think so @1 & @3 has sense, but without introduction of special operator. @2 is bad and can be solved by cast one or second operand.

Yes. I like how #2 follows the int/numeric analogy: if you want a
numeric result from `int / int` you can say `int::numeric / int`.

So my understanding is that conventionally cast functions are named
after the destination type, e.g. int8multirange(int8range) would be
the function to cast an int8range to an int8multirange. And
int8range(int8multirange) would go the other way (if we do that). We
already use these names for the "constructor" functions, but I think
that is actually okay. For the multirange->range cast, the parameter
type & number are different, so there is no real conflict. For the
range->multirange cast, the parameter type is the same, and the
constructor function is variadic---but I think that's fine, because
the semantics are the same: build a multirange whose only element is
the given range:

regression=# select int8multirange(int8range(1,2));
int8multirange
----------------
{[1,2)}
(1 row)

Even the NULL handling is already what we want:

regression=# select int8multirange(null);
int8multirange
----------------
NULL
(1 row)

So I think it's fine, but I'm curious whether you see any problems
there? (I guess if there is a problem it's no big deal to name the
function something else....)

Thanks,
Paul

#64Pavel Stehule
pavel.stehule@gmail.com
In reply to: Paul A Jungwirth (#63)
Re: range_agg

pá 22. 11. 2019 v 17:25 odesílatel Paul A Jungwirth <
pj@illuminatedcomputing.com> napsal:

On Thu, Nov 21, 2019 at 9:21 PM Pavel Stehule <pavel.stehule@gmail.com>
wrote:

I though about it, and I think so cast from multirange to range is

useless, minimally it should be explicit.

I agree: definitely not implicit. If I think of a good reason for it
I'll add it, but otherwise I'll leave it out.

On second hand - from range to multirange should be implicit.

Okay.

The original patch did

1. MR @x MR = MR
2. R @x R = MR
3. MR @x R = MR

I think so @1 & @3 has sense, but without introduction of special

operator. @2 is bad and can be solved by cast one or second operand.

Yes. I like how #2 follows the int/numeric analogy: if you want a
numeric result from `int / int` you can say `int::numeric / int`.

So my understanding is that conventionally cast functions are named
after the destination type, e.g. int8multirange(int8range) would be
the function to cast an int8range to an int8multirange. And
int8range(int8multirange) would go the other way (if we do that). We
already use these names for the "constructor" functions, but I think
that is actually okay. For the multirange->range cast, the parameter
type & number are different, so there is no real conflict. For the
range->multirange cast, the parameter type is the same, and the
constructor function is variadic---but I think that's fine, because
the semantics are the same: build a multirange whose only element is
the given range:

regression=# select int8multirange(int8range(1,2));
int8multirange
----------------
{[1,2)}
(1 row)

Even the NULL handling is already what we want:

regression=# select int8multirange(null);
int8multirange
----------------
NULL
(1 row)

So I think it's fine, but I'm curious whether you see any problems
there? (I guess if there is a problem it's no big deal to name the
function something else....)

It looks well now. I am not sure about benefit of cast from MR to R if MR
has more than one values. But it can be there for completeness.

I think in this moment is not important to implement all functionality -
for start is good to implement basic functionality that can be good. It can
be enhanced step by step in next versions.

Pavel

Show quoted text

Thanks,
Paul

#65Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Paul A Jungwirth (#59)
6 attachment(s)
Re: range_agg

Hi Paul

I'm starting to look at this again. Here's a few proposed changes to
the current code as I read along.

I noticed that 0001 does not compile on its own. It works as soon as I
add 0002. What this is telling me is that the current patch split is
not serving any goals; I think it's okay to merge them all into a single
commit. If you want to split it in smaller pieces, it needs to be stuff
that can be committed separately.

One possible candidate for that is the new makeUniqueTypeName function
you propose. I added this comment to explain what it does:

 /*
- * makeUniqueTypeName: Prepend underscores as needed until we make a name that
- * doesn't collide with anything. Tries the original typeName if requested.
+ * makeUniqueTypeName
+ *     Generate a unique name for a prospective new type
+ *
+ * Given a typeName of length namelen, produce a new name into dest (an output
+ * buffer allocated by caller, which must of length NAMEDATALEN) by prepending
+ * underscores, until a non-conflicting name results.
+ *
+ * If tryOriginalName, first try with zero underscores.
  *
  * Returns the number of underscores added.
  */

This seems a little too strange; why not have the function allocate its
output buffer instead, and return it? In order to support the case of
it failing to find an appropriate name, have it return NULL, for caller
to throw the "could not form ... name" error.

The attached 0001 simplifies makeMultirangeConstructors; I think it was
too baroque just because of it trying to imitate makeRangeConstructors;
it seems easier to just repeat the ProcedureCreate call than trying to
be clever. (makeRangeConstructors's comment is lying about the number
of constructors it creates after commit df73584431e7, BTW. But note
that the cruft in it to support doing it twice is not as much as in the
new code).

The other patches should be self-explanatory (I already submitted 0002
previously.)

I'll keep going over the rest of it. Thanks!

--
�lvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

Attachments:

0001-Simplify-makeMultirangeConstructors.patchtext/x-diff; charset=us-asciiDownload
From 6a5b230f741fb4272770d09750b57fdad225027e Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Thu, 28 Nov 2019 19:08:43 -0300
Subject: [PATCH 1/6] Simplify makeMultirangeConstructors

---
 src/backend/commands/typecmds.c | 134 +++++++++++++++++++-------------
 1 file changed, 80 insertions(+), 54 deletions(-)

diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index 38948a049b..1b012c9cad 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -1784,70 +1784,96 @@ 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 = rangeArrayOid;
 	ObjectAddress myself,
 				referenced;
-	int			i;
-
-	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)};
+	oidvector  *argtypes;
+	Datum		allParamTypes;
+	ArrayType  *allParameterTypes;
+	Datum		paramModes;
+	ArrayType  *parameterModes;
 
 	referenced.classId = TypeRelationId;
 	referenced.objectId = multirangeOid;
 	referenced.objectSubId = 0;
 
-	for (i = 0; i < lengthof(prosrc); i++)
-	{
-		oidvector  *constructorArgTypesVector;
+	argtypes = buildoidvector(NULL, 0);
+	myself = ProcedureCreate(name,	/* name: same as multirange type */
+							 namespace,
+							 false, /* replace */
+							 false, /* returns set */
+							 multirangeOid, /* return type */
+							 BOOTSTRAP_SUPERUSERID, /* proowner */
+							 INTERNALlanguageId,	/* language */
+							 F_FMGR_INTERNAL_VALIDATOR,
+							 "multirange_constructor0", /* prosrc */
+							 NULL,	/* probin */
+							 PROKIND_FUNCTION,
+							 false, /* security_definer */
+							 false, /* leakproof */
+							 false, /* isStrict */
+							 PROVOLATILE_IMMUTABLE, /* volatility */
+							 PROPARALLEL_SAFE,	/* parallel safety */
+							 argtypes,	/* parameterTypes */
+							 PointerGetDatum(NULL), /* allParameterTypes */
+							 PointerGetDatum(NULL), /* parameterModes */
+							 PointerGetDatum(NULL), /* parameterNames */
+							 NIL,	/* parameterDefaults */
+							 PointerGetDatum(NULL), /* trftypes */
+							 PointerGetDatum(NULL), /* proconfig */
+							 InvalidOid,	/* prosupport */
+							 1.0,	/* procost */
+							 0.0);	/* prorows */
 
-		constructorArgTypesVector = buildoidvector(&constructorArgTypes,
-												   pronargs[i]);
+	/*
+	 * Make the constructor 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);
 
-		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 */
+	pfree(argtypes);
 
-		/*
-		 * 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);
-	}
+	argtypes = buildoidvector(&rangeArrayOid, 1);
+	allParamTypes = ObjectIdGetDatum(rangeArrayOid);
+	allParameterTypes = construct_array(&allParamTypes,
+										1, OIDOID,
+										sizeof(Oid), true, 'i');
+	paramModes = CharGetDatum(FUNC_PARAM_VARIADIC);
+	parameterModes = construct_array(&paramModes, 1, CHAROID,
+									 1, true, 'c');
+
+	myself = ProcedureCreate(name,	/* name: same as multirange type */
+							 namespace,
+							 false, /* replace */
+							 false, /* returns set */
+							 multirangeOid, /* return type */
+							 BOOTSTRAP_SUPERUSERID, /* proowner */
+							 INTERNALlanguageId,	/* language */
+							 F_FMGR_INTERNAL_VALIDATOR,
+							 "multirange_constructor1", /* prosrc */
+							 NULL,	/* probin */
+							 PROKIND_FUNCTION,
+							 false, /* security_definer */
+							 false, /* leakproof */
+							 false, /* isStrict */
+							 PROVOLATILE_IMMUTABLE, /* volatility */
+							 PROPARALLEL_SAFE,	/* parallel safety */
+							 argtypes,	/* parameterTypes */
+							 PointerGetDatum(allParameterTypes),	/* allParameterTypes */
+							 PointerGetDatum(parameterModes),	/* parameterModes */
+							 PointerGetDatum(NULL), /* parameterNames */
+							 NIL,	/* parameterDefaults */
+							 PointerGetDatum(NULL), /* trftypes */
+							 PointerGetDatum(NULL), /* proconfig */
+							 InvalidOid,	/* prosupport */
+							 1.0,	/* procost */
+							 0.0);	/* prorows */
+	/* ditto */
+	recordDependencyOn(&myself, &referenced, DEPENDENCY_INTERNAL);
+
+	pfree(argtypes);
+	pfree(allParameterTypes);
+	pfree(parameterModes);
 }
 
 /*
-- 
2.20.1

0002-silence-compiler-warning.patchtext/x-diff; charset=us-asciiDownload
From 589ef7f9a7f6debe767e7d3e0427941b349a3d13 Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Thu, 28 Nov 2019 19:08:46 -0300
Subject: [PATCH 2/6] silence compiler warning

---
 src/backend/parser/parse_coerce.c | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/src/backend/parser/parse_coerce.c b/src/backend/parser/parse_coerce.c
index 49c75dc12d..b43b2dc612 100644
--- a/src/backend/parser/parse_coerce.c
+++ b/src/backend/parser/parse_coerce.c
@@ -1592,6 +1592,8 @@ check_generic_type_consistency(const Oid *actual_arg_types,
 			return false;
 		}
 	}
+	else
+		range_typelem = InvalidOid; /* keep compiler quiet */
 
 	/* Get the element type based on the multirange type, if we have one */
 	if (OidIsValid(multirange_typeid))
-- 
2.20.1

0003-Be-less-verbose-on-variable-names.patchtext/x-diff; charset=us-asciiDownload
From b116b5c95f875361c01985b56c2f68d271a12e5e Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Thu, 28 Nov 2019 19:30:03 -0300
Subject: [PATCH 3/6] Be less verbose on variable names

---
 src/backend/commands/typecmds.c            | 23 +++++++++-------------
 src/backend/utils/adt/pg_upgrade_support.c |  4 ++--
 src/include/catalog/binary_upgrade.h       |  4 ++--
 3 files changed, 13 insertions(+), 18 deletions(-)

diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index 1b012c9cad..4ca2d3364b 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -85,8 +85,8 @@ typedef struct
 
 /* Potentially set by pg_upgrade_support functions */
 Oid			binary_upgrade_next_array_pg_type_oid = InvalidOid;
-Oid			binary_upgrade_next_multirange_pg_type_oid = InvalidOid;
-Oid			binary_upgrade_next_multirange_array_pg_type_oid = InvalidOid;
+Oid			binary_upgrade_next_mrng_pg_type_oid = InvalidOid;
+Oid			binary_upgrade_next_mrng_array_pg_type_oid = InvalidOid;
 
 static void makeRangeConstructors(const char *name, Oid namespace,
 								  Oid rangeOid, Oid subtype);
@@ -1529,13 +1529,9 @@ DefineRange(CreateRangeStmt *stmt)
 	/* alignment must be 'i' or 'd' for ranges */
 	alignment = (subtypalign == 'd') ? 'd' : 'i';
 
-	/* Allocate OID for array type */
+	/* Allocate OID for array type, its multirange and its multirange array */
 	rangeArrayOid = AssignTypeArrayOid();
-
-	/* Allocate OID for multirange type */
 	multirangeOid = AssignTypeMultirangeOid();
-
-	/* Allocate OID for multirange array type */
 	multirangeArrayOid = AssignTypeMultirangeArrayOid();
 
 	/* Create the pg_type entry */
@@ -1574,7 +1570,6 @@ DefineRange(CreateRangeStmt *stmt)
 	Assert(typoid == address.objectId);
 
 	/* Create the multirange that goes with it */
-
 	multirangeTypeName = makeMultirangeTypeName(typeName, typeNamespace);
 
 	mltrngaddress =
@@ -2313,13 +2308,13 @@ AssignTypeMultirangeOid(void)
 	/* Use binary-upgrade override for pg_type.oid? */
 	if (IsBinaryUpgrade)
 	{
-		if (!OidIsValid(binary_upgrade_next_multirange_pg_type_oid))
+		if (!OidIsValid(binary_upgrade_next_mrng_pg_type_oid))
 			ereport(ERROR,
 					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 					 errmsg("pg_type multirange OID value not set when in binary upgrade mode")));
 
-		type_multirange_oid = binary_upgrade_next_multirange_pg_type_oid;
-		binary_upgrade_next_multirange_pg_type_oid = InvalidOid;
+		type_multirange_oid = binary_upgrade_next_mrng_pg_type_oid;
+		binary_upgrade_next_mrng_pg_type_oid = InvalidOid;
 	}
 	else
 	{
@@ -2346,13 +2341,13 @@ AssignTypeMultirangeArrayOid(void)
 	/* Use binary-upgrade override for pg_type.oid? */
 	if (IsBinaryUpgrade)
 	{
-		if (!OidIsValid(binary_upgrade_next_multirange_array_pg_type_oid))
+		if (!OidIsValid(binary_upgrade_next_mrng_array_pg_type_oid))
 			ereport(ERROR,
 					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 					 errmsg("pg_type multirange array OID value not set when in binary upgrade mode")));
 
-		type_multirange_array_oid = binary_upgrade_next_multirange_array_pg_type_oid;
-		binary_upgrade_next_multirange_array_pg_type_oid = InvalidOid;
+		type_multirange_array_oid = binary_upgrade_next_mrng_array_pg_type_oid;
+		binary_upgrade_next_mrng_array_pg_type_oid = InvalidOid;
 	}
 	else
 	{
diff --git a/src/backend/utils/adt/pg_upgrade_support.c b/src/backend/utils/adt/pg_upgrade_support.c
index d980b96f48..418c26c81b 100644
--- a/src/backend/utils/adt/pg_upgrade_support.c
+++ b/src/backend/utils/adt/pg_upgrade_support.c
@@ -57,7 +57,7 @@ binary_upgrade_set_next_multirange_pg_type_oid(PG_FUNCTION_ARGS)
 	Oid			typoid = PG_GETARG_OID(0);
 
 	CHECK_IS_BINARY_UPGRADE;
-	binary_upgrade_next_multirange_pg_type_oid = typoid;
+	binary_upgrade_next_mrng_pg_type_oid = typoid;
 
 	PG_RETURN_VOID();
 }
@@ -68,7 +68,7 @@ binary_upgrade_set_next_multirange_array_pg_type_oid(PG_FUNCTION_ARGS)
 	Oid			typoid = PG_GETARG_OID(0);
 
 	CHECK_IS_BINARY_UPGRADE;
-	binary_upgrade_next_multirange_array_pg_type_oid = typoid;
+	binary_upgrade_next_mrng_array_pg_type_oid = typoid;
 
 	PG_RETURN_VOID();
 }
diff --git a/src/include/catalog/binary_upgrade.h b/src/include/catalog/binary_upgrade.h
index 2b6e87bb84..ba132ddf23 100644
--- a/src/include/catalog/binary_upgrade.h
+++ b/src/include/catalog/binary_upgrade.h
@@ -16,8 +16,8 @@
 
 extern PGDLLIMPORT Oid binary_upgrade_next_pg_type_oid;
 extern PGDLLIMPORT Oid binary_upgrade_next_array_pg_type_oid;
-extern PGDLLIMPORT Oid binary_upgrade_next_multirange_pg_type_oid;
-extern PGDLLIMPORT Oid binary_upgrade_next_multirange_array_pg_type_oid;
+extern PGDLLIMPORT Oid binary_upgrade_next_mrng_pg_type_oid;
+extern PGDLLIMPORT Oid binary_upgrade_next_mrng_array_pg_type_oid;
 extern PGDLLIMPORT Oid binary_upgrade_next_toast_pg_type_oid;
 
 extern PGDLLIMPORT Oid binary_upgrade_next_heap_pg_class_oid;
-- 
2.20.1

0004-Protect-comment-against-pgindent.patchtext/x-diff; charset=us-asciiDownload
From e78eb8bc8277bb09a98ae6b8c18252df834bce83 Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Thu, 28 Nov 2019 19:36:53 -0300
Subject: [PATCH 4/6] Protect comment against pgindent

---
 src/backend/utils/adt/multirangetypes.c | 7 ++++---
 1 file changed, 4 insertions(+), 3 deletions(-)

diff --git a/src/backend/utils/adt/multirangetypes.c b/src/backend/utils/adt/multirangetypes.c
index 7717acb3b1..b8ff39077c 100644
--- a/src/backend/utils/adt/multirangetypes.c
+++ b/src/backend/utils/adt/multirangetypes.c
@@ -1136,15 +1136,16 @@ multirange_intersect_multirange_internal(Oid mltrngtypoid, TypeCacheEntry *range
 	if (range_count1 == 0 || range_count2 == 0)
 		return make_multirange(mltrngtypoid, rangetyp, 0, NULL);
 
-	/*
+	/*-----------------------------------------------
 	 * Worst case is a stitching pattern like this:
 	 *
 	 * mr1: --- --- --- ---
 	 * mr2:   --- --- ---
 	 * mr3:   - - - - - -
 	 *
-	 * That seems to be range_count1 + range_count2 - 1, but one extra won't
-	 * hurt.
+	 * That seems to be range_count1 + range_count2 - 1,
+	 * but one extra won't hurt.
+	 *-----------------------------------------------
 	 */
 	ranges3 = palloc0((range_count1 + range_count2) * sizeof(RangeType *));
 	range_count3 = 0;
-- 
2.20.1

0005-pgindent.patchtext/x-diff; charset=us-asciiDownload
From 981a6862601e22141938b9cfdea9149b1fdf65f3 Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Thu, 28 Nov 2019 19:35:43 -0300
Subject: [PATCH 5/6] pgindent

---
 src/backend/utils/adt/multirangetypes.c | 14 +++++++-------
 src/backend/utils/fmgr/funcapi.c        | 12 ++++++------
 src/bin/pg_dump/pg_dump.c               | 11 ++++++-----
 3 files changed, 19 insertions(+), 18 deletions(-)

diff --git a/src/backend/utils/adt/multirangetypes.c b/src/backend/utils/adt/multirangetypes.c
index b8ff39077c..f4dec1bbc9 100644
--- a/src/backend/utils/adt/multirangetypes.c
+++ b/src/backend/utils/adt/multirangetypes.c
@@ -330,13 +330,13 @@ multirange_recv(PG_FUNCTION_ARGS)
 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;
+	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);
 
diff --git a/src/backend/utils/fmgr/funcapi.c b/src/backend/utils/fmgr/funcapi.c
index 25623461bb..808a0e2ca7 100644
--- a/src/backend/utils/fmgr/funcapi.c
+++ b/src/backend/utils/fmgr/funcapi.c
@@ -630,8 +630,8 @@ resolve_polymorphic_tupdesc(TupleDesc tupdesc, oidvector *declared_args,
 	{
 		if (OidIsValid(anymultirange_type))
 		{
-			Oid		rngtype;
-			Oid		subtype;
+			Oid			rngtype;
+			Oid			subtype;
 
 			rngtype = resolve_generic_type(ANYRANGEOID,
 										   anymultirange_type,
@@ -665,9 +665,9 @@ resolve_polymorphic_tupdesc(TupleDesc tupdesc, oidvector *declared_args,
 	{
 		if (OidIsValid(anyrange_type))
 		{
-			Oid		subtype;
-			Oid		mltrngtype;
-			Oid		rngtype;
+			Oid			subtype;
+			Oid			mltrngtype;
+			Oid			rngtype;
 
 			subtype = resolve_generic_type(ANYELEMENTOID,
 										   anyrange_type,
@@ -911,7 +911,7 @@ resolve_polymorphic_argtypes(int numargs, Oid *argtypes, char *argmodes,
 		{
 			Oid			rngtype;
 			Oid			subtype;
-			
+
 			rngtype = resolve_generic_type(ANYRANGEOID,
 										   anymultirange_type,
 										   ANYMULTIRANGEOID);
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 4a0a03d50c..8272a0451b 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -4276,15 +4276,16 @@ dumpSubscription(Archive *fout, SubscriptionInfo *subinfo)
 	free(qsubname);
 }
 
-static Oid get_next_possible_free_pg_type_oid(Archive *fout, PQExpBuffer upgrade_query)
+static Oid
+get_next_possible_free_pg_type_oid(Archive *fout, PQExpBuffer upgrade_query)
 {
 	/*
 	 * If the old version didn't assign an array type, but the new version
-	 * does, we must select an unused type OID to assign.  This currently
-	 * only happens for domains, when upgrading pre-v11 to v11 and up.
+	 * does, we must select an unused type OID to assign.  This currently only
+	 * happens for domains, when upgrading pre-v11 to v11 and up.
 	 *
-	 * Note: local state here is kind of ugly, but we must have some,
-	 * since we mustn't choose the same unused OID more than once.
+	 * Note: local state here is kind of ugly, but we must have some, since we
+	 * mustn't choose the same unused OID more than once.
 	 */
 	static Oid	next_possible_free_oid = FirstNormalObjectId;
 	PGresult   *res;
-- 
2.20.1

0006-Add-comment-and-asserts-to-makeUniqueTypeName.patchtext/x-diff; charset=us-asciiDownload
From 04f1d482757debaa7ad44e8b59d76dab33d95503 Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Fri, 29 Nov 2019 23:03:13 -0300
Subject: [PATCH 6/6] Add comment and asserts to makeUniqueTypeName

---
 src/backend/catalog/pg_type.c | 13 +++++++++++--
 1 file changed, 11 insertions(+), 2 deletions(-)

diff --git a/src/backend/catalog/pg_type.c b/src/backend/catalog/pg_type.c
index de0bb24649..7b871e7b0d 100644
--- a/src/backend/catalog/pg_type.c
+++ b/src/backend/catalog/pg_type.c
@@ -912,8 +912,14 @@ makeMultirangeTypeName(const char *rangeTypeName, Oid typeNamespace)
 }
 
 /*
- * makeUniqueTypeName: Prepend underscores as needed until we make a name that
- * doesn't collide with anything. Tries the original typeName if requested.
+ * makeUniqueTypeName
+ *		Generate a unique name for a prospective new type
+ *
+ * Given a typeName of length namelen, produce a new name into dest (an output
+ * buffer allocated by caller, which must of length NAMEDATALEN) by prepending
+ * underscores, until a non-conflicting name results.
+ *
+ * If tryOriginalName, first try with zero underscores.
  *
  * Returns the number of underscores added.
  */
@@ -924,6 +930,9 @@ makeUniqueTypeName(char *dest, const char *typeName, size_t namelen, Oid typeNam
 	Relation	pg_type_desc;
 	int			i;
 
+	Assert(strlen(typeName) == namelen);
+	Assert(namelen < NAMEDATALEN);
+
 	pg_type_desc = table_open(TypeRelationId, AccessShareLock);
 
 	for (i = 0; i < NAMEDATALEN - 1; i++)
-- 
2.20.1

#66Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Paul A Jungwirth (#59)
13 attachment(s)
Re: range_agg

I took the liberty of rebasing this series on top of recent branch
master. The first four are mostly Paul's originals, except for conflict
fixes; the rest are changes I'm proposing as I go along figuring out the
whole thing. (I would post just my proposed changes, if it weren't for
the rebasing; apologies for the messiness.)

I am not convinced that adding TYPTYPE_MULTIRANGE is really necessary.
Why can't we just treat those types as TYPTYPE_RANGE and distinguish
them using TYPCATEGORY_MULTIRANGE? That's what we do for arrays. I'll
try to do that next.

I think the algorithm for coming up with the multirange name is
suboptimal. It works fine with the name is short enough that we can add
a few extra letters, but otherwise the result look pretty silly. I
think we can still improve on that. I propose to make
makeUniqueTypeName accept a suffix, and truncate the letters that appear
*before* the suffix rather than truncating after it's been appended.

There's a number of ereport() calls that should become elog(); and a
bunch of others that should probably acquire errcode() and be
reformatted per our style.

Regarding Pavel's documentation markup issue,

I am not sure how much is correct to use <literallayout class="monospaced">
in doc. It is used for ranges, and multiranges, but no in other places

I looked at the generated PDF and the table looks pretty bad; the words
in those entries overlap the words in the cell to their right. But that
also happens with entries that do not use <literallayout class="x">!
See [1]https://twitter.com/alvherre/status/1205563468595781633 for an example of the existing docs being badly formatted. The
docbook documentation [2]https://tdg.docbook.org/tdg/5.1/literallayout.html seems to suggest that what Paul used is the
appropriate way to do this.

Maybe a way is to make each entry have more than one row -- so the
example would appear below the other three fields in its own row, and
would be able to use the whole width of the table.

[1]: https://twitter.com/alvherre/status/1205563468595781633
[2]: https://tdg.docbook.org/tdg/5.1/literallayout.html

--
�lvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

Attachments:

v7-0001-multirange-type-basics.patchtext/x-diff; charset=us-asciiDownload
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

v7-0002-multirange-operators-and-functions.patchtext/x-diff; charset=us-asciiDownload
From 57d3a34cc7415ef878d1d613836e7452527b0a2d Mon Sep 17 00:00:00 2001
From: "Paul A. Jungwirth" <pj@illuminatedcomputing.com>
Date: Sat, 21 Sep 2019 21:28:20 -0700
Subject: [PATCH v7 02/13] multirange operators and functions

---
 src/backend/catalog/pg_type.c                 |    2 -
 src/backend/commands/typecmds.c               |    6 +-
 src/backend/parser/parse_coerce.c             |    2 +
 src/backend/utils/adt/multirangetypes.c       | 1389 +++++++++-
 src/backend/utils/adt/rangetypes.c            |  236 +-
 src/backend/utils/fmgr/funcapi.c              |   97 +-
 src/include/catalog/pg_aggregate.dat          |   11 +
 src/include/catalog/pg_amproc.dat             |    5 +-
 src/include/catalog/pg_operator.dat           |  169 ++
 src/include/catalog/pg_proc.dat               |  185 ++
 src/include/utils/multirangetypes.h           |   34 +
 src/include/utils/rangetypes.h                |   31 +-
 src/test/regress/expected/dependency.out      |    1 +
 src/test/regress/expected/multirangetypes.out | 2397 +++++++++++++++++
 src/test/regress/expected/opr_sanity.out      |    4 +-
 src/test/regress/expected/rangetypes.out      |   38 +-
 src/test/regress/expected/sanity_check.out    |    2 +
 src/test/regress/sql/multirangetypes.sql      |  607 +++++
 src/test/regress/sql/rangetypes.sql           |   13 +
 19 files changed, 5095 insertions(+), 134 deletions(-)

diff --git a/src/backend/catalog/pg_type.c b/src/backend/catalog/pg_type.c
index 8a85d3412a..73b8ace4c5 100644
--- a/src/backend/catalog/pg_type.c
+++ b/src/backend/catalog/pg_type.c
@@ -876,7 +876,6 @@ 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;
@@ -892,7 +891,6 @@ makeMultirangeTypeName(const char *rangeTypeName, Oid typeNamespace)
 	if (rangestr)
 	{
 		rangeoffset = rangestr - rangeTypeName;
-		rangelen = strlen(rangestr);
 		strlcpy(mltrng + rangeoffset, "multi", NAMEDATALEN - rangeoffset);
 		strlcpy(mltrng + rangeoffset + 5, rangestr, NAMEDATALEN - rangeoffset - 5);
 		namelen += 5;
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index 14a6857062..7a440cafd1 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -1783,13 +1783,11 @@ makeMultirangeConstructors(const char *name, Oid namespace,
 	"multirange_constructor1"};
 	static const int pronargs[2] = {0, 1};
 
-	Oid			constructorArgTypes[0];
+	Oid			constructorArgTypes = rangeArrayOid;
 	ObjectAddress myself,
 				referenced;
 	int			i;
 
-	constructorArgTypes[0] = rangeArrayOid;
-
 	Datum		allParamTypes[1] = {ObjectIdGetDatum(rangeArrayOid)};
 	ArrayType  *allParameterTypes = construct_array(allParamTypes, 1, OIDOID,
 													sizeof(Oid), true, 'i');
@@ -1808,7 +1806,7 @@ makeMultirangeConstructors(const char *name, Oid namespace,
 	{
 		oidvector  *constructorArgTypesVector;
 
-		constructorArgTypesVector = buildoidvector(constructorArgTypes,
+		constructorArgTypesVector = buildoidvector(&constructorArgTypes,
 												   pronargs[i]);
 
 		myself = ProcedureCreate(name,	/* name: same as multirange type */
diff --git a/src/backend/parser/parse_coerce.c b/src/backend/parser/parse_coerce.c
index 4c7a5f7fbe..49c75dc12d 100644
--- a/src/backend/parser/parse_coerce.c
+++ b/src/backend/parser/parse_coerce.c
@@ -1918,6 +1918,8 @@ enforce_generic_type_consistency(const Oid *actual_arg_types,
 							   format_type_be(range_typeid),
 							   format_type_be(elem_typeid))));
 		}
+		else
+			range_typelem = InvalidOid;
 	}
 
 	/* Get the range type based on the multirange type, if we have one */
diff --git a/src/backend/utils/adt/multirangetypes.c b/src/backend/utils/adt/multirangetypes.c
index 5323a6a635..7717acb3b1 100644
--- a/src/backend/utils/adt/multirangetypes.c
+++ b/src/backend/utils/adt/multirangetypes.c
@@ -91,7 +91,6 @@ multirange_in(PG_FUNCTION_ARGS)
 	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;
@@ -101,13 +100,12 @@ multirange_in(PG_FUNCTION_ARGS)
 	MultirangeType *ret;
 	MultirangeParseState parse_state;
 	const char *ptr = input_str;
-	const char *range_str;
+	const char *range_str = NULL;
 	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))
@@ -351,9 +349,12 @@ multirange_send(PG_FUNCTION_ARGS)
 	for (i = 0; i < range_count; i++)
 	{
 		Datum		range = RangeTypePGetDatum(ranges[i]);
+		uint32		range_len;
+		char	   *range_data;
+
 		range = PointerGetDatum(SendFunctionCall(&cache->proc, range));
-		uint32		range_len = VARSIZE(range) - VARHDRSZ;
-		char	   *range_data = VARDATA(range);
+		range_len = VARSIZE(range) - VARHDRSZ;
+		range_data = VARDATA(range);
 
 		pq_sendint32(buf, range_len);
 		pq_sendbytes(buf, range_data, range_len);
@@ -726,6 +727,829 @@ multirange_constructor0(PG_FUNCTION_ARGS)
 }
 
 
+/* multirange, multirange -> multirange type functions */
+
+/* multirange union */
+Datum
+range_union_range(PG_FUNCTION_ARGS)
+{
+	RangeType  *r1 = PG_GETARG_RANGE_P(0);
+	RangeType  *r2 = PG_GETARG_RANGE_P(1);
+	Oid			mltrngtypoid = get_fn_expr_rettype(fcinfo->flinfo);
+	TypeCacheEntry *typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
+	MultirangeType *mr = make_multirange(mltrngtypoid, typcache->rngtype, 1, &r2);
+
+	PG_RETURN_MULTIRANGE_P(range_union_multirange_internal(typcache, r1, mr));
+}
+
+Datum
+range_union_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_MULTIRANGE_P(range_union_multirange_internal(typcache, r, mr));
+}
+
+Datum
+multirange_union_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_MULTIRANGE_P(range_union_multirange_internal(typcache, r, mr));
+}
+
+Datum
+multirange_union_multirange(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
+	MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count1;
+	int32		range_count2;
+	int32		range_count3;
+	RangeType **ranges1;
+	RangeType **ranges2;
+	RangeType **ranges3;
+
+	if (MultirangeIsEmpty(mr1))
+		PG_RETURN_MULTIRANGE_P(mr2);
+	if (MultirangeIsEmpty(mr2))
+		PG_RETURN_MULTIRANGE_P(mr1);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr1));
+
+	multirange_deserialize(mr1, &range_count1, &ranges1);
+	multirange_deserialize(mr2, &range_count2, &ranges2);
+
+	range_count3 = range_count1 + range_count2;
+	ranges3 = palloc0(range_count3 * sizeof(RangeType *));
+	memcpy(ranges3, ranges1, range_count1 * sizeof(RangeType *));
+	memcpy(ranges3 + range_count1, ranges2, range_count2 * sizeof(RangeType *));
+	PG_RETURN_MULTIRANGE_P(make_multirange(typcache->type_id, typcache->rngtype,
+										   range_count3, ranges3));
+}
+
+MultirangeType *
+range_union_multirange_internal(TypeCacheEntry *typcache, RangeType *r,
+								MultirangeType * mr)
+{
+	int32		range_count;
+	RangeType **ranges1;
+	RangeType **ranges2;
+
+	if (RangeIsEmpty(r))
+		return mr;
+	if (MultirangeIsEmpty(mr))
+		return make_multirange(typcache->type_id, typcache->rngtype, 1, &r);
+
+	multirange_deserialize(mr, &range_count, &ranges1);
+
+	ranges2 = palloc0((range_count + 1) * sizeof(RangeType *));
+
+	memcpy(ranges2, ranges1, range_count * sizeof(RangeType *));
+	ranges2[range_count] = r;
+	return make_multirange(typcache->type_id, typcache->rngtype, range_count + 1, ranges2);
+}
+
+/* multirange minus */
+Datum
+range_minus_range(PG_FUNCTION_ARGS)
+{
+	RangeType  *r1 = PG_GETARG_RANGE_P(0);
+	RangeType  *r2 = PG_GETARG_RANGE_P(1);
+	Oid			mltrngtypoid = get_fn_expr_rettype(fcinfo->flinfo);
+	TypeCacheEntry *typcache;
+	TypeCacheEntry *rangetyp;
+
+	typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
+	rangetyp = typcache->rngtype;
+
+	if (RangeIsEmpty(r1) || RangeIsEmpty(r2))
+		PG_RETURN_MULTIRANGE_P(make_multirange(mltrngtypoid, rangetyp, 1, &r1));
+
+	PG_RETURN_MULTIRANGE_P(multirange_minus_multirange_internal(mltrngtypoid,
+																rangetyp,
+																1,
+																&r1,
+																1,
+																&r2));
+}
+
+Datum
+range_minus_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	Oid			mltrngtypoid = MultirangeTypeGetOid(mr);
+	TypeCacheEntry *typcache;
+	TypeCacheEntry *rangetyp;
+	int32		range_count;
+	RangeType **ranges;
+
+	typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
+	rangetyp = typcache->rngtype;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		PG_RETURN_MULTIRANGE_P(make_multirange(mltrngtypoid, rangetyp, 1, &r));
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_MULTIRANGE_P(multirange_minus_multirange_internal(mltrngtypoid,
+																rangetyp,
+																1,
+																&r,
+																range_count,
+																ranges));
+}
+
+Datum
+multirange_minus_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	Oid			mltrngtypoid = MultirangeTypeGetOid(mr);
+	TypeCacheEntry *typcache;
+	TypeCacheEntry *rangetyp;
+	int32		range_count;
+	RangeType **ranges;
+
+	typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
+	rangetyp = typcache->rngtype;
+
+	if (MultirangeIsEmpty(mr) || RangeIsEmpty(r))
+		PG_RETURN_MULTIRANGE_P(mr);
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_MULTIRANGE_P(multirange_minus_multirange_internal(mltrngtypoid,
+																rangetyp,
+																range_count,
+																ranges,
+																1,
+																&r));
+}
+
+Datum
+multirange_minus_multirange(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
+	MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
+	Oid			mltrngtypoid = MultirangeTypeGetOid(mr1);
+	TypeCacheEntry *typcache;
+	TypeCacheEntry *rangetyp;
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+
+	typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
+	rangetyp = typcache->rngtype;
+
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		PG_RETURN_MULTIRANGE_P(mr1);
+
+	multirange_deserialize(mr1, &range_count1, &ranges1);
+	multirange_deserialize(mr2, &range_count2, &ranges2);
+
+	PG_RETURN_MULTIRANGE_P(multirange_minus_multirange_internal(mltrngtypoid,
+																rangetyp,
+																range_count1,
+																ranges1,
+																range_count2,
+																ranges2));
+}
+
+MultirangeType *
+multirange_minus_multirange_internal(Oid mltrngtypoid, TypeCacheEntry *rangetyp,
+									 int32 range_count1, RangeType **ranges1, int32 range_count2, RangeType **ranges2)
+{
+	RangeType  *r1;
+	RangeType  *r2;
+	RangeType **ranges3;
+	int32		range_count3;
+	int32		i1;
+	int32		i2;
+
+	/*
+	 * Worst case: every range in ranges1 makes a different cut to some range
+	 * in ranges2.
+	 */
+	ranges3 = palloc0((range_count1 + range_count2) * sizeof(RangeType *));
+	range_count3 = 0;
+
+	/*
+	 * For each range in mr1, keep subtracting until it's gone or the ranges
+	 * in mr2 have passed it. After a subtraction we assign what's left back
+	 * to r1. The parallel progress through mr1 and mr2 is similar to
+	 * multirange_overlaps_multirange_internal.
+	 */
+	r2 = ranges2[0];
+	for (i1 = 0, i2 = 0; i1 < range_count1; i1++)
+	{
+		r1 = ranges1[i1];
+
+		/* Discard r2s while r2 << r1 */
+		while (r2 != NULL && range_before_internal(rangetyp, r2, r1))
+		{
+			r2 = ++i2 >= range_count2 ? NULL : ranges2[i2];
+		}
+
+		while (r2 != NULL)
+		{
+			if (range_split_internal(rangetyp, r1, r2, &ranges3[range_count3], &r1))
+			{
+				/*
+				 * If r2 takes a bite out of the middle of r1, we need two
+				 * outputs
+				 */
+				range_count3++;
+				r2 = ++i2 >= range_count2 ? NULL : ranges2[i2];
+
+			}
+			else if (range_overlaps_internal(rangetyp, r1, r2))
+			{
+				/*
+				 * If r2 overlaps r1, replace r1 with r1 - r2.
+				 */
+				r1 = range_minus_internal(rangetyp, r1, r2);
+
+				/*
+				 * If r2 goes past r1, then we need to stay with it, in case
+				 * it hits future r1s. Otherwise we need to keep r1, in case
+				 * future r2s hit it. Since we already subtracted, there's no
+				 * point in using the overright/overleft calls.
+				 */
+				if (RangeIsEmpty(r1) || range_before_internal(rangetyp, r1, r2))
+					break;
+				else
+					r2 = ++i2 >= range_count2 ? NULL : ranges2[i2];
+
+			}
+			else
+			{
+				/*
+				 * This and all future r2s are past r1, so keep them. Also
+				 * assign whatever is left of r1 to the result.
+				 */
+				break;
+			}
+		}
+
+		/*
+		 * Nothing else can remove anything from r1, so keep it. Even if r1 is
+		 * empty here, make_multirange will remove it.
+		 */
+		ranges3[range_count3++] = r1;
+	}
+
+	return make_multirange(mltrngtypoid, rangetyp, range_count3, ranges3);
+}
+
+/* multirange intersection */
+Datum
+range_intersect_range(PG_FUNCTION_ARGS)
+{
+	RangeType  *r1 = PG_GETARG_RANGE_P(0);
+	RangeType  *r2 = PG_GETARG_RANGE_P(1);
+	Oid			mltrngtypoid = get_fn_expr_rettype(fcinfo->flinfo);
+	TypeCacheEntry *typcache;
+	TypeCacheEntry *rangetyp;
+
+	typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
+	rangetyp = typcache->rngtype;
+
+	if (RangeIsEmpty(r1) || RangeIsEmpty(r2))
+		PG_RETURN_MULTIRANGE_P(make_empty_multirange(mltrngtypoid, rangetyp));
+
+	PG_RETURN_MULTIRANGE_P(multirange_intersect_multirange_internal(mltrngtypoid,
+																	rangetyp,
+																	1,
+																	&r1,
+																	1,
+																	&r2));
+}
+
+Datum
+range_intersect_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r1 = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
+	Oid			mltrngtypoid = MultirangeTypeGetOid(mr2);
+	TypeCacheEntry *typcache;
+	TypeCacheEntry *rangetyp;
+	int32		range_count2;
+	RangeType **ranges2;
+
+	typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
+	rangetyp = typcache->rngtype;
+
+	if (RangeIsEmpty(r1) || MultirangeIsEmpty(mr2))
+		PG_RETURN_MULTIRANGE_P(make_empty_multirange(mltrngtypoid, rangetyp));
+
+	multirange_deserialize(mr2, &range_count2, &ranges2);
+
+	PG_RETURN_MULTIRANGE_P(multirange_intersect_multirange_internal(mltrngtypoid,
+																	rangetyp,
+																	1,
+																	&r1,
+																	range_count2,
+																	ranges2));
+}
+
+Datum
+multirange_intersect_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r2 = PG_GETARG_RANGE_P(1);
+	Oid			mltrngtypoid = MultirangeTypeGetOid(mr1);
+	TypeCacheEntry *typcache;
+	TypeCacheEntry *rangetyp;
+	int32		range_count1;
+	RangeType **ranges1;
+
+	typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
+	rangetyp = typcache->rngtype;
+
+	if (MultirangeIsEmpty(mr1) || RangeIsEmpty(r2))
+		PG_RETURN_MULTIRANGE_P(make_empty_multirange(mltrngtypoid, rangetyp));
+
+	multirange_deserialize(mr1, &range_count1, &ranges1);
+
+	PG_RETURN_MULTIRANGE_P(multirange_intersect_multirange_internal(mltrngtypoid,
+																	rangetyp,
+																	range_count1,
+																	ranges1,
+																	1,
+																	&r2));
+}
+
+Datum
+multirange_intersect_multirange(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
+	MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
+	Oid			mltrngtypoid = MultirangeTypeGetOid(mr1);
+	TypeCacheEntry *typcache;
+	TypeCacheEntry *rangetyp;
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+
+	typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
+	rangetyp = typcache->rngtype;
+
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		PG_RETURN_MULTIRANGE_P(make_empty_multirange(mltrngtypoid, rangetyp));
+
+	multirange_deserialize(mr1, &range_count1, &ranges1);
+	multirange_deserialize(mr2, &range_count2, &ranges2);
+
+	PG_RETURN_MULTIRANGE_P(multirange_intersect_multirange_internal(mltrngtypoid,
+																	rangetyp,
+																	range_count1,
+																	ranges1,
+																	range_count2,
+																	ranges2));
+}
+
+MultirangeType *
+multirange_intersect_multirange_internal(Oid mltrngtypoid, TypeCacheEntry *rangetyp,
+										 int32 range_count1, RangeType **ranges1,
+										 int32 range_count2, RangeType **ranges2)
+{
+	RangeType  *r1;
+	RangeType  *r2;
+	RangeType **ranges3;
+	int32		range_count3;
+	int32		i1;
+	int32		i2;
+
+	if (range_count1 == 0 || range_count2 == 0)
+		return make_multirange(mltrngtypoid, rangetyp, 0, NULL);
+
+	/*
+	 * Worst case is a stitching pattern like this:
+	 *
+	 * mr1: --- --- --- ---
+	 * mr2:   --- --- ---
+	 * mr3:   - - - - - -
+	 *
+	 * That seems to be range_count1 + range_count2 - 1, but one extra won't
+	 * hurt.
+	 */
+	ranges3 = palloc0((range_count1 + range_count2) * sizeof(RangeType *));
+	range_count3 = 0;
+
+	/*
+	 * For each range in mr1, keep intersecting until the ranges in mr2 have
+	 * passed it. The parallel progress through mr1 and mr2 is similar to
+	 * multirange_minus_multirange_internal, but we don't have to assign back
+	 * to r1.
+	 */
+	r2 = ranges2[0];
+	for (i1 = 0, i2 = 0; i1 < range_count1; i1++)
+	{
+		r1 = ranges1[i1];
+
+		/* Discard r2s while r2 << r1 */
+		while (r2 != NULL && range_before_internal(rangetyp, r2, r1))
+		{
+			r2 = ++i2 >= range_count2 ? NULL : ranges2[i2];
+		}
+
+		while (r2 != NULL)
+		{
+			if (range_overlaps_internal(rangetyp, r1, r2))
+			{
+				/* Keep the overlapping part */
+				ranges3[range_count3++] = range_intersect_internal(rangetyp, r1, r2);
+
+				/* If we "used up" all of r2, go to the next one... */
+				if (range_overleft_internal(rangetyp, r2, r1))
+					r2 = ++i2 >= range_count2 ? NULL : ranges2[i2];
+
+				/* ...otherwise go to the next r1 */
+				else
+					break;
+			}
+			else
+				/* We're past r1, so move to the next one */
+				break;
+		}
+
+		/* If we're out of r2s, there can be no more intersections */
+		if (r2 == NULL)
+			break;
+	}
+
+	return make_multirange(mltrngtypoid, rangetyp, range_count3, ranges3);
+}
+
+/*
+ * range_agg_transfn: combine adjacent/overlapping ranges.
+ *
+ * All we do here is gather the input ranges into an array
+ * so that the finalfn can sort and combine them.
+ */
+Datum
+range_agg_transfn(PG_FUNCTION_ARGS)
+{
+	MemoryContext aggContext;
+	Oid			rngtypoid;
+	ArrayBuildState *state;
+
+	if (!AggCheckCallContext(fcinfo, &aggContext))
+		elog(ERROR, "range_agg_transfn called in non-aggregate context");
+
+	rngtypoid = get_fn_expr_argtype(fcinfo->flinfo, 1);
+	if (!type_is_range(rngtypoid))
+		ereport(ERROR, (errmsg("range_agg must be called with a range")));
+
+	if (PG_ARGISNULL(0))
+		state = initArrayResult(rngtypoid, aggContext, false);
+	else
+		state = (ArrayBuildState *) PG_GETARG_POINTER(0);
+
+	/* skip NULLs */
+	if (!PG_ARGISNULL(1))
+		accumArrayResult(state, PG_GETARG_DATUM(1), false, rngtypoid, aggContext);
+
+	PG_RETURN_POINTER(state);
+}
+
+/*
+ * range_agg_finalfn: use our internal array to merge touching ranges.
+ */
+Datum
+range_agg_finalfn(PG_FUNCTION_ARGS)
+{
+	MemoryContext aggContext;
+	Oid			mltrngtypoid;
+	TypeCacheEntry *typcache;
+	ArrayBuildState *state;
+	int32		range_count;
+	RangeType **ranges;
+	int			i;
+
+	if (!AggCheckCallContext(fcinfo, &aggContext))
+		elog(ERROR, "range_agg_finalfn called in non-aggregate context");
+
+	state = PG_ARGISNULL(0) ? NULL : (ArrayBuildState *) PG_GETARG_POINTER(0);
+	if (state == NULL)
+		/* This shouldn't be possible, but just in case.... */
+		PG_RETURN_NULL();
+
+	/* Also return NULL if we had zero inputs, like other aggregates */
+	range_count = state->nelems;
+	if (range_count == 0)
+		PG_RETURN_NULL();
+
+	mltrngtypoid = get_fn_expr_rettype(fcinfo->flinfo);
+	typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
+
+	ranges = palloc0(range_count * sizeof(RangeType *));
+	for (i = 0; i < range_count; i++)
+		ranges[i] = DatumGetRangeTypeP(state->dvalues[i]);
+
+	PG_RETURN_MULTIRANGE_P(make_multirange(mltrngtypoid, typcache->rngtype, range_count, ranges));
+}
+
+Datum
+multirange_intersect_agg_transfn(PG_FUNCTION_ARGS)
+{
+	MemoryContext aggContext;
+	Oid			mltrngtypoid;
+	TypeCacheEntry *typcache;
+	MultirangeType *result;
+	MultirangeType *current;
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+
+	if (!AggCheckCallContext(fcinfo, &aggContext))
+		elog(ERROR, "multirange_intersect_agg_transfn called in non-aggregate context");
+
+	mltrngtypoid = get_fn_expr_argtype(fcinfo->flinfo, 1);
+	if (!type_is_multirange(mltrngtypoid))
+		ereport(ERROR, (errmsg("range_intersect_agg must be called with a multirange")));
+
+	typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
+
+	/* strictness ensures these are non-null */
+	result = PG_GETARG_MULTIRANGE_P(0);
+	current = PG_GETARG_MULTIRANGE_P(1);
+
+	multirange_deserialize(result, &range_count1, &ranges1);
+	multirange_deserialize(current, &range_count2, &ranges2);
+
+	result = multirange_intersect_multirange_internal(mltrngtypoid,
+													  typcache->rngtype,
+													  range_count1,
+													  ranges1,
+													  range_count2,
+													  ranges2);
+	PG_RETURN_RANGE_P(result);
+}
+
+
+/* multirange -> element type functions */
+
+/* extract lower bound value */
+Datum
+multirange_lower(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+	bool		isnull;
+	Datum		result;
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_NULL();
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	result = range_lower_internal(typcache->rngtype, ranges[0], &isnull);
+
+	if (isnull)
+		PG_RETURN_NULL();
+	else
+		PG_RETURN_DATUM(result);
+}
+
+/* extract upper bound value */
+Datum
+multirange_upper(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+	bool		isnull;
+	Datum		result;
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_NULL();
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	result = range_upper_internal(typcache->rngtype, ranges[range_count - 1], &isnull);
+
+	if (isnull)
+		PG_RETURN_NULL();
+	else
+		PG_RETURN_DATUM(result);
+}
+
+
+/* multirange -> bool functions */
+
+/* is multirange empty? */
+Datum
+multirange_empty(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+
+	PG_RETURN_BOOL(mr->rangeCount == 0);
+}
+
+/* is lower bound inclusive? */
+Datum
+multirange_lower_inc(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	int32		range_count;
+	RangeType **ranges;
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_has_flag(ranges[0], RANGE_LB_INC));
+}
+
+/* is upper bound inclusive? */
+Datum
+multirange_upper_inc(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	int32		range_count;
+	RangeType **ranges;
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_has_flag(ranges[range_count - 1], RANGE_UB_INC));
+}
+
+/* is lower bound infinite? */
+Datum
+multirange_lower_inf(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	int32		range_count;
+	RangeType **ranges;
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_has_flag(ranges[0], RANGE_LB_INF));
+}
+
+/* is upper bound infinite? */
+Datum
+multirange_upper_inf(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	int32		range_count;
+	RangeType **ranges;
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_has_flag(ranges[range_count - 1], RANGE_UB_INF));
+}
+
+
+
+/* multirange, element -> bool functions */
+
+/* contains? */
+Datum
+multirange_contains_elem(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	Datum		val = PG_GETARG_DATUM(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(multirange_contains_elem_internal(typcache, mr, val));
+}
+
+/* contained by? */
+Datum
+elem_contained_by_multirange(PG_FUNCTION_ARGS)
+{
+	Datum		val = PG_GETARG_DATUM(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(multirange_contains_elem_internal(typcache, mr, val));
+}
+
+/*
+ * Test whether multirange mr contains a specific element value.
+ */
+bool
+multirange_contains_elem_internal(TypeCacheEntry *typcache, MultirangeType * mr, Datum val)
+{
+	TypeCacheEntry *rangetyp;
+	int32		range_count;
+	RangeType **ranges;
+	RangeType  *r;
+	int			i;
+
+	rangetyp = typcache->rngtype;
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	for (i = 0; i < range_count; i++)
+	{
+		r = ranges[i];
+		if (range_contains_elem_internal(rangetyp, r, val))
+			return true;
+	}
+
+	return false;
+}
+
+/* multirange, range -> bool functions */
+
+/* contains? */
+Datum
+multirange_contains_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(multirange_contains_range_internal(typcache, mr, r));
+}
+
+/* contained by? */
+Datum
+range_contained_by_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(multirange_contains_range_internal(typcache, mr, r));
+}
+
+/*
+ * Test whether multirange mr contains a specific range r.
+ */
+bool
+multirange_contains_range_internal(TypeCacheEntry *typcache, MultirangeType * mr, RangeType *r)
+{
+	TypeCacheEntry *rangetyp;
+	int32		range_count;
+	RangeType **ranges;
+	RangeType  *mrr;
+	int			i;
+
+	rangetyp = typcache->rngtype;
+
+	/*
+	 * Every multirange contains an infinite number of empty ranges, even an
+	 * empty one.
+	 */
+	if (RangeIsEmpty(r))
+		return true;
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	for (i = 0; i < range_count; i++)
+	{
+		mrr = ranges[i];
+		if (range_contains_internal(rangetyp, mrr, r))
+			return true;
+	}
+
+	return false;
+}
+
+
 /* multirange, multirange -> bool functions */
 
 /* equality (internal version) */
@@ -795,6 +1619,538 @@ multirange_ne(PG_FUNCTION_ARGS)
 	PG_RETURN_BOOL(multirange_ne_internal(typcache, mr1, mr2));
 }
 
+/* overlaps? */
+Datum
+range_overlaps_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(range_overlaps_multirange_internal(typcache, r, mr));
+}
+
+Datum
+multirange_overlaps_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(range_overlaps_multirange_internal(typcache, r, mr));
+}
+
+Datum
+multirange_overlaps_multirange(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_overlaps_multirange_internal(typcache, mr1, mr2));
+}
+
+bool
+range_overlaps_multirange_internal(TypeCacheEntry *typcache, RangeType *r, MultirangeType * mr)
+{
+	TypeCacheEntry *rangetyp;
+	int32		range_count;
+	int32		i;
+	RangeType **ranges;
+	RangeType  *mrr;
+
+	/*
+	 * Empties never overlap, even with empties. (This seems strange since
+	 * they *do* contain each other, but we want to follow how ranges work.)
+	 */
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		return false;
+
+	rangetyp = typcache->rngtype;
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	/* Scan through mrr and once it gets to r they either overlap or not */
+	mrr = ranges[0];
+
+	/* Discard mrrs while mrr << r */
+	i = 0;
+	while (range_before_internal(rangetyp, mrr, r))
+	{
+		if (++i >= range_count)
+			return false;
+		mrr = ranges[i];
+	}
+
+	/* Now either we overlap or we passed r */
+	return range_overlaps_internal(rangetyp, mrr, r);
+}
+
+bool
+multirange_overlaps_multirange_internal(TypeCacheEntry *typcache, MultirangeType * mr1,
+										MultirangeType * mr2)
+{
+	TypeCacheEntry *rangetyp;
+	int32		range_count1;
+	int32		range_count2;
+	int32		i1;
+	int32		i2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+	RangeType  *r1;
+	RangeType  *r2;
+
+	/*
+	 * Empties never overlap, even with empties. (This seems strange since
+	 * they *do* contain each other, but we want to follow how ranges work.)
+	 */
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		return false;
+
+	rangetyp = typcache->rngtype;
+
+	multirange_deserialize(mr1, &range_count1, &ranges1);
+	multirange_deserialize(mr2, &range_count2, &ranges2);
+
+	/*
+	 * Every range in mr1 gets a chance to overlap with the ranges in mr2, but
+	 * we can use their ordering to avoid O(n^2). This is similar to
+	 * range_overlaps_multirange where r1 : r2 :: mrr : r, but there if we
+	 * don't find an overlap with r we're done, and here if we don't find an
+	 * overlap with r2 we try the next r2.
+	 */
+	r1 = ranges1[0];
+	for (i1 = 0, i2 = 0; i2 < range_count2; i2++)
+	{
+		r2 = ranges2[i2];
+
+		/* Discard r1s while r1 << r2 */
+		while (range_before_internal(rangetyp, r1, r2))
+		{
+			if (++i1 >= range_count1)
+				return false;
+			r1 = ranges1[i1];
+		}
+
+		/*
+		 * If r1 && r2, we're done, otherwise we failed to find an overlap for
+		 * r2, so go to the next one.
+		 */
+		if (range_overlaps_internal(rangetyp, r1, r2))
+			return true;
+	}
+
+	/* We looked through all of mr2 without finding an overlap */
+	return false;
+}
+
+/* does not extend to right of? */
+Datum
+range_overleft_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_overleft_internal(typcache->rngtype, r, ranges[range_count - 1]));
+}
+
+Datum
+multirange_overleft_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_overleft_internal(typcache->rngtype, ranges[range_count - 1], r));
+}
+
+Datum
+multirange_overleft_multirange(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
+	MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr1));
+
+	multirange_deserialize(mr1, &range_count1, &ranges1);
+	multirange_deserialize(mr2, &range_count2, &ranges2);
+
+	PG_RETURN_BOOL(range_overleft_internal(typcache->rngtype, ranges1[range_count1 - 1], ranges2[range_count2 - 1]));
+}
+
+/* does not extend to left of? */
+Datum
+range_overright_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_overright_internal(typcache->rngtype, r, ranges[0]));
+}
+
+Datum
+multirange_overright_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_overright_internal(typcache->rngtype, ranges[0], r));
+}
+
+Datum
+multirange_overright_multirange(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
+	MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr1));
+
+	multirange_deserialize(mr1, &range_count1, &ranges1);
+	multirange_deserialize(mr2, &range_count2, &ranges2);
+
+	PG_RETURN_BOOL(range_overright_internal(typcache->rngtype, ranges1[0], ranges2[0]));
+}
+
+/* contains? */
+Datum
+multirange_contains_multirange(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_contains_multirange_internal(typcache, mr1, mr2));
+}
+
+/* contained by? */
+Datum
+multirange_contained_by_multirange(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_contains_multirange_internal(typcache, mr2, mr1));
+}
+
+/*
+ * Test whether multirange mr1 contains every range from another multirange mr2.
+ */
+bool
+multirange_contains_multirange_internal(TypeCacheEntry *typcache,
+										MultirangeType * mr1, MultirangeType * mr2)
+{
+	TypeCacheEntry *rangetyp;
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+	RangeType  *r1;
+	RangeType  *r2;
+	int			i1,
+				i2;
+
+	rangetyp = typcache->rngtype;
+
+	multirange_deserialize(mr1, &range_count1, &ranges1);
+	multirange_deserialize(mr2, &range_count2, &ranges2);
+
+	/*
+	 * We follow the same logic for empties as ranges: - an empty multirange
+	 * contains an empty range/multirange. - an empty multirange can't contain
+	 * any other range/multirange. - an empty multirange is contained by any
+	 * other range/multirange.
+	 */
+
+	if (range_count2 == 0)
+		return true;
+	if (range_count1 == 0)
+		return false;
+
+	/*
+	 * Every range in mr2 must be contained by some range in mr1. To avoid
+	 * O(n^2) we walk through both ranges in tandem.
+	 */
+	r1 = ranges1[0];
+	for (i1 = 0, i2 = 0; i2 < range_count2; i2++)
+	{
+		r2 = ranges2[i2];
+
+		/* Discard r1s while r1 << r2 */
+		while (range_before_internal(rangetyp, r1, r2))
+		{
+			if (++i1 >= range_count1)
+				return false;
+			r1 = ranges1[i1];
+		}
+
+		/*
+		 * If r1 @> r2, go to the next r2, otherwise return false (since every
+		 * r1[n] and r1[n+1] must have a gap). Note this will give weird
+		 * answers if you don't canonicalize, e.g. with a custom
+		 * int2multirange {[1,1], [2,2]} there is a "gap". But that is
+		 * consistent with other range operators, e.g. '[1,1]'::int2range -|-
+		 * '[2,2]'::int2range is false.
+		 */
+		if (!range_contains_internal(rangetyp, r1, r2))
+			return false;
+	}
+
+	/* All ranges in mr2 are satisfied */
+	return true;
+}
+
+/* strictly left of? */
+Datum
+range_before_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(range_before_multirange_internal(typcache, r, mr));
+}
+
+Datum
+multirange_before_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(range_after_multirange_internal(typcache, r, mr));
+}
+
+Datum
+multirange_before_multirange(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_before_multirange_internal(typcache, mr1, mr2));
+}
+
+/* strictly right of? */
+Datum
+range_after_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(range_after_multirange_internal(typcache, r, mr));
+}
+
+Datum
+multirange_after_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(range_before_multirange_internal(typcache, r, mr));
+}
+
+Datum
+multirange_after_multirange(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_before_multirange_internal(typcache, mr2, mr1));
+}
+
+/* strictly left of? (internal version) */
+bool
+range_before_multirange_internal(TypeCacheEntry *typcache, RangeType *r,
+								 MultirangeType * mr)
+{
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		return false;
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	return range_before_internal(typcache->rngtype, r, ranges[0]);
+}
+
+bool
+multirange_before_multirange_internal(TypeCacheEntry *typcache, MultirangeType * mr1,
+									  MultirangeType * mr2)
+{
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		return false;
+
+	multirange_deserialize(mr1, &range_count1, &ranges1);
+	multirange_deserialize(mr2, &range_count2, &ranges2);
+
+	return range_before_internal(typcache->rngtype, ranges1[range_count1 - 1],
+								 ranges2[0]);
+}
+
+/* strictly right of? (internal version) */
+bool
+range_after_multirange_internal(TypeCacheEntry *typcache, RangeType *r,
+								MultirangeType * mr)
+{
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		return false;
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	return range_after_internal(typcache->rngtype, r, ranges[range_count - 1]);
+}
+
+/* adjacent to? */
+Datum
+range_adjacent_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		return false;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_adjacent_internal(typcache->rngtype, r, ranges[0]));
+}
+
+Datum
+multirange_adjacent_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		return false;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_adjacent_internal(typcache->rngtype, ranges[range_count - 1], r));
+}
+
+Datum
+multirange_adjacent_multirange(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
+	MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		return false;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr1));
+
+	multirange_deserialize(mr1, &range_count1, &ranges1);
+	multirange_deserialize(mr2, &range_count2, &ranges2);
+
+	PG_RETURN_BOOL(range_adjacent_internal(typcache->rngtype, ranges1[range_count1 - 1], ranges2[0]));
+}
+
 /* Btree support */
 
 /* btree comparator */
@@ -891,6 +2247,29 @@ multirange_gt(PG_FUNCTION_ARGS)
 	PG_RETURN_BOOL(cmp > 0);
 }
 
+/* multirange -> range functions */
+
+/* Find the smallest range that includes everything in the multirange */
+Datum
+range_merge_from_multirange(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	Oid			mltrngtypoid = MultirangeTypeGetOid(mr);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_RANGE_P(make_empty_range(typcache->rngtype));
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_RANGE_P(range_union_internal(typcache->rngtype, ranges[0],
+										   ranges[range_count - 1], false));
+}
+
 /* Hash support */
 
 /* hash a multirange value */
diff --git a/src/backend/utils/adt/rangetypes.c b/src/backend/utils/adt/rangetypes.c
index 65f68614c1..0651618363 100644
--- a/src/backend/utils/adt/rangetypes.c
+++ b/src/backend/utils/adt/rangetypes.c
@@ -429,19 +429,37 @@ range_lower(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
 	TypeCacheEntry *typcache;
+	bool		isnull;
+	Datum		result;
+
+	typcache = range_get_typcache(fcinfo, RangeTypeGetOid(r1));
+
+	result = range_lower_internal(typcache, r1, &isnull);
+
+	if (isnull)
+		PG_RETURN_NULL();
+	else
+		PG_RETURN_DATUM(result);
+}
+
+Datum
+range_lower_internal(TypeCacheEntry *typcache, const RangeType *r1, bool *isnull)
+{
 	RangeBound	lower;
 	RangeBound	upper;
 	bool		empty;
 
-	typcache = range_get_typcache(fcinfo, RangeTypeGetOid(r1));
-
 	range_deserialize(typcache, r1, &lower, &upper, &empty);
 
 	/* Return NULL if there's no finite lower bound */
 	if (empty || lower.infinite)
-		PG_RETURN_NULL();
+	{
+		*isnull = true;
+		return 0;
+	}
 
-	PG_RETURN_DATUM(lower.val);
+	*isnull = false;
+	return lower.val;
 }
 
 /* extract upper bound value */
@@ -450,19 +468,37 @@ range_upper(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
 	TypeCacheEntry *typcache;
+	bool		isnull;
+	Datum		result;
+
+	typcache = range_get_typcache(fcinfo, RangeTypeGetOid(r1));
+
+	result = range_upper_internal(typcache, r1, &isnull);
+
+	if (isnull)
+		PG_RETURN_NULL();
+	else
+		PG_RETURN_DATUM(result);
+}
+
+Datum
+range_upper_internal(TypeCacheEntry *typcache, const RangeType *r1, bool *isnull)
+{
 	RangeBound	lower;
 	RangeBound	upper;
 	bool		empty;
 
-	typcache = range_get_typcache(fcinfo, RangeTypeGetOid(r1));
-
 	range_deserialize(typcache, r1, &lower, &upper, &empty);
 
 	/* Return NULL if there's no finite upper bound */
 	if (empty || upper.infinite)
-		PG_RETURN_NULL();
+	{
+		*isnull = true;
+		return 0;
+	}
 
-	PG_RETURN_DATUM(upper.val);
+	*isnull = false;
+	return upper.val;
 }
 
 
@@ -473,9 +509,8 @@ Datum
 range_empty(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
-	char		flags = range_get_flags(r1);
 
-	PG_RETURN_BOOL(flags & RANGE_EMPTY);
+	PG_RETURN_BOOL(range_has_flag(r1, RANGE_EMPTY));
 }
 
 /* is lower bound inclusive? */
@@ -483,9 +518,8 @@ Datum
 range_lower_inc(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
-	char		flags = range_get_flags(r1);
 
-	PG_RETURN_BOOL(flags & RANGE_LB_INC);
+	PG_RETURN_BOOL(range_has_flag(r1, RANGE_LB_INC));
 }
 
 /* is upper bound inclusive? */
@@ -493,9 +527,8 @@ Datum
 range_upper_inc(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
-	char		flags = range_get_flags(r1);
 
-	PG_RETURN_BOOL(flags & RANGE_UB_INC);
+	PG_RETURN_BOOL(range_has_flag(r1, RANGE_UB_INC));
 }
 
 /* is lower bound infinite? */
@@ -503,9 +536,8 @@ Datum
 range_lower_inf(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
-	char		flags = range_get_flags(r1);
 
-	PG_RETURN_BOOL(flags & RANGE_LB_INF);
+	PG_RETURN_BOOL(range_has_flag(r1, RANGE_LB_INF));
 }
 
 /* is upper bound infinite? */
@@ -513,9 +545,8 @@ Datum
 range_upper_inf(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
-	char		flags = range_get_flags(r1);
 
-	PG_RETURN_BOOL(flags & RANGE_UB_INF);
+	PG_RETURN_BOOL(range_has_flag(r1, RANGE_UB_INF));
 }
 
 
@@ -955,7 +986,25 @@ range_minus(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
 	RangeType  *r2 = PG_GETARG_RANGE_P(1);
+	RangeType  *ret;
 	TypeCacheEntry *typcache;
+
+	/* 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));
+
+	ret = range_minus_internal(typcache, r1, r2);
+	if (ret)
+		PG_RETURN_RANGE_P(ret);
+	else
+		PG_RETURN_NULL();
+}
+
+RangeType *
+range_minus_internal(TypeCacheEntry *typcache, RangeType *r1, RangeType *r2)
+{
 	RangeBound	lower1,
 				lower2;
 	RangeBound	upper1,
@@ -967,18 +1016,12 @@ range_minus(PG_FUNCTION_ARGS)
 				cmp_u1l2,
 				cmp_u1u2;
 
-	/* 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));
-
 	range_deserialize(typcache, r1, &lower1, &upper1, &empty1);
 	range_deserialize(typcache, r2, &lower2, &upper2, &empty2);
 
 	/* if either is empty, r1 is the correct answer */
 	if (empty1 || empty2)
-		PG_RETURN_RANGE_P(r1);
+		return r1;
 
 	cmp_l1l2 = range_cmp_bounds(typcache, &lower1, &lower2);
 	cmp_l1u2 = range_cmp_bounds(typcache, &lower1, &upper2);
@@ -991,34 +1034,34 @@ range_minus(PG_FUNCTION_ARGS)
 				 errmsg("result of range difference would not be contiguous")));
 
 	if (cmp_l1u2 > 0 || cmp_u1l2 < 0)
-		PG_RETURN_RANGE_P(r1);
+		return r1;
 
 	if (cmp_l1l2 >= 0 && cmp_u1u2 <= 0)
-		PG_RETURN_RANGE_P(make_empty_range(typcache));
+		return make_empty_range(typcache);
 
 	if (cmp_l1l2 <= 0 && cmp_u1l2 >= 0 && cmp_u1u2 <= 0)
 	{
 		lower2.inclusive = !lower2.inclusive;
 		lower2.lower = false;	/* it will become the upper bound */
-		PG_RETURN_RANGE_P(make_range(typcache, &lower1, &lower2, false));
+		return make_range(typcache, &lower1, &lower2, false);
 	}
 
 	if (cmp_l1l2 >= 0 && cmp_u1u2 >= 0 && cmp_l1u2 <= 0)
 	{
 		upper2.inclusive = !upper2.inclusive;
 		upper2.lower = true;	/* it will become the lower bound */
-		PG_RETURN_RANGE_P(make_range(typcache, &upper2, &upper1, false));
+		return make_range(typcache, &upper2, &upper1, false);
 	}
 
 	elog(ERROR, "unexpected case in range_minus");
-	PG_RETURN_NULL();
+	return NULL;
 }
 
 /*
  * Set union.  If strict is true, it is an error that the two input ranges
  * are not adjacent or overlapping.
  */
-static RangeType *
+RangeType *
 range_union_internal(TypeCacheEntry *typcache, RangeType *r1, RangeType *r2,
 					 bool strict)
 {
@@ -1099,6 +1142,19 @@ range_intersect(PG_FUNCTION_ARGS)
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
 	RangeType  *r2 = PG_GETARG_RANGE_P(1);
 	TypeCacheEntry *typcache;
+
+	/* 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));
+
+	PG_RETURN_RANGE_P(range_intersect_internal(typcache, r1, r2));
+}
+
+RangeType *
+range_intersect_internal(TypeCacheEntry *typcache, const RangeType *r1, const RangeType *r2)
+{
 	RangeBound	lower1,
 				lower2;
 	RangeBound	upper1,
@@ -1108,17 +1164,11 @@ range_intersect(PG_FUNCTION_ARGS)
 	RangeBound *result_lower;
 	RangeBound *result_upper;
 
-	/* 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));
-
 	range_deserialize(typcache, r1, &lower1, &upper1, &empty1);
 	range_deserialize(typcache, r2, &lower2, &upper2, &empty2);
 
-	if (empty1 || empty2 || !DatumGetBool(range_overlaps(fcinfo)))
-		PG_RETURN_RANGE_P(make_empty_range(typcache));
+	if (empty1 || empty2 || !range_overlaps_internal(typcache, r1, r2))
+		return make_empty_range(typcache);
 
 	if (range_cmp_bounds(typcache, &lower1, &lower2) >= 0)
 		result_lower = &lower1;
@@ -1130,54 +1180,83 @@ range_intersect(PG_FUNCTION_ARGS)
 	else
 		result_upper = &upper2;
 
-	PG_RETURN_RANGE_P(make_range(typcache, result_lower, result_upper, false));
+	return make_range(typcache, result_lower, result_upper, false);
 }
 
-/* Btree support */
+/* range, range -> range, range functions */
 
-/* btree comparator */
-Datum
-range_cmp(PG_FUNCTION_ARGS)
+/*
+ * range_split_internal - if r2 intersects the middle of r1, leaving non-empty
+ * ranges on both sides, then return true and set output1 and output2 to the
+ * results of r1 - r2 (in order). Otherwise return false and don't set output1
+ * or output2. Neither input range should be empty.
+ */
+bool
+range_split_internal(TypeCacheEntry *typcache, const RangeType *r1, const RangeType *r2,
+					 RangeType **output1, RangeType **output2)
 {
-	RangeType  *r1 = PG_GETARG_RANGE_P(0);
-	RangeType  *r2 = PG_GETARG_RANGE_P(1);
-	TypeCacheEntry *typcache;
 	RangeBound	lower1,
 				lower2;
 	RangeBound	upper1,
 				upper2;
 	bool		empty1,
 				empty2;
-	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));
 
 	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
+	if (range_cmp_bounds(typcache, &lower1, &lower2) < 0 &&
+		range_cmp_bounds(typcache, &upper1, &upper2) > 0)
 	{
-		cmp = range_cmp_bounds(typcache, &lower1, &lower2);
-		if (cmp == 0)
-			cmp = range_cmp_bounds(typcache, &upper1, &upper2);
+		/*
+		 * Need to invert inclusive/exclusive for the lower2 and upper2
+		 * points. They can't be infinite though. We're allowed to overwrite
+		 * these RangeBounds since they only exist locally.
+		 */
+		lower2.inclusive = !lower2.inclusive;
+		lower2.lower = false;
+		upper2.inclusive = !upper2.inclusive;
+		upper2.lower = true;
+
+		*output1 = make_range(typcache, &lower1, &lower2, false);
+		*output2 = make_range(typcache, &upper2, &upper1, false);
+		return true;
 	}
 
-	return cmp;
+	return false;
 }
 
+/* range -> range aggregate functions */
+
+Datum
+range_intersect_agg_transfn(PG_FUNCTION_ARGS)
+{
+	MemoryContext aggContext;
+	Oid			rngtypoid;
+	TypeCacheEntry *typcache;
+	RangeType  *result;
+	RangeType  *current;
+
+	if (!AggCheckCallContext(fcinfo, &aggContext))
+		elog(ERROR, "range_intersect_agg_transfn called in non-aggregate context");
+
+	rngtypoid = get_fn_expr_argtype(fcinfo->flinfo, 1);
+	if (!type_is_range(rngtypoid))
+		ereport(ERROR, (errmsg("range_intersect_agg must be called with a range")));
+
+	typcache = range_get_typcache(fcinfo, rngtypoid);
+
+	/* strictness ensures these are non-null */
+	result = PG_GETARG_RANGE_P(0);
+	current = PG_GETARG_RANGE_P(1);
+
+	result = range_intersect_internal(typcache, result, current);
+	PG_RETURN_RANGE_P(result);
+}
+
+
+/* Btree support */
+
 /* btree comparator */
 Datum
 range_cmp(PG_FUNCTION_ARGS)
@@ -1207,7 +1286,7 @@ range_cmp(PG_FUNCTION_ARGS)
  * Internal version of range_cmp
  */
 int
-range_cmp_internal(TypeCacheEntry *typcache, RangeType *r1, RangeType *r2)
+range_cmp_internal(TypeCacheEntry *typcache, const RangeType *r1, const RangeType *r2)
 {
 	RangeBound	lower1,
 				lower2;
@@ -1273,7 +1352,7 @@ range_gt(PG_FUNCTION_ARGS)
 /* Hash support */
 
 uint32
-hash_range_internal(TypeCacheEntry *typcache, RangeType *r)
+hash_range_internal(TypeCacheEntry *typcache, const RangeType *r)
 {
 	uint32		result;
 	TypeCacheEntry *scache;
@@ -1343,7 +1422,7 @@ hash_range(PG_FUNCTION_ARGS)
 }
 
 uint64
-hash_range_extended_internal(TypeCacheEntry *typcache, RangeType *r, Datum seed)
+hash_range_extended_internal(TypeCacheEntry *typcache, const RangeType *r, Datum seed)
 {
 	uint64		result;
 	TypeCacheEntry *scache;
@@ -1837,6 +1916,21 @@ range_get_flags(const RangeType *range)
 	return *((char *) range + VARSIZE(range) - 1);
 }
 
+/*
+ * range_has_flag: set whether a range has a specific flag.
+ *
+ * This lets expose some of our functions that just check flags
+ * to the rest of the code base (like to multiranges)
+ * without writing full-fledged *_internal versions.
+ */
+bool
+range_has_flag(const RangeType *r1, char flag)
+{
+	char		flags = range_get_flags(r1);
+
+	return flags & flag;
+}
+
 /*
  * range_set_contain_empty: set the RANGE_CONTAIN_EMPTY bit in the value.
  *
diff --git a/src/backend/utils/fmgr/funcapi.c b/src/backend/utils/fmgr/funcapi.c
index d65968d4eb..25623461bb 100644
--- a/src/backend/utils/fmgr/funcapi.c
+++ b/src/backend/utils/fmgr/funcapi.c
@@ -573,18 +573,20 @@ resolve_polymorphic_tupdesc(TupleDesc tupdesc, oidvector *declared_args,
 
 		if (OidIsValid(anymultirange_type))
 		{
-			Oid			rngtype = resolve_generic_type(ANYRANGEOID,
-													   anymultirange_type,
-													   ANYMULTIRANGEOID);
+			Oid			rngtype;
+			Oid			subtype;
+
+			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);
+			subtype = resolve_generic_type(ANYELEMENTOID,
+										   anyrange_type,
+										   ANYRANGEOID);
 
 			/* check for inconsistent array and multirange results */
 			if (OidIsValid(anyelement_type) && anyelement_type != subtype)
@@ -628,18 +630,21 @@ resolve_polymorphic_tupdesc(TupleDesc tupdesc, oidvector *declared_args,
 	{
 		if (OidIsValid(anymultirange_type))
 		{
-			Oid			rngtype = resolve_generic_type(ANYRANGEOID,
-													   anymultirange_type,
-													   ANYMULTIRANGEOID);
+			Oid		rngtype;
+			Oid		subtype;
+
+			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);
+			subtype = resolve_generic_type(ANYELEMENTOID,
+										   anyrange_type,
+										   ANYRANGEOID);
 
 			/* check for inconsistent array and multirange results */
 			if (OidIsValid(anyelement_type) && anyelement_type != subtype)
@@ -660,21 +665,25 @@ resolve_polymorphic_tupdesc(TupleDesc tupdesc, oidvector *declared_args,
 	{
 		if (OidIsValid(anyrange_type))
 		{
-			Oid			subtype = resolve_generic_type(ANYELEMENTOID,
-													   anyrange_type,
-													   ANYRANGEOID);
+			Oid		subtype;
+			Oid		mltrngtype;
+			Oid		rngtype;
+
+			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);
+			mltrngtype = resolve_generic_type(ANYMULTIRANGEOID,
+											  anyrange_type,
+											  ANYRANGEOID);
 
 			/* check for inconsistent range and multirange results */
-			Oid			rngtype = get_multirange_subtype(mltrngtype);
+			rngtype = get_multirange_subtype(mltrngtype);
 
 			if (OidIsValid(anyrange_type) && anyrange_type != rngtype)
 				return false;
@@ -900,18 +909,21 @@ resolve_polymorphic_argtypes(int numargs, Oid *argtypes, char *argmodes,
 
 		if (OidIsValid(anymultirange_type))
 		{
-			Oid			rngtype = resolve_generic_type(ANYRANGEOID,
-													   anymultirange_type,
-													   ANYMULTIRANGEOID);
+			Oid			rngtype;
+			Oid			subtype;
+			
+			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);
+			subtype = resolve_generic_type(ANYELEMENTOID,
+										   anyrange_type,
+										   ANYRANGEOID);
 
 			/* check for inconsistent array and multirange results */
 			if (OidIsValid(anyelement_type) && anyelement_type != subtype)
@@ -955,18 +967,21 @@ resolve_polymorphic_argtypes(int numargs, Oid *argtypes, char *argmodes,
 	{
 		if (OidIsValid(anymultirange_type))
 		{
-			Oid			rngtype = resolve_generic_type(ANYRANGEOID,
-													   anymultirange_type,
-													   ANYMULTIRANGEOID);
+			Oid			rngtype;
+			Oid			subtype;
+
+			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);
+			subtype = resolve_generic_type(ANYELEMENTOID,
+										   anyrange_type,
+										   ANYRANGEOID);
 
 			/* check for inconsistent array and multirange results */
 			if (OidIsValid(anyelement_type) && anyelement_type != subtype)
@@ -987,21 +1002,25 @@ resolve_polymorphic_argtypes(int numargs, Oid *argtypes, char *argmodes,
 	{
 		if (OidIsValid(anyrange_type))
 		{
-			Oid			subtype = resolve_generic_type(ANYELEMENTOID,
-													   anyrange_type,
-													   ANYRANGEOID);
+			Oid			subtype;
+			Oid			mltrngtype;
+			Oid			rngtype;
+
+			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);
+			mltrngtype = resolve_generic_type(ANYMULTIRANGEOID,
+											  anyrange_type,
+											  ANYRANGEOID);
 
 			/* check for inconsistent range and multirange results */
-			Oid			rngtype = get_multirange_subtype(mltrngtype);
+			rngtype = get_multirange_subtype(mltrngtype);
 
 			if (OidIsValid(anyrange_type) && anyrange_type != rngtype)
 				return false;
diff --git a/src/include/catalog/pg_aggregate.dat b/src/include/catalog/pg_aggregate.dat
index 242d843347..07a7211c15 100644
--- a/src/include/catalog/pg_aggregate.dat
+++ b/src/include/catalog/pg_aggregate.dat
@@ -538,6 +538,17 @@
   aggtransfn => 'bytea_string_agg_transfn',
   aggfinalfn => 'bytea_string_agg_finalfn', aggtranstype => 'internal' },
 
+# range
+{ aggfnoid => 'range_intersect_agg(anyrange)',
+  aggtransfn => 'range_intersect_agg_transfn',
+  aggcombinefn => 'range_intersect_agg_transfn', aggtranstype => 'anyrange' },
+{ aggfnoid => 'range_intersect_agg(anymultirange)',
+  aggtransfn => 'multirange_intersect_agg_transfn',
+  aggcombinefn => 'multirange_intersect_agg_transfn', aggtranstype => 'anymultirange' },
+{ aggfnoid => 'range_agg(anyrange)', aggtransfn => 'range_agg_transfn',
+  aggfinalfn => 'range_agg_finalfn', aggfinalextra => 't',
+  aggtranstype => 'internal' },
+
 # json
 { aggfnoid => 'json_agg', aggtransfn => 'json_agg_transfn',
   aggfinalfn => 'json_agg_finalfn', aggtranstype => 'internal' },
diff --git a/src/include/catalog/pg_amproc.dat b/src/include/catalog/pg_amproc.dat
index b7cb2cb000..32965d6b3d 100644
--- a/src/include/catalog/pg_amproc.dat
+++ b/src/include/catalog/pg_amproc.dat
@@ -1210,12 +1210,13 @@
   amprocrighttype => 'anyrange', amprocnum => '4',
   amproc => 'brin_inclusion_union' },
 { amprocfamily => 'brin/range_inclusion_ops', amproclefttype => 'anyrange',
-  amprocrighttype => 'anyrange', amprocnum => '11', amproc => 'range_merge' },
+  amprocrighttype => 'anyrange', amprocnum => '11',
+  amproc => 'range_merge(anyrange,anyrange)' },
 { amprocfamily => 'brin/range_inclusion_ops', amproclefttype => 'anyrange',
   amprocrighttype => 'anyrange', amprocnum => '13',
   amproc => 'range_contains' },
 { amprocfamily => 'brin/range_inclusion_ops', amproclefttype => 'anyrange',
-  amprocrighttype => 'anyrange', amprocnum => '14', amproc => 'isempty' },
+  amprocrighttype => 'anyrange', amprocnum => '14', amproc => 'isempty(anyrange)' },
 
 # minmax pg_lsn
 { amprocfamily => 'brin/pg_lsn_minmax_ops', amproclefttype => 'pg_lsn',
diff --git a/src/include/catalog/pg_operator.dat b/src/include/catalog/pg_operator.dat
index 7b7ce38076..1afdc1db7a 100644
--- a/src/include/catalog/pg_operator.dat
+++ b/src/include/catalog/pg_operator.dat
@@ -3330,5 +3330,174 @@
   oprresult => 'bool', oprcom => '<(anymultirange,anymultirange)',
   oprnegate => '<=(anymultirange,anymultirange)', oprcode => 'multirange_gt',
   oprrest => 'scalargtsel', oprjoin => 'scalargtjoinsel' },
+{ oid => '8073', oid_symbol => 'OID_RANGE_OVERLAPS_MULTIRANGE_OP',
+  descr => 'overlaps',
+  oprname => '&&', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '&&(anymultirange,anyrange)',
+  oprcode => 'range_overlaps_multirange', oprrest => 'areasel',
+  oprjoin => 'areajoinsel' },
+{ oid => '8074', oid_symbol => 'OID_MULTIRANGE_OVERLAPS_RANGE_OP', descr => 'contains',
+  oprname => '&&', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcom => '&&(anyrange,anymultirange)',
+  oprcode => 'multirange_overlaps_range', oprrest => 'areasel',
+  oprjoin => 'areajoinsel' },
+{ oid => '8075', oid_symbol => 'OID_MULTIRANGE_OVERLAPS_MULTIRANGE_OP', descr => 'contains',
+  oprname => '&&', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '&&(anymultirange,anymultirange)',
+  oprcode => 'multirange_overlaps_multirange', oprrest => 'areasel',
+  oprjoin => 'areajoinsel' },
+{ oid => '8064', oid_symbol => 'OID_MULTIRANGE_CONTAINS_ELEM_OP',
+  descr => 'contains',
+  oprname => '@>', oprleft => 'anymultirange', oprright => 'anyelement',
+  oprresult => 'bool', oprcom => '<@(anyelement,anymultirange)',
+  oprcode => 'multirange_contains_elem', oprrest => 'contsel',
+  oprjoin => 'contjoinsel' },
+{ oid => '8065', oid_symbol => 'OID_MULTIRANGE_CONTAINS_RANGE_OP', descr => 'contains',
+  oprname => '@>', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcom => '<@(anyrange,anymultirange)',
+  oprcode => 'multirange_contains_range', oprrest => 'contsel',
+  oprjoin => 'contjoinsel' },
+{ oid => '8066', oid_symbol => 'OID_MULTIRANGE_CONTAINS_MULTIRANGE_OP', descr => 'contains',
+  oprname => '@>', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '<@(anymultirange,anymultirange)',
+  oprcode => 'multirange_contains_multirange', oprrest => 'contsel',
+  oprjoin => 'contjoinsel' },
+{ oid => '8067', oid_symbol => 'OID_MULTIRANGE_ELEM_CONTAINED_OP',
+  descr => 'is contained by',
+  oprname => '<@', oprleft => 'anyelement', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '@>(anymultirange,anyelement)',
+  oprcode => 'elem_contained_by_multirange', oprrest => 'contsel',
+  oprjoin => 'contjoinsel' },
+{ oid => '8068', oid_symbol => 'OID_MULTIRANGE_RANGE_CONTAINED_OP',
+  descr => 'is contained by',
+  oprname => '<@', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '@>(anymultirange,anyrange)',
+  oprcode => 'range_contained_by_multirange', oprrest => 'contsel',
+  oprjoin => 'contjoinsel' },
+{ oid => '8069', oid_symbol => 'OID_MULTIRANGE_MULTIRANGE_CONTAINED_OP',
+  descr => 'is contained by',
+  oprname => '<@', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '@>(anymultirange,anymultirange)',
+  oprcode => 'multirange_contained_by_multirange', oprrest => 'contsel',
+  oprjoin => 'contjoinsel' },
+{ oid => '8106', oid_symbol => 'OID_RANGE_OVERLAPS_LEFT_MULTIRANGE_OP',
+  descr => 'overlaps or is left of',
+  oprname => '&<', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcode => 'range_overleft_multirange', oprrest => 'scalarltsel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8107', oid_symbol => 'OID_MULTIRANGE_OVERLAPS_LEFT_RANGE_OP',
+  descr => 'overlaps or is left of',
+  oprname => '&<', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcode => 'multirange_overleft_range', oprrest => 'scalarltsel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8108', oid_symbol => 'OID_MULTIRANGE_OVERLAPS_LEFT_MULTIRANGE_OP',
+  descr => 'overlaps or is left of',
+  oprname => '&<', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcode => 'multirange_overleft_multirange', oprrest => 'scalarltsel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8109', oid_symbol => 'OID_RANGE_OVERLAPS_RIGHT_MULTIRANGE_OP',
+  descr => 'overlaps or is right of',
+  oprname => '&>', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcode => 'range_overright_multirange', oprrest => 'scalargtsel',
+  oprjoin => 'scalargtjoinsel' },
+{ oid => '8110', oid_symbol => 'OID_MULTIRANGE_OVERLAPS_RIGHT_RANGE_OP',
+  descr => 'overlaps or is right of',
+  oprname => '&>', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcode => 'multirange_overright_range', oprrest => 'scalargtsel',
+  oprjoin => 'scalargtjoinsel' },
+{ oid => '8111', oid_symbol => 'OID_MULTIRANGE_OVERLAPS_RIGHT_MULTIRANGE_OP',
+  descr => 'overlaps or is right of',
+  oprname => '&>', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcode => 'multirange_overright_multirange', oprrest => 'scalargtsel',
+  oprjoin => 'scalargtjoinsel' },
+{ oid => '8091', oid_symbol => 'OID_RANGE_ADJACENT_MULTIRANGE_OP', descr => 'is adjacent to',
+  oprname => '-|-', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '-|-(anymultirange,anyrange)',
+  oprcode => 'range_adjacent_multirange', oprrest => 'scalarltsel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8092', oid_symbol => 'OID_MULTIRANGE_ADJACENT_RANGE_OP', descr => 'is adjacent to',
+  oprname => '-|-', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcom => '-|-(anyrange,anymultirange)',
+  oprcode => 'multirange_adjacent_range', oprrest => 'scalarltsel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8093', oid_symbol => 'OID_MULTIRANGE_ADJACENT_MULTIRANGE_OP', descr => 'is adjacent to',
+  oprname => '-|-', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '-|-(anymultirange,anymultirange)',
+  oprcode => 'multirange_adjacent_multirange', oprrest => 'scalarltsel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8138', descr => 'multirange union',
+  oprname => '@+', oprleft => 'anyrange', oprright => 'anyrange',
+  oprresult => 'anymultirange', oprcom => '@+(anyrange,anyrange)',
+  oprcode => 'range_union_range' },
+{ oid => '8115', descr => 'multirange union',
+  oprname => '@+', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'anymultirange', oprcom => '@+(anymultirange,anyrange)',
+  oprcode => 'range_union_multirange' },
+{ oid => '8116', descr => 'multirange union',
+  oprname => '@+', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'anymultirange', oprcom => '@+(anyrange,anymultirange)',
+  oprcode => 'multirange_union_range' },
+{ oid => '8117', descr => 'multirange union',
+  oprname => '@+', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'anymultirange', oprcom => '@+(anymultirange,anymultirange)',
+  oprcode => 'multirange_union_multirange' },
+{ oid => '8140', descr => 'multirange minus',
+  oprname => '@-', oprleft => 'anyrange', oprright => 'anyrange',
+  oprresult => 'anymultirange', oprcode => 'range_minus_range' },
+{ oid => '8121', descr => 'multirange minus',
+  oprname => '@-', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'anymultirange', oprcode => 'range_minus_multirange' },
+{ oid => '8122', descr => 'multirange minus',
+  oprname => '@-', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'anymultirange', oprcode => 'multirange_minus_range' },
+{ oid => '8123', descr => 'multirange minus',
+  oprname => '@-', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'anymultirange', oprcode => 'multirange_minus_multirange' },
+{ oid => '8142', descr => 'multirange intersect',
+  oprname => '@*', oprleft => 'anyrange', oprright => 'anyrange',
+  oprresult => 'anymultirange', oprcom => '@*(anyrange,anyrange)',
+  oprcode => 'range_intersect_range' },
+{ oid => '8127', descr => 'multirange intersect',
+  oprname => '@*', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'anymultirange', oprcom => '@*(anymultirange,anyrange)',
+  oprcode => 'range_intersect_multirange' },
+{ oid => '8128', descr => 'multirange intersect',
+  oprname => '@*', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'anymultirange', oprcom => '@*(anyrange,anymultirange)',
+  oprcode => 'multirange_intersect_range' },
+{ oid => '8129', descr => 'multirange intersect',
+  oprname => '@*', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'anymultirange', oprcom => '@*(anymultirange,anymultirange)',
+  oprcode => 'multirange_intersect_multirange' },
+{ oid => '8082', oid_symbol => 'OID_RANGE_BEFORE_MULTIRANGE_OP', descr => 'is left of',
+  oprname => '<<', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '>>(anymultirange,anyrange)',
+  oprcode => 'range_before_multirange', oprrest => 'scalarltsel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8083', oid_symbol => 'OID_MULTIRANGE_BEFORE_RANGE_OP', descr => 'is left of',
+  oprname => '<<', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcom => '>>(anyrange,anymultirange)',
+  oprcode => 'multirange_before_range', oprrest => 'scalarltsel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8084', oid_symbol => 'OID_MULTIRANGE_BEFORE_MULTIRANGE_OP', descr => 'is left of',
+  oprname => '<<', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '>>(anymultirange,anymultirange)',
+  oprcode => 'multirange_before_multirange', oprrest => 'scalarltsel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8085', oid_symbol => 'OID_RANGE_AFTER_MULTIRANGE_OP', descr => 'is right of',
+  oprname => '>>', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '<<(anymultirange,anyrange)',
+  oprcode => 'range_after_multirange', oprrest => 'scalargtsel',
+  oprjoin => 'scalargtjoinsel' },
+{ oid => '8086', oid_symbol => 'OID_MULTIRANGE_AFTER_RANGE_OP', descr => 'is right of',
+  oprname => '>>', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcom => '<<(anyrange,anymultirange)',
+  oprcode => 'multirange_after_range', oprrest => 'scalargtsel',
+  oprjoin => 'scalargtjoinsel' },
+{ oid => '8087', oid_symbol => 'OID_MULTIRANGE_AFTER_MULTIRANGE_OP', descr => 'is right of',
+  oprname => '>>', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '<<(anymultirange,anymultirange)',
+  oprcode => 'multirange_after_multirange', oprrest => 'scalargtsel',
+  oprjoin => 'scalargtjoinsel' },
 
 ]
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index abb956514e..4311db01ff 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -9605,6 +9605,10 @@
   descr => 'the smallest range which includes both of the given ranges',
   proname => 'range_merge', prorettype => 'anyrange',
   proargtypes => 'anyrange anyrange', prosrc => 'range_merge' },
+{ oid => '8143',
+  descr => 'the smallest range which includes the whole multirange',
+  proname => 'range_merge', prorettype => 'anyrange',
+  proargtypes => 'anymultirange', prosrc => 'range_merge_from_multirange' },
 { oid => '3868',
   proname => 'range_intersect', prorettype => 'anyrange',
   proargtypes => 'anyrange anyrange', prosrc => 'range_intersect' },
@@ -9654,6 +9658,13 @@
 { oid => '3169', descr => 'restriction selectivity for range operators',
   proname => 'rangesel', provolatile => 's', prorettype => 'float8',
   proargtypes => 'internal oid internal int4', prosrc => 'rangesel' },
+{ oid => '8133', descr => 'range aggregate by intersecting',
+  proname => 'range_intersect_agg_transfn', prorettype => 'anyrange',
+  proargtypes => 'anyrange anyrange', prosrc => 'range_intersect_agg_transfn'},
+{ oid => '8134', descr => 'range aggregate by intersecting',
+  proname => 'range_intersect_agg', prokind => 'a', proisstrict => 'f',
+  prorettype => 'anyrange', proargtypes => 'anyrange',
+  prosrc => 'aggregate_dummy'},
 
 { oid => '3914', descr => 'convert an int4 range to canonical form',
   proname => 'int4range_canonical', prorettype => 'int4range',
@@ -9741,9 +9752,165 @@
 { oid => '8007', descr => 'I/O',
   proname => 'multirange_send', provolatile => 's', prorettype => 'bytea',
   proargtypes => 'anymultirange', prosrc => 'multirange_send' },
+{ oid => '8094', descr => 'lower bound of multirange',
+  proname => 'lower', prorettype => 'anyelement', proargtypes => 'anymultirange',
+  prosrc => 'multirange_lower' },
+{ oid => '8095', descr => 'upper bound of multirange',
+  proname => 'upper', prorettype => 'anyelement', proargtypes => 'anymultirange',
+  prosrc => 'multirange_upper' },
+{ oid => '8057', descr => 'is the multirange empty?',
+  proname => 'isempty', prorettype => 'bool',
+  proargtypes => 'anymultirange', prosrc => 'multirange_empty' },
+{ oid => '8096', descr => 'is the multirange\'s lower bound inclusive?',
+  proname => 'lower_inc', prorettype => 'bool', proargtypes => 'anymultirange',
+  prosrc => 'multirange_lower_inc' },
+{ oid => '8097', descr => 'is the multirange\'s upper bound inclusive?',
+  proname => 'upper_inc', prorettype => 'bool', proargtypes => 'anymultirange',
+  prosrc => 'multirange_upper_inc' },
+{ oid => '8098', descr => 'is the multirange\'s lower bound infinite?',
+  proname => 'lower_inf', prorettype => 'bool', proargtypes => 'anymultirange',
+  prosrc => 'multirange_lower_inf' },
+{ oid => '8099', descr => 'is the multirange\'s upper bound infinite?',
+  proname => 'upper_inf', prorettype => 'bool', proargtypes => 'anymultirange',
+  prosrc => 'multirange_upper_inf' },
 { oid => '8008', descr => 'multirange typanalyze',
   proname => 'multirange_typanalyze', provolatile => 's', prorettype => 'bool',
   proargtypes => 'internal', prosrc => 'multirange_typanalyze' },
+{ oid => '8033',
+  proname => 'multirange_eq', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_eq' },
+{ oid => '8034',
+  proname => 'multirange_ne', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_ne' },
+{ oid => '8070',
+  proname => 'range_overlaps_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_overlaps_multirange' },
+{ oid => '8071',
+  proname => 'multirange_overlaps_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_overlaps_range' },
+{ oid => '8072',
+  proname => 'multirange_overlaps_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_overlaps_multirange' },
+{ oid => '8058',
+  proname => 'multirange_contains_elem', prorettype => 'bool',
+  proargtypes => 'anymultirange anyelement', prosrc => 'multirange_contains_elem' },
+{ oid => '8059',
+  proname => 'multirange_contains_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_contains_range' },
+{ oid => '8060',
+  proname => 'multirange_contains_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_contains_multirange' },
+{ oid => '8061',
+  proname => 'elem_contained_by_multirange', prorettype => 'bool',
+  proargtypes => 'anyelement anymultirange', prosrc => 'elem_contained_by_multirange' },
+{ oid => '8062',
+  proname => 'range_contained_by_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_contained_by_multirange' },
+{ oid => '8063',
+  proname => 'multirange_contained_by_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_contained_by_multirange' },
+{ oid => '8088',
+  proname => 'range_adjacent_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_adjacent_multirange' },
+{ oid => '8089',
+  proname => 'multirange_adjacent_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_adjacent_multirange' },
+{ oid => '8090',
+  proname => 'multirange_adjacent_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_adjacent_range' },
+{ oid => '8076',
+  proname => 'range_before_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_before_multirange' },
+{ oid => '8077',
+  proname => 'multirange_before_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_before_range' },
+{ oid => '8078',
+  proname => 'multirange_before_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_before_multirange' },
+{ oid => '8079',
+  proname => 'range_after_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_after_multirange' },
+{ oid => '8080',
+  proname => 'multirange_after_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_after_range' },
+{ oid => '8081',
+  proname => 'multirange_after_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_after_multirange' },
+{ oid => '8100',
+  proname => 'range_overleft_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_overleft_multirange' },
+{ oid => '8101',
+  proname => 'multirange_overleft_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_overleft_range' },
+{ oid => '8102',
+  proname => 'multirange_overleft_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_overleft_multirange' },
+{ oid => '8103',
+  proname => 'range_overright_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_overright_multirange' },
+{ oid => '8104',
+  proname => 'multirange_overright_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_overright_range' },
+{ oid => '8105',
+  proname => 'multirange_overright_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_overright_multirange' },
+{ oid => '8137',
+  proname => 'range_union_range', prorettype => 'anymultirange',
+  proargtypes => 'anyrange anyrange', prosrc => 'range_union_range' },
+{ oid => '8112',
+  proname => 'range_union_multirange', prorettype => 'anymultirange',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_union_multirange' },
+{ oid => '8113',
+  proname => 'multirange_union_range', prorettype => 'anymultirange',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_union_range' },
+{ oid => '8114',
+  proname => 'multirange_union_multirange', prorettype => 'anymultirange',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_union_multirange' },
+{ oid => '8139',
+  proname => 'range_minus_range', prorettype => 'anymultirange',
+  proargtypes => 'anyrange anyrange', prosrc => 'range_minus_range' },
+{ oid => '8118',
+  proname => 'range_minus_multirange', prorettype => 'anymultirange',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_minus_multirange' },
+{ oid => '8119',
+  proname => 'multirange_minus_range', prorettype => 'anymultirange',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_minus_range' },
+{ oid => '8120',
+  proname => 'multirange_minus_multirange', prorettype => 'anymultirange',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_minus_multirange' },
+{ oid => '8141',
+  proname => 'range_intersect_range', prorettype => 'anymultirange',
+  proargtypes => 'anyrange anyrange', prosrc => 'range_intersect_range' },
+{ oid => '8124',
+  proname => 'range_intersect_multirange', prorettype => 'anymultirange',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_intersect_multirange' },
+{ oid => '8125',
+  proname => 'multirange_intersect_range', prorettype => 'anymultirange',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_intersect_range' },
+{ oid => '8126',
+  proname => 'multirange_intersect_multirange', prorettype => 'anymultirange',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_intersect_multirange' },
+{ oid => '8022', descr => 'less-equal-greater',
+  proname => 'multirange_cmp', prorettype => 'int4',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_cmp' },
+{ oid => '8023',
+  proname => 'multirange_lt', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_lt' },
+{ oid => '8024',
+  proname => 'multirange_le', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_le' },
+{ oid => '8025',
+  proname => 'multirange_ge', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_ge' },
+{ oid => '8026',
+  proname => 'multirange_gt', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_gt' },
+{ oid => '8038', descr => 'hash a multirange',
+  proname => 'hash_multirange', prorettype => 'int4', proargtypes => 'anymultirange',
+  prosrc => 'hash_multirange' },
+{ oid => '8039', descr => 'hash a multirange',
+  proname => 'hash_multirange_extended', prorettype => 'int8',
+  proargtypes => 'anymultirange int8', prosrc => 'hash_multirange_extended' },
 
 { oid => '8045', descr => 'int4multirange constructor',
   proname => 'int4multirange', proisstrict => 'f',
@@ -9799,6 +9966,24 @@
   prorettype => 'int8multirange', proargtypes => '_int8range',
   proallargtypes => '{_int8range}', proargmodes => '{v}',
   prosrc => 'multirange_constructor1' },
+{ oid => '8130', descr => 'aggregate transition function',
+  proname => 'range_agg_transfn', proisstrict => 'f', prorettype => 'internal',
+  proargtypes => 'internal anyrange', prosrc => 'range_agg_transfn' },
+{ oid => '8131', descr => 'aggregate final function',
+  proname => 'range_agg_finalfn', proisstrict => 'f', prorettype => 'anymultirange',
+  proargtypes => 'internal anyrange', prosrc => 'range_agg_finalfn' },
+{ oid => '8132', descr => 'combine aggregate input into a multirange',
+  proname => 'range_agg', prokind => 'a', proisstrict => 'f',
+  prorettype => 'anymultirange', proargtypes => 'anyrange',
+  prosrc => 'aggregate_dummy' },
+{ oid => '8135', descr => 'range aggregate by intersecting',
+  proname => 'multirange_intersect_agg_transfn', prorettype => 'anymultirange',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_intersect_agg_transfn'},
+{ oid => '8136', descr => 'range aggregate by intersecting',
+  proname => 'range_intersect_agg', prokind => 'a', proisstrict => 'f',
+  prorettype => 'anymultirange', proargtypes => 'anymultirange',
+  prosrc => 'aggregate_dummy'},
+
 # date, time, timestamp constructors
 { oid => '3846', descr => 'construct date',
   proname => 'make_date', prorettype => 'date', proargtypes => 'int4 int4 int4',
diff --git a/src/include/utils/multirangetypes.h b/src/include/utils/multirangetypes.h
index ed2e19aafa..cc4f82b0ba 100644
--- a/src/include/utils/multirangetypes.h
+++ b/src/include/utils/multirangetypes.h
@@ -59,6 +59,40 @@ extern bool multirange_eq_internal(TypeCacheEntry *typcache, MultirangeType * mr
 								   MultirangeType * mr2);
 extern bool multirange_ne_internal(TypeCacheEntry *typcache, MultirangeType * mr1,
 								   MultirangeType * mr2);
+extern bool multirange_contains_elem_internal(TypeCacheEntry *typcache, MultirangeType * mr,
+											  Datum elem);
+extern bool multirange_contains_range_internal(TypeCacheEntry *typcache, MultirangeType * mr,
+											   RangeType *r);
+extern bool multirange_contains_multirange_internal(TypeCacheEntry *typcache,
+													MultirangeType * mr1,
+													MultirangeType * mr2);
+extern bool range_overlaps_multirange_internal(TypeCacheEntry *typcache, RangeType *r,
+											   MultirangeType * mr);
+extern bool multirange_overlaps_multirange_internal(TypeCacheEntry *typcache,
+													MultirangeType * mr1,
+													MultirangeType * mr2);
+extern bool range_before_multirange_internal(TypeCacheEntry *typcache, RangeType *r,
+											 MultirangeType * mr);
+extern bool range_after_multirange_internal(TypeCacheEntry *typcache, RangeType *r,
+											MultirangeType * mr);
+extern bool multirange_before_multirange_internal(TypeCacheEntry *typcache,
+												  MultirangeType * mr1,
+												  MultirangeType * mr2);
+extern MultirangeType * range_union_multirange_internal(TypeCacheEntry *typcache,
+														RangeType *r,
+														MultirangeType * mr);
+extern MultirangeType * multirange_minus_multirange_internal(Oid mltrngtypoid,
+															 TypeCacheEntry *rangetyp,
+															 int32 range_count1,
+															 RangeType **ranges1,
+															 int32 range_count2,
+															 RangeType **ranges2);
+extern MultirangeType * multirange_intersect_multirange_internal(Oid mltrngtypoid,
+																 TypeCacheEntry *rangetyp,
+																 int32 range_count1,
+																 RangeType **ranges1,
+																 int32 range_count2,
+																 RangeType **ranges2);
 
 /* assorted support functions */
 extern TypeCacheEntry *multirange_get_typcache(FunctionCallInfo fcinfo,
diff --git a/src/include/utils/rangetypes.h b/src/include/utils/rangetypes.h
index 521710d71f..0bee724886 100644
--- a/src/include/utils/rangetypes.h
+++ b/src/include/utils/rangetypes.h
@@ -94,13 +94,21 @@ typedef struct
  * prototypes for functions defined in rangetypes.c
  */
 
-extern bool range_contains_elem_internal(TypeCacheEntry *typcache, const RangeType *r, Datum val);
-
 /* internal versions of the above */
+extern int	range_cmp_internal(TypeCacheEntry *typcache, const RangeType *r1,
+							   const RangeType *r2);
+extern uint32 hash_range_internal(TypeCacheEntry *typcache, const RangeType *r);
+extern uint64 hash_range_extended_internal(TypeCacheEntry *typcache, const RangeType *r,
+										   Datum seed);
+extern Datum range_lower_internal(TypeCacheEntry *typcache, const RangeType *r1,
+								  bool *isnull);
+extern Datum range_upper_internal(TypeCacheEntry *typcache, const RangeType *r1,
+								  bool *isnull);
 extern bool range_eq_internal(TypeCacheEntry *typcache, const RangeType *r1,
 							  const RangeType *r2);
 extern bool range_ne_internal(TypeCacheEntry *typcache, const RangeType *r1,
 							  const RangeType *r2);
+extern bool range_contains_elem_internal(TypeCacheEntry *typcache, const RangeType *r, Datum val);
 extern bool range_contains_internal(TypeCacheEntry *typcache, const RangeType *r1,
 									const RangeType *r2);
 extern bool range_contained_by_internal(TypeCacheEntry *typcache, const RangeType *r1,
@@ -117,6 +125,12 @@ extern bool range_overleft_internal(TypeCacheEntry *typcache, const RangeType *r
 									const RangeType *r2);
 extern bool range_overright_internal(TypeCacheEntry *typcache, const RangeType *r1,
 									 const RangeType *r2);
+extern RangeType *range_union_internal(TypeCacheEntry *typcache, RangeType *r1,
+									   RangeType *r2, bool strict);
+extern RangeType *range_minus_internal(TypeCacheEntry *typcache, RangeType *r1,
+									   RangeType *r2);
+extern RangeType *range_intersect_internal(TypeCacheEntry *typcache, const RangeType *r1,
+										   const RangeType *r2);
 
 /* assorted support functions */
 extern TypeCacheEntry *range_get_typcache(FunctionCallInfo fcinfo,
@@ -127,15 +141,20 @@ extern void range_deserialize(TypeCacheEntry *typcache, const RangeType *range,
 							  RangeBound *lower, RangeBound *upper,
 							  bool *empty);
 extern char range_get_flags(const RangeType *range);
+extern bool range_has_flag(const RangeType *range, char flag);
 extern void range_set_contain_empty(RangeType *range);
 extern RangeType *make_range(TypeCacheEntry *typcache, RangeBound *lower,
 							 RangeBound *upper, bool empty);
 extern int range_cmp_bounds(TypeCacheEntry *typcache, const RangeBound *b1,
-							const RangeBound *b2);
+							 const RangeBound *b2);
 extern int range_cmp_bound_values(TypeCacheEntry *typcache, const RangeBound *b1,
-								  const RangeBound *b2);
-extern bool bounds_adjacent(TypeCacheEntry *typcache, RangeBound bound1,
-							RangeBound bound2);
+								   const RangeBound *b2);
+extern int range_compare(const void *key1, const void *key2, void *arg);
+extern bool bounds_adjacent(TypeCacheEntry *typcache, const RangeBound bound1,
+							const RangeBound bound2);
 extern RangeType *make_empty_range(TypeCacheEntry *typcache);
+extern bool range_split_internal(TypeCacheEntry *typcache, const RangeType *r1,
+								 const RangeType *r2, RangeType **output1,
+								 RangeType **output2);
 
 #endif							/* RANGETYPES_H */
diff --git a/src/test/regress/expected/dependency.out b/src/test/regress/expected/dependency.out
index b4a11b8aa9..778699a961 100644
--- a/src/test/regress/expected/dependency.out
+++ b/src/test/regress/expected/dependency.out
@@ -141,6 +141,7 @@ owner of table deptest
 owner of function deptest_func()
 owner of type deptest_enum
 owner of type deptest_range
+owner of type deptest_multirange
 owner of table deptest2
 owner of sequence ss1
 owner of type deptest_t
diff --git a/src/test/regress/expected/multirangetypes.out b/src/test/regress/expected/multirangetypes.out
index 5749537635..2276d6d002 100644
--- a/src/test/regress/expected/multirangetypes.out
+++ b/src/test/regress/expected/multirangetypes.out
@@ -290,3 +290,2400 @@ select textmultirange(textrange('a', 'c'), textrange('b', 'd'));
  {[a,d)}
 (1 row)
 
+--
+-- create some test data and test the operators
+--
+CREATE TABLE nummultirange_test (nmr NUMMULTIRANGE);
+CREATE INDEX nummultirange_test_btree ON nummultirange_test(nmr);
+INSERT INTO nummultirange_test VALUES('{}');
+INSERT INTO nummultirange_test VALUES('{[,)}');
+INSERT INTO nummultirange_test VALUES('{[3,]}');
+INSERT INTO nummultirange_test VALUES('{[,), [3,]}');
+INSERT INTO nummultirange_test VALUES('{[, 5)}');
+INSERT INTO nummultirange_test VALUES(nummultirange());
+INSERT INTO nummultirange_test VALUES(nummultirange(variadic '{}'::numrange[]));
+INSERT INTO nummultirange_test VALUES(nummultirange(numrange(1.1, 2.2)));
+INSERT INTO nummultirange_test VALUES('{empty}');
+INSERT INTO nummultirange_test VALUES(nummultirange(numrange(1.7, 1.7, '[]'), numrange(1.7, 1.9)));
+INSERT INTO nummultirange_test VALUES(nummultirange(numrange(1.7, 1.7, '[]'), numrange(1.9, 2.1)));
+SELECT nmr, isempty(nmr), lower(nmr), upper(nmr) FROM nummultirange_test ORDER BY nmr;
+          nmr          | isempty | lower | upper 
+-----------------------+---------+-------+-------
+ {}                    | t       |       |      
+ {}                    | t       |       |      
+ {}                    | t       |       |      
+ {}                    | t       |       |      
+ {(,5)}                | f       |       |     5
+ {(,)}                 | f       |       |      
+ {(,)}                 | f       |       |      
+ {[1.1,2.2)}           | f       |   1.1 |   2.2
+ {[1.7,1.7],[1.9,2.1)} | f       |   1.7 |   2.1
+ {[1.7,1.9)}           | f       |   1.7 |   1.9
+ {[3,)}                | f       |     3 |      
+(11 rows)
+
+SELECT nmr, lower_inc(nmr), lower_inf(nmr), upper_inc(nmr), upper_inf(nmr) FROM nummultirange_test ORDER BY nmr;
+          nmr          | lower_inc | lower_inf | upper_inc | upper_inf 
+-----------------------+-----------+-----------+-----------+-----------
+ {}                    | f         | f         | f         | f
+ {}                    | f         | f         | f         | f
+ {}                    | f         | f         | f         | f
+ {}                    | f         | f         | f         | f
+ {(,5)}                | f         | t         | f         | f
+ {(,)}                 | f         | t         | f         | t
+ {(,)}                 | f         | t         | f         | t
+ {[1.1,2.2)}           | t         | f         | f         | f
+ {[1.7,1.7],[1.9,2.1)} | t         | f         | f         | f
+ {[1.7,1.9)}           | t         | f         | f         | f
+ {[3,)}                | t         | f         | f         | t
+(11 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr = '{}';
+ nmr 
+-----
+ {}
+ {}
+ {}
+ {}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr = '{(,5)}';
+  nmr   
+--------
+ {(,5)}
+(1 row)
+
+SELECT * FROM nummultirange_test WHERE nmr = '{[3,)}';
+  nmr   
+--------
+ {[3,)}
+(1 row)
+
+SELECT * FROM nummultirange_test WHERE nmr = '{[1.7,1.7]}';
+ nmr 
+-----
+(0 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr = '{[1.7,1.7],[1.9,2.1)}';
+          nmr          
+-----------------------
+ {[1.7,1.7],[1.9,2.1)}
+(1 row)
+
+SELECT * FROM nummultirange_test WHERE nmr < '{}';
+ nmr 
+-----
+(0 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr < '{[-1000.0, -1000.0]}';
+  nmr   
+--------
+ {}
+ {(,)}
+ {(,)}
+ {(,5)}
+ {}
+ {}
+ {}
+(7 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr < '{[0.0, 1.0]}';
+  nmr   
+--------
+ {}
+ {(,)}
+ {(,)}
+ {(,5)}
+ {}
+ {}
+ {}
+(7 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr < '{[1000.0, 1001.0]}';
+          nmr          
+-----------------------
+ {}
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+ {}
+ {}
+ {[1.1,2.2)}
+ {}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(11 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr <= '{}';
+ nmr 
+-----
+ {}
+ {}
+ {}
+ {}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr <= '{[3,)}';
+          nmr          
+-----------------------
+ {}
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+ {}
+ {}
+ {[1.1,2.2)}
+ {}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(11 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr >= '{}';
+          nmr          
+-----------------------
+ {}
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+ {}
+ {}
+ {[1.1,2.2)}
+ {}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(11 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr >= '{[3,)}';
+  nmr   
+--------
+ {[3,)}
+(1 row)
+
+SELECT * FROM nummultirange_test WHERE nmr > '{}';
+          nmr          
+-----------------------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+ {[1.1,2.2)}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(7 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr > '{[-1000.0, -1000.0]}';
+          nmr          
+-----------------------
+ {[3,)}
+ {[1.1,2.2)}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr > '{[0.0, 1.0]}';
+          nmr          
+-----------------------
+ {[3,)}
+ {[1.1,2.2)}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr > '{[1000.0, 1001.0]}';
+ nmr 
+-----
+(0 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr <> '{}';
+          nmr          
+-----------------------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+ {[1.1,2.2)}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(7 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr <> '{(,5)}';
+          nmr          
+-----------------------
+ {}
+ {(,)}
+ {[3,)}
+ {(,)}
+ {}
+ {}
+ {[1.1,2.2)}
+ {}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(10 rows)
+
+select nummultirange(numrange(2.0, 1.0));
+ERROR:  range lower bound must be less than or equal to range upper bound
+select nummultirange(numrange(5.0, 6.0), numrange(1.0, 2.0));
+     nummultirange     
+-----------------------
+ {[1.0,2.0),[5.0,6.0)}
+(1 row)
+
+-- overlaps
+SELECT * FROM nummultirange_test WHERE range_overlaps_multirange(numrange(4.0, 4.2), nmr);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE numrange(4.0, 4.2) && nmr;
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE multirange_overlaps_range(nmr, numrange(4.0, 4.2));
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr && numrange(4.0, 4.2);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE multirange_overlaps_multirange(nmr, nummultirange(numrange(4.0, 4.2), numrange(6.0, 7.0)));
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr && nummultirange(numrange(4.0, 4.2), numrange(6.0, 7.0));
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr && nummultirange(numrange(6.0, 7.0));
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+(3 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr && nummultirange(numrange(6.0, 7.0), numrange(8.0, 9.0));
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+(3 rows)
+
+-- mr contains x
+SELECT * FROM nummultirange_test WHERE multirange_contains_elem(nmr, 4.0);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr @> 4.0;
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE multirange_contains_range(nmr, numrange(4.0, 4.2));
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr @> numrange(4.0, 4.2);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE multirange_contains_multirange(nmr, '{[4.0,4.2), [6.0, 8.0)}');
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+(3 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr @> '{[4.0,4.2), [6.0, 8.0)}'::nummultirange;
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+(3 rows)
+
+-- x is contained by mr
+SELECT * FROM nummultirange_test WHERE elem_contained_by_multirange(4.0, nmr);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE 4.0 <@ nmr;
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE range_contained_by_multirange(numrange(4.0, 4.2), nmr);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE numrange(4.0, 4.2) <@ nmr;
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE multirange_contained_by_multirange('{[4.0,4.2), [6.0, 8.0)}', nmr);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+(3 rows)
+
+SELECT * FROM nummultirange_test WHERE '{[4.0,4.2), [6.0, 8.0)}'::nummultirange <@ nmr;
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+(3 rows)
+
+-- overlaps
+SELECT 'empty'::numrange && nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT 'empty'::numrange && nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() && 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) && 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() && nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() && nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) && nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) && nummultirange(numrange(1,2), numrange(7,8));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(7,8)) && nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) && nummultirange(numrange(1,2), numrange(3.5,8));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(3.5,8)) && numrange(3,4);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(3.5,8)) && nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+-- contains
+SELECT nummultirange() @> nummultirange();
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange() @> 'empty'::numrange;
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(null,null)) @> numrange(1,2);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(null,null)) @> numrange(null,2);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(null,null)) @> numrange(2,null);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(null,5)) @> numrange(null,3);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(null,5)) @> numrange(null,8);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(5,null)) @> numrange(8,null);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(5,null)) @> numrange(3,null);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,5)) @> numrange(8,9);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,5)) @> numrange(3,9);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,5)) @> numrange(1,4);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,5)) @> numrange(1,5);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(-4,-2), numrange(1,5)) @> numrange(1,5);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,5), numrange(8,9)) @> numrange(1,5);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,5), numrange(8,9)) @> numrange(6,7);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,5), numrange(6,9)) @> numrange(6,7);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[1,5)}'::nummultirange @> '{[1,5)}';
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[-4,-2), [1,5)}'::nummultirange @> '{[1,5)}';
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[1,5), [8,9)}'::nummultirange @> '{[1,5)}';
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[1,5), [8,9)}'::nummultirange @> '{[6,7)}';
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT '{[1,5), [6,9)}'::nummultirange @> '{[6,7)}';
+ ?column? 
+----------
+ t
+(1 row)
+
+-- is contained by
+SELECT nummultirange() <@ nummultirange();
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT 'empty'::numrange <@ nummultirange();
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,2) <@ nummultirange(numrange(null,null));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(null,2) <@ nummultirange(numrange(null,null));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(2,null) <@ nummultirange(numrange(null,null));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(null,3) <@ nummultirange(numrange(null,5));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(null,8) <@ nummultirange(numrange(null,5));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(8,null) <@ nummultirange(numrange(5,null));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(3,null) <@ nummultirange(numrange(5,null));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(8,9) <@ nummultirange(numrange(1,5));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(3,9) <@ nummultirange(numrange(1,5));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(1,4) <@ nummultirange(numrange(1,5));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,5) <@ nummultirange(numrange(1,5));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,5) <@ nummultirange(numrange(-4,-2), numrange(1,5));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,5) <@ nummultirange(numrange(1,5), numrange(8,9));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(6,7) <@ nummultirange(numrange(1,5), numrange(8,9));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(6,7) <@ nummultirange(numrange(1,5), numrange(6,9));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[1,5)}' <@ '{[1,5)}'::nummultirange;
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[1,5)}' <@ '{[-4,-2), [1,5)}'::nummultirange;
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[1,5)}' <@ '{[1,5), [8,9)}'::nummultirange;
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[6,7)}' <@ '{[1,5), [8,9)}'::nummultirange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT '{[6,7)}' <@ '{[1,5), [6,9)}'::nummultirange;
+ ?column? 
+----------
+ t
+(1 row)
+
+-- overleft
+SELECT 'empty'::numrange &< nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT 'empty'::numrange &< nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() &< 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) &< 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() &< nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) &< nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() &< nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(6,7) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(1,2) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,4) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,6) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(3.5,6) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(6,7)) &< numrange(3,4);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) &< numrange(3,4);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,4)) &< numrange(3,4);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,6)) &< numrange(3,4);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3.5,6)) &< numrange(3,4);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(6,7)) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,4)) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,6)) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3.5,6)) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+-- overright
+SELECT nummultirange() &> 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) &> 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT 'empty'::numrange &> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT 'empty'::numrange &> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() &> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() &> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) &> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> numrange(6,7);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> numrange(1,2);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> numrange(1,4);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> numrange(1,6);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> numrange(3.5,6);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(3,4) &> nummultirange(numrange(6,7));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(3,4) &> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(3,4) &> nummultirange(numrange(1,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(3,4) &> nummultirange(numrange(1,6));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(3,4) &> nummultirange(numrange(3.5,6));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(6,7));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(1,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(1,6));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(3.5,6));
+ ?column? 
+----------
+ f
+(1 row)
+
+-- meets
+SELECT 'empty'::numrange -|- nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT 'empty'::numrange -|- nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() -|- 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() -|- nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() -|- nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(1,2) -|- nummultirange(numrange(2,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,2) -|- nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- numrange(2,4);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- numrange(3,4);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- nummultirange(numrange(2,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(5,6)) -|- nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(5,6)) -|- nummultirange(numrange(6,7));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(5,6)) -|- nummultirange(numrange(8,9));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- nummultirange(numrange(2,4), numrange(6,7));
+ ?column? 
+----------
+ t
+(1 row)
+
+-- strictly left
+select 'empty'::numrange << nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(1,2) << nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(1,2) << nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+select numrange(1,2) << nummultirange(numrange(0,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(1,2) << nummultirange(numrange(0,4), numrange(7,8));
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange() << 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange() << numrange(1,2);
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(3,4)) << numrange(3,6);
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(0,2)) << numrange(3,6);
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange(numrange(0,2), numrange(7,8)) << numrange(3,6);
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(-4,-2), numrange(0,2)) << numrange(3,6);
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange() << nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange() << nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(1,2)) << nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(1,2)) << nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(1,2)) << nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange(numrange(1,2)) << nummultirange(numrange(3,4), numrange(7,8));
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange(numrange(1,2), numrange(4,5)) << nummultirange(numrange(3,4), numrange(7,8));
+ ?column? 
+----------
+ f
+(1 row)
+
+-- strictly right
+select nummultirange() >> 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange() >> numrange(1,2);
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(3,4)) >> numrange(1,2);
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange(numrange(0,4)) >> numrange(1,2);
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(0,4), numrange(7,8)) >> numrange(1,2);
+ ?column? 
+----------
+ f
+(1 row)
+
+select 'empty'::numrange >> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(1,2) >> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(3,6) >> nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(3,6) >> nummultirange(numrange(0,2));
+ ?column? 
+----------
+ t
+(1 row)
+
+select numrange(3,6) >> nummultirange(numrange(0,2), numrange(7,8));
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(3,6) >> nummultirange(numrange(-4,-2), numrange(0,2));
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange() >> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(1,2)) >> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange() >> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(1,2)) >> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(3,4)) >> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange(numrange(3,4), numrange(7,8)) >> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange(numrange(3,4), numrange(7,8)) >> nummultirange(numrange(1,2), numrange(4,5));
+ ?column? 
+----------
+ f
+(1 row)
+
+-- union
+SELECT 'empty'::numrange @+ 'empty'::numrange;
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT 'empty'::numrange @+ nummultirange();
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange() @+ 'empty'::numrange;
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange() @+ nummultirange();
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT 'empty'::numrange @+ numrange(1,2);
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT 'empty'::numrange @+ nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT numrange(1,2) @+ nummultirange();
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) @+ 'empty'::numrange;
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT numrange(1,2) @+ 'empty'::numrange;
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange() @+ numrange(1,2);
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange() @+ nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) @+ nummultirange();
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT numrange(1,2) @+ numrange(1,2);
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT numrange(1,2) @+ numrange(2,4);
+ ?column? 
+----------
+ {[1,4)}
+(1 row)
+
+SELECT numrange(1,2) @+ numrange(3,4);
+   ?column?    
+---------------
+ {[1,2),[3,4)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) @+ nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) @+ nummultirange(numrange(2,4));
+ ?column? 
+----------
+ {[1,4)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) @+ nummultirange(numrange(3,4));
+   ?column?    
+---------------
+ {[1,2),[3,4)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) @+ nummultirange(numrange(2,4));
+ ?column? 
+----------
+ {[1,5)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) @+ nummultirange(numrange(3,4));
+   ?column?    
+---------------
+ {[1,2),[3,5)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) @+ nummultirange(numrange(0,9));
+ ?column? 
+----------
+ {[0,9)}
+(1 row)
+
+-- merge
+SELECT range_merge(nummultirange());
+ range_merge 
+-------------
+ empty
+(1 row)
+
+SELECT range_merge(nummultirange(numrange(1,2)));
+ range_merge 
+-------------
+ [1,2)
+(1 row)
+
+SELECT range_merge(nummultirange(numrange(1,2), numrange(7,8)));
+ range_merge 
+-------------
+ [1,8)
+(1 row)
+
+-- minus
+SELECT 'empty'::numrange @- 'empty'::numrange;
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT 'empty'::numrange @- nummultirange();
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange() @- 'empty'::numrange;
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange() @- nummultirange();
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT 'empty'::numrange @- numrange(1,2);
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT 'empty'::numrange @- nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT numrange(1,2) @- nummultirange();
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) @- 'empty'::numrange;
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT 'empty'::numrange @- numrange(1,2);
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT numrange(1,2) @- 'empty'::numrange;
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange() @- numrange(1,2);
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange() @- nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) @- nummultirange();
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT numrange(1,3) @- numrange(1,3);
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT numrange(1,3) @- numrange(1,2);
+ ?column? 
+----------
+ {[2,3)}
+(1 row)
+
+SELECT numrange(1,3) @- numrange(2,4);
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT numrange(1,3) @- numrange(3,4);
+ ?column? 
+----------
+ {[1,3)}
+(1 row)
+
+SELECT numrange(1,3) @- nummultirange(numrange(1,3));
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT numrange(1,3) @- nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {[2,3)}
+(1 row)
+
+SELECT numrange(1,3) @- nummultirange(numrange(2,4));
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT numrange(1,3) @- nummultirange(numrange(3,4));
+ ?column? 
+----------
+ {[1,3)}
+(1 row)
+
+SELECT numrange(1,10) @- nummultirange(numrange(1,2), numrange(4,5));
+    ?column?    
+----------------
+ {[2,4),[5,10)}
+(1 row)
+
+SELECT nummultirange(numrange(1,10)) @- numrange(0,2);
+ ?column? 
+----------
+ {[2,10)}
+(1 row)
+
+SELECT nummultirange(numrange(1,10)) @- numrange(1,2);
+ ?column? 
+----------
+ {[2,10)}
+(1 row)
+
+SELECT nummultirange(numrange(1,10)) @- numrange(3,4);
+    ?column?    
+----------------
+ {[1,3),[4,10)}
+(1 row)
+
+SELECT nummultirange(numrange(1,10)) @- numrange(13,18);
+ ?column? 
+----------
+ {[1,10)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(3,4)) @- nummultirange();
+   ?column?    
+---------------
+ {[1,2),[3,4)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) @- nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) @- nummultirange(numrange(2,4));
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) @- nummultirange(numrange(3,4));
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,4)) @- nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {[2,4)}
+(1 row)
+
+SELECT nummultirange(numrange(1,4)) @- nummultirange(numrange(2,3));
+   ?column?    
+---------------
+ {[1,2),[3,4)}
+(1 row)
+
+SELECT nummultirange(numrange(1,4)) @- nummultirange(numrange(0,8));
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange(numrange(1,4)) @- nummultirange(numrange(0,2));
+ ?column? 
+----------
+ {[2,4)}
+(1 row)
+
+SELECT nummultirange(numrange(1,8)) @- nummultirange(numrange(0,2), numrange(3,4));
+   ?column?    
+---------------
+ {[2,3),[4,8)}
+(1 row)
+
+SELECT nummultirange(numrange(1,8)) @- nummultirange(numrange(2,3), numrange(5,null));
+   ?column?    
+---------------
+ {[1,2),[3,5)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) @- nummultirange(numrange(-2,0));
+   ?column?    
+---------------
+ {[1,2),[4,5)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) @- nummultirange(numrange(2,4));
+   ?column?    
+---------------
+ {[1,2),[4,5)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) @- nummultirange(numrange(3,5));
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) @- nummultirange(numrange(0,9));
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange(numrange(1,3), numrange(4,5)) @- nummultirange(numrange(2,9));
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) @- nummultirange(numrange(8,9));
+   ?column?    
+---------------
+ {[1,2),[4,5)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) @- nummultirange(numrange(-2,0), numrange(8,9));
+   ?column?    
+---------------
+ {[1,2),[4,5)}
+(1 row)
+
+-- intersection
+SELECT 'empty'::numrange @* 'empty'::numrange;
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT 'empty'::numrange @* nummultirange();
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange() @* 'empty'::numrange;
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange() @* nummultirange();
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT 'empty'::numrange @* numrange(1,2);
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT 'empty'::numrange @* nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT numrange(1,2) @* 'empty'::numrange;
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT numrange(1,2) @* nummultirange();
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) @* 'empty'::numrange;
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange() @* numrange(1,2);
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange() @* nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) @* nummultirange();
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT numrange(1,3) @* numrange(1,3);
+ ?column? 
+----------
+ {[1,3)}
+(1 row)
+
+SELECT numrange(1,3) @* numrange(1,2);
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT numrange(1,3) @* numrange(1,5);
+ ?column? 
+----------
+ {[1,3)}
+(1 row)
+
+SELECT numrange(1,3) @* numrange(2,5);
+ ?column? 
+----------
+ {[2,3)}
+(1 row)
+
+SELECT numrange(1,5) @* numrange(2,3);
+ ?column? 
+----------
+ {[2,3)}
+(1 row)
+
+SELECT '{[1,3)}'::nummultirange @* numrange(1,3);
+ ?column? 
+----------
+ {[1,3)}
+(1 row)
+
+SELECT '{[1,3)}'::nummultirange @* numrange(1,2);
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT '{[1,3)}'::nummultirange @* numrange(1,5);
+ ?column? 
+----------
+ {[1,3)}
+(1 row)
+
+SELECT '{[1,3)}'::nummultirange @* numrange(2,5);
+ ?column? 
+----------
+ {[2,3)}
+(1 row)
+
+SELECT '{[1,5)}'::nummultirange @* numrange(2,3);
+ ?column? 
+----------
+ {[2,3)}
+(1 row)
+
+SELECT numrange(1,3) @* '{[1,3)}'::nummultirange;
+ ?column? 
+----------
+ {[1,3)}
+(1 row)
+
+SELECT numrange(1,2) @* '{[1,3)}'::nummultirange;
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT numrange(1,5) @* '{[1,3)}'::nummultirange;
+ ?column? 
+----------
+ {[1,3)}
+(1 row)
+
+SELECT numrange(2,5) @* '{[1,3)}'::nummultirange;
+ ?column? 
+----------
+ {[2,3)}
+(1 row)
+
+SELECT numrange(2,3) @* '{[1,5)}'::nummultirange;
+ ?column? 
+----------
+ {[2,3)}
+(1 row)
+
+SELECT '{[1,3)}'::nummultirange @* '{[1,5)}'::nummultirange;
+ ?column? 
+----------
+ {[1,3)}
+(1 row)
+
+SELECT '{[1,3)}'::nummultirange @* '{[0,5)}'::nummultirange;
+ ?column? 
+----------
+ {[1,3)}
+(1 row)
+
+SELECT '{[1,3)}'::nummultirange @* '{[0,2)}'::nummultirange;
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT '{[1,3)}'::nummultirange @* '{[2,5)}'::nummultirange;
+ ?column? 
+----------
+ {[2,3)}
+(1 row)
+
+SELECT '{[1,4)}'::nummultirange @* '{[2,3)}'::nummultirange;
+ ?column? 
+----------
+ {[2,3)}
+(1 row)
+
+SELECT '{[1,4)}'::nummultirange @* '{[0,2), [3,5)}'::nummultirange;
+   ?column?    
+---------------
+ {[1,2),[3,4)}
+(1 row)
+
+SELECT '{[1,4), [7,10)}'::nummultirange @* '{[0,8), [9,12)}'::nummultirange;
+       ?column?       
+----------------------
+ {[1,4),[7,8),[9,10)}
+(1 row)
+
+SELECT '{[1,4), [7,10)}'::nummultirange @* '{[9,12)}'::nummultirange;
+ ?column? 
+----------
+ {[9,10)}
+(1 row)
+
+SELECT '{[1,4), [7,10)}'::nummultirange @* '{[-5,-4), [5,6), [9,12)}'::nummultirange;
+ ?column? 
+----------
+ {[9,10)}
+(1 row)
+
+SELECT '{[1,4), [7,10)}'::nummultirange @* '{[0,2), [3,8), [9,12)}'::nummultirange;
+          ?column?          
+----------------------------
+ {[1,2),[3,4),[7,8),[9,10)}
+(1 row)
+
+SELECT '{[1,4), [7,10)}'::nummultirange @* '{[0,2), [3,8), [9,12)}'::nummultirange;
+          ?column?          
+----------------------------
+ {[1,2),[3,4),[7,8),[9,10)}
+(1 row)
+
+--
+-- range_agg function
+--
+create table reservations ( room_id integer not null, booked_during daterange );
+insert into reservations values
+-- 1: has a meets and a gap
+(1, daterange('2018-07-01', '2018-07-07')),
+(1, daterange('2018-07-07', '2018-07-14')),
+(1, daterange('2018-07-20', '2018-07-22')),
+-- 2: just a single row
+(2, daterange('2018-07-01', '2018-07-03')),
+-- 3: one null range
+(3, NULL),
+-- 4: two null ranges
+(4, NULL),
+(4, NULL),
+-- 5: a null range and a non-null range
+(5, NULL),
+(5, daterange('2018-07-01', '2018-07-03')),
+-- 6: has overlap
+(6, daterange('2018-07-01', '2018-07-07')),
+(6, daterange('2018-07-05', '2018-07-10')),
+-- 7: two ranges that meet: no gap or overlap
+(7, daterange('2018-07-01', '2018-07-07')),
+(7, daterange('2018-07-07', '2018-07-14')),
+-- 8: an empty range
+(8, 'empty'::daterange)
+;
+SELECT   room_id, range_agg(booked_during)
+FROM     reservations
+GROUP BY room_id
+ORDER BY room_id;
+ room_id |                     range_agg                     
+---------+---------------------------------------------------
+       1 | {[07-01-2018,07-14-2018),[07-20-2018,07-22-2018)}
+       2 | {[07-01-2018,07-03-2018)}
+       3 | 
+       4 | 
+       5 | {[07-01-2018,07-03-2018)}
+       6 | {[07-01-2018,07-10-2018)}
+       7 | {[07-01-2018,07-14-2018)}
+       8 | {}
+(8 rows)
+
+-- range_agg on a custom range type too
+SELECT  range_agg(r)
+FROM    (VALUES
+          ('[a,c]'::textrange),
+          ('[b,b]'::textrange),
+          ('[c,f]'::textrange),
+          ('[g,h)'::textrange),
+          ('[h,j)'::textrange)
+        ) t(r);
+   range_agg   
+---------------
+ {[a,f],[g,j)}
+(1 row)
+
+select range_intersect_agg(nmr) from nummultirange_test;
+ range_intersect_agg 
+---------------------
+ {}
+(1 row)
+
+select range_intersect_agg(nmr) from nummultirange_test where false;
+ range_intersect_agg 
+---------------------
+ 
+(1 row)
+
+-- test with just one input:
+select range_intersect_agg(nmr) from (values ('{[1,2]}'::nummultirange)) t(nmr);
+ range_intersect_agg 
+---------------------
+ {[1,2]}
+(1 row)
+
+select range_intersect_agg(nmr) from nummultirange_test where nmr @> 4.0;
+ range_intersect_agg 
+---------------------
+ {[3,5)}
+(1 row)
+
+create table nummultirange_test2(nmr nummultirange);
+create index nummultirange_test2_hash_idx on nummultirange_test2 using hash (nmr);
+INSERT INTO nummultirange_test2 VALUES('{[, 5)}');
+INSERT INTO nummultirange_test2 VALUES(nummultirange(numrange(1.1, 2.2)));
+INSERT INTO nummultirange_test2 VALUES(nummultirange(numrange(1.1, 2.2)));
+INSERT INTO nummultirange_test2 VALUES(nummultirange(numrange(1.1, 2.2,'()')));
+INSERT INTO nummultirange_test2 VALUES('{}');
+select * from nummultirange_test2 where nmr = '{}';
+ nmr 
+-----
+ {}
+(1 row)
+
+select * from nummultirange_test2 where nmr = nummultirange(numrange(1.1, 2.2));
+     nmr     
+-------------
+ {[1.1,2.2)}
+ {[1.1,2.2)}
+(2 rows)
+
+select * from nummultirange_test2 where nmr = nummultirange(numrange(1.1, 2.3));
+ nmr 
+-----
+(0 rows)
+
+set enable_nestloop=t;
+set enable_hashjoin=f;
+set enable_mergejoin=f;
+select * from nummultirange_test natural join nummultirange_test2 order by nmr;
+     nmr     
+-------------
+ {}
+ {}
+ {}
+ {}
+ {(,5)}
+ {[1.1,2.2)}
+ {[1.1,2.2)}
+(7 rows)
+
+set enable_nestloop=f;
+set enable_hashjoin=t;
+set enable_mergejoin=f;
+select * from nummultirange_test natural join nummultirange_test2 order by nmr;
+     nmr     
+-------------
+ {}
+ {}
+ {}
+ {}
+ {(,5)}
+ {[1.1,2.2)}
+ {[1.1,2.2)}
+(7 rows)
+
+set enable_nestloop=f;
+set enable_hashjoin=f;
+set enable_mergejoin=t;
+select * from nummultirange_test natural join nummultirange_test2 order by nmr;
+     nmr     
+-------------
+ {}
+ {}
+ {}
+ {}
+ {(,5)}
+ {[1.1,2.2)}
+ {[1.1,2.2)}
+(7 rows)
+
+set enable_nestloop to default;
+set enable_hashjoin to default;
+set enable_mergejoin to default;
+DROP TABLE nummultirange_test2;
+--
+-- Test user-defined multirange of floats
+--
+select '{[123.001, 5.e9)}'::float8multirange @> 888.882::float8;
+ ?column? 
+----------
+ t
+(1 row)
+
+create table float8multirange_test(f8mr float8multirange, i int);
+insert into float8multirange_test values(float8multirange(float8range(-100.00007, '1.111113e9')), 42);
+select * from float8multirange_test;
+           f8mr            | i  
+---------------------------+----
+ {[-100.00007,1111113000)} | 42
+(1 row)
+
+drop table float8multirange_test;
+--
+-- Test multirange types over domains
+--
+create domain mydomain as int4;
+create type mydomainrange as range(subtype=mydomain);
+select '{[4,50)}'::mydomainmultirange @> 7::mydomain;
+ ?column? 
+----------
+ t
+(1 row)
+
+drop domain mydomain cascade;
+NOTICE:  drop cascades to type mydomainrange
+--
+-- Test domains over multirange types
+--
+create domain restrictedmultirange as int4multirange check (upper(value) < 10);
+select '{[4,5)}'::restrictedmultirange @> 7;
+ ?column? 
+----------
+ f
+(1 row)
+
+select '{[4,50)}'::restrictedmultirange @> 7; -- should fail
+ERROR:  value for domain restrictedmultirange violates check constraint "restrictedmultirange_check"
+drop domain restrictedmultirange;
+--
+-- Test multiple multirange types over the same subtype
+--
+create type textrange1 as range(subtype=text, collation="C");
+create type textrange2 as range(subtype=text, collation="C");
+select textmultirange1(textrange2('a','Z'));  -- should fail
+ERROR:  function textmultirange1(textrange2) does not exist
+LINE 1: select textmultirange1(textrange2('a','Z'));
+               ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+select textmultirange1(textrange1('a','Z')) @> 'b'::text;
+ERROR:  range lower bound must be less than or equal to range upper bound
+select textmultirange2(textrange2('a','z')) @> 'b'::text;
+ ?column? 
+----------
+ t
+(1 row)
+
+drop type textrange1;
+drop type textrange2;
+--
+-- Test polymorphic type system
+--
+create function anyarray_anymultirange_func(a anyarray, r anymultirange)
+  returns anyelement as 'select $1[1] + lower($2);' language sql;
+select anyarray_anymultirange_func(ARRAY[1,2], int4multirange(int4range(10,20)));
+ anyarray_anymultirange_func 
+-----------------------------
+                          11
+(1 row)
+
+-- should fail
+select anyarray_anymultirange_func(ARRAY[1,2], nummultirange(numrange(10,20)));
+ERROR:  function anyarray_anymultirange_func(integer[], nummultirange) does not exist
+LINE 1: select anyarray_anymultirange_func(ARRAY[1,2], nummultirange...
+               ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+drop function anyarray_anymultirange_func(anyarray, anymultirange);
+-- should fail
+create function bogus_func(anyelement)
+  returns anymultirange as 'select int4multirange(int4range(1,10))' language sql;
+ERROR:  cannot determine result data type
+DETAIL:  A function returning "anyrange" or "anymultirange" must have at least one "anyrange" or "anymultirange" argument.
+-- should fail
+create function bogus_func(int)
+  returns anymultirange as 'select int4multirange(int4range(1,10))' language sql;
+ERROR:  cannot determine result data type
+DETAIL:  A function returning a polymorphic type must have at least one polymorphic argument.
+create function range_add_bounds(anymultirange)
+  returns anyelement as 'select lower($1) + upper($1)' language sql;
+select range_add_bounds(int4multirange(int4range(1, 17)));
+ range_add_bounds 
+------------------
+               18
+(1 row)
+
+select range_add_bounds(nummultirange(numrange(1.0001, 123.123)));
+ range_add_bounds 
+------------------
+         124.1231
+(1 row)
+
+create function multirangetypes_sql(q anymultirange, b anyarray, out c anyelement)
+  as $$ select upper($1) + $2[1] $$
+  language sql;
+select multirangetypes_sql(int4multirange(int4range(1,10)), ARRAY[2,20]);
+ multirangetypes_sql 
+---------------------
+                  12
+(1 row)
+
+select multirangetypes_sql(nummultirange(numrange(1,10)), ARRAY[2,20]);  -- match failure
+ERROR:  function multirangetypes_sql(nummultirange, integer[]) does not exist
+LINE 1: select multirangetypes_sql(nummultirange(numrange(1,10)), AR...
+               ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+--
+-- Arrays of multiranges
+--
+select ARRAY[nummultirange(numrange(1.1, 1.2)), nummultirange(numrange(12.3, 155.5))];
+              array               
+----------------------------------
+ {"{[1.1,1.2)}","{[12.3,155.5)}"}
+(1 row)
+
+create table i8mr_array (f1 int, f2 int8multirange[]);
+insert into i8mr_array values (42, array[int8multirange(int8range(1,10)), int8multirange(int8range(2,20))]);
+select * from i8mr_array;
+ f1 |           f2            
+----+-------------------------
+ 42 | {"{[1,10)}","{[2,20)}"}
+(1 row)
+
+drop table i8mr_array;
+--
+-- Multiranges of arrays
+--
+select arraymultirange(arrayrange(ARRAY[1,2], ARRAY[2,1]));
+   arraymultirange   
+---------------------
+ {["{1,2}","{2,1}")}
+(1 row)
+
+select arraymultirange(arrayrange(ARRAY[2,1], ARRAY[1,2]));  -- fail
+ERROR:  range lower bound must be less than or equal to range upper bound
+select array[1,1] <@ arraymultirange(arrayrange(array[1,2], array[2,1]));
+ ?column? 
+----------
+ f
+(1 row)
+
+select array[1,3] <@ arraymultirange(arrayrange(array[1,2], array[2,1]));
+ ?column? 
+----------
+ t
+(1 row)
+
+--
+-- Ranges of composites
+--
+create type two_ints as (a int, b int);
+create type two_ints_range as range (subtype = two_ints);
+-- with force_parallel_mode on, this exercises tqueue.c's range remapping
+select *, row_to_json(upper(t)) as u from
+  (values (two_ints_multirange(two_ints_range(row(1,2), row(3,4)))),
+          (two_ints_multirange(two_ints_range(row(5,6), row(7,8))))) v(t);
+          t          |       u       
+---------------------+---------------
+ {["(1,2)","(3,4)")} | {"a":3,"b":4}
+ {["(5,6)","(7,8)")} | {"a":7,"b":8}
+(2 rows)
+
+drop type two_ints cascade;
+NOTICE:  drop cascades to type two_ints_range
+--
+-- Check behavior when subtype lacks a hash function
+--
+set enable_sort = off;  -- try to make it pick a hash setop implementation
+select '{(2,5)}'::cashmultirange except select '{(5,6)}'::cashmultirange;
+ cashmultirange  
+-----------------
+ {($2.00,$5.00)}
+(1 row)
+
+reset enable_sort;
+--
+-- OUT/INOUT/TABLE functions
+--
+-- infer anymultirange from anymultirange
+create function mr_outparam_succeed(i anymultirange, out r anymultirange, out t text)
+  as $$ select $1, 'foo'::text $$ language sql;
+select * from mr_outparam_succeed(int4multirange(int4range(1,2)));
+    r    |  t  
+---------+-----
+ {[1,2)} | foo
+(1 row)
+
+-- infer anyarray from anymultirange
+create function mr_outparam_succeed2(i anymultirange, out r anyarray, out t text)
+  as $$ select ARRAY[upper($1)], 'foo'::text $$ language sql;
+select * from mr_outparam_succeed2(int4multirange(int4range(1,2)));
+  r  |  t  
+-----+-----
+ {2} | foo
+(1 row)
+
+-- infer anyrange from anymultirange
+create function mr_outparam_succeed3(i anymultirange, out r anyrange, out t text)
+  as $$ select range_merge($1), 'foo'::text $$ language sql;
+select * from mr_outparam_succeed3(int4multirange(int4range(1,2)));
+   r   |  t  
+-------+-----
+ [1,2) | foo
+(1 row)
+
+-- infer anymultirange from anyrange
+create function mr_outparam_succeed4(i anyrange, out r anymultirange, out t text)
+  as $$ select $1 @+ $1, 'foo'::text $$ language sql;
+select * from mr_outparam_succeed4(int4range(1,2));
+    r    |  t  
+---------+-----
+ {[1,2)} | foo
+(1 row)
+
+-- infer anyelement from anymultirange
+create function mr_inoutparam_succeed(out i anyelement, inout r anymultirange)
+  as $$ select upper($1), $1 $$ language sql;
+select * from mr_inoutparam_succeed(int4multirange(int4range(1,2)));
+ i |    r    
+---+---------
+ 2 | {[1,2)}
+(1 row)
+
+-- infer anyelement+anymultirange from anyelement+anymultirange
+create function mr_table_succeed(i anyelement, r anymultirange) returns table(i anyelement, r anymultirange)
+  as $$ select $1, $2 $$ language sql;
+select * from mr_table_succeed(123, int4multirange(int4range(1,11)));
+  i  |    r     
+-----+----------
+ 123 | {[1,11)}
+(1 row)
+
+-- should fail
+create function mr_outparam_fail(i anyelement, out r anymultirange, out t text)
+  as $$ select '[1,10]', 'foo' $$ language sql;
+ERROR:  cannot determine result data type
+DETAIL:  A function returning "anyrange" or "anymultirange" must have at least one "anyrange" or "anymultirange" argument.
+--should fail
+create function mr_inoutparam_fail(inout i anyelement, out r anymultirange)
+  as $$ select $1, '[1,10]' $$ language sql;
+ERROR:  cannot determine result data type
+DETAIL:  A function returning "anyrange" or "anymultirange" must have at least one "anyrange" or "anymultirange" argument.
+--should fail
+create function mr_table_fail(i anyelement) returns table(i anyelement, r anymultirange)
+  as $$ select $1, '[1,10]' $$ language sql;
+ERROR:  cannot determine result data type
+DETAIL:  A function returning "anyrange" or "anymultirange" must have at least one "anyrange" or "anymultirange" argument.
diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out
index 916e1016b5..e818eaea79 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -1101,13 +1101,15 @@ ORDER BY 1, 2;
  ?|   | ?|
  ?||  | ?||
  @    | ~
+ @*   | @*
+ @+   | @+
  @@   | @@
  @@@  | @@@
  |    | |
  ~<=~ | ~>=~
  ~<~  | ~>~
  ~=   | ~=
-(30 rows)
+(32 rows)
 
 -- Likewise for negator pairs.
 SELECT DISTINCT o1.oprname AS op1, o2.oprname AS op2
diff --git a/src/test/regress/expected/rangetypes.out b/src/test/regress/expected/rangetypes.out
index 6fd16bddd1..1bfe426be6 100644
--- a/src/test/regress/expected/rangetypes.out
+++ b/src/test/regress/expected/rangetypes.out
@@ -518,6 +518,24 @@ select numrange(1.0, 2.0) * numrange(2.5, 3.0);
  empty
 (1 row)
 
+select range_intersect_agg(nr) from numrange_test;
+ range_intersect_agg 
+---------------------
+ empty
+(1 row)
+
+select range_intersect_agg(nr) from numrange_test where false;
+ range_intersect_agg 
+---------------------
+ 
+(1 row)
+
+select range_intersect_agg(nr) from numrange_test where nr @> 4.0;
+ range_intersect_agg 
+---------------------
+ [3,5)
+(1 row)
+
 create table numrange_test2(nr numrange);
 create index numrange_test2_hash_idx on numrange_test2 using hash (nr);
 INSERT INTO numrange_test2 VALUES('[, 5)');
@@ -1284,7 +1302,7 @@ drop function anyarray_anyrange_func(anyarray, anyrange);
 create function bogus_func(anyelement)
   returns anyrange as 'select int4range(1,10)' language sql;
 ERROR:  cannot determine result data type
-DETAIL:  A function returning "anyrange" must have at least one "anyrange" argument.
+DETAIL:  A function returning "anyrange" or "anymultirange" must have at least one "anyrange" or "anymultirange" argument.
 -- should fail
 create function bogus_func(int)
   returns anyrange as 'select int4range(1,10)' language sql;
@@ -1392,6 +1410,7 @@ reset enable_sort;
 --
 -- OUT/INOUT/TABLE functions
 --
+-- infer anyrange from anyrange
 create function outparam_succeed(i anyrange, out r anyrange, out t text)
   as $$ select $1, 'foo'::text $$ language sql;
 select * from outparam_succeed(int4range(1,2));
@@ -1400,6 +1419,16 @@ select * from outparam_succeed(int4range(1,2));
  [1,2) | foo
 (1 row)
 
+-- infer anyarray from anyrange
+create function outparam_succeed2(i anyrange, out r anyarray, out t text)
+  as $$ select ARRAY[upper($1)], 'foo'::text $$ language sql;
+select * from outparam_succeed2(int4range(int4range(1,2)));
+  r  |  t  
+-----+-----
+ {2} | foo
+(1 row)
+
+-- infer anyelement from anyrange
 create function inoutparam_succeed(out i anyelement, inout r anyrange)
   as $$ select upper($1), $1 $$ language sql;
 select * from inoutparam_succeed(int4range(1,2));
@@ -1408,6 +1437,7 @@ select * from inoutparam_succeed(int4range(1,2));
  2 | [1,2)
 (1 row)
 
+-- infer anyelement+anyrange from anyelement+anyrange
 create function table_succeed(i anyelement, r anyrange) returns table(i anyelement, r anyrange)
   as $$ select $1, $2 $$ language sql;
 select * from table_succeed(123, int4range(1,11));
@@ -1420,14 +1450,14 @@ select * from table_succeed(123, int4range(1,11));
 create function outparam_fail(i anyelement, out r anyrange, out t text)
   as $$ select '[1,10]', 'foo' $$ language sql;
 ERROR:  cannot determine result data type
-DETAIL:  A function returning "anyrange" must have at least one "anyrange" argument.
+DETAIL:  A function returning "anyrange" or "anymultirange" must have at least one "anyrange" or "anymultirange" argument.
 --should fail
 create function inoutparam_fail(inout i anyelement, out r anyrange)
   as $$ select $1, '[1,10]' $$ language sql;
 ERROR:  cannot determine result data type
-DETAIL:  A function returning "anyrange" must have at least one "anyrange" argument.
+DETAIL:  A function returning "anyrange" or "anymultirange" must have at least one "anyrange" or "anymultirange" argument.
 --should fail
 create function table_fail(i anyelement) returns table(i anyelement, r anyrange)
   as $$ select $1, '[1,10]' $$ language sql;
 ERROR:  cannot determine result data type
-DETAIL:  A function returning "anyrange" must have at least one "anyrange" argument.
+DETAIL:  A function returning "anyrange" or "anymultirange" must have at least one "anyrange" or "anymultirange" argument.
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index 070de78e85..6f7fcf6326 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -92,6 +92,7 @@ num_exp_sqrt|t
 num_exp_sub|t
 num_input_test|f
 num_result|f
+nummultirange_test|t
 onek|t
 onek2|t
 path_tbl|f
@@ -172,6 +173,7 @@ quad_poly_tbl|t
 radix_text_tbl|t
 ramp|f
 real_city|f
+reservations|f
 road|t
 shighway|t
 slow_emp4000|f
diff --git a/src/test/regress/sql/multirangetypes.sql b/src/test/regress/sql/multirangetypes.sql
index 8651ba3f3d..b3ede18f96 100644
--- a/src/test/regress/sql/multirangetypes.sql
+++ b/src/test/regress/sql/multirangetypes.sql
@@ -63,3 +63,610 @@ select textmultirange();
 select textmultirange(textrange('a', 'c'));
 select textmultirange(textrange('a', 'c'), textrange('f', 'g'));
 select textmultirange(textrange('a', 'c'), textrange('b', 'd'));
+
+--
+-- create some test data and test the operators
+--
+
+CREATE TABLE nummultirange_test (nmr NUMMULTIRANGE);
+CREATE INDEX nummultirange_test_btree ON nummultirange_test(nmr);
+
+INSERT INTO nummultirange_test VALUES('{}');
+INSERT INTO nummultirange_test VALUES('{[,)}');
+INSERT INTO nummultirange_test VALUES('{[3,]}');
+INSERT INTO nummultirange_test VALUES('{[,), [3,]}');
+INSERT INTO nummultirange_test VALUES('{[, 5)}');
+INSERT INTO nummultirange_test VALUES(nummultirange());
+INSERT INTO nummultirange_test VALUES(nummultirange(variadic '{}'::numrange[]));
+INSERT INTO nummultirange_test VALUES(nummultirange(numrange(1.1, 2.2)));
+INSERT INTO nummultirange_test VALUES('{empty}');
+INSERT INTO nummultirange_test VALUES(nummultirange(numrange(1.7, 1.7, '[]'), numrange(1.7, 1.9)));
+INSERT INTO nummultirange_test VALUES(nummultirange(numrange(1.7, 1.7, '[]'), numrange(1.9, 2.1)));
+
+SELECT nmr, isempty(nmr), lower(nmr), upper(nmr) FROM nummultirange_test ORDER BY nmr;
+SELECT nmr, lower_inc(nmr), lower_inf(nmr), upper_inc(nmr), upper_inf(nmr) FROM nummultirange_test ORDER BY nmr;
+
+SELECT * FROM nummultirange_test WHERE nmr = '{}';
+SELECT * FROM nummultirange_test WHERE nmr = '{(,5)}';
+SELECT * FROM nummultirange_test WHERE nmr = '{[3,)}';
+SELECT * FROM nummultirange_test WHERE nmr = '{[1.7,1.7]}';
+SELECT * FROM nummultirange_test WHERE nmr = '{[1.7,1.7],[1.9,2.1)}';
+SELECT * FROM nummultirange_test WHERE nmr < '{}';
+SELECT * FROM nummultirange_test WHERE nmr < '{[-1000.0, -1000.0]}';
+SELECT * FROM nummultirange_test WHERE nmr < '{[0.0, 1.0]}';
+SELECT * FROM nummultirange_test WHERE nmr < '{[1000.0, 1001.0]}';
+SELECT * FROM nummultirange_test WHERE nmr <= '{}';
+SELECT * FROM nummultirange_test WHERE nmr <= '{[3,)}';
+SELECT * FROM nummultirange_test WHERE nmr >= '{}';
+SELECT * FROM nummultirange_test WHERE nmr >= '{[3,)}';
+SELECT * FROM nummultirange_test WHERE nmr > '{}';
+SELECT * FROM nummultirange_test WHERE nmr > '{[-1000.0, -1000.0]}';
+SELECT * FROM nummultirange_test WHERE nmr > '{[0.0, 1.0]}';
+SELECT * FROM nummultirange_test WHERE nmr > '{[1000.0, 1001.0]}';
+SELECT * FROM nummultirange_test WHERE nmr <> '{}';
+SELECT * FROM nummultirange_test WHERE nmr <> '{(,5)}';
+
+select nummultirange(numrange(2.0, 1.0));
+select nummultirange(numrange(5.0, 6.0), numrange(1.0, 2.0));
+
+-- overlaps
+SELECT * FROM nummultirange_test WHERE range_overlaps_multirange(numrange(4.0, 4.2), nmr);
+SELECT * FROM nummultirange_test WHERE numrange(4.0, 4.2) && nmr;
+SELECT * FROM nummultirange_test WHERE multirange_overlaps_range(nmr, numrange(4.0, 4.2));
+SELECT * FROM nummultirange_test WHERE nmr && numrange(4.0, 4.2);
+SELECT * FROM nummultirange_test WHERE multirange_overlaps_multirange(nmr, nummultirange(numrange(4.0, 4.2), numrange(6.0, 7.0)));
+SELECT * FROM nummultirange_test WHERE nmr && nummultirange(numrange(4.0, 4.2), numrange(6.0, 7.0));
+SELECT * FROM nummultirange_test WHERE nmr && nummultirange(numrange(6.0, 7.0));
+SELECT * FROM nummultirange_test WHERE nmr && nummultirange(numrange(6.0, 7.0), numrange(8.0, 9.0));
+
+-- mr contains x
+SELECT * FROM nummultirange_test WHERE multirange_contains_elem(nmr, 4.0);
+SELECT * FROM nummultirange_test WHERE nmr @> 4.0;
+SELECT * FROM nummultirange_test WHERE multirange_contains_range(nmr, numrange(4.0, 4.2));
+SELECT * FROM nummultirange_test WHERE nmr @> numrange(4.0, 4.2);
+SELECT * FROM nummultirange_test WHERE multirange_contains_multirange(nmr, '{[4.0,4.2), [6.0, 8.0)}');
+SELECT * FROM nummultirange_test WHERE nmr @> '{[4.0,4.2), [6.0, 8.0)}'::nummultirange;
+
+-- x is contained by mr
+SELECT * FROM nummultirange_test WHERE elem_contained_by_multirange(4.0, nmr);
+SELECT * FROM nummultirange_test WHERE 4.0 <@ nmr;
+SELECT * FROM nummultirange_test WHERE range_contained_by_multirange(numrange(4.0, 4.2), nmr);
+SELECT * FROM nummultirange_test WHERE numrange(4.0, 4.2) <@ nmr;
+SELECT * FROM nummultirange_test WHERE multirange_contained_by_multirange('{[4.0,4.2), [6.0, 8.0)}', nmr);
+SELECT * FROM nummultirange_test WHERE '{[4.0,4.2), [6.0, 8.0)}'::nummultirange <@ nmr;
+
+-- overlaps
+SELECT 'empty'::numrange && nummultirange();
+SELECT 'empty'::numrange && nummultirange(numrange(1,2));
+SELECT nummultirange() && 'empty'::numrange;
+SELECT nummultirange(numrange(1,2)) && 'empty'::numrange;
+SELECT nummultirange() && nummultirange();
+SELECT nummultirange() && nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) && nummultirange();
+SELECT nummultirange(numrange(3,4)) && nummultirange(numrange(1,2), numrange(7,8));
+SELECT nummultirange(numrange(1,2), numrange(7,8)) && nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(3,4)) && nummultirange(numrange(1,2), numrange(3.5,8));
+SELECT nummultirange(numrange(1,2), numrange(3.5,8)) && numrange(3,4);
+SELECT nummultirange(numrange(1,2), numrange(3.5,8)) && nummultirange(numrange(3,4));
+
+-- contains
+SELECT nummultirange() @> nummultirange();
+SELECT nummultirange() @> 'empty'::numrange;
+SELECT nummultirange(numrange(null,null)) @> numrange(1,2);
+SELECT nummultirange(numrange(null,null)) @> numrange(null,2);
+SELECT nummultirange(numrange(null,null)) @> numrange(2,null);
+SELECT nummultirange(numrange(null,5)) @> numrange(null,3);
+SELECT nummultirange(numrange(null,5)) @> numrange(null,8);
+SELECT nummultirange(numrange(5,null)) @> numrange(8,null);
+SELECT nummultirange(numrange(5,null)) @> numrange(3,null);
+SELECT nummultirange(numrange(1,5)) @> numrange(8,9);
+SELECT nummultirange(numrange(1,5)) @> numrange(3,9);
+SELECT nummultirange(numrange(1,5)) @> numrange(1,4);
+SELECT nummultirange(numrange(1,5)) @> numrange(1,5);
+SELECT nummultirange(numrange(-4,-2), numrange(1,5)) @> numrange(1,5);
+SELECT nummultirange(numrange(1,5), numrange(8,9)) @> numrange(1,5);
+SELECT nummultirange(numrange(1,5), numrange(8,9)) @> numrange(6,7);
+SELECT nummultirange(numrange(1,5), numrange(6,9)) @> numrange(6,7);
+SELECT '{[1,5)}'::nummultirange @> '{[1,5)}';
+SELECT '{[-4,-2), [1,5)}'::nummultirange @> '{[1,5)}';
+SELECT '{[1,5), [8,9)}'::nummultirange @> '{[1,5)}';
+SELECT '{[1,5), [8,9)}'::nummultirange @> '{[6,7)}';
+SELECT '{[1,5), [6,9)}'::nummultirange @> '{[6,7)}';
+
+-- is contained by
+SELECT nummultirange() <@ nummultirange();
+SELECT 'empty'::numrange <@ nummultirange();
+SELECT numrange(1,2) <@ nummultirange(numrange(null,null));
+SELECT numrange(null,2) <@ nummultirange(numrange(null,null));
+SELECT numrange(2,null) <@ nummultirange(numrange(null,null));
+SELECT numrange(null,3) <@ nummultirange(numrange(null,5));
+SELECT numrange(null,8) <@ nummultirange(numrange(null,5));
+SELECT numrange(8,null) <@ nummultirange(numrange(5,null));
+SELECT numrange(3,null) <@ nummultirange(numrange(5,null));
+SELECT numrange(8,9) <@ nummultirange(numrange(1,5));
+SELECT numrange(3,9) <@ nummultirange(numrange(1,5));
+SELECT numrange(1,4) <@ nummultirange(numrange(1,5));
+SELECT numrange(1,5) <@ nummultirange(numrange(1,5));
+SELECT numrange(1,5) <@ nummultirange(numrange(-4,-2), numrange(1,5));
+SELECT numrange(1,5) <@ nummultirange(numrange(1,5), numrange(8,9));
+SELECT numrange(6,7) <@ nummultirange(numrange(1,5), numrange(8,9));
+SELECT numrange(6,7) <@ nummultirange(numrange(1,5), numrange(6,9));
+SELECT '{[1,5)}' <@ '{[1,5)}'::nummultirange;
+SELECT '{[1,5)}' <@ '{[-4,-2), [1,5)}'::nummultirange;
+SELECT '{[1,5)}' <@ '{[1,5), [8,9)}'::nummultirange;
+SELECT '{[6,7)}' <@ '{[1,5), [8,9)}'::nummultirange;
+SELECT '{[6,7)}' <@ '{[1,5), [6,9)}'::nummultirange;
+
+-- overleft
+SELECT 'empty'::numrange &< nummultirange();
+SELECT 'empty'::numrange &< nummultirange(numrange(1,2));
+SELECT nummultirange() &< 'empty'::numrange;
+SELECT nummultirange(numrange(1,2)) &< 'empty'::numrange;
+SELECT nummultirange() &< nummultirange();
+SELECT nummultirange(numrange(1,2)) &< nummultirange();
+SELECT nummultirange() &< nummultirange(numrange(1,2));
+SELECT numrange(6,7) &< nummultirange(numrange(3,4));
+SELECT numrange(1,2) &< nummultirange(numrange(3,4));
+SELECT numrange(1,4) &< nummultirange(numrange(3,4));
+SELECT numrange(1,6) &< nummultirange(numrange(3,4));
+SELECT numrange(3.5,6) &< nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(6,7)) &< numrange(3,4);
+SELECT nummultirange(numrange(1,2)) &< numrange(3,4);
+SELECT nummultirange(numrange(1,4)) &< numrange(3,4);
+SELECT nummultirange(numrange(1,6)) &< numrange(3,4);
+SELECT nummultirange(numrange(3.5,6)) &< numrange(3,4);
+SELECT nummultirange(numrange(6,7)) &< nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,2)) &< nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,4)) &< nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,6)) &< nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(3.5,6)) &< nummultirange(numrange(3,4));
+
+-- overright
+SELECT nummultirange() &> 'empty'::numrange;
+SELECT nummultirange(numrange(1,2)) &> 'empty'::numrange;
+SELECT 'empty'::numrange &> nummultirange();
+SELECT 'empty'::numrange &> nummultirange(numrange(1,2));
+SELECT nummultirange() &> nummultirange();
+SELECT nummultirange() &> nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) &> nummultirange();
+SELECT nummultirange(numrange(3,4)) &> numrange(6,7);
+SELECT nummultirange(numrange(3,4)) &> numrange(1,2);
+SELECT nummultirange(numrange(3,4)) &> numrange(1,4);
+SELECT nummultirange(numrange(3,4)) &> numrange(1,6);
+SELECT nummultirange(numrange(3,4)) &> numrange(3.5,6);
+SELECT numrange(3,4) &> nummultirange(numrange(6,7));
+SELECT numrange(3,4) &> nummultirange(numrange(1,2));
+SELECT numrange(3,4) &> nummultirange(numrange(1,4));
+SELECT numrange(3,4) &> nummultirange(numrange(1,6));
+SELECT numrange(3,4) &> nummultirange(numrange(3.5,6));
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(6,7));
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(1,4));
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(1,6));
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(3.5,6));
+
+-- meets
+SELECT 'empty'::numrange -|- nummultirange();
+SELECT 'empty'::numrange -|- nummultirange(numrange(1,2));
+SELECT nummultirange() -|- 'empty'::numrange;
+SELECT nummultirange(numrange(1,2)) -|- 'empty'::numrange;
+SELECT nummultirange() -|- nummultirange();
+SELECT nummultirange(numrange(1,2)) -|- nummultirange();
+SELECT nummultirange() -|- nummultirange(numrange(1,2));
+SELECT numrange(1,2) -|- nummultirange(numrange(2,4));
+SELECT numrange(1,2) -|- nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,2)) -|- numrange(2,4);
+SELECT nummultirange(numrange(1,2)) -|- numrange(3,4);
+SELECT nummultirange(numrange(1,2)) -|- nummultirange(numrange(2,4));
+SELECT nummultirange(numrange(1,2)) -|- nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,2), numrange(5,6)) -|- nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,2), numrange(5,6)) -|- nummultirange(numrange(6,7));
+SELECT nummultirange(numrange(1,2), numrange(5,6)) -|- nummultirange(numrange(8,9));
+SELECT nummultirange(numrange(1,2)) -|- nummultirange(numrange(2,4), numrange(6,7));
+
+-- strictly left
+select 'empty'::numrange << nummultirange();
+select numrange(1,2) << nummultirange();
+select numrange(1,2) << nummultirange(numrange(3,4));
+select numrange(1,2) << nummultirange(numrange(0,4));
+select numrange(1,2) << nummultirange(numrange(0,4), numrange(7,8));
+select nummultirange() << 'empty'::numrange;
+select nummultirange() << numrange(1,2);
+select nummultirange(numrange(3,4)) << numrange(3,6);
+select nummultirange(numrange(0,2)) << numrange(3,6);
+select nummultirange(numrange(0,2), numrange(7,8)) << numrange(3,6);
+select nummultirange(numrange(-4,-2), numrange(0,2)) << numrange(3,6);
+select nummultirange() << nummultirange();
+select nummultirange() << nummultirange(numrange(1,2));
+select nummultirange(numrange(1,2)) << nummultirange();
+select nummultirange(numrange(1,2)) << nummultirange(numrange(1,2));
+select nummultirange(numrange(1,2)) << nummultirange(numrange(3,4));
+select nummultirange(numrange(1,2)) << nummultirange(numrange(3,4), numrange(7,8));
+select nummultirange(numrange(1,2), numrange(4,5)) << nummultirange(numrange(3,4), numrange(7,8));
+
+-- strictly right
+select nummultirange() >> 'empty'::numrange;
+select nummultirange() >> numrange(1,2);
+select nummultirange(numrange(3,4)) >> numrange(1,2);
+select nummultirange(numrange(0,4)) >> numrange(1,2);
+select nummultirange(numrange(0,4), numrange(7,8)) >> numrange(1,2);
+select 'empty'::numrange >> nummultirange();
+select numrange(1,2) >> nummultirange();
+select numrange(3,6) >> nummultirange(numrange(3,4));
+select numrange(3,6) >> nummultirange(numrange(0,2));
+select numrange(3,6) >> nummultirange(numrange(0,2), numrange(7,8));
+select numrange(3,6) >> nummultirange(numrange(-4,-2), numrange(0,2));
+select nummultirange() >> nummultirange();
+select nummultirange(numrange(1,2)) >> nummultirange();
+select nummultirange() >> nummultirange(numrange(1,2));
+select nummultirange(numrange(1,2)) >> nummultirange(numrange(1,2));
+select nummultirange(numrange(3,4)) >> nummultirange(numrange(1,2));
+select nummultirange(numrange(3,4), numrange(7,8)) >> nummultirange(numrange(1,2));
+select nummultirange(numrange(3,4), numrange(7,8)) >> nummultirange(numrange(1,2), numrange(4,5));
+
+-- union
+SELECT 'empty'::numrange @+ 'empty'::numrange;
+SELECT 'empty'::numrange @+ nummultirange();
+SELECT nummultirange() @+ 'empty'::numrange;
+SELECT nummultirange() @+ nummultirange();
+SELECT 'empty'::numrange @+ numrange(1,2);
+SELECT 'empty'::numrange @+ nummultirange(numrange(1,2));
+SELECT numrange(1,2) @+ nummultirange();
+SELECT nummultirange(numrange(1,2)) @+ 'empty'::numrange;
+SELECT numrange(1,2) @+ 'empty'::numrange;
+SELECT nummultirange() @+ numrange(1,2);
+SELECT nummultirange() @+ nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) @+ nummultirange();
+SELECT numrange(1,2) @+ numrange(1,2);
+SELECT numrange(1,2) @+ numrange(2,4);
+SELECT numrange(1,2) @+ numrange(3,4);
+SELECT nummultirange(numrange(1,2)) @+ nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) @+ nummultirange(numrange(2,4));
+SELECT nummultirange(numrange(1,2)) @+ nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) @+ nummultirange(numrange(2,4));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) @+ nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) @+ nummultirange(numrange(0,9));
+
+-- merge
+SELECT range_merge(nummultirange());
+SELECT range_merge(nummultirange(numrange(1,2)));
+SELECT range_merge(nummultirange(numrange(1,2), numrange(7,8)));
+
+-- minus
+SELECT 'empty'::numrange @- 'empty'::numrange;
+SELECT 'empty'::numrange @- nummultirange();
+SELECT nummultirange() @- 'empty'::numrange;
+SELECT nummultirange() @- nummultirange();
+SELECT 'empty'::numrange @- numrange(1,2);
+SELECT 'empty'::numrange @- nummultirange(numrange(1,2));
+SELECT numrange(1,2) @- nummultirange();
+SELECT nummultirange(numrange(1,2)) @- 'empty'::numrange;
+SELECT 'empty'::numrange @- numrange(1,2);
+SELECT numrange(1,2) @- 'empty'::numrange;
+SELECT nummultirange() @- numrange(1,2);
+SELECT nummultirange() @- nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) @- nummultirange();
+SELECT numrange(1,3) @- numrange(1,3);
+SELECT numrange(1,3) @- numrange(1,2);
+SELECT numrange(1,3) @- numrange(2,4);
+SELECT numrange(1,3) @- numrange(3,4);
+SELECT numrange(1,3) @- nummultirange(numrange(1,3));
+SELECT numrange(1,3) @- nummultirange(numrange(1,2));
+SELECT numrange(1,3) @- nummultirange(numrange(2,4));
+SELECT numrange(1,3) @- nummultirange(numrange(3,4));
+SELECT numrange(1,10) @- nummultirange(numrange(1,2), numrange(4,5));
+SELECT nummultirange(numrange(1,10)) @- numrange(0,2);
+SELECT nummultirange(numrange(1,10)) @- numrange(1,2);
+SELECT nummultirange(numrange(1,10)) @- numrange(3,4);
+SELECT nummultirange(numrange(1,10)) @- numrange(13,18);
+SELECT nummultirange(numrange(1,2), numrange(3,4)) @- nummultirange();
+SELECT nummultirange(numrange(1,2)) @- nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) @- nummultirange(numrange(2,4));
+SELECT nummultirange(numrange(1,2)) @- nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,4)) @- nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,4)) @- nummultirange(numrange(2,3));
+SELECT nummultirange(numrange(1,4)) @- nummultirange(numrange(0,8));
+SELECT nummultirange(numrange(1,4)) @- nummultirange(numrange(0,2));
+SELECT nummultirange(numrange(1,8)) @- nummultirange(numrange(0,2), numrange(3,4));
+SELECT nummultirange(numrange(1,8)) @- nummultirange(numrange(2,3), numrange(5,null));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) @- nummultirange(numrange(-2,0));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) @- nummultirange(numrange(2,4));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) @- nummultirange(numrange(3,5));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) @- nummultirange(numrange(0,9));
+SELECT nummultirange(numrange(1,3), numrange(4,5)) @- nummultirange(numrange(2,9));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) @- nummultirange(numrange(8,9));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) @- nummultirange(numrange(-2,0), numrange(8,9));
+
+-- intersection
+SELECT 'empty'::numrange @* 'empty'::numrange;
+SELECT 'empty'::numrange @* nummultirange();
+SELECT nummultirange() @* 'empty'::numrange;
+SELECT nummultirange() @* nummultirange();
+SELECT 'empty'::numrange @* numrange(1,2);
+SELECT 'empty'::numrange @* nummultirange(numrange(1,2));
+SELECT numrange(1,2) @* 'empty'::numrange;
+SELECT numrange(1,2) @* nummultirange();
+SELECT nummultirange(numrange(1,2)) @* 'empty'::numrange;
+SELECT nummultirange() @* numrange(1,2);
+SELECT nummultirange() @* nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) @* nummultirange();
+SELECT numrange(1,3) @* numrange(1,3);
+SELECT numrange(1,3) @* numrange(1,2);
+SELECT numrange(1,3) @* numrange(1,5);
+SELECT numrange(1,3) @* numrange(2,5);
+SELECT numrange(1,5) @* numrange(2,3);
+SELECT '{[1,3)}'::nummultirange @* numrange(1,3);
+SELECT '{[1,3)}'::nummultirange @* numrange(1,2);
+SELECT '{[1,3)}'::nummultirange @* numrange(1,5);
+SELECT '{[1,3)}'::nummultirange @* numrange(2,5);
+SELECT '{[1,5)}'::nummultirange @* numrange(2,3);
+SELECT numrange(1,3) @* '{[1,3)}'::nummultirange;
+SELECT numrange(1,2) @* '{[1,3)}'::nummultirange;
+SELECT numrange(1,5) @* '{[1,3)}'::nummultirange;
+SELECT numrange(2,5) @* '{[1,3)}'::nummultirange;
+SELECT numrange(2,3) @* '{[1,5)}'::nummultirange;
+SELECT '{[1,3)}'::nummultirange @* '{[1,5)}'::nummultirange;
+SELECT '{[1,3)}'::nummultirange @* '{[0,5)}'::nummultirange;
+SELECT '{[1,3)}'::nummultirange @* '{[0,2)}'::nummultirange;
+SELECT '{[1,3)}'::nummultirange @* '{[2,5)}'::nummultirange;
+SELECT '{[1,4)}'::nummultirange @* '{[2,3)}'::nummultirange;
+SELECT '{[1,4)}'::nummultirange @* '{[0,2), [3,5)}'::nummultirange;
+SELECT '{[1,4), [7,10)}'::nummultirange @* '{[0,8), [9,12)}'::nummultirange;
+SELECT '{[1,4), [7,10)}'::nummultirange @* '{[9,12)}'::nummultirange;
+SELECT '{[1,4), [7,10)}'::nummultirange @* '{[-5,-4), [5,6), [9,12)}'::nummultirange;
+SELECT '{[1,4), [7,10)}'::nummultirange @* '{[0,2), [3,8), [9,12)}'::nummultirange;
+SELECT '{[1,4), [7,10)}'::nummultirange @* '{[0,2), [3,8), [9,12)}'::nummultirange;
+
+--
+-- range_agg function
+--
+create table reservations ( room_id integer not null, booked_during daterange );
+insert into reservations values
+-- 1: has a meets and a gap
+(1, daterange('2018-07-01', '2018-07-07')),
+(1, daterange('2018-07-07', '2018-07-14')),
+(1, daterange('2018-07-20', '2018-07-22')),
+-- 2: just a single row
+(2, daterange('2018-07-01', '2018-07-03')),
+-- 3: one null range
+(3, NULL),
+-- 4: two null ranges
+(4, NULL),
+(4, NULL),
+-- 5: a null range and a non-null range
+(5, NULL),
+(5, daterange('2018-07-01', '2018-07-03')),
+-- 6: has overlap
+(6, daterange('2018-07-01', '2018-07-07')),
+(6, daterange('2018-07-05', '2018-07-10')),
+-- 7: two ranges that meet: no gap or overlap
+(7, daterange('2018-07-01', '2018-07-07')),
+(7, daterange('2018-07-07', '2018-07-14')),
+-- 8: an empty range
+(8, 'empty'::daterange)
+;
+SELECT   room_id, range_agg(booked_during)
+FROM     reservations
+GROUP BY room_id
+ORDER BY room_id;
+
+-- range_agg on a custom range type too
+SELECT  range_agg(r)
+FROM    (VALUES
+          ('[a,c]'::textrange),
+          ('[b,b]'::textrange),
+          ('[c,f]'::textrange),
+          ('[g,h)'::textrange),
+          ('[h,j)'::textrange)
+        ) t(r);
+
+select range_intersect_agg(nmr) from nummultirange_test;
+select range_intersect_agg(nmr) from nummultirange_test where false;
+-- test with just one input:
+select range_intersect_agg(nmr) from (values ('{[1,2]}'::nummultirange)) t(nmr);
+select range_intersect_agg(nmr) from nummultirange_test where nmr @> 4.0;
+
+create table nummultirange_test2(nmr nummultirange);
+create index nummultirange_test2_hash_idx on nummultirange_test2 using hash (nmr);
+
+INSERT INTO nummultirange_test2 VALUES('{[, 5)}');
+INSERT INTO nummultirange_test2 VALUES(nummultirange(numrange(1.1, 2.2)));
+INSERT INTO nummultirange_test2 VALUES(nummultirange(numrange(1.1, 2.2)));
+INSERT INTO nummultirange_test2 VALUES(nummultirange(numrange(1.1, 2.2,'()')));
+INSERT INTO nummultirange_test2 VALUES('{}');
+
+select * from nummultirange_test2 where nmr = '{}';
+select * from nummultirange_test2 where nmr = nummultirange(numrange(1.1, 2.2));
+select * from nummultirange_test2 where nmr = nummultirange(numrange(1.1, 2.3));
+
+set enable_nestloop=t;
+set enable_hashjoin=f;
+set enable_mergejoin=f;
+select * from nummultirange_test natural join nummultirange_test2 order by nmr;
+set enable_nestloop=f;
+set enable_hashjoin=t;
+set enable_mergejoin=f;
+select * from nummultirange_test natural join nummultirange_test2 order by nmr;
+set enable_nestloop=f;
+set enable_hashjoin=f;
+set enable_mergejoin=t;
+select * from nummultirange_test natural join nummultirange_test2 order by nmr;
+
+set enable_nestloop to default;
+set enable_hashjoin to default;
+set enable_mergejoin to default;
+
+DROP TABLE nummultirange_test2;
+
+--
+-- Test user-defined multirange of floats
+--
+
+select '{[123.001, 5.e9)}'::float8multirange @> 888.882::float8;
+create table float8multirange_test(f8mr float8multirange, i int);
+insert into float8multirange_test values(float8multirange(float8range(-100.00007, '1.111113e9')), 42);
+select * from float8multirange_test;
+drop table float8multirange_test;
+
+--
+-- Test multirange types over domains
+--
+
+create domain mydomain as int4;
+create type mydomainrange as range(subtype=mydomain);
+select '{[4,50)}'::mydomainmultirange @> 7::mydomain;
+drop domain mydomain cascade;
+
+--
+-- Test domains over multirange types
+--
+
+create domain restrictedmultirange as int4multirange check (upper(value) < 10);
+select '{[4,5)}'::restrictedmultirange @> 7;
+select '{[4,50)}'::restrictedmultirange @> 7; -- should fail
+drop domain restrictedmultirange;
+
+--
+-- Test multiple multirange types over the same subtype
+--
+
+create type textrange1 as range(subtype=text, collation="C");
+create type textrange2 as range(subtype=text, collation="C");
+
+select textmultirange1(textrange2('a','Z'));  -- should fail
+select textmultirange1(textrange1('a','Z')) @> 'b'::text;
+select textmultirange2(textrange2('a','z')) @> 'b'::text;
+
+drop type textrange1;
+drop type textrange2;
+
+--
+-- Test polymorphic type system
+--
+
+create function anyarray_anymultirange_func(a anyarray, r anymultirange)
+  returns anyelement as 'select $1[1] + lower($2);' language sql;
+
+select anyarray_anymultirange_func(ARRAY[1,2], int4multirange(int4range(10,20)));
+
+-- should fail
+select anyarray_anymultirange_func(ARRAY[1,2], nummultirange(numrange(10,20)));
+
+drop function anyarray_anymultirange_func(anyarray, anymultirange);
+
+-- should fail
+create function bogus_func(anyelement)
+  returns anymultirange as 'select int4multirange(int4range(1,10))' language sql;
+
+-- should fail
+create function bogus_func(int)
+  returns anymultirange as 'select int4multirange(int4range(1,10))' language sql;
+
+create function range_add_bounds(anymultirange)
+  returns anyelement as 'select lower($1) + upper($1)' language sql;
+
+select range_add_bounds(int4multirange(int4range(1, 17)));
+select range_add_bounds(nummultirange(numrange(1.0001, 123.123)));
+
+create function multirangetypes_sql(q anymultirange, b anyarray, out c anyelement)
+  as $$ select upper($1) + $2[1] $$
+  language sql;
+
+select multirangetypes_sql(int4multirange(int4range(1,10)), ARRAY[2,20]);
+select multirangetypes_sql(nummultirange(numrange(1,10)), ARRAY[2,20]);  -- match failure
+
+--
+-- Arrays of multiranges
+--
+
+select ARRAY[nummultirange(numrange(1.1, 1.2)), nummultirange(numrange(12.3, 155.5))];
+
+create table i8mr_array (f1 int, f2 int8multirange[]);
+insert into i8mr_array values (42, array[int8multirange(int8range(1,10)), int8multirange(int8range(2,20))]);
+select * from i8mr_array;
+drop table i8mr_array;
+
+--
+-- Multiranges of arrays
+--
+
+select arraymultirange(arrayrange(ARRAY[1,2], ARRAY[2,1]));
+select arraymultirange(arrayrange(ARRAY[2,1], ARRAY[1,2]));  -- fail
+
+select array[1,1] <@ arraymultirange(arrayrange(array[1,2], array[2,1]));
+select array[1,3] <@ arraymultirange(arrayrange(array[1,2], array[2,1]));
+
+--
+-- Ranges of composites
+--
+
+create type two_ints as (a int, b int);
+create type two_ints_range as range (subtype = two_ints);
+
+-- with force_parallel_mode on, this exercises tqueue.c's range remapping
+select *, row_to_json(upper(t)) as u from
+  (values (two_ints_multirange(two_ints_range(row(1,2), row(3,4)))),
+          (two_ints_multirange(two_ints_range(row(5,6), row(7,8))))) v(t);
+
+drop type two_ints cascade;
+
+--
+-- Check behavior when subtype lacks a hash function
+--
+
+set enable_sort = off;  -- try to make it pick a hash setop implementation
+
+select '{(2,5)}'::cashmultirange except select '{(5,6)}'::cashmultirange;
+
+reset enable_sort;
+
+--
+-- OUT/INOUT/TABLE functions
+--
+
+-- infer anymultirange from anymultirange
+create function mr_outparam_succeed(i anymultirange, out r anymultirange, out t text)
+  as $$ select $1, 'foo'::text $$ language sql;
+
+select * from mr_outparam_succeed(int4multirange(int4range(1,2)));
+
+-- infer anyarray from anymultirange
+create function mr_outparam_succeed2(i anymultirange, out r anyarray, out t text)
+  as $$ select ARRAY[upper($1)], 'foo'::text $$ language sql;
+
+select * from mr_outparam_succeed2(int4multirange(int4range(1,2)));
+
+-- infer anyrange from anymultirange
+create function mr_outparam_succeed3(i anymultirange, out r anyrange, out t text)
+  as $$ select range_merge($1), 'foo'::text $$ language sql;
+select * from mr_outparam_succeed3(int4multirange(int4range(1,2)));
+
+-- infer anymultirange from anyrange
+create function mr_outparam_succeed4(i anyrange, out r anymultirange, out t text)
+  as $$ select $1 @+ $1, 'foo'::text $$ language sql;
+
+select * from mr_outparam_succeed4(int4range(1,2));
+
+-- infer anyelement from anymultirange
+create function mr_inoutparam_succeed(out i anyelement, inout r anymultirange)
+  as $$ select upper($1), $1 $$ language sql;
+
+select * from mr_inoutparam_succeed(int4multirange(int4range(1,2)));
+
+-- infer anyelement+anymultirange from anyelement+anymultirange
+create function mr_table_succeed(i anyelement, r anymultirange) returns table(i anyelement, r anymultirange)
+  as $$ select $1, $2 $$ language sql;
+
+select * from mr_table_succeed(123, int4multirange(int4range(1,11)));
+
+-- should fail
+create function mr_outparam_fail(i anyelement, out r anymultirange, out t text)
+  as $$ select '[1,10]', 'foo' $$ language sql;
+
+--should fail
+create function mr_inoutparam_fail(inout i anyelement, out r anymultirange)
+  as $$ select $1, '[1,10]' $$ language sql;
+
+--should fail
+create function mr_table_fail(i anyelement) returns table(i anyelement, r anymultirange)
+  as $$ select $1, '[1,10]' $$ language sql;
diff --git a/src/test/regress/sql/rangetypes.sql b/src/test/regress/sql/rangetypes.sql
index 8960add976..32e18661d0 100644
--- a/src/test/regress/sql/rangetypes.sql
+++ b/src/test/regress/sql/rangetypes.sql
@@ -118,6 +118,10 @@ select numrange(1.0, 2.0) * numrange(2.0, 3.0);
 select numrange(1.0, 2.0) * numrange(1.5, 3.0);
 select numrange(1.0, 2.0) * numrange(2.5, 3.0);
 
+select range_intersect_agg(nr) from numrange_test;
+select range_intersect_agg(nr) from numrange_test where false;
+select range_intersect_agg(nr) from numrange_test where nr @> 4.0;
+
 create table numrange_test2(nr numrange);
 create index numrange_test2_hash_idx on numrange_test2 using hash (nr);
 
@@ -481,16 +485,25 @@ reset enable_sort;
 -- OUT/INOUT/TABLE functions
 --
 
+-- infer anyrange from anyrange
 create function outparam_succeed(i anyrange, out r anyrange, out t text)
   as $$ select $1, 'foo'::text $$ language sql;
 
 select * from outparam_succeed(int4range(1,2));
 
+-- infer anyarray from anyrange
+create function outparam_succeed2(i anyrange, out r anyarray, out t text)
+  as $$ select ARRAY[upper($1)], 'foo'::text $$ language sql;
+
+select * from outparam_succeed2(int4range(int4range(1,2)));
+
+-- infer anyelement from anyrange
 create function inoutparam_succeed(out i anyelement, inout r anyrange)
   as $$ select upper($1), $1 $$ language sql;
 
 select * from inoutparam_succeed(int4range(1,2));
 
+-- infer anyelement+anyrange from anyelement+anyrange
 create function table_succeed(i anyelement, r anyrange) returns table(i anyelement, r anyrange)
   as $$ select $1, $2 $$ language sql;
 
-- 
2.20.1

v7-0003-multirange-pg_dump.patchtext/x-diff; charset=us-asciiDownload
From 20cc5ccc98f0ccf21911b37da17fc91227dac17f Mon Sep 17 00:00:00 2001
From: "Paul A. Jungwirth" <pj@illuminatedcomputing.com>
Date: Sat, 21 Sep 2019 21:28:37 -0700
Subject: [PATCH v7 03/13] multirange pg_dump

---
 src/backend/commands/typecmds.c            |  85 ++++++++++++--
 src/backend/utils/adt/pg_upgrade_support.c |  22 ++++
 src/bin/pg_dump/pg_dump.c                  | 130 +++++++++++++++------
 src/bin/pg_dump/pg_dump.h                  |   1 +
 src/include/catalog/binary_upgrade.h       |   2 +
 src/include/catalog/pg_proc.dat            |   8 ++
 src/include/commands/typecmds.h            |   2 +
 7 files changed, 205 insertions(+), 45 deletions(-)

diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index 7a440cafd1..38948a049b 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -85,6 +85,8 @@ typedef struct
 
 /* Potentially set by pg_upgrade_support functions */
 Oid			binary_upgrade_next_array_pg_type_oid = InvalidOid;
+Oid			binary_upgrade_next_multirange_pg_type_oid = InvalidOid;
+Oid			binary_upgrade_next_multirange_array_pg_type_oid = InvalidOid;
 
 static void makeRangeConstructors(const char *name, Oid namespace,
 								  Oid rangeOid, Oid subtype);
@@ -1356,11 +1358,11 @@ DefineRange(CreateRangeStmt *stmt)
 	char	   *typeName;
 	Oid			typeNamespace;
 	Oid			typoid;
-	Oid			mltrngtypoid;
 	char	   *rangeArrayName;
 	char	   *multirangeTypeName;
 	char	   *multirangeArrayName;
 	Oid			rangeArrayOid;
+	Oid			multirangeOid;
 	Oid			multirangeArrayOid;
 	Oid			rangeSubtype = InvalidOid;
 	List	   *rangeSubOpclassName = NIL;
@@ -1530,8 +1532,11 @@ DefineRange(CreateRangeStmt *stmt)
 	/* Allocate OID for array type */
 	rangeArrayOid = AssignTypeArrayOid();
 
+	/* Allocate OID for multirange type */
+	multirangeOid = AssignTypeMultirangeOid();
+
 	/* Allocate OID for multirange array type */
-	multirangeArrayOid = AssignTypeArrayOid();
+	multirangeArrayOid = AssignTypeMultirangeArrayOid();
 
 	/* Create the pg_type entry */
 	address =
@@ -1573,7 +1578,7 @@ DefineRange(CreateRangeStmt *stmt)
 	multirangeTypeName = makeMultirangeTypeName(typeName, typeNamespace);
 
 	mltrngaddress =
-		TypeCreate(InvalidOid,	/* no predetermined type OID */
+		TypeCreate(multirangeOid,	/* force assignment of this type OID */
 				   multirangeTypeName,	/* type name */
 				   typeNamespace,	/* namespace */
 				   InvalidOid,	/* relation oid (n/a here) */
@@ -1604,11 +1609,11 @@ DefineRange(CreateRangeStmt *stmt)
 				   0,			/* Array dimensions of typbasetype */
 				   false,		/* Type NOT NULL */
 				   InvalidOid); /* type's collation (ranges never have one) */
-	mltrngtypoid = mltrngaddress.objectId;
+	Assert(multirangeOid == mltrngaddress.objectId);
 
 	/* Create the entry in pg_range */
 	RangeCreate(typoid, rangeSubtype, rangeCollation, rangeSubOpclass,
-				rangeCanonical, rangeSubtypeDiff, mltrngtypoid);
+				rangeCanonical, rangeSubtypeDiff, multirangeOid);
 
 	/*
 	 * Create the array type that goes with it.
@@ -1671,7 +1676,7 @@ DefineRange(CreateRangeStmt *stmt)
 			   InvalidOid,		/* typmodin procedure - none */
 			   InvalidOid,		/* typmodout procedure - none */
 			   F_ARRAY_TYPANALYZE,	/* analyze procedure */
-			   mltrngtypoid,	/* element type ID */
+			   multirangeOid,	/* element type ID */
 			   true,			/* yes this is an array type */
 			   InvalidOid,		/* no further array type */
 			   InvalidOid,		/* base type ID */
@@ -1688,7 +1693,7 @@ DefineRange(CreateRangeStmt *stmt)
 	/* And create the constructor functions for this range type */
 	makeRangeConstructors(typeName, typeNamespace, typoid, rangeSubtype);
 	makeMultirangeConstructors(multirangeTypeName, typeNamespace,
-							   mltrngtypoid, rangeArrayOid);
+							   multirangeOid, rangeArrayOid);
 
 	pfree(multirangeTypeName);
 	pfree(multirangeArrayName);
@@ -2269,6 +2274,72 @@ AssignTypeArrayOid(void)
 	return type_array_oid;
 }
 
+/*
+ *	AssignTypeMultirangeOid
+ *
+ *	Pre-assign the range type's multirange OID for use in pg_type.oid
+ */
+Oid
+AssignTypeMultirangeOid(void)
+{
+	Oid			type_multirange_oid;
+
+	/* Use binary-upgrade override for pg_type.oid? */
+	if (IsBinaryUpgrade)
+	{
+		if (!OidIsValid(binary_upgrade_next_multirange_pg_type_oid))
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+					 errmsg("pg_type multirange OID value not set when in binary upgrade mode")));
+
+		type_multirange_oid = binary_upgrade_next_multirange_pg_type_oid;
+		binary_upgrade_next_multirange_pg_type_oid = InvalidOid;
+	}
+	else
+	{
+		Relation	pg_type = table_open(TypeRelationId, AccessShareLock);
+
+		type_multirange_oid = GetNewOidWithIndex(pg_type, TypeOidIndexId,
+												 Anum_pg_type_oid);
+		table_close(pg_type, AccessShareLock);
+	}
+
+	return type_multirange_oid;
+}
+
+/*
+ *	AssignTypeMultirangeArrayOid
+ *
+ *	Pre-assign the range type's multirange array OID for use in pg_type.typarray
+ */
+Oid
+AssignTypeMultirangeArrayOid(void)
+{
+	Oid			type_multirange_array_oid;
+
+	/* Use binary-upgrade override for pg_type.oid? */
+	if (IsBinaryUpgrade)
+	{
+		if (!OidIsValid(binary_upgrade_next_multirange_array_pg_type_oid))
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+					 errmsg("pg_type multirange array OID value not set when in binary upgrade mode")));
+
+		type_multirange_array_oid = binary_upgrade_next_multirange_array_pg_type_oid;
+		binary_upgrade_next_multirange_array_pg_type_oid = InvalidOid;
+	}
+	else
+	{
+		Relation	pg_type = table_open(TypeRelationId, AccessShareLock);
+
+		type_multirange_array_oid = GetNewOidWithIndex(pg_type, TypeOidIndexId,
+													   Anum_pg_type_oid);
+		table_close(pg_type, AccessShareLock);
+	}
+
+	return type_multirange_array_oid;
+}
+
 
 /*-------------------------------------------------------------------
  * DefineCompositeType
diff --git a/src/backend/utils/adt/pg_upgrade_support.c b/src/backend/utils/adt/pg_upgrade_support.c
index 99db5ba389..d980b96f48 100644
--- a/src/backend/utils/adt/pg_upgrade_support.c
+++ b/src/backend/utils/adt/pg_upgrade_support.c
@@ -51,6 +51,28 @@ binary_upgrade_set_next_array_pg_type_oid(PG_FUNCTION_ARGS)
 	PG_RETURN_VOID();
 }
 
+Datum
+binary_upgrade_set_next_multirange_pg_type_oid(PG_FUNCTION_ARGS)
+{
+	Oid			typoid = PG_GETARG_OID(0);
+
+	CHECK_IS_BINARY_UPGRADE;
+	binary_upgrade_next_multirange_pg_type_oid = typoid;
+
+	PG_RETURN_VOID();
+}
+
+Datum
+binary_upgrade_set_next_multirange_array_pg_type_oid(PG_FUNCTION_ARGS)
+{
+	Oid			typoid = PG_GETARG_OID(0);
+
+	CHECK_IS_BINARY_UPGRADE;
+	binary_upgrade_next_multirange_array_pg_type_oid = typoid;
+
+	PG_RETURN_VOID();
+}
+
 Datum
 binary_upgrade_set_next_toast_pg_type_oid(PG_FUNCTION_ARGS)
 {
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 08658c8e86..4a0a03d50c 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -274,7 +274,8 @@ static void dumpSearchPath(Archive *AH);
 static void binary_upgrade_set_type_oids_by_type_oid(Archive *fout,
 													 PQExpBuffer upgrade_buffer,
 													 Oid pg_type_oid,
-													 bool force_array_type);
+													 bool force_array_type,
+													 bool include_multirange_type);
 static bool binary_upgrade_set_type_oids_by_rel_oid(Archive *fout,
 													PQExpBuffer upgrade_buffer, Oid pg_rel_oid);
 static void binary_upgrade_set_pg_class_oids(Archive *fout,
@@ -1583,7 +1584,7 @@ selectDumpableType(TypeInfo *tyinfo, Archive *fout)
 	}
 
 	/* skip auto-generated array types */
-	if (tyinfo->isArray)
+	if (tyinfo->isArray || tyinfo->isMultirange)
 	{
 		tyinfo->dobj.objType = DO_DUMMY_TYPE;
 
@@ -4275,15 +4276,48 @@ dumpSubscription(Archive *fout, SubscriptionInfo *subinfo)
 	free(qsubname);
 }
 
+static Oid get_next_possible_free_pg_type_oid(Archive *fout, PQExpBuffer upgrade_query)
+{
+	/*
+	 * If the old version didn't assign an array type, but the new version
+	 * does, we must select an unused type OID to assign.  This currently
+	 * only happens for domains, when upgrading pre-v11 to v11 and up.
+	 *
+	 * Note: local state here is kind of ugly, but we must have some,
+	 * since we mustn't choose the same unused OID more than once.
+	 */
+	static Oid	next_possible_free_oid = FirstNormalObjectId;
+	PGresult   *res;
+	bool		is_dup;
+
+	do
+	{
+		++next_possible_free_oid;
+		printfPQExpBuffer(upgrade_query,
+						  "SELECT EXISTS(SELECT 1 "
+						  "FROM pg_catalog.pg_type "
+						  "WHERE oid = '%u'::pg_catalog.oid);",
+						  next_possible_free_oid);
+		res = ExecuteSqlQueryForSingleRow(fout, upgrade_query->data);
+		is_dup = (PQgetvalue(res, 0, 0)[0] == 't');
+		PQclear(res);
+	} while (is_dup);
+
+	return next_possible_free_oid;
+}
+
 static void
 binary_upgrade_set_type_oids_by_type_oid(Archive *fout,
 										 PQExpBuffer upgrade_buffer,
 										 Oid pg_type_oid,
-										 bool force_array_type)
+										 bool force_array_type,
+										 bool include_multirange_type)
 {
 	PQExpBuffer upgrade_query = createPQExpBuffer();
 	PGresult   *res;
 	Oid			pg_type_array_oid;
+	Oid			pg_type_multirange_oid;
+	Oid			pg_type_multirange_array_oid;
 
 	appendPQExpBufferStr(upgrade_buffer, "\n-- For binary upgrade, must preserve pg_type oid\n");
 	appendPQExpBuffer(upgrade_buffer,
@@ -4304,33 +4338,7 @@ binary_upgrade_set_type_oids_by_type_oid(Archive *fout,
 	PQclear(res);
 
 	if (!OidIsValid(pg_type_array_oid) && force_array_type)
-	{
-		/*
-		 * If the old version didn't assign an array type, but the new version
-		 * does, we must select an unused type OID to assign.  This currently
-		 * only happens for domains, when upgrading pre-v11 to v11 and up.
-		 *
-		 * Note: local state here is kind of ugly, but we must have some,
-		 * since we mustn't choose the same unused OID more than once.
-		 */
-		static Oid	next_possible_free_oid = FirstNormalObjectId;
-		bool		is_dup;
-
-		do
-		{
-			++next_possible_free_oid;
-			printfPQExpBuffer(upgrade_query,
-							  "SELECT EXISTS(SELECT 1 "
-							  "FROM pg_catalog.pg_type "
-							  "WHERE oid = '%u'::pg_catalog.oid);",
-							  next_possible_free_oid);
-			res = ExecuteSqlQueryForSingleRow(fout, upgrade_query->data);
-			is_dup = (PQgetvalue(res, 0, 0)[0] == 't');
-			PQclear(res);
-		} while (is_dup);
-
-		pg_type_array_oid = next_possible_free_oid;
-	}
+		pg_type_array_oid = get_next_possible_free_pg_type_oid(fout, upgrade_query);
 
 	if (OidIsValid(pg_type_array_oid))
 	{
@@ -4341,6 +4349,46 @@ binary_upgrade_set_type_oids_by_type_oid(Archive *fout,
 						  pg_type_array_oid);
 	}
 
+	/*
+	 * Pre-set the multirange type oid and its own array type oid.
+	 */
+	if (include_multirange_type)
+	{
+		if (fout->remoteVersion >= 130000)
+		{
+			appendPQExpBuffer(upgrade_query,
+							  "SELECT t.oid, t.typarray "
+							  "FROM pg_catalog.pg_type t "
+							  "JOIN pg_catalog.pg_range r "
+							  "ON t.oid = r.mltrngtypid "
+							  "WHERE r.rngtypid = '%u'::pg_catalog.oid;",
+							  pg_type_oid);
+
+			res = ExecuteSqlQueryForSingleRow(fout, upgrade_query->data);
+
+			pg_type_multirange_oid = atooid(PQgetvalue(res, 0, PQfnumber(res, "oid")));
+			pg_type_multirange_array_oid = atooid(PQgetvalue(res, 0, PQfnumber(res, "typarray")));
+
+			PQclear(res);
+		}
+		else
+		{
+			pg_type_multirange_oid = get_next_possible_free_pg_type_oid(fout, upgrade_query);
+			pg_type_multirange_array_oid = get_next_possible_free_pg_type_oid(fout, upgrade_query);
+		}
+
+		appendPQExpBufferStr(upgrade_buffer,
+							 "\n-- For binary upgrade, must preserve multirange pg_type oid\n");
+		appendPQExpBuffer(upgrade_buffer,
+						  "SELECT pg_catalog.binary_upgrade_set_next_multirange_pg_type_oid('%u'::pg_catalog.oid);\n\n",
+						  pg_type_multirange_oid);
+		appendPQExpBufferStr(upgrade_buffer,
+							 "\n-- For binary upgrade, must preserve multirange pg_type array oid\n");
+		appendPQExpBuffer(upgrade_buffer,
+						  "SELECT pg_catalog.binary_upgrade_set_next_multirange_array_pg_type_oid('%u'::pg_catalog.oid);\n\n",
+						  pg_type_multirange_array_oid);
+	}
+
 	destroyPQExpBuffer(upgrade_query);
 }
 
@@ -4374,7 +4422,7 @@ binary_upgrade_set_type_oids_by_rel_oid(Archive *fout,
 	pg_type_oid = atooid(PQgetvalue(upgrade_res, 0, PQfnumber(upgrade_res, "crel")));
 
 	binary_upgrade_set_type_oids_by_type_oid(fout, upgrade_buffer,
-											 pg_type_oid, false);
+											 pg_type_oid, false, false);
 
 	if (!PQgetisnull(upgrade_res, 0, PQfnumber(upgrade_res, "trel")))
 	{
@@ -4923,6 +4971,11 @@ getTypes(Archive *fout, int *numTypes)
 		else
 			tyinfo[i].isArray = false;
 
+		if (tyinfo[i].typtype == 'm')
+			tyinfo[i].isMultirange = true;
+		else
+			tyinfo[i].isMultirange = false;
+
 		/* Decide whether we want to dump it */
 		selectDumpableType(&tyinfo[i], fout);
 
@@ -10245,7 +10298,7 @@ dumpEnumType(Archive *fout, TypeInfo *tyinfo)
 	if (dopt->binary_upgrade)
 		binary_upgrade_set_type_oids_by_type_oid(fout, q,
 												 tyinfo->dobj.catId.oid,
-												 false);
+												 false, false);
 
 	appendPQExpBuffer(q, "CREATE TYPE %s AS ENUM (",
 					  qualtypname);
@@ -10371,7 +10424,7 @@ dumpRangeType(Archive *fout, TypeInfo *tyinfo)
 	if (dopt->binary_upgrade)
 		binary_upgrade_set_type_oids_by_type_oid(fout, q,
 												 tyinfo->dobj.catId.oid,
-												 false);
+												 false, true);
 
 	appendPQExpBuffer(q, "CREATE TYPE %s AS RANGE (",
 					  qualtypname);
@@ -10477,7 +10530,7 @@ dumpUndefinedType(Archive *fout, TypeInfo *tyinfo)
 	if (dopt->binary_upgrade)
 		binary_upgrade_set_type_oids_by_type_oid(fout, q,
 												 tyinfo->dobj.catId.oid,
-												 false);
+												 false, false);
 
 	appendPQExpBuffer(q, "CREATE TYPE %s;\n",
 					  qualtypname);
@@ -10682,7 +10735,7 @@ dumpBaseType(Archive *fout, TypeInfo *tyinfo)
 	if (dopt->binary_upgrade)
 		binary_upgrade_set_type_oids_by_type_oid(fout, q,
 												 tyinfo->dobj.catId.oid,
-												 false);
+												 false, false);
 
 	appendPQExpBuffer(q,
 					  "CREATE TYPE %s (\n"
@@ -10869,7 +10922,8 @@ dumpDomain(Archive *fout, TypeInfo *tyinfo)
 	if (dopt->binary_upgrade)
 		binary_upgrade_set_type_oids_by_type_oid(fout, q,
 												 tyinfo->dobj.catId.oid,
-												 true); /* force array type */
+												 true,		/* force array type */
+												 false);	/* force multirange type */
 
 	qtypname = pg_strdup(fmtId(tyinfo->dobj.name));
 	qualtypname = pg_strdup(fmtQualifiedDumpable(tyinfo));
@@ -11057,7 +11111,7 @@ dumpCompositeType(Archive *fout, TypeInfo *tyinfo)
 	{
 		binary_upgrade_set_type_oids_by_type_oid(fout, q,
 												 tyinfo->dobj.catId.oid,
-												 false);
+												 false, false);
 		binary_upgrade_set_pg_class_oids(fout, q, tyinfo->typrelid, false);
 	}
 
@@ -11331,7 +11385,7 @@ dumpShellType(Archive *fout, ShellTypeInfo *stinfo)
 	if (dopt->binary_upgrade)
 		binary_upgrade_set_type_oids_by_type_oid(fout, q,
 												 stinfo->baseType->dobj.catId.oid,
-												 false);
+												 false, false);
 
 	appendPQExpBuffer(q, "CREATE TYPE %s;\n",
 					  fmtQualifiedDumpable(stinfo));
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 7b2c1524a5..e27a752b6d 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -175,6 +175,7 @@ typedef struct _typeInfo
 	char		typrelkind;		/* 'r', 'v', 'c', etc */
 	char		typtype;		/* 'b', 'c', etc */
 	bool		isArray;		/* true if auto-generated array type */
+	bool		isMultirange;	/* true if auto-generated multirange type */
 	bool		isDefined;		/* true if typisdefined */
 	/* If needed, we'll create a "shell type" entry for it; link that here: */
 	struct _shellTypeInfo *shellType;	/* shell-type entry, or NULL */
diff --git a/src/include/catalog/binary_upgrade.h b/src/include/catalog/binary_upgrade.h
index 2927b7a4d3..2b6e87bb84 100644
--- a/src/include/catalog/binary_upgrade.h
+++ b/src/include/catalog/binary_upgrade.h
@@ -16,6 +16,8 @@
 
 extern PGDLLIMPORT Oid binary_upgrade_next_pg_type_oid;
 extern PGDLLIMPORT Oid binary_upgrade_next_array_pg_type_oid;
+extern PGDLLIMPORT Oid binary_upgrade_next_multirange_pg_type_oid;
+extern PGDLLIMPORT Oid binary_upgrade_next_multirange_array_pg_type_oid;
 extern PGDLLIMPORT Oid binary_upgrade_next_toast_pg_type_oid;
 
 extern PGDLLIMPORT Oid binary_upgrade_next_heap_pg_class_oid;
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 4311db01ff..5150fb3ea3 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10366,6 +10366,14 @@
   proname => 'binary_upgrade_set_next_array_pg_type_oid', provolatile => 'v',
   proparallel => 'r', prorettype => 'void', proargtypes => 'oid',
   prosrc => 'binary_upgrade_set_next_array_pg_type_oid' },
+{ oid => '8144', descr => 'for use by pg_upgrade',
+  proname => 'binary_upgrade_set_next_multirange_pg_type_oid', provolatile => 'v',
+  proparallel => 'r', prorettype => 'void', proargtypes => 'oid',
+  prosrc => 'binary_upgrade_set_next_multirange_pg_type_oid' },
+{ oid => '8145', descr => 'for use by pg_upgrade',
+  proname => 'binary_upgrade_set_next_multirange_array_pg_type_oid', provolatile => 'v',
+  proparallel => 'r', prorettype => 'void', proargtypes => 'oid',
+  prosrc => 'binary_upgrade_set_next_multirange_array_pg_type_oid' },
 { oid => '3585', descr => 'for use by pg_upgrade',
   proname => 'binary_upgrade_set_next_toast_pg_type_oid', provolatile => 'v',
   proparallel => 'r', prorettype => 'void', proargtypes => 'oid',
diff --git a/src/include/commands/typecmds.h b/src/include/commands/typecmds.h
index 8af0665f29..2b9115b6a0 100644
--- a/src/include/commands/typecmds.h
+++ b/src/include/commands/typecmds.h
@@ -29,6 +29,8 @@ extern ObjectAddress DefineRange(CreateRangeStmt *stmt);
 extern ObjectAddress AlterEnum(AlterEnumStmt *stmt);
 extern ObjectAddress DefineCompositeType(RangeVar *typevar, List *coldeflist);
 extern Oid	AssignTypeArrayOid(void);
+extern Oid	AssignTypeMultirangeOid(void);
+extern Oid	AssignTypeMultirangeArrayOid(void);
 
 extern ObjectAddress AlterDomainDefault(List *names, Node *defaultRaw);
 extern ObjectAddress AlterDomainNotNull(List *names, bool notNull);
-- 
2.20.1

v7-0004-multirange-docs.patchtext/x-diff; charset=us-asciiDownload
From 7bfb960aa885de5834b4a0bdcce57ead7eaf8962 Mon Sep 17 00:00:00 2001
From: "Paul A. Jungwirth" <pj@illuminatedcomputing.com>
Date: Sat, 21 Sep 2019 21:28:47 -0700
Subject: [PATCH v7 04/13] multirange docs

---
 doc/src/sgml/extend.sgml     |  28 +++-
 doc/src/sgml/func.sgml       | 302 +++++++++++++++++++++++++++++++----
 doc/src/sgml/rangetypes.sgml |  47 +++++-
 3 files changed, 342 insertions(+), 35 deletions(-)

diff --git a/doc/src/sgml/extend.sgml b/doc/src/sgml/extend.sgml
index a3046f22d0..7ea2f65778 100644
--- a/doc/src/sgml/extend.sgml
+++ b/doc/src/sgml/extend.sgml
@@ -229,9 +229,9 @@
    </indexterm>
 
     <para>
-     Five pseudo-types of special interest are <type>anyelement</type>,
+     Six pseudo-types of special interest are <type>anyelement</type>,
      <type>anyarray</type>, <type>anynonarray</type>, <type>anyenum</type>,
-     and <type>anyrange</type>,
+     <type>anyrange</type>, and <type>anymultirange</type>.
      which are collectively called <firstterm>polymorphic types</firstterm>.
      Any function declared using these types is said to be
      a <firstterm>polymorphic function</firstterm>.  A polymorphic function can
@@ -250,15 +250,15 @@
      position declared as <type>anyarray</type> can have any array data type,
      but similarly they must all be the same type.  And similarly,
      positions declared as <type>anyrange</type> must all be the same range
-     type.  Furthermore, if there are
+     type.  Likewise for <type>anymultirange</type>.
+    </para>
+
+    <para>
+     Furthermore, if there are
      positions declared <type>anyarray</type> and others declared
      <type>anyelement</type>, the actual array type in the
      <type>anyarray</type> positions must be an array whose elements are
      the same type appearing in the <type>anyelement</type> positions.
-     Similarly, if there are positions declared <type>anyrange</type>
-     and others declared <type>anyelement</type>, the actual range type in
-     the <type>anyrange</type> positions must be a range whose subtype is
-     the same type appearing in the <type>anyelement</type> positions.
      <type>anynonarray</type> is treated exactly the same as <type>anyelement</type>,
      but adds the additional constraint that the actual type must not be
      an array type.
@@ -267,6 +267,20 @@
      be an enum type.
     </para>
 
+    <para>
+     Similarly, if there are positions declared <type>anyrange</type>
+     and others declared <type>anyelement</type>, the actual range type in
+     the <type>anyrange</type> positions must be a range whose subtype is
+     the same type appearing in the <type>anyelement</type> positions.
+     The type <type>anymultirange</type> accepts a multirange
+     whose contents match the other polymorphic types.
+     That is it must hold ranges matching <type>anyrange</type>
+     and base type elements matching <type>anyelement</type> (if either of
+     those are present). If <type>anyarray</type> is present its elements
+     must match the base type of the <type>anyrange</type> and/or the base
+     type of the ranges in an <type>anymultirange</type>.
+    </para>
+
     <para>
      Thus, when more than one argument position is declared with a polymorphic
      type, the net effect is that only certain combinations of actual argument
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 57a1539506..af547a2232 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -14566,7 +14566,9 @@ NULL baz</literallayout>(3 rows)</entry>
 
   <para>
    <xref linkend="range-operators-table"/> shows the operators
-   available for range types.
+   available for range and multirange types.
+   Many of these operators will accept either a range or multirange
+   on either side.
   </para>
 
     <table id="range-operators-table">
@@ -14576,7 +14578,7 @@ NULL baz</literallayout>(3 rows)</entry>
        <row>
         <entry>Operator</entry>
         <entry>Description</entry>
-        <entry>Example</entry>
+        <entry>Examples</entry>
         <entry>Result</entry>
        </row>
       </thead>
@@ -14584,136 +14586,247 @@ NULL baz</literallayout>(3 rows)</entry>
        <row>
         <entry> <literal>=</literal> </entry>
         <entry>equal</entry>
-        <entry><literal>int4range(1,5) = '[1,4]'::int4range</literal></entry>
+        <entry><literallayout class="monospaced">int4range(1,5) = '[1,4]'::int4range
+'{[1,5)}'::int4multirange = '{[1,4]}'::int4multirange</literallayout></entry>
         <entry><literal>t</literal></entry>
        </row>
 
        <row>
         <entry> <literal>&lt;&gt;</literal> </entry>
         <entry>not equal</entry>
-        <entry><literal>numrange(1.1,2.2) &lt;&gt; numrange(1.1,2.3)</literal></entry>
+        <entry><literallayout class="monospaced">numrange(1.1,2.2) &lt;&gt; numrange(1.1,2.3)
+'{[1.1,2.2)}'::nummultirange &lt;&gt; '{[1.1,2.3)}'::nummultirange</literallayout></entry>
         <entry><literal>t</literal></entry>
        </row>
 
        <row>
         <entry> <literal>&lt;</literal> </entry>
         <entry>less than</entry>
-        <entry><literal>int4range(1,10) &lt; int4range(2,3)</literal></entry>
+        <entry><literallayout class="monospaced">int4range(1,10) &lt; int4range(2,3)
+'{[1,10)}'::int4multirange &lt; '{[2,3)}'::int4multirange</literallayout>
+        </entry>
         <entry><literal>t</literal></entry>
        </row>
 
        <row>
         <entry> <literal>&gt;</literal> </entry>
         <entry>greater than</entry>
-        <entry><literal>int4range(1,10) &gt; int4range(1,5)</literal></entry>
+        <entry>
+          <literallayout class="monospaced">int4range(1,10) &gt; int4range(1,5)
+'{[1,10)}'::int4multirange &gt; '{[1,5)}'::int4multirange</literallayout>
+        </entry>
         <entry><literal>t</literal></entry>
        </row>
 
        <row>
         <entry> <literal>&lt;=</literal> </entry>
         <entry>less than or equal</entry>
-        <entry><literal>numrange(1.1,2.2) &lt;= numrange(1.1,2.2)</literal></entry>
+        <entry>
+          <literallayout class="monospaced">numrange(1.1,2.2) &lt;= numrange(1.1,2.2)
+'{[1.1,2.2)}'::nummultirange &lt;= '{[1.1,2.2)}'::nummultirange</literallayout>
+        </entry>
         <entry><literal>t</literal></entry>
        </row>
 
        <row>
         <entry> <literal>&gt;=</literal> </entry>
         <entry>greater than or equal</entry>
-        <entry><literal>numrange(1.1,2.2) &gt;= numrange(1.1,2.0)</literal></entry>
+        <entry>
+          <literallayout class="monospaced">numrange(1.1,2.2) &gt;= numrange(1.1,2.0)
+'{[1.1,2.2)}'::nummultirange &gt;= '{[1.1,2.0)}'::nummultirange</literallayout>
+        </entry>
+        <entry><literal>t</literal></entry>
+       </row>
+
+       <row>
+        <entry> <literal>@&gt;</literal> </entry>
+        <entry>contains multirange</entry>
+        <entry>
+          <literallayout class="monospaced">int4range(2,4) @&gt; '{[2,3)}'::int4multirange
+'{[2,4)}'::int4multirange @&gt; '{[2,3)}'::int4multirange</literallayout>
+        </entry>
         <entry><literal>t</literal></entry>
        </row>
 
        <row>
         <entry> <literal>@&gt;</literal> </entry>
         <entry>contains range</entry>
-        <entry><literal>int4range(2,4) @&gt; int4range(2,3)</literal></entry>
+        <entry>
+          <literallayout class="monospaced">int4range(2,4) @&gt; int4range(2,3)
+'{[2,4)}'::int4multirange @&gt; int4range(2,3)</literallayout>
+        </entry>
         <entry><literal>t</literal></entry>
        </row>
 
        <row>
         <entry> <literal>@&gt;</literal> </entry>
         <entry>contains element</entry>
-        <entry><literal>'[2011-01-01,2011-03-01)'::tsrange @&gt; '2011-01-10'::timestamp</literal></entry>
+        <entry>
+          <literallayout class="monospaced">'[2011-01-01,2011-03-01)'::tsrange @&gt; '2011-01-10'::timestamp
+'{[2011-01-01,2011-03-01)}'::tsmultirange @&gt; '2011-01-10'::timestamp</literallayout>
+        </entry>
+        <entry><literal>t</literal></entry>
+       </row>
+
+       <row>
+        <entry> <literal>&lt;@</literal> </entry>
+        <entry>multirange is contained by</entry>
+        <entry>
+          <literallayout class="monospaced">'{[2,4)}'::int4multirange &lt;@ int4range(1,7)
+'{[2,4)}'::int4multirange &lt;@ '{[1,7)}'::int4multirange</literallayout>
+        </entry>
         <entry><literal>t</literal></entry>
        </row>
 
        <row>
         <entry> <literal>&lt;@</literal> </entry>
         <entry>range is contained by</entry>
-        <entry><literal>int4range(2,4) &lt;@ int4range(1,7)</literal></entry>
+        <entry>
+          <literallayout class="monospaced">int4range(2,4) &lt;@ int4range(1,7)
+int4range(2,4) &lt;@ '{[1,7)}'::int4multirange</literallayout>
+        </entry>
         <entry><literal>t</literal></entry>
        </row>
 
        <row>
         <entry> <literal>&lt;@</literal> </entry>
         <entry>element is contained by</entry>
-        <entry><literal>42 &lt;@ int4range(1,7)</literal></entry>
+        <entry>
+          <literallayout class="monospaced">42 &lt;@ int4range(1,7)
+42 &lt;@ '{[1,7)}'::int4multirange</literallayout>
+        </entry>
         <entry><literal>f</literal></entry>
        </row>
 
        <row>
         <entry> <literal>&amp;&amp;</literal> </entry>
         <entry>overlap (have points in common)</entry>
-        <entry><literal>int8range(3,7) &amp;&amp; int8range(4,12)</literal></entry>
+        <entry>
+          <literallayout class="monospaced">int8range(3,7) &amp;&amp; int8range(4,12)
+int8range(3,7) &amp;&amp; '{[4,12)}'::int8multirange
+'{[3,7)}'::int8multirange &amp;&amp; int8range(4,12)
+'{[3,7)}'::int8multirange &amp;&amp; '{[4,12)}'::int8multirange</literallayout>
+        </entry>
         <entry><literal>t</literal></entry>
        </row>
 
        <row>
         <entry> <literal>&lt;&lt;</literal> </entry>
         <entry>strictly left of</entry>
-        <entry><literal>int8range(1,10) &lt;&lt; int8range(100,110)</literal></entry>
+        <entry>
+          <literallayout class="monospaced">int8range(1,10) &lt;&lt; int8range(100,110)
+int8range(1,10) &lt;&lt; '{[100,110)}'::int8multirange
+'{[1,10)}'::int8multirange &lt;&lt; int8range(100,110)
+'{[1,10)}'::int8multirange &lt;&lt; '{[100,110)}'::int8multirange</literallayout>
+        </entry>
         <entry><literal>t</literal></entry>
        </row>
 
        <row>
         <entry> <literal>&gt;&gt;</literal> </entry>
         <entry>strictly right of</entry>
-        <entry><literal>int8range(50,60) &gt;&gt; int8range(20,30)</literal></entry>
+        <entry>
+          <literallayout class="monospaced">int8range(50,60) &gt;&gt; int8range(20,30)
+int8range(50,60) &gt;&gt; '{[20,30)}'::int8multirange
+'{[50,60)}'::int8multirange &gt;&gt; int8range(20,30)
+'{[50,60)}'::int8multirange &gt;&gt; '{[20,30)}'::int8multirange</literallayout>
+        </entry>
         <entry><literal>t</literal></entry>
        </row>
 
        <row>
         <entry> <literal>&amp;&lt;</literal> </entry>
         <entry>does not extend to the right of</entry>
-        <entry><literal>int8range(1,20) &amp;&lt; int8range(18,20)</literal></entry>
+        <entry>
+          <literallayout class="monospaced">int8range(1,20) &amp;&lt; int8range(18,20)
+int8range(1,20) &amp;&lt; '{[18,20)}'::int8multirange
+'{[1,20)}'::int8multirange &amp;&lt; int8range(18,20)
+'{[1,20)}'::int8multirange &amp;&lt; '{[18,20)}'::int8multirange</literallayout>
+        </entry>
         <entry><literal>t</literal></entry>
        </row>
 
        <row>
         <entry> <literal>&amp;&gt;</literal> </entry>
         <entry>does not extend to the left of</entry>
-        <entry><literal>int8range(7,20) &amp;&gt; int8range(5,10)</literal></entry>
+        <entry>
+          <literallayout class="monospaced">int8range(7,20) &amp;&gt; int8range(5,10)
+int8range(7,20) &amp;&gt; '{[5,10)}'::int8multirange
+'{[7,20)}'::int8multirange &amp;&gt; int8range(5,10)
+'{[7,20)}'::int8multirange &amp;&gt; '{[5,10)}'::int8multirange</literallayout>
+        </entry>
         <entry><literal>t</literal></entry>
        </row>
 
        <row>
         <entry> <literal>-|-</literal> </entry>
         <entry>is adjacent to</entry>
-        <entry><literal>numrange(1.1,2.2) -|- numrange(2.2,3.3)</literal></entry>
+        <entry>
+          <literallayout class="monospaced">numrange(1.1,2.2) -|- numrange(2.2,3.3)
+numrange(1.1,2.2) -|- '{[2.2,3.3)}'::nummultirange
+'{[1.1,2.2)}'::nummultirange -|- numrange(2.2,3.3)
+'{[1.1,2.2)}'::nummultirange -|- '{[2.2,3.3)}'::nummultirange</literallayout>
+        </entry>
         <entry><literal>t</literal></entry>
        </row>
 
        <row>
         <entry> <literal>+</literal> </entry>
         <entry>union</entry>
-        <entry><literal>numrange(5,15) + numrange(10,20)</literal></entry>
+        <entry><literallayout class="monospaced">numrange(5,15) + numrange(10,20)</literallayout></entry>
         <entry><literal>[5,20)</literal></entry>
        </row>
 
+       <row>
+        <entry> <literal>@+</literal> </entry>
+        <entry>safe union</entry>
+        <entry>
+          <literallayout class="monospaced">numrange(5,10) @+ numrange(15,20)
+numrange(5,10) @+ '{[15,20)}'::nummultirange
+'{[5,10)}'::nummultirange @+ numrange(15,20)
+'{[5,10)}'::nummultirange @+ '{[15,20)}'::nummultirange</literallayout>
+        </entry>
+        <entry><literal>{[5,10), [15,20)}</literal></entry>
+       </row>
+
        <row>
         <entry> <literal>*</literal> </entry>
         <entry>intersection</entry>
-        <entry><literal>int8range(5,15) * int8range(10,20)</literal></entry>
+        <entry><literallayout class="monospaced">int8range(5,15) * int8range(10,20)</literallayout></entry>
         <entry><literal>[10,15)</literal></entry>
        </row>
 
+       <row>
+        <entry> <literal>@*</literal> </entry>
+        <entry>safe intersection</entry>
+        <entry>
+          <literallayout class="monospaced">int8range(5,15) @* int8range(10,20)
+int8range(5,15) @* '{[10,20)}'::int8multirange
+'{[5,15)}'::int8multirange @* int8range(10,20)
+'{[5,15)}'::int8multirange @* '{[10,20)}'::int8multirange</literallayout>
+        </entry>
+        <entry><literal>{[10,15)}</literal></entry>
+       </row>
+
        <row>
         <entry> <literal>-</literal> </entry>
         <entry>difference</entry>
-        <entry><literal>int8range(5,15) - int8range(10,20)</literal></entry>
+        <entry><literallayout class="monospaced">int8range(5,15) - int8range(10,20)</literallayout></entry>
         <entry><literal>[5,10)</literal></entry>
        </row>
 
+       <row>
+        <entry> <literal>@-</literal> </entry>
+        <entry>safe difference</entry>
+        <entry>
+          <literallayout class="monospaced">int8range(5,20) @- int8range(10,15)
+int8range(5,20) @- '{[10,15)}'::int8multirange
+'{[5,20)}'::int8multirange @- int8range(10,15)
+'{[5,20)}'::int8multirange @- '{[10,15)}'::int8multirange</literallayout>
+        </entry>
+        <entry><literal>{[5,10), [15,20)}</literal></entry>
+       </row>
+
       </tbody>
      </tgroup>
     </table>
@@ -14729,19 +14842,28 @@ NULL baz</literallayout>(3 rows)</entry>
 
   <para>
    The left-of/right-of/adjacent operators always return false when an empty
-   range is involved; that is, an empty range is not considered to be either
-   before or after any other range.
+   range or multirange is involved; that is, an empty range is not considered to
+   be either before or after any other range.
+  </para>
+
+  <para>
+   Elsewhere empty ranges and multiranges are treated as the additive identity:
+   anything unioned with an empty value is itself. Anything minus an empty
+   value is itself. An empty multirange has exactly the same points as an empty
+   range. Every range contains the empty range. Every multirange contains as many
+   empty ranges as you like.
   </para>
 
   <para>
    The union and difference operators will fail if the resulting range would
    need to contain two disjoint sub-ranges, as such a range cannot be
-   represented.
+   represented. The safe versions of union, intersection, and difference
+   return multiranges, so they can handle gaps without failing.
   </para>
 
   <para>
    <xref linkend="range-functions-table"/> shows the functions
-   available for use with range types.
+   available for use with range and multirange types.
   </para>
 
   <indexterm>
@@ -14790,6 +14912,17 @@ NULL baz</literallayout>(3 rows)</entry>
         <entry><literal>lower(numrange(1.1,2.2))</literal></entry>
         <entry><literal>1.1</literal></entry>
        </row>
+       <row>
+        <entry>
+         <literal>
+          <function>lower</function>(<type>anymultirange</type>)
+         </literal>
+        </entry>
+        <entry>multirange's element type</entry>
+        <entry>lower bound of multirange</entry>
+        <entry><literal>lower('{[1.1,2.2)}'::nummultirange)</literal></entry>
+        <entry><literal>1.1</literal></entry>
+       </row>
        <row>
         <entry>
          <literal>
@@ -14801,6 +14934,17 @@ NULL baz</literallayout>(3 rows)</entry>
         <entry><literal>upper(numrange(1.1,2.2))</literal></entry>
         <entry><literal>2.2</literal></entry>
        </row>
+       <row>
+        <entry>
+         <literal>
+          <function>upper</function>(<type>anymultirange</type>)
+         </literal>
+        </entry>
+        <entry>multirange's element type</entry>
+        <entry>upper bound of multirange</entry>
+        <entry><literal>upper('{[1.1,2.2)}'::nummultirange)</literal></entry>
+        <entry><literal>2.2</literal></entry>
+       </row>
        <row>
         <entry>
          <literal>
@@ -14812,6 +14956,17 @@ NULL baz</literallayout>(3 rows)</entry>
         <entry><literal>isempty(numrange(1.1,2.2))</literal></entry>
         <entry><literal>false</literal></entry>
        </row>
+       <row>
+        <entry>
+         <literal>
+          <function>isempty</function>(<type>anymultirange</type>)
+         </literal>
+        </entry>
+        <entry><type>boolean</type></entry>
+        <entry>is the multirange empty?</entry>
+        <entry><literal>isempty('{[1.1,2.2)}'::nummultirange)</literal></entry>
+        <entry><literal>false</literal></entry>
+       </row>
        <row>
         <entry>
          <literal>
@@ -14823,6 +14978,17 @@ NULL baz</literallayout>(3 rows)</entry>
         <entry><literal>lower_inc(numrange(1.1,2.2))</literal></entry>
         <entry><literal>true</literal></entry>
        </row>
+       <row>
+        <entry>
+         <literal>
+          <function>lower_inc</function>(<type>anymultirange</type>)
+         </literal>
+        </entry>
+        <entry><type>boolean</type></entry>
+        <entry>is the lower bound inclusive?</entry>
+        <entry><literal>lower_inc('{[1.1,2.2)}'::nummultirange)</literal></entry>
+        <entry><literal>true</literal></entry>
+       </row>
        <row>
         <entry>
          <literal>
@@ -14834,6 +15000,17 @@ NULL baz</literallayout>(3 rows)</entry>
         <entry><literal>upper_inc(numrange(1.1,2.2))</literal></entry>
         <entry><literal>false</literal></entry>
        </row>
+       <row>
+        <entry>
+         <literal>
+          <function>upper_inc</function>(<type>anymultirange</type>)
+         </literal>
+        </entry>
+        <entry><type>boolean</type></entry>
+        <entry>is the upper bound inclusive?</entry>
+        <entry><literal>upper_inc('{[1.1,2.2)}'::nummultirange)</literal></entry>
+        <entry><literal>false</literal></entry>
+       </row>
        <row>
         <entry>
          <literal>
@@ -14845,6 +15022,17 @@ NULL baz</literallayout>(3 rows)</entry>
         <entry><literal>lower_inf('(,)'::daterange)</literal></entry>
         <entry><literal>true</literal></entry>
        </row>
+       <row>
+        <entry>
+         <literal>
+          <function>lower_inf</function>(<type>anymultirange</type>)
+         </literal>
+        </entry>
+        <entry><type>boolean</type></entry>
+        <entry>is the lower bound infinite?</entry>
+        <entry><literal>lower_inf('{(,)}'::datemultirange)</literal></entry>
+        <entry><literal>true</literal></entry>
+       </row>
        <row>
         <entry>
          <literal>
@@ -14856,6 +15044,17 @@ NULL baz</literallayout>(3 rows)</entry>
         <entry><literal>upper_inf('(,)'::daterange)</literal></entry>
         <entry><literal>true</literal></entry>
        </row>
+       <row>
+        <entry>
+         <literal>
+          <function>upper_inf</function>(<type>anymultirange</type>)
+         </literal>
+        </entry>
+        <entry><type>boolean</type></entry>
+        <entry>is the upper bound infinite?</entry>
+        <entry><literal>upper_inf('{(,)}'::datemultirange)</literal></entry>
+        <entry><literal>true</literal></entry>
+       </row>
        <row>
         <entry>
          <literal>
@@ -14867,16 +15066,27 @@ NULL baz</literallayout>(3 rows)</entry>
         <entry><literal>range_merge('[1,2)'::int4range, '[3,4)'::int4range)</literal></entry>
         <entry><literal>[1,4)</literal></entry>
        </row>
+       <row>
+        <entry>
+         <literal>
+          <function>range_merge</function>(<type>anymultirange</type>)
+         </literal>
+        </entry>
+        <entry><type>anyrange</type></entry>
+        <entry>the smallest range which includes the entire multirange</entry>
+        <entry><literal>range_merge('{[1,2), [3,4)}'::int4multirange)</literal></entry>
+        <entry><literal>[1,4)</literal></entry>
+       </row>
       </tbody>
      </tgroup>
     </table>
 
   <para>
    The <function>lower</function> and  <function>upper</function> functions return null
-   if the range is empty or the requested bound is infinite.
+   if the input range/multirange is empty or the requested bound is infinite.
    The <function>lower_inc</function>, <function>upper_inc</function>,
    <function>lower_inf</function>, and <function>upper_inf</function>
-   functions all return false for an empty range.
+   functions all return false for an empty range or multirange.
   </para>
   </sect1>
 
@@ -15195,6 +15405,44 @@ NULL baz</literallayout>(3 rows)</entry>
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <indexterm>
+        <primary>range_agg</primary>
+       </indexterm>
+       <function>
+         range_agg(<replaceable class="parameter">expression</replaceable>)
+       </function>
+      </entry>
+      <entry>
+       <type>anyrange</type>
+      </entry>
+      <entry>
+       <type>multirange</type> of the argument range type
+      </entry>
+      <entry>Yes</entry>
+      <entry>union of the non-null input values</entry>
+     </row>
+
+     <row>
+      <entry>
+       <indexterm>
+        <primary>range_intersect_agg</primary>
+       </indexterm>
+       <function>
+         range_intersect_agg(<replaceable class="parameter">expression</replaceable>)
+       </function>
+      </entry>
+      <entry>
+       <type>anyrange</type>
+      </entry>
+      <entry>
+       <type>multirange</type> of the argument range type
+      </entry>
+      <entry>No</entry>
+      <entry>intersection of the non-null input values</entry>
+     </row>
+
      <row>
       <entry>
        <indexterm>
diff --git a/doc/src/sgml/rangetypes.sgml b/doc/src/sgml/rangetypes.sgml
index 3a034d9b06..3e37b352fc 100644
--- a/doc/src/sgml/rangetypes.sgml
+++ b/doc/src/sgml/rangetypes.sgml
@@ -27,6 +27,13 @@
   ranges from an instrument, and so forth can also be useful.
  </para>
 
+ <para>
+  Every range type has a corresponding multirange type. A multirange is
+  an ordered list of non-continguous, non-empty, non-null ranges. Most
+  range operators also work on multiranges, and they have a few functions
+  of their own.
+ </para>
+
  <sect2 id="rangetypes-builtin">
   <title>Built-in Range Types</title>
 
@@ -235,10 +242,30 @@ SELECT '[4,4]'::int4range;
 SELECT '[4,4)'::int4range;
 </programlisting>
   </para>
+
+  <para>
+   The input for a multirange is curly brackets (<literal>{</literal> and
+   <literal>}</literal>) containing zero or more valid ranges,
+   separated by commas. Whitespace is permitted around the brackets and
+   commas. This is intended to be reminiscent of array syntax, although
+   multiranges are much simpler: they have just one dimension and there is
+   no need to quote their contents. (The bounds of their ranges may be
+   quoted as above however.)
+  </para>
+
+  <para>
+  Examples:
+<programlisting>
+SELECT '{}'::int4multirange;
+SELECT '{[3,7)}'::int4multirange;
+SELECT '{[3,7), [8,9)}'::int4multirange;
+</programlisting>
+  </para>
+
  </sect2>
 
  <sect2 id="rangetypes-construct">
-  <title>Constructing Ranges</title>
+  <title>Constructing Ranges and Multiranges</title>
 
   <para>
    Each range type has a constructor function with the same name as the range
@@ -270,6 +297,19 @@ SELECT int8range(1, 14, '(]');
 
 -- Using NULL for either bound causes the range to be unbounded on that side.
 SELECT numrange(NULL, 2.2);
+</programlisting>
+  </para>
+
+  <para>
+   Each range type also has a multirange constructor with the same name as the
+   multirange type.  The constructor function takes zero or more arguments
+   which are all ranges of the appropriate type.
+   For example:
+
+<programlisting>
+SELECT nummultirange();
+SELECT nummultirange(numrange(1.0, 14.0));
+SELECT nummultirange(numrange(1.0, 14.0), numrange(20.0, 25.0));
 </programlisting>
   </para>
  </sect2>
@@ -344,6 +384,11 @@ SELECT '[1.234, 5.678]'::floatrange;
    function in this example.
   </para>
 
+  <para>
+   When you define your own range your automatically get a corresponding
+   multirange type.
+  </para>
+
   <para>
    Defining your own range type also allows you to specify a different
    subtype B-tree operator class or collation to use, so as to change the sort
-- 
2.20.1

v7-0005-Simplify-makeMultirangeConstructors.patchtext/x-diff; charset=us-asciiDownload
From 4a1189b23402f1404d429c9611851e2e5b7ce352 Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Thu, 28 Nov 2019 19:08:43 -0300
Subject: [PATCH v7 05/13] Simplify makeMultirangeConstructors

---
 src/backend/commands/typecmds.c | 134 +++++++++++++++++++-------------
 1 file changed, 80 insertions(+), 54 deletions(-)

diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index 38948a049b..1b012c9cad 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -1784,70 +1784,96 @@ 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 = rangeArrayOid;
 	ObjectAddress myself,
 				referenced;
-	int			i;
-
-	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)};
+	oidvector  *argtypes;
+	Datum		allParamTypes;
+	ArrayType  *allParameterTypes;
+	Datum		paramModes;
+	ArrayType  *parameterModes;
 
 	referenced.classId = TypeRelationId;
 	referenced.objectId = multirangeOid;
 	referenced.objectSubId = 0;
 
-	for (i = 0; i < lengthof(prosrc); i++)
-	{
-		oidvector  *constructorArgTypesVector;
+	argtypes = buildoidvector(NULL, 0);
+	myself = ProcedureCreate(name,	/* name: same as multirange type */
+							 namespace,
+							 false, /* replace */
+							 false, /* returns set */
+							 multirangeOid, /* return type */
+							 BOOTSTRAP_SUPERUSERID, /* proowner */
+							 INTERNALlanguageId,	/* language */
+							 F_FMGR_INTERNAL_VALIDATOR,
+							 "multirange_constructor0", /* prosrc */
+							 NULL,	/* probin */
+							 PROKIND_FUNCTION,
+							 false, /* security_definer */
+							 false, /* leakproof */
+							 false, /* isStrict */
+							 PROVOLATILE_IMMUTABLE, /* volatility */
+							 PROPARALLEL_SAFE,	/* parallel safety */
+							 argtypes,	/* parameterTypes */
+							 PointerGetDatum(NULL), /* allParameterTypes */
+							 PointerGetDatum(NULL), /* parameterModes */
+							 PointerGetDatum(NULL), /* parameterNames */
+							 NIL,	/* parameterDefaults */
+							 PointerGetDatum(NULL), /* trftypes */
+							 PointerGetDatum(NULL), /* proconfig */
+							 InvalidOid,	/* prosupport */
+							 1.0,	/* procost */
+							 0.0);	/* prorows */
 
-		constructorArgTypesVector = buildoidvector(&constructorArgTypes,
-												   pronargs[i]);
+	/*
+	 * Make the constructor 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);
 
-		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 */
+	pfree(argtypes);
 
-		/*
-		 * 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);
-	}
+	argtypes = buildoidvector(&rangeArrayOid, 1);
+	allParamTypes = ObjectIdGetDatum(rangeArrayOid);
+	allParameterTypes = construct_array(&allParamTypes,
+										1, OIDOID,
+										sizeof(Oid), true, 'i');
+	paramModes = CharGetDatum(FUNC_PARAM_VARIADIC);
+	parameterModes = construct_array(&paramModes, 1, CHAROID,
+									 1, true, 'c');
+
+	myself = ProcedureCreate(name,	/* name: same as multirange type */
+							 namespace,
+							 false, /* replace */
+							 false, /* returns set */
+							 multirangeOid, /* return type */
+							 BOOTSTRAP_SUPERUSERID, /* proowner */
+							 INTERNALlanguageId,	/* language */
+							 F_FMGR_INTERNAL_VALIDATOR,
+							 "multirange_constructor1", /* prosrc */
+							 NULL,	/* probin */
+							 PROKIND_FUNCTION,
+							 false, /* security_definer */
+							 false, /* leakproof */
+							 false, /* isStrict */
+							 PROVOLATILE_IMMUTABLE, /* volatility */
+							 PROPARALLEL_SAFE,	/* parallel safety */
+							 argtypes,	/* parameterTypes */
+							 PointerGetDatum(allParameterTypes),	/* allParameterTypes */
+							 PointerGetDatum(parameterModes),	/* parameterModes */
+							 PointerGetDatum(NULL), /* parameterNames */
+							 NIL,	/* parameterDefaults */
+							 PointerGetDatum(NULL), /* trftypes */
+							 PointerGetDatum(NULL), /* proconfig */
+							 InvalidOid,	/* prosupport */
+							 1.0,	/* procost */
+							 0.0);	/* prorows */
+	/* ditto */
+	recordDependencyOn(&myself, &referenced, DEPENDENCY_INTERNAL);
+
+	pfree(argtypes);
+	pfree(allParameterTypes);
+	pfree(parameterModes);
 }
 
 /*
-- 
2.20.1

v7-0006-silence-compiler-warning.patchtext/x-diff; charset=us-asciiDownload
From 0f411ccb39adc38ffa51037d04c818726aee834e Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Thu, 28 Nov 2019 19:08:46 -0300
Subject: [PATCH v7 06/13] silence compiler warning

---
 src/backend/parser/parse_coerce.c | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/src/backend/parser/parse_coerce.c b/src/backend/parser/parse_coerce.c
index 49c75dc12d..b43b2dc612 100644
--- a/src/backend/parser/parse_coerce.c
+++ b/src/backend/parser/parse_coerce.c
@@ -1592,6 +1592,8 @@ check_generic_type_consistency(const Oid *actual_arg_types,
 			return false;
 		}
 	}
+	else
+		range_typelem = InvalidOid; /* keep compiler quiet */
 
 	/* Get the element type based on the multirange type, if we have one */
 	if (OidIsValid(multirange_typeid))
-- 
2.20.1

v7-0007-Be-less-verbose-on-variable-names.patchtext/x-diff; charset=us-asciiDownload
From 7837930d4c44922fe30031c13b5a425d66ce5e6c Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Thu, 28 Nov 2019 19:30:03 -0300
Subject: [PATCH v7 07/13] Be less verbose on variable names

---
 src/backend/commands/typecmds.c            | 23 +++++++++-------------
 src/backend/utils/adt/pg_upgrade_support.c |  4 ++--
 src/include/catalog/binary_upgrade.h       |  4 ++--
 3 files changed, 13 insertions(+), 18 deletions(-)

diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index 1b012c9cad..4ca2d3364b 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -85,8 +85,8 @@ typedef struct
 
 /* Potentially set by pg_upgrade_support functions */
 Oid			binary_upgrade_next_array_pg_type_oid = InvalidOid;
-Oid			binary_upgrade_next_multirange_pg_type_oid = InvalidOid;
-Oid			binary_upgrade_next_multirange_array_pg_type_oid = InvalidOid;
+Oid			binary_upgrade_next_mrng_pg_type_oid = InvalidOid;
+Oid			binary_upgrade_next_mrng_array_pg_type_oid = InvalidOid;
 
 static void makeRangeConstructors(const char *name, Oid namespace,
 								  Oid rangeOid, Oid subtype);
@@ -1529,13 +1529,9 @@ DefineRange(CreateRangeStmt *stmt)
 	/* alignment must be 'i' or 'd' for ranges */
 	alignment = (subtypalign == 'd') ? 'd' : 'i';
 
-	/* Allocate OID for array type */
+	/* Allocate OID for array type, its multirange and its multirange array */
 	rangeArrayOid = AssignTypeArrayOid();
-
-	/* Allocate OID for multirange type */
 	multirangeOid = AssignTypeMultirangeOid();
-
-	/* Allocate OID for multirange array type */
 	multirangeArrayOid = AssignTypeMultirangeArrayOid();
 
 	/* Create the pg_type entry */
@@ -1574,7 +1570,6 @@ DefineRange(CreateRangeStmt *stmt)
 	Assert(typoid == address.objectId);
 
 	/* Create the multirange that goes with it */
-
 	multirangeTypeName = makeMultirangeTypeName(typeName, typeNamespace);
 
 	mltrngaddress =
@@ -2313,13 +2308,13 @@ AssignTypeMultirangeOid(void)
 	/* Use binary-upgrade override for pg_type.oid? */
 	if (IsBinaryUpgrade)
 	{
-		if (!OidIsValid(binary_upgrade_next_multirange_pg_type_oid))
+		if (!OidIsValid(binary_upgrade_next_mrng_pg_type_oid))
 			ereport(ERROR,
 					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 					 errmsg("pg_type multirange OID value not set when in binary upgrade mode")));
 
-		type_multirange_oid = binary_upgrade_next_multirange_pg_type_oid;
-		binary_upgrade_next_multirange_pg_type_oid = InvalidOid;
+		type_multirange_oid = binary_upgrade_next_mrng_pg_type_oid;
+		binary_upgrade_next_mrng_pg_type_oid = InvalidOid;
 	}
 	else
 	{
@@ -2346,13 +2341,13 @@ AssignTypeMultirangeArrayOid(void)
 	/* Use binary-upgrade override for pg_type.oid? */
 	if (IsBinaryUpgrade)
 	{
-		if (!OidIsValid(binary_upgrade_next_multirange_array_pg_type_oid))
+		if (!OidIsValid(binary_upgrade_next_mrng_array_pg_type_oid))
 			ereport(ERROR,
 					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 					 errmsg("pg_type multirange array OID value not set when in binary upgrade mode")));
 
-		type_multirange_array_oid = binary_upgrade_next_multirange_array_pg_type_oid;
-		binary_upgrade_next_multirange_array_pg_type_oid = InvalidOid;
+		type_multirange_array_oid = binary_upgrade_next_mrng_array_pg_type_oid;
+		binary_upgrade_next_mrng_array_pg_type_oid = InvalidOid;
 	}
 	else
 	{
diff --git a/src/backend/utils/adt/pg_upgrade_support.c b/src/backend/utils/adt/pg_upgrade_support.c
index d980b96f48..418c26c81b 100644
--- a/src/backend/utils/adt/pg_upgrade_support.c
+++ b/src/backend/utils/adt/pg_upgrade_support.c
@@ -57,7 +57,7 @@ binary_upgrade_set_next_multirange_pg_type_oid(PG_FUNCTION_ARGS)
 	Oid			typoid = PG_GETARG_OID(0);
 
 	CHECK_IS_BINARY_UPGRADE;
-	binary_upgrade_next_multirange_pg_type_oid = typoid;
+	binary_upgrade_next_mrng_pg_type_oid = typoid;
 
 	PG_RETURN_VOID();
 }
@@ -68,7 +68,7 @@ binary_upgrade_set_next_multirange_array_pg_type_oid(PG_FUNCTION_ARGS)
 	Oid			typoid = PG_GETARG_OID(0);
 
 	CHECK_IS_BINARY_UPGRADE;
-	binary_upgrade_next_multirange_array_pg_type_oid = typoid;
+	binary_upgrade_next_mrng_array_pg_type_oid = typoid;
 
 	PG_RETURN_VOID();
 }
diff --git a/src/include/catalog/binary_upgrade.h b/src/include/catalog/binary_upgrade.h
index 2b6e87bb84..ba132ddf23 100644
--- a/src/include/catalog/binary_upgrade.h
+++ b/src/include/catalog/binary_upgrade.h
@@ -16,8 +16,8 @@
 
 extern PGDLLIMPORT Oid binary_upgrade_next_pg_type_oid;
 extern PGDLLIMPORT Oid binary_upgrade_next_array_pg_type_oid;
-extern PGDLLIMPORT Oid binary_upgrade_next_multirange_pg_type_oid;
-extern PGDLLIMPORT Oid binary_upgrade_next_multirange_array_pg_type_oid;
+extern PGDLLIMPORT Oid binary_upgrade_next_mrng_pg_type_oid;
+extern PGDLLIMPORT Oid binary_upgrade_next_mrng_array_pg_type_oid;
 extern PGDLLIMPORT Oid binary_upgrade_next_toast_pg_type_oid;
 
 extern PGDLLIMPORT Oid binary_upgrade_next_heap_pg_class_oid;
-- 
2.20.1

v7-0008-Protect-comment-against-pgindent.patchtext/x-diff; charset=us-asciiDownload
From b162aa439321b717fcc390edc5b024e37659fcbb Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Thu, 28 Nov 2019 19:36:53 -0300
Subject: [PATCH v7 08/13] Protect comment against pgindent

---
 src/backend/utils/adt/multirangetypes.c | 7 ++++---
 1 file changed, 4 insertions(+), 3 deletions(-)

diff --git a/src/backend/utils/adt/multirangetypes.c b/src/backend/utils/adt/multirangetypes.c
index 7717acb3b1..b8ff39077c 100644
--- a/src/backend/utils/adt/multirangetypes.c
+++ b/src/backend/utils/adt/multirangetypes.c
@@ -1136,15 +1136,16 @@ multirange_intersect_multirange_internal(Oid mltrngtypoid, TypeCacheEntry *range
 	if (range_count1 == 0 || range_count2 == 0)
 		return make_multirange(mltrngtypoid, rangetyp, 0, NULL);
 
-	/*
+	/*-----------------------------------------------
 	 * Worst case is a stitching pattern like this:
 	 *
 	 * mr1: --- --- --- ---
 	 * mr2:   --- --- ---
 	 * mr3:   - - - - - -
 	 *
-	 * That seems to be range_count1 + range_count2 - 1, but one extra won't
-	 * hurt.
+	 * That seems to be range_count1 + range_count2 - 1,
+	 * but one extra won't hurt.
+	 *-----------------------------------------------
 	 */
 	ranges3 = palloc0((range_count1 + range_count2) * sizeof(RangeType *));
 	range_count3 = 0;
-- 
2.20.1

v7-0009-pgindent.patchtext/x-diff; charset=us-asciiDownload
From 0223030d278ac74dc3cc95f0fff32034a40d903e Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Thu, 28 Nov 2019 19:35:43 -0300
Subject: [PATCH v7 09/13] pgindent

---
 src/backend/utils/adt/multirangetypes.c | 14 +++++++-------
 src/backend/utils/fmgr/funcapi.c        | 12 ++++++------
 src/bin/pg_dump/pg_dump.c               | 11 ++++++-----
 3 files changed, 19 insertions(+), 18 deletions(-)

diff --git a/src/backend/utils/adt/multirangetypes.c b/src/backend/utils/adt/multirangetypes.c
index b8ff39077c..f4dec1bbc9 100644
--- a/src/backend/utils/adt/multirangetypes.c
+++ b/src/backend/utils/adt/multirangetypes.c
@@ -330,13 +330,13 @@ multirange_recv(PG_FUNCTION_ARGS)
 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;
+	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);
 
diff --git a/src/backend/utils/fmgr/funcapi.c b/src/backend/utils/fmgr/funcapi.c
index 25623461bb..808a0e2ca7 100644
--- a/src/backend/utils/fmgr/funcapi.c
+++ b/src/backend/utils/fmgr/funcapi.c
@@ -630,8 +630,8 @@ resolve_polymorphic_tupdesc(TupleDesc tupdesc, oidvector *declared_args,
 	{
 		if (OidIsValid(anymultirange_type))
 		{
-			Oid		rngtype;
-			Oid		subtype;
+			Oid			rngtype;
+			Oid			subtype;
 
 			rngtype = resolve_generic_type(ANYRANGEOID,
 										   anymultirange_type,
@@ -665,9 +665,9 @@ resolve_polymorphic_tupdesc(TupleDesc tupdesc, oidvector *declared_args,
 	{
 		if (OidIsValid(anyrange_type))
 		{
-			Oid		subtype;
-			Oid		mltrngtype;
-			Oid		rngtype;
+			Oid			subtype;
+			Oid			mltrngtype;
+			Oid			rngtype;
 
 			subtype = resolve_generic_type(ANYELEMENTOID,
 										   anyrange_type,
@@ -911,7 +911,7 @@ resolve_polymorphic_argtypes(int numargs, Oid *argtypes, char *argmodes,
 		{
 			Oid			rngtype;
 			Oid			subtype;
-			
+
 			rngtype = resolve_generic_type(ANYRANGEOID,
 										   anymultirange_type,
 										   ANYMULTIRANGEOID);
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 4a0a03d50c..8272a0451b 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -4276,15 +4276,16 @@ dumpSubscription(Archive *fout, SubscriptionInfo *subinfo)
 	free(qsubname);
 }
 
-static Oid get_next_possible_free_pg_type_oid(Archive *fout, PQExpBuffer upgrade_query)
+static Oid
+get_next_possible_free_pg_type_oid(Archive *fout, PQExpBuffer upgrade_query)
 {
 	/*
 	 * If the old version didn't assign an array type, but the new version
-	 * does, we must select an unused type OID to assign.  This currently
-	 * only happens for domains, when upgrading pre-v11 to v11 and up.
+	 * does, we must select an unused type OID to assign.  This currently only
+	 * happens for domains, when upgrading pre-v11 to v11 and up.
 	 *
-	 * Note: local state here is kind of ugly, but we must have some,
-	 * since we mustn't choose the same unused OID more than once.
+	 * Note: local state here is kind of ugly, but we must have some, since we
+	 * mustn't choose the same unused OID more than once.
 	 */
 	static Oid	next_possible_free_oid = FirstNormalObjectId;
 	PGresult   *res;
-- 
2.20.1

v7-0010-fix-typo.patchtext/x-diff; charset=us-asciiDownload
From 9706285f95d797a0c1600ef5a905fb430282caac Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Tue, 17 Dec 2019 12:47:28 -0300
Subject: [PATCH v7 10/13] fix typo

---
 doc/src/sgml/rangetypes.sgml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/doc/src/sgml/rangetypes.sgml b/doc/src/sgml/rangetypes.sgml
index 3e37b352fc..060a715983 100644
--- a/doc/src/sgml/rangetypes.sgml
+++ b/doc/src/sgml/rangetypes.sgml
@@ -385,7 +385,7 @@ SELECT '[1.234, 5.678]'::floatrange;
   </para>
 
   <para>
-   When you define your own range your automatically get a corresponding
+   When you define your own range you automatically get a corresponding
    multirange type.
   </para>
 
-- 
2.20.1

v7-0011-minor-style-change.patchtext/x-diff; charset=us-asciiDownload
From 65daf0d772fcb165cfd72e83e1ad91f4431a9c56 Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Tue, 17 Dec 2019 12:47:44 -0300
Subject: [PATCH v7 11/13] minor style change

---
 src/backend/catalog/pg_proc.c | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/src/backend/catalog/pg_proc.c b/src/backend/catalog/pg_proc.c
index 80cc0721a5..cd8b6ba3cf 100644
--- a/src/backend/catalog/pg_proc.c
+++ b/src/backend/catalog/pg_proc.c
@@ -245,8 +245,9 @@ 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 || returnType == ANYMULTIRANGEOID
-		 || anyrangeOutParam) && !anyrangeInParam)
+	if ((returnType == ANYRANGEOID ||
+		 returnType == ANYMULTIRANGEOID ||
+		 anyrangeOutParam) && !anyrangeInParam)
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
 				 errmsg("cannot determine result data type"),
-- 
2.20.1

v7-0012-remove-spurious-newline.patchtext/x-diff; charset=us-asciiDownload
From 7a11ec82be138cda77df12577f412dd0372b62ac Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Tue, 17 Dec 2019 13:57:54 -0300
Subject: [PATCH v7 12/13] remove spurious newline

---
 src/backend/catalog/pg_range.c | 1 -
 1 file changed, 1 deletion(-)

diff --git a/src/backend/catalog/pg_range.c b/src/backend/catalog/pg_range.c
index d914f04858..8afb107f95 100644
--- a/src/backend/catalog/pg_range.c
+++ b/src/backend/catalog/pg_range.c
@@ -103,7 +103,6 @@ RangeCreate(Oid rangeTypeOid, Oid rangeSubType, Oid rangeCollation,
 	}
 
 	/* record multirange type's dependency on the range type */
-
 	referencing.classId = TypeRelationId;
 	referencing.objectId = multirangeTypeOid;
 	referencing.objectSubId = 0;
-- 
2.20.1

v7-0013-rewrite-makeUniqueTypeName.patchtext/x-diff; charset=us-asciiDownload
From 31d17076f77c045c2645ab91c0dce6e9a52710ac Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Fri, 29 Nov 2019 23:03:13 -0300
Subject: [PATCH v7 13/13] rewrite makeUniqueTypeName

---
 src/backend/catalog/pg_type.c | 113 ++++++++++++++++------------------
 1 file changed, 53 insertions(+), 60 deletions(-)

diff --git a/src/backend/catalog/pg_type.c b/src/backend/catalog/pg_type.c
index 73b8ace4c5..9e3b866d1c 100644
--- a/src/backend/catalog/pg_type.c
+++ b/src/backend/catalog/pg_type.c
@@ -27,6 +27,7 @@
 #include "catalog/pg_proc.h"
 #include "catalog/pg_type.h"
 #include "commands/typecmds.h"
+#include "mb/pg_wchar.h"
 #include "miscadmin.h"
 #include "parser/scansup.h"
 #include "utils/acl.h"
@@ -36,8 +37,8 @@
 #include "utils/rel.h"
 #include "utils/syscache.h"
 
-static int	makeUniqueTypeName(char *dest, const char *typeName, size_t namelen,
-							   Oid typeNamespace, bool tryOriginalName);
+static char *makeUniqueTypeName(const char *typeName, Oid typeNamespace,
+								bool tryOriginal);
 
 /* Potentially set by pg_upgrade_support functions */
 Oid			binary_upgrade_next_pg_type_oid = InvalidOid;
@@ -786,12 +787,10 @@ RenameTypeInternal(Oid typeOid, const char *newTypeName, Oid typeNamespace)
 char *
 makeArrayTypeName(const char *typeName, Oid typeNamespace)
 {
-	char	   *arr = (char *) palloc(NAMEDATALEN);
-	int			namelen = strlen(typeName);
-	int			underscores;
+	char	   *arr;
 
-	underscores = makeUniqueTypeName(arr, typeName, namelen, typeNamespace, false);
-	if (underscores >= NAMEDATALEN - 1)
+	arr = makeUniqueTypeName(typeName, typeNamespace, false);
+	if (arr == NULL)
 		ereport(ERROR,
 				(errcode(ERRCODE_DUPLICATE_OBJECT),
 				 errmsg("could not form array type name for type \"%s\"",
@@ -868,91 +867,85 @@ moveArrayTypeName(Oid typeOid, const char *typeName, Oid typeNamespace)
  * makeMultirangeTypeName
  *	  - given a range type name, make a multirange type name for it
  *
- * the caller is responsible for pfreeing the result
+ * 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);
+	char	   *buf;
+	char	   *mrname;
 	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,
+	 * "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;
-		strlcpy(mltrng + rangeoffset, "multi", NAMEDATALEN - rangeoffset);
-		strlcpy(mltrng + rangeoffset + 5, rangestr, NAMEDATALEN - rangeoffset - 5);
-		namelen += 5;
+		char	*prefix = pnstrdup(rangeTypeName, rangestr - rangeTypeName);
+
+		buf = psprintf("%s%s%s", prefix, "multi", rangestr);
 	}
 	else
-	{
-		strlcpy(mltrng + namelen, "multirange", NAMEDATALEN - namelen);
-		namelen += 10;
-	}
+		buf = psprintf("%s_multirange", pnstrdup(rangeTypeName, NAMEDATALEN - 12));
 
-	underscores = makeUniqueTypeName(mltrngunique, mltrng, namelen, typeNamespace, true);
-	if (underscores >= NAMEDATALEN - 1)
+	/* clip it at NAMEDATALEN-1 bytes */
+	buf[pg_mbcliplen(buf, strlen(buf), NAMEDATALEN - 1)] = '\0';
+
+	mrname = makeUniqueTypeName(buf, typeNamespace, true);
+	if (mrname == NULL)
 		ereport(ERROR,
 				(errcode(ERRCODE_DUPLICATE_OBJECT),
 				 errmsg("could not form multirange type name for type \"%s\"",
 						rangeTypeName)));
 
-	return mltrngunique;
+	return mrname;
 }
 
 /*
- * makeUniqueTypeName: Prepend underscores as needed until we make a name that
- * doesn't collide with anything. Tries the original typeName if requested.
+ * makeUniqueTypeName
+ *		Generate a unique name for a prospective new type
  *
- * Returns the number of underscores added.
+ * Given a typeName, return a new palloc'ed name by prepending underscores
+ * until a non-conflicting name results.
+ *
+ * If tryOriginal, first try with zero underscores.
  */
-int
-makeUniqueTypeName(char *dest, const char *typeName, size_t namelen, Oid typeNamespace,
-				   bool tryOriginalName)
+static char *
+makeUniqueTypeName(const char *typeName, Oid typeNamespace, bool tryOriginal)
 {
 	int			i;
+	int			namelen;
+	char		dest[NAMEDATALEN];
 
-	for (i = 0; i < NAMEDATALEN - 1; i++)
+	Assert(strlen(typeName) <= NAMEDATALEN - 1);
+
+	if (tryOriginal &&
+		!SearchSysCacheExists2(TYPENAMENSP,
+							   CStringGetDatum(typeName),
+							   ObjectIdGetDatum(typeNamespace)))
+		return pstrdup(typeName);
+
+	/*
+	 * The idea is to prepend underscores as needed until we make a name that
+	 * doesn't collide with anything ...
+	 */
+	namelen = strlen(typeName);
+	for (i = 1; i < NAMEDATALEN - 1; i++)
 	{
-		if (i == 0)
-		{
-			if (tryOriginalName &&
-				!SearchSysCacheExists2(TYPENAMENSP,
-									   CStringGetDatum(typeName),
-									   ObjectIdGetDatum(typeNamespace)))
-			{
-				strcpy(dest, typeName);
-				break;
-			}
+		dest[i - 1] = '_';
+		strlcpy(dest + i, typeName, NAMEDATALEN - i);
+		if (namelen + i >= NAMEDATALEN)
+			truncate_identifier(dest, NAMEDATALEN, false);
 
-		}
-		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;
-		}
+		if (!SearchSysCacheExists2(TYPENAMENSP,
+								   CStringGetDatum(dest),
+								   ObjectIdGetDatum(typeNamespace)))
+			return pstrdup(dest);
 	}
 
-	return i;
+	return NULL;
 }
-- 
2.20.1

#67Pavel Stehule
pavel.stehule@gmail.com
In reply to: Alvaro Herrera (#66)
Re: range_agg

pá 20. 12. 2019 v 18:43 odesílatel Alvaro Herrera <alvherre@2ndquadrant.com>
napsal:

I took the liberty of rebasing this series on top of recent branch
master. The first four are mostly Paul's originals, except for conflict
fixes; the rest are changes I'm proposing as I go along figuring out the
whole thing. (I would post just my proposed changes, if it weren't for
the rebasing; apologies for the messiness.)

I am not convinced that adding TYPTYPE_MULTIRANGE is really necessary.
Why can't we just treat those types as TYPTYPE_RANGE and distinguish
them using TYPCATEGORY_MULTIRANGE? That's what we do for arrays. I'll
try to do that next.

I think the algorithm for coming up with the multirange name is
suboptimal. It works fine with the name is short enough that we can add
a few extra letters, but otherwise the result look pretty silly. I
think we can still improve on that. I propose to make
makeUniqueTypeName accept a suffix, and truncate the letters that appear
*before* the suffix rather than truncating after it's been appended.

There's a number of ereport() calls that should become elog(); and a
bunch of others that should probably acquire errcode() and be
reformatted per our style.

Regarding Pavel's documentation markup issue,

I am not sure how much is correct to use <literallayout

class="monospaced">

in doc. It is used for ranges, and multiranges, but no in other places

I looked at the generated PDF and the table looks pretty bad; the words
in those entries overlap the words in the cell to their right. But that
also happens with entries that do not use <literallayout class="x">!
See [1] for an example of the existing docs being badly formatted. The
docbook documentation [2] seems to suggest that what Paul used is the
appropriate way to do this.

Maybe a way is to make each entry have more than one row -- so the
example would appear below the other three fields in its own row, and
would be able to use the whole width of the table.

I had a talk with Paul about possible simplification of designed operators.
Last message from Paul was - he is working on new version.

Regards

Pavel

Show quoted text

[1] https://twitter.com/alvherre/status/1205563468595781633
[2] https://tdg.docbook.org/tdg/5.1/literallayout.html

--
Álvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

#68Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Alvaro Herrera (#66)
Re: range_agg

On 2019-Dec-20, Alvaro Herrera wrote:

I am not convinced that adding TYPTYPE_MULTIRANGE is really necessary.
Why can't we just treat those types as TYPTYPE_RANGE and distinguish
them using TYPCATEGORY_MULTIRANGE? That's what we do for arrays. I'll
try to do that next.

I think this can be simplified if we make the the multirange's
pg_type.typelem carry the base range's OID (the link in the other
direction already appears as pg_range.mltrngtypid, though I'd recommend
renaming that to pg_range.rngmultitypid to maintain the "rng" prefix
convention). Then we can distinguish a multirange from a plain range
easily, both of which have typtype as TYPTYPE_RANGE, because typelem !=
0 in a multi. That knowledge can be encapsulated easily in
type_is_multirange and pg_dump's getTypes.

--
�lvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

#69Paul A Jungwirth
pj@illuminatedcomputing.com
In reply to: Pavel Stehule (#67)
Re: range_agg

On Fri, Dec 20, 2019 at 10:19 AM Pavel Stehule <pavel.stehule@gmail.com> wrote:

I had a talk with Paul about possible simplification of designed operators. Last message from Paul was - he is working on new version.

Thanks Alvaro & Pavel for helping move this forward. I've added the
casts but they aren't used automatically for things like
`range_union(r, mr)` or `mr + r`, even though they are implicit.
That's because the casts are for concrete types (e.g. int4range ->
int4multirange) but the functions & operators are for polymorphic
types (anymultirange + anymultirange). So I'd like to get some
feedback about the best way to proceed.

Is it permitted to add casts with polymorphic inputs & outputs? Is
that something that we would actually want to do? I'd probably need
both the polymorphic and concrete casts so that you could still say
`int4range(1,2)::int4multirange`.

Should I change the coerce code to look for casts among concrete types
when the function has polymorphic types? I'm pretty scared to do
something like that though, both because of the complexity and lest I
cause unintended effects.

Should I just give up on implicit casts and require you to specify
one? That makes it a little more annoying to mix range & multirange
types, but personally I'm okay with that. This is my preferred
approach.

I have some time over the holidays to work on the other changes Alvaro
has suggested.

Thanks,
Paul

#70Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Paul A Jungwirth (#69)
Re: range_agg

On 2019-Dec-20, Paul A Jungwirth wrote:

Is it permitted to add casts with polymorphic inputs & outputs? Is
that something that we would actually want to do? I'd probably need
both the polymorphic and concrete casts so that you could still say
`int4range(1,2)::int4multirange`.

I'm embarrased to admit that I don't grok the type system well enough
(yet) to answer this question.

Should I change the coerce code to look for casts among concrete types
when the function has polymorphic types? I'm pretty scared to do
something like that though, both because of the complexity and lest I
cause unintended effects.

Yeah, I suggest to stay away from that. I think this multirange thing
is groundbreaking enough that we don't need to cause additional
potential breakage.

Should I just give up on implicit casts and require you to specify
one? That makes it a little more annoying to mix range & multirange
types, but personally I'm okay with that. This is my preferred
approach.

+1

I have some time over the holidays to work on the other changes Alvaro
has suggested.

I hope not to have made things worse by posting a rebase. Anyway,
that's the reason I posted my other changes separately.

--
�lvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

#71Tom Lane
tgl@sss.pgh.pa.us
In reply to: Alvaro Herrera (#70)
Re: range_agg

Alvaro Herrera <alvherre@2ndquadrant.com> writes:

On 2019-Dec-20, Paul A Jungwirth wrote:

Is it permitted to add casts with polymorphic inputs & outputs? Is
that something that we would actually want to do? I'd probably need
both the polymorphic and concrete casts so that you could still say
`int4range(1,2)::int4multirange`.

I'm embarrased to admit that I don't grok the type system well enough
(yet) to answer this question.

I would say no; if you want behavior like that you'd have to add code for
it into the coercion machinery, much like the casts around, say, types
record and record[] versus named composites and arrays of same. Expecting
the generic cast machinery to do the right thing would be foolhardy.

In any case, even if it did do the right thing, you'd still need
some additional polymorphic type to express the behavior you wanted,
no? So it's not clear there'd be any net savings of effort.

regards, tom lane

#72Paul A Jungwirth
pj@illuminatedcomputing.com
In reply to: Alvaro Herrera (#70)
1 attachment(s)
Re: range_agg

On Fri, Dec 20, 2019 at 11:29 AM Alvaro Herrera
<alvherre@2ndquadrant.com> wrote:

Should I just give up on implicit casts and require you to specify
one? That makes it a little more annoying to mix range & multirange
types, but personally I'm okay with that. This is my preferred
approach.

+1

Here is a patch adding the casts, rebasing on the latest master, and
incorporating Alvaro's changes. Per his earlier suggestion I combined
it all into one patch file, which also makes it easier to apply
rebases & updates.

My work on adding casts also removes the @+ / @- / @* operators and
adds + / - / * operators where both parameters are multiranges. I
retained other operators with mixed range/multirange parameters, both
because there are already range operators with mixed range/scalar
parameters (e.g. <@), and because it seemed like the objection to @+ /
@- / @* was not mixed parameters per se, but rather their
unguessability. Since the other operators are the same as the existing
range operators, they don't share that problem.

This still leaves the question of how best to format the docs for
these operators. I like being able to combine all the <@ variations
(e.g.) into one table row, but if that is too ugly I could give them
separate rows instead. Giving them all their own row consumes a lot of
vertical space though, and to me that makes the docs more tedious to
read & browse, so it's harder to grasp all the available range-related
operations at a glance.

I'm skeptical of changing pg_type.typtype from 'm' to 'r'. A
multirange isn't a range, so why should we give it the same type? Also
won't this break any queries that are using that column to find range
types? What is the motivation to use the same typtype for both ranges
and multiranges? (There is plenty I don't understand here, e.g. why we
have both typtype and typcategory, so maybe there is a good reason I'm
missing.)

I experimented with setting pg_type.typelem to the multirange's range
type, but it seemed to break a lot of things, and reading the code I
saw some places that treat a non-zero typelem as synonymous with being
an array. So I'm reluctant to make this change also, especially when
it is just as easy to query pg_range to get a multirange's range type.
Also range types themselves don't set typelem to their base type, and
it seems like we'd want to treat ranges and multiranges the same way
here.

Alvaro also suggested renaming pg_range.mltrngtypid to
pg_range.rngmultitypid, so it shares the same "rng" prefix as the
other columns in this table. Having a different prefix does stand out.
I've included that change in this patch too.

Yours,
Paul

Attachments:

v8-multirange.patchapplication/octet-stream; name=v8-multirange.patchDownload
diff --git a/doc/src/sgml/extend.sgml b/doc/src/sgml/extend.sgml
index a3046f22d0..7ea2f65778 100644
--- a/doc/src/sgml/extend.sgml
+++ b/doc/src/sgml/extend.sgml
@@ -229,9 +229,9 @@
    </indexterm>
 
     <para>
-     Five pseudo-types of special interest are <type>anyelement</type>,
+     Six pseudo-types of special interest are <type>anyelement</type>,
      <type>anyarray</type>, <type>anynonarray</type>, <type>anyenum</type>,
-     and <type>anyrange</type>,
+     <type>anyrange</type>, and <type>anymultirange</type>.
      which are collectively called <firstterm>polymorphic types</firstterm>.
      Any function declared using these types is said to be
      a <firstterm>polymorphic function</firstterm>.  A polymorphic function can
@@ -250,15 +250,15 @@
      position declared as <type>anyarray</type> can have any array data type,
      but similarly they must all be the same type.  And similarly,
      positions declared as <type>anyrange</type> must all be the same range
-     type.  Furthermore, if there are
+     type.  Likewise for <type>anymultirange</type>.
+    </para>
+
+    <para>
+     Furthermore, if there are
      positions declared <type>anyarray</type> and others declared
      <type>anyelement</type>, the actual array type in the
      <type>anyarray</type> positions must be an array whose elements are
      the same type appearing in the <type>anyelement</type> positions.
-     Similarly, if there are positions declared <type>anyrange</type>
-     and others declared <type>anyelement</type>, the actual range type in
-     the <type>anyrange</type> positions must be a range whose subtype is
-     the same type appearing in the <type>anyelement</type> positions.
      <type>anynonarray</type> is treated exactly the same as <type>anyelement</type>,
      but adds the additional constraint that the actual type must not be
      an array type.
@@ -268,6 +268,20 @@
     </para>
 
     <para>
+     Similarly, if there are positions declared <type>anyrange</type>
+     and others declared <type>anyelement</type>, the actual range type in
+     the <type>anyrange</type> positions must be a range whose subtype is
+     the same type appearing in the <type>anyelement</type> positions.
+     The type <type>anymultirange</type> accepts a multirange
+     whose contents match the other polymorphic types.
+     That is it must hold ranges matching <type>anyrange</type>
+     and base type elements matching <type>anyelement</type> (if either of
+     those are present). If <type>anyarray</type> is present its elements
+     must match the base type of the <type>anyrange</type> and/or the base
+     type of the ranges in an <type>anymultirange</type>.
+    </para>
+
+    <para>
      Thus, when more than one argument position is declared with a polymorphic
      type, the net effect is that only certain combinations of actual argument
      types are allowed.  For example, a function declared as
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 57a1539506..c049d94344 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -14566,7 +14566,9 @@ NULL baz</literallayout>(3 rows)</entry>
 
   <para>
    <xref linkend="range-operators-table"/> shows the operators
-   available for range types.
+   available for range and multirange types.
+   Many of these operators will accept either a range or multirange
+   on either side.
   </para>
 
     <table id="range-operators-table">
@@ -14576,7 +14578,7 @@ NULL baz</literallayout>(3 rows)</entry>
        <row>
         <entry>Operator</entry>
         <entry>Description</entry>
-        <entry>Example</entry>
+        <entry>Examples</entry>
         <entry>Result</entry>
        </row>
       </thead>
@@ -14584,136 +14586,232 @@ NULL baz</literallayout>(3 rows)</entry>
        <row>
         <entry> <literal>=</literal> </entry>
         <entry>equal</entry>
-        <entry><literal>int4range(1,5) = '[1,4]'::int4range</literal></entry>
+        <entry><literallayout class="monospaced">int4range(1,5) = '[1,4]'::int4range
+'{[1,5)}'::int4multirange = '{[1,4]}'::int4multirange</literallayout></entry>
         <entry><literal>t</literal></entry>
        </row>
 
        <row>
         <entry> <literal>&lt;&gt;</literal> </entry>
         <entry>not equal</entry>
-        <entry><literal>numrange(1.1,2.2) &lt;&gt; numrange(1.1,2.3)</literal></entry>
+        <entry><literallayout class="monospaced">numrange(1.1,2.2) &lt;&gt; numrange(1.1,2.3)
+'{[1.1,2.2)}'::nummultirange &lt;&gt; '{[1.1,2.3)}'::nummultirange</literallayout></entry>
         <entry><literal>t</literal></entry>
        </row>
 
        <row>
         <entry> <literal>&lt;</literal> </entry>
         <entry>less than</entry>
-        <entry><literal>int4range(1,10) &lt; int4range(2,3)</literal></entry>
+        <entry><literallayout class="monospaced">int4range(1,10) &lt; int4range(2,3)
+'{[1,10)}'::int4multirange &lt; '{[2,3)}'::int4multirange</literallayout>
+        </entry>
         <entry><literal>t</literal></entry>
        </row>
 
        <row>
         <entry> <literal>&gt;</literal> </entry>
         <entry>greater than</entry>
-        <entry><literal>int4range(1,10) &gt; int4range(1,5)</literal></entry>
+        <entry>
+          <literallayout class="monospaced">int4range(1,10) &gt; int4range(1,5)
+'{[1,10)}'::int4multirange &gt; '{[1,5)}'::int4multirange</literallayout>
+        </entry>
         <entry><literal>t</literal></entry>
        </row>
 
        <row>
         <entry> <literal>&lt;=</literal> </entry>
         <entry>less than or equal</entry>
-        <entry><literal>numrange(1.1,2.2) &lt;= numrange(1.1,2.2)</literal></entry>
+        <entry>
+          <literallayout class="monospaced">numrange(1.1,2.2) &lt;= numrange(1.1,2.2)
+'{[1.1,2.2)}'::nummultirange &lt;= '{[1.1,2.2)}'::nummultirange</literallayout>
+        </entry>
         <entry><literal>t</literal></entry>
        </row>
 
        <row>
         <entry> <literal>&gt;=</literal> </entry>
         <entry>greater than or equal</entry>
-        <entry><literal>numrange(1.1,2.2) &gt;= numrange(1.1,2.0)</literal></entry>
+        <entry>
+          <literallayout class="monospaced">numrange(1.1,2.2) &gt;= numrange(1.1,2.0)
+'{[1.1,2.2)}'::nummultirange &gt;= '{[1.1,2.0)}'::nummultirange</literallayout>
+        </entry>
+        <entry><literal>t</literal></entry>
+       </row>
+
+       <row>
+        <entry> <literal>@&gt;</literal> </entry>
+        <entry>contains multirange</entry>
+        <entry>
+          <literallayout class="monospaced">int4range(2,4) @&gt; '{[2,3)}'::int4multirange
+'{[2,4)}'::int4multirange @&gt; '{[2,3)}'::int4multirange</literallayout>
+        </entry>
         <entry><literal>t</literal></entry>
        </row>
 
        <row>
         <entry> <literal>@&gt;</literal> </entry>
         <entry>contains range</entry>
-        <entry><literal>int4range(2,4) @&gt; int4range(2,3)</literal></entry>
+        <entry>
+          <literallayout class="monospaced">int4range(2,4) @&gt; int4range(2,3)
+'{[2,4)}'::int4multirange @&gt; int4range(2,3)</literallayout>
+        </entry>
         <entry><literal>t</literal></entry>
        </row>
 
        <row>
         <entry> <literal>@&gt;</literal> </entry>
         <entry>contains element</entry>
-        <entry><literal>'[2011-01-01,2011-03-01)'::tsrange @&gt; '2011-01-10'::timestamp</literal></entry>
+        <entry>
+          <literallayout class="monospaced">'[2011-01-01,2011-03-01)'::tsrange @&gt; '2011-01-10'::timestamp
+'{[2011-01-01,2011-03-01)}'::tsmultirange @&gt; '2011-01-10'::timestamp</literallayout>
+        </entry>
+        <entry><literal>t</literal></entry>
+       </row>
+
+       <row>
+        <entry> <literal>&lt;@</literal> </entry>
+        <entry>multirange is contained by</entry>
+        <entry>
+          <literallayout class="monospaced">'{[2,4)}'::int4multirange &lt;@ int4range(1,7)
+'{[2,4)}'::int4multirange &lt;@ '{[1,7)}'::int4multirange</literallayout>
+        </entry>
         <entry><literal>t</literal></entry>
        </row>
 
        <row>
         <entry> <literal>&lt;@</literal> </entry>
         <entry>range is contained by</entry>
-        <entry><literal>int4range(2,4) &lt;@ int4range(1,7)</literal></entry>
+        <entry>
+          <literallayout class="monospaced">int4range(2,4) &lt;@ int4range(1,7)
+int4range(2,4) &lt;@ '{[1,7)}'::int4multirange</literallayout>
+        </entry>
         <entry><literal>t</literal></entry>
        </row>
 
        <row>
         <entry> <literal>&lt;@</literal> </entry>
         <entry>element is contained by</entry>
-        <entry><literal>42 &lt;@ int4range(1,7)</literal></entry>
+        <entry>
+          <literallayout class="monospaced">42 &lt;@ int4range(1,7)
+42 &lt;@ '{[1,7)}'::int4multirange</literallayout>
+        </entry>
         <entry><literal>f</literal></entry>
        </row>
 
        <row>
         <entry> <literal>&amp;&amp;</literal> </entry>
         <entry>overlap (have points in common)</entry>
-        <entry><literal>int8range(3,7) &amp;&amp; int8range(4,12)</literal></entry>
+        <entry>
+          <literallayout class="monospaced">int8range(3,7) &amp;&amp; int8range(4,12)
+int8range(3,7) &amp;&amp; '{[4,12)}'::int8multirange
+'{[3,7)}'::int8multirange &amp;&amp; int8range(4,12)
+'{[3,7)}'::int8multirange &amp;&amp; '{[4,12)}'::int8multirange</literallayout>
+        </entry>
         <entry><literal>t</literal></entry>
        </row>
 
        <row>
         <entry> <literal>&lt;&lt;</literal> </entry>
         <entry>strictly left of</entry>
-        <entry><literal>int8range(1,10) &lt;&lt; int8range(100,110)</literal></entry>
+        <entry>
+          <literallayout class="monospaced">int8range(1,10) &lt;&lt; int8range(100,110)
+int8range(1,10) &lt;&lt; '{[100,110)}'::int8multirange
+'{[1,10)}'::int8multirange &lt;&lt; int8range(100,110)
+'{[1,10)}'::int8multirange &lt;&lt; '{[100,110)}'::int8multirange</literallayout>
+        </entry>
         <entry><literal>t</literal></entry>
        </row>
 
        <row>
         <entry> <literal>&gt;&gt;</literal> </entry>
         <entry>strictly right of</entry>
-        <entry><literal>int8range(50,60) &gt;&gt; int8range(20,30)</literal></entry>
+        <entry>
+          <literallayout class="monospaced">int8range(50,60) &gt;&gt; int8range(20,30)
+int8range(50,60) &gt;&gt; '{[20,30)}'::int8multirange
+'{[50,60)}'::int8multirange &gt;&gt; int8range(20,30)
+'{[50,60)}'::int8multirange &gt;&gt; '{[20,30)}'::int8multirange</literallayout>
+        </entry>
         <entry><literal>t</literal></entry>
        </row>
 
        <row>
         <entry> <literal>&amp;&lt;</literal> </entry>
         <entry>does not extend to the right of</entry>
-        <entry><literal>int8range(1,20) &amp;&lt; int8range(18,20)</literal></entry>
+        <entry>
+          <literallayout class="monospaced">int8range(1,20) &amp;&lt; int8range(18,20)
+int8range(1,20) &amp;&lt; '{[18,20)}'::int8multirange
+'{[1,20)}'::int8multirange &amp;&lt; int8range(18,20)
+'{[1,20)}'::int8multirange &amp;&lt; '{[18,20)}'::int8multirange</literallayout>
+        </entry>
         <entry><literal>t</literal></entry>
        </row>
 
        <row>
         <entry> <literal>&amp;&gt;</literal> </entry>
         <entry>does not extend to the left of</entry>
-        <entry><literal>int8range(7,20) &amp;&gt; int8range(5,10)</literal></entry>
+        <entry>
+          <literallayout class="monospaced">int8range(7,20) &amp;&gt; int8range(5,10)
+int8range(7,20) &amp;&gt; '{[5,10)}'::int8multirange
+'{[7,20)}'::int8multirange &amp;&gt; int8range(5,10)
+'{[7,20)}'::int8multirange &amp;&gt; '{[5,10)}'::int8multirange</literallayout>
+        </entry>
         <entry><literal>t</literal></entry>
        </row>
 
        <row>
         <entry> <literal>-|-</literal> </entry>
         <entry>is adjacent to</entry>
-        <entry><literal>numrange(1.1,2.2) -|- numrange(2.2,3.3)</literal></entry>
+        <entry>
+          <literallayout class="monospaced">numrange(1.1,2.2) -|- numrange(2.2,3.3)
+numrange(1.1,2.2) -|- '{[2.2,3.3)}'::nummultirange
+'{[1.1,2.2)}'::nummultirange -|- numrange(2.2,3.3)
+'{[1.1,2.2)}'::nummultirange -|- '{[2.2,3.3)}'::nummultirange</literallayout>
+        </entry>
         <entry><literal>t</literal></entry>
        </row>
 
        <row>
         <entry> <literal>+</literal> </entry>
         <entry>union</entry>
-        <entry><literal>numrange(5,15) + numrange(10,20)</literal></entry>
+        <entry><literallayout class="monospaced">numrange(5,15) + numrange(10,20)</literallayout></entry>
         <entry><literal>[5,20)</literal></entry>
        </row>
 
        <row>
+        <entry> <literal>+</literal> </entry>
+        <entry>multirange union</entry>
+        <entry><literallayout class="monospaced">'{[5,10)}'::nummultirange @+ '{[15,20)}'::nummultirange</literallayout></entry>
+        <entry><literal>{[5,10), [15,20)}</literal></entry>
+       </row>
+
+       <row>
         <entry> <literal>*</literal> </entry>
         <entry>intersection</entry>
-        <entry><literal>int8range(5,15) * int8range(10,20)</literal></entry>
+        <entry><literallayout class="monospaced">int8range(5,15) * int8range(10,20)</literallayout></entry>
         <entry><literal>[10,15)</literal></entry>
        </row>
 
        <row>
+        <entry> <literal>*</literal> </entry>
+        <entry>multirange intersection</entry>
+        <entry><literallayout class="monospaced">'{[5,15)}'::int8multirange @* '{[10,20)}'::int8multirange</literallayout></entry>
+        <entry><literal>{[10,15)}</literal></entry>
+       </row>
+
+       <row>
         <entry> <literal>-</literal> </entry>
         <entry>difference</entry>
-        <entry><literal>int8range(5,15) - int8range(10,20)</literal></entry>
+        <entry><literallayout class="monospaced">int8range(5,15) - int8range(10,20)</literallayout></entry>
         <entry><literal>[5,10)</literal></entry>
        </row>
 
+       <row>
+        <entry> <literal>-</literal> </entry>
+        <entry>multirange difference</entry>
+        <entry><literallayout class="monospaced">'{[5,20)}'::int8multirange @- '{[10,15)}'::int8multirange</literallayout></entry>
+        <entry><literal>{[5,10), [15,20)}</literal></entry>
+       </row>
+
       </tbody>
      </tgroup>
     </table>
@@ -14729,19 +14827,28 @@ NULL baz</literallayout>(3 rows)</entry>
 
   <para>
    The left-of/right-of/adjacent operators always return false when an empty
-   range is involved; that is, an empty range is not considered to be either
-   before or after any other range.
+   range or multirange is involved; that is, an empty range is not considered to
+   be either before or after any other range.
+  </para>
+
+  <para>
+   Elsewhere empty ranges and multiranges are treated as the additive identity:
+   anything unioned with an empty value is itself. Anything minus an empty
+   value is itself. An empty multirange has exactly the same points as an empty
+   range. Every range contains the empty range. Every multirange contains as many
+   empty ranges as you like.
   </para>
 
   <para>
    The union and difference operators will fail if the resulting range would
    need to contain two disjoint sub-ranges, as such a range cannot be
-   represented.
+   represented. The safe versions of union, intersection, and difference
+   return multiranges, so they can handle gaps without failing.
   </para>
 
   <para>
    <xref linkend="range-functions-table"/> shows the functions
-   available for use with range types.
+   available for use with range and multirange types.
   </para>
 
   <indexterm>
@@ -14793,6 +14900,17 @@ NULL baz</literallayout>(3 rows)</entry>
        <row>
         <entry>
          <literal>
+          <function>lower</function>(<type>anymultirange</type>)
+         </literal>
+        </entry>
+        <entry>multirange's element type</entry>
+        <entry>lower bound of multirange</entry>
+        <entry><literal>lower('{[1.1,2.2)}'::nummultirange)</literal></entry>
+        <entry><literal>1.1</literal></entry>
+       </row>
+       <row>
+        <entry>
+         <literal>
           <function>upper</function>(<type>anyrange</type>)
          </literal>
         </entry>
@@ -14804,6 +14922,17 @@ NULL baz</literallayout>(3 rows)</entry>
        <row>
         <entry>
          <literal>
+          <function>upper</function>(<type>anymultirange</type>)
+         </literal>
+        </entry>
+        <entry>multirange's element type</entry>
+        <entry>upper bound of multirange</entry>
+        <entry><literal>upper('{[1.1,2.2)}'::nummultirange)</literal></entry>
+        <entry><literal>2.2</literal></entry>
+       </row>
+       <row>
+        <entry>
+         <literal>
           <function>isempty</function>(<type>anyrange</type>)
          </literal>
         </entry>
@@ -14815,6 +14944,17 @@ NULL baz</literallayout>(3 rows)</entry>
        <row>
         <entry>
          <literal>
+          <function>isempty</function>(<type>anymultirange</type>)
+         </literal>
+        </entry>
+        <entry><type>boolean</type></entry>
+        <entry>is the multirange empty?</entry>
+        <entry><literal>isempty('{[1.1,2.2)}'::nummultirange)</literal></entry>
+        <entry><literal>false</literal></entry>
+       </row>
+       <row>
+        <entry>
+         <literal>
           <function>lower_inc</function>(<type>anyrange</type>)
          </literal>
         </entry>
@@ -14826,6 +14966,17 @@ NULL baz</literallayout>(3 rows)</entry>
        <row>
         <entry>
          <literal>
+          <function>lower_inc</function>(<type>anymultirange</type>)
+         </literal>
+        </entry>
+        <entry><type>boolean</type></entry>
+        <entry>is the lower bound inclusive?</entry>
+        <entry><literal>lower_inc('{[1.1,2.2)}'::nummultirange)</literal></entry>
+        <entry><literal>true</literal></entry>
+       </row>
+       <row>
+        <entry>
+         <literal>
           <function>upper_inc</function>(<type>anyrange</type>)
          </literal>
         </entry>
@@ -14837,6 +14988,17 @@ NULL baz</literallayout>(3 rows)</entry>
        <row>
         <entry>
          <literal>
+          <function>upper_inc</function>(<type>anymultirange</type>)
+         </literal>
+        </entry>
+        <entry><type>boolean</type></entry>
+        <entry>is the upper bound inclusive?</entry>
+        <entry><literal>upper_inc('{[1.1,2.2)}'::nummultirange)</literal></entry>
+        <entry><literal>false</literal></entry>
+       </row>
+       <row>
+        <entry>
+         <literal>
           <function>lower_inf</function>(<type>anyrange</type>)
          </literal>
         </entry>
@@ -14848,6 +15010,17 @@ NULL baz</literallayout>(3 rows)</entry>
        <row>
         <entry>
          <literal>
+          <function>lower_inf</function>(<type>anymultirange</type>)
+         </literal>
+        </entry>
+        <entry><type>boolean</type></entry>
+        <entry>is the lower bound infinite?</entry>
+        <entry><literal>lower_inf('{(,)}'::datemultirange)</literal></entry>
+        <entry><literal>true</literal></entry>
+       </row>
+       <row>
+        <entry>
+         <literal>
           <function>upper_inf</function>(<type>anyrange</type>)
          </literal>
         </entry>
@@ -14859,6 +15032,17 @@ NULL baz</literallayout>(3 rows)</entry>
        <row>
         <entry>
          <literal>
+          <function>upper_inf</function>(<type>anymultirange</type>)
+         </literal>
+        </entry>
+        <entry><type>boolean</type></entry>
+        <entry>is the upper bound infinite?</entry>
+        <entry><literal>upper_inf('{(,)}'::datemultirange)</literal></entry>
+        <entry><literal>true</literal></entry>
+       </row>
+       <row>
+        <entry>
+         <literal>
           <function>range_merge</function>(<type>anyrange</type>, <type>anyrange</type>)
          </literal>
         </entry>
@@ -14867,16 +15051,27 @@ NULL baz</literallayout>(3 rows)</entry>
         <entry><literal>range_merge('[1,2)'::int4range, '[3,4)'::int4range)</literal></entry>
         <entry><literal>[1,4)</literal></entry>
        </row>
+       <row>
+        <entry>
+         <literal>
+          <function>range_merge</function>(<type>anymultirange</type>)
+         </literal>
+        </entry>
+        <entry><type>anyrange</type></entry>
+        <entry>the smallest range which includes the entire multirange</entry>
+        <entry><literal>range_merge('{[1,2), [3,4)}'::int4multirange)</literal></entry>
+        <entry><literal>[1,4)</literal></entry>
+       </row>
       </tbody>
      </tgroup>
     </table>
 
   <para>
    The <function>lower</function> and  <function>upper</function> functions return null
-   if the range is empty or the requested bound is infinite.
+   if the input range/multirange is empty or the requested bound is infinite.
    The <function>lower_inc</function>, <function>upper_inc</function>,
    <function>lower_inf</function>, and <function>upper_inf</function>
-   functions all return false for an empty range.
+   functions all return false for an empty range or multirange.
   </para>
   </sect1>
 
@@ -15198,6 +15393,44 @@ NULL baz</literallayout>(3 rows)</entry>
      <row>
       <entry>
        <indexterm>
+        <primary>range_agg</primary>
+       </indexterm>
+       <function>
+         range_agg(<replaceable class="parameter">expression</replaceable>)
+       </function>
+      </entry>
+      <entry>
+       <type>anyrange</type>
+      </entry>
+      <entry>
+       <type>multirange</type> of the argument range type
+      </entry>
+      <entry>Yes</entry>
+      <entry>union of the non-null input values</entry>
+     </row>
+
+     <row>
+      <entry>
+       <indexterm>
+        <primary>range_intersect_agg</primary>
+       </indexterm>
+       <function>
+         range_intersect_agg(<replaceable class="parameter">expression</replaceable>)
+       </function>
+      </entry>
+      <entry>
+       <type>anyrange</type>
+      </entry>
+      <entry>
+       <type>multirange</type> of the argument range type
+      </entry>
+      <entry>No</entry>
+      <entry>intersection of the non-null input values</entry>
+     </row>
+
+     <row>
+      <entry>
+       <indexterm>
         <primary>string_agg</primary>
        </indexterm>
        <function>
diff --git a/doc/src/sgml/rangetypes.sgml b/doc/src/sgml/rangetypes.sgml
index b75fb3a392..c8784bdce3 100644
--- a/doc/src/sgml/rangetypes.sgml
+++ b/doc/src/sgml/rangetypes.sgml
@@ -27,6 +27,13 @@
   ranges from an instrument, and so forth can also be useful.
  </para>
 
+ <para>
+  Every range type has a corresponding multirange type. A multirange is
+  an ordered list of non-continguous, non-empty, non-null ranges. Most
+  range operators also work on multiranges, and they have a few functions
+  of their own.
+ </para>
+
  <sect2 id="rangetypes-builtin">
   <title>Built-in Range Types</title>
 
@@ -232,10 +239,30 @@ SELECT '[4,4]'::int4range;
 SELECT '[4,4)'::int4range;
 </programlisting>
   </para>
+
+  <para>
+   The input for a multirange is curly brackets (<literal>{</literal> and
+   <literal>}</literal>) containing zero or more valid ranges,
+   separated by commas. Whitespace is permitted around the brackets and
+   commas. This is intended to be reminiscent of array syntax, although
+   multiranges are much simpler: they have just one dimension and there is
+   no need to quote their contents. (The bounds of their ranges may be
+   quoted as above however.)
+  </para>
+
+  <para>
+  Examples:
+<programlisting>
+SELECT '{}'::int4multirange;
+SELECT '{[3,7)}'::int4multirange;
+SELECT '{[3,7), [8,9)}'::int4multirange;
+</programlisting>
+  </para>
+
  </sect2>
 
  <sect2 id="rangetypes-construct">
-  <title>Constructing Ranges</title>
+  <title>Constructing Ranges and Multiranges</title>
 
   <para>
    Each range type has a constructor function with the same name as the range
@@ -269,6 +296,19 @@ SELECT int8range(1, 14, '(]');
 SELECT numrange(NULL, 2.2);
 </programlisting>
   </para>
+
+  <para>
+   Each range type also has a multirange constructor with the same name as the
+   multirange type.  The constructor function takes zero or more arguments
+   which are all ranges of the appropriate type.
+   For example:
+
+<programlisting>
+SELECT nummultirange();
+SELECT nummultirange(numrange(1.0, 14.0));
+SELECT nummultirange(numrange(1.0, 14.0), numrange(20.0, 25.0));
+</programlisting>
+  </para>
  </sect2>
 
  <sect2 id="rangetypes-discrete">
@@ -342,6 +382,11 @@ SELECT '[1.234, 5.678]'::floatrange;
   </para>
 
   <para>
+   When you define your own range you automatically get a corresponding
+   multirange type.
+  </para>
+
+  <para>
    Defining your own range type also allows you to specify a different
    subtype B-tree operator class or collation to use, so as to change the sort
    ordering that determines which values fall into a given range.
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index 61db650755..61baffe789 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -25,6 +25,7 @@ OBJS = \
 	objectaddress.o \
 	partition.o \
 	pg_aggregate.o \
+	pg_cast.o \
 	pg_collation.o \
 	pg_constraint.o \
 	pg_conversion.o \
diff --git a/src/backend/catalog/pg_cast.c b/src/backend/catalog/pg_cast.c
new file mode 100644
index 0000000000..50b44e3a96
--- /dev/null
+++ b/src/backend/catalog/pg_cast.c
@@ -0,0 +1,116 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_cast.c
+ *	  routines to support manipulation of the pg_cast relation
+ *
+ * Portions Copyright (c) 1996-2019, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ *	  src/backend/catalog/pg_cast.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "catalog/pg_cast.h"
+#include "access/table.h"
+#include "catalog/catalog.h"
+#include "catalog/dependency.h"
+#include "catalog/indexing.h"
+#include "catalog/objectaccess.h"
+#include "catalog/pg_proc.h"
+#include "catalog/pg_type.h"
+#include "tcop/tcopprot.h"
+#include "utils/builtins.h"
+#include "utils/rel.h"
+#include "utils/syscache.h"
+
+/*
+ * ----------------------------------------------------------------
+ *		CastCreate
+ * ----------------------------------------------------------------
+ */
+ObjectAddress
+CastCreate(Oid sourcetypeid, Oid targettypeid, Oid funcid, char castcontext,
+		   char castmethod, DependencyType behavior)
+{
+	Relation		relation;
+	HeapTuple		tuple;
+	Oid				castid;
+	Datum			values[Natts_pg_cast];
+	bool			nulls[Natts_pg_cast];
+	ObjectAddress	myself,
+					referenced;
+
+	relation = table_open(CastRelationId, RowExclusiveLock);
+
+	/*
+	 * Check for duplicate.  This is just to give a friendly error message,
+	 * the unique index would catch it anyway (so no need to sweat about race
+	 * conditions).
+	 */
+	tuple = SearchSysCache2(CASTSOURCETARGET,
+							ObjectIdGetDatum(sourcetypeid),
+							ObjectIdGetDatum(targettypeid));
+	if (HeapTupleIsValid(tuple))
+		ereport(ERROR,
+				(errcode(ERRCODE_DUPLICATE_OBJECT),
+				 errmsg("cast from type %s to type %s already exists",
+						format_type_be(sourcetypeid),
+						format_type_be(targettypeid))));
+
+	/* ready to go */
+	castid = GetNewOidWithIndex(relation, CastOidIndexId, Anum_pg_cast_oid);
+	values[Anum_pg_cast_oid - 1] = ObjectIdGetDatum(castid);
+	values[Anum_pg_cast_castsource - 1] = ObjectIdGetDatum(sourcetypeid);
+	values[Anum_pg_cast_casttarget - 1] = ObjectIdGetDatum(targettypeid);
+	values[Anum_pg_cast_castfunc - 1] = ObjectIdGetDatum(funcid);
+	values[Anum_pg_cast_castcontext - 1] = CharGetDatum(castcontext);
+	values[Anum_pg_cast_castmethod - 1] = CharGetDatum(castmethod);
+
+	MemSet(nulls, false, sizeof(nulls));
+
+	tuple = heap_form_tuple(RelationGetDescr(relation), values, nulls);
+
+	CatalogTupleInsert(relation, tuple);
+
+	/* make dependency entries */
+	myself.classId = CastRelationId;
+	myself.objectId = castid;
+	myself.objectSubId = 0;
+
+	/* dependency on source type */
+	referenced.classId = TypeRelationId;
+	referenced.objectId = sourcetypeid;
+	referenced.objectSubId = 0;
+	recordDependencyOn(&myself, &referenced, behavior);
+
+	/* dependency on target type */
+	referenced.classId = TypeRelationId;
+	referenced.objectId = targettypeid;
+	referenced.objectSubId = 0;
+	recordDependencyOn(&myself, &referenced, behavior);
+
+	/* dependency on function */
+	if (OidIsValid(funcid))
+	{
+		referenced.classId = ProcedureRelationId;
+		referenced.objectId = funcid;
+		referenced.objectSubId = 0;
+		recordDependencyOn(&myself, &referenced, behavior);
+	}
+
+	/* dependency on extension */
+	recordDependencyOnCurrentExtension(&myself, false);
+
+	/* Post creation hook for new cast */
+	InvokeObjectPostCreateHook(CastRelationId, castid, 0);
+
+	heap_freetuple(tuple);
+
+	table_close(relation, RowExclusiveLock);
+
+	return myself;
+}
diff --git a/src/backend/catalog/pg_proc.c b/src/backend/catalog/pg_proc.c
index 9dba42671a..406f404a43 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,13 @@ 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 b5bc36c2bd..1217a6ddd0 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_rngmultitypid - 1] = ObjectIdGetDatum(multirangeTypeOid);
 
 	tup = heap_form_tuple(RelationGetDescr(pg_range), values, nulls);
 
@@ -100,6 +102,12 @@ 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 8d7572da51..d74697ef9c 100644
--- a/src/backend/catalog/pg_type.c
+++ b/src/backend/catalog/pg_type.c
@@ -27,6 +27,7 @@
 #include "catalog/pg_proc.h"
 #include "catalog/pg_type.h"
 #include "commands/typecmds.h"
+#include "mb/pg_wchar.h"
 #include "miscadmin.h"
 #include "parser/scansup.h"
 #include "utils/acl.h"
@@ -36,6 +37,9 @@
 #include "utils/rel.h"
 #include "utils/syscache.h"
 
+static char *makeUniqueTypeName(const char *typeName, Oid typeNamespace,
+								bool tryOriginal);
+
 /* Potentially set by pg_upgrade_support functions */
 Oid			binary_upgrade_next_pg_type_oid = InvalidOid;
 
@@ -783,31 +787,10 @@ RenameTypeInternal(Oid typeOid, const char *newTypeName, Oid typeNamespace)
 char *
 makeArrayTypeName(const char *typeName, Oid typeNamespace)
 {
-	char	   *arr = (char *) palloc(NAMEDATALEN);
-	int			namelen = strlen(typeName);
-	int			i;
-
-	/*
-	 * 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;
-	}
+	char	   *arr;
 
-	if (i >= NAMEDATALEN - 1)
+	arr = makeUniqueTypeName(typeName, typeNamespace, false);
+	if (arr == NULL)
 		ereport(ERROR,
 				(errcode(ERRCODE_DUPLICATE_OBJECT),
 				 errmsg("could not form array type name for type \"%s\"",
@@ -878,3 +861,91 @@ moveArrayTypeName(Oid typeOid, const char *typeName, Oid typeNamespace)
 
 	return true;
 }
+
+
+/*
+ * makeMultirangeTypeName
+ *	  - given a range type name, make a multirange type name for it
+ *
+ * caller is responsible for pfreeing the result
+ */
+char *
+makeMultirangeTypeName(const char *rangeTypeName, Oid typeNamespace)
+{
+	char	   *buf;
+	char	   *mrname;
+	char	   *rangestr;
+
+	/*
+	 * 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...
+	 */
+	rangestr = strstr(rangeTypeName, "range");
+	if (rangestr)
+	{
+		char	*prefix = pnstrdup(rangeTypeName, rangestr - rangeTypeName);
+
+		buf = psprintf("%s%s%s", prefix, "multi", rangestr);
+	}
+	else
+		buf = psprintf("%s_multirange", pnstrdup(rangeTypeName, NAMEDATALEN - 12));
+
+	/* clip it at NAMEDATALEN-1 bytes */
+	buf[pg_mbcliplen(buf, strlen(buf), NAMEDATALEN - 1)] = '\0';
+
+	mrname = makeUniqueTypeName(buf, typeNamespace, true);
+	if (mrname == NULL)
+		ereport(ERROR,
+				(errcode(ERRCODE_DUPLICATE_OBJECT),
+				 errmsg("could not form multirange type name for type \"%s\"",
+						rangeTypeName)));
+
+	return mrname;
+}
+
+/*
+ * makeUniqueTypeName
+ *		Generate a unique name for a prospective new type
+ *
+ * Given a typeName, return a new palloc'ed name by preprending underscores
+ * until a non-conflicting name results.
+ *
+ * If tryOriginal, first try with zero underscores.
+ */
+static char *
+makeUniqueTypeName(const char *typeName, Oid typeNamespace, bool tryOriginal)
+{
+	int			i;
+	int			namelen;
+	char		dest[NAMEDATALEN];
+
+	Assert(strlen(typeName) <= NAMEDATALEN - 1);
+
+	if (tryOriginal &&
+		!SearchSysCacheExists2(TYPENAMENSP,
+							   CStringGetDatum(typeName),
+							   ObjectIdGetDatum(typeNamespace)))
+		return pstrdup(typeName);
+
+	/*
+	 * The idea is to prepend underscores as needed until we make a name that
+	 * doesn't collide with anything ...
+	 */
+	namelen = strlen(typeName);
+	for (i = 1; i < NAMEDATALEN - 1; i++)
+	{
+		dest[i - 1] = '_';
+		strlcpy(dest + i, typeName, NAMEDATALEN - i);
+		if (namelen + i >= NAMEDATALEN)
+			truncate_identifier(dest, NAMEDATALEN, false);
+
+		if (!SearchSysCacheExists2(TYPENAMENSP,
+								   CStringGetDatum(dest),
+								   ObjectIdGetDatum(typeNamespace)))
+			return pstrdup(dest);
+	}
+
+	return NULL;
+}
diff --git a/src/backend/commands/functioncmds.c b/src/backend/commands/functioncmds.c
index c31c57e5e9..732487ff8d 100644
--- a/src/backend/commands/functioncmds.c
+++ b/src/backend/commands/functioncmds.c
@@ -1497,17 +1497,12 @@ CreateCast(CreateCastStmt *stmt)
 	char		sourcetyptype;
 	char		targettyptype;
 	Oid			funcid;
-	Oid			castid;
 	int			nargs;
 	char		castcontext;
 	char		castmethod;
-	Relation	relation;
 	HeapTuple	tuple;
-	Datum		values[Natts_pg_cast];
-	bool		nulls[Natts_pg_cast];
-	ObjectAddress myself,
-				referenced;
 	AclResult	aclresult;
+	ObjectAddress	myself;
 
 	sourcetypeid = typenameTypeId(NULL, stmt->sourcetype);
 	targettypeid = typenameTypeId(NULL, stmt->targettype);
@@ -1731,74 +1726,8 @@ CreateCast(CreateCastStmt *stmt)
 			break;
 	}
 
-	relation = table_open(CastRelationId, RowExclusiveLock);
-
-	/*
-	 * Check for duplicate.  This is just to give a friendly error message,
-	 * the unique index would catch it anyway (so no need to sweat about race
-	 * conditions).
-	 */
-	tuple = SearchSysCache2(CASTSOURCETARGET,
-							ObjectIdGetDatum(sourcetypeid),
-							ObjectIdGetDatum(targettypeid));
-	if (HeapTupleIsValid(tuple))
-		ereport(ERROR,
-				(errcode(ERRCODE_DUPLICATE_OBJECT),
-				 errmsg("cast from type %s to type %s already exists",
-						format_type_be(sourcetypeid),
-						format_type_be(targettypeid))));
-
-	/* ready to go */
-	castid = GetNewOidWithIndex(relation, CastOidIndexId, Anum_pg_cast_oid);
-	values[Anum_pg_cast_oid - 1] = ObjectIdGetDatum(castid);
-	values[Anum_pg_cast_castsource - 1] = ObjectIdGetDatum(sourcetypeid);
-	values[Anum_pg_cast_casttarget - 1] = ObjectIdGetDatum(targettypeid);
-	values[Anum_pg_cast_castfunc - 1] = ObjectIdGetDatum(funcid);
-	values[Anum_pg_cast_castcontext - 1] = CharGetDatum(castcontext);
-	values[Anum_pg_cast_castmethod - 1] = CharGetDatum(castmethod);
-
-	MemSet(nulls, false, sizeof(nulls));
-
-	tuple = heap_form_tuple(RelationGetDescr(relation), values, nulls);
-
-	CatalogTupleInsert(relation, tuple);
-
-	/* make dependency entries */
-	myself.classId = CastRelationId;
-	myself.objectId = castid;
-	myself.objectSubId = 0;
-
-	/* dependency on source type */
-	referenced.classId = TypeRelationId;
-	referenced.objectId = sourcetypeid;
-	referenced.objectSubId = 0;
-	recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
-
-	/* dependency on target type */
-	referenced.classId = TypeRelationId;
-	referenced.objectId = targettypeid;
-	referenced.objectSubId = 0;
-	recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
-
-	/* dependency on function */
-	if (OidIsValid(funcid))
-	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = funcid;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
-	}
-
-	/* dependency on extension */
-	recordDependencyOnCurrentExtension(&myself, false);
-
-	/* Post creation hook for new cast */
-	InvokeObjectPostCreateHook(CastRelationId, castid, 0);
-
-	heap_freetuple(tuple);
-
-	table_close(relation, RowExclusiveLock);
-
+	myself = CastCreate(sourcetypeid, targettypeid, funcid, castcontext,
+						castmethod, DEPENDENCY_NORMAL);
 	return myself;
 }
 
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index 52097363fd..6c9c450a8f 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -42,6 +42,7 @@
 #include "catalog/objectaccess.h"
 #include "catalog/pg_am.h"
 #include "catalog/pg_authid.h"
+#include "catalog/pg_cast.h"
 #include "catalog/pg_collation.h"
 #include "catalog/pg_constraint.h"
 #include "catalog/pg_depend.h"
@@ -85,9 +86,14 @@ typedef struct
 
 /* Potentially set by pg_upgrade_support functions */
 Oid			binary_upgrade_next_array_pg_type_oid = InvalidOid;
+Oid			binary_upgrade_next_mrng_pg_type_oid = InvalidOid;
+Oid			binary_upgrade_next_mrng_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 rangeOid,
+									   Oid rangeArrayOid, Oid *castFuncOid);
 static Oid	findTypeInputFunction(List *procname, Oid typeOid);
 static Oid	findTypeOutputFunction(List *procname, Oid typeOid);
 static Oid	findTypeReceiveFunction(List *procname, Oid typeOid);
@@ -811,7 +817,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",
@@ -1354,7 +1361,11 @@ DefineRange(CreateRangeStmt *stmt)
 	Oid			typeNamespace;
 	Oid			typoid;
 	char	   *rangeArrayName;
+	char	   *multirangeTypeName;
+	char	   *multirangeArrayName;
 	Oid			rangeArrayOid;
+	Oid			multirangeOid;
+	Oid			multirangeArrayOid;
 	Oid			rangeSubtype = InvalidOid;
 	List	   *rangeSubOpclassName = NIL;
 	List	   *rangeCollationName = NIL;
@@ -1371,6 +1382,8 @@ DefineRange(CreateRangeStmt *stmt)
 	AclResult	aclresult;
 	ListCell   *lc;
 	ObjectAddress address;
+	ObjectAddress mltrngaddress;
+	Oid			castFuncOid;
 
 	/* Convert list of names to a name and namespace */
 	typeNamespace = QualifiedNameGetCreationNamespace(stmt->typeName,
@@ -1519,8 +1532,10 @@ DefineRange(CreateRangeStmt *stmt)
 	/* alignment must be 'i' or 'd' for ranges */
 	alignment = (subtypalign == 'd') ? 'd' : 'i';
 
-	/* Allocate OID for array type */
+	/* Allocate OID for array type, its multirange, and its multirange array */
 	rangeArrayOid = AssignTypeArrayOid();
+	multirangeOid = AssignTypeMultirangeOid();
+	multirangeArrayOid = AssignTypeMultirangeArrayOid();
 
 	/* Create the pg_type entry */
 	address =
@@ -1557,9 +1572,46 @@ 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(multirangeOid,	/* force assignment of this 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) */
+	Assert(multirangeOid == mltrngaddress.objectId);
+
 	/* Create the entry in pg_range */
 	RangeCreate(typoid, rangeSubtype, rangeCollation, rangeSubOpclass,
-				rangeCanonical, rangeSubtypeDiff);
+				rangeCanonical, rangeSubtypeDiff, multirangeOid);
 
 	/*
 	 * Create the array type that goes with it.
@@ -1600,8 +1652,53 @@ 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 */
+			   multirangeOid,	/* 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,
+							   multirangeOid, typoid, rangeArrayOid,
+							   &castFuncOid);
+
+	/* Create cast from the range type to its multirange type */
+	CastCreate(typoid, multirangeOid, castFuncOid, 'e', 'f', DEPENDENCY_INTERNAL);
+
+	pfree(multirangeTypeName);
+	pfree(multirangeArrayName);
 
 	return address;
 }
@@ -1679,6 +1776,147 @@ 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.
+ *
+ * Sets castFuncOid to the oid of the new constructor that can be used
+ * to cast from a range to a multirange.
+ */
+static void
+makeMultirangeConstructors(const char *name, Oid namespace,
+						   Oid multirangeOid, Oid rangeOid, Oid rangeArrayOid,
+						   Oid *castFuncOid)
+{
+	ObjectAddress myself,
+				referenced;
+	oidvector  *argtypes;
+	Datum		allParamTypes;
+	ArrayType  *allParameterTypes;
+	Datum		paramModes;
+	ArrayType  *parameterModes;
+
+	referenced.classId = TypeRelationId;
+	referenced.objectId = multirangeOid;
+	referenced.objectSubId = 0;
+
+	/* 0-arg constructor - for empty multiranges */
+	argtypes = buildoidvector(NULL, 0);
+	myself = ProcedureCreate(name,	/* name: same as multirange type */
+							 namespace,
+							 false,	/* replace */
+							 false,	/* returns set */
+							 multirangeOid,	/* return type */
+							 BOOTSTRAP_SUPERUSERID,	/* proowner */
+							 INTERNALlanguageId,	/* language */
+							 F_FMGR_INTERNAL_VALIDATOR,
+							 "multirange_constructor0",	/* prosrc */
+							 NULL,	/* probin */
+							 PROKIND_FUNCTION,
+							 false,	/* security_definer */
+							 false,	/* leakproof */
+							 false, /* isStrict */
+							 PROVOLATILE_IMMUTABLE,	/* volatility */
+							 PROPARALLEL_SAFE,	/* parallel safety */
+							 argtypes,	/* parameterTypes */
+							 PointerGetDatum(NULL),	/* allParameterTypes */
+							 PointerGetDatum(NULL),	/* parameterModes */
+							 PointerGetDatum(NULL),	/* parameterNames */
+							 NIL,	/* parameterDefaults */
+							 PointerGetDatum(NULL),	/* trftypes */
+							 PointerGetDatum(NULL),	/* proconfig */
+							 InvalidOid,	/* prosupport */
+							 1.0,	/* procost */
+							 0.0);	/* prorows */
+	/*
+	 * Make the constructor 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);
+	pfree(argtypes);
+
+	/*
+	 * 1-arg constructor - for casts
+	 *
+	 * In theory we shouldn't need both this and the vararg (n-arg) constructor,
+	 * but having a separate 1-arg function lets us define casts against it.
+	 */
+	argtypes = buildoidvector(&rangeOid, 1);
+	myself = ProcedureCreate(name,	/* name: same as multirange type */
+							 namespace,
+							 false,	/* replace */
+							 false,	/* returns set */
+							 multirangeOid,	/* return type */
+							 BOOTSTRAP_SUPERUSERID,	/* proowner */
+							 INTERNALlanguageId,	/* language */
+							 F_FMGR_INTERNAL_VALIDATOR,
+							 "multirange_constructor1",	/* prosrc */
+							 NULL,	/* probin */
+							 PROKIND_FUNCTION,
+							 false,	/* security_definer */
+							 false,	/* leakproof */
+							 true, /* isStrict */
+							 PROVOLATILE_IMMUTABLE,	/* volatility */
+							 PROPARALLEL_SAFE,	/* parallel safety */
+							 argtypes,	/* parameterTypes */
+							 PointerGetDatum(NULL),	/* allParameterTypes */
+							 PointerGetDatum(NULL),	/* parameterModes */
+							 PointerGetDatum(NULL),	/* parameterNames */
+							 NIL,	/* parameterDefaults */
+							 PointerGetDatum(NULL),	/* trftypes */
+							 PointerGetDatum(NULL),	/* proconfig */
+							 InvalidOid,	/* prosupport */
+							 1.0,	/* procost */
+							 0.0);	/* prorows */
+	/* ditto */
+	recordDependencyOn(&myself, &referenced, DEPENDENCY_INTERNAL);
+	pfree(argtypes);
+	*castFuncOid = myself.objectId;
+
+	/* n-arg constructor - vararg */
+	argtypes = buildoidvector(&rangeArrayOid, 1);
+	allParamTypes = ObjectIdGetDatum(rangeArrayOid);
+	allParameterTypes = construct_array(&allParamTypes,
+										1, OIDOID,
+										sizeof(Oid), true, 'i');
+	paramModes = CharGetDatum(FUNC_PARAM_VARIADIC);
+	parameterModes = construct_array(&paramModes, 1, CHAROID,
+									 1, true, 'c');
+	myself = ProcedureCreate(name,	/* name: same as multirange type */
+							 namespace,
+							 false,	/* replace */
+							 false,	/* returns set */
+							 multirangeOid,	/* return type */
+							 BOOTSTRAP_SUPERUSERID,	/* proowner */
+							 INTERNALlanguageId,	/* language */
+							 F_FMGR_INTERNAL_VALIDATOR,
+							 "multirange_constructor2",	/* prosrc */
+							 NULL,	/* probin */
+							 PROKIND_FUNCTION,
+							 false,	/* security_definer */
+							 false,	/* leakproof */
+							 false, /* isStrict */
+							 PROVOLATILE_IMMUTABLE,	/* volatility */
+							 PROPARALLEL_SAFE,	/* parallel safety */
+							 argtypes,	/* parameterTypes */
+							 PointerGetDatum(allParameterTypes),	/* allParameterTypes */
+							 PointerGetDatum(parameterModes),	/* parameterModes */
+							 PointerGetDatum(NULL),	/* parameterNames */
+							 NIL,	/* parameterDefaults */
+							 PointerGetDatum(NULL),	/* trftypes */
+							 PointerGetDatum(NULL),	/* proconfig */
+							 InvalidOid,	/* prosupport */
+							 1.0,	/* procost */
+							 0.0);	/* prorows */
+	/* ditto */
+	recordDependencyOn(&myself, &referenced, DEPENDENCY_INTERNAL);
+	pfree(argtypes);
+	pfree(allParameterTypes);
+	pfree(parameterModes);
+}
 
 /*
  * Find suitable I/O functions for a type.
@@ -2104,6 +2342,72 @@ AssignTypeArrayOid(void)
 	return type_array_oid;
 }
 
+/*
+ *	AssignTypeMultirangeOid
+ *
+ *	Pre-assign the range type's multirange OID for use in pg_type.oid
+ */
+Oid
+AssignTypeMultirangeOid(void)
+{
+	Oid			type_multirange_oid;
+
+	/* Use binary-upgrade override for pg_type.oid? */
+	if (IsBinaryUpgrade)
+	{
+		if (!OidIsValid(binary_upgrade_next_mrng_pg_type_oid))
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+					 errmsg("pg_type multirange OID value not set when in binary upgrade mode")));
+
+		type_multirange_oid = binary_upgrade_next_mrng_pg_type_oid;
+		binary_upgrade_next_mrng_pg_type_oid = InvalidOid;
+	}
+	else
+	{
+		Relation	pg_type = table_open(TypeRelationId, AccessShareLock);
+
+		type_multirange_oid = GetNewOidWithIndex(pg_type, TypeOidIndexId,
+												 Anum_pg_type_oid);
+		table_close(pg_type, AccessShareLock);
+	}
+
+	return type_multirange_oid;
+}
+
+/*
+ *	AssignTypeMultirangeArrayOid
+ *
+ *	Pre-assign the range type's multirange array OID for use in pg_type.typarray
+ */
+Oid
+AssignTypeMultirangeArrayOid(void)
+{
+	Oid			type_multirange_array_oid;
+
+	/* Use binary-upgrade override for pg_type.oid? */
+	if (IsBinaryUpgrade)
+	{
+		if (!OidIsValid(binary_upgrade_next_mrng_array_pg_type_oid))
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+					 errmsg("pg_type multirange array OID value not set when in binary upgrade mode")));
+
+		type_multirange_array_oid = binary_upgrade_next_mrng_array_pg_type_oid;
+		binary_upgrade_next_mrng_array_pg_type_oid = InvalidOid;
+	}
+	else
+	{
+		Relation	pg_type = table_open(TypeRelationId, AccessShareLock);
+
+		type_multirange_array_oid = GetNewOidWithIndex(pg_type, TypeOidIndexId,
+													   Anum_pg_type_oid);
+		table_close(pg_type, AccessShareLock);
+	}
+
+	return type_multirange_array_oid;
+}
+
 
 /*-------------------------------------------------------------------
  * DefineCompositeType
diff --git a/src/backend/parser/parse_coerce.c b/src/backend/parser/parse_coerce.c
index 929f758ef4..47d17f43a4 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
@@ -1481,6 +1483,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;
@@ -1527,6 +1531,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 */
@@ -1577,6 +1590,39 @@ check_generic_type_consistency(const Oid *actual_arg_types,
 			/* otherwise, they better match */
 			return false;
 		}
+		else
+			range_typelem = InvalidOid;	/* keep compiler quiet */
+	}
+
+	/* 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)
@@ -1680,13 +1726,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.
@@ -1744,7 +1794,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;
@@ -1762,6 +1812,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;
+		}
 	}
 
 	/*
@@ -1812,9 +1882,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
@@ -1846,6 +1919,71 @@ enforce_generic_type_consistency(const Oid *actual_arg_types,
 							   format_type_be(range_typeid),
 							   format_type_be(elem_typeid))));
 		}
+		else
+			range_typelem = InvalidOid;
+	}
+
+	/* 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))
@@ -1855,6 +1993,7 @@ enforce_generic_type_consistency(const Oid *actual_arg_types,
 			elem_typeid = ANYELEMENTOID;
 			array_typeid = ANYARRAYOID;
 			range_typeid = ANYRANGEOID;
+			multirange_typeid = ANYMULTIRANGEOID;
 		}
 		else
 		{
@@ -1927,6 +2066,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;
+			}
 		}
 	}
 
@@ -1958,6 +2108,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 ||
@@ -2006,8 +2172,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);
@@ -2019,11 +2184,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)
 		{
@@ -2051,6 +2242,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)
@@ -2059,6 +2263,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 */
@@ -2173,6 +2445,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..f1342181a0
--- /dev/null
+++ b/src/backend/utils/adt/multirangetypes.c
@@ -0,0 +1,2171 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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;
+	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 = NULL;
+	int32		range_str_len;
+	char	   *range_str_copy;
+
+	cache = get_multirange_io_data(fcinfo, mltrngtypoid, IOFunc_input);
+	rangetyp = cache->typcache->rngtype;
+
+	/* 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]);
+		uint32		range_len;
+		char	   *range_data;
+
+		range = PointerGetDatum(SendFunctionCall(&cache->proc, range));
+		range_len = VARSIZE(range) - VARHDRSZ;
+		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_constructor2(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));
+}
+
+/*
+ * Construct multirange value from a single range.
+ * It'd be nice if we could just use multirange_constructor2
+ * for this case, but we need a non-variadic single-arg function
+ * to let us define a CAST from a range to its multirange.
+ */
+Datum
+multirange_constructor1(PG_FUNCTION_ARGS)
+{
+	Oid			mltrngtypid = get_fn_expr_rettype(fcinfo->flinfo);
+	Oid			rngtypid;
+	TypeCacheEntry *typcache;
+	TypeCacheEntry *rangetyp;
+	RangeType *range;
+
+	typcache = multirange_get_typcache(fcinfo, mltrngtypid);
+	rangetyp = typcache->rngtype;
+
+	/*
+	 * 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")));
+
+	range = PG_GETARG_RANGE_P(0);
+
+	/* Make sure the range type matches. */
+	rngtypid = RangeTypeGetOid(range);
+	if (rngtypid != rangetyp->type_id)
+		ereport(ERROR, (errmsg("type %u does not match constructor type", rngtypid)));
+
+	PG_RETURN_MULTIRANGE_P(make_multirange(mltrngtypid, rangetyp, 1, &range));
+}
+
+/*
+ * 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 -> multirange type functions */
+
+/* multirange union */
+Datum
+multirange_union(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
+	MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count1;
+	int32		range_count2;
+	int32		range_count3;
+	RangeType **ranges1;
+	RangeType **ranges2;
+	RangeType **ranges3;
+
+	if (MultirangeIsEmpty(mr1))
+		PG_RETURN_MULTIRANGE_P(mr2);
+	if (MultirangeIsEmpty(mr2))
+		PG_RETURN_MULTIRANGE_P(mr1);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr1));
+
+	multirange_deserialize(mr1, &range_count1, &ranges1);
+	multirange_deserialize(mr2, &range_count2, &ranges2);
+
+	range_count3 = range_count1 + range_count2;
+	ranges3 = palloc0(range_count3 * sizeof(RangeType *));
+	memcpy(ranges3, ranges1, range_count1 * sizeof(RangeType *));
+	memcpy(ranges3 + range_count1, ranges2, range_count2 * sizeof(RangeType *));
+	PG_RETURN_MULTIRANGE_P(make_multirange(typcache->type_id, typcache->rngtype,
+										   range_count3, ranges3));
+}
+
+/* multirange minus */
+Datum
+multirange_minus(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
+	MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
+	Oid			mltrngtypoid = MultirangeTypeGetOid(mr1);
+	TypeCacheEntry *typcache;
+	TypeCacheEntry *rangetyp;
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+
+	typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
+	rangetyp = typcache->rngtype;
+
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		PG_RETURN_MULTIRANGE_P(mr1);
+
+	multirange_deserialize(mr1, &range_count1, &ranges1);
+	multirange_deserialize(mr2, &range_count2, &ranges2);
+
+	PG_RETURN_MULTIRANGE_P(multirange_minus_internal(mltrngtypoid,
+													 rangetyp,
+													 range_count1,
+													 ranges1,
+													 range_count2,
+													 ranges2));
+}
+
+MultirangeType *
+multirange_minus_internal(Oid mltrngtypoid, TypeCacheEntry *rangetyp,
+						 int32 range_count1, RangeType **ranges1,
+						 int32 range_count2, RangeType **ranges2)
+{
+	RangeType  *r1;
+	RangeType  *r2;
+	RangeType **ranges3;
+	int32		range_count3;
+	int32		i1;
+	int32		i2;
+
+	/*
+	 * Worst case: every range in ranges1 makes a different cut to some range
+	 * in ranges2.
+	 */
+	ranges3 = palloc0((range_count1 + range_count2) * sizeof(RangeType *));
+	range_count3 = 0;
+
+	/*
+	 * For each range in mr1, keep subtracting until it's gone or the ranges
+	 * in mr2 have passed it. After a subtraction we assign what's left back
+	 * to r1. The parallel progress through mr1 and mr2 is similar to
+	 * multirange_overlaps_multirange_internal.
+	 */
+	r2 = ranges2[0];
+	for (i1 = 0, i2 = 0; i1 < range_count1; i1++)
+	{
+		r1 = ranges1[i1];
+
+		/* Discard r2s while r2 << r1 */
+		while (r2 != NULL && range_before_internal(rangetyp, r2, r1))
+		{
+			r2 = ++i2 >= range_count2 ? NULL : ranges2[i2];
+		}
+
+		while (r2 != NULL)
+		{
+			if (range_split_internal(rangetyp, r1, r2, &ranges3[range_count3], &r1))
+			{
+				/*
+				 * If r2 takes a bite out of the middle of r1, we need two
+				 * outputs
+				 */
+				range_count3++;
+				r2 = ++i2 >= range_count2 ? NULL : ranges2[i2];
+
+			}
+			else if (range_overlaps_internal(rangetyp, r1, r2))
+			{
+				/*
+				 * If r2 overlaps r1, replace r1 with r1 - r2.
+				 */
+				r1 = range_minus_internal(rangetyp, r1, r2);
+
+				/*
+				 * If r2 goes past r1, then we need to stay with it, in case
+				 * it hits future r1s. Otherwise we need to keep r1, in case
+				 * future r2s hit it. Since we already subtracted, there's no
+				 * point in using the overright/overleft calls.
+				 */
+				if (RangeIsEmpty(r1) || range_before_internal(rangetyp, r1, r2))
+					break;
+				else
+					r2 = ++i2 >= range_count2 ? NULL : ranges2[i2];
+
+			}
+			else
+			{
+				/*
+				 * This and all future r2s are past r1, so keep them. Also
+				 * assign whatever is left of r1 to the result.
+				 */
+				break;
+			}
+		}
+
+		/*
+		 * Nothing else can remove anything from r1, so keep it. Even if r1 is
+		 * empty here, make_multirange will remove it.
+		 */
+		ranges3[range_count3++] = r1;
+	}
+
+	return make_multirange(mltrngtypoid, rangetyp, range_count3, ranges3);
+}
+
+/* multirange intersection */
+Datum
+multirange_intersect(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
+	MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
+	Oid			mltrngtypoid = MultirangeTypeGetOid(mr1);
+	TypeCacheEntry *typcache;
+	TypeCacheEntry *rangetyp;
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+
+	typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
+	rangetyp = typcache->rngtype;
+
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		PG_RETURN_MULTIRANGE_P(make_empty_multirange(mltrngtypoid, rangetyp));
+
+	multirange_deserialize(mr1, &range_count1, &ranges1);
+	multirange_deserialize(mr2, &range_count2, &ranges2);
+
+	PG_RETURN_MULTIRANGE_P(multirange_intersect_internal(mltrngtypoid,
+														 rangetyp,
+														 range_count1,
+														 ranges1,
+														 range_count2,
+														 ranges2));
+}
+
+MultirangeType *
+multirange_intersect_internal(Oid mltrngtypoid, TypeCacheEntry *rangetyp,
+							  int32 range_count1, RangeType **ranges1,
+							  int32 range_count2, RangeType **ranges2)
+{
+	RangeType  *r1;
+	RangeType  *r2;
+	RangeType **ranges3;
+	int32		range_count3;
+	int32		i1;
+	int32		i2;
+
+	if (range_count1 == 0 || range_count2 == 0)
+		return make_multirange(mltrngtypoid, rangetyp, 0, NULL);
+
+	/*-----------------------------------------------
+	 * Worst case is a stitching pattern like this:
+	 *
+	 * mr1: --- --- --- ---
+	 * mr2:   --- --- ---
+	 * mr3:   - - - - - -
+	 *
+	 * That seems to be range_count1 + range_count2 - 1,
+	 * but one extra won't hurt.
+	 *-----------------------------------------------
+	 */
+	ranges3 = palloc0((range_count1 + range_count2) * sizeof(RangeType *));
+	range_count3 = 0;
+
+	/*
+	 * For each range in mr1, keep intersecting until the ranges in mr2 have
+	 * passed it. The parallel progress through mr1 and mr2 is similar to
+	 * multirange_minus_multirange_internal, but we don't have to assign back
+	 * to r1.
+	 */
+	r2 = ranges2[0];
+	for (i1 = 0, i2 = 0; i1 < range_count1; i1++)
+	{
+		r1 = ranges1[i1];
+
+		/* Discard r2s while r2 << r1 */
+		while (r2 != NULL && range_before_internal(rangetyp, r2, r1))
+		{
+			r2 = ++i2 >= range_count2 ? NULL : ranges2[i2];
+		}
+
+		while (r2 != NULL)
+		{
+			if (range_overlaps_internal(rangetyp, r1, r2))
+			{
+				/* Keep the overlapping part */
+				ranges3[range_count3++] = range_intersect_internal(rangetyp, r1, r2);
+
+				/* If we "used up" all of r2, go to the next one... */
+				if (range_overleft_internal(rangetyp, r2, r1))
+					r2 = ++i2 >= range_count2 ? NULL : ranges2[i2];
+
+				/* ...otherwise go to the next r1 */
+				else
+					break;
+			}
+			else
+				/* We're past r1, so move to the next one */
+				break;
+		}
+
+		/* If we're out of r2s, there can be no more intersections */
+		if (r2 == NULL)
+			break;
+	}
+
+	return make_multirange(mltrngtypoid, rangetyp, range_count3, ranges3);
+}
+
+/*
+ * range_agg_transfn: combine adjacent/overlapping ranges.
+ *
+ * All we do here is gather the input ranges into an array
+ * so that the finalfn can sort and combine them.
+ */
+Datum
+range_agg_transfn(PG_FUNCTION_ARGS)
+{
+	MemoryContext aggContext;
+	Oid			rngtypoid;
+	ArrayBuildState *state;
+
+	if (!AggCheckCallContext(fcinfo, &aggContext))
+		elog(ERROR, "range_agg_transfn called in non-aggregate context");
+
+	rngtypoid = get_fn_expr_argtype(fcinfo->flinfo, 1);
+	if (!type_is_range(rngtypoid))
+		ereport(ERROR, (errmsg("range_agg must be called with a range")));
+
+	if (PG_ARGISNULL(0))
+		state = initArrayResult(rngtypoid, aggContext, false);
+	else
+		state = (ArrayBuildState *) PG_GETARG_POINTER(0);
+
+	/* skip NULLs */
+	if (!PG_ARGISNULL(1))
+		accumArrayResult(state, PG_GETARG_DATUM(1), false, rngtypoid, aggContext);
+
+	PG_RETURN_POINTER(state);
+}
+
+/*
+ * range_agg_finalfn: use our internal array to merge touching ranges.
+ */
+Datum
+range_agg_finalfn(PG_FUNCTION_ARGS)
+{
+	MemoryContext aggContext;
+	Oid			mltrngtypoid;
+	TypeCacheEntry *typcache;
+	ArrayBuildState *state;
+	int32		range_count;
+	RangeType **ranges;
+	int			i;
+
+	if (!AggCheckCallContext(fcinfo, &aggContext))
+		elog(ERROR, "range_agg_finalfn called in non-aggregate context");
+
+	state = PG_ARGISNULL(0) ? NULL : (ArrayBuildState *) PG_GETARG_POINTER(0);
+	if (state == NULL)
+		/* This shouldn't be possible, but just in case.... */
+		PG_RETURN_NULL();
+
+	/* Also return NULL if we had zero inputs, like other aggregates */
+	range_count = state->nelems;
+	if (range_count == 0)
+		PG_RETURN_NULL();
+
+	mltrngtypoid = get_fn_expr_rettype(fcinfo->flinfo);
+	typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
+
+	ranges = palloc0(range_count * sizeof(RangeType *));
+	for (i = 0; i < range_count; i++)
+		ranges[i] = DatumGetRangeTypeP(state->dvalues[i]);
+
+	PG_RETURN_MULTIRANGE_P(make_multirange(mltrngtypoid, typcache->rngtype, range_count, ranges));
+}
+
+Datum
+multirange_intersect_agg_transfn(PG_FUNCTION_ARGS)
+{
+	MemoryContext aggContext;
+	Oid			mltrngtypoid;
+	TypeCacheEntry *typcache;
+	MultirangeType *result;
+	MultirangeType *current;
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+
+	if (!AggCheckCallContext(fcinfo, &aggContext))
+		elog(ERROR, "multirange_intersect_agg_transfn called in non-aggregate context");
+
+	mltrngtypoid = get_fn_expr_argtype(fcinfo->flinfo, 1);
+	if (!type_is_multirange(mltrngtypoid))
+		ereport(ERROR, (errmsg("range_intersect_agg must be called with a multirange")));
+
+	typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
+
+	/* strictness ensures these are non-null */
+	result = PG_GETARG_MULTIRANGE_P(0);
+	current = PG_GETARG_MULTIRANGE_P(1);
+
+	multirange_deserialize(result, &range_count1, &ranges1);
+	multirange_deserialize(current, &range_count2, &ranges2);
+
+	result = multirange_intersect_internal(mltrngtypoid,
+										   typcache->rngtype,
+										   range_count1,
+										   ranges1,
+										   range_count2,
+										   ranges2);
+	PG_RETURN_RANGE_P(result);
+}
+
+
+/* multirange -> element type functions */
+
+/* extract lower bound value */
+Datum
+multirange_lower(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+	bool		isnull;
+	Datum		result;
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_NULL();
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	result = range_lower_internal(typcache->rngtype, ranges[0], &isnull);
+
+	if (isnull)
+		PG_RETURN_NULL();
+	else
+		PG_RETURN_DATUM(result);
+}
+
+/* extract upper bound value */
+Datum
+multirange_upper(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+	bool		isnull;
+	Datum		result;
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_NULL();
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	result = range_upper_internal(typcache->rngtype, ranges[range_count - 1], &isnull);
+
+	if (isnull)
+		PG_RETURN_NULL();
+	else
+		PG_RETURN_DATUM(result);
+}
+
+
+/* multirange -> bool functions */
+
+/* is multirange empty? */
+Datum
+multirange_empty(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+
+	PG_RETURN_BOOL(mr->rangeCount == 0);
+}
+
+/* is lower bound inclusive? */
+Datum
+multirange_lower_inc(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	int32		range_count;
+	RangeType **ranges;
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_has_flag(ranges[0], RANGE_LB_INC));
+}
+
+/* is upper bound inclusive? */
+Datum
+multirange_upper_inc(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	int32		range_count;
+	RangeType **ranges;
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_has_flag(ranges[range_count - 1], RANGE_UB_INC));
+}
+
+/* is lower bound infinite? */
+Datum
+multirange_lower_inf(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	int32		range_count;
+	RangeType **ranges;
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_has_flag(ranges[0], RANGE_LB_INF));
+}
+
+/* is upper bound infinite? */
+Datum
+multirange_upper_inf(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	int32		range_count;
+	RangeType **ranges;
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_has_flag(ranges[range_count - 1], RANGE_UB_INF));
+}
+
+
+
+/* multirange, element -> bool functions */
+
+/* contains? */
+Datum
+multirange_contains_elem(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	Datum		val = PG_GETARG_DATUM(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(multirange_contains_elem_internal(typcache, mr, val));
+}
+
+/* contained by? */
+Datum
+elem_contained_by_multirange(PG_FUNCTION_ARGS)
+{
+	Datum		val = PG_GETARG_DATUM(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(multirange_contains_elem_internal(typcache, mr, val));
+}
+
+/*
+ * Test whether multirange mr contains a specific element value.
+ */
+bool
+multirange_contains_elem_internal(TypeCacheEntry *typcache, MultirangeType * mr, Datum val)
+{
+	TypeCacheEntry *rangetyp;
+	int32		range_count;
+	RangeType **ranges;
+	RangeType  *r;
+	int			i;
+
+	rangetyp = typcache->rngtype;
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	for (i = 0; i < range_count; i++)
+	{
+		r = ranges[i];
+		if (range_contains_elem_internal(rangetyp, r, val))
+			return true;
+	}
+
+	return false;
+}
+
+/* multirange, range -> bool functions */
+
+/* contains? */
+Datum
+multirange_contains_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(multirange_contains_range_internal(typcache, mr, r));
+}
+
+/* contained by? */
+Datum
+range_contained_by_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(multirange_contains_range_internal(typcache, mr, r));
+}
+
+/*
+ * Test whether multirange mr contains a specific range r.
+ */
+bool
+multirange_contains_range_internal(TypeCacheEntry *typcache, MultirangeType * mr, RangeType *r)
+{
+	TypeCacheEntry *rangetyp;
+	int32		range_count;
+	RangeType **ranges;
+	RangeType  *mrr;
+	int			i;
+
+	rangetyp = typcache->rngtype;
+
+	/*
+	 * Every multirange contains an infinite number of empty ranges, even an
+	 * empty one.
+	 */
+	if (RangeIsEmpty(r))
+		return true;
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	for (i = 0; i < range_count; i++)
+	{
+		mrr = ranges[i];
+		if (range_contains_internal(rangetyp, mrr, r))
+			return true;
+	}
+
+	return false;
+}
+
+
+/* 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));
+}
+
+/* overlaps? */
+Datum
+range_overlaps_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(range_overlaps_multirange_internal(typcache, r, mr));
+}
+
+Datum
+multirange_overlaps_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(range_overlaps_multirange_internal(typcache, r, mr));
+}
+
+Datum
+multirange_overlaps_multirange(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_overlaps_multirange_internal(typcache, mr1, mr2));
+}
+
+bool
+range_overlaps_multirange_internal(TypeCacheEntry *typcache, RangeType *r, MultirangeType * mr)
+{
+	TypeCacheEntry *rangetyp;
+	int32		range_count;
+	int32		i;
+	RangeType **ranges;
+	RangeType  *mrr;
+
+	/*
+	 * Empties never overlap, even with empties. (This seems strange since
+	 * they *do* contain each other, but we want to follow how ranges work.)
+	 */
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		return false;
+
+	rangetyp = typcache->rngtype;
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	/* Scan through mrr and once it gets to r they either overlap or not */
+	mrr = ranges[0];
+
+	/* Discard mrrs while mrr << r */
+	i = 0;
+	while (range_before_internal(rangetyp, mrr, r))
+	{
+		if (++i >= range_count)
+			return false;
+		mrr = ranges[i];
+	}
+
+	/* Now either we overlap or we passed r */
+	return range_overlaps_internal(rangetyp, mrr, r);
+}
+
+bool
+multirange_overlaps_multirange_internal(TypeCacheEntry *typcache, MultirangeType * mr1,
+										MultirangeType * mr2)
+{
+	TypeCacheEntry *rangetyp;
+	int32		range_count1;
+	int32		range_count2;
+	int32		i1;
+	int32		i2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+	RangeType  *r1;
+	RangeType  *r2;
+
+	/*
+	 * Empties never overlap, even with empties. (This seems strange since
+	 * they *do* contain each other, but we want to follow how ranges work.)
+	 */
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		return false;
+
+	rangetyp = typcache->rngtype;
+
+	multirange_deserialize(mr1, &range_count1, &ranges1);
+	multirange_deserialize(mr2, &range_count2, &ranges2);
+
+	/*
+	 * Every range in mr1 gets a chance to overlap with the ranges in mr2, but
+	 * we can use their ordering to avoid O(n^2). This is similar to
+	 * range_overlaps_multirange where r1 : r2 :: mrr : r, but there if we
+	 * don't find an overlap with r we're done, and here if we don't find an
+	 * overlap with r2 we try the next r2.
+	 */
+	r1 = ranges1[0];
+	for (i1 = 0, i2 = 0; i2 < range_count2; i2++)
+	{
+		r2 = ranges2[i2];
+
+		/* Discard r1s while r1 << r2 */
+		while (range_before_internal(rangetyp, r1, r2))
+		{
+			if (++i1 >= range_count1)
+				return false;
+			r1 = ranges1[i1];
+		}
+
+		/*
+		 * If r1 && r2, we're done, otherwise we failed to find an overlap for
+		 * r2, so go to the next one.
+		 */
+		if (range_overlaps_internal(rangetyp, r1, r2))
+			return true;
+	}
+
+	/* We looked through all of mr2 without finding an overlap */
+	return false;
+}
+
+/* does not extend to right of? */
+Datum
+range_overleft_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_overleft_internal(typcache->rngtype, r, ranges[range_count - 1]));
+}
+
+Datum
+multirange_overleft_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_overleft_internal(typcache->rngtype, ranges[range_count - 1], r));
+}
+
+Datum
+multirange_overleft_multirange(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
+	MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr1));
+
+	multirange_deserialize(mr1, &range_count1, &ranges1);
+	multirange_deserialize(mr2, &range_count2, &ranges2);
+
+	PG_RETURN_BOOL(range_overleft_internal(typcache->rngtype, ranges1[range_count1 - 1], ranges2[range_count2 - 1]));
+}
+
+/* does not extend to left of? */
+Datum
+range_overright_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_overright_internal(typcache->rngtype, r, ranges[0]));
+}
+
+Datum
+multirange_overright_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_overright_internal(typcache->rngtype, ranges[0], r));
+}
+
+Datum
+multirange_overright_multirange(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
+	MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr1));
+
+	multirange_deserialize(mr1, &range_count1, &ranges1);
+	multirange_deserialize(mr2, &range_count2, &ranges2);
+
+	PG_RETURN_BOOL(range_overright_internal(typcache->rngtype, ranges1[0], ranges2[0]));
+}
+
+/* contains? */
+Datum
+multirange_contains_multirange(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_contains_multirange_internal(typcache, mr1, mr2));
+}
+
+/* contained by? */
+Datum
+multirange_contained_by_multirange(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_contains_multirange_internal(typcache, mr2, mr1));
+}
+
+/*
+ * Test whether multirange mr1 contains every range from another multirange mr2.
+ */
+bool
+multirange_contains_multirange_internal(TypeCacheEntry *typcache,
+										MultirangeType * mr1, MultirangeType * mr2)
+{
+	TypeCacheEntry *rangetyp;
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+	RangeType  *r1;
+	RangeType  *r2;
+	int			i1,
+				i2;
+
+	rangetyp = typcache->rngtype;
+
+	multirange_deserialize(mr1, &range_count1, &ranges1);
+	multirange_deserialize(mr2, &range_count2, &ranges2);
+
+	/*
+	 * We follow the same logic for empties as ranges: - an empty multirange
+	 * contains an empty range/multirange. - an empty multirange can't contain
+	 * any other range/multirange. - an empty multirange is contained by any
+	 * other range/multirange.
+	 */
+
+	if (range_count2 == 0)
+		return true;
+	if (range_count1 == 0)
+		return false;
+
+	/*
+	 * Every range in mr2 must be contained by some range in mr1. To avoid
+	 * O(n^2) we walk through both ranges in tandem.
+	 */
+	r1 = ranges1[0];
+	for (i1 = 0, i2 = 0; i2 < range_count2; i2++)
+	{
+		r2 = ranges2[i2];
+
+		/* Discard r1s while r1 << r2 */
+		while (range_before_internal(rangetyp, r1, r2))
+		{
+			if (++i1 >= range_count1)
+				return false;
+			r1 = ranges1[i1];
+		}
+
+		/*
+		 * If r1 @> r2, go to the next r2, otherwise return false (since every
+		 * r1[n] and r1[n+1] must have a gap). Note this will give weird
+		 * answers if you don't canonicalize, e.g. with a custom
+		 * int2multirange {[1,1], [2,2]} there is a "gap". But that is
+		 * consistent with other range operators, e.g. '[1,1]'::int2range -|-
+		 * '[2,2]'::int2range is false.
+		 */
+		if (!range_contains_internal(rangetyp, r1, r2))
+			return false;
+	}
+
+	/* All ranges in mr2 are satisfied */
+	return true;
+}
+
+/* strictly left of? */
+Datum
+range_before_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(range_before_multirange_internal(typcache, r, mr));
+}
+
+Datum
+multirange_before_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(range_after_multirange_internal(typcache, r, mr));
+}
+
+Datum
+multirange_before_multirange(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_before_multirange_internal(typcache, mr1, mr2));
+}
+
+/* strictly right of? */
+Datum
+range_after_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(range_after_multirange_internal(typcache, r, mr));
+}
+
+Datum
+multirange_after_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(range_before_multirange_internal(typcache, r, mr));
+}
+
+Datum
+multirange_after_multirange(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_before_multirange_internal(typcache, mr2, mr1));
+}
+
+/* strictly left of? (internal version) */
+bool
+range_before_multirange_internal(TypeCacheEntry *typcache, RangeType *r,
+								 MultirangeType * mr)
+{
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		return false;
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	return range_before_internal(typcache->rngtype, r, ranges[0]);
+}
+
+bool
+multirange_before_multirange_internal(TypeCacheEntry *typcache, MultirangeType * mr1,
+									  MultirangeType * mr2)
+{
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		return false;
+
+	multirange_deserialize(mr1, &range_count1, &ranges1);
+	multirange_deserialize(mr2, &range_count2, &ranges2);
+
+	return range_before_internal(typcache->rngtype, ranges1[range_count1 - 1],
+								 ranges2[0]);
+}
+
+/* strictly right of? (internal version) */
+bool
+range_after_multirange_internal(TypeCacheEntry *typcache, RangeType *r,
+								MultirangeType * mr)
+{
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		return false;
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	return range_after_internal(typcache->rngtype, r, ranges[range_count - 1]);
+}
+
+/* adjacent to? */
+Datum
+range_adjacent_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		return false;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_adjacent_internal(typcache->rngtype, r, ranges[0]));
+}
+
+Datum
+multirange_adjacent_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		return false;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_adjacent_internal(typcache->rngtype, ranges[range_count - 1], r));
+}
+
+Datum
+multirange_adjacent_multirange(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
+	MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		return false;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr1));
+
+	multirange_deserialize(mr1, &range_count1, &ranges1);
+	multirange_deserialize(mr2, &range_count2, &ranges2);
+
+	PG_RETURN_BOOL(range_adjacent_internal(typcache->rngtype, ranges1[range_count1 - 1], ranges2[0]));
+}
+
+/* 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);
+}
+
+/* multirange -> range functions */
+
+/* Find the smallest range that includes everything in the multirange */
+Datum
+range_merge_from_multirange(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	Oid			mltrngtypoid = MultirangeTypeGetOid(mr);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_RANGE_P(make_empty_range(typcache->rngtype));
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_RANGE_P(range_union_internal(typcache->rngtype, ranges[0],
+										   ranges[range_count - 1], false));
+}
+
+/* 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/pg_upgrade_support.c b/src/backend/utils/adt/pg_upgrade_support.c
index 8b26e1a93d..9362995741 100644
--- a/src/backend/utils/adt/pg_upgrade_support.c
+++ b/src/backend/utils/adt/pg_upgrade_support.c
@@ -52,6 +52,28 @@ binary_upgrade_set_next_array_pg_type_oid(PG_FUNCTION_ARGS)
 }
 
 Datum
+binary_upgrade_set_next_multirange_pg_type_oid(PG_FUNCTION_ARGS)
+{
+	Oid			typoid = PG_GETARG_OID(0);
+
+	CHECK_IS_BINARY_UPGRADE;
+	binary_upgrade_next_mrng_pg_type_oid = typoid;
+
+	PG_RETURN_VOID();
+}
+
+Datum
+binary_upgrade_set_next_multirange_array_pg_type_oid(PG_FUNCTION_ARGS)
+{
+	Oid			typoid = PG_GETARG_OID(0);
+
+	CHECK_IS_BINARY_UPGRADE;
+	binary_upgrade_next_mrng_array_pg_type_oid = typoid;
+
+	PG_RETURN_VOID();
+}
+
+Datum
 binary_upgrade_set_next_toast_pg_type_oid(PG_FUNCTION_ARGS)
 {
 	Oid			typoid = PG_GETARG_OID(0);
diff --git a/src/backend/utils/adt/pseudotypes.c b/src/backend/utils/adt/pseudotypes.c
index ad0e363c70..152e77f44c 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"
 
 
 /*
@@ -185,6 +186,30 @@ anyrange_out(PG_FUNCTION_ARGS)
 }
 
 /*
+ * 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.
  *
  * We allow this so that PL functions can return VOID without any special
diff --git a/src/backend/utils/adt/rangetypes.c b/src/backend/utils/adt/rangetypes.c
index 639e1dad6c..be1e044269 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
 {
@@ -431,19 +429,37 @@ range_lower(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
 	TypeCacheEntry *typcache;
+	bool		isnull;
+	Datum		result;
+
+	typcache = range_get_typcache(fcinfo, RangeTypeGetOid(r1));
+
+	result = range_lower_internal(typcache, r1, &isnull);
+
+	if (isnull)
+		PG_RETURN_NULL();
+	else
+		PG_RETURN_DATUM(result);
+}
+
+Datum
+range_lower_internal(TypeCacheEntry *typcache, const RangeType *r1, bool *isnull)
+{
 	RangeBound	lower;
 	RangeBound	upper;
 	bool		empty;
 
-	typcache = range_get_typcache(fcinfo, RangeTypeGetOid(r1));
-
 	range_deserialize(typcache, r1, &lower, &upper, &empty);
 
 	/* Return NULL if there's no finite lower bound */
 	if (empty || lower.infinite)
-		PG_RETURN_NULL();
+	{
+		*isnull = true;
+		return 0;
+	}
 
-	PG_RETURN_DATUM(lower.val);
+	*isnull = false;
+	return lower.val;
 }
 
 /* extract upper bound value */
@@ -452,19 +468,37 @@ range_upper(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
 	TypeCacheEntry *typcache;
+	bool		isnull;
+	Datum		result;
+
+	typcache = range_get_typcache(fcinfo, RangeTypeGetOid(r1));
+
+	result = range_upper_internal(typcache, r1, &isnull);
+
+	if (isnull)
+		PG_RETURN_NULL();
+	else
+		PG_RETURN_DATUM(result);
+}
+
+Datum
+range_upper_internal(TypeCacheEntry *typcache, const RangeType *r1, bool *isnull)
+{
 	RangeBound	lower;
 	RangeBound	upper;
 	bool		empty;
 
-	typcache = range_get_typcache(fcinfo, RangeTypeGetOid(r1));
-
 	range_deserialize(typcache, r1, &lower, &upper, &empty);
 
 	/* Return NULL if there's no finite upper bound */
 	if (empty || upper.infinite)
-		PG_RETURN_NULL();
+	{
+		*isnull = true;
+		return 0;
+	}
 
-	PG_RETURN_DATUM(upper.val);
+	*isnull = false;
+	return upper.val;
 }
 
 
@@ -475,9 +509,8 @@ Datum
 range_empty(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
-	char		flags = range_get_flags(r1);
 
-	PG_RETURN_BOOL(flags & RANGE_EMPTY);
+	PG_RETURN_BOOL(range_has_flag(r1, RANGE_EMPTY));
 }
 
 /* is lower bound inclusive? */
@@ -485,9 +518,8 @@ Datum
 range_lower_inc(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
-	char		flags = range_get_flags(r1);
 
-	PG_RETURN_BOOL(flags & RANGE_LB_INC);
+	PG_RETURN_BOOL(range_has_flag(r1, RANGE_LB_INC));
 }
 
 /* is upper bound inclusive? */
@@ -495,9 +527,8 @@ Datum
 range_upper_inc(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
-	char		flags = range_get_flags(r1);
 
-	PG_RETURN_BOOL(flags & RANGE_UB_INC);
+	PG_RETURN_BOOL(range_has_flag(r1, RANGE_UB_INC));
 }
 
 /* is lower bound infinite? */
@@ -505,9 +536,8 @@ Datum
 range_lower_inf(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
-	char		flags = range_get_flags(r1);
 
-	PG_RETURN_BOOL(flags & RANGE_LB_INF);
+	PG_RETURN_BOOL(range_has_flag(r1, RANGE_LB_INF));
 }
 
 /* is upper bound infinite? */
@@ -515,9 +545,8 @@ Datum
 range_upper_inf(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
-	char		flags = range_get_flags(r1);
 
-	PG_RETURN_BOOL(flags & RANGE_UB_INF);
+	PG_RETURN_BOOL(range_has_flag(r1, RANGE_UB_INF));
 }
 
 
@@ -957,7 +986,25 @@ range_minus(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
 	RangeType  *r2 = PG_GETARG_RANGE_P(1);
+	RangeType  *ret;
 	TypeCacheEntry *typcache;
+
+	/* 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));
+
+	ret = range_minus_internal(typcache, r1, r2);
+	if (ret)
+		PG_RETURN_RANGE_P(ret);
+	else
+		PG_RETURN_NULL();
+}
+
+RangeType *
+range_minus_internal(TypeCacheEntry *typcache, RangeType *r1, RangeType *r2)
+{
 	RangeBound	lower1,
 				lower2;
 	RangeBound	upper1,
@@ -969,18 +1016,12 @@ range_minus(PG_FUNCTION_ARGS)
 				cmp_u1l2,
 				cmp_u1u2;
 
-	/* 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));
-
 	range_deserialize(typcache, r1, &lower1, &upper1, &empty1);
 	range_deserialize(typcache, r2, &lower2, &upper2, &empty2);
 
 	/* if either is empty, r1 is the correct answer */
 	if (empty1 || empty2)
-		PG_RETURN_RANGE_P(r1);
+		return r1;
 
 	cmp_l1l2 = range_cmp_bounds(typcache, &lower1, &lower2);
 	cmp_l1u2 = range_cmp_bounds(typcache, &lower1, &upper2);
@@ -993,34 +1034,34 @@ range_minus(PG_FUNCTION_ARGS)
 				 errmsg("result of range difference would not be contiguous")));
 
 	if (cmp_l1u2 > 0 || cmp_u1l2 < 0)
-		PG_RETURN_RANGE_P(r1);
+		return r1;
 
 	if (cmp_l1l2 >= 0 && cmp_u1u2 <= 0)
-		PG_RETURN_RANGE_P(make_empty_range(typcache));
+		return make_empty_range(typcache);
 
 	if (cmp_l1l2 <= 0 && cmp_u1l2 >= 0 && cmp_u1u2 <= 0)
 	{
 		lower2.inclusive = !lower2.inclusive;
 		lower2.lower = false;	/* it will become the upper bound */
-		PG_RETURN_RANGE_P(make_range(typcache, &lower1, &lower2, false));
+		return make_range(typcache, &lower1, &lower2, false);
 	}
 
 	if (cmp_l1l2 >= 0 && cmp_u1u2 >= 0 && cmp_l1u2 <= 0)
 	{
 		upper2.inclusive = !upper2.inclusive;
 		upper2.lower = true;	/* it will become the lower bound */
-		PG_RETURN_RANGE_P(make_range(typcache, &upper2, &upper1, false));
+		return make_range(typcache, &upper2, &upper1, false);
 	}
 
 	elog(ERROR, "unexpected case in range_minus");
-	PG_RETURN_NULL();
+	return NULL;
 }
 
 /*
  * Set union.  If strict is true, it is an error that the two input ranges
  * are not adjacent or overlapping.
  */
-static RangeType *
+RangeType *
 range_union_internal(TypeCacheEntry *typcache, RangeType *r1, RangeType *r2,
 					 bool strict)
 {
@@ -1101,6 +1142,19 @@ range_intersect(PG_FUNCTION_ARGS)
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
 	RangeType  *r2 = PG_GETARG_RANGE_P(1);
 	TypeCacheEntry *typcache;
+
+	/* 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));
+
+	PG_RETURN_RANGE_P(range_intersect_internal(typcache, r1, r2));
+}
+
+RangeType *
+range_intersect_internal(TypeCacheEntry *typcache, const RangeType *r1, const RangeType *r2)
+{
 	RangeBound	lower1,
 				lower2;
 	RangeBound	upper1,
@@ -1110,17 +1164,11 @@ range_intersect(PG_FUNCTION_ARGS)
 	RangeBound *result_lower;
 	RangeBound *result_upper;
 
-	/* 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));
-
 	range_deserialize(typcache, r1, &lower1, &upper1, &empty1);
 	range_deserialize(typcache, r2, &lower2, &upper2, &empty2);
 
-	if (empty1 || empty2 || !DatumGetBool(range_overlaps(fcinfo)))
-		PG_RETURN_RANGE_P(make_empty_range(typcache));
+	if (empty1 || empty2 || !range_overlaps_internal(typcache, r1, r2))
+		return make_empty_range(typcache);
 
 	if (range_cmp_bounds(typcache, &lower1, &lower2) >= 0)
 		result_lower = &lower1;
@@ -1132,9 +1180,81 @@ range_intersect(PG_FUNCTION_ARGS)
 	else
 		result_upper = &upper2;
 
-	PG_RETURN_RANGE_P(make_range(typcache, result_lower, result_upper, false));
+	return make_range(typcache, result_lower, result_upper, false);
+}
+
+/* range, range -> range, range functions */
+
+/*
+ * range_split_internal - if r2 intersects the middle of r1, leaving non-empty
+ * ranges on both sides, then return true and set output1 and output2 to the
+ * results of r1 - r2 (in order). Otherwise return false and don't set output1
+ * or output2. Neither input range should be empty.
+ */
+bool
+range_split_internal(TypeCacheEntry *typcache, const RangeType *r1, const RangeType *r2,
+					 RangeType **output1, RangeType **output2)
+{
+	RangeBound	lower1,
+				lower2;
+	RangeBound	upper1,
+				upper2;
+	bool		empty1,
+				empty2;
+
+	range_deserialize(typcache, r1, &lower1, &upper1, &empty1);
+	range_deserialize(typcache, r2, &lower2, &upper2, &empty2);
+
+	if (range_cmp_bounds(typcache, &lower1, &lower2) < 0 &&
+		range_cmp_bounds(typcache, &upper1, &upper2) > 0)
+	{
+		/*
+		 * Need to invert inclusive/exclusive for the lower2 and upper2
+		 * points. They can't be infinite though. We're allowed to overwrite
+		 * these RangeBounds since they only exist locally.
+		 */
+		lower2.inclusive = !lower2.inclusive;
+		lower2.lower = false;
+		upper2.inclusive = !upper2.inclusive;
+		upper2.lower = true;
+
+		*output1 = make_range(typcache, &lower1, &lower2, false);
+		*output2 = make_range(typcache, &upper2, &upper1, false);
+		return true;
+	}
+
+	return false;
 }
 
+/* range -> range aggregate functions */
+
+Datum
+range_intersect_agg_transfn(PG_FUNCTION_ARGS)
+{
+	MemoryContext aggContext;
+	Oid			rngtypoid;
+	TypeCacheEntry *typcache;
+	RangeType  *result;
+	RangeType  *current;
+
+	if (!AggCheckCallContext(fcinfo, &aggContext))
+		elog(ERROR, "range_intersect_agg_transfn called in non-aggregate context");
+
+	rngtypoid = get_fn_expr_argtype(fcinfo->flinfo, 1);
+	if (!type_is_range(rngtypoid))
+		ereport(ERROR, (errmsg("range_intersect_agg must be called with a range")));
+
+	typcache = range_get_typcache(fcinfo, rngtypoid);
+
+	/* strictness ensures these are non-null */
+	result = PG_GETARG_RANGE_P(0);
+	current = PG_GETARG_RANGE_P(1);
+
+	result = range_intersect_internal(typcache, result, current);
+	PG_RETURN_RANGE_P(result);
+}
+
+
 /* Btree support */
 
 /* btree comparator */
@@ -1144,12 +1264,6 @@ range_cmp(PG_FUNCTION_ARGS)
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
 	RangeType  *r2 = PG_GETARG_RANGE_P(1);
 	TypeCacheEntry *typcache;
-	RangeBound	lower1,
-				lower2;
-	RangeBound	upper1,
-				upper2;
-	bool		empty1,
-				empty2;
 	int			cmp;
 
 	check_stack_depth();		/* recurses when subtype is a range type */
@@ -1160,6 +1274,28 @@ range_cmp(PG_FUNCTION_ARGS)
 
 	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, const RangeType *r1, const 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);
 
@@ -1177,10 +1313,7 @@ range_cmp(PG_FUNCTION_ARGS)
 			cmp = range_cmp_bounds(typcache, &upper1, &upper2);
 	}
 
-	PG_FREE_IF_COPY(r1, 0);
-	PG_FREE_IF_COPY(r2, 1);
-
-	PG_RETURN_INT32(cmp);
+	return cmp;
 }
 
 /* inequality operators using the range_cmp function */
@@ -1218,13 +1351,10 @@ range_gt(PG_FUNCTION_ARGS)
 
 /* Hash support */
 
-/* hash a range value */
-Datum
-hash_range(PG_FUNCTION_ARGS)
+uint32
+hash_range_internal(TypeCacheEntry *typcache, const RangeType *r)
 {
-	RangeType  *r = PG_GETARG_RANGE_P(0);
 	uint32		result;
-	TypeCacheEntry *typcache;
 	TypeCacheEntry *scache;
 	RangeBound	lower;
 	RangeBound	upper;
@@ -1233,10 +1363,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 +1404,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, const RangeType *r, Datum seed)
+{
+	uint64		result;
 	TypeCacheEntry *scache;
 	RangeBound	lower;
 	RangeBound	upper;
@@ -1300,10 +1433,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 +1471,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));
 }
 
 /*
@@ -1770,6 +1917,21 @@ range_get_flags(const RangeType *range)
 }
 
 /*
+ * range_has_flag: set whether a range has a specific flag.
+ *
+ * This lets expose some of our functions that just check flags
+ * to the rest of the code base (like to multiranges)
+ * without writing full-fledged *_internal versions.
+ */
+bool
+range_has_flag(const RangeType *r1, char flag)
+{
+	char		flags = range_get_flags(r1);
+
+	return flags & flag;
+}
+
+/*
  * range_set_contain_empty: set the RANGE_CONTAIN_EMPTY bit in the value.
  *
  * This is only needed in GiST operations, so we don't include a provision
@@ -1938,6 +2100,45 @@ range_cmp_bound_values(TypeCacheEntry *typcache, const RangeBound *b1,
 }
 
 /*
+ * 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.
  */
 RangeType *
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index 1e3e6d3a7e..2f6257c95f 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -2469,6 +2469,16 @@ type_is_range(Oid typid)
 }
 
 /*
+ * 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
  *
  *		Given the type OID, fetch its category and preferred-type status.
@@ -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->rngmultitypid;
+		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 53d9ddf159..fb9179c42a 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_rngmultitypid,
+			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 cdf6331a97..6677bc8bdc 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;
@@ -759,6 +777,16 @@ lookup_type_cache(Oid type_id, int flags)
 	}
 
 	/*
+	 * 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
 	 */
 	if ((flags & TYPECACHE_DOMAIN_BASE_INFO) &&
@@ -871,6 +899,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
  *
  * Note: we assume we're called in a relatively short-lived context, so it's
@@ -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 b7eee3da1d..defc801f7a 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,131 @@ resolve_polymorphic_tupdesc(TupleDesc tupdesc, oidvector *declared_args,
 				return false;
 			anyelement_type = subtype;
 		}
+
+		if (OidIsValid(anymultirange_type))
+		{
+			Oid			rngtype;
+			Oid			subtype;
+
+			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;
+			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;
+			Oid			subtype;
+
+			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;
+
+			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;
+			Oid			mltrngtype;
+			Oid			rngtype;
+
+			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;
+
+			mltrngtype = resolve_generic_type(ANYMULTIRANGEOID,
+											  anyrange_type,
+											  ANYRANGEOID);
+
+			/* check for inconsistent range and multirange results */
+			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 +761,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 +793,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 +856,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 +880,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 +906,132 @@ resolve_polymorphic_argtypes(int numargs, Oid *argtypes, char *argmodes,
 				return false;
 			anyelement_type = subtype;
 		}
+
+		if (OidIsValid(anymultirange_type))
+		{
+			Oid			rngtype;
+			Oid			subtype;
+
+			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;
+
+			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;
+			Oid			subtype;
+
+			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;
+
+			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;
+			Oid			mltrngtype;
+			Oid			rngtype;
+
+			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;
+
+			mltrngtype = resolve_generic_type(ANYMULTIRANGEOID,
+											  anyrange_type,
+											  ANYRANGEOID);
+
+			/* check for inconsistent range and multirange results */
+			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 +1051,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/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 799b6988b7..7237b5e52a 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -274,7 +274,8 @@ static void dumpSearchPath(Archive *AH);
 static void binary_upgrade_set_type_oids_by_type_oid(Archive *fout,
 													 PQExpBuffer upgrade_buffer,
 													 Oid pg_type_oid,
-													 bool force_array_type);
+													 bool force_array_type,
+													 bool include_multirange_type);
 static bool binary_upgrade_set_type_oids_by_rel_oid(Archive *fout,
 													PQExpBuffer upgrade_buffer, Oid pg_rel_oid);
 static void binary_upgrade_set_pg_class_oids(Archive *fout,
@@ -1583,7 +1584,7 @@ selectDumpableType(TypeInfo *tyinfo, Archive *fout)
 	}
 
 	/* skip auto-generated array types */
-	if (tyinfo->isArray)
+	if (tyinfo->isArray || tyinfo->isMultirange)
 	{
 		tyinfo->dobj.objType = DO_DUMMY_TYPE;
 
@@ -4275,15 +4276,49 @@ dumpSubscription(Archive *fout, SubscriptionInfo *subinfo)
 	free(qsubname);
 }
 
+static Oid
+get_next_possible_free_pg_type_oid(Archive *fout, PQExpBuffer upgrade_query)
+{
+	/*
+	 * If the old version didn't assign an array type, but the new version
+	 * does, we must select an unused type OID to assign.  This currently only
+	 * happens for domains, when upgrading pre-v11 to v11 and up.
+	 *
+	 * Note: local state here is kind of ugly, but we must have some, since we
+	 * mustn't choose the same unused OID more than once.
+	 */
+	static Oid	next_possible_free_oid = FirstNormalObjectId;
+	PGresult   *res;
+	bool		is_dup;
+
+	do
+	{
+		++next_possible_free_oid;
+		printfPQExpBuffer(upgrade_query,
+						  "SELECT EXISTS(SELECT 1 "
+						  "FROM pg_catalog.pg_type "
+						  "WHERE oid = '%u'::pg_catalog.oid);",
+						  next_possible_free_oid);
+		res = ExecuteSqlQueryForSingleRow(fout, upgrade_query->data);
+		is_dup = (PQgetvalue(res, 0, 0)[0] == 't');
+		PQclear(res);
+	} while (is_dup);
+
+	return next_possible_free_oid;
+}
+
 static void
 binary_upgrade_set_type_oids_by_type_oid(Archive *fout,
 										 PQExpBuffer upgrade_buffer,
 										 Oid pg_type_oid,
-										 bool force_array_type)
+										 bool force_array_type,
+										 bool include_multirange_type)
 {
 	PQExpBuffer upgrade_query = createPQExpBuffer();
 	PGresult   *res;
 	Oid			pg_type_array_oid;
+	Oid			pg_type_multirange_oid;
+	Oid			pg_type_multirange_array_oid;
 
 	appendPQExpBufferStr(upgrade_buffer, "\n-- For binary upgrade, must preserve pg_type oid\n");
 	appendPQExpBuffer(upgrade_buffer,
@@ -4304,33 +4339,7 @@ binary_upgrade_set_type_oids_by_type_oid(Archive *fout,
 	PQclear(res);
 
 	if (!OidIsValid(pg_type_array_oid) && force_array_type)
-	{
-		/*
-		 * If the old version didn't assign an array type, but the new version
-		 * does, we must select an unused type OID to assign.  This currently
-		 * only happens for domains, when upgrading pre-v11 to v11 and up.
-		 *
-		 * Note: local state here is kind of ugly, but we must have some,
-		 * since we mustn't choose the same unused OID more than once.
-		 */
-		static Oid	next_possible_free_oid = FirstNormalObjectId;
-		bool		is_dup;
-
-		do
-		{
-			++next_possible_free_oid;
-			printfPQExpBuffer(upgrade_query,
-							  "SELECT EXISTS(SELECT 1 "
-							  "FROM pg_catalog.pg_type "
-							  "WHERE oid = '%u'::pg_catalog.oid);",
-							  next_possible_free_oid);
-			res = ExecuteSqlQueryForSingleRow(fout, upgrade_query->data);
-			is_dup = (PQgetvalue(res, 0, 0)[0] == 't');
-			PQclear(res);
-		} while (is_dup);
-
-		pg_type_array_oid = next_possible_free_oid;
-	}
+		pg_type_array_oid = get_next_possible_free_pg_type_oid(fout, upgrade_query);
 
 	if (OidIsValid(pg_type_array_oid))
 	{
@@ -4341,6 +4350,46 @@ binary_upgrade_set_type_oids_by_type_oid(Archive *fout,
 						  pg_type_array_oid);
 	}
 
+	/*
+	 * Pre-set the multirange type oid and its own array type oid.
+	 */
+	if (include_multirange_type)
+	{
+		if (fout->remoteVersion >= 130000)
+		{
+			appendPQExpBuffer(upgrade_query,
+							  "SELECT t.oid, t.typarray "
+							  "FROM pg_catalog.pg_type t "
+							  "JOIN pg_catalog.pg_range r "
+							  "ON t.oid = r.rngmultitypid "
+							  "WHERE r.rngtypid = '%u'::pg_catalog.oid;",
+							  pg_type_oid);
+
+			res = ExecuteSqlQueryForSingleRow(fout, upgrade_query->data);
+
+			pg_type_multirange_oid = atooid(PQgetvalue(res, 0, PQfnumber(res, "oid")));
+			pg_type_multirange_array_oid = atooid(PQgetvalue(res, 0, PQfnumber(res, "typarray")));
+
+			PQclear(res);
+		}
+		else
+		{
+			pg_type_multirange_oid = get_next_possible_free_pg_type_oid(fout, upgrade_query);
+			pg_type_multirange_array_oid = get_next_possible_free_pg_type_oid(fout, upgrade_query);
+		}
+
+		appendPQExpBufferStr(upgrade_buffer,
+							 "\n-- For binary upgrade, must preserve multirange pg_type oid\n");
+		appendPQExpBuffer(upgrade_buffer,
+						  "SELECT pg_catalog.binary_upgrade_set_next_multirange_pg_type_oid('%u'::pg_catalog.oid);\n\n",
+						  pg_type_multirange_oid);
+		appendPQExpBufferStr(upgrade_buffer,
+							 "\n-- For binary upgrade, must preserve multirange pg_type array oid\n");
+		appendPQExpBuffer(upgrade_buffer,
+						  "SELECT pg_catalog.binary_upgrade_set_next_multirange_array_pg_type_oid('%u'::pg_catalog.oid);\n\n",
+						  pg_type_multirange_array_oid);
+	}
+
 	destroyPQExpBuffer(upgrade_query);
 }
 
@@ -4374,7 +4423,7 @@ binary_upgrade_set_type_oids_by_rel_oid(Archive *fout,
 	pg_type_oid = atooid(PQgetvalue(upgrade_res, 0, PQfnumber(upgrade_res, "crel")));
 
 	binary_upgrade_set_type_oids_by_type_oid(fout, upgrade_buffer,
-											 pg_type_oid, false);
+											 pg_type_oid, false, false);
 
 	if (!PQgetisnull(upgrade_res, 0, PQfnumber(upgrade_res, "trel")))
 	{
@@ -4923,6 +4972,11 @@ getTypes(Archive *fout, int *numTypes)
 		else
 			tyinfo[i].isArray = false;
 
+		if (tyinfo[i].typtype == 'm')
+			tyinfo[i].isMultirange = true;
+		else
+			tyinfo[i].isMultirange = false;
+
 		/* Decide whether we want to dump it */
 		selectDumpableType(&tyinfo[i], fout);
 
@@ -8037,9 +8091,12 @@ getProcLangs(Archive *fout, int *numProcLangs)
 
 /*
  * getCasts
- *	  get basic information about every cast in the system
+ *	  get basic information about most casts in the system
  *
  * numCasts is set to the number of casts read in
+ *
+ * Skip casts from a range to its multirange, since we'll create those
+ * automatically.
  */
 CastInfo *
 getCasts(Archive *fout, int *numCasts)
@@ -8057,7 +8114,20 @@ getCasts(Archive *fout, int *numCasts)
 	int			i_castcontext;
 	int			i_castmethod;
 
-	if (fout->remoteVersion >= 80400)
+	if (fout->remoteVersion >= 130000)
+	{
+		appendPQExpBufferStr(query, "SELECT tableoid, oid, "
+							 "castsource, casttarget, castfunc, castcontext, "
+							 "castmethod "
+							 "FROM pg_cast c "
+							 "WHERE NOT EXISTS ( "
+							 "SELECT 1 FROM pg_range r "
+							 "WHERE c.castsource = r.rngtypid "
+							 "AND c.casttarget = r.rngmultitypid "
+							 ") "
+							 "ORDER BY 3,4");
+	}
+	else if (fout->remoteVersion >= 80400)
 	{
 		appendPQExpBufferStr(query, "SELECT tableoid, oid, "
 							 "castsource, casttarget, castfunc, castcontext, "
@@ -10245,7 +10315,7 @@ dumpEnumType(Archive *fout, TypeInfo *tyinfo)
 	if (dopt->binary_upgrade)
 		binary_upgrade_set_type_oids_by_type_oid(fout, q,
 												 tyinfo->dobj.catId.oid,
-												 false);
+												 false, false);
 
 	appendPQExpBuffer(q, "CREATE TYPE %s AS ENUM (",
 					  qualtypname);
@@ -10371,7 +10441,7 @@ dumpRangeType(Archive *fout, TypeInfo *tyinfo)
 	if (dopt->binary_upgrade)
 		binary_upgrade_set_type_oids_by_type_oid(fout, q,
 												 tyinfo->dobj.catId.oid,
-												 false);
+												 false, true);
 
 	appendPQExpBuffer(q, "CREATE TYPE %s AS RANGE (",
 					  qualtypname);
@@ -10477,7 +10547,7 @@ dumpUndefinedType(Archive *fout, TypeInfo *tyinfo)
 	if (dopt->binary_upgrade)
 		binary_upgrade_set_type_oids_by_type_oid(fout, q,
 												 tyinfo->dobj.catId.oid,
-												 false);
+												 false, false);
 
 	appendPQExpBuffer(q, "CREATE TYPE %s;\n",
 					  qualtypname);
@@ -10682,7 +10752,7 @@ dumpBaseType(Archive *fout, TypeInfo *tyinfo)
 	if (dopt->binary_upgrade)
 		binary_upgrade_set_type_oids_by_type_oid(fout, q,
 												 tyinfo->dobj.catId.oid,
-												 false);
+												 false, false);
 
 	appendPQExpBuffer(q,
 					  "CREATE TYPE %s (\n"
@@ -10869,7 +10939,8 @@ dumpDomain(Archive *fout, TypeInfo *tyinfo)
 	if (dopt->binary_upgrade)
 		binary_upgrade_set_type_oids_by_type_oid(fout, q,
 												 tyinfo->dobj.catId.oid,
-												 true); /* force array type */
+												 true,		/* force array type */
+												 false);	/* force multirange type */
 
 	qtypname = pg_strdup(fmtId(tyinfo->dobj.name));
 	qualtypname = pg_strdup(fmtQualifiedDumpable(tyinfo));
@@ -11057,7 +11128,7 @@ dumpCompositeType(Archive *fout, TypeInfo *tyinfo)
 	{
 		binary_upgrade_set_type_oids_by_type_oid(fout, q,
 												 tyinfo->dobj.catId.oid,
-												 false);
+												 false, false);
 		binary_upgrade_set_pg_class_oids(fout, q, tyinfo->typrelid, false);
 	}
 
@@ -11331,7 +11402,7 @@ dumpShellType(Archive *fout, ShellTypeInfo *stinfo)
 	if (dopt->binary_upgrade)
 		binary_upgrade_set_type_oids_by_type_oid(fout, q,
 												 stinfo->baseType->dobj.catId.oid,
-												 false);
+												 false, false);
 
 	appendPQExpBuffer(q, "CREATE TYPE %s;\n",
 					  fmtQualifiedDumpable(stinfo));
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 21004e5078..63e0ba7116 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -175,6 +175,7 @@ typedef struct _typeInfo
 	char		typrelkind;		/* 'r', 'v', 'c', etc */
 	char		typtype;		/* 'b', 'c', etc */
 	bool		isArray;		/* true if auto-generated array type */
+	bool		isMultirange;	/* true if auto-generated multirange type */
 	bool		isDefined;		/* true if typisdefined */
 	/* If needed, we'll create a "shell type" entry for it; link that here: */
 	struct _shellTypeInfo *shellType;	/* shell-type entry, or NULL */
diff --git a/src/include/access/tupmacs.h b/src/include/access/tupmacs.h
index 0b205a0dc6..3c113fa3bd 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/binary_upgrade.h b/src/include/catalog/binary_upgrade.h
index 12d94fe1b3..01d95117cd 100644
--- a/src/include/catalog/binary_upgrade.h
+++ b/src/include/catalog/binary_upgrade.h
@@ -16,6 +16,8 @@
 
 extern PGDLLIMPORT Oid binary_upgrade_next_pg_type_oid;
 extern PGDLLIMPORT Oid binary_upgrade_next_array_pg_type_oid;
+extern PGDLLIMPORT Oid binary_upgrade_next_mrng_pg_type_oid;
+extern PGDLLIMPORT Oid binary_upgrade_next_mrng_array_pg_type_oid;
 extern PGDLLIMPORT Oid binary_upgrade_next_toast_pg_type_oid;
 
 extern PGDLLIMPORT Oid binary_upgrade_next_heap_pg_class_oid;
diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h
index 13e07fc314..48a6bf5598 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_rngmultitypid_index, 8001, on pg_range using btree(rngmultitypid 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_aggregate.dat b/src/include/catalog/pg_aggregate.dat
index ffabe275c0..03bab74253 100644
--- a/src/include/catalog/pg_aggregate.dat
+++ b/src/include/catalog/pg_aggregate.dat
@@ -538,6 +538,17 @@
   aggtransfn => 'bytea_string_agg_transfn',
   aggfinalfn => 'bytea_string_agg_finalfn', aggtranstype => 'internal' },
 
+# range
+{ aggfnoid => 'range_intersect_agg(anyrange)',
+  aggtransfn => 'range_intersect_agg_transfn',
+  aggcombinefn => 'range_intersect_agg_transfn', aggtranstype => 'anyrange' },
+{ aggfnoid => 'range_intersect_agg(anymultirange)',
+  aggtransfn => 'multirange_intersect_agg_transfn',
+  aggcombinefn => 'multirange_intersect_agg_transfn', aggtranstype => 'anymultirange' },
+{ aggfnoid => 'range_agg(anyrange)', aggtransfn => 'range_agg_transfn',
+  aggfinalfn => 'range_agg_finalfn', aggfinalextra => 't',
+  aggtranstype => 'internal' },
+
 # json
 { aggfnoid => 'json_agg', aggtransfn => 'json_agg_transfn',
   aggfinalfn => 'json_agg_finalfn', aggtranstype => 'internal' },
diff --git a/src/include/catalog/pg_amop.dat b/src/include/catalog/pg_amop.dat
index 11aaa519c8..827ac4f03c 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 c67768fcab..0dafd4ec63 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',
@@ -1203,12 +1210,13 @@
   amprocrighttype => 'anyrange', amprocnum => '4',
   amproc => 'brin_inclusion_union' },
 { amprocfamily => 'brin/range_inclusion_ops', amproclefttype => 'anyrange',
-  amprocrighttype => 'anyrange', amprocnum => '11', amproc => 'range_merge' },
+  amprocrighttype => 'anyrange', amprocnum => '11',
+  amproc => 'range_merge(anyrange,anyrange)' },
 { amprocfamily => 'brin/range_inclusion_ops', amproclefttype => 'anyrange',
   amprocrighttype => 'anyrange', amprocnum => '13',
   amproc => 'range_contains' },
 { amprocfamily => 'brin/range_inclusion_ops', amproclefttype => 'anyrange',
-  amprocrighttype => 'anyrange', amprocnum => '14', amproc => 'isempty' },
+  amprocrighttype => 'anyrange', amprocnum => '14', amproc => 'isempty(anyrange)' },
 
 # minmax pg_lsn
 { amprocfamily => 'brin/pg_lsn_minmax_ops', amproclefttype => 'pg_lsn',
diff --git a/src/include/catalog/pg_cast.dat b/src/include/catalog/pg_cast.dat
index 6ef8b8a4e7..14277c8fdf 100644
--- a/src/include/catalog/pg_cast.dat
+++ b/src/include/catalog/pg_cast.dat
@@ -512,4 +512,17 @@
 { castsource => 'jsonb', casttarget => 'float8', castfunc => 'float8(jsonb)',
   castcontext => 'e', castmethod => 'f' },
 
+# range to multirange
+{ castsource => 'int4range', casttarget => 'int4multirange', castfunc => 'int4multirange(int4range)',
+  castcontext => 'e', castmethod => 'f' },
+{ castsource => 'int8range', casttarget => 'int8multirange', castfunc => 'int8multirange(int8range)',
+  castcontext => 'e', castmethod => 'f' },
+{ castsource => 'numrange', casttarget => 'nummultirange', castfunc => 'nummultirange(numrange)',
+  castcontext => 'e', castmethod => 'f' },
+{ castsource => 'daterange', casttarget => 'datemultirange', castfunc => 'datemultirange(daterange)',
+  castcontext => 'e', castmethod => 'f' },
+{ castsource => 'tsrange', casttarget => 'tsmultirange', castfunc => 'tsmultirange(tsrange)',
+  castcontext => 'e', castmethod => 'f' },
+{ castsource => 'tstzrange', casttarget => 'tstzmultirange', castfunc => 'tstzmultirange(tstzrange)',
+  castcontext => 'e', castmethod => 'f' },
 ]
diff --git a/src/include/catalog/pg_cast.h b/src/include/catalog/pg_cast.h
index 1b81b52df6..f23ba28d9c 100644
--- a/src/include/catalog/pg_cast.h
+++ b/src/include/catalog/pg_cast.h
@@ -23,6 +23,8 @@
 #include "catalog/genbki.h"
 #include "catalog/pg_cast_d.h"
 
+#include "catalog/dependency.h"
+
 /* ----------------
  *		pg_cast definition.  cpp turns this into
  *		typedef struct FormData_pg_cast
@@ -87,4 +89,11 @@ typedef enum CoercionMethod
 
 #endif							/* EXPOSE_TO_CLIENT_CODE */
 
+extern ObjectAddress CastCreate(Oid sourcetypeid,
+								Oid targettypeid,
+								Oid funcid,
+								char castcontext,
+								char castmethod,
+								DependencyType behavior);
+
 #endif							/* PG_CAST_H */
diff --git a/src/include/catalog/pg_opclass.dat b/src/include/catalog/pg_opclass.dat
index ab2f50c9eb..eebebbcdb4 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 7c135da3b1..24f4a32203 100644
--- a/src/include/catalog/pg_operator.dat
+++ b/src/include/catalog/pg_operator.dat
@@ -3297,5 +3297,174 @@
   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' },
+{ oid => '8073', oid_symbol => 'OID_RANGE_OVERLAPS_MULTIRANGE_OP',
+  descr => 'overlaps',
+  oprname => '&&', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '&&(anymultirange,anyrange)',
+  oprcode => 'range_overlaps_multirange', oprrest => 'areasel',
+  oprjoin => 'areajoinsel' },
+{ oid => '8074', oid_symbol => 'OID_MULTIRANGE_OVERLAPS_RANGE_OP', descr => 'contains',
+  oprname => '&&', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcom => '&&(anyrange,anymultirange)',
+  oprcode => 'multirange_overlaps_range', oprrest => 'areasel',
+  oprjoin => 'areajoinsel' },
+{ oid => '8075', oid_symbol => 'OID_MULTIRANGE_OVERLAPS_MULTIRANGE_OP', descr => 'contains',
+  oprname => '&&', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '&&(anymultirange,anymultirange)',
+  oprcode => 'multirange_overlaps_multirange', oprrest => 'areasel',
+  oprjoin => 'areajoinsel' },
+{ oid => '8064', oid_symbol => 'OID_MULTIRANGE_CONTAINS_ELEM_OP',
+  descr => 'contains',
+  oprname => '@>', oprleft => 'anymultirange', oprright => 'anyelement',
+  oprresult => 'bool', oprcom => '<@(anyelement,anymultirange)',
+  oprcode => 'multirange_contains_elem', oprrest => 'contsel',
+  oprjoin => 'contjoinsel' },
+{ oid => '8065', oid_symbol => 'OID_MULTIRANGE_CONTAINS_RANGE_OP', descr => 'contains',
+  oprname => '@>', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcom => '<@(anyrange,anymultirange)',
+  oprcode => 'multirange_contains_range', oprrest => 'contsel',
+  oprjoin => 'contjoinsel' },
+{ oid => '8066', oid_symbol => 'OID_MULTIRANGE_CONTAINS_MULTIRANGE_OP', descr => 'contains',
+  oprname => '@>', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '<@(anymultirange,anymultirange)',
+  oprcode => 'multirange_contains_multirange', oprrest => 'contsel',
+  oprjoin => 'contjoinsel' },
+{ oid => '8067', oid_symbol => 'OID_MULTIRANGE_ELEM_CONTAINED_OP',
+  descr => 'is contained by',
+  oprname => '<@', oprleft => 'anyelement', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '@>(anymultirange,anyelement)',
+  oprcode => 'elem_contained_by_multirange', oprrest => 'contsel',
+  oprjoin => 'contjoinsel' },
+{ oid => '8068', oid_symbol => 'OID_MULTIRANGE_RANGE_CONTAINED_OP',
+  descr => 'is contained by',
+  oprname => '<@', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '@>(anymultirange,anyrange)',
+  oprcode => 'range_contained_by_multirange', oprrest => 'contsel',
+  oprjoin => 'contjoinsel' },
+{ oid => '8069', oid_symbol => 'OID_MULTIRANGE_MULTIRANGE_CONTAINED_OP',
+  descr => 'is contained by',
+  oprname => '<@', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '@>(anymultirange,anymultirange)',
+  oprcode => 'multirange_contained_by_multirange', oprrest => 'contsel',
+  oprjoin => 'contjoinsel' },
+{ oid => '8106', oid_symbol => 'OID_RANGE_OVERLAPS_LEFT_MULTIRANGE_OP',
+  descr => 'overlaps or is left of',
+  oprname => '&<', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcode => 'range_overleft_multirange', oprrest => 'scalarltsel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8107', oid_symbol => 'OID_MULTIRANGE_OVERLAPS_LEFT_RANGE_OP',
+  descr => 'overlaps or is left of',
+  oprname => '&<', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcode => 'multirange_overleft_range', oprrest => 'scalarltsel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8108', oid_symbol => 'OID_MULTIRANGE_OVERLAPS_LEFT_MULTIRANGE_OP',
+  descr => 'overlaps or is left of',
+  oprname => '&<', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcode => 'multirange_overleft_multirange', oprrest => 'scalarltsel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8109', oid_symbol => 'OID_RANGE_OVERLAPS_RIGHT_MULTIRANGE_OP',
+  descr => 'overlaps or is right of',
+  oprname => '&>', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcode => 'range_overright_multirange', oprrest => 'scalargtsel',
+  oprjoin => 'scalargtjoinsel' },
+{ oid => '8110', oid_symbol => 'OID_MULTIRANGE_OVERLAPS_RIGHT_RANGE_OP',
+  descr => 'overlaps or is right of',
+  oprname => '&>', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcode => 'multirange_overright_range', oprrest => 'scalargtsel',
+  oprjoin => 'scalargtjoinsel' },
+{ oid => '8111', oid_symbol => 'OID_MULTIRANGE_OVERLAPS_RIGHT_MULTIRANGE_OP',
+  descr => 'overlaps or is right of',
+  oprname => '&>', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcode => 'multirange_overright_multirange', oprrest => 'scalargtsel',
+  oprjoin => 'scalargtjoinsel' },
+{ oid => '8091', oid_symbol => 'OID_RANGE_ADJACENT_MULTIRANGE_OP', descr => 'is adjacent to',
+  oprname => '-|-', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '-|-(anymultirange,anyrange)',
+  oprcode => 'range_adjacent_multirange', oprrest => 'scalarltsel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8092', oid_symbol => 'OID_MULTIRANGE_ADJACENT_RANGE_OP', descr => 'is adjacent to',
+  oprname => '-|-', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcom => '-|-(anyrange,anymultirange)',
+  oprcode => 'multirange_adjacent_range', oprrest => 'scalarltsel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8093', oid_symbol => 'OID_MULTIRANGE_ADJACENT_MULTIRANGE_OP', descr => 'is adjacent to',
+  oprname => '-|-', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '-|-(anymultirange,anymultirange)',
+  oprcode => 'multirange_adjacent_multirange', oprrest => 'scalarltsel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8117', descr => 'multirange union',
+  oprname => '+', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'anymultirange', oprcom => '+(anymultirange,anymultirange)',
+  oprcode => 'multirange_union' },
+{ oid => '8123', descr => 'multirange minus',
+  oprname => '-', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'anymultirange', oprcode => 'multirange_minus' },
+{ oid => '8129', descr => 'multirange intersect',
+  oprname => '*', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'anymultirange', oprcom => '*(anymultirange,anymultirange)',
+  oprcode => 'multirange_intersect' },
+{ oid => '8082', oid_symbol => 'OID_RANGE_BEFORE_MULTIRANGE_OP', descr => 'is left of',
+  oprname => '<<', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '>>(anymultirange,anyrange)',
+  oprcode => 'range_before_multirange', oprrest => 'scalarltsel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8083', oid_symbol => 'OID_MULTIRANGE_BEFORE_RANGE_OP', descr => 'is left of',
+  oprname => '<<', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcom => '>>(anyrange,anymultirange)',
+  oprcode => 'multirange_before_range', oprrest => 'scalarltsel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8084', oid_symbol => 'OID_MULTIRANGE_BEFORE_MULTIRANGE_OP', descr => 'is left of',
+  oprname => '<<', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '>>(anymultirange,anymultirange)',
+  oprcode => 'multirange_before_multirange', oprrest => 'scalarltsel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8085', oid_symbol => 'OID_RANGE_AFTER_MULTIRANGE_OP', descr => 'is right of',
+  oprname => '>>', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '<<(anymultirange,anyrange)',
+  oprcode => 'range_after_multirange', oprrest => 'scalargtsel',
+  oprjoin => 'scalargtjoinsel' },
+{ oid => '8086', oid_symbol => 'OID_MULTIRANGE_AFTER_RANGE_OP', descr => 'is right of',
+  oprname => '>>', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcom => '<<(anyrange,anymultirange)',
+  oprcode => 'multirange_after_range', oprrest => 'scalargtsel',
+  oprjoin => 'scalargtjoinsel' },
+{ oid => '8087', oid_symbol => 'OID_MULTIRANGE_AFTER_MULTIRANGE_OP', descr => 'is right of',
+  oprname => '>>', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '<<(anymultirange,anymultirange)',
+  oprcode => 'multirange_after_multirange', oprrest => 'scalargtsel',
+  oprjoin => 'scalargtjoinsel' },
 
 ]
diff --git a/src/include/catalog/pg_opfamily.dat b/src/include/catalog/pg_opfamily.dat
index 26227df216..901fd7b303 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 0b6045acb1..686eba1c4a 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -9605,6 +9605,10 @@
   descr => 'the smallest range which includes both of the given ranges',
   proname => 'range_merge', prorettype => 'anyrange',
   proargtypes => 'anyrange anyrange', prosrc => 'range_merge' },
+{ oid => '8143',
+  descr => 'the smallest range which includes the whole multirange',
+  proname => 'range_merge', prorettype => 'anyrange',
+  proargtypes => 'anymultirange', prosrc => 'range_merge_from_multirange' },
 { oid => '3868',
   proname => 'range_intersect', prorettype => 'anyrange',
   proargtypes => 'anyrange anyrange', prosrc => 'range_intersect' },
@@ -9654,6 +9658,13 @@
 { oid => '3169', descr => 'restriction selectivity for range operators',
   proname => 'rangesel', provolatile => 's', prorettype => 'float8',
   proargtypes => 'internal oid internal int4', prosrc => 'rangesel' },
+{ oid => '8133', descr => 'range aggregate by intersecting',
+  proname => 'range_intersect_agg_transfn', prorettype => 'anyrange',
+  proargtypes => 'anyrange anyrange', prosrc => 'range_intersect_agg_transfn'},
+{ oid => '8134', descr => 'range aggregate by intersecting',
+  proname => 'range_intersect_agg', prokind => 'a', proisstrict => 'f',
+  prorettype => 'anyrange', proargtypes => 'anyrange',
+  prosrc => 'aggregate_dummy'},
 
 { oid => '3914', descr => 'convert an int4 range to canonical form',
   proname => 'int4range_canonical', prorettype => 'int4range',
@@ -9722,6 +9733,258 @@
   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 => '8094', descr => 'lower bound of multirange',
+  proname => 'lower', prorettype => 'anyelement', proargtypes => 'anymultirange',
+  prosrc => 'multirange_lower' },
+{ oid => '8095', descr => 'upper bound of multirange',
+  proname => 'upper', prorettype => 'anyelement', proargtypes => 'anymultirange',
+  prosrc => 'multirange_upper' },
+{ oid => '8057', descr => 'is the multirange empty?',
+  proname => 'isempty', prorettype => 'bool',
+  proargtypes => 'anymultirange', prosrc => 'multirange_empty' },
+{ oid => '8096', descr => 'is the multirange\'s lower bound inclusive?',
+  proname => 'lower_inc', prorettype => 'bool', proargtypes => 'anymultirange',
+  prosrc => 'multirange_lower_inc' },
+{ oid => '8097', descr => 'is the multirange\'s upper bound inclusive?',
+  proname => 'upper_inc', prorettype => 'bool', proargtypes => 'anymultirange',
+  prosrc => 'multirange_upper_inc' },
+{ oid => '8098', descr => 'is the multirange\'s lower bound infinite?',
+  proname => 'lower_inf', prorettype => 'bool', proargtypes => 'anymultirange',
+  prosrc => 'multirange_lower_inf' },
+{ oid => '8099', descr => 'is the multirange\'s upper bound infinite?',
+  proname => 'upper_inf', prorettype => 'bool', proargtypes => 'anymultirange',
+  prosrc => 'multirange_upper_inf' },
+{ oid => '8008', descr => 'multirange typanalyze',
+  proname => 'multirange_typanalyze', provolatile => 's', prorettype => 'bool',
+  proargtypes => 'internal', prosrc => 'multirange_typanalyze' },
+{ oid => '8033',
+  proname => 'multirange_eq', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_eq' },
+{ oid => '8034',
+  proname => 'multirange_ne', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_ne' },
+{ oid => '8070',
+  proname => 'range_overlaps_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_overlaps_multirange' },
+{ oid => '8071',
+  proname => 'multirange_overlaps_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_overlaps_range' },
+{ oid => '8072',
+  proname => 'multirange_overlaps_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_overlaps_multirange' },
+{ oid => '8058',
+  proname => 'multirange_contains_elem', prorettype => 'bool',
+  proargtypes => 'anymultirange anyelement', prosrc => 'multirange_contains_elem' },
+{ oid => '8059',
+  proname => 'multirange_contains_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_contains_range' },
+{ oid => '8060',
+  proname => 'multirange_contains_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_contains_multirange' },
+{ oid => '8061',
+  proname => 'elem_contained_by_multirange', prorettype => 'bool',
+  proargtypes => 'anyelement anymultirange', prosrc => 'elem_contained_by_multirange' },
+{ oid => '8062',
+  proname => 'range_contained_by_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_contained_by_multirange' },
+{ oid => '8063',
+  proname => 'multirange_contained_by_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_contained_by_multirange' },
+{ oid => '8088',
+  proname => 'range_adjacent_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_adjacent_multirange' },
+{ oid => '8089',
+  proname => 'multirange_adjacent_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_adjacent_multirange' },
+{ oid => '8090',
+  proname => 'multirange_adjacent_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_adjacent_range' },
+{ oid => '8076',
+  proname => 'range_before_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_before_multirange' },
+{ oid => '8077',
+  proname => 'multirange_before_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_before_range' },
+{ oid => '8078',
+  proname => 'multirange_before_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_before_multirange' },
+{ oid => '8079',
+  proname => 'range_after_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_after_multirange' },
+{ oid => '8080',
+  proname => 'multirange_after_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_after_range' },
+{ oid => '8081',
+  proname => 'multirange_after_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_after_multirange' },
+{ oid => '8100',
+  proname => 'range_overleft_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_overleft_multirange' },
+{ oid => '8101',
+  proname => 'multirange_overleft_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_overleft_range' },
+{ oid => '8102',
+  proname => 'multirange_overleft_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_overleft_multirange' },
+{ oid => '8103',
+  proname => 'range_overright_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_overright_multirange' },
+{ oid => '8104',
+  proname => 'multirange_overright_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_overright_range' },
+{ oid => '8105',
+  proname => 'multirange_overright_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_overright_multirange' },
+{ oid => '8114',
+  proname => 'multirange_union', prorettype => 'anymultirange',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_union' },
+{ oid => '8120',
+  proname => 'multirange_minus', prorettype => 'anymultirange',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_minus' },
+{ oid => '8126',
+  proname => 'multirange_intersect', prorettype => 'anymultirange',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_intersect' },
+{ oid => '8022', descr => 'less-equal-greater',
+  proname => 'multirange_cmp', prorettype => 'int4',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_cmp' },
+{ oid => '8023',
+  proname => 'multirange_lt', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_lt' },
+{ oid => '8024',
+  proname => 'multirange_le', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_le' },
+{ oid => '8025',
+  proname => 'multirange_ge', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_ge' },
+{ oid => '8026',
+  proname => 'multirange_gt', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_gt' },
+{ oid => '8038', descr => 'hash a multirange',
+  proname => 'hash_multirange', prorettype => 'int4', proargtypes => 'anymultirange',
+  prosrc => 'hash_multirange' },
+{ oid => '8039', descr => 'hash a multirange',
+  proname => 'hash_multirange_extended', prorettype => 'int8',
+  proargtypes => 'anymultirange int8', prosrc => 'hash_multirange_extended' },
+
+{ oid => '8045', descr => 'int4multirange constructor',
+  proname => 'int4multirange', proisstrict => 'f',
+  prorettype => 'int4multirange', proargtypes => '',
+  prosrc => 'multirange_constructor0' },
+{ oid => '8146', descr => 'int4multirange constructor',
+  proname => 'int4multirange', proisstrict => 't',
+  prorettype => 'int4multirange', proargtypes => 'int4range',
+  prosrc => 'multirange_constructor1' },
+{ oid => '8046', descr => 'int4multirange constructor',
+  proname => 'int4multirange', provariadic => 'int4range', proisstrict => 'f',
+  prorettype => 'int4multirange', proargtypes => '_int4range',
+  proallargtypes => '{_int4range}', proargmodes => '{v}',
+  prosrc => 'multirange_constructor2' },
+{ oid => '8047', descr => 'nummultirange constructor',
+  proname => 'nummultirange', proisstrict => 'f',
+  prorettype => 'nummultirange', proargtypes => '',
+  prosrc => 'multirange_constructor0' },
+{ oid => '8147', descr => 'nummultirange constructor',
+  proname => 'nummultirange', proisstrict => 't',
+  prorettype => 'nummultirange', proargtypes => 'numrange',
+  prosrc => 'multirange_constructor1' },
+{ oid => '8048', descr => 'nummultirange constructor',
+  proname => 'nummultirange', provariadic => 'numrange', proisstrict => 'f',
+  prorettype => 'nummultirange', proargtypes => '_numrange',
+  proallargtypes => '{_numrange}', proargmodes => '{v}',
+  prosrc => 'multirange_constructor2' },
+{ oid => '8049', descr => 'tsmultirange constructor',
+  proname => 'tsmultirange', proisstrict => 'f',
+  prorettype => 'tsmultirange', proargtypes => '',
+  prosrc => 'multirange_constructor0' },
+{ oid => '8148', descr => 'tsmultirange constructor',
+  proname => 'tsmultirange', proisstrict => 't',
+  prorettype => 'tsmultirange', proargtypes => 'tsrange',
+  prosrc => 'multirange_constructor1' },
+{ oid => '8050', descr => 'tsmultirange constructor',
+  proname => 'tsmultirange', provariadic => 'tsrange', proisstrict => 'f',
+  prorettype => 'tsmultirange', proargtypes => '_tsrange',
+  proallargtypes => '{_tsrange}', proargmodes => '{v}',
+  prosrc => 'multirange_constructor2' },
+{ oid => '8051', descr => 'tstzmultirange constructor',
+  proname => 'tstzmultirange', proisstrict => 'f',
+  prorettype => 'tstzmultirange', proargtypes => '',
+  prosrc => 'multirange_constructor0' },
+{ oid => '8149', descr => 'tstzmultirange constructor',
+  proname => 'tstzmultirange', proisstrict => 't',
+  prorettype => 'tstzmultirange', proargtypes => 'tstzrange',
+  prosrc => 'multirange_constructor1' },
+{ oid => '8052', descr => 'tstzmultirange constructor',
+  proname => 'tstzmultirange', provariadic => 'tstzrange', proisstrict => 'f',
+  prorettype => 'tstzmultirange', proargtypes => '_tstzrange',
+  proallargtypes => '{_tstzrange}', proargmodes => '{v}',
+  prosrc => 'multirange_constructor2' },
+{ oid => '8053', descr => 'datemultirange constructor',
+  proname => 'datemultirange', proisstrict => 'f',
+  prorettype => 'datemultirange', proargtypes => '',
+  prosrc => 'multirange_constructor0' },
+{ oid => '8150', descr => 'datemultirange constructor',
+  proname => 'datemultirange', proisstrict => 't',
+  prorettype => 'datemultirange', proargtypes => 'daterange',
+  prosrc => 'multirange_constructor1' },
+{ oid => '8054', descr => 'datemultirange constructor',
+  proname => 'datemultirange', provariadic => 'daterange', proisstrict => 'f',
+  prorettype => 'datemultirange', proargtypes => '_daterange',
+  proallargtypes => '{_daterange}', proargmodes => '{v}',
+  prosrc => 'multirange_constructor2' },
+{ oid => '8055', descr => 'int8multirange constructor',
+  proname => 'int8multirange', proisstrict => 'f',
+  prorettype => 'int8multirange', proargtypes => '',
+  prosrc => 'multirange_constructor0' },
+{ oid => '8151', descr => 'int8multirange constructor',
+  proname => 'int8multirange', proisstrict => 't',
+  prorettype => 'int8multirange', proargtypes => 'int8range',
+  prosrc => 'multirange_constructor1' },
+{ oid => '8056', descr => 'int8multirange constructor',
+  proname => 'int8multirange', provariadic => 'int8range', proisstrict => 'f',
+  prorettype => 'int8multirange', proargtypes => '_int8range',
+  proallargtypes => '{_int8range}', proargmodes => '{v}',
+  prosrc => 'multirange_constructor2' },
+{ oid => '8152', descr => 'anymultirange cast',
+  proname => 'anymultirange', proisstrict => 't',
+  prorettype => 'anymultirange', proargtypes => 'anyrange',
+  prosrc => 'multirange_constructor1' },
+{ oid => '8130', descr => 'aggregate transition function',
+  proname => 'range_agg_transfn', proisstrict => 'f', prorettype => 'internal',
+  proargtypes => 'internal anyrange', prosrc => 'range_agg_transfn' },
+{ oid => '8131', descr => 'aggregate final function',
+  proname => 'range_agg_finalfn', proisstrict => 'f', prorettype => 'anymultirange',
+  proargtypes => 'internal anyrange', prosrc => 'range_agg_finalfn' },
+{ oid => '8132', descr => 'combine aggregate input into a multirange',
+  proname => 'range_agg', prokind => 'a', proisstrict => 'f',
+  prorettype => 'anymultirange', proargtypes => 'anyrange',
+  prosrc => 'aggregate_dummy' },
+{ oid => '8135', descr => 'range aggregate by intersecting',
+  proname => 'multirange_intersect_agg_transfn', prorettype => 'anymultirange',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_intersect_agg_transfn'},
+{ oid => '8136', descr => 'range aggregate by intersecting',
+  proname => 'range_intersect_agg', prokind => 'a', proisstrict => 'f',
+  prorettype => 'anymultirange', proargtypes => 'anymultirange',
+  prosrc => 'aggregate_dummy'},
+
 # date, time, timestamp constructors
 { oid => '3846', descr => 'construct date',
   proname => 'make_date', prorettype => 'date', proargtypes => 'int4 int4 int4',
@@ -10104,6 +10367,14 @@
   proname => 'binary_upgrade_set_next_array_pg_type_oid', provolatile => 'v',
   proparallel => 'r', prorettype => 'void', proargtypes => 'oid',
   prosrc => 'binary_upgrade_set_next_array_pg_type_oid' },
+{ oid => '8144', descr => 'for use by pg_upgrade',
+  proname => 'binary_upgrade_set_next_multirange_pg_type_oid', provolatile => 'v',
+  proparallel => 'r', prorettype => 'void', proargtypes => 'oid',
+  prosrc => 'binary_upgrade_set_next_multirange_pg_type_oid' },
+{ oid => '8145', descr => 'for use by pg_upgrade',
+  proname => 'binary_upgrade_set_next_multirange_array_pg_type_oid', provolatile => 'v',
+  proparallel => 'r', prorettype => 'void', proargtypes => 'oid',
+  prosrc => 'binary_upgrade_set_next_multirange_array_pg_type_oid' },
 { oid => '3585', descr => 'for use by pg_upgrade',
   proname => 'binary_upgrade_set_next_toast_pg_type_oid', provolatile => 'v',
   proparallel => 'r', prorettype => 'void', proargtypes => 'oid',
diff --git a/src/include/catalog/pg_range.dat b/src/include/catalog/pg_range.dat
index 479754c245..10060255c9 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',
+  rngmultitypid => 'int4multirange' },
 { rngtypid => 'numrange', rngsubtype => 'numeric',
   rngsubopc => 'btree/numeric_ops', rngcanonical => '-',
-  rngsubdiff => 'numrange_subdiff' },
+  rngsubdiff => 'numrange_subdiff', rngmultitypid => 'nummultirange' },
 { rngtypid => 'tsrange', rngsubtype => 'timestamp',
   rngsubopc => 'btree/timestamp_ops', rngcanonical => '-',
-  rngsubdiff => 'tsrange_subdiff' },
+  rngsubdiff => 'tsrange_subdiff', rngmultitypid => 'tsmultirange' },
 { rngtypid => 'tstzrange', rngsubtype => 'timestamptz',
   rngsubopc => 'btree/timestamptz_ops', rngcanonical => '-',
-  rngsubdiff => 'tstzrange_subdiff' },
+  rngsubdiff => 'tstzrange_subdiff', rngmultitypid => 'tstzmultirange' },
 { rngtypid => 'daterange', rngsubtype => 'date', rngsubopc => 'btree/date_ops',
-  rngcanonical => 'daterange_canonical', rngsubdiff => 'daterange_subdiff' },
+  rngcanonical => 'daterange_canonical', rngsubdiff => 'daterange_subdiff',
+  rngmultitypid => 'datemultirange' },
 { rngtypid => 'int8range', rngsubtype => 'int8', rngsubopc => 'btree/int8_ops',
-  rngcanonical => 'int8range_canonical', rngsubdiff => 'int8range_subdiff' },
+  rngcanonical => 'int8range_canonical', rngsubdiff => 'int8range_subdiff',
+  rngmultitypid => 'int8multirange' },
 
 ]
diff --git a/src/include/catalog/pg_range.h b/src/include/catalog/pg_range.h
index 98172bb1b6..3efd463c6d 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			rngmultitypid 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 fe2c4eabb4..59bae8232f 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 e1a5ab3df3..600ac58c9d 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/commands/typecmds.h b/src/include/commands/typecmds.h
index fc18d64347..7d0a4a9ea5 100644
--- a/src/include/commands/typecmds.h
+++ b/src/include/commands/typecmds.h
@@ -29,6 +29,8 @@ extern ObjectAddress DefineRange(CreateRangeStmt *stmt);
 extern ObjectAddress AlterEnum(AlterEnumStmt *stmt);
 extern ObjectAddress DefineCompositeType(RangeVar *typevar, List *coldeflist);
 extern Oid	AssignTypeArrayOid(void);
+extern Oid	AssignTypeMultirangeOid(void);
+extern Oid	AssignTypeMultirangeArrayOid(void);
 
 extern ObjectAddress AlterDomainDefault(List *names, Node *defaultRaw);
 extern ObjectAddress AlterDomainNotNull(List *names, bool notNull);
diff --git a/src/include/utils/lsyscache.h b/src/include/utils/lsyscache.h
index a216d6793d..b5ff168160 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..8779bd1f6e
--- /dev/null
+++ b/src/include/utils/multirangetypes.h
@@ -0,0 +1,103 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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);
+extern bool multirange_contains_elem_internal(TypeCacheEntry *typcache, MultirangeType * mr,
+											  Datum elem);
+extern bool multirange_contains_range_internal(TypeCacheEntry *typcache, MultirangeType * mr,
+											   RangeType *r);
+extern bool multirange_contains_multirange_internal(TypeCacheEntry *typcache,
+													MultirangeType * mr1,
+													MultirangeType * mr2);
+extern bool range_overlaps_multirange_internal(TypeCacheEntry *typcache, RangeType *r,
+											   MultirangeType * mr);
+extern bool multirange_overlaps_multirange_internal(TypeCacheEntry *typcache,
+													MultirangeType * mr1,
+													MultirangeType * mr2);
+extern bool range_before_multirange_internal(TypeCacheEntry *typcache, RangeType *r,
+											 MultirangeType * mr);
+extern bool range_after_multirange_internal(TypeCacheEntry *typcache, RangeType *r,
+											MultirangeType * mr);
+extern bool multirange_before_multirange_internal(TypeCacheEntry *typcache,
+												  MultirangeType * mr1,
+												  MultirangeType * mr2);
+extern MultirangeType * multirange_minus_internal(Oid mltrngtypoid,
+												  TypeCacheEntry *rangetyp,
+												  int32 range_count1,
+												  RangeType **ranges1,
+												  int32 range_count2,
+												  RangeType **ranges2);
+extern MultirangeType * multirange_intersect_internal(Oid mltrngtypoid,
+													  TypeCacheEntry *rangetyp,
+													  int32 range_count1,
+													  RangeType **ranges1,
+													  int32 range_count2,
+													  RangeType **ranges2);
+
+/* 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 0cbbf09033..204aee4054 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)
 
@@ -92,13 +94,21 @@ typedef struct
  * prototypes for functions defined in rangetypes.c
  */
 
-extern bool range_contains_elem_internal(TypeCacheEntry *typcache, const RangeType *r, Datum val);
-
 /* internal versions of the above */
+extern int	range_cmp_internal(TypeCacheEntry *typcache, const RangeType *r1,
+							   const RangeType *r2);
+extern uint32 hash_range_internal(TypeCacheEntry *typcache, const RangeType *r);
+extern uint64 hash_range_extended_internal(TypeCacheEntry *typcache, const RangeType *r,
+										   Datum seed);
+extern Datum range_lower_internal(TypeCacheEntry *typcache, const RangeType *r1,
+								  bool *isnull);
+extern Datum range_upper_internal(TypeCacheEntry *typcache, const RangeType *r1,
+								  bool *isnull);
 extern bool range_eq_internal(TypeCacheEntry *typcache, const RangeType *r1,
 							  const RangeType *r2);
 extern bool range_ne_internal(TypeCacheEntry *typcache, const RangeType *r1,
 							  const RangeType *r2);
+extern bool range_contains_elem_internal(TypeCacheEntry *typcache, const RangeType *r, Datum val);
 extern bool range_contains_internal(TypeCacheEntry *typcache, const RangeType *r1,
 									const RangeType *r2);
 extern bool range_contained_by_internal(TypeCacheEntry *typcache, const RangeType *r1,
@@ -115,6 +125,12 @@ extern bool range_overleft_internal(TypeCacheEntry *typcache, const RangeType *r
 									const RangeType *r2);
 extern bool range_overright_internal(TypeCacheEntry *typcache, const RangeType *r1,
 									 const RangeType *r2);
+extern RangeType *range_union_internal(TypeCacheEntry *typcache, RangeType *r1,
+									   RangeType *r2, bool strict);
+extern RangeType *range_minus_internal(TypeCacheEntry *typcache, RangeType *r1,
+									   RangeType *r2);
+extern RangeType *range_intersect_internal(TypeCacheEntry *typcache, const RangeType *r1,
+										   const RangeType *r2);
 
 /* assorted support functions */
 extern TypeCacheEntry *range_get_typcache(FunctionCallInfo fcinfo,
@@ -125,6 +141,7 @@ extern void range_deserialize(TypeCacheEntry *typcache, const RangeType *range,
 							  RangeBound *lower, RangeBound *upper,
 							  bool *empty);
 extern char range_get_flags(const RangeType *range);
+extern bool range_has_flag(const RangeType *range, char flag);
 extern void range_set_contain_empty(RangeType *range);
 extern RangeType *make_range(TypeCacheEntry *typcache, RangeBound *lower,
 							 RangeBound *upper, bool empty);
@@ -132,8 +149,12 @@ extern int range_cmp_bounds(TypeCacheEntry *typcache, const RangeBound *b1,
 							const RangeBound *b2);
 extern int range_cmp_bound_values(TypeCacheEntry *typcache, const RangeBound *b1,
 								  const RangeBound *b2);
-extern bool bounds_adjacent(TypeCacheEntry *typcache, RangeBound bound1,
-							RangeBound bound2);
+extern int range_compare(const void *key1, const void *key2, void *arg);
+extern bool bounds_adjacent(TypeCacheEntry *typcache, const RangeBound bound1,
+							const RangeBound bound2);
 extern RangeType *make_empty_range(TypeCacheEntry *typcache);
+extern bool range_split_internal(TypeCacheEntry *typcache, const RangeType *r1,
+								 const RangeType *r2, RangeType **output1,
+								 RangeType **output2);
 
 #endif							/* RANGETYPES_H */
diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h
index f27b73d76d..bc51bbeb54 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 66ff17dbd5..fe240ea004 100644
--- a/src/include/utils/typcache.h
+++ b/src/include/utils/typcache.h
@@ -99,6 +99,11 @@ typedef struct TypeCacheEntry
 	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 66bf9f1211..e277329931 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/dependency.out b/src/test/regress/expected/dependency.out
index b4a11b8aa9..778699a961 100644
--- a/src/test/regress/expected/dependency.out
+++ b/src/test/regress/expected/dependency.out
@@ -141,6 +141,7 @@ owner of table deptest
 owner of function deptest_func()
 owner of type deptest_enum
 owner of type deptest_range
+owner of type deptest_multirange
 owner of table deptest2
 owner of sequence ss1
 owner of type deptest_t
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..4446f8e4c3
--- /dev/null
+++ b/src/test/regress/expected/multirangetypes.out
@@ -0,0 +1,2386 @@
+-- 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)
+
+--
+-- test casts, both a built-in range type and a user-defined one:
+--
+select 'empty'::int4range::int4multirange;
+ int4multirange 
+----------------
+ {}
+(1 row)
+
+select int4range(1, 3)::int4multirange;
+ int4range 
+-----------
+ {[1,3)}
+(1 row)
+
+select int4range(1, null)::int4multirange;
+ int4range 
+-----------
+ {[1,)}
+(1 row)
+
+select int4range(null, null)::int4multirange;
+ int4range 
+-----------
+ {(,)}
+(1 row)
+
+select 'empty'::textrange::textmultirange;
+ textmultirange 
+----------------
+ {}
+(1 row)
+
+select textrange('a', 'c')::textmultirange;
+ textrange 
+-----------
+ {[a,c)}
+(1 row)
+
+select textrange('a', null)::textmultirange;
+ textrange 
+-----------
+ {[a,)}
+(1 row)
+
+select textrange(null, null)::textmultirange;
+ textrange 
+-----------
+ {(,)}
+(1 row)
+
+--
+-- create some test data and test the operators
+--
+CREATE TABLE nummultirange_test (nmr NUMMULTIRANGE);
+CREATE INDEX nummultirange_test_btree ON nummultirange_test(nmr);
+INSERT INTO nummultirange_test VALUES('{}');
+INSERT INTO nummultirange_test VALUES('{[,)}');
+INSERT INTO nummultirange_test VALUES('{[3,]}');
+INSERT INTO nummultirange_test VALUES('{[,), [3,]}');
+INSERT INTO nummultirange_test VALUES('{[, 5)}');
+INSERT INTO nummultirange_test VALUES(nummultirange());
+INSERT INTO nummultirange_test VALUES(nummultirange(variadic '{}'::numrange[]));
+INSERT INTO nummultirange_test VALUES(nummultirange(numrange(1.1, 2.2)));
+INSERT INTO nummultirange_test VALUES('{empty}');
+INSERT INTO nummultirange_test VALUES(nummultirange(numrange(1.7, 1.7, '[]'), numrange(1.7, 1.9)));
+INSERT INTO nummultirange_test VALUES(nummultirange(numrange(1.7, 1.7, '[]'), numrange(1.9, 2.1)));
+SELECT nmr, isempty(nmr), lower(nmr), upper(nmr) FROM nummultirange_test ORDER BY nmr;
+          nmr          | isempty | lower | upper 
+-----------------------+---------+-------+-------
+ {}                    | t       |       |      
+ {}                    | t       |       |      
+ {}                    | t       |       |      
+ {}                    | t       |       |      
+ {(,5)}                | f       |       |     5
+ {(,)}                 | f       |       |      
+ {(,)}                 | f       |       |      
+ {[1.1,2.2)}           | f       |   1.1 |   2.2
+ {[1.7,1.7],[1.9,2.1)} | f       |   1.7 |   2.1
+ {[1.7,1.9)}           | f       |   1.7 |   1.9
+ {[3,)}                | f       |     3 |      
+(11 rows)
+
+SELECT nmr, lower_inc(nmr), lower_inf(nmr), upper_inc(nmr), upper_inf(nmr) FROM nummultirange_test ORDER BY nmr;
+          nmr          | lower_inc | lower_inf | upper_inc | upper_inf 
+-----------------------+-----------+-----------+-----------+-----------
+ {}                    | f         | f         | f         | f
+ {}                    | f         | f         | f         | f
+ {}                    | f         | f         | f         | f
+ {}                    | f         | f         | f         | f
+ {(,5)}                | f         | t         | f         | f
+ {(,)}                 | f         | t         | f         | t
+ {(,)}                 | f         | t         | f         | t
+ {[1.1,2.2)}           | t         | f         | f         | f
+ {[1.7,1.7],[1.9,2.1)} | t         | f         | f         | f
+ {[1.7,1.9)}           | t         | f         | f         | f
+ {[3,)}                | t         | f         | f         | t
+(11 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr = '{}';
+ nmr 
+-----
+ {}
+ {}
+ {}
+ {}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr = '{(,5)}';
+  nmr   
+--------
+ {(,5)}
+(1 row)
+
+SELECT * FROM nummultirange_test WHERE nmr = '{[3,)}';
+  nmr   
+--------
+ {[3,)}
+(1 row)
+
+SELECT * FROM nummultirange_test WHERE nmr = '{[1.7,1.7]}';
+ nmr 
+-----
+(0 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr = '{[1.7,1.7],[1.9,2.1)}';
+          nmr          
+-----------------------
+ {[1.7,1.7],[1.9,2.1)}
+(1 row)
+
+SELECT * FROM nummultirange_test WHERE nmr < '{}';
+ nmr 
+-----
+(0 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr < '{[-1000.0, -1000.0]}';
+  nmr   
+--------
+ {}
+ {(,)}
+ {(,)}
+ {(,5)}
+ {}
+ {}
+ {}
+(7 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr < '{[0.0, 1.0]}';
+  nmr   
+--------
+ {}
+ {(,)}
+ {(,)}
+ {(,5)}
+ {}
+ {}
+ {}
+(7 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr < '{[1000.0, 1001.0]}';
+          nmr          
+-----------------------
+ {}
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+ {}
+ {}
+ {[1.1,2.2)}
+ {}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(11 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr <= '{}';
+ nmr 
+-----
+ {}
+ {}
+ {}
+ {}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr <= '{[3,)}';
+          nmr          
+-----------------------
+ {}
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+ {}
+ {}
+ {[1.1,2.2)}
+ {}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(11 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr >= '{}';
+          nmr          
+-----------------------
+ {}
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+ {}
+ {}
+ {[1.1,2.2)}
+ {}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(11 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr >= '{[3,)}';
+  nmr   
+--------
+ {[3,)}
+(1 row)
+
+SELECT * FROM nummultirange_test WHERE nmr > '{}';
+          nmr          
+-----------------------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+ {[1.1,2.2)}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(7 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr > '{[-1000.0, -1000.0]}';
+          nmr          
+-----------------------
+ {[3,)}
+ {[1.1,2.2)}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr > '{[0.0, 1.0]}';
+          nmr          
+-----------------------
+ {[3,)}
+ {[1.1,2.2)}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr > '{[1000.0, 1001.0]}';
+ nmr 
+-----
+(0 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr <> '{}';
+          nmr          
+-----------------------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+ {[1.1,2.2)}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(7 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr <> '{(,5)}';
+          nmr          
+-----------------------
+ {}
+ {(,)}
+ {[3,)}
+ {(,)}
+ {}
+ {}
+ {[1.1,2.2)}
+ {}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(10 rows)
+
+select nummultirange(numrange(2.0, 1.0));
+ERROR:  range lower bound must be less than or equal to range upper bound
+select nummultirange(numrange(5.0, 6.0), numrange(1.0, 2.0));
+     nummultirange     
+-----------------------
+ {[1.0,2.0),[5.0,6.0)}
+(1 row)
+
+-- overlaps
+SELECT * FROM nummultirange_test WHERE range_overlaps_multirange(numrange(4.0, 4.2), nmr);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE numrange(4.0, 4.2) && nmr;
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE multirange_overlaps_range(nmr, numrange(4.0, 4.2));
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr && numrange(4.0, 4.2);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE multirange_overlaps_multirange(nmr, nummultirange(numrange(4.0, 4.2), numrange(6.0, 7.0)));
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr && nummultirange(numrange(4.0, 4.2), numrange(6.0, 7.0));
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr && nummultirange(numrange(6.0, 7.0));
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+(3 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr && nummultirange(numrange(6.0, 7.0), numrange(8.0, 9.0));
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+(3 rows)
+
+-- mr contains x
+SELECT * FROM nummultirange_test WHERE multirange_contains_elem(nmr, 4.0);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr @> 4.0;
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE multirange_contains_range(nmr, numrange(4.0, 4.2));
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr @> numrange(4.0, 4.2);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE multirange_contains_multirange(nmr, '{[4.0,4.2), [6.0, 8.0)}');
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+(3 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr @> '{[4.0,4.2), [6.0, 8.0)}'::nummultirange;
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+(3 rows)
+
+-- x is contained by mr
+SELECT * FROM nummultirange_test WHERE elem_contained_by_multirange(4.0, nmr);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE 4.0 <@ nmr;
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE range_contained_by_multirange(numrange(4.0, 4.2), nmr);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE numrange(4.0, 4.2) <@ nmr;
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE multirange_contained_by_multirange('{[4.0,4.2), [6.0, 8.0)}', nmr);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+(3 rows)
+
+SELECT * FROM nummultirange_test WHERE '{[4.0,4.2), [6.0, 8.0)}'::nummultirange <@ nmr;
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+(3 rows)
+
+-- overlaps
+SELECT 'empty'::numrange && nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT 'empty'::numrange && nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() && 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) && 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() && nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() && nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) && nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) && nummultirange(numrange(1,2), numrange(7,8));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(7,8)) && nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) && nummultirange(numrange(1,2), numrange(3.5,8));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(3.5,8)) && numrange(3,4);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(3.5,8)) && nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+-- contains
+SELECT nummultirange() @> nummultirange();
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange() @> 'empty'::numrange;
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(null,null)) @> numrange(1,2);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(null,null)) @> numrange(null,2);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(null,null)) @> numrange(2,null);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(null,5)) @> numrange(null,3);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(null,5)) @> numrange(null,8);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(5,null)) @> numrange(8,null);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(5,null)) @> numrange(3,null);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,5)) @> numrange(8,9);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,5)) @> numrange(3,9);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,5)) @> numrange(1,4);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,5)) @> numrange(1,5);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(-4,-2), numrange(1,5)) @> numrange(1,5);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,5), numrange(8,9)) @> numrange(1,5);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,5), numrange(8,9)) @> numrange(6,7);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,5), numrange(6,9)) @> numrange(6,7);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[1,5)}'::nummultirange @> '{[1,5)}';
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[-4,-2), [1,5)}'::nummultirange @> '{[1,5)}';
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[1,5), [8,9)}'::nummultirange @> '{[1,5)}';
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[1,5), [8,9)}'::nummultirange @> '{[6,7)}';
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT '{[1,5), [6,9)}'::nummultirange @> '{[6,7)}';
+ ?column? 
+----------
+ t
+(1 row)
+
+-- is contained by
+SELECT nummultirange() <@ nummultirange();
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT 'empty'::numrange <@ nummultirange();
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,2) <@ nummultirange(numrange(null,null));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(null,2) <@ nummultirange(numrange(null,null));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(2,null) <@ nummultirange(numrange(null,null));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(null,3) <@ nummultirange(numrange(null,5));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(null,8) <@ nummultirange(numrange(null,5));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(8,null) <@ nummultirange(numrange(5,null));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(3,null) <@ nummultirange(numrange(5,null));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(8,9) <@ nummultirange(numrange(1,5));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(3,9) <@ nummultirange(numrange(1,5));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(1,4) <@ nummultirange(numrange(1,5));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,5) <@ nummultirange(numrange(1,5));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,5) <@ nummultirange(numrange(-4,-2), numrange(1,5));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,5) <@ nummultirange(numrange(1,5), numrange(8,9));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(6,7) <@ nummultirange(numrange(1,5), numrange(8,9));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(6,7) <@ nummultirange(numrange(1,5), numrange(6,9));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[1,5)}' <@ '{[1,5)}'::nummultirange;
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[1,5)}' <@ '{[-4,-2), [1,5)}'::nummultirange;
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[1,5)}' <@ '{[1,5), [8,9)}'::nummultirange;
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[6,7)}' <@ '{[1,5), [8,9)}'::nummultirange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT '{[6,7)}' <@ '{[1,5), [6,9)}'::nummultirange;
+ ?column? 
+----------
+ t
+(1 row)
+
+-- overleft
+SELECT 'empty'::numrange &< nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT 'empty'::numrange &< nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() &< 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) &< 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() &< nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) &< nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() &< nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(6,7) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(1,2) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,4) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,6) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(3.5,6) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(6,7)) &< numrange(3,4);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) &< numrange(3,4);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,4)) &< numrange(3,4);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,6)) &< numrange(3,4);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3.5,6)) &< numrange(3,4);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(6,7)) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,4)) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,6)) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3.5,6)) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+-- overright
+SELECT nummultirange() &> 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) &> 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT 'empty'::numrange &> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT 'empty'::numrange &> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() &> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() &> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) &> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> numrange(6,7);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> numrange(1,2);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> numrange(1,4);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> numrange(1,6);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> numrange(3.5,6);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(3,4) &> nummultirange(numrange(6,7));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(3,4) &> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(3,4) &> nummultirange(numrange(1,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(3,4) &> nummultirange(numrange(1,6));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(3,4) &> nummultirange(numrange(3.5,6));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(6,7));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(1,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(1,6));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(3.5,6));
+ ?column? 
+----------
+ f
+(1 row)
+
+-- meets
+SELECT 'empty'::numrange -|- nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT 'empty'::numrange -|- nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() -|- 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() -|- nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() -|- nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(1,2) -|- nummultirange(numrange(2,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,2) -|- nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- numrange(2,4);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- numrange(3,4);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- nummultirange(numrange(2,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(5,6)) -|- nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(5,6)) -|- nummultirange(numrange(6,7));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(5,6)) -|- nummultirange(numrange(8,9));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- nummultirange(numrange(2,4), numrange(6,7));
+ ?column? 
+----------
+ t
+(1 row)
+
+-- strictly left
+select 'empty'::numrange << nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(1,2) << nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(1,2) << nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+select numrange(1,2) << nummultirange(numrange(0,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(1,2) << nummultirange(numrange(0,4), numrange(7,8));
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange() << 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange() << numrange(1,2);
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(3,4)) << numrange(3,6);
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(0,2)) << numrange(3,6);
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange(numrange(0,2), numrange(7,8)) << numrange(3,6);
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(-4,-2), numrange(0,2)) << numrange(3,6);
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange() << nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange() << nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(1,2)) << nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(1,2)) << nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(1,2)) << nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange(numrange(1,2)) << nummultirange(numrange(3,4), numrange(7,8));
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange(numrange(1,2), numrange(4,5)) << nummultirange(numrange(3,4), numrange(7,8));
+ ?column? 
+----------
+ f
+(1 row)
+
+-- strictly right
+select nummultirange() >> 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange() >> numrange(1,2);
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(3,4)) >> numrange(1,2);
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange(numrange(0,4)) >> numrange(1,2);
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(0,4), numrange(7,8)) >> numrange(1,2);
+ ?column? 
+----------
+ f
+(1 row)
+
+select 'empty'::numrange >> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(1,2) >> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(3,6) >> nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(3,6) >> nummultirange(numrange(0,2));
+ ?column? 
+----------
+ t
+(1 row)
+
+select numrange(3,6) >> nummultirange(numrange(0,2), numrange(7,8));
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(3,6) >> nummultirange(numrange(-4,-2), numrange(0,2));
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange() >> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(1,2)) >> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange() >> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(1,2)) >> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(3,4)) >> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange(numrange(3,4), numrange(7,8)) >> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange(numrange(3,4), numrange(7,8)) >> nummultirange(numrange(1,2), numrange(4,5));
+ ?column? 
+----------
+ f
+(1 row)
+
+-- union
+SELECT nummultirange() + nummultirange();
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange() + nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) + nummultirange();
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) + nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) + nummultirange(numrange(2,4));
+ ?column? 
+----------
+ {[1,4)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) + nummultirange(numrange(3,4));
+   ?column?    
+---------------
+ {[1,2),[3,4)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) + nummultirange(numrange(2,4));
+ ?column? 
+----------
+ {[1,5)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) + nummultirange(numrange(3,4));
+   ?column?    
+---------------
+ {[1,2),[3,5)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) + nummultirange(numrange(0,9));
+ ?column? 
+----------
+ {[0,9)}
+(1 row)
+
+-- merge
+SELECT range_merge(nummultirange());
+ range_merge 
+-------------
+ empty
+(1 row)
+
+SELECT range_merge(nummultirange(numrange(1,2)));
+ range_merge 
+-------------
+ [1,2)
+(1 row)
+
+SELECT range_merge(nummultirange(numrange(1,2), numrange(7,8)));
+ range_merge 
+-------------
+ [1,8)
+(1 row)
+
+-- minus
+SELECT nummultirange() - nummultirange();
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange() - nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) - nummultirange();
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(3,4)) - nummultirange();
+   ?column?    
+---------------
+ {[1,2),[3,4)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) - nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) - nummultirange(numrange(2,4));
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) - nummultirange(numrange(3,4));
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {[2,4)}
+(1 row)
+
+SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(2,3));
+   ?column?    
+---------------
+ {[1,2),[3,4)}
+(1 row)
+
+SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(0,8));
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(0,2));
+ ?column? 
+----------
+ {[2,4)}
+(1 row)
+
+SELECT nummultirange(numrange(1,8)) - nummultirange(numrange(0,2), numrange(3,4));
+   ?column?    
+---------------
+ {[2,3),[4,8)}
+(1 row)
+
+SELECT nummultirange(numrange(1,8)) - nummultirange(numrange(2,3), numrange(5,null));
+   ?column?    
+---------------
+ {[1,2),[3,5)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(-2,0));
+   ?column?    
+---------------
+ {[1,2),[4,5)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(2,4));
+   ?column?    
+---------------
+ {[1,2),[4,5)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(3,5));
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(0,9));
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange(numrange(1,3), numrange(4,5)) - nummultirange(numrange(2,9));
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(8,9));
+   ?column?    
+---------------
+ {[1,2),[4,5)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(-2,0), numrange(8,9));
+   ?column?    
+---------------
+ {[1,2),[4,5)}
+(1 row)
+
+-- intersection
+SELECT nummultirange() * nummultirange();
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange() * nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) * nummultirange();
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT '{[1,3)}'::nummultirange * '{[1,5)}'::nummultirange;
+ ?column? 
+----------
+ {[1,3)}
+(1 row)
+
+SELECT '{[1,3)}'::nummultirange * '{[0,5)}'::nummultirange;
+ ?column? 
+----------
+ {[1,3)}
+(1 row)
+
+SELECT '{[1,3)}'::nummultirange * '{[0,2)}'::nummultirange;
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT '{[1,3)}'::nummultirange * '{[2,5)}'::nummultirange;
+ ?column? 
+----------
+ {[2,3)}
+(1 row)
+
+SELECT '{[1,4)}'::nummultirange * '{[2,3)}'::nummultirange;
+ ?column? 
+----------
+ {[2,3)}
+(1 row)
+
+SELECT '{[1,4)}'::nummultirange * '{[0,2), [3,5)}'::nummultirange;
+   ?column?    
+---------------
+ {[1,2),[3,4)}
+(1 row)
+
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[0,8), [9,12)}'::nummultirange;
+       ?column?       
+----------------------
+ {[1,4),[7,8),[9,10)}
+(1 row)
+
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[9,12)}'::nummultirange;
+ ?column? 
+----------
+ {[9,10)}
+(1 row)
+
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[-5,-4), [5,6), [9,12)}'::nummultirange;
+ ?column? 
+----------
+ {[9,10)}
+(1 row)
+
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[0,2), [3,8), [9,12)}'::nummultirange;
+          ?column?          
+----------------------------
+ {[1,2),[3,4),[7,8),[9,10)}
+(1 row)
+
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[0,2), [3,8), [9,12)}'::nummultirange;
+          ?column?          
+----------------------------
+ {[1,2),[3,4),[7,8),[9,10)}
+(1 row)
+
+--
+-- range_agg function
+--
+create table reservations ( room_id integer not null, booked_during daterange );
+insert into reservations values
+-- 1: has a meets and a gap
+(1, daterange('2018-07-01', '2018-07-07')),
+(1, daterange('2018-07-07', '2018-07-14')),
+(1, daterange('2018-07-20', '2018-07-22')),
+-- 2: just a single row
+(2, daterange('2018-07-01', '2018-07-03')),
+-- 3: one null range
+(3, NULL),
+-- 4: two null ranges
+(4, NULL),
+(4, NULL),
+-- 5: a null range and a non-null range
+(5, NULL),
+(5, daterange('2018-07-01', '2018-07-03')),
+-- 6: has overlap
+(6, daterange('2018-07-01', '2018-07-07')),
+(6, daterange('2018-07-05', '2018-07-10')),
+-- 7: two ranges that meet: no gap or overlap
+(7, daterange('2018-07-01', '2018-07-07')),
+(7, daterange('2018-07-07', '2018-07-14')),
+-- 8: an empty range
+(8, 'empty'::daterange)
+;
+SELECT   room_id, range_agg(booked_during)
+FROM     reservations
+GROUP BY room_id
+ORDER BY room_id;
+ room_id |                     range_agg                     
+---------+---------------------------------------------------
+       1 | {[07-01-2018,07-14-2018),[07-20-2018,07-22-2018)}
+       2 | {[07-01-2018,07-03-2018)}
+       3 | 
+       4 | 
+       5 | {[07-01-2018,07-03-2018)}
+       6 | {[07-01-2018,07-10-2018)}
+       7 | {[07-01-2018,07-14-2018)}
+       8 | {}
+(8 rows)
+
+-- range_agg on a custom range type too
+SELECT  range_agg(r)
+FROM    (VALUES
+          ('[a,c]'::textrange),
+          ('[b,b]'::textrange),
+          ('[c,f]'::textrange),
+          ('[g,h)'::textrange),
+          ('[h,j)'::textrange)
+        ) t(r);
+   range_agg   
+---------------
+ {[a,f],[g,j)}
+(1 row)
+
+select range_intersect_agg(nmr) from nummultirange_test;
+ range_intersect_agg 
+---------------------
+ {}
+(1 row)
+
+select range_intersect_agg(nmr) from nummultirange_test where false;
+ range_intersect_agg 
+---------------------
+ 
+(1 row)
+
+-- test with just one input:
+select range_intersect_agg(nmr) from (values ('{[1,2]}'::nummultirange)) t(nmr);
+ range_intersect_agg 
+---------------------
+ {[1,2]}
+(1 row)
+
+select range_intersect_agg(nmr) from nummultirange_test where nmr @> 4.0;
+ range_intersect_agg 
+---------------------
+ {[3,5)}
+(1 row)
+
+create table nummultirange_test2(nmr nummultirange);
+create index nummultirange_test2_hash_idx on nummultirange_test2 using hash (nmr);
+INSERT INTO nummultirange_test2 VALUES('{[, 5)}');
+INSERT INTO nummultirange_test2 VALUES(nummultirange(numrange(1.1, 2.2)));
+INSERT INTO nummultirange_test2 VALUES(nummultirange(numrange(1.1, 2.2)));
+INSERT INTO nummultirange_test2 VALUES(nummultirange(numrange(1.1, 2.2,'()')));
+INSERT INTO nummultirange_test2 VALUES('{}');
+select * from nummultirange_test2 where nmr = '{}';
+ nmr 
+-----
+ {}
+(1 row)
+
+select * from nummultirange_test2 where nmr = nummultirange(numrange(1.1, 2.2));
+     nmr     
+-------------
+ {[1.1,2.2)}
+ {[1.1,2.2)}
+(2 rows)
+
+select * from nummultirange_test2 where nmr = nummultirange(numrange(1.1, 2.3));
+ nmr 
+-----
+(0 rows)
+
+set enable_nestloop=t;
+set enable_hashjoin=f;
+set enable_mergejoin=f;
+select * from nummultirange_test natural join nummultirange_test2 order by nmr;
+     nmr     
+-------------
+ {}
+ {}
+ {}
+ {}
+ {(,5)}
+ {[1.1,2.2)}
+ {[1.1,2.2)}
+(7 rows)
+
+set enable_nestloop=f;
+set enable_hashjoin=t;
+set enable_mergejoin=f;
+select * from nummultirange_test natural join nummultirange_test2 order by nmr;
+     nmr     
+-------------
+ {}
+ {}
+ {}
+ {}
+ {(,5)}
+ {[1.1,2.2)}
+ {[1.1,2.2)}
+(7 rows)
+
+set enable_nestloop=f;
+set enable_hashjoin=f;
+set enable_mergejoin=t;
+select * from nummultirange_test natural join nummultirange_test2 order by nmr;
+     nmr     
+-------------
+ {}
+ {}
+ {}
+ {}
+ {(,5)}
+ {[1.1,2.2)}
+ {[1.1,2.2)}
+(7 rows)
+
+set enable_nestloop to default;
+set enable_hashjoin to default;
+set enable_mergejoin to default;
+DROP TABLE nummultirange_test2;
+--
+-- Test user-defined multirange of floats
+--
+select '{[123.001, 5.e9)}'::float8multirange @> 888.882::float8;
+ ?column? 
+----------
+ t
+(1 row)
+
+create table float8multirange_test(f8mr float8multirange, i int);
+insert into float8multirange_test values(float8multirange(float8range(-100.00007, '1.111113e9')), 42);
+select * from float8multirange_test;
+           f8mr            | i  
+---------------------------+----
+ {[-100.00007,1111113000)} | 42
+(1 row)
+
+drop table float8multirange_test;
+--
+-- Test multirange types over domains
+--
+create domain mydomain as int4;
+create type mydomainrange as range(subtype=mydomain);
+select '{[4,50)}'::mydomainmultirange @> 7::mydomain;
+ ?column? 
+----------
+ t
+(1 row)
+
+drop domain mydomain cascade;
+NOTICE:  drop cascades to type mydomainrange
+--
+-- Test domains over multirange types
+--
+create domain restrictedmultirange as int4multirange check (upper(value) < 10);
+select '{[4,5)}'::restrictedmultirange @> 7;
+ ?column? 
+----------
+ f
+(1 row)
+
+select '{[4,50)}'::restrictedmultirange @> 7; -- should fail
+ERROR:  value for domain restrictedmultirange violates check constraint "restrictedmultirange_check"
+drop domain restrictedmultirange;
+--
+-- Test multiple multirange types over the same subtype
+--
+create type textrange1 as range(subtype=text, collation="C");
+create type textrange2 as range(subtype=text, collation="C");
+select textmultirange1(textrange2('a','Z'));  -- should fail
+ERROR:  function textmultirange1(textrange2) does not exist
+LINE 1: select textmultirange1(textrange2('a','Z'));
+               ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+select textmultirange1(textrange1('a','Z')) @> 'b'::text;
+ERROR:  range lower bound must be less than or equal to range upper bound
+select textmultirange2(textrange2('a','z')) @> 'b'::text;
+ ?column? 
+----------
+ t
+(1 row)
+
+drop type textrange1;
+drop type textrange2;
+--
+-- Test polymorphic type system
+--
+create function anyarray_anymultirange_func(a anyarray, r anymultirange)
+  returns anyelement as 'select $1[1] + lower($2);' language sql;
+select anyarray_anymultirange_func(ARRAY[1,2], int4multirange(int4range(10,20)));
+ anyarray_anymultirange_func 
+-----------------------------
+                          11
+(1 row)
+
+-- should fail
+select anyarray_anymultirange_func(ARRAY[1,2], nummultirange(numrange(10,20)));
+ERROR:  function anyarray_anymultirange_func(integer[], nummultirange) does not exist
+LINE 1: select anyarray_anymultirange_func(ARRAY[1,2], nummultirange...
+               ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+drop function anyarray_anymultirange_func(anyarray, anymultirange);
+-- should fail
+create function bogus_func(anyelement)
+  returns anymultirange as 'select int4multirange(int4range(1,10))' language sql;
+ERROR:  cannot determine result data type
+DETAIL:  A function returning "anyrange" or "anymultirange" must have at least one "anyrange" or "anymultirange" argument.
+-- should fail
+create function bogus_func(int)
+  returns anymultirange as 'select int4multirange(int4range(1,10))' language sql;
+ERROR:  cannot determine result data type
+DETAIL:  A function returning a polymorphic type must have at least one polymorphic argument.
+create function range_add_bounds(anymultirange)
+  returns anyelement as 'select lower($1) + upper($1)' language sql;
+select range_add_bounds(int4multirange(int4range(1, 17)));
+ range_add_bounds 
+------------------
+               18
+(1 row)
+
+select range_add_bounds(nummultirange(numrange(1.0001, 123.123)));
+ range_add_bounds 
+------------------
+         124.1231
+(1 row)
+
+create function multirangetypes_sql(q anymultirange, b anyarray, out c anyelement)
+  as $$ select upper($1) + $2[1] $$
+  language sql;
+select multirangetypes_sql(int4multirange(int4range(1,10)), ARRAY[2,20]);
+ multirangetypes_sql 
+---------------------
+                  12
+(1 row)
+
+select multirangetypes_sql(nummultirange(numrange(1,10)), ARRAY[2,20]);  -- match failure
+ERROR:  function multirangetypes_sql(nummultirange, integer[]) does not exist
+LINE 1: select multirangetypes_sql(nummultirange(numrange(1,10)), AR...
+               ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+--
+-- Arrays of multiranges
+--
+select ARRAY[nummultirange(numrange(1.1, 1.2)), nummultirange(numrange(12.3, 155.5))];
+              array               
+----------------------------------
+ {"{[1.1,1.2)}","{[12.3,155.5)}"}
+(1 row)
+
+create table i8mr_array (f1 int, f2 int8multirange[]);
+insert into i8mr_array values (42, array[int8multirange(int8range(1,10)), int8multirange(int8range(2,20))]);
+select * from i8mr_array;
+ f1 |           f2            
+----+-------------------------
+ 42 | {"{[1,10)}","{[2,20)}"}
+(1 row)
+
+drop table i8mr_array;
+--
+-- Multiranges of arrays
+--
+select arraymultirange(arrayrange(ARRAY[1,2], ARRAY[2,1]));
+   arraymultirange   
+---------------------
+ {["{1,2}","{2,1}")}
+(1 row)
+
+select arraymultirange(arrayrange(ARRAY[2,1], ARRAY[1,2]));  -- fail
+ERROR:  range lower bound must be less than or equal to range upper bound
+select array[1,1] <@ arraymultirange(arrayrange(array[1,2], array[2,1]));
+ ?column? 
+----------
+ f
+(1 row)
+
+select array[1,3] <@ arraymultirange(arrayrange(array[1,2], array[2,1]));
+ ?column? 
+----------
+ t
+(1 row)
+
+--
+-- Ranges of composites
+--
+create type two_ints as (a int, b int);
+create type two_ints_range as range (subtype = two_ints);
+-- with force_parallel_mode on, this exercises tqueue.c's range remapping
+select *, row_to_json(upper(t)) as u from
+  (values (two_ints_multirange(two_ints_range(row(1,2), row(3,4)))),
+          (two_ints_multirange(two_ints_range(row(5,6), row(7,8))))) v(t);
+          t          |       u       
+---------------------+---------------
+ {["(1,2)","(3,4)")} | {"a":3,"b":4}
+ {["(5,6)","(7,8)")} | {"a":7,"b":8}
+(2 rows)
+
+drop type two_ints cascade;
+NOTICE:  drop cascades to type two_ints_range
+--
+-- Check behavior when subtype lacks a hash function
+--
+set enable_sort = off;  -- try to make it pick a hash setop implementation
+select '{(2,5)}'::cashmultirange except select '{(5,6)}'::cashmultirange;
+ cashmultirange  
+-----------------
+ {($2.00,$5.00)}
+(1 row)
+
+reset enable_sort;
+--
+-- OUT/INOUT/TABLE functions
+--
+-- infer anymultirange from anymultirange
+create function mr_outparam_succeed(i anymultirange, out r anymultirange, out t text)
+  as $$ select $1, 'foo'::text $$ language sql;
+select * from mr_outparam_succeed(int4multirange(int4range(1,2)));
+    r    |  t  
+---------+-----
+ {[1,2)} | foo
+(1 row)
+
+-- infer anyarray from anymultirange
+create function mr_outparam_succeed2(i anymultirange, out r anyarray, out t text)
+  as $$ select ARRAY[upper($1)], 'foo'::text $$ language sql;
+select * from mr_outparam_succeed2(int4multirange(int4range(1,2)));
+  r  |  t  
+-----+-----
+ {2} | foo
+(1 row)
+
+-- infer anyrange from anymultirange
+create function mr_outparam_succeed3(i anymultirange, out r anyrange, out t text)
+  as $$ select range_merge($1), 'foo'::text $$ language sql;
+select * from mr_outparam_succeed3(int4multirange(int4range(1,2)));
+   r   |  t  
+-------+-----
+ [1,2) | foo
+(1 row)
+
+-- infer anymultirange from anyrange
+create function mr_outparam_succeed4(i anyrange, out r anymultirange, out t text)
+  as $$ select anymultirange($1), 'foo'::text $$ language sql;
+select * from mr_outparam_succeed4(int4range(1,2));
+    r    |  t  
+---------+-----
+ {[1,2)} | foo
+(1 row)
+
+-- infer anyelement from anymultirange
+create function mr_inoutparam_succeed(out i anyelement, inout r anymultirange)
+  as $$ select upper($1), $1 $$ language sql;
+select * from mr_inoutparam_succeed(int4multirange(int4range(1,2)));
+ i |    r    
+---+---------
+ 2 | {[1,2)}
+(1 row)
+
+-- infer anyelement+anymultirange from anyelement+anymultirange
+create function mr_table_succeed(i anyelement, r anymultirange) returns table(i anyelement, r anymultirange)
+  as $$ select $1, $2 $$ language sql;
+select * from mr_table_succeed(123, int4multirange(int4range(1,11)));
+  i  |    r     
+-----+----------
+ 123 | {[1,11)}
+(1 row)
+
+-- should fail
+create function mr_outparam_fail(i anyelement, out r anymultirange, out t text)
+  as $$ select '[1,10]', 'foo' $$ language sql;
+ERROR:  cannot determine result data type
+DETAIL:  A function returning "anyrange" or "anymultirange" must have at least one "anyrange" or "anymultirange" argument.
+--should fail
+create function mr_inoutparam_fail(inout i anyelement, out r anymultirange)
+  as $$ select $1, '[1,10]' $$ language sql;
+ERROR:  cannot determine result data type
+DETAIL:  A function returning "anyrange" or "anymultirange" must have at least one "anyrange" or "anymultirange" argument.
+--should fail
+create function mr_table_fail(i anyelement) returns table(i anyelement, r anymultirange)
+  as $$ select $1, '[1,10]' $$ language sql;
+ERROR:  cannot determine result data type
+DETAIL:  A function returning "anyrange" or "anymultirange" must have at least one "anyrange" or "anymultirange" argument.
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/rangetypes.out b/src/test/regress/expected/rangetypes.out
index 5ed6ae47ec..4a46a286a1 100644
--- a/src/test/regress/expected/rangetypes.out
+++ b/src/test/regress/expected/rangetypes.out
@@ -518,6 +518,24 @@ select numrange(1.0, 2.0) * numrange(2.5, 3.0);
  empty
 (1 row)
 
+select range_intersect_agg(nr) from numrange_test;
+ range_intersect_agg 
+---------------------
+ empty
+(1 row)
+
+select range_intersect_agg(nr) from numrange_test where false;
+ range_intersect_agg 
+---------------------
+ 
+(1 row)
+
+select range_intersect_agg(nr) from numrange_test where nr @> 4.0;
+ range_intersect_agg 
+---------------------
+ [3,5)
+(1 row)
+
 create table numrange_test2(nr numrange);
 create index numrange_test2_hash_idx on numrange_test2 using hash (nr);
 INSERT INTO numrange_test2 VALUES('[, 5)');
@@ -1284,7 +1302,7 @@ drop function anyarray_anyrange_func(anyarray, anyrange);
 create function bogus_func(anyelement)
   returns anyrange as 'select int4range(1,10)' language sql;
 ERROR:  cannot determine result data type
-DETAIL:  A function returning "anyrange" must have at least one "anyrange" argument.
+DETAIL:  A function returning "anyrange" or "anymultirange" must have at least one "anyrange" or "anymultirange" argument.
 -- should fail
 create function bogus_func(int)
   returns anyrange as 'select int4range(1,10)' language sql;
@@ -1395,6 +1413,7 @@ reset enable_sort;
 --
 -- OUT/INOUT/TABLE functions
 --
+-- infer anyrange from anyrange
 create function outparam_succeed(i anyrange, out r anyrange, out t text)
   as $$ select $1, 'foo'::text $$ language sql;
 select * from outparam_succeed(int4range(1,2));
@@ -1403,6 +1422,16 @@ select * from outparam_succeed(int4range(1,2));
  [1,2) | foo
 (1 row)
 
+-- infer anyarray from anyrange
+create function outparam_succeed2(i anyrange, out r anyarray, out t text)
+  as $$ select ARRAY[upper($1)], 'foo'::text $$ language sql;
+select * from outparam_succeed2(int4range(int4range(1,2)));
+  r  |  t  
+-----+-----
+ {2} | foo
+(1 row)
+
+-- infer anyelement from anyrange
 create function inoutparam_succeed(out i anyelement, inout r anyrange)
   as $$ select upper($1), $1 $$ language sql;
 select * from inoutparam_succeed(int4range(1,2));
@@ -1411,6 +1440,7 @@ select * from inoutparam_succeed(int4range(1,2));
  2 | [1,2)
 (1 row)
 
+-- infer anyelement+anyrange from anyelement+anyrange
 create function table_succeed(i anyelement, r anyrange) returns table(i anyelement, r anyrange)
   as $$ select $1, $2 $$ language sql;
 select * from table_succeed(123, int4range(1,11));
@@ -1423,14 +1453,14 @@ select * from table_succeed(123, int4range(1,11));
 create function outparam_fail(i anyelement, out r anyrange, out t text)
   as $$ select '[1,10]', 'foo' $$ language sql;
 ERROR:  cannot determine result data type
-DETAIL:  A function returning "anyrange" must have at least one "anyrange" argument.
+DETAIL:  A function returning "anyrange" or "anymultirange" must have at least one "anyrange" or "anymultirange" argument.
 --should fail
 create function inoutparam_fail(inout i anyelement, out r anyrange)
   as $$ select $1, '[1,10]' $$ language sql;
 ERROR:  cannot determine result data type
-DETAIL:  A function returning "anyrange" must have at least one "anyrange" argument.
+DETAIL:  A function returning "anyrange" or "anymultirange" must have at least one "anyrange" or "anymultirange" argument.
 --should fail
 create function table_fail(i anyelement) returns table(i anyelement, r anyrange)
   as $$ select $1, '[1,10]' $$ language sql;
 ERROR:  cannot determine result data type
-DETAIL:  A function returning "anyrange" must have at least one "anyrange" argument.
+DETAIL:  A function returning "anyrange" or "anymultirange" must have at least one "anyrange" or "anymultirange" argument.
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index 070de78e85..6f7fcf6326 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -92,6 +92,7 @@ num_exp_sqrt|t
 num_exp_sub|t
 num_input_test|f
 num_result|f
+nummultirange_test|t
 onek|t
 onek2|t
 path_tbl|f
@@ -172,6 +173,7 @@ quad_poly_tbl|t
 radix_text_tbl|t
 ramp|f
 real_city|f
+reservations|f
 road|t
 shighway|t
 slow_emp4000|f
diff --git a/src/test/regress/expected/type_sanity.out b/src/test/regress/expected/type_sanity.out
index cd7fc03b04..60a9b4d22f 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.rngmultitypid
+FROM pg_range p1
+WHERE p1.rngmultitypid IS NULL OR p1.rngmultitypid = 0;
+ rngtypid | rngsubtype | rngmultitypid 
+----------+------------+---------------
+(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..40f4ef35b0
--- /dev/null
+++ b/src/test/regress/sql/multirangetypes.sql
@@ -0,0 +1,625 @@
+-- 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'));
+
+--
+-- test casts, both a built-in range type and a user-defined one:
+--
+select 'empty'::int4range::int4multirange;
+select int4range(1, 3)::int4multirange;
+select int4range(1, null)::int4multirange;
+select int4range(null, null)::int4multirange;
+select 'empty'::textrange::textmultirange;
+select textrange('a', 'c')::textmultirange;
+select textrange('a', null)::textmultirange;
+select textrange(null, null)::textmultirange;
+
+--
+-- create some test data and test the operators
+--
+
+CREATE TABLE nummultirange_test (nmr NUMMULTIRANGE);
+CREATE INDEX nummultirange_test_btree ON nummultirange_test(nmr);
+
+INSERT INTO nummultirange_test VALUES('{}');
+INSERT INTO nummultirange_test VALUES('{[,)}');
+INSERT INTO nummultirange_test VALUES('{[3,]}');
+INSERT INTO nummultirange_test VALUES('{[,), [3,]}');
+INSERT INTO nummultirange_test VALUES('{[, 5)}');
+INSERT INTO nummultirange_test VALUES(nummultirange());
+INSERT INTO nummultirange_test VALUES(nummultirange(variadic '{}'::numrange[]));
+INSERT INTO nummultirange_test VALUES(nummultirange(numrange(1.1, 2.2)));
+INSERT INTO nummultirange_test VALUES('{empty}');
+INSERT INTO nummultirange_test VALUES(nummultirange(numrange(1.7, 1.7, '[]'), numrange(1.7, 1.9)));
+INSERT INTO nummultirange_test VALUES(nummultirange(numrange(1.7, 1.7, '[]'), numrange(1.9, 2.1)));
+
+SELECT nmr, isempty(nmr), lower(nmr), upper(nmr) FROM nummultirange_test ORDER BY nmr;
+SELECT nmr, lower_inc(nmr), lower_inf(nmr), upper_inc(nmr), upper_inf(nmr) FROM nummultirange_test ORDER BY nmr;
+
+SELECT * FROM nummultirange_test WHERE nmr = '{}';
+SELECT * FROM nummultirange_test WHERE nmr = '{(,5)}';
+SELECT * FROM nummultirange_test WHERE nmr = '{[3,)}';
+SELECT * FROM nummultirange_test WHERE nmr = '{[1.7,1.7]}';
+SELECT * FROM nummultirange_test WHERE nmr = '{[1.7,1.7],[1.9,2.1)}';
+SELECT * FROM nummultirange_test WHERE nmr < '{}';
+SELECT * FROM nummultirange_test WHERE nmr < '{[-1000.0, -1000.0]}';
+SELECT * FROM nummultirange_test WHERE nmr < '{[0.0, 1.0]}';
+SELECT * FROM nummultirange_test WHERE nmr < '{[1000.0, 1001.0]}';
+SELECT * FROM nummultirange_test WHERE nmr <= '{}';
+SELECT * FROM nummultirange_test WHERE nmr <= '{[3,)}';
+SELECT * FROM nummultirange_test WHERE nmr >= '{}';
+SELECT * FROM nummultirange_test WHERE nmr >= '{[3,)}';
+SELECT * FROM nummultirange_test WHERE nmr > '{}';
+SELECT * FROM nummultirange_test WHERE nmr > '{[-1000.0, -1000.0]}';
+SELECT * FROM nummultirange_test WHERE nmr > '{[0.0, 1.0]}';
+SELECT * FROM nummultirange_test WHERE nmr > '{[1000.0, 1001.0]}';
+SELECT * FROM nummultirange_test WHERE nmr <> '{}';
+SELECT * FROM nummultirange_test WHERE nmr <> '{(,5)}';
+
+select nummultirange(numrange(2.0, 1.0));
+select nummultirange(numrange(5.0, 6.0), numrange(1.0, 2.0));
+
+-- overlaps
+SELECT * FROM nummultirange_test WHERE range_overlaps_multirange(numrange(4.0, 4.2), nmr);
+SELECT * FROM nummultirange_test WHERE numrange(4.0, 4.2) && nmr;
+SELECT * FROM nummultirange_test WHERE multirange_overlaps_range(nmr, numrange(4.0, 4.2));
+SELECT * FROM nummultirange_test WHERE nmr && numrange(4.0, 4.2);
+SELECT * FROM nummultirange_test WHERE multirange_overlaps_multirange(nmr, nummultirange(numrange(4.0, 4.2), numrange(6.0, 7.0)));
+SELECT * FROM nummultirange_test WHERE nmr && nummultirange(numrange(4.0, 4.2), numrange(6.0, 7.0));
+SELECT * FROM nummultirange_test WHERE nmr && nummultirange(numrange(6.0, 7.0));
+SELECT * FROM nummultirange_test WHERE nmr && nummultirange(numrange(6.0, 7.0), numrange(8.0, 9.0));
+
+-- mr contains x
+SELECT * FROM nummultirange_test WHERE multirange_contains_elem(nmr, 4.0);
+SELECT * FROM nummultirange_test WHERE nmr @> 4.0;
+SELECT * FROM nummultirange_test WHERE multirange_contains_range(nmr, numrange(4.0, 4.2));
+SELECT * FROM nummultirange_test WHERE nmr @> numrange(4.0, 4.2);
+SELECT * FROM nummultirange_test WHERE multirange_contains_multirange(nmr, '{[4.0,4.2), [6.0, 8.0)}');
+SELECT * FROM nummultirange_test WHERE nmr @> '{[4.0,4.2), [6.0, 8.0)}'::nummultirange;
+
+-- x is contained by mr
+SELECT * FROM nummultirange_test WHERE elem_contained_by_multirange(4.0, nmr);
+SELECT * FROM nummultirange_test WHERE 4.0 <@ nmr;
+SELECT * FROM nummultirange_test WHERE range_contained_by_multirange(numrange(4.0, 4.2), nmr);
+SELECT * FROM nummultirange_test WHERE numrange(4.0, 4.2) <@ nmr;
+SELECT * FROM nummultirange_test WHERE multirange_contained_by_multirange('{[4.0,4.2), [6.0, 8.0)}', nmr);
+SELECT * FROM nummultirange_test WHERE '{[4.0,4.2), [6.0, 8.0)}'::nummultirange <@ nmr;
+
+-- overlaps
+SELECT 'empty'::numrange && nummultirange();
+SELECT 'empty'::numrange && nummultirange(numrange(1,2));
+SELECT nummultirange() && 'empty'::numrange;
+SELECT nummultirange(numrange(1,2)) && 'empty'::numrange;
+SELECT nummultirange() && nummultirange();
+SELECT nummultirange() && nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) && nummultirange();
+SELECT nummultirange(numrange(3,4)) && nummultirange(numrange(1,2), numrange(7,8));
+SELECT nummultirange(numrange(1,2), numrange(7,8)) && nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(3,4)) && nummultirange(numrange(1,2), numrange(3.5,8));
+SELECT nummultirange(numrange(1,2), numrange(3.5,8)) && numrange(3,4);
+SELECT nummultirange(numrange(1,2), numrange(3.5,8)) && nummultirange(numrange(3,4));
+
+-- contains
+SELECT nummultirange() @> nummultirange();
+SELECT nummultirange() @> 'empty'::numrange;
+SELECT nummultirange(numrange(null,null)) @> numrange(1,2);
+SELECT nummultirange(numrange(null,null)) @> numrange(null,2);
+SELECT nummultirange(numrange(null,null)) @> numrange(2,null);
+SELECT nummultirange(numrange(null,5)) @> numrange(null,3);
+SELECT nummultirange(numrange(null,5)) @> numrange(null,8);
+SELECT nummultirange(numrange(5,null)) @> numrange(8,null);
+SELECT nummultirange(numrange(5,null)) @> numrange(3,null);
+SELECT nummultirange(numrange(1,5)) @> numrange(8,9);
+SELECT nummultirange(numrange(1,5)) @> numrange(3,9);
+SELECT nummultirange(numrange(1,5)) @> numrange(1,4);
+SELECT nummultirange(numrange(1,5)) @> numrange(1,5);
+SELECT nummultirange(numrange(-4,-2), numrange(1,5)) @> numrange(1,5);
+SELECT nummultirange(numrange(1,5), numrange(8,9)) @> numrange(1,5);
+SELECT nummultirange(numrange(1,5), numrange(8,9)) @> numrange(6,7);
+SELECT nummultirange(numrange(1,5), numrange(6,9)) @> numrange(6,7);
+SELECT '{[1,5)}'::nummultirange @> '{[1,5)}';
+SELECT '{[-4,-2), [1,5)}'::nummultirange @> '{[1,5)}';
+SELECT '{[1,5), [8,9)}'::nummultirange @> '{[1,5)}';
+SELECT '{[1,5), [8,9)}'::nummultirange @> '{[6,7)}';
+SELECT '{[1,5), [6,9)}'::nummultirange @> '{[6,7)}';
+
+-- is contained by
+SELECT nummultirange() <@ nummultirange();
+SELECT 'empty'::numrange <@ nummultirange();
+SELECT numrange(1,2) <@ nummultirange(numrange(null,null));
+SELECT numrange(null,2) <@ nummultirange(numrange(null,null));
+SELECT numrange(2,null) <@ nummultirange(numrange(null,null));
+SELECT numrange(null,3) <@ nummultirange(numrange(null,5));
+SELECT numrange(null,8) <@ nummultirange(numrange(null,5));
+SELECT numrange(8,null) <@ nummultirange(numrange(5,null));
+SELECT numrange(3,null) <@ nummultirange(numrange(5,null));
+SELECT numrange(8,9) <@ nummultirange(numrange(1,5));
+SELECT numrange(3,9) <@ nummultirange(numrange(1,5));
+SELECT numrange(1,4) <@ nummultirange(numrange(1,5));
+SELECT numrange(1,5) <@ nummultirange(numrange(1,5));
+SELECT numrange(1,5) <@ nummultirange(numrange(-4,-2), numrange(1,5));
+SELECT numrange(1,5) <@ nummultirange(numrange(1,5), numrange(8,9));
+SELECT numrange(6,7) <@ nummultirange(numrange(1,5), numrange(8,9));
+SELECT numrange(6,7) <@ nummultirange(numrange(1,5), numrange(6,9));
+SELECT '{[1,5)}' <@ '{[1,5)}'::nummultirange;
+SELECT '{[1,5)}' <@ '{[-4,-2), [1,5)}'::nummultirange;
+SELECT '{[1,5)}' <@ '{[1,5), [8,9)}'::nummultirange;
+SELECT '{[6,7)}' <@ '{[1,5), [8,9)}'::nummultirange;
+SELECT '{[6,7)}' <@ '{[1,5), [6,9)}'::nummultirange;
+
+-- overleft
+SELECT 'empty'::numrange &< nummultirange();
+SELECT 'empty'::numrange &< nummultirange(numrange(1,2));
+SELECT nummultirange() &< 'empty'::numrange;
+SELECT nummultirange(numrange(1,2)) &< 'empty'::numrange;
+SELECT nummultirange() &< nummultirange();
+SELECT nummultirange(numrange(1,2)) &< nummultirange();
+SELECT nummultirange() &< nummultirange(numrange(1,2));
+SELECT numrange(6,7) &< nummultirange(numrange(3,4));
+SELECT numrange(1,2) &< nummultirange(numrange(3,4));
+SELECT numrange(1,4) &< nummultirange(numrange(3,4));
+SELECT numrange(1,6) &< nummultirange(numrange(3,4));
+SELECT numrange(3.5,6) &< nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(6,7)) &< numrange(3,4);
+SELECT nummultirange(numrange(1,2)) &< numrange(3,4);
+SELECT nummultirange(numrange(1,4)) &< numrange(3,4);
+SELECT nummultirange(numrange(1,6)) &< numrange(3,4);
+SELECT nummultirange(numrange(3.5,6)) &< numrange(3,4);
+SELECT nummultirange(numrange(6,7)) &< nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,2)) &< nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,4)) &< nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,6)) &< nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(3.5,6)) &< nummultirange(numrange(3,4));
+
+-- overright
+SELECT nummultirange() &> 'empty'::numrange;
+SELECT nummultirange(numrange(1,2)) &> 'empty'::numrange;
+SELECT 'empty'::numrange &> nummultirange();
+SELECT 'empty'::numrange &> nummultirange(numrange(1,2));
+SELECT nummultirange() &> nummultirange();
+SELECT nummultirange() &> nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) &> nummultirange();
+SELECT nummultirange(numrange(3,4)) &> numrange(6,7);
+SELECT nummultirange(numrange(3,4)) &> numrange(1,2);
+SELECT nummultirange(numrange(3,4)) &> numrange(1,4);
+SELECT nummultirange(numrange(3,4)) &> numrange(1,6);
+SELECT nummultirange(numrange(3,4)) &> numrange(3.5,6);
+SELECT numrange(3,4) &> nummultirange(numrange(6,7));
+SELECT numrange(3,4) &> nummultirange(numrange(1,2));
+SELECT numrange(3,4) &> nummultirange(numrange(1,4));
+SELECT numrange(3,4) &> nummultirange(numrange(1,6));
+SELECT numrange(3,4) &> nummultirange(numrange(3.5,6));
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(6,7));
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(1,4));
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(1,6));
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(3.5,6));
+
+-- meets
+SELECT 'empty'::numrange -|- nummultirange();
+SELECT 'empty'::numrange -|- nummultirange(numrange(1,2));
+SELECT nummultirange() -|- 'empty'::numrange;
+SELECT nummultirange(numrange(1,2)) -|- 'empty'::numrange;
+SELECT nummultirange() -|- nummultirange();
+SELECT nummultirange(numrange(1,2)) -|- nummultirange();
+SELECT nummultirange() -|- nummultirange(numrange(1,2));
+SELECT numrange(1,2) -|- nummultirange(numrange(2,4));
+SELECT numrange(1,2) -|- nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,2)) -|- numrange(2,4);
+SELECT nummultirange(numrange(1,2)) -|- numrange(3,4);
+SELECT nummultirange(numrange(1,2)) -|- nummultirange(numrange(2,4));
+SELECT nummultirange(numrange(1,2)) -|- nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,2), numrange(5,6)) -|- nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,2), numrange(5,6)) -|- nummultirange(numrange(6,7));
+SELECT nummultirange(numrange(1,2), numrange(5,6)) -|- nummultirange(numrange(8,9));
+SELECT nummultirange(numrange(1,2)) -|- nummultirange(numrange(2,4), numrange(6,7));
+
+-- strictly left
+select 'empty'::numrange << nummultirange();
+select numrange(1,2) << nummultirange();
+select numrange(1,2) << nummultirange(numrange(3,4));
+select numrange(1,2) << nummultirange(numrange(0,4));
+select numrange(1,2) << nummultirange(numrange(0,4), numrange(7,8));
+select nummultirange() << 'empty'::numrange;
+select nummultirange() << numrange(1,2);
+select nummultirange(numrange(3,4)) << numrange(3,6);
+select nummultirange(numrange(0,2)) << numrange(3,6);
+select nummultirange(numrange(0,2), numrange(7,8)) << numrange(3,6);
+select nummultirange(numrange(-4,-2), numrange(0,2)) << numrange(3,6);
+select nummultirange() << nummultirange();
+select nummultirange() << nummultirange(numrange(1,2));
+select nummultirange(numrange(1,2)) << nummultirange();
+select nummultirange(numrange(1,2)) << nummultirange(numrange(1,2));
+select nummultirange(numrange(1,2)) << nummultirange(numrange(3,4));
+select nummultirange(numrange(1,2)) << nummultirange(numrange(3,4), numrange(7,8));
+select nummultirange(numrange(1,2), numrange(4,5)) << nummultirange(numrange(3,4), numrange(7,8));
+
+-- strictly right
+select nummultirange() >> 'empty'::numrange;
+select nummultirange() >> numrange(1,2);
+select nummultirange(numrange(3,4)) >> numrange(1,2);
+select nummultirange(numrange(0,4)) >> numrange(1,2);
+select nummultirange(numrange(0,4), numrange(7,8)) >> numrange(1,2);
+select 'empty'::numrange >> nummultirange();
+select numrange(1,2) >> nummultirange();
+select numrange(3,6) >> nummultirange(numrange(3,4));
+select numrange(3,6) >> nummultirange(numrange(0,2));
+select numrange(3,6) >> nummultirange(numrange(0,2), numrange(7,8));
+select numrange(3,6) >> nummultirange(numrange(-4,-2), numrange(0,2));
+select nummultirange() >> nummultirange();
+select nummultirange(numrange(1,2)) >> nummultirange();
+select nummultirange() >> nummultirange(numrange(1,2));
+select nummultirange(numrange(1,2)) >> nummultirange(numrange(1,2));
+select nummultirange(numrange(3,4)) >> nummultirange(numrange(1,2));
+select nummultirange(numrange(3,4), numrange(7,8)) >> nummultirange(numrange(1,2));
+select nummultirange(numrange(3,4), numrange(7,8)) >> nummultirange(numrange(1,2), numrange(4,5));
+
+-- union
+SELECT nummultirange() + nummultirange();
+SELECT nummultirange() + nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) + nummultirange();
+SELECT nummultirange(numrange(1,2)) + nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) + nummultirange(numrange(2,4));
+SELECT nummultirange(numrange(1,2)) + nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) + nummultirange(numrange(2,4));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) + nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) + nummultirange(numrange(0,9));
+
+-- merge
+SELECT range_merge(nummultirange());
+SELECT range_merge(nummultirange(numrange(1,2)));
+SELECT range_merge(nummultirange(numrange(1,2), numrange(7,8)));
+
+-- minus
+SELECT nummultirange() - nummultirange();
+SELECT nummultirange() - nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) - nummultirange();
+SELECT nummultirange(numrange(1,2), numrange(3,4)) - nummultirange();
+SELECT nummultirange(numrange(1,2)) - nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) - nummultirange(numrange(2,4));
+SELECT nummultirange(numrange(1,2)) - nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(2,3));
+SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(0,8));
+SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(0,2));
+SELECT nummultirange(numrange(1,8)) - nummultirange(numrange(0,2), numrange(3,4));
+SELECT nummultirange(numrange(1,8)) - nummultirange(numrange(2,3), numrange(5,null));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(-2,0));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(2,4));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(3,5));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(0,9));
+SELECT nummultirange(numrange(1,3), numrange(4,5)) - nummultirange(numrange(2,9));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(8,9));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(-2,0), numrange(8,9));
+
+-- intersection
+SELECT nummultirange() * nummultirange();
+SELECT nummultirange() * nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) * nummultirange();
+SELECT '{[1,3)}'::nummultirange * '{[1,5)}'::nummultirange;
+SELECT '{[1,3)}'::nummultirange * '{[0,5)}'::nummultirange;
+SELECT '{[1,3)}'::nummultirange * '{[0,2)}'::nummultirange;
+SELECT '{[1,3)}'::nummultirange * '{[2,5)}'::nummultirange;
+SELECT '{[1,4)}'::nummultirange * '{[2,3)}'::nummultirange;
+SELECT '{[1,4)}'::nummultirange * '{[0,2), [3,5)}'::nummultirange;
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[0,8), [9,12)}'::nummultirange;
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[9,12)}'::nummultirange;
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[-5,-4), [5,6), [9,12)}'::nummultirange;
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[0,2), [3,8), [9,12)}'::nummultirange;
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[0,2), [3,8), [9,12)}'::nummultirange;
+
+--
+-- range_agg function
+--
+create table reservations ( room_id integer not null, booked_during daterange );
+insert into reservations values
+-- 1: has a meets and a gap
+(1, daterange('2018-07-01', '2018-07-07')),
+(1, daterange('2018-07-07', '2018-07-14')),
+(1, daterange('2018-07-20', '2018-07-22')),
+-- 2: just a single row
+(2, daterange('2018-07-01', '2018-07-03')),
+-- 3: one null range
+(3, NULL),
+-- 4: two null ranges
+(4, NULL),
+(4, NULL),
+-- 5: a null range and a non-null range
+(5, NULL),
+(5, daterange('2018-07-01', '2018-07-03')),
+-- 6: has overlap
+(6, daterange('2018-07-01', '2018-07-07')),
+(6, daterange('2018-07-05', '2018-07-10')),
+-- 7: two ranges that meet: no gap or overlap
+(7, daterange('2018-07-01', '2018-07-07')),
+(7, daterange('2018-07-07', '2018-07-14')),
+-- 8: an empty range
+(8, 'empty'::daterange)
+;
+SELECT   room_id, range_agg(booked_during)
+FROM     reservations
+GROUP BY room_id
+ORDER BY room_id;
+
+-- range_agg on a custom range type too
+SELECT  range_agg(r)
+FROM    (VALUES
+          ('[a,c]'::textrange),
+          ('[b,b]'::textrange),
+          ('[c,f]'::textrange),
+          ('[g,h)'::textrange),
+          ('[h,j)'::textrange)
+        ) t(r);
+
+select range_intersect_agg(nmr) from nummultirange_test;
+select range_intersect_agg(nmr) from nummultirange_test where false;
+-- test with just one input:
+select range_intersect_agg(nmr) from (values ('{[1,2]}'::nummultirange)) t(nmr);
+select range_intersect_agg(nmr) from nummultirange_test where nmr @> 4.0;
+
+create table nummultirange_test2(nmr nummultirange);
+create index nummultirange_test2_hash_idx on nummultirange_test2 using hash (nmr);
+
+INSERT INTO nummultirange_test2 VALUES('{[, 5)}');
+INSERT INTO nummultirange_test2 VALUES(nummultirange(numrange(1.1, 2.2)));
+INSERT INTO nummultirange_test2 VALUES(nummultirange(numrange(1.1, 2.2)));
+INSERT INTO nummultirange_test2 VALUES(nummultirange(numrange(1.1, 2.2,'()')));
+INSERT INTO nummultirange_test2 VALUES('{}');
+
+select * from nummultirange_test2 where nmr = '{}';
+select * from nummultirange_test2 where nmr = nummultirange(numrange(1.1, 2.2));
+select * from nummultirange_test2 where nmr = nummultirange(numrange(1.1, 2.3));
+
+set enable_nestloop=t;
+set enable_hashjoin=f;
+set enable_mergejoin=f;
+select * from nummultirange_test natural join nummultirange_test2 order by nmr;
+set enable_nestloop=f;
+set enable_hashjoin=t;
+set enable_mergejoin=f;
+select * from nummultirange_test natural join nummultirange_test2 order by nmr;
+set enable_nestloop=f;
+set enable_hashjoin=f;
+set enable_mergejoin=t;
+select * from nummultirange_test natural join nummultirange_test2 order by nmr;
+
+set enable_nestloop to default;
+set enable_hashjoin to default;
+set enable_mergejoin to default;
+
+DROP TABLE nummultirange_test2;
+
+--
+-- Test user-defined multirange of floats
+--
+
+select '{[123.001, 5.e9)}'::float8multirange @> 888.882::float8;
+create table float8multirange_test(f8mr float8multirange, i int);
+insert into float8multirange_test values(float8multirange(float8range(-100.00007, '1.111113e9')), 42);
+select * from float8multirange_test;
+drop table float8multirange_test;
+
+--
+-- Test multirange types over domains
+--
+
+create domain mydomain as int4;
+create type mydomainrange as range(subtype=mydomain);
+select '{[4,50)}'::mydomainmultirange @> 7::mydomain;
+drop domain mydomain cascade;
+
+--
+-- Test domains over multirange types
+--
+
+create domain restrictedmultirange as int4multirange check (upper(value) < 10);
+select '{[4,5)}'::restrictedmultirange @> 7;
+select '{[4,50)}'::restrictedmultirange @> 7; -- should fail
+drop domain restrictedmultirange;
+
+--
+-- Test multiple multirange types over the same subtype
+--
+
+create type textrange1 as range(subtype=text, collation="C");
+create type textrange2 as range(subtype=text, collation="C");
+
+select textmultirange1(textrange2('a','Z'));  -- should fail
+select textmultirange1(textrange1('a','Z')) @> 'b'::text;
+select textmultirange2(textrange2('a','z')) @> 'b'::text;
+
+drop type textrange1;
+drop type textrange2;
+
+--
+-- Test polymorphic type system
+--
+
+create function anyarray_anymultirange_func(a anyarray, r anymultirange)
+  returns anyelement as 'select $1[1] + lower($2);' language sql;
+
+select anyarray_anymultirange_func(ARRAY[1,2], int4multirange(int4range(10,20)));
+
+-- should fail
+select anyarray_anymultirange_func(ARRAY[1,2], nummultirange(numrange(10,20)));
+
+drop function anyarray_anymultirange_func(anyarray, anymultirange);
+
+-- should fail
+create function bogus_func(anyelement)
+  returns anymultirange as 'select int4multirange(int4range(1,10))' language sql;
+
+-- should fail
+create function bogus_func(int)
+  returns anymultirange as 'select int4multirange(int4range(1,10))' language sql;
+
+create function range_add_bounds(anymultirange)
+  returns anyelement as 'select lower($1) + upper($1)' language sql;
+
+select range_add_bounds(int4multirange(int4range(1, 17)));
+select range_add_bounds(nummultirange(numrange(1.0001, 123.123)));
+
+create function multirangetypes_sql(q anymultirange, b anyarray, out c anyelement)
+  as $$ select upper($1) + $2[1] $$
+  language sql;
+
+select multirangetypes_sql(int4multirange(int4range(1,10)), ARRAY[2,20]);
+select multirangetypes_sql(nummultirange(numrange(1,10)), ARRAY[2,20]);  -- match failure
+
+--
+-- Arrays of multiranges
+--
+
+select ARRAY[nummultirange(numrange(1.1, 1.2)), nummultirange(numrange(12.3, 155.5))];
+
+create table i8mr_array (f1 int, f2 int8multirange[]);
+insert into i8mr_array values (42, array[int8multirange(int8range(1,10)), int8multirange(int8range(2,20))]);
+select * from i8mr_array;
+drop table i8mr_array;
+
+--
+-- Multiranges of arrays
+--
+
+select arraymultirange(arrayrange(ARRAY[1,2], ARRAY[2,1]));
+select arraymultirange(arrayrange(ARRAY[2,1], ARRAY[1,2]));  -- fail
+
+select array[1,1] <@ arraymultirange(arrayrange(array[1,2], array[2,1]));
+select array[1,3] <@ arraymultirange(arrayrange(array[1,2], array[2,1]));
+
+--
+-- Ranges of composites
+--
+
+create type two_ints as (a int, b int);
+create type two_ints_range as range (subtype = two_ints);
+
+-- with force_parallel_mode on, this exercises tqueue.c's range remapping
+select *, row_to_json(upper(t)) as u from
+  (values (two_ints_multirange(two_ints_range(row(1,2), row(3,4)))),
+          (two_ints_multirange(two_ints_range(row(5,6), row(7,8))))) v(t);
+
+drop type two_ints cascade;
+
+--
+-- Check behavior when subtype lacks a hash function
+--
+
+set enable_sort = off;  -- try to make it pick a hash setop implementation
+
+select '{(2,5)}'::cashmultirange except select '{(5,6)}'::cashmultirange;
+
+reset enable_sort;
+
+--
+-- OUT/INOUT/TABLE functions
+--
+
+-- infer anymultirange from anymultirange
+create function mr_outparam_succeed(i anymultirange, out r anymultirange, out t text)
+  as $$ select $1, 'foo'::text $$ language sql;
+
+select * from mr_outparam_succeed(int4multirange(int4range(1,2)));
+
+-- infer anyarray from anymultirange
+create function mr_outparam_succeed2(i anymultirange, out r anyarray, out t text)
+  as $$ select ARRAY[upper($1)], 'foo'::text $$ language sql;
+
+select * from mr_outparam_succeed2(int4multirange(int4range(1,2)));
+
+-- infer anyrange from anymultirange
+create function mr_outparam_succeed3(i anymultirange, out r anyrange, out t text)
+  as $$ select range_merge($1), 'foo'::text $$ language sql;
+select * from mr_outparam_succeed3(int4multirange(int4range(1,2)));
+
+-- infer anymultirange from anyrange
+create function mr_outparam_succeed4(i anyrange, out r anymultirange, out t text)
+  as $$ select anymultirange($1), 'foo'::text $$ language sql;
+
+select * from mr_outparam_succeed4(int4range(1,2));
+
+-- infer anyelement from anymultirange
+create function mr_inoutparam_succeed(out i anyelement, inout r anymultirange)
+  as $$ select upper($1), $1 $$ language sql;
+
+select * from mr_inoutparam_succeed(int4multirange(int4range(1,2)));
+
+-- infer anyelement+anymultirange from anyelement+anymultirange
+create function mr_table_succeed(i anyelement, r anymultirange) returns table(i anyelement, r anymultirange)
+  as $$ select $1, $2 $$ language sql;
+
+select * from mr_table_succeed(123, int4multirange(int4range(1,11)));
+
+-- should fail
+create function mr_outparam_fail(i anyelement, out r anymultirange, out t text)
+  as $$ select '[1,10]', 'foo' $$ language sql;
+
+--should fail
+create function mr_inoutparam_fail(inout i anyelement, out r anymultirange)
+  as $$ select $1, '[1,10]' $$ language sql;
+
+--should fail
+create function mr_table_fail(i anyelement) returns table(i anyelement, r anymultirange)
+  as $$ select $1, '[1,10]' $$ language sql;
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/rangetypes.sql b/src/test/regress/sql/rangetypes.sql
index 2d0ec8964e..4e57352799 100644
--- a/src/test/regress/sql/rangetypes.sql
+++ b/src/test/regress/sql/rangetypes.sql
@@ -118,6 +118,10 @@ select numrange(1.0, 2.0) * numrange(2.0, 3.0);
 select numrange(1.0, 2.0) * numrange(1.5, 3.0);
 select numrange(1.0, 2.0) * numrange(2.5, 3.0);
 
+select range_intersect_agg(nr) from numrange_test;
+select range_intersect_agg(nr) from numrange_test where false;
+select range_intersect_agg(nr) from numrange_test where nr @> 4.0;
+
 create table numrange_test2(nr numrange);
 create index numrange_test2_hash_idx on numrange_test2 using hash (nr);
 
@@ -484,16 +488,25 @@ reset enable_sort;
 -- OUT/INOUT/TABLE functions
 --
 
+-- infer anyrange from anyrange
 create function outparam_succeed(i anyrange, out r anyrange, out t text)
   as $$ select $1, 'foo'::text $$ language sql;
 
 select * from outparam_succeed(int4range(1,2));
 
+-- infer anyarray from anyrange
+create function outparam_succeed2(i anyrange, out r anyarray, out t text)
+  as $$ select ARRAY[upper($1)], 'foo'::text $$ language sql;
+
+select * from outparam_succeed2(int4range(int4range(1,2)));
+
+-- infer anyelement from anyrange
 create function inoutparam_succeed(out i anyelement, inout r anyrange)
   as $$ select upper($1), $1 $$ language sql;
 
 select * from inoutparam_succeed(int4range(1,2));
 
+-- infer anyelement+anyrange from anyelement+anyrange
 create function table_succeed(i anyelement, r anyrange) returns table(i anyelement, r anyrange)
   as $$ select $1, $2 $$ language sql;
 
diff --git a/src/test/regress/sql/type_sanity.sql b/src/test/regress/sql/type_sanity.sql
index fed413bf98..2e34bc8b65 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.rngmultitypid
+FROM pg_range p1
+WHERE p1.rngmultitypid IS NULL OR p1.rngmultitypid = 0;
#73Pavel Stehule
pavel.stehule@gmail.com
In reply to: Paul A Jungwirth (#72)
Re: range_agg

Hi

so 4. 1. 2020 v 6:29 odesílatel Paul A Jungwirth <
pj@illuminatedcomputing.com> napsal:

On Fri, Dec 20, 2019 at 11:29 AM Alvaro Herrera
<alvherre@2ndquadrant.com> wrote:

Should I just give up on implicit casts and require you to specify
one? That makes it a little more annoying to mix range & multirange
types, but personally I'm okay with that. This is my preferred
approach.

+1

Here is a patch adding the casts, rebasing on the latest master, and
incorporating Alvaro's changes. Per his earlier suggestion I combined
it all into one patch file, which also makes it easier to apply
rebases & updates.

This patch was applied cleanly and all tests passed

My work on adding casts also removes the @+ / @- / @* operators and
adds + / - / * operators where both parameters are multiranges. I
retained other operators with mixed range/multirange parameters, both
because there are already range operators with mixed range/scalar
parameters (e.g. <@), and because it seemed like the objection to @+ /
@- / @* was not mixed parameters per se, but rather their
unguessability. Since the other operators are the same as the existing
range operators, they don't share that problem.

looks well

This still leaves the question of how best to format the docs for
these operators. I like being able to combine all the <@ variations
(e.g.) into one table row, but if that is too ugly I could give them
separate rows instead. Giving them all their own row consumes a lot of
vertical space though, and to me that makes the docs more tedious to
read & browse, so it's harder to grasp all the available range-related
operations at a glance.

I have similar opinion - maybe is better do documentation for range and
multirange separately. Sometimes there are still removed operators @+

I'm skeptical of changing pg_type.typtype from 'm' to 'r'. A
multirange isn't a range, so why should we give it the same type? Also
won't this break any queries that are using that column to find range
types? What is the motivation to use the same typtype for both ranges
and multiranges? (There is plenty I don't understand here, e.g. why we
have both typtype and typcategory, so maybe there is a good reason I'm
missing.)

If you can share TYPTYPE_RANGE in code for multiranges, then it should be
'r'. If not, then it needs own value.

I experimented with setting pg_type.typelem to the multirange's range
type, but it seemed to break a lot of things, and reading the code I
saw some places that treat a non-zero typelem as synonymous with being
an array. So I'm reluctant to make this change also, especially when
it is just as easy to query pg_range to get a multirange's range type.

ok, it is unhappy, but it is true. This note should be somewhere in code,
please

Also range types themselves don't set typelem to their base type, and
it seems like we'd want to treat ranges and multiranges the same way
here.

Alvaro also suggested renaming pg_range.mltrngtypid to
pg_range.rngmultitypid, so it shares the same "rng" prefix as the
other columns in this table. Having a different prefix does stand out.
I've included that change in this patch too.

Personally I have not any comments to implemented functionality.

Show quoted text

Yours,
Paul

#74Paul A Jungwirth
pj@illuminatedcomputing.com
In reply to: Pavel Stehule (#73)
1 attachment(s)
Re: range_agg

On Fri, Jan 10, 2020 at 1:38 AM Pavel Stehule <pavel.stehule@gmail.com> wrote:

This still leaves the question of how best to format the docs for
these operators. I like being able to combine all the <@ variations
(e.g.) into one table row, but if that is too ugly I could give them
separate rows instead. Giving them all their own row consumes a lot of
vertical space though, and to me that makes the docs more tedious to
read & browse, so it's harder to grasp all the available range-related
operations at a glance.

I have similar opinion - maybe is better do documentation for range and multirange separately. Sometimes there are still removed operators @+

I like keeping the range/multirange operators together since they are
so similar for both types, but if others disagree I'd be grateful for
more feedback.

You're right that I left in a few references to the old @+ style
operators in the examples; I've fixed those.

If you can share TYPTYPE_RANGE in code for multiranges, then it should be 'r'. If not, then it needs own value.

Okay. I think a new 'm' value is warranted because they are not interchangeable.

I experimented with setting pg_type.typelem to the multirange's range
type, but it seemed to break a lot of things, and reading the code I
saw some places that treat a non-zero typelem as synonymous with being
an array. So I'm reluctant to make this change also, especially when
it is just as easy to query pg_range to get a multirange's range type.

ok, it is unhappy, but it is true. This note should be somewhere in code, please

I've added a comment about this. I put it at the top of DefineRange
but let me know if that's the wrong place.

The attached file is also rebased on currrent master.

Thanks!
Paul

Attachments:

v9-multirange.patchapplication/octet-stream; name=v9-multirange.patchDownload
diff --git a/doc/src/sgml/extend.sgml b/doc/src/sgml/extend.sgml
index a3046f22d0..7ea2f65778 100644
--- a/doc/src/sgml/extend.sgml
+++ b/doc/src/sgml/extend.sgml
@@ -229,9 +229,9 @@
    </indexterm>
 
     <para>
-     Five pseudo-types of special interest are <type>anyelement</type>,
+     Six pseudo-types of special interest are <type>anyelement</type>,
      <type>anyarray</type>, <type>anynonarray</type>, <type>anyenum</type>,
-     and <type>anyrange</type>,
+     <type>anyrange</type>, and <type>anymultirange</type>.
      which are collectively called <firstterm>polymorphic types</firstterm>.
      Any function declared using these types is said to be
      a <firstterm>polymorphic function</firstterm>.  A polymorphic function can
@@ -250,15 +250,15 @@
      position declared as <type>anyarray</type> can have any array data type,
      but similarly they must all be the same type.  And similarly,
      positions declared as <type>anyrange</type> must all be the same range
-     type.  Furthermore, if there are
+     type.  Likewise for <type>anymultirange</type>.
+    </para>
+
+    <para>
+     Furthermore, if there are
      positions declared <type>anyarray</type> and others declared
      <type>anyelement</type>, the actual array type in the
      <type>anyarray</type> positions must be an array whose elements are
      the same type appearing in the <type>anyelement</type> positions.
-     Similarly, if there are positions declared <type>anyrange</type>
-     and others declared <type>anyelement</type>, the actual range type in
-     the <type>anyrange</type> positions must be a range whose subtype is
-     the same type appearing in the <type>anyelement</type> positions.
      <type>anynonarray</type> is treated exactly the same as <type>anyelement</type>,
      but adds the additional constraint that the actual type must not be
      an array type.
@@ -268,6 +268,20 @@
     </para>
 
     <para>
+     Similarly, if there are positions declared <type>anyrange</type>
+     and others declared <type>anyelement</type>, the actual range type in
+     the <type>anyrange</type> positions must be a range whose subtype is
+     the same type appearing in the <type>anyelement</type> positions.
+     The type <type>anymultirange</type> accepts a multirange
+     whose contents match the other polymorphic types.
+     That is it must hold ranges matching <type>anyrange</type>
+     and base type elements matching <type>anyelement</type> (if either of
+     those are present). If <type>anyarray</type> is present its elements
+     must match the base type of the <type>anyrange</type> and/or the base
+     type of the ranges in an <type>anymultirange</type>.
+    </para>
+
+    <para>
      Thus, when more than one argument position is declared with a polymorphic
      type, the net effect is that only certain combinations of actual argument
      types are allowed.  For example, a function declared as
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 72072e7545..55951dc31a 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -14612,12 +14612,14 @@ NULL baz</literallayout>(3 rows)</entry>
   <title>Range Functions and Operators</title>
 
   <para>
-   See <xref linkend="rangetypes"/> for an overview of range types.
+   See <xref linkend="rangetypes"/> for an overview of range and multirange types.
   </para>
 
   <para>
    <xref linkend="range-operators-table"/> shows the operators
-   available for range types.
+   available for range and multirange types.
+   Many of these operators will accept either a range or multirange
+   on either side.
   </para>
 
     <table id="range-operators-table">
@@ -14627,7 +14629,7 @@ NULL baz</literallayout>(3 rows)</entry>
        <row>
         <entry>Operator</entry>
         <entry>Description</entry>
-        <entry>Example</entry>
+        <entry>Examples</entry>
         <entry>Result</entry>
        </row>
       </thead>
@@ -14635,136 +14637,232 @@ NULL baz</literallayout>(3 rows)</entry>
        <row>
         <entry> <literal>=</literal> </entry>
         <entry>equal</entry>
-        <entry><literal>int4range(1,5) = '[1,4]'::int4range</literal></entry>
+        <entry><literallayout class="monospaced">int4range(1,5) = '[1,4]'::int4range
+'{[1,5)}'::int4multirange = '{[1,4]}'::int4multirange</literallayout></entry>
         <entry><literal>t</literal></entry>
        </row>
 
        <row>
         <entry> <literal>&lt;&gt;</literal> </entry>
         <entry>not equal</entry>
-        <entry><literal>numrange(1.1,2.2) &lt;&gt; numrange(1.1,2.3)</literal></entry>
+        <entry><literallayout class="monospaced">numrange(1.1,2.2) &lt;&gt; numrange(1.1,2.3)
+'{[1.1,2.2)}'::nummultirange &lt;&gt; '{[1.1,2.3)}'::nummultirange</literallayout></entry>
         <entry><literal>t</literal></entry>
        </row>
 
        <row>
         <entry> <literal>&lt;</literal> </entry>
         <entry>less than</entry>
-        <entry><literal>int4range(1,10) &lt; int4range(2,3)</literal></entry>
+        <entry><literallayout class="monospaced">int4range(1,10) &lt; int4range(2,3)
+'{[1,10)}'::int4multirange &lt; '{[2,3)}'::int4multirange</literallayout>
+        </entry>
         <entry><literal>t</literal></entry>
        </row>
 
        <row>
         <entry> <literal>&gt;</literal> </entry>
         <entry>greater than</entry>
-        <entry><literal>int4range(1,10) &gt; int4range(1,5)</literal></entry>
+        <entry>
+          <literallayout class="monospaced">int4range(1,10) &gt; int4range(1,5)
+'{[1,10)}'::int4multirange &gt; '{[1,5)}'::int4multirange</literallayout>
+        </entry>
         <entry><literal>t</literal></entry>
        </row>
 
        <row>
         <entry> <literal>&lt;=</literal> </entry>
         <entry>less than or equal</entry>
-        <entry><literal>numrange(1.1,2.2) &lt;= numrange(1.1,2.2)</literal></entry>
+        <entry>
+          <literallayout class="monospaced">numrange(1.1,2.2) &lt;= numrange(1.1,2.2)
+'{[1.1,2.2)}'::nummultirange &lt;= '{[1.1,2.2)}'::nummultirange</literallayout>
+        </entry>
         <entry><literal>t</literal></entry>
        </row>
 
        <row>
         <entry> <literal>&gt;=</literal> </entry>
         <entry>greater than or equal</entry>
-        <entry><literal>numrange(1.1,2.2) &gt;= numrange(1.1,2.0)</literal></entry>
+        <entry>
+          <literallayout class="monospaced">numrange(1.1,2.2) &gt;= numrange(1.1,2.0)
+'{[1.1,2.2)}'::nummultirange &gt;= '{[1.1,2.0)}'::nummultirange</literallayout>
+        </entry>
+        <entry><literal>t</literal></entry>
+       </row>
+
+       <row>
+        <entry> <literal>@&gt;</literal> </entry>
+        <entry>contains multirange</entry>
+        <entry>
+          <literallayout class="monospaced">int4range(2,4) @&gt; '{[2,3)}'::int4multirange
+'{[2,4)}'::int4multirange @&gt; '{[2,3)}'::int4multirange</literallayout>
+        </entry>
         <entry><literal>t</literal></entry>
        </row>
 
        <row>
         <entry> <literal>@&gt;</literal> </entry>
         <entry>contains range</entry>
-        <entry><literal>int4range(2,4) @&gt; int4range(2,3)</literal></entry>
+        <entry>
+          <literallayout class="monospaced">int4range(2,4) @&gt; int4range(2,3)
+'{[2,4)}'::int4multirange @&gt; int4range(2,3)</literallayout>
+        </entry>
         <entry><literal>t</literal></entry>
        </row>
 
        <row>
         <entry> <literal>@&gt;</literal> </entry>
         <entry>contains element</entry>
-        <entry><literal>'[2011-01-01,2011-03-01)'::tsrange @&gt; '2011-01-10'::timestamp</literal></entry>
+        <entry>
+          <literallayout class="monospaced">'[2011-01-01,2011-03-01)'::tsrange @&gt; '2011-01-10'::timestamp
+'{[2011-01-01,2011-03-01)}'::tsmultirange @&gt; '2011-01-10'::timestamp</literallayout>
+        </entry>
+        <entry><literal>t</literal></entry>
+       </row>
+
+       <row>
+        <entry> <literal>&lt;@</literal> </entry>
+        <entry>multirange is contained by</entry>
+        <entry>
+          <literallayout class="monospaced">'{[2,4)}'::int4multirange &lt;@ int4range(1,7)
+'{[2,4)}'::int4multirange &lt;@ '{[1,7)}'::int4multirange</literallayout>
+        </entry>
         <entry><literal>t</literal></entry>
        </row>
 
        <row>
         <entry> <literal>&lt;@</literal> </entry>
         <entry>range is contained by</entry>
-        <entry><literal>int4range(2,4) &lt;@ int4range(1,7)</literal></entry>
+        <entry>
+          <literallayout class="monospaced">int4range(2,4) &lt;@ int4range(1,7)
+int4range(2,4) &lt;@ '{[1,7)}'::int4multirange</literallayout>
+        </entry>
         <entry><literal>t</literal></entry>
        </row>
 
        <row>
         <entry> <literal>&lt;@</literal> </entry>
         <entry>element is contained by</entry>
-        <entry><literal>42 &lt;@ int4range(1,7)</literal></entry>
+        <entry>
+          <literallayout class="monospaced">42 &lt;@ int4range(1,7)
+42 &lt;@ '{[1,7)}'::int4multirange</literallayout>
+        </entry>
         <entry><literal>f</literal></entry>
        </row>
 
        <row>
         <entry> <literal>&amp;&amp;</literal> </entry>
         <entry>overlap (have points in common)</entry>
-        <entry><literal>int8range(3,7) &amp;&amp; int8range(4,12)</literal></entry>
+        <entry>
+          <literallayout class="monospaced">int8range(3,7) &amp;&amp; int8range(4,12)
+int8range(3,7) &amp;&amp; '{[4,12)}'::int8multirange
+'{[3,7)}'::int8multirange &amp;&amp; int8range(4,12)
+'{[3,7)}'::int8multirange &amp;&amp; '{[4,12)}'::int8multirange</literallayout>
+        </entry>
         <entry><literal>t</literal></entry>
        </row>
 
        <row>
         <entry> <literal>&lt;&lt;</literal> </entry>
         <entry>strictly left of</entry>
-        <entry><literal>int8range(1,10) &lt;&lt; int8range(100,110)</literal></entry>
+        <entry>
+          <literallayout class="monospaced">int8range(1,10) &lt;&lt; int8range(100,110)
+int8range(1,10) &lt;&lt; '{[100,110)}'::int8multirange
+'{[1,10)}'::int8multirange &lt;&lt; int8range(100,110)
+'{[1,10)}'::int8multirange &lt;&lt; '{[100,110)}'::int8multirange</literallayout>
+        </entry>
         <entry><literal>t</literal></entry>
        </row>
 
        <row>
         <entry> <literal>&gt;&gt;</literal> </entry>
         <entry>strictly right of</entry>
-        <entry><literal>int8range(50,60) &gt;&gt; int8range(20,30)</literal></entry>
+        <entry>
+          <literallayout class="monospaced">int8range(50,60) &gt;&gt; int8range(20,30)
+int8range(50,60) &gt;&gt; '{[20,30)}'::int8multirange
+'{[50,60)}'::int8multirange &gt;&gt; int8range(20,30)
+'{[50,60)}'::int8multirange &gt;&gt; '{[20,30)}'::int8multirange</literallayout>
+        </entry>
         <entry><literal>t</literal></entry>
        </row>
 
        <row>
         <entry> <literal>&amp;&lt;</literal> </entry>
         <entry>does not extend to the right of</entry>
-        <entry><literal>int8range(1,20) &amp;&lt; int8range(18,20)</literal></entry>
+        <entry>
+          <literallayout class="monospaced">int8range(1,20) &amp;&lt; int8range(18,20)
+int8range(1,20) &amp;&lt; '{[18,20)}'::int8multirange
+'{[1,20)}'::int8multirange &amp;&lt; int8range(18,20)
+'{[1,20)}'::int8multirange &amp;&lt; '{[18,20)}'::int8multirange</literallayout>
+        </entry>
         <entry><literal>t</literal></entry>
        </row>
 
        <row>
         <entry> <literal>&amp;&gt;</literal> </entry>
         <entry>does not extend to the left of</entry>
-        <entry><literal>int8range(7,20) &amp;&gt; int8range(5,10)</literal></entry>
+        <entry>
+          <literallayout class="monospaced">int8range(7,20) &amp;&gt; int8range(5,10)
+int8range(7,20) &amp;&gt; '{[5,10)}'::int8multirange
+'{[7,20)}'::int8multirange &amp;&gt; int8range(5,10)
+'{[7,20)}'::int8multirange &amp;&gt; '{[5,10)}'::int8multirange</literallayout>
+        </entry>
         <entry><literal>t</literal></entry>
        </row>
 
        <row>
         <entry> <literal>-|-</literal> </entry>
         <entry>is adjacent to</entry>
-        <entry><literal>numrange(1.1,2.2) -|- numrange(2.2,3.3)</literal></entry>
+        <entry>
+          <literallayout class="monospaced">numrange(1.1,2.2) -|- numrange(2.2,3.3)
+numrange(1.1,2.2) -|- '{[2.2,3.3)}'::nummultirange
+'{[1.1,2.2)}'::nummultirange -|- numrange(2.2,3.3)
+'{[1.1,2.2)}'::nummultirange -|- '{[2.2,3.3)}'::nummultirange</literallayout>
+        </entry>
         <entry><literal>t</literal></entry>
        </row>
 
        <row>
         <entry> <literal>+</literal> </entry>
         <entry>union</entry>
-        <entry><literal>numrange(5,15) + numrange(10,20)</literal></entry>
+        <entry><literallayout class="monospaced">numrange(5,15) + numrange(10,20)</literallayout></entry>
         <entry><literal>[5,20)</literal></entry>
        </row>
 
        <row>
+        <entry> <literal>+</literal> </entry>
+        <entry>multirange union</entry>
+        <entry><literallayout class="monospaced">'{[5,10)}'::nummultirange + '{[15,20)}'::nummultirange</literallayout></entry>
+        <entry><literal>{[5,10), [15,20)}</literal></entry>
+       </row>
+
+       <row>
         <entry> <literal>*</literal> </entry>
         <entry>intersection</entry>
-        <entry><literal>int8range(5,15) * int8range(10,20)</literal></entry>
+        <entry><literallayout class="monospaced">int8range(5,15) * int8range(10,20)</literallayout></entry>
         <entry><literal>[10,15)</literal></entry>
        </row>
 
        <row>
+        <entry> <literal>*</literal> </entry>
+        <entry>multirange intersection</entry>
+        <entry><literallayout class="monospaced">'{[5,15)}'::int8multirange * '{[10,20)}'::int8multirange</literallayout></entry>
+        <entry><literal>{[10,15)}</literal></entry>
+       </row>
+
+       <row>
         <entry> <literal>-</literal> </entry>
         <entry>difference</entry>
-        <entry><literal>int8range(5,15) - int8range(10,20)</literal></entry>
+        <entry><literallayout class="monospaced">int8range(5,15) - int8range(10,20)</literallayout></entry>
         <entry><literal>[5,10)</literal></entry>
        </row>
 
+       <row>
+        <entry> <literal>-</literal> </entry>
+        <entry>multirange difference</entry>
+        <entry><literallayout class="monospaced">'{[5,20)}'::int8multirange - '{[10,15)}'::int8multirange</literallayout></entry>
+        <entry><literal>{[5,10), [15,20)}</literal></entry>
+       </row>
+
       </tbody>
      </tgroup>
     </table>
@@ -14780,19 +14878,28 @@ NULL baz</literallayout>(3 rows)</entry>
 
   <para>
    The left-of/right-of/adjacent operators always return false when an empty
-   range is involved; that is, an empty range is not considered to be either
-   before or after any other range.
+   range or multirange is involved; that is, an empty range is not considered to
+   be either before or after any other range.
+  </para>
+
+  <para>
+   Elsewhere empty ranges and multiranges are treated as the additive identity:
+   anything unioned with an empty value is itself. Anything minus an empty
+   value is itself. An empty multirange has exactly the same points as an empty
+   range. Every range contains the empty range. Every multirange contains as many
+   empty ranges as you like.
   </para>
 
   <para>
    The union and difference operators will fail if the resulting range would
    need to contain two disjoint sub-ranges, as such a range cannot be
-   represented.
+   represented. The safe versions of union, intersection, and difference
+   return multiranges, so they can handle gaps without failing.
   </para>
 
   <para>
    <xref linkend="range-functions-table"/> shows the functions
-   available for use with range types.
+   available for use with range and multirange types.
   </para>
 
   <indexterm>
@@ -14844,6 +14951,17 @@ NULL baz</literallayout>(3 rows)</entry>
        <row>
         <entry>
          <literal>
+          <function>lower</function>(<type>anymultirange</type>)
+         </literal>
+        </entry>
+        <entry>multirange's element type</entry>
+        <entry>lower bound of multirange</entry>
+        <entry><literal>lower('{[1.1,2.2)}'::nummultirange)</literal></entry>
+        <entry><literal>1.1</literal></entry>
+       </row>
+       <row>
+        <entry>
+         <literal>
           <function>upper</function>(<type>anyrange</type>)
          </literal>
         </entry>
@@ -14855,6 +14973,17 @@ NULL baz</literallayout>(3 rows)</entry>
        <row>
         <entry>
          <literal>
+          <function>upper</function>(<type>anymultirange</type>)
+         </literal>
+        </entry>
+        <entry>multirange's element type</entry>
+        <entry>upper bound of multirange</entry>
+        <entry><literal>upper('{[1.1,2.2)}'::nummultirange)</literal></entry>
+        <entry><literal>2.2</literal></entry>
+       </row>
+       <row>
+        <entry>
+         <literal>
           <function>isempty</function>(<type>anyrange</type>)
          </literal>
         </entry>
@@ -14866,6 +14995,17 @@ NULL baz</literallayout>(3 rows)</entry>
        <row>
         <entry>
          <literal>
+          <function>isempty</function>(<type>anymultirange</type>)
+         </literal>
+        </entry>
+        <entry><type>boolean</type></entry>
+        <entry>is the multirange empty?</entry>
+        <entry><literal>isempty('{[1.1,2.2)}'::nummultirange)</literal></entry>
+        <entry><literal>false</literal></entry>
+       </row>
+       <row>
+        <entry>
+         <literal>
           <function>lower_inc</function>(<type>anyrange</type>)
          </literal>
         </entry>
@@ -14877,6 +15017,17 @@ NULL baz</literallayout>(3 rows)</entry>
        <row>
         <entry>
          <literal>
+          <function>lower_inc</function>(<type>anymultirange</type>)
+         </literal>
+        </entry>
+        <entry><type>boolean</type></entry>
+        <entry>is the lower bound inclusive?</entry>
+        <entry><literal>lower_inc('{[1.1,2.2)}'::nummultirange)</literal></entry>
+        <entry><literal>true</literal></entry>
+       </row>
+       <row>
+        <entry>
+         <literal>
           <function>upper_inc</function>(<type>anyrange</type>)
          </literal>
         </entry>
@@ -14888,6 +15039,17 @@ NULL baz</literallayout>(3 rows)</entry>
        <row>
         <entry>
          <literal>
+          <function>upper_inc</function>(<type>anymultirange</type>)
+         </literal>
+        </entry>
+        <entry><type>boolean</type></entry>
+        <entry>is the upper bound inclusive?</entry>
+        <entry><literal>upper_inc('{[1.1,2.2)}'::nummultirange)</literal></entry>
+        <entry><literal>false</literal></entry>
+       </row>
+       <row>
+        <entry>
+         <literal>
           <function>lower_inf</function>(<type>anyrange</type>)
          </literal>
         </entry>
@@ -14899,6 +15061,17 @@ NULL baz</literallayout>(3 rows)</entry>
        <row>
         <entry>
          <literal>
+          <function>lower_inf</function>(<type>anymultirange</type>)
+         </literal>
+        </entry>
+        <entry><type>boolean</type></entry>
+        <entry>is the lower bound infinite?</entry>
+        <entry><literal>lower_inf('{(,)}'::datemultirange)</literal></entry>
+        <entry><literal>true</literal></entry>
+       </row>
+       <row>
+        <entry>
+         <literal>
           <function>upper_inf</function>(<type>anyrange</type>)
          </literal>
         </entry>
@@ -14910,6 +15083,17 @@ NULL baz</literallayout>(3 rows)</entry>
        <row>
         <entry>
          <literal>
+          <function>upper_inf</function>(<type>anymultirange</type>)
+         </literal>
+        </entry>
+        <entry><type>boolean</type></entry>
+        <entry>is the upper bound infinite?</entry>
+        <entry><literal>upper_inf('{(,)}'::datemultirange)</literal></entry>
+        <entry><literal>true</literal></entry>
+       </row>
+       <row>
+        <entry>
+         <literal>
           <function>range_merge</function>(<type>anyrange</type>, <type>anyrange</type>)
          </literal>
         </entry>
@@ -14918,16 +15102,27 @@ NULL baz</literallayout>(3 rows)</entry>
         <entry><literal>range_merge('[1,2)'::int4range, '[3,4)'::int4range)</literal></entry>
         <entry><literal>[1,4)</literal></entry>
        </row>
+       <row>
+        <entry>
+         <literal>
+          <function>range_merge</function>(<type>anymultirange</type>)
+         </literal>
+        </entry>
+        <entry><type>anyrange</type></entry>
+        <entry>the smallest range which includes the entire multirange</entry>
+        <entry><literal>range_merge('{[1,2), [3,4)}'::int4multirange)</literal></entry>
+        <entry><literal>[1,4)</literal></entry>
+       </row>
       </tbody>
      </tgroup>
     </table>
 
   <para>
    The <function>lower</function> and  <function>upper</function> functions return null
-   if the range is empty or the requested bound is infinite.
+   if the input range/multirange is empty or the requested bound is infinite.
    The <function>lower_inc</function>, <function>upper_inc</function>,
    <function>lower_inf</function>, and <function>upper_inf</function>
-   functions all return false for an empty range.
+   functions all return false for an empty range or multirange.
   </para>
   </sect1>
 
@@ -15249,6 +15444,44 @@ NULL baz</literallayout>(3 rows)</entry>
      <row>
       <entry>
        <indexterm>
+        <primary>range_agg</primary>
+       </indexterm>
+       <function>
+         range_agg(<replaceable class="parameter">expression</replaceable>)
+       </function>
+      </entry>
+      <entry>
+       <type>anyrange</type>
+      </entry>
+      <entry>
+       <type>multirange</type> of the argument range type
+      </entry>
+      <entry>Yes</entry>
+      <entry>union of the non-null input values</entry>
+     </row>
+
+     <row>
+      <entry>
+       <indexterm>
+        <primary>range_intersect_agg</primary>
+       </indexterm>
+       <function>
+         range_intersect_agg(<replaceable class="parameter">expression</replaceable>)
+       </function>
+      </entry>
+      <entry>
+       <type>anyrange</type>
+      </entry>
+      <entry>
+       <type>multirange</type> of the argument range type
+      </entry>
+      <entry>No</entry>
+      <entry>intersection of the non-null input values</entry>
+     </row>
+
+     <row>
+      <entry>
+       <indexterm>
         <primary>string_agg</primary>
        </indexterm>
        <function>
diff --git a/doc/src/sgml/rangetypes.sgml b/doc/src/sgml/rangetypes.sgml
index b75fb3a392..c8784bdce3 100644
--- a/doc/src/sgml/rangetypes.sgml
+++ b/doc/src/sgml/rangetypes.sgml
@@ -27,6 +27,13 @@
   ranges from an instrument, and so forth can also be useful.
  </para>
 
+ <para>
+  Every range type has a corresponding multirange type. A multirange is
+  an ordered list of non-continguous, non-empty, non-null ranges. Most
+  range operators also work on multiranges, and they have a few functions
+  of their own.
+ </para>
+
  <sect2 id="rangetypes-builtin">
   <title>Built-in Range Types</title>
 
@@ -232,10 +239,30 @@ SELECT '[4,4]'::int4range;
 SELECT '[4,4)'::int4range;
 </programlisting>
   </para>
+
+  <para>
+   The input for a multirange is curly brackets (<literal>{</literal> and
+   <literal>}</literal>) containing zero or more valid ranges,
+   separated by commas. Whitespace is permitted around the brackets and
+   commas. This is intended to be reminiscent of array syntax, although
+   multiranges are much simpler: they have just one dimension and there is
+   no need to quote their contents. (The bounds of their ranges may be
+   quoted as above however.)
+  </para>
+
+  <para>
+  Examples:
+<programlisting>
+SELECT '{}'::int4multirange;
+SELECT '{[3,7)}'::int4multirange;
+SELECT '{[3,7), [8,9)}'::int4multirange;
+</programlisting>
+  </para>
+
  </sect2>
 
  <sect2 id="rangetypes-construct">
-  <title>Constructing Ranges</title>
+  <title>Constructing Ranges and Multiranges</title>
 
   <para>
    Each range type has a constructor function with the same name as the range
@@ -269,6 +296,19 @@ SELECT int8range(1, 14, '(]');
 SELECT numrange(NULL, 2.2);
 </programlisting>
   </para>
+
+  <para>
+   Each range type also has a multirange constructor with the same name as the
+   multirange type.  The constructor function takes zero or more arguments
+   which are all ranges of the appropriate type.
+   For example:
+
+<programlisting>
+SELECT nummultirange();
+SELECT nummultirange(numrange(1.0, 14.0));
+SELECT nummultirange(numrange(1.0, 14.0), numrange(20.0, 25.0));
+</programlisting>
+  </para>
  </sect2>
 
  <sect2 id="rangetypes-discrete">
@@ -342,6 +382,11 @@ SELECT '[1.234, 5.678]'::floatrange;
   </para>
 
   <para>
+   When you define your own range you automatically get a corresponding
+   multirange type.
+  </para>
+
+  <para>
    Defining your own range type also allows you to specify a different
    subtype B-tree operator class or collation to use, so as to change the sort
    ordering that determines which values fall into a given range.
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index 61db650755..61baffe789 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -25,6 +25,7 @@ OBJS = \
 	objectaddress.o \
 	partition.o \
 	pg_aggregate.o \
+	pg_cast.o \
 	pg_collation.o \
 	pg_constraint.o \
 	pg_conversion.o \
diff --git a/src/backend/catalog/pg_cast.c b/src/backend/catalog/pg_cast.c
new file mode 100644
index 0000000000..50b44e3a96
--- /dev/null
+++ b/src/backend/catalog/pg_cast.c
@@ -0,0 +1,116 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_cast.c
+ *	  routines to support manipulation of the pg_cast relation
+ *
+ * Portions Copyright (c) 1996-2019, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ *	  src/backend/catalog/pg_cast.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "catalog/pg_cast.h"
+#include "access/table.h"
+#include "catalog/catalog.h"
+#include "catalog/dependency.h"
+#include "catalog/indexing.h"
+#include "catalog/objectaccess.h"
+#include "catalog/pg_proc.h"
+#include "catalog/pg_type.h"
+#include "tcop/tcopprot.h"
+#include "utils/builtins.h"
+#include "utils/rel.h"
+#include "utils/syscache.h"
+
+/*
+ * ----------------------------------------------------------------
+ *		CastCreate
+ * ----------------------------------------------------------------
+ */
+ObjectAddress
+CastCreate(Oid sourcetypeid, Oid targettypeid, Oid funcid, char castcontext,
+		   char castmethod, DependencyType behavior)
+{
+	Relation		relation;
+	HeapTuple		tuple;
+	Oid				castid;
+	Datum			values[Natts_pg_cast];
+	bool			nulls[Natts_pg_cast];
+	ObjectAddress	myself,
+					referenced;
+
+	relation = table_open(CastRelationId, RowExclusiveLock);
+
+	/*
+	 * Check for duplicate.  This is just to give a friendly error message,
+	 * the unique index would catch it anyway (so no need to sweat about race
+	 * conditions).
+	 */
+	tuple = SearchSysCache2(CASTSOURCETARGET,
+							ObjectIdGetDatum(sourcetypeid),
+							ObjectIdGetDatum(targettypeid));
+	if (HeapTupleIsValid(tuple))
+		ereport(ERROR,
+				(errcode(ERRCODE_DUPLICATE_OBJECT),
+				 errmsg("cast from type %s to type %s already exists",
+						format_type_be(sourcetypeid),
+						format_type_be(targettypeid))));
+
+	/* ready to go */
+	castid = GetNewOidWithIndex(relation, CastOidIndexId, Anum_pg_cast_oid);
+	values[Anum_pg_cast_oid - 1] = ObjectIdGetDatum(castid);
+	values[Anum_pg_cast_castsource - 1] = ObjectIdGetDatum(sourcetypeid);
+	values[Anum_pg_cast_casttarget - 1] = ObjectIdGetDatum(targettypeid);
+	values[Anum_pg_cast_castfunc - 1] = ObjectIdGetDatum(funcid);
+	values[Anum_pg_cast_castcontext - 1] = CharGetDatum(castcontext);
+	values[Anum_pg_cast_castmethod - 1] = CharGetDatum(castmethod);
+
+	MemSet(nulls, false, sizeof(nulls));
+
+	tuple = heap_form_tuple(RelationGetDescr(relation), values, nulls);
+
+	CatalogTupleInsert(relation, tuple);
+
+	/* make dependency entries */
+	myself.classId = CastRelationId;
+	myself.objectId = castid;
+	myself.objectSubId = 0;
+
+	/* dependency on source type */
+	referenced.classId = TypeRelationId;
+	referenced.objectId = sourcetypeid;
+	referenced.objectSubId = 0;
+	recordDependencyOn(&myself, &referenced, behavior);
+
+	/* dependency on target type */
+	referenced.classId = TypeRelationId;
+	referenced.objectId = targettypeid;
+	referenced.objectSubId = 0;
+	recordDependencyOn(&myself, &referenced, behavior);
+
+	/* dependency on function */
+	if (OidIsValid(funcid))
+	{
+		referenced.classId = ProcedureRelationId;
+		referenced.objectId = funcid;
+		referenced.objectSubId = 0;
+		recordDependencyOn(&myself, &referenced, behavior);
+	}
+
+	/* dependency on extension */
+	recordDependencyOnCurrentExtension(&myself, false);
+
+	/* Post creation hook for new cast */
+	InvokeObjectPostCreateHook(CastRelationId, castid, 0);
+
+	heap_freetuple(tuple);
+
+	table_close(relation, RowExclusiveLock);
+
+	return myself;
+}
diff --git a/src/backend/catalog/pg_proc.c b/src/backend/catalog/pg_proc.c
index 5194dcaac0..6efcac52a0 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,13 @@ 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 b5bc36c2bd..1217a6ddd0 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_rngmultitypid - 1] = ObjectIdGetDatum(multirangeTypeOid);
 
 	tup = heap_form_tuple(RelationGetDescr(pg_range), values, nulls);
 
@@ -100,6 +102,12 @@ 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 8d7572da51..d74697ef9c 100644
--- a/src/backend/catalog/pg_type.c
+++ b/src/backend/catalog/pg_type.c
@@ -27,6 +27,7 @@
 #include "catalog/pg_proc.h"
 #include "catalog/pg_type.h"
 #include "commands/typecmds.h"
+#include "mb/pg_wchar.h"
 #include "miscadmin.h"
 #include "parser/scansup.h"
 #include "utils/acl.h"
@@ -36,6 +37,9 @@
 #include "utils/rel.h"
 #include "utils/syscache.h"
 
+static char *makeUniqueTypeName(const char *typeName, Oid typeNamespace,
+								bool tryOriginal);
+
 /* Potentially set by pg_upgrade_support functions */
 Oid			binary_upgrade_next_pg_type_oid = InvalidOid;
 
@@ -783,31 +787,10 @@ RenameTypeInternal(Oid typeOid, const char *newTypeName, Oid typeNamespace)
 char *
 makeArrayTypeName(const char *typeName, Oid typeNamespace)
 {
-	char	   *arr = (char *) palloc(NAMEDATALEN);
-	int			namelen = strlen(typeName);
-	int			i;
-
-	/*
-	 * 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;
-	}
+	char	   *arr;
 
-	if (i >= NAMEDATALEN - 1)
+	arr = makeUniqueTypeName(typeName, typeNamespace, false);
+	if (arr == NULL)
 		ereport(ERROR,
 				(errcode(ERRCODE_DUPLICATE_OBJECT),
 				 errmsg("could not form array type name for type \"%s\"",
@@ -878,3 +861,91 @@ moveArrayTypeName(Oid typeOid, const char *typeName, Oid typeNamespace)
 
 	return true;
 }
+
+
+/*
+ * makeMultirangeTypeName
+ *	  - given a range type name, make a multirange type name for it
+ *
+ * caller is responsible for pfreeing the result
+ */
+char *
+makeMultirangeTypeName(const char *rangeTypeName, Oid typeNamespace)
+{
+	char	   *buf;
+	char	   *mrname;
+	char	   *rangestr;
+
+	/*
+	 * 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...
+	 */
+	rangestr = strstr(rangeTypeName, "range");
+	if (rangestr)
+	{
+		char	*prefix = pnstrdup(rangeTypeName, rangestr - rangeTypeName);
+
+		buf = psprintf("%s%s%s", prefix, "multi", rangestr);
+	}
+	else
+		buf = psprintf("%s_multirange", pnstrdup(rangeTypeName, NAMEDATALEN - 12));
+
+	/* clip it at NAMEDATALEN-1 bytes */
+	buf[pg_mbcliplen(buf, strlen(buf), NAMEDATALEN - 1)] = '\0';
+
+	mrname = makeUniqueTypeName(buf, typeNamespace, true);
+	if (mrname == NULL)
+		ereport(ERROR,
+				(errcode(ERRCODE_DUPLICATE_OBJECT),
+				 errmsg("could not form multirange type name for type \"%s\"",
+						rangeTypeName)));
+
+	return mrname;
+}
+
+/*
+ * makeUniqueTypeName
+ *		Generate a unique name for a prospective new type
+ *
+ * Given a typeName, return a new palloc'ed name by preprending underscores
+ * until a non-conflicting name results.
+ *
+ * If tryOriginal, first try with zero underscores.
+ */
+static char *
+makeUniqueTypeName(const char *typeName, Oid typeNamespace, bool tryOriginal)
+{
+	int			i;
+	int			namelen;
+	char		dest[NAMEDATALEN];
+
+	Assert(strlen(typeName) <= NAMEDATALEN - 1);
+
+	if (tryOriginal &&
+		!SearchSysCacheExists2(TYPENAMENSP,
+							   CStringGetDatum(typeName),
+							   ObjectIdGetDatum(typeNamespace)))
+		return pstrdup(typeName);
+
+	/*
+	 * The idea is to prepend underscores as needed until we make a name that
+	 * doesn't collide with anything ...
+	 */
+	namelen = strlen(typeName);
+	for (i = 1; i < NAMEDATALEN - 1; i++)
+	{
+		dest[i - 1] = '_';
+		strlcpy(dest + i, typeName, NAMEDATALEN - i);
+		if (namelen + i >= NAMEDATALEN)
+			truncate_identifier(dest, NAMEDATALEN, false);
+
+		if (!SearchSysCacheExists2(TYPENAMENSP,
+								   CStringGetDatum(dest),
+								   ObjectIdGetDatum(typeNamespace)))
+			return pstrdup(dest);
+	}
+
+	return NULL;
+}
diff --git a/src/backend/commands/functioncmds.c b/src/backend/commands/functioncmds.c
index c31c57e5e9..732487ff8d 100644
--- a/src/backend/commands/functioncmds.c
+++ b/src/backend/commands/functioncmds.c
@@ -1497,17 +1497,12 @@ CreateCast(CreateCastStmt *stmt)
 	char		sourcetyptype;
 	char		targettyptype;
 	Oid			funcid;
-	Oid			castid;
 	int			nargs;
 	char		castcontext;
 	char		castmethod;
-	Relation	relation;
 	HeapTuple	tuple;
-	Datum		values[Natts_pg_cast];
-	bool		nulls[Natts_pg_cast];
-	ObjectAddress myself,
-				referenced;
 	AclResult	aclresult;
+	ObjectAddress	myself;
 
 	sourcetypeid = typenameTypeId(NULL, stmt->sourcetype);
 	targettypeid = typenameTypeId(NULL, stmt->targettype);
@@ -1731,74 +1726,8 @@ CreateCast(CreateCastStmt *stmt)
 			break;
 	}
 
-	relation = table_open(CastRelationId, RowExclusiveLock);
-
-	/*
-	 * Check for duplicate.  This is just to give a friendly error message,
-	 * the unique index would catch it anyway (so no need to sweat about race
-	 * conditions).
-	 */
-	tuple = SearchSysCache2(CASTSOURCETARGET,
-							ObjectIdGetDatum(sourcetypeid),
-							ObjectIdGetDatum(targettypeid));
-	if (HeapTupleIsValid(tuple))
-		ereport(ERROR,
-				(errcode(ERRCODE_DUPLICATE_OBJECT),
-				 errmsg("cast from type %s to type %s already exists",
-						format_type_be(sourcetypeid),
-						format_type_be(targettypeid))));
-
-	/* ready to go */
-	castid = GetNewOidWithIndex(relation, CastOidIndexId, Anum_pg_cast_oid);
-	values[Anum_pg_cast_oid - 1] = ObjectIdGetDatum(castid);
-	values[Anum_pg_cast_castsource - 1] = ObjectIdGetDatum(sourcetypeid);
-	values[Anum_pg_cast_casttarget - 1] = ObjectIdGetDatum(targettypeid);
-	values[Anum_pg_cast_castfunc - 1] = ObjectIdGetDatum(funcid);
-	values[Anum_pg_cast_castcontext - 1] = CharGetDatum(castcontext);
-	values[Anum_pg_cast_castmethod - 1] = CharGetDatum(castmethod);
-
-	MemSet(nulls, false, sizeof(nulls));
-
-	tuple = heap_form_tuple(RelationGetDescr(relation), values, nulls);
-
-	CatalogTupleInsert(relation, tuple);
-
-	/* make dependency entries */
-	myself.classId = CastRelationId;
-	myself.objectId = castid;
-	myself.objectSubId = 0;
-
-	/* dependency on source type */
-	referenced.classId = TypeRelationId;
-	referenced.objectId = sourcetypeid;
-	referenced.objectSubId = 0;
-	recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
-
-	/* dependency on target type */
-	referenced.classId = TypeRelationId;
-	referenced.objectId = targettypeid;
-	referenced.objectSubId = 0;
-	recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
-
-	/* dependency on function */
-	if (OidIsValid(funcid))
-	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = funcid;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
-	}
-
-	/* dependency on extension */
-	recordDependencyOnCurrentExtension(&myself, false);
-
-	/* Post creation hook for new cast */
-	InvokeObjectPostCreateHook(CastRelationId, castid, 0);
-
-	heap_freetuple(tuple);
-
-	table_close(relation, RowExclusiveLock);
-
+	myself = CastCreate(sourcetypeid, targettypeid, funcid, castcontext,
+						castmethod, DEPENDENCY_NORMAL);
 	return myself;
 }
 
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index 52097363fd..a8dea8d58e 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -42,6 +42,7 @@
 #include "catalog/objectaccess.h"
 #include "catalog/pg_am.h"
 #include "catalog/pg_authid.h"
+#include "catalog/pg_cast.h"
 #include "catalog/pg_collation.h"
 #include "catalog/pg_constraint.h"
 #include "catalog/pg_depend.h"
@@ -85,9 +86,14 @@ typedef struct
 
 /* Potentially set by pg_upgrade_support functions */
 Oid			binary_upgrade_next_array_pg_type_oid = InvalidOid;
+Oid			binary_upgrade_next_mrng_pg_type_oid = InvalidOid;
+Oid			binary_upgrade_next_mrng_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 rangeOid,
+									   Oid rangeArrayOid, Oid *castFuncOid);
 static Oid	findTypeInputFunction(List *procname, Oid typeOid);
 static Oid	findTypeOutputFunction(List *procname, Oid typeOid);
 static Oid	findTypeReceiveFunction(List *procname, Oid typeOid);
@@ -811,7 +817,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",
@@ -1346,6 +1353,11 @@ checkEnumOwner(HeapTuple tup)
 /*
  * DefineRange
  *		Registers a new range type.
+ *
+ * Perhaps it might be worthwhile to set pg_type.typelem to the base type,
+ * and likewise on multiranges to set it to the range type. But having a
+ * non-zero typelem is treated elsewhere as a synonym for being an array,
+ * and users might have queries with that same assumption.
  */
 ObjectAddress
 DefineRange(CreateRangeStmt *stmt)
@@ -1354,7 +1366,11 @@ DefineRange(CreateRangeStmt *stmt)
 	Oid			typeNamespace;
 	Oid			typoid;
 	char	   *rangeArrayName;
+	char	   *multirangeTypeName;
+	char	   *multirangeArrayName;
 	Oid			rangeArrayOid;
+	Oid			multirangeOid;
+	Oid			multirangeArrayOid;
 	Oid			rangeSubtype = InvalidOid;
 	List	   *rangeSubOpclassName = NIL;
 	List	   *rangeCollationName = NIL;
@@ -1371,6 +1387,8 @@ DefineRange(CreateRangeStmt *stmt)
 	AclResult	aclresult;
 	ListCell   *lc;
 	ObjectAddress address;
+	ObjectAddress mltrngaddress;
+	Oid			castFuncOid;
 
 	/* Convert list of names to a name and namespace */
 	typeNamespace = QualifiedNameGetCreationNamespace(stmt->typeName,
@@ -1519,8 +1537,10 @@ DefineRange(CreateRangeStmt *stmt)
 	/* alignment must be 'i' or 'd' for ranges */
 	alignment = (subtypalign == 'd') ? 'd' : 'i';
 
-	/* Allocate OID for array type */
+	/* Allocate OID for array type, its multirange, and its multirange array */
 	rangeArrayOid = AssignTypeArrayOid();
+	multirangeOid = AssignTypeMultirangeOid();
+	multirangeArrayOid = AssignTypeMultirangeArrayOid();
 
 	/* Create the pg_type entry */
 	address =
@@ -1557,9 +1577,46 @@ 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(multirangeOid,	/* force assignment of this 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) */
+	Assert(multirangeOid == mltrngaddress.objectId);
+
 	/* Create the entry in pg_range */
 	RangeCreate(typoid, rangeSubtype, rangeCollation, rangeSubOpclass,
-				rangeCanonical, rangeSubtypeDiff);
+				rangeCanonical, rangeSubtypeDiff, multirangeOid);
 
 	/*
 	 * Create the array type that goes with it.
@@ -1600,8 +1657,53 @@ 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 */
+			   multirangeOid,	/* 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,
+							   multirangeOid, typoid, rangeArrayOid,
+							   &castFuncOid);
+
+	/* Create cast from the range type to its multirange type */
+	CastCreate(typoid, multirangeOid, castFuncOid, 'e', 'f', DEPENDENCY_INTERNAL);
+
+	pfree(multirangeTypeName);
+	pfree(multirangeArrayName);
 
 	return address;
 }
@@ -1679,6 +1781,147 @@ 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.
+ *
+ * Sets castFuncOid to the oid of the new constructor that can be used
+ * to cast from a range to a multirange.
+ */
+static void
+makeMultirangeConstructors(const char *name, Oid namespace,
+						   Oid multirangeOid, Oid rangeOid, Oid rangeArrayOid,
+						   Oid *castFuncOid)
+{
+	ObjectAddress myself,
+				referenced;
+	oidvector  *argtypes;
+	Datum		allParamTypes;
+	ArrayType  *allParameterTypes;
+	Datum		paramModes;
+	ArrayType  *parameterModes;
+
+	referenced.classId = TypeRelationId;
+	referenced.objectId = multirangeOid;
+	referenced.objectSubId = 0;
+
+	/* 0-arg constructor - for empty multiranges */
+	argtypes = buildoidvector(NULL, 0);
+	myself = ProcedureCreate(name,	/* name: same as multirange type */
+							 namespace,
+							 false,	/* replace */
+							 false,	/* returns set */
+							 multirangeOid,	/* return type */
+							 BOOTSTRAP_SUPERUSERID,	/* proowner */
+							 INTERNALlanguageId,	/* language */
+							 F_FMGR_INTERNAL_VALIDATOR,
+							 "multirange_constructor0",	/* prosrc */
+							 NULL,	/* probin */
+							 PROKIND_FUNCTION,
+							 false,	/* security_definer */
+							 false,	/* leakproof */
+							 false, /* isStrict */
+							 PROVOLATILE_IMMUTABLE,	/* volatility */
+							 PROPARALLEL_SAFE,	/* parallel safety */
+							 argtypes,	/* parameterTypes */
+							 PointerGetDatum(NULL),	/* allParameterTypes */
+							 PointerGetDatum(NULL),	/* parameterModes */
+							 PointerGetDatum(NULL),	/* parameterNames */
+							 NIL,	/* parameterDefaults */
+							 PointerGetDatum(NULL),	/* trftypes */
+							 PointerGetDatum(NULL),	/* proconfig */
+							 InvalidOid,	/* prosupport */
+							 1.0,	/* procost */
+							 0.0);	/* prorows */
+	/*
+	 * Make the constructor 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);
+	pfree(argtypes);
+
+	/*
+	 * 1-arg constructor - for casts
+	 *
+	 * In theory we shouldn't need both this and the vararg (n-arg) constructor,
+	 * but having a separate 1-arg function lets us define casts against it.
+	 */
+	argtypes = buildoidvector(&rangeOid, 1);
+	myself = ProcedureCreate(name,	/* name: same as multirange type */
+							 namespace,
+							 false,	/* replace */
+							 false,	/* returns set */
+							 multirangeOid,	/* return type */
+							 BOOTSTRAP_SUPERUSERID,	/* proowner */
+							 INTERNALlanguageId,	/* language */
+							 F_FMGR_INTERNAL_VALIDATOR,
+							 "multirange_constructor1",	/* prosrc */
+							 NULL,	/* probin */
+							 PROKIND_FUNCTION,
+							 false,	/* security_definer */
+							 false,	/* leakproof */
+							 true, /* isStrict */
+							 PROVOLATILE_IMMUTABLE,	/* volatility */
+							 PROPARALLEL_SAFE,	/* parallel safety */
+							 argtypes,	/* parameterTypes */
+							 PointerGetDatum(NULL),	/* allParameterTypes */
+							 PointerGetDatum(NULL),	/* parameterModes */
+							 PointerGetDatum(NULL),	/* parameterNames */
+							 NIL,	/* parameterDefaults */
+							 PointerGetDatum(NULL),	/* trftypes */
+							 PointerGetDatum(NULL),	/* proconfig */
+							 InvalidOid,	/* prosupport */
+							 1.0,	/* procost */
+							 0.0);	/* prorows */
+	/* ditto */
+	recordDependencyOn(&myself, &referenced, DEPENDENCY_INTERNAL);
+	pfree(argtypes);
+	*castFuncOid = myself.objectId;
+
+	/* n-arg constructor - vararg */
+	argtypes = buildoidvector(&rangeArrayOid, 1);
+	allParamTypes = ObjectIdGetDatum(rangeArrayOid);
+	allParameterTypes = construct_array(&allParamTypes,
+										1, OIDOID,
+										sizeof(Oid), true, 'i');
+	paramModes = CharGetDatum(FUNC_PARAM_VARIADIC);
+	parameterModes = construct_array(&paramModes, 1, CHAROID,
+									 1, true, 'c');
+	myself = ProcedureCreate(name,	/* name: same as multirange type */
+							 namespace,
+							 false,	/* replace */
+							 false,	/* returns set */
+							 multirangeOid,	/* return type */
+							 BOOTSTRAP_SUPERUSERID,	/* proowner */
+							 INTERNALlanguageId,	/* language */
+							 F_FMGR_INTERNAL_VALIDATOR,
+							 "multirange_constructor2",	/* prosrc */
+							 NULL,	/* probin */
+							 PROKIND_FUNCTION,
+							 false,	/* security_definer */
+							 false,	/* leakproof */
+							 false, /* isStrict */
+							 PROVOLATILE_IMMUTABLE,	/* volatility */
+							 PROPARALLEL_SAFE,	/* parallel safety */
+							 argtypes,	/* parameterTypes */
+							 PointerGetDatum(allParameterTypes),	/* allParameterTypes */
+							 PointerGetDatum(parameterModes),	/* parameterModes */
+							 PointerGetDatum(NULL),	/* parameterNames */
+							 NIL,	/* parameterDefaults */
+							 PointerGetDatum(NULL),	/* trftypes */
+							 PointerGetDatum(NULL),	/* proconfig */
+							 InvalidOid,	/* prosupport */
+							 1.0,	/* procost */
+							 0.0);	/* prorows */
+	/* ditto */
+	recordDependencyOn(&myself, &referenced, DEPENDENCY_INTERNAL);
+	pfree(argtypes);
+	pfree(allParameterTypes);
+	pfree(parameterModes);
+}
 
 /*
  * Find suitable I/O functions for a type.
@@ -2104,6 +2347,72 @@ AssignTypeArrayOid(void)
 	return type_array_oid;
 }
 
+/*
+ *	AssignTypeMultirangeOid
+ *
+ *	Pre-assign the range type's multirange OID for use in pg_type.oid
+ */
+Oid
+AssignTypeMultirangeOid(void)
+{
+	Oid			type_multirange_oid;
+
+	/* Use binary-upgrade override for pg_type.oid? */
+	if (IsBinaryUpgrade)
+	{
+		if (!OidIsValid(binary_upgrade_next_mrng_pg_type_oid))
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+					 errmsg("pg_type multirange OID value not set when in binary upgrade mode")));
+
+		type_multirange_oid = binary_upgrade_next_mrng_pg_type_oid;
+		binary_upgrade_next_mrng_pg_type_oid = InvalidOid;
+	}
+	else
+	{
+		Relation	pg_type = table_open(TypeRelationId, AccessShareLock);
+
+		type_multirange_oid = GetNewOidWithIndex(pg_type, TypeOidIndexId,
+												 Anum_pg_type_oid);
+		table_close(pg_type, AccessShareLock);
+	}
+
+	return type_multirange_oid;
+}
+
+/*
+ *	AssignTypeMultirangeArrayOid
+ *
+ *	Pre-assign the range type's multirange array OID for use in pg_type.typarray
+ */
+Oid
+AssignTypeMultirangeArrayOid(void)
+{
+	Oid			type_multirange_array_oid;
+
+	/* Use binary-upgrade override for pg_type.oid? */
+	if (IsBinaryUpgrade)
+	{
+		if (!OidIsValid(binary_upgrade_next_mrng_array_pg_type_oid))
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+					 errmsg("pg_type multirange array OID value not set when in binary upgrade mode")));
+
+		type_multirange_array_oid = binary_upgrade_next_mrng_array_pg_type_oid;
+		binary_upgrade_next_mrng_array_pg_type_oid = InvalidOid;
+	}
+	else
+	{
+		Relation	pg_type = table_open(TypeRelationId, AccessShareLock);
+
+		type_multirange_array_oid = GetNewOidWithIndex(pg_type, TypeOidIndexId,
+													   Anum_pg_type_oid);
+		table_close(pg_type, AccessShareLock);
+	}
+
+	return type_multirange_array_oid;
+}
+
 
 /*-------------------------------------------------------------------
  * DefineCompositeType
diff --git a/src/backend/parser/parse_coerce.c b/src/backend/parser/parse_coerce.c
index 929f758ef4..47d17f43a4 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
@@ -1481,6 +1483,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;
@@ -1527,6 +1531,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 */
@@ -1577,6 +1590,39 @@ check_generic_type_consistency(const Oid *actual_arg_types,
 			/* otherwise, they better match */
 			return false;
 		}
+		else
+			range_typelem = InvalidOid;	/* keep compiler quiet */
+	}
+
+	/* 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)
@@ -1680,13 +1726,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.
@@ -1744,7 +1794,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;
@@ -1762,6 +1812,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;
+		}
 	}
 
 	/*
@@ -1812,9 +1882,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
@@ -1846,6 +1919,71 @@ enforce_generic_type_consistency(const Oid *actual_arg_types,
 							   format_type_be(range_typeid),
 							   format_type_be(elem_typeid))));
 		}
+		else
+			range_typelem = InvalidOid;
+	}
+
+	/* 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))
@@ -1855,6 +1993,7 @@ enforce_generic_type_consistency(const Oid *actual_arg_types,
 			elem_typeid = ANYELEMENTOID;
 			array_typeid = ANYARRAYOID;
 			range_typeid = ANYRANGEOID;
+			multirange_typeid = ANYMULTIRANGEOID;
 		}
 		else
 		{
@@ -1927,6 +2066,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;
+			}
 		}
 	}
 
@@ -1958,6 +2108,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 ||
@@ -2006,8 +2172,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);
@@ -2019,11 +2184,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)
 		{
@@ -2051,6 +2242,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)
@@ -2059,6 +2263,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 */
@@ -2173,6 +2445,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..f1342181a0
--- /dev/null
+++ b/src/backend/utils/adt/multirangetypes.c
@@ -0,0 +1,2171 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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;
+	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 = NULL;
+	int32		range_str_len;
+	char	   *range_str_copy;
+
+	cache = get_multirange_io_data(fcinfo, mltrngtypoid, IOFunc_input);
+	rangetyp = cache->typcache->rngtype;
+
+	/* 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]);
+		uint32		range_len;
+		char	   *range_data;
+
+		range = PointerGetDatum(SendFunctionCall(&cache->proc, range));
+		range_len = VARSIZE(range) - VARHDRSZ;
+		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_constructor2(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));
+}
+
+/*
+ * Construct multirange value from a single range.
+ * It'd be nice if we could just use multirange_constructor2
+ * for this case, but we need a non-variadic single-arg function
+ * to let us define a CAST from a range to its multirange.
+ */
+Datum
+multirange_constructor1(PG_FUNCTION_ARGS)
+{
+	Oid			mltrngtypid = get_fn_expr_rettype(fcinfo->flinfo);
+	Oid			rngtypid;
+	TypeCacheEntry *typcache;
+	TypeCacheEntry *rangetyp;
+	RangeType *range;
+
+	typcache = multirange_get_typcache(fcinfo, mltrngtypid);
+	rangetyp = typcache->rngtype;
+
+	/*
+	 * 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")));
+
+	range = PG_GETARG_RANGE_P(0);
+
+	/* Make sure the range type matches. */
+	rngtypid = RangeTypeGetOid(range);
+	if (rngtypid != rangetyp->type_id)
+		ereport(ERROR, (errmsg("type %u does not match constructor type", rngtypid)));
+
+	PG_RETURN_MULTIRANGE_P(make_multirange(mltrngtypid, rangetyp, 1, &range));
+}
+
+/*
+ * 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 -> multirange type functions */
+
+/* multirange union */
+Datum
+multirange_union(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
+	MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count1;
+	int32		range_count2;
+	int32		range_count3;
+	RangeType **ranges1;
+	RangeType **ranges2;
+	RangeType **ranges3;
+
+	if (MultirangeIsEmpty(mr1))
+		PG_RETURN_MULTIRANGE_P(mr2);
+	if (MultirangeIsEmpty(mr2))
+		PG_RETURN_MULTIRANGE_P(mr1);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr1));
+
+	multirange_deserialize(mr1, &range_count1, &ranges1);
+	multirange_deserialize(mr2, &range_count2, &ranges2);
+
+	range_count3 = range_count1 + range_count2;
+	ranges3 = palloc0(range_count3 * sizeof(RangeType *));
+	memcpy(ranges3, ranges1, range_count1 * sizeof(RangeType *));
+	memcpy(ranges3 + range_count1, ranges2, range_count2 * sizeof(RangeType *));
+	PG_RETURN_MULTIRANGE_P(make_multirange(typcache->type_id, typcache->rngtype,
+										   range_count3, ranges3));
+}
+
+/* multirange minus */
+Datum
+multirange_minus(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
+	MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
+	Oid			mltrngtypoid = MultirangeTypeGetOid(mr1);
+	TypeCacheEntry *typcache;
+	TypeCacheEntry *rangetyp;
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+
+	typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
+	rangetyp = typcache->rngtype;
+
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		PG_RETURN_MULTIRANGE_P(mr1);
+
+	multirange_deserialize(mr1, &range_count1, &ranges1);
+	multirange_deserialize(mr2, &range_count2, &ranges2);
+
+	PG_RETURN_MULTIRANGE_P(multirange_minus_internal(mltrngtypoid,
+													 rangetyp,
+													 range_count1,
+													 ranges1,
+													 range_count2,
+													 ranges2));
+}
+
+MultirangeType *
+multirange_minus_internal(Oid mltrngtypoid, TypeCacheEntry *rangetyp,
+						 int32 range_count1, RangeType **ranges1,
+						 int32 range_count2, RangeType **ranges2)
+{
+	RangeType  *r1;
+	RangeType  *r2;
+	RangeType **ranges3;
+	int32		range_count3;
+	int32		i1;
+	int32		i2;
+
+	/*
+	 * Worst case: every range in ranges1 makes a different cut to some range
+	 * in ranges2.
+	 */
+	ranges3 = palloc0((range_count1 + range_count2) * sizeof(RangeType *));
+	range_count3 = 0;
+
+	/*
+	 * For each range in mr1, keep subtracting until it's gone or the ranges
+	 * in mr2 have passed it. After a subtraction we assign what's left back
+	 * to r1. The parallel progress through mr1 and mr2 is similar to
+	 * multirange_overlaps_multirange_internal.
+	 */
+	r2 = ranges2[0];
+	for (i1 = 0, i2 = 0; i1 < range_count1; i1++)
+	{
+		r1 = ranges1[i1];
+
+		/* Discard r2s while r2 << r1 */
+		while (r2 != NULL && range_before_internal(rangetyp, r2, r1))
+		{
+			r2 = ++i2 >= range_count2 ? NULL : ranges2[i2];
+		}
+
+		while (r2 != NULL)
+		{
+			if (range_split_internal(rangetyp, r1, r2, &ranges3[range_count3], &r1))
+			{
+				/*
+				 * If r2 takes a bite out of the middle of r1, we need two
+				 * outputs
+				 */
+				range_count3++;
+				r2 = ++i2 >= range_count2 ? NULL : ranges2[i2];
+
+			}
+			else if (range_overlaps_internal(rangetyp, r1, r2))
+			{
+				/*
+				 * If r2 overlaps r1, replace r1 with r1 - r2.
+				 */
+				r1 = range_minus_internal(rangetyp, r1, r2);
+
+				/*
+				 * If r2 goes past r1, then we need to stay with it, in case
+				 * it hits future r1s. Otherwise we need to keep r1, in case
+				 * future r2s hit it. Since we already subtracted, there's no
+				 * point in using the overright/overleft calls.
+				 */
+				if (RangeIsEmpty(r1) || range_before_internal(rangetyp, r1, r2))
+					break;
+				else
+					r2 = ++i2 >= range_count2 ? NULL : ranges2[i2];
+
+			}
+			else
+			{
+				/*
+				 * This and all future r2s are past r1, so keep them. Also
+				 * assign whatever is left of r1 to the result.
+				 */
+				break;
+			}
+		}
+
+		/*
+		 * Nothing else can remove anything from r1, so keep it. Even if r1 is
+		 * empty here, make_multirange will remove it.
+		 */
+		ranges3[range_count3++] = r1;
+	}
+
+	return make_multirange(mltrngtypoid, rangetyp, range_count3, ranges3);
+}
+
+/* multirange intersection */
+Datum
+multirange_intersect(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
+	MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
+	Oid			mltrngtypoid = MultirangeTypeGetOid(mr1);
+	TypeCacheEntry *typcache;
+	TypeCacheEntry *rangetyp;
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+
+	typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
+	rangetyp = typcache->rngtype;
+
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		PG_RETURN_MULTIRANGE_P(make_empty_multirange(mltrngtypoid, rangetyp));
+
+	multirange_deserialize(mr1, &range_count1, &ranges1);
+	multirange_deserialize(mr2, &range_count2, &ranges2);
+
+	PG_RETURN_MULTIRANGE_P(multirange_intersect_internal(mltrngtypoid,
+														 rangetyp,
+														 range_count1,
+														 ranges1,
+														 range_count2,
+														 ranges2));
+}
+
+MultirangeType *
+multirange_intersect_internal(Oid mltrngtypoid, TypeCacheEntry *rangetyp,
+							  int32 range_count1, RangeType **ranges1,
+							  int32 range_count2, RangeType **ranges2)
+{
+	RangeType  *r1;
+	RangeType  *r2;
+	RangeType **ranges3;
+	int32		range_count3;
+	int32		i1;
+	int32		i2;
+
+	if (range_count1 == 0 || range_count2 == 0)
+		return make_multirange(mltrngtypoid, rangetyp, 0, NULL);
+
+	/*-----------------------------------------------
+	 * Worst case is a stitching pattern like this:
+	 *
+	 * mr1: --- --- --- ---
+	 * mr2:   --- --- ---
+	 * mr3:   - - - - - -
+	 *
+	 * That seems to be range_count1 + range_count2 - 1,
+	 * but one extra won't hurt.
+	 *-----------------------------------------------
+	 */
+	ranges3 = palloc0((range_count1 + range_count2) * sizeof(RangeType *));
+	range_count3 = 0;
+
+	/*
+	 * For each range in mr1, keep intersecting until the ranges in mr2 have
+	 * passed it. The parallel progress through mr1 and mr2 is similar to
+	 * multirange_minus_multirange_internal, but we don't have to assign back
+	 * to r1.
+	 */
+	r2 = ranges2[0];
+	for (i1 = 0, i2 = 0; i1 < range_count1; i1++)
+	{
+		r1 = ranges1[i1];
+
+		/* Discard r2s while r2 << r1 */
+		while (r2 != NULL && range_before_internal(rangetyp, r2, r1))
+		{
+			r2 = ++i2 >= range_count2 ? NULL : ranges2[i2];
+		}
+
+		while (r2 != NULL)
+		{
+			if (range_overlaps_internal(rangetyp, r1, r2))
+			{
+				/* Keep the overlapping part */
+				ranges3[range_count3++] = range_intersect_internal(rangetyp, r1, r2);
+
+				/* If we "used up" all of r2, go to the next one... */
+				if (range_overleft_internal(rangetyp, r2, r1))
+					r2 = ++i2 >= range_count2 ? NULL : ranges2[i2];
+
+				/* ...otherwise go to the next r1 */
+				else
+					break;
+			}
+			else
+				/* We're past r1, so move to the next one */
+				break;
+		}
+
+		/* If we're out of r2s, there can be no more intersections */
+		if (r2 == NULL)
+			break;
+	}
+
+	return make_multirange(mltrngtypoid, rangetyp, range_count3, ranges3);
+}
+
+/*
+ * range_agg_transfn: combine adjacent/overlapping ranges.
+ *
+ * All we do here is gather the input ranges into an array
+ * so that the finalfn can sort and combine them.
+ */
+Datum
+range_agg_transfn(PG_FUNCTION_ARGS)
+{
+	MemoryContext aggContext;
+	Oid			rngtypoid;
+	ArrayBuildState *state;
+
+	if (!AggCheckCallContext(fcinfo, &aggContext))
+		elog(ERROR, "range_agg_transfn called in non-aggregate context");
+
+	rngtypoid = get_fn_expr_argtype(fcinfo->flinfo, 1);
+	if (!type_is_range(rngtypoid))
+		ereport(ERROR, (errmsg("range_agg must be called with a range")));
+
+	if (PG_ARGISNULL(0))
+		state = initArrayResult(rngtypoid, aggContext, false);
+	else
+		state = (ArrayBuildState *) PG_GETARG_POINTER(0);
+
+	/* skip NULLs */
+	if (!PG_ARGISNULL(1))
+		accumArrayResult(state, PG_GETARG_DATUM(1), false, rngtypoid, aggContext);
+
+	PG_RETURN_POINTER(state);
+}
+
+/*
+ * range_agg_finalfn: use our internal array to merge touching ranges.
+ */
+Datum
+range_agg_finalfn(PG_FUNCTION_ARGS)
+{
+	MemoryContext aggContext;
+	Oid			mltrngtypoid;
+	TypeCacheEntry *typcache;
+	ArrayBuildState *state;
+	int32		range_count;
+	RangeType **ranges;
+	int			i;
+
+	if (!AggCheckCallContext(fcinfo, &aggContext))
+		elog(ERROR, "range_agg_finalfn called in non-aggregate context");
+
+	state = PG_ARGISNULL(0) ? NULL : (ArrayBuildState *) PG_GETARG_POINTER(0);
+	if (state == NULL)
+		/* This shouldn't be possible, but just in case.... */
+		PG_RETURN_NULL();
+
+	/* Also return NULL if we had zero inputs, like other aggregates */
+	range_count = state->nelems;
+	if (range_count == 0)
+		PG_RETURN_NULL();
+
+	mltrngtypoid = get_fn_expr_rettype(fcinfo->flinfo);
+	typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
+
+	ranges = palloc0(range_count * sizeof(RangeType *));
+	for (i = 0; i < range_count; i++)
+		ranges[i] = DatumGetRangeTypeP(state->dvalues[i]);
+
+	PG_RETURN_MULTIRANGE_P(make_multirange(mltrngtypoid, typcache->rngtype, range_count, ranges));
+}
+
+Datum
+multirange_intersect_agg_transfn(PG_FUNCTION_ARGS)
+{
+	MemoryContext aggContext;
+	Oid			mltrngtypoid;
+	TypeCacheEntry *typcache;
+	MultirangeType *result;
+	MultirangeType *current;
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+
+	if (!AggCheckCallContext(fcinfo, &aggContext))
+		elog(ERROR, "multirange_intersect_agg_transfn called in non-aggregate context");
+
+	mltrngtypoid = get_fn_expr_argtype(fcinfo->flinfo, 1);
+	if (!type_is_multirange(mltrngtypoid))
+		ereport(ERROR, (errmsg("range_intersect_agg must be called with a multirange")));
+
+	typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
+
+	/* strictness ensures these are non-null */
+	result = PG_GETARG_MULTIRANGE_P(0);
+	current = PG_GETARG_MULTIRANGE_P(1);
+
+	multirange_deserialize(result, &range_count1, &ranges1);
+	multirange_deserialize(current, &range_count2, &ranges2);
+
+	result = multirange_intersect_internal(mltrngtypoid,
+										   typcache->rngtype,
+										   range_count1,
+										   ranges1,
+										   range_count2,
+										   ranges2);
+	PG_RETURN_RANGE_P(result);
+}
+
+
+/* multirange -> element type functions */
+
+/* extract lower bound value */
+Datum
+multirange_lower(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+	bool		isnull;
+	Datum		result;
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_NULL();
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	result = range_lower_internal(typcache->rngtype, ranges[0], &isnull);
+
+	if (isnull)
+		PG_RETURN_NULL();
+	else
+		PG_RETURN_DATUM(result);
+}
+
+/* extract upper bound value */
+Datum
+multirange_upper(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+	bool		isnull;
+	Datum		result;
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_NULL();
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	result = range_upper_internal(typcache->rngtype, ranges[range_count - 1], &isnull);
+
+	if (isnull)
+		PG_RETURN_NULL();
+	else
+		PG_RETURN_DATUM(result);
+}
+
+
+/* multirange -> bool functions */
+
+/* is multirange empty? */
+Datum
+multirange_empty(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+
+	PG_RETURN_BOOL(mr->rangeCount == 0);
+}
+
+/* is lower bound inclusive? */
+Datum
+multirange_lower_inc(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	int32		range_count;
+	RangeType **ranges;
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_has_flag(ranges[0], RANGE_LB_INC));
+}
+
+/* is upper bound inclusive? */
+Datum
+multirange_upper_inc(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	int32		range_count;
+	RangeType **ranges;
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_has_flag(ranges[range_count - 1], RANGE_UB_INC));
+}
+
+/* is lower bound infinite? */
+Datum
+multirange_lower_inf(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	int32		range_count;
+	RangeType **ranges;
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_has_flag(ranges[0], RANGE_LB_INF));
+}
+
+/* is upper bound infinite? */
+Datum
+multirange_upper_inf(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	int32		range_count;
+	RangeType **ranges;
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_has_flag(ranges[range_count - 1], RANGE_UB_INF));
+}
+
+
+
+/* multirange, element -> bool functions */
+
+/* contains? */
+Datum
+multirange_contains_elem(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	Datum		val = PG_GETARG_DATUM(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(multirange_contains_elem_internal(typcache, mr, val));
+}
+
+/* contained by? */
+Datum
+elem_contained_by_multirange(PG_FUNCTION_ARGS)
+{
+	Datum		val = PG_GETARG_DATUM(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(multirange_contains_elem_internal(typcache, mr, val));
+}
+
+/*
+ * Test whether multirange mr contains a specific element value.
+ */
+bool
+multirange_contains_elem_internal(TypeCacheEntry *typcache, MultirangeType * mr, Datum val)
+{
+	TypeCacheEntry *rangetyp;
+	int32		range_count;
+	RangeType **ranges;
+	RangeType  *r;
+	int			i;
+
+	rangetyp = typcache->rngtype;
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	for (i = 0; i < range_count; i++)
+	{
+		r = ranges[i];
+		if (range_contains_elem_internal(rangetyp, r, val))
+			return true;
+	}
+
+	return false;
+}
+
+/* multirange, range -> bool functions */
+
+/* contains? */
+Datum
+multirange_contains_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(multirange_contains_range_internal(typcache, mr, r));
+}
+
+/* contained by? */
+Datum
+range_contained_by_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(multirange_contains_range_internal(typcache, mr, r));
+}
+
+/*
+ * Test whether multirange mr contains a specific range r.
+ */
+bool
+multirange_contains_range_internal(TypeCacheEntry *typcache, MultirangeType * mr, RangeType *r)
+{
+	TypeCacheEntry *rangetyp;
+	int32		range_count;
+	RangeType **ranges;
+	RangeType  *mrr;
+	int			i;
+
+	rangetyp = typcache->rngtype;
+
+	/*
+	 * Every multirange contains an infinite number of empty ranges, even an
+	 * empty one.
+	 */
+	if (RangeIsEmpty(r))
+		return true;
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	for (i = 0; i < range_count; i++)
+	{
+		mrr = ranges[i];
+		if (range_contains_internal(rangetyp, mrr, r))
+			return true;
+	}
+
+	return false;
+}
+
+
+/* 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));
+}
+
+/* overlaps? */
+Datum
+range_overlaps_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(range_overlaps_multirange_internal(typcache, r, mr));
+}
+
+Datum
+multirange_overlaps_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(range_overlaps_multirange_internal(typcache, r, mr));
+}
+
+Datum
+multirange_overlaps_multirange(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_overlaps_multirange_internal(typcache, mr1, mr2));
+}
+
+bool
+range_overlaps_multirange_internal(TypeCacheEntry *typcache, RangeType *r, MultirangeType * mr)
+{
+	TypeCacheEntry *rangetyp;
+	int32		range_count;
+	int32		i;
+	RangeType **ranges;
+	RangeType  *mrr;
+
+	/*
+	 * Empties never overlap, even with empties. (This seems strange since
+	 * they *do* contain each other, but we want to follow how ranges work.)
+	 */
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		return false;
+
+	rangetyp = typcache->rngtype;
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	/* Scan through mrr and once it gets to r they either overlap or not */
+	mrr = ranges[0];
+
+	/* Discard mrrs while mrr << r */
+	i = 0;
+	while (range_before_internal(rangetyp, mrr, r))
+	{
+		if (++i >= range_count)
+			return false;
+		mrr = ranges[i];
+	}
+
+	/* Now either we overlap or we passed r */
+	return range_overlaps_internal(rangetyp, mrr, r);
+}
+
+bool
+multirange_overlaps_multirange_internal(TypeCacheEntry *typcache, MultirangeType * mr1,
+										MultirangeType * mr2)
+{
+	TypeCacheEntry *rangetyp;
+	int32		range_count1;
+	int32		range_count2;
+	int32		i1;
+	int32		i2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+	RangeType  *r1;
+	RangeType  *r2;
+
+	/*
+	 * Empties never overlap, even with empties. (This seems strange since
+	 * they *do* contain each other, but we want to follow how ranges work.)
+	 */
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		return false;
+
+	rangetyp = typcache->rngtype;
+
+	multirange_deserialize(mr1, &range_count1, &ranges1);
+	multirange_deserialize(mr2, &range_count2, &ranges2);
+
+	/*
+	 * Every range in mr1 gets a chance to overlap with the ranges in mr2, but
+	 * we can use their ordering to avoid O(n^2). This is similar to
+	 * range_overlaps_multirange where r1 : r2 :: mrr : r, but there if we
+	 * don't find an overlap with r we're done, and here if we don't find an
+	 * overlap with r2 we try the next r2.
+	 */
+	r1 = ranges1[0];
+	for (i1 = 0, i2 = 0; i2 < range_count2; i2++)
+	{
+		r2 = ranges2[i2];
+
+		/* Discard r1s while r1 << r2 */
+		while (range_before_internal(rangetyp, r1, r2))
+		{
+			if (++i1 >= range_count1)
+				return false;
+			r1 = ranges1[i1];
+		}
+
+		/*
+		 * If r1 && r2, we're done, otherwise we failed to find an overlap for
+		 * r2, so go to the next one.
+		 */
+		if (range_overlaps_internal(rangetyp, r1, r2))
+			return true;
+	}
+
+	/* We looked through all of mr2 without finding an overlap */
+	return false;
+}
+
+/* does not extend to right of? */
+Datum
+range_overleft_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_overleft_internal(typcache->rngtype, r, ranges[range_count - 1]));
+}
+
+Datum
+multirange_overleft_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_overleft_internal(typcache->rngtype, ranges[range_count - 1], r));
+}
+
+Datum
+multirange_overleft_multirange(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
+	MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr1));
+
+	multirange_deserialize(mr1, &range_count1, &ranges1);
+	multirange_deserialize(mr2, &range_count2, &ranges2);
+
+	PG_RETURN_BOOL(range_overleft_internal(typcache->rngtype, ranges1[range_count1 - 1], ranges2[range_count2 - 1]));
+}
+
+/* does not extend to left of? */
+Datum
+range_overright_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_overright_internal(typcache->rngtype, r, ranges[0]));
+}
+
+Datum
+multirange_overright_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_overright_internal(typcache->rngtype, ranges[0], r));
+}
+
+Datum
+multirange_overright_multirange(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
+	MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr1));
+
+	multirange_deserialize(mr1, &range_count1, &ranges1);
+	multirange_deserialize(mr2, &range_count2, &ranges2);
+
+	PG_RETURN_BOOL(range_overright_internal(typcache->rngtype, ranges1[0], ranges2[0]));
+}
+
+/* contains? */
+Datum
+multirange_contains_multirange(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_contains_multirange_internal(typcache, mr1, mr2));
+}
+
+/* contained by? */
+Datum
+multirange_contained_by_multirange(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_contains_multirange_internal(typcache, mr2, mr1));
+}
+
+/*
+ * Test whether multirange mr1 contains every range from another multirange mr2.
+ */
+bool
+multirange_contains_multirange_internal(TypeCacheEntry *typcache,
+										MultirangeType * mr1, MultirangeType * mr2)
+{
+	TypeCacheEntry *rangetyp;
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+	RangeType  *r1;
+	RangeType  *r2;
+	int			i1,
+				i2;
+
+	rangetyp = typcache->rngtype;
+
+	multirange_deserialize(mr1, &range_count1, &ranges1);
+	multirange_deserialize(mr2, &range_count2, &ranges2);
+
+	/*
+	 * We follow the same logic for empties as ranges: - an empty multirange
+	 * contains an empty range/multirange. - an empty multirange can't contain
+	 * any other range/multirange. - an empty multirange is contained by any
+	 * other range/multirange.
+	 */
+
+	if (range_count2 == 0)
+		return true;
+	if (range_count1 == 0)
+		return false;
+
+	/*
+	 * Every range in mr2 must be contained by some range in mr1. To avoid
+	 * O(n^2) we walk through both ranges in tandem.
+	 */
+	r1 = ranges1[0];
+	for (i1 = 0, i2 = 0; i2 < range_count2; i2++)
+	{
+		r2 = ranges2[i2];
+
+		/* Discard r1s while r1 << r2 */
+		while (range_before_internal(rangetyp, r1, r2))
+		{
+			if (++i1 >= range_count1)
+				return false;
+			r1 = ranges1[i1];
+		}
+
+		/*
+		 * If r1 @> r2, go to the next r2, otherwise return false (since every
+		 * r1[n] and r1[n+1] must have a gap). Note this will give weird
+		 * answers if you don't canonicalize, e.g. with a custom
+		 * int2multirange {[1,1], [2,2]} there is a "gap". But that is
+		 * consistent with other range operators, e.g. '[1,1]'::int2range -|-
+		 * '[2,2]'::int2range is false.
+		 */
+		if (!range_contains_internal(rangetyp, r1, r2))
+			return false;
+	}
+
+	/* All ranges in mr2 are satisfied */
+	return true;
+}
+
+/* strictly left of? */
+Datum
+range_before_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(range_before_multirange_internal(typcache, r, mr));
+}
+
+Datum
+multirange_before_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(range_after_multirange_internal(typcache, r, mr));
+}
+
+Datum
+multirange_before_multirange(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_before_multirange_internal(typcache, mr1, mr2));
+}
+
+/* strictly right of? */
+Datum
+range_after_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(range_after_multirange_internal(typcache, r, mr));
+}
+
+Datum
+multirange_after_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(range_before_multirange_internal(typcache, r, mr));
+}
+
+Datum
+multirange_after_multirange(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_before_multirange_internal(typcache, mr2, mr1));
+}
+
+/* strictly left of? (internal version) */
+bool
+range_before_multirange_internal(TypeCacheEntry *typcache, RangeType *r,
+								 MultirangeType * mr)
+{
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		return false;
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	return range_before_internal(typcache->rngtype, r, ranges[0]);
+}
+
+bool
+multirange_before_multirange_internal(TypeCacheEntry *typcache, MultirangeType * mr1,
+									  MultirangeType * mr2)
+{
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		return false;
+
+	multirange_deserialize(mr1, &range_count1, &ranges1);
+	multirange_deserialize(mr2, &range_count2, &ranges2);
+
+	return range_before_internal(typcache->rngtype, ranges1[range_count1 - 1],
+								 ranges2[0]);
+}
+
+/* strictly right of? (internal version) */
+bool
+range_after_multirange_internal(TypeCacheEntry *typcache, RangeType *r,
+								MultirangeType * mr)
+{
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		return false;
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	return range_after_internal(typcache->rngtype, r, ranges[range_count - 1]);
+}
+
+/* adjacent to? */
+Datum
+range_adjacent_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		return false;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_adjacent_internal(typcache->rngtype, r, ranges[0]));
+}
+
+Datum
+multirange_adjacent_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		return false;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_adjacent_internal(typcache->rngtype, ranges[range_count - 1], r));
+}
+
+Datum
+multirange_adjacent_multirange(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
+	MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		return false;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr1));
+
+	multirange_deserialize(mr1, &range_count1, &ranges1);
+	multirange_deserialize(mr2, &range_count2, &ranges2);
+
+	PG_RETURN_BOOL(range_adjacent_internal(typcache->rngtype, ranges1[range_count1 - 1], ranges2[0]));
+}
+
+/* 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);
+}
+
+/* multirange -> range functions */
+
+/* Find the smallest range that includes everything in the multirange */
+Datum
+range_merge_from_multirange(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	Oid			mltrngtypoid = MultirangeTypeGetOid(mr);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_RANGE_P(make_empty_range(typcache->rngtype));
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_RANGE_P(range_union_internal(typcache->rngtype, ranges[0],
+										   ranges[range_count - 1], false));
+}
+
+/* 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/pg_upgrade_support.c b/src/backend/utils/adt/pg_upgrade_support.c
index 8b26e1a93d..9362995741 100644
--- a/src/backend/utils/adt/pg_upgrade_support.c
+++ b/src/backend/utils/adt/pg_upgrade_support.c
@@ -52,6 +52,28 @@ binary_upgrade_set_next_array_pg_type_oid(PG_FUNCTION_ARGS)
 }
 
 Datum
+binary_upgrade_set_next_multirange_pg_type_oid(PG_FUNCTION_ARGS)
+{
+	Oid			typoid = PG_GETARG_OID(0);
+
+	CHECK_IS_BINARY_UPGRADE;
+	binary_upgrade_next_mrng_pg_type_oid = typoid;
+
+	PG_RETURN_VOID();
+}
+
+Datum
+binary_upgrade_set_next_multirange_array_pg_type_oid(PG_FUNCTION_ARGS)
+{
+	Oid			typoid = PG_GETARG_OID(0);
+
+	CHECK_IS_BINARY_UPGRADE;
+	binary_upgrade_next_mrng_array_pg_type_oid = typoid;
+
+	PG_RETURN_VOID();
+}
+
+Datum
 binary_upgrade_set_next_toast_pg_type_oid(PG_FUNCTION_ARGS)
 {
 	Oid			typoid = PG_GETARG_OID(0);
diff --git a/src/backend/utils/adt/pseudotypes.c b/src/backend/utils/adt/pseudotypes.c
index ad0e363c70..152e77f44c 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"
 
 
 /*
@@ -185,6 +186,30 @@ anyrange_out(PG_FUNCTION_ARGS)
 }
 
 /*
+ * 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.
  *
  * We allow this so that PL functions can return VOID without any special
diff --git a/src/backend/utils/adt/rangetypes.c b/src/backend/utils/adt/rangetypes.c
index 639e1dad6c..be1e044269 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
 {
@@ -431,19 +429,37 @@ range_lower(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
 	TypeCacheEntry *typcache;
+	bool		isnull;
+	Datum		result;
+
+	typcache = range_get_typcache(fcinfo, RangeTypeGetOid(r1));
+
+	result = range_lower_internal(typcache, r1, &isnull);
+
+	if (isnull)
+		PG_RETURN_NULL();
+	else
+		PG_RETURN_DATUM(result);
+}
+
+Datum
+range_lower_internal(TypeCacheEntry *typcache, const RangeType *r1, bool *isnull)
+{
 	RangeBound	lower;
 	RangeBound	upper;
 	bool		empty;
 
-	typcache = range_get_typcache(fcinfo, RangeTypeGetOid(r1));
-
 	range_deserialize(typcache, r1, &lower, &upper, &empty);
 
 	/* Return NULL if there's no finite lower bound */
 	if (empty || lower.infinite)
-		PG_RETURN_NULL();
+	{
+		*isnull = true;
+		return 0;
+	}
 
-	PG_RETURN_DATUM(lower.val);
+	*isnull = false;
+	return lower.val;
 }
 
 /* extract upper bound value */
@@ -452,19 +468,37 @@ range_upper(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
 	TypeCacheEntry *typcache;
+	bool		isnull;
+	Datum		result;
+
+	typcache = range_get_typcache(fcinfo, RangeTypeGetOid(r1));
+
+	result = range_upper_internal(typcache, r1, &isnull);
+
+	if (isnull)
+		PG_RETURN_NULL();
+	else
+		PG_RETURN_DATUM(result);
+}
+
+Datum
+range_upper_internal(TypeCacheEntry *typcache, const RangeType *r1, bool *isnull)
+{
 	RangeBound	lower;
 	RangeBound	upper;
 	bool		empty;
 
-	typcache = range_get_typcache(fcinfo, RangeTypeGetOid(r1));
-
 	range_deserialize(typcache, r1, &lower, &upper, &empty);
 
 	/* Return NULL if there's no finite upper bound */
 	if (empty || upper.infinite)
-		PG_RETURN_NULL();
+	{
+		*isnull = true;
+		return 0;
+	}
 
-	PG_RETURN_DATUM(upper.val);
+	*isnull = false;
+	return upper.val;
 }
 
 
@@ -475,9 +509,8 @@ Datum
 range_empty(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
-	char		flags = range_get_flags(r1);
 
-	PG_RETURN_BOOL(flags & RANGE_EMPTY);
+	PG_RETURN_BOOL(range_has_flag(r1, RANGE_EMPTY));
 }
 
 /* is lower bound inclusive? */
@@ -485,9 +518,8 @@ Datum
 range_lower_inc(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
-	char		flags = range_get_flags(r1);
 
-	PG_RETURN_BOOL(flags & RANGE_LB_INC);
+	PG_RETURN_BOOL(range_has_flag(r1, RANGE_LB_INC));
 }
 
 /* is upper bound inclusive? */
@@ -495,9 +527,8 @@ Datum
 range_upper_inc(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
-	char		flags = range_get_flags(r1);
 
-	PG_RETURN_BOOL(flags & RANGE_UB_INC);
+	PG_RETURN_BOOL(range_has_flag(r1, RANGE_UB_INC));
 }
 
 /* is lower bound infinite? */
@@ -505,9 +536,8 @@ Datum
 range_lower_inf(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
-	char		flags = range_get_flags(r1);
 
-	PG_RETURN_BOOL(flags & RANGE_LB_INF);
+	PG_RETURN_BOOL(range_has_flag(r1, RANGE_LB_INF));
 }
 
 /* is upper bound infinite? */
@@ -515,9 +545,8 @@ Datum
 range_upper_inf(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
-	char		flags = range_get_flags(r1);
 
-	PG_RETURN_BOOL(flags & RANGE_UB_INF);
+	PG_RETURN_BOOL(range_has_flag(r1, RANGE_UB_INF));
 }
 
 
@@ -957,7 +986,25 @@ range_minus(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
 	RangeType  *r2 = PG_GETARG_RANGE_P(1);
+	RangeType  *ret;
 	TypeCacheEntry *typcache;
+
+	/* 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));
+
+	ret = range_minus_internal(typcache, r1, r2);
+	if (ret)
+		PG_RETURN_RANGE_P(ret);
+	else
+		PG_RETURN_NULL();
+}
+
+RangeType *
+range_minus_internal(TypeCacheEntry *typcache, RangeType *r1, RangeType *r2)
+{
 	RangeBound	lower1,
 				lower2;
 	RangeBound	upper1,
@@ -969,18 +1016,12 @@ range_minus(PG_FUNCTION_ARGS)
 				cmp_u1l2,
 				cmp_u1u2;
 
-	/* 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));
-
 	range_deserialize(typcache, r1, &lower1, &upper1, &empty1);
 	range_deserialize(typcache, r2, &lower2, &upper2, &empty2);
 
 	/* if either is empty, r1 is the correct answer */
 	if (empty1 || empty2)
-		PG_RETURN_RANGE_P(r1);
+		return r1;
 
 	cmp_l1l2 = range_cmp_bounds(typcache, &lower1, &lower2);
 	cmp_l1u2 = range_cmp_bounds(typcache, &lower1, &upper2);
@@ -993,34 +1034,34 @@ range_minus(PG_FUNCTION_ARGS)
 				 errmsg("result of range difference would not be contiguous")));
 
 	if (cmp_l1u2 > 0 || cmp_u1l2 < 0)
-		PG_RETURN_RANGE_P(r1);
+		return r1;
 
 	if (cmp_l1l2 >= 0 && cmp_u1u2 <= 0)
-		PG_RETURN_RANGE_P(make_empty_range(typcache));
+		return make_empty_range(typcache);
 
 	if (cmp_l1l2 <= 0 && cmp_u1l2 >= 0 && cmp_u1u2 <= 0)
 	{
 		lower2.inclusive = !lower2.inclusive;
 		lower2.lower = false;	/* it will become the upper bound */
-		PG_RETURN_RANGE_P(make_range(typcache, &lower1, &lower2, false));
+		return make_range(typcache, &lower1, &lower2, false);
 	}
 
 	if (cmp_l1l2 >= 0 && cmp_u1u2 >= 0 && cmp_l1u2 <= 0)
 	{
 		upper2.inclusive = !upper2.inclusive;
 		upper2.lower = true;	/* it will become the lower bound */
-		PG_RETURN_RANGE_P(make_range(typcache, &upper2, &upper1, false));
+		return make_range(typcache, &upper2, &upper1, false);
 	}
 
 	elog(ERROR, "unexpected case in range_minus");
-	PG_RETURN_NULL();
+	return NULL;
 }
 
 /*
  * Set union.  If strict is true, it is an error that the two input ranges
  * are not adjacent or overlapping.
  */
-static RangeType *
+RangeType *
 range_union_internal(TypeCacheEntry *typcache, RangeType *r1, RangeType *r2,
 					 bool strict)
 {
@@ -1101,6 +1142,19 @@ range_intersect(PG_FUNCTION_ARGS)
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
 	RangeType  *r2 = PG_GETARG_RANGE_P(1);
 	TypeCacheEntry *typcache;
+
+	/* 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));
+
+	PG_RETURN_RANGE_P(range_intersect_internal(typcache, r1, r2));
+}
+
+RangeType *
+range_intersect_internal(TypeCacheEntry *typcache, const RangeType *r1, const RangeType *r2)
+{
 	RangeBound	lower1,
 				lower2;
 	RangeBound	upper1,
@@ -1110,17 +1164,11 @@ range_intersect(PG_FUNCTION_ARGS)
 	RangeBound *result_lower;
 	RangeBound *result_upper;
 
-	/* 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));
-
 	range_deserialize(typcache, r1, &lower1, &upper1, &empty1);
 	range_deserialize(typcache, r2, &lower2, &upper2, &empty2);
 
-	if (empty1 || empty2 || !DatumGetBool(range_overlaps(fcinfo)))
-		PG_RETURN_RANGE_P(make_empty_range(typcache));
+	if (empty1 || empty2 || !range_overlaps_internal(typcache, r1, r2))
+		return make_empty_range(typcache);
 
 	if (range_cmp_bounds(typcache, &lower1, &lower2) >= 0)
 		result_lower = &lower1;
@@ -1132,9 +1180,81 @@ range_intersect(PG_FUNCTION_ARGS)
 	else
 		result_upper = &upper2;
 
-	PG_RETURN_RANGE_P(make_range(typcache, result_lower, result_upper, false));
+	return make_range(typcache, result_lower, result_upper, false);
+}
+
+/* range, range -> range, range functions */
+
+/*
+ * range_split_internal - if r2 intersects the middle of r1, leaving non-empty
+ * ranges on both sides, then return true and set output1 and output2 to the
+ * results of r1 - r2 (in order). Otherwise return false and don't set output1
+ * or output2. Neither input range should be empty.
+ */
+bool
+range_split_internal(TypeCacheEntry *typcache, const RangeType *r1, const RangeType *r2,
+					 RangeType **output1, RangeType **output2)
+{
+	RangeBound	lower1,
+				lower2;
+	RangeBound	upper1,
+				upper2;
+	bool		empty1,
+				empty2;
+
+	range_deserialize(typcache, r1, &lower1, &upper1, &empty1);
+	range_deserialize(typcache, r2, &lower2, &upper2, &empty2);
+
+	if (range_cmp_bounds(typcache, &lower1, &lower2) < 0 &&
+		range_cmp_bounds(typcache, &upper1, &upper2) > 0)
+	{
+		/*
+		 * Need to invert inclusive/exclusive for the lower2 and upper2
+		 * points. They can't be infinite though. We're allowed to overwrite
+		 * these RangeBounds since they only exist locally.
+		 */
+		lower2.inclusive = !lower2.inclusive;
+		lower2.lower = false;
+		upper2.inclusive = !upper2.inclusive;
+		upper2.lower = true;
+
+		*output1 = make_range(typcache, &lower1, &lower2, false);
+		*output2 = make_range(typcache, &upper2, &upper1, false);
+		return true;
+	}
+
+	return false;
 }
 
+/* range -> range aggregate functions */
+
+Datum
+range_intersect_agg_transfn(PG_FUNCTION_ARGS)
+{
+	MemoryContext aggContext;
+	Oid			rngtypoid;
+	TypeCacheEntry *typcache;
+	RangeType  *result;
+	RangeType  *current;
+
+	if (!AggCheckCallContext(fcinfo, &aggContext))
+		elog(ERROR, "range_intersect_agg_transfn called in non-aggregate context");
+
+	rngtypoid = get_fn_expr_argtype(fcinfo->flinfo, 1);
+	if (!type_is_range(rngtypoid))
+		ereport(ERROR, (errmsg("range_intersect_agg must be called with a range")));
+
+	typcache = range_get_typcache(fcinfo, rngtypoid);
+
+	/* strictness ensures these are non-null */
+	result = PG_GETARG_RANGE_P(0);
+	current = PG_GETARG_RANGE_P(1);
+
+	result = range_intersect_internal(typcache, result, current);
+	PG_RETURN_RANGE_P(result);
+}
+
+
 /* Btree support */
 
 /* btree comparator */
@@ -1144,12 +1264,6 @@ range_cmp(PG_FUNCTION_ARGS)
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
 	RangeType  *r2 = PG_GETARG_RANGE_P(1);
 	TypeCacheEntry *typcache;
-	RangeBound	lower1,
-				lower2;
-	RangeBound	upper1,
-				upper2;
-	bool		empty1,
-				empty2;
 	int			cmp;
 
 	check_stack_depth();		/* recurses when subtype is a range type */
@@ -1160,6 +1274,28 @@ range_cmp(PG_FUNCTION_ARGS)
 
 	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, const RangeType *r1, const 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);
 
@@ -1177,10 +1313,7 @@ range_cmp(PG_FUNCTION_ARGS)
 			cmp = range_cmp_bounds(typcache, &upper1, &upper2);
 	}
 
-	PG_FREE_IF_COPY(r1, 0);
-	PG_FREE_IF_COPY(r2, 1);
-
-	PG_RETURN_INT32(cmp);
+	return cmp;
 }
 
 /* inequality operators using the range_cmp function */
@@ -1218,13 +1351,10 @@ range_gt(PG_FUNCTION_ARGS)
 
 /* Hash support */
 
-/* hash a range value */
-Datum
-hash_range(PG_FUNCTION_ARGS)
+uint32
+hash_range_internal(TypeCacheEntry *typcache, const RangeType *r)
 {
-	RangeType  *r = PG_GETARG_RANGE_P(0);
 	uint32		result;
-	TypeCacheEntry *typcache;
 	TypeCacheEntry *scache;
 	RangeBound	lower;
 	RangeBound	upper;
@@ -1233,10 +1363,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 +1404,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, const RangeType *r, Datum seed)
+{
+	uint64		result;
 	TypeCacheEntry *scache;
 	RangeBound	lower;
 	RangeBound	upper;
@@ -1300,10 +1433,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 +1471,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));
 }
 
 /*
@@ -1770,6 +1917,21 @@ range_get_flags(const RangeType *range)
 }
 
 /*
+ * range_has_flag: set whether a range has a specific flag.
+ *
+ * This lets expose some of our functions that just check flags
+ * to the rest of the code base (like to multiranges)
+ * without writing full-fledged *_internal versions.
+ */
+bool
+range_has_flag(const RangeType *r1, char flag)
+{
+	char		flags = range_get_flags(r1);
+
+	return flags & flag;
+}
+
+/*
  * range_set_contain_empty: set the RANGE_CONTAIN_EMPTY bit in the value.
  *
  * This is only needed in GiST operations, so we don't include a provision
@@ -1938,6 +2100,45 @@ range_cmp_bound_values(TypeCacheEntry *typcache, const RangeBound *b1,
 }
 
 /*
+ * 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.
  */
 RangeType *
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index 1e3e6d3a7e..2f6257c95f 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -2469,6 +2469,16 @@ type_is_range(Oid typid)
 }
 
 /*
+ * 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
  *
  *		Given the type OID, fetch its category and preferred-type status.
@@ -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->rngmultitypid;
+		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 53d9ddf159..fb9179c42a 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_rngmultitypid,
+			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 cdf6331a97..6677bc8bdc 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;
@@ -759,6 +777,16 @@ lookup_type_cache(Oid type_id, int flags)
 	}
 
 	/*
+	 * 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
 	 */
 	if ((flags & TYPECACHE_DOMAIN_BASE_INFO) &&
@@ -871,6 +899,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
  *
  * Note: we assume we're called in a relatively short-lived context, so it's
@@ -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 b7eee3da1d..defc801f7a 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,131 @@ resolve_polymorphic_tupdesc(TupleDesc tupdesc, oidvector *declared_args,
 				return false;
 			anyelement_type = subtype;
 		}
+
+		if (OidIsValid(anymultirange_type))
+		{
+			Oid			rngtype;
+			Oid			subtype;
+
+			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;
+			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;
+			Oid			subtype;
+
+			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;
+
+			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;
+			Oid			mltrngtype;
+			Oid			rngtype;
+
+			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;
+
+			mltrngtype = resolve_generic_type(ANYMULTIRANGEOID,
+											  anyrange_type,
+											  ANYRANGEOID);
+
+			/* check for inconsistent range and multirange results */
+			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 +761,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 +793,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 +856,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 +880,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 +906,132 @@ resolve_polymorphic_argtypes(int numargs, Oid *argtypes, char *argmodes,
 				return false;
 			anyelement_type = subtype;
 		}
+
+		if (OidIsValid(anymultirange_type))
+		{
+			Oid			rngtype;
+			Oid			subtype;
+
+			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;
+
+			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;
+			Oid			subtype;
+
+			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;
+
+			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;
+			Oid			mltrngtype;
+			Oid			rngtype;
+
+			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;
+
+			mltrngtype = resolve_generic_type(ANYMULTIRANGEOID,
+											  anyrange_type,
+											  ANYRANGEOID);
+
+			/* check for inconsistent range and multirange results */
+			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 +1051,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/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 799b6988b7..7237b5e52a 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -274,7 +274,8 @@ static void dumpSearchPath(Archive *AH);
 static void binary_upgrade_set_type_oids_by_type_oid(Archive *fout,
 													 PQExpBuffer upgrade_buffer,
 													 Oid pg_type_oid,
-													 bool force_array_type);
+													 bool force_array_type,
+													 bool include_multirange_type);
 static bool binary_upgrade_set_type_oids_by_rel_oid(Archive *fout,
 													PQExpBuffer upgrade_buffer, Oid pg_rel_oid);
 static void binary_upgrade_set_pg_class_oids(Archive *fout,
@@ -1583,7 +1584,7 @@ selectDumpableType(TypeInfo *tyinfo, Archive *fout)
 	}
 
 	/* skip auto-generated array types */
-	if (tyinfo->isArray)
+	if (tyinfo->isArray || tyinfo->isMultirange)
 	{
 		tyinfo->dobj.objType = DO_DUMMY_TYPE;
 
@@ -4275,15 +4276,49 @@ dumpSubscription(Archive *fout, SubscriptionInfo *subinfo)
 	free(qsubname);
 }
 
+static Oid
+get_next_possible_free_pg_type_oid(Archive *fout, PQExpBuffer upgrade_query)
+{
+	/*
+	 * If the old version didn't assign an array type, but the new version
+	 * does, we must select an unused type OID to assign.  This currently only
+	 * happens for domains, when upgrading pre-v11 to v11 and up.
+	 *
+	 * Note: local state here is kind of ugly, but we must have some, since we
+	 * mustn't choose the same unused OID more than once.
+	 */
+	static Oid	next_possible_free_oid = FirstNormalObjectId;
+	PGresult   *res;
+	bool		is_dup;
+
+	do
+	{
+		++next_possible_free_oid;
+		printfPQExpBuffer(upgrade_query,
+						  "SELECT EXISTS(SELECT 1 "
+						  "FROM pg_catalog.pg_type "
+						  "WHERE oid = '%u'::pg_catalog.oid);",
+						  next_possible_free_oid);
+		res = ExecuteSqlQueryForSingleRow(fout, upgrade_query->data);
+		is_dup = (PQgetvalue(res, 0, 0)[0] == 't');
+		PQclear(res);
+	} while (is_dup);
+
+	return next_possible_free_oid;
+}
+
 static void
 binary_upgrade_set_type_oids_by_type_oid(Archive *fout,
 										 PQExpBuffer upgrade_buffer,
 										 Oid pg_type_oid,
-										 bool force_array_type)
+										 bool force_array_type,
+										 bool include_multirange_type)
 {
 	PQExpBuffer upgrade_query = createPQExpBuffer();
 	PGresult   *res;
 	Oid			pg_type_array_oid;
+	Oid			pg_type_multirange_oid;
+	Oid			pg_type_multirange_array_oid;
 
 	appendPQExpBufferStr(upgrade_buffer, "\n-- For binary upgrade, must preserve pg_type oid\n");
 	appendPQExpBuffer(upgrade_buffer,
@@ -4304,33 +4339,7 @@ binary_upgrade_set_type_oids_by_type_oid(Archive *fout,
 	PQclear(res);
 
 	if (!OidIsValid(pg_type_array_oid) && force_array_type)
-	{
-		/*
-		 * If the old version didn't assign an array type, but the new version
-		 * does, we must select an unused type OID to assign.  This currently
-		 * only happens for domains, when upgrading pre-v11 to v11 and up.
-		 *
-		 * Note: local state here is kind of ugly, but we must have some,
-		 * since we mustn't choose the same unused OID more than once.
-		 */
-		static Oid	next_possible_free_oid = FirstNormalObjectId;
-		bool		is_dup;
-
-		do
-		{
-			++next_possible_free_oid;
-			printfPQExpBuffer(upgrade_query,
-							  "SELECT EXISTS(SELECT 1 "
-							  "FROM pg_catalog.pg_type "
-							  "WHERE oid = '%u'::pg_catalog.oid);",
-							  next_possible_free_oid);
-			res = ExecuteSqlQueryForSingleRow(fout, upgrade_query->data);
-			is_dup = (PQgetvalue(res, 0, 0)[0] == 't');
-			PQclear(res);
-		} while (is_dup);
-
-		pg_type_array_oid = next_possible_free_oid;
-	}
+		pg_type_array_oid = get_next_possible_free_pg_type_oid(fout, upgrade_query);
 
 	if (OidIsValid(pg_type_array_oid))
 	{
@@ -4341,6 +4350,46 @@ binary_upgrade_set_type_oids_by_type_oid(Archive *fout,
 						  pg_type_array_oid);
 	}
 
+	/*
+	 * Pre-set the multirange type oid and its own array type oid.
+	 */
+	if (include_multirange_type)
+	{
+		if (fout->remoteVersion >= 130000)
+		{
+			appendPQExpBuffer(upgrade_query,
+							  "SELECT t.oid, t.typarray "
+							  "FROM pg_catalog.pg_type t "
+							  "JOIN pg_catalog.pg_range r "
+							  "ON t.oid = r.rngmultitypid "
+							  "WHERE r.rngtypid = '%u'::pg_catalog.oid;",
+							  pg_type_oid);
+
+			res = ExecuteSqlQueryForSingleRow(fout, upgrade_query->data);
+
+			pg_type_multirange_oid = atooid(PQgetvalue(res, 0, PQfnumber(res, "oid")));
+			pg_type_multirange_array_oid = atooid(PQgetvalue(res, 0, PQfnumber(res, "typarray")));
+
+			PQclear(res);
+		}
+		else
+		{
+			pg_type_multirange_oid = get_next_possible_free_pg_type_oid(fout, upgrade_query);
+			pg_type_multirange_array_oid = get_next_possible_free_pg_type_oid(fout, upgrade_query);
+		}
+
+		appendPQExpBufferStr(upgrade_buffer,
+							 "\n-- For binary upgrade, must preserve multirange pg_type oid\n");
+		appendPQExpBuffer(upgrade_buffer,
+						  "SELECT pg_catalog.binary_upgrade_set_next_multirange_pg_type_oid('%u'::pg_catalog.oid);\n\n",
+						  pg_type_multirange_oid);
+		appendPQExpBufferStr(upgrade_buffer,
+							 "\n-- For binary upgrade, must preserve multirange pg_type array oid\n");
+		appendPQExpBuffer(upgrade_buffer,
+						  "SELECT pg_catalog.binary_upgrade_set_next_multirange_array_pg_type_oid('%u'::pg_catalog.oid);\n\n",
+						  pg_type_multirange_array_oid);
+	}
+
 	destroyPQExpBuffer(upgrade_query);
 }
 
@@ -4374,7 +4423,7 @@ binary_upgrade_set_type_oids_by_rel_oid(Archive *fout,
 	pg_type_oid = atooid(PQgetvalue(upgrade_res, 0, PQfnumber(upgrade_res, "crel")));
 
 	binary_upgrade_set_type_oids_by_type_oid(fout, upgrade_buffer,
-											 pg_type_oid, false);
+											 pg_type_oid, false, false);
 
 	if (!PQgetisnull(upgrade_res, 0, PQfnumber(upgrade_res, "trel")))
 	{
@@ -4923,6 +4972,11 @@ getTypes(Archive *fout, int *numTypes)
 		else
 			tyinfo[i].isArray = false;
 
+		if (tyinfo[i].typtype == 'm')
+			tyinfo[i].isMultirange = true;
+		else
+			tyinfo[i].isMultirange = false;
+
 		/* Decide whether we want to dump it */
 		selectDumpableType(&tyinfo[i], fout);
 
@@ -8037,9 +8091,12 @@ getProcLangs(Archive *fout, int *numProcLangs)
 
 /*
  * getCasts
- *	  get basic information about every cast in the system
+ *	  get basic information about most casts in the system
  *
  * numCasts is set to the number of casts read in
+ *
+ * Skip casts from a range to its multirange, since we'll create those
+ * automatically.
  */
 CastInfo *
 getCasts(Archive *fout, int *numCasts)
@@ -8057,7 +8114,20 @@ getCasts(Archive *fout, int *numCasts)
 	int			i_castcontext;
 	int			i_castmethod;
 
-	if (fout->remoteVersion >= 80400)
+	if (fout->remoteVersion >= 130000)
+	{
+		appendPQExpBufferStr(query, "SELECT tableoid, oid, "
+							 "castsource, casttarget, castfunc, castcontext, "
+							 "castmethod "
+							 "FROM pg_cast c "
+							 "WHERE NOT EXISTS ( "
+							 "SELECT 1 FROM pg_range r "
+							 "WHERE c.castsource = r.rngtypid "
+							 "AND c.casttarget = r.rngmultitypid "
+							 ") "
+							 "ORDER BY 3,4");
+	}
+	else if (fout->remoteVersion >= 80400)
 	{
 		appendPQExpBufferStr(query, "SELECT tableoid, oid, "
 							 "castsource, casttarget, castfunc, castcontext, "
@@ -10245,7 +10315,7 @@ dumpEnumType(Archive *fout, TypeInfo *tyinfo)
 	if (dopt->binary_upgrade)
 		binary_upgrade_set_type_oids_by_type_oid(fout, q,
 												 tyinfo->dobj.catId.oid,
-												 false);
+												 false, false);
 
 	appendPQExpBuffer(q, "CREATE TYPE %s AS ENUM (",
 					  qualtypname);
@@ -10371,7 +10441,7 @@ dumpRangeType(Archive *fout, TypeInfo *tyinfo)
 	if (dopt->binary_upgrade)
 		binary_upgrade_set_type_oids_by_type_oid(fout, q,
 												 tyinfo->dobj.catId.oid,
-												 false);
+												 false, true);
 
 	appendPQExpBuffer(q, "CREATE TYPE %s AS RANGE (",
 					  qualtypname);
@@ -10477,7 +10547,7 @@ dumpUndefinedType(Archive *fout, TypeInfo *tyinfo)
 	if (dopt->binary_upgrade)
 		binary_upgrade_set_type_oids_by_type_oid(fout, q,
 												 tyinfo->dobj.catId.oid,
-												 false);
+												 false, false);
 
 	appendPQExpBuffer(q, "CREATE TYPE %s;\n",
 					  qualtypname);
@@ -10682,7 +10752,7 @@ dumpBaseType(Archive *fout, TypeInfo *tyinfo)
 	if (dopt->binary_upgrade)
 		binary_upgrade_set_type_oids_by_type_oid(fout, q,
 												 tyinfo->dobj.catId.oid,
-												 false);
+												 false, false);
 
 	appendPQExpBuffer(q,
 					  "CREATE TYPE %s (\n"
@@ -10869,7 +10939,8 @@ dumpDomain(Archive *fout, TypeInfo *tyinfo)
 	if (dopt->binary_upgrade)
 		binary_upgrade_set_type_oids_by_type_oid(fout, q,
 												 tyinfo->dobj.catId.oid,
-												 true); /* force array type */
+												 true,		/* force array type */
+												 false);	/* force multirange type */
 
 	qtypname = pg_strdup(fmtId(tyinfo->dobj.name));
 	qualtypname = pg_strdup(fmtQualifiedDumpable(tyinfo));
@@ -11057,7 +11128,7 @@ dumpCompositeType(Archive *fout, TypeInfo *tyinfo)
 	{
 		binary_upgrade_set_type_oids_by_type_oid(fout, q,
 												 tyinfo->dobj.catId.oid,
-												 false);
+												 false, false);
 		binary_upgrade_set_pg_class_oids(fout, q, tyinfo->typrelid, false);
 	}
 
@@ -11331,7 +11402,7 @@ dumpShellType(Archive *fout, ShellTypeInfo *stinfo)
 	if (dopt->binary_upgrade)
 		binary_upgrade_set_type_oids_by_type_oid(fout, q,
 												 stinfo->baseType->dobj.catId.oid,
-												 false);
+												 false, false);
 
 	appendPQExpBuffer(q, "CREATE TYPE %s;\n",
 					  fmtQualifiedDumpable(stinfo));
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 21004e5078..63e0ba7116 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -175,6 +175,7 @@ typedef struct _typeInfo
 	char		typrelkind;		/* 'r', 'v', 'c', etc */
 	char		typtype;		/* 'b', 'c', etc */
 	bool		isArray;		/* true if auto-generated array type */
+	bool		isMultirange;	/* true if auto-generated multirange type */
 	bool		isDefined;		/* true if typisdefined */
 	/* If needed, we'll create a "shell type" entry for it; link that here: */
 	struct _shellTypeInfo *shellType;	/* shell-type entry, or NULL */
diff --git a/src/include/access/tupmacs.h b/src/include/access/tupmacs.h
index 0b205a0dc6..3c113fa3bd 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/binary_upgrade.h b/src/include/catalog/binary_upgrade.h
index 12d94fe1b3..01d95117cd 100644
--- a/src/include/catalog/binary_upgrade.h
+++ b/src/include/catalog/binary_upgrade.h
@@ -16,6 +16,8 @@
 
 extern PGDLLIMPORT Oid binary_upgrade_next_pg_type_oid;
 extern PGDLLIMPORT Oid binary_upgrade_next_array_pg_type_oid;
+extern PGDLLIMPORT Oid binary_upgrade_next_mrng_pg_type_oid;
+extern PGDLLIMPORT Oid binary_upgrade_next_mrng_array_pg_type_oid;
 extern PGDLLIMPORT Oid binary_upgrade_next_toast_pg_type_oid;
 
 extern PGDLLIMPORT Oid binary_upgrade_next_heap_pg_class_oid;
diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h
index 13e07fc314..48a6bf5598 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_rngmultitypid_index, 8001, on pg_range using btree(rngmultitypid 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_aggregate.dat b/src/include/catalog/pg_aggregate.dat
index ffabe275c0..03bab74253 100644
--- a/src/include/catalog/pg_aggregate.dat
+++ b/src/include/catalog/pg_aggregate.dat
@@ -538,6 +538,17 @@
   aggtransfn => 'bytea_string_agg_transfn',
   aggfinalfn => 'bytea_string_agg_finalfn', aggtranstype => 'internal' },
 
+# range
+{ aggfnoid => 'range_intersect_agg(anyrange)',
+  aggtransfn => 'range_intersect_agg_transfn',
+  aggcombinefn => 'range_intersect_agg_transfn', aggtranstype => 'anyrange' },
+{ aggfnoid => 'range_intersect_agg(anymultirange)',
+  aggtransfn => 'multirange_intersect_agg_transfn',
+  aggcombinefn => 'multirange_intersect_agg_transfn', aggtranstype => 'anymultirange' },
+{ aggfnoid => 'range_agg(anyrange)', aggtransfn => 'range_agg_transfn',
+  aggfinalfn => 'range_agg_finalfn', aggfinalextra => 't',
+  aggtranstype => 'internal' },
+
 # json
 { aggfnoid => 'json_agg', aggtransfn => 'json_agg_transfn',
   aggfinalfn => 'json_agg_finalfn', aggtranstype => 'internal' },
diff --git a/src/include/catalog/pg_amop.dat b/src/include/catalog/pg_amop.dat
index 11aaa519c8..827ac4f03c 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 c67768fcab..0dafd4ec63 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',
@@ -1203,12 +1210,13 @@
   amprocrighttype => 'anyrange', amprocnum => '4',
   amproc => 'brin_inclusion_union' },
 { amprocfamily => 'brin/range_inclusion_ops', amproclefttype => 'anyrange',
-  amprocrighttype => 'anyrange', amprocnum => '11', amproc => 'range_merge' },
+  amprocrighttype => 'anyrange', amprocnum => '11',
+  amproc => 'range_merge(anyrange,anyrange)' },
 { amprocfamily => 'brin/range_inclusion_ops', amproclefttype => 'anyrange',
   amprocrighttype => 'anyrange', amprocnum => '13',
   amproc => 'range_contains' },
 { amprocfamily => 'brin/range_inclusion_ops', amproclefttype => 'anyrange',
-  amprocrighttype => 'anyrange', amprocnum => '14', amproc => 'isempty' },
+  amprocrighttype => 'anyrange', amprocnum => '14', amproc => 'isempty(anyrange)' },
 
 # minmax pg_lsn
 { amprocfamily => 'brin/pg_lsn_minmax_ops', amproclefttype => 'pg_lsn',
diff --git a/src/include/catalog/pg_cast.dat b/src/include/catalog/pg_cast.dat
index 6ef8b8a4e7..14277c8fdf 100644
--- a/src/include/catalog/pg_cast.dat
+++ b/src/include/catalog/pg_cast.dat
@@ -512,4 +512,17 @@
 { castsource => 'jsonb', casttarget => 'float8', castfunc => 'float8(jsonb)',
   castcontext => 'e', castmethod => 'f' },
 
+# range to multirange
+{ castsource => 'int4range', casttarget => 'int4multirange', castfunc => 'int4multirange(int4range)',
+  castcontext => 'e', castmethod => 'f' },
+{ castsource => 'int8range', casttarget => 'int8multirange', castfunc => 'int8multirange(int8range)',
+  castcontext => 'e', castmethod => 'f' },
+{ castsource => 'numrange', casttarget => 'nummultirange', castfunc => 'nummultirange(numrange)',
+  castcontext => 'e', castmethod => 'f' },
+{ castsource => 'daterange', casttarget => 'datemultirange', castfunc => 'datemultirange(daterange)',
+  castcontext => 'e', castmethod => 'f' },
+{ castsource => 'tsrange', casttarget => 'tsmultirange', castfunc => 'tsmultirange(tsrange)',
+  castcontext => 'e', castmethod => 'f' },
+{ castsource => 'tstzrange', casttarget => 'tstzmultirange', castfunc => 'tstzmultirange(tstzrange)',
+  castcontext => 'e', castmethod => 'f' },
 ]
diff --git a/src/include/catalog/pg_cast.h b/src/include/catalog/pg_cast.h
index 1b81b52df6..f23ba28d9c 100644
--- a/src/include/catalog/pg_cast.h
+++ b/src/include/catalog/pg_cast.h
@@ -23,6 +23,8 @@
 #include "catalog/genbki.h"
 #include "catalog/pg_cast_d.h"
 
+#include "catalog/dependency.h"
+
 /* ----------------
  *		pg_cast definition.  cpp turns this into
  *		typedef struct FormData_pg_cast
@@ -87,4 +89,11 @@ typedef enum CoercionMethod
 
 #endif							/* EXPOSE_TO_CLIENT_CODE */
 
+extern ObjectAddress CastCreate(Oid sourcetypeid,
+								Oid targettypeid,
+								Oid funcid,
+								char castcontext,
+								char castmethod,
+								DependencyType behavior);
+
 #endif							/* PG_CAST_H */
diff --git a/src/include/catalog/pg_opclass.dat b/src/include/catalog/pg_opclass.dat
index ab2f50c9eb..eebebbcdb4 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 7c135da3b1..24f4a32203 100644
--- a/src/include/catalog/pg_operator.dat
+++ b/src/include/catalog/pg_operator.dat
@@ -3297,5 +3297,174 @@
   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' },
+{ oid => '8073', oid_symbol => 'OID_RANGE_OVERLAPS_MULTIRANGE_OP',
+  descr => 'overlaps',
+  oprname => '&&', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '&&(anymultirange,anyrange)',
+  oprcode => 'range_overlaps_multirange', oprrest => 'areasel',
+  oprjoin => 'areajoinsel' },
+{ oid => '8074', oid_symbol => 'OID_MULTIRANGE_OVERLAPS_RANGE_OP', descr => 'contains',
+  oprname => '&&', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcom => '&&(anyrange,anymultirange)',
+  oprcode => 'multirange_overlaps_range', oprrest => 'areasel',
+  oprjoin => 'areajoinsel' },
+{ oid => '8075', oid_symbol => 'OID_MULTIRANGE_OVERLAPS_MULTIRANGE_OP', descr => 'contains',
+  oprname => '&&', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '&&(anymultirange,anymultirange)',
+  oprcode => 'multirange_overlaps_multirange', oprrest => 'areasel',
+  oprjoin => 'areajoinsel' },
+{ oid => '8064', oid_symbol => 'OID_MULTIRANGE_CONTAINS_ELEM_OP',
+  descr => 'contains',
+  oprname => '@>', oprleft => 'anymultirange', oprright => 'anyelement',
+  oprresult => 'bool', oprcom => '<@(anyelement,anymultirange)',
+  oprcode => 'multirange_contains_elem', oprrest => 'contsel',
+  oprjoin => 'contjoinsel' },
+{ oid => '8065', oid_symbol => 'OID_MULTIRANGE_CONTAINS_RANGE_OP', descr => 'contains',
+  oprname => '@>', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcom => '<@(anyrange,anymultirange)',
+  oprcode => 'multirange_contains_range', oprrest => 'contsel',
+  oprjoin => 'contjoinsel' },
+{ oid => '8066', oid_symbol => 'OID_MULTIRANGE_CONTAINS_MULTIRANGE_OP', descr => 'contains',
+  oprname => '@>', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '<@(anymultirange,anymultirange)',
+  oprcode => 'multirange_contains_multirange', oprrest => 'contsel',
+  oprjoin => 'contjoinsel' },
+{ oid => '8067', oid_symbol => 'OID_MULTIRANGE_ELEM_CONTAINED_OP',
+  descr => 'is contained by',
+  oprname => '<@', oprleft => 'anyelement', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '@>(anymultirange,anyelement)',
+  oprcode => 'elem_contained_by_multirange', oprrest => 'contsel',
+  oprjoin => 'contjoinsel' },
+{ oid => '8068', oid_symbol => 'OID_MULTIRANGE_RANGE_CONTAINED_OP',
+  descr => 'is contained by',
+  oprname => '<@', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '@>(anymultirange,anyrange)',
+  oprcode => 'range_contained_by_multirange', oprrest => 'contsel',
+  oprjoin => 'contjoinsel' },
+{ oid => '8069', oid_symbol => 'OID_MULTIRANGE_MULTIRANGE_CONTAINED_OP',
+  descr => 'is contained by',
+  oprname => '<@', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '@>(anymultirange,anymultirange)',
+  oprcode => 'multirange_contained_by_multirange', oprrest => 'contsel',
+  oprjoin => 'contjoinsel' },
+{ oid => '8106', oid_symbol => 'OID_RANGE_OVERLAPS_LEFT_MULTIRANGE_OP',
+  descr => 'overlaps or is left of',
+  oprname => '&<', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcode => 'range_overleft_multirange', oprrest => 'scalarltsel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8107', oid_symbol => 'OID_MULTIRANGE_OVERLAPS_LEFT_RANGE_OP',
+  descr => 'overlaps or is left of',
+  oprname => '&<', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcode => 'multirange_overleft_range', oprrest => 'scalarltsel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8108', oid_symbol => 'OID_MULTIRANGE_OVERLAPS_LEFT_MULTIRANGE_OP',
+  descr => 'overlaps or is left of',
+  oprname => '&<', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcode => 'multirange_overleft_multirange', oprrest => 'scalarltsel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8109', oid_symbol => 'OID_RANGE_OVERLAPS_RIGHT_MULTIRANGE_OP',
+  descr => 'overlaps or is right of',
+  oprname => '&>', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcode => 'range_overright_multirange', oprrest => 'scalargtsel',
+  oprjoin => 'scalargtjoinsel' },
+{ oid => '8110', oid_symbol => 'OID_MULTIRANGE_OVERLAPS_RIGHT_RANGE_OP',
+  descr => 'overlaps or is right of',
+  oprname => '&>', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcode => 'multirange_overright_range', oprrest => 'scalargtsel',
+  oprjoin => 'scalargtjoinsel' },
+{ oid => '8111', oid_symbol => 'OID_MULTIRANGE_OVERLAPS_RIGHT_MULTIRANGE_OP',
+  descr => 'overlaps or is right of',
+  oprname => '&>', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcode => 'multirange_overright_multirange', oprrest => 'scalargtsel',
+  oprjoin => 'scalargtjoinsel' },
+{ oid => '8091', oid_symbol => 'OID_RANGE_ADJACENT_MULTIRANGE_OP', descr => 'is adjacent to',
+  oprname => '-|-', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '-|-(anymultirange,anyrange)',
+  oprcode => 'range_adjacent_multirange', oprrest => 'scalarltsel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8092', oid_symbol => 'OID_MULTIRANGE_ADJACENT_RANGE_OP', descr => 'is adjacent to',
+  oprname => '-|-', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcom => '-|-(anyrange,anymultirange)',
+  oprcode => 'multirange_adjacent_range', oprrest => 'scalarltsel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8093', oid_symbol => 'OID_MULTIRANGE_ADJACENT_MULTIRANGE_OP', descr => 'is adjacent to',
+  oprname => '-|-', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '-|-(anymultirange,anymultirange)',
+  oprcode => 'multirange_adjacent_multirange', oprrest => 'scalarltsel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8117', descr => 'multirange union',
+  oprname => '+', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'anymultirange', oprcom => '+(anymultirange,anymultirange)',
+  oprcode => 'multirange_union' },
+{ oid => '8123', descr => 'multirange minus',
+  oprname => '-', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'anymultirange', oprcode => 'multirange_minus' },
+{ oid => '8129', descr => 'multirange intersect',
+  oprname => '*', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'anymultirange', oprcom => '*(anymultirange,anymultirange)',
+  oprcode => 'multirange_intersect' },
+{ oid => '8082', oid_symbol => 'OID_RANGE_BEFORE_MULTIRANGE_OP', descr => 'is left of',
+  oprname => '<<', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '>>(anymultirange,anyrange)',
+  oprcode => 'range_before_multirange', oprrest => 'scalarltsel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8083', oid_symbol => 'OID_MULTIRANGE_BEFORE_RANGE_OP', descr => 'is left of',
+  oprname => '<<', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcom => '>>(anyrange,anymultirange)',
+  oprcode => 'multirange_before_range', oprrest => 'scalarltsel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8084', oid_symbol => 'OID_MULTIRANGE_BEFORE_MULTIRANGE_OP', descr => 'is left of',
+  oprname => '<<', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '>>(anymultirange,anymultirange)',
+  oprcode => 'multirange_before_multirange', oprrest => 'scalarltsel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8085', oid_symbol => 'OID_RANGE_AFTER_MULTIRANGE_OP', descr => 'is right of',
+  oprname => '>>', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '<<(anymultirange,anyrange)',
+  oprcode => 'range_after_multirange', oprrest => 'scalargtsel',
+  oprjoin => 'scalargtjoinsel' },
+{ oid => '8086', oid_symbol => 'OID_MULTIRANGE_AFTER_RANGE_OP', descr => 'is right of',
+  oprname => '>>', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcom => '<<(anyrange,anymultirange)',
+  oprcode => 'multirange_after_range', oprrest => 'scalargtsel',
+  oprjoin => 'scalargtjoinsel' },
+{ oid => '8087', oid_symbol => 'OID_MULTIRANGE_AFTER_MULTIRANGE_OP', descr => 'is right of',
+  oprname => '>>', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '<<(anymultirange,anymultirange)',
+  oprcode => 'multirange_after_multirange', oprrest => 'scalargtsel',
+  oprjoin => 'scalargtjoinsel' },
 
 ]
diff --git a/src/include/catalog/pg_opfamily.dat b/src/include/catalog/pg_opfamily.dat
index 26227df216..901fd7b303 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 fcf2a1214c..1f9f61a902 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -9624,6 +9624,10 @@
   descr => 'the smallest range which includes both of the given ranges',
   proname => 'range_merge', prorettype => 'anyrange',
   proargtypes => 'anyrange anyrange', prosrc => 'range_merge' },
+{ oid => '8143',
+  descr => 'the smallest range which includes the whole multirange',
+  proname => 'range_merge', prorettype => 'anyrange',
+  proargtypes => 'anymultirange', prosrc => 'range_merge_from_multirange' },
 { oid => '3868',
   proname => 'range_intersect', prorettype => 'anyrange',
   proargtypes => 'anyrange anyrange', prosrc => 'range_intersect' },
@@ -9673,6 +9677,13 @@
 { oid => '3169', descr => 'restriction selectivity for range operators',
   proname => 'rangesel', provolatile => 's', prorettype => 'float8',
   proargtypes => 'internal oid internal int4', prosrc => 'rangesel' },
+{ oid => '8133', descr => 'range aggregate by intersecting',
+  proname => 'range_intersect_agg_transfn', prorettype => 'anyrange',
+  proargtypes => 'anyrange anyrange', prosrc => 'range_intersect_agg_transfn'},
+{ oid => '8134', descr => 'range aggregate by intersecting',
+  proname => 'range_intersect_agg', prokind => 'a', proisstrict => 'f',
+  prorettype => 'anyrange', proargtypes => 'anyrange',
+  prosrc => 'aggregate_dummy'},
 
 { oid => '3914', descr => 'convert an int4 range to canonical form',
   proname => 'int4range_canonical', prorettype => 'int4range',
@@ -9741,6 +9752,258 @@
   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 => '8094', descr => 'lower bound of multirange',
+  proname => 'lower', prorettype => 'anyelement', proargtypes => 'anymultirange',
+  prosrc => 'multirange_lower' },
+{ oid => '8095', descr => 'upper bound of multirange',
+  proname => 'upper', prorettype => 'anyelement', proargtypes => 'anymultirange',
+  prosrc => 'multirange_upper' },
+{ oid => '8057', descr => 'is the multirange empty?',
+  proname => 'isempty', prorettype => 'bool',
+  proargtypes => 'anymultirange', prosrc => 'multirange_empty' },
+{ oid => '8096', descr => 'is the multirange\'s lower bound inclusive?',
+  proname => 'lower_inc', prorettype => 'bool', proargtypes => 'anymultirange',
+  prosrc => 'multirange_lower_inc' },
+{ oid => '8097', descr => 'is the multirange\'s upper bound inclusive?',
+  proname => 'upper_inc', prorettype => 'bool', proargtypes => 'anymultirange',
+  prosrc => 'multirange_upper_inc' },
+{ oid => '8098', descr => 'is the multirange\'s lower bound infinite?',
+  proname => 'lower_inf', prorettype => 'bool', proargtypes => 'anymultirange',
+  prosrc => 'multirange_lower_inf' },
+{ oid => '8099', descr => 'is the multirange\'s upper bound infinite?',
+  proname => 'upper_inf', prorettype => 'bool', proargtypes => 'anymultirange',
+  prosrc => 'multirange_upper_inf' },
+{ oid => '8008', descr => 'multirange typanalyze',
+  proname => 'multirange_typanalyze', provolatile => 's', prorettype => 'bool',
+  proargtypes => 'internal', prosrc => 'multirange_typanalyze' },
+{ oid => '8033',
+  proname => 'multirange_eq', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_eq' },
+{ oid => '8034',
+  proname => 'multirange_ne', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_ne' },
+{ oid => '8070',
+  proname => 'range_overlaps_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_overlaps_multirange' },
+{ oid => '8071',
+  proname => 'multirange_overlaps_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_overlaps_range' },
+{ oid => '8072',
+  proname => 'multirange_overlaps_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_overlaps_multirange' },
+{ oid => '8058',
+  proname => 'multirange_contains_elem', prorettype => 'bool',
+  proargtypes => 'anymultirange anyelement', prosrc => 'multirange_contains_elem' },
+{ oid => '8059',
+  proname => 'multirange_contains_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_contains_range' },
+{ oid => '8060',
+  proname => 'multirange_contains_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_contains_multirange' },
+{ oid => '8061',
+  proname => 'elem_contained_by_multirange', prorettype => 'bool',
+  proargtypes => 'anyelement anymultirange', prosrc => 'elem_contained_by_multirange' },
+{ oid => '8062',
+  proname => 'range_contained_by_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_contained_by_multirange' },
+{ oid => '8063',
+  proname => 'multirange_contained_by_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_contained_by_multirange' },
+{ oid => '8088',
+  proname => 'range_adjacent_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_adjacent_multirange' },
+{ oid => '8089',
+  proname => 'multirange_adjacent_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_adjacent_multirange' },
+{ oid => '8090',
+  proname => 'multirange_adjacent_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_adjacent_range' },
+{ oid => '8076',
+  proname => 'range_before_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_before_multirange' },
+{ oid => '8077',
+  proname => 'multirange_before_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_before_range' },
+{ oid => '8078',
+  proname => 'multirange_before_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_before_multirange' },
+{ oid => '8079',
+  proname => 'range_after_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_after_multirange' },
+{ oid => '8080',
+  proname => 'multirange_after_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_after_range' },
+{ oid => '8081',
+  proname => 'multirange_after_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_after_multirange' },
+{ oid => '8100',
+  proname => 'range_overleft_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_overleft_multirange' },
+{ oid => '8101',
+  proname => 'multirange_overleft_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_overleft_range' },
+{ oid => '8102',
+  proname => 'multirange_overleft_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_overleft_multirange' },
+{ oid => '8103',
+  proname => 'range_overright_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_overright_multirange' },
+{ oid => '8104',
+  proname => 'multirange_overright_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_overright_range' },
+{ oid => '8105',
+  proname => 'multirange_overright_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_overright_multirange' },
+{ oid => '8114',
+  proname => 'multirange_union', prorettype => 'anymultirange',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_union' },
+{ oid => '8120',
+  proname => 'multirange_minus', prorettype => 'anymultirange',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_minus' },
+{ oid => '8126',
+  proname => 'multirange_intersect', prorettype => 'anymultirange',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_intersect' },
+{ oid => '8022', descr => 'less-equal-greater',
+  proname => 'multirange_cmp', prorettype => 'int4',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_cmp' },
+{ oid => '8023',
+  proname => 'multirange_lt', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_lt' },
+{ oid => '8024',
+  proname => 'multirange_le', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_le' },
+{ oid => '8025',
+  proname => 'multirange_ge', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_ge' },
+{ oid => '8026',
+  proname => 'multirange_gt', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_gt' },
+{ oid => '8038', descr => 'hash a multirange',
+  proname => 'hash_multirange', prorettype => 'int4', proargtypes => 'anymultirange',
+  prosrc => 'hash_multirange' },
+{ oid => '8039', descr => 'hash a multirange',
+  proname => 'hash_multirange_extended', prorettype => 'int8',
+  proargtypes => 'anymultirange int8', prosrc => 'hash_multirange_extended' },
+
+{ oid => '8045', descr => 'int4multirange constructor',
+  proname => 'int4multirange', proisstrict => 'f',
+  prorettype => 'int4multirange', proargtypes => '',
+  prosrc => 'multirange_constructor0' },
+{ oid => '8146', descr => 'int4multirange constructor',
+  proname => 'int4multirange', proisstrict => 't',
+  prorettype => 'int4multirange', proargtypes => 'int4range',
+  prosrc => 'multirange_constructor1' },
+{ oid => '8046', descr => 'int4multirange constructor',
+  proname => 'int4multirange', provariadic => 'int4range', proisstrict => 'f',
+  prorettype => 'int4multirange', proargtypes => '_int4range',
+  proallargtypes => '{_int4range}', proargmodes => '{v}',
+  prosrc => 'multirange_constructor2' },
+{ oid => '8047', descr => 'nummultirange constructor',
+  proname => 'nummultirange', proisstrict => 'f',
+  prorettype => 'nummultirange', proargtypes => '',
+  prosrc => 'multirange_constructor0' },
+{ oid => '8147', descr => 'nummultirange constructor',
+  proname => 'nummultirange', proisstrict => 't',
+  prorettype => 'nummultirange', proargtypes => 'numrange',
+  prosrc => 'multirange_constructor1' },
+{ oid => '8048', descr => 'nummultirange constructor',
+  proname => 'nummultirange', provariadic => 'numrange', proisstrict => 'f',
+  prorettype => 'nummultirange', proargtypes => '_numrange',
+  proallargtypes => '{_numrange}', proargmodes => '{v}',
+  prosrc => 'multirange_constructor2' },
+{ oid => '8049', descr => 'tsmultirange constructor',
+  proname => 'tsmultirange', proisstrict => 'f',
+  prorettype => 'tsmultirange', proargtypes => '',
+  prosrc => 'multirange_constructor0' },
+{ oid => '8148', descr => 'tsmultirange constructor',
+  proname => 'tsmultirange', proisstrict => 't',
+  prorettype => 'tsmultirange', proargtypes => 'tsrange',
+  prosrc => 'multirange_constructor1' },
+{ oid => '8050', descr => 'tsmultirange constructor',
+  proname => 'tsmultirange', provariadic => 'tsrange', proisstrict => 'f',
+  prorettype => 'tsmultirange', proargtypes => '_tsrange',
+  proallargtypes => '{_tsrange}', proargmodes => '{v}',
+  prosrc => 'multirange_constructor2' },
+{ oid => '8051', descr => 'tstzmultirange constructor',
+  proname => 'tstzmultirange', proisstrict => 'f',
+  prorettype => 'tstzmultirange', proargtypes => '',
+  prosrc => 'multirange_constructor0' },
+{ oid => '8149', descr => 'tstzmultirange constructor',
+  proname => 'tstzmultirange', proisstrict => 't',
+  prorettype => 'tstzmultirange', proargtypes => 'tstzrange',
+  prosrc => 'multirange_constructor1' },
+{ oid => '8052', descr => 'tstzmultirange constructor',
+  proname => 'tstzmultirange', provariadic => 'tstzrange', proisstrict => 'f',
+  prorettype => 'tstzmultirange', proargtypes => '_tstzrange',
+  proallargtypes => '{_tstzrange}', proargmodes => '{v}',
+  prosrc => 'multirange_constructor2' },
+{ oid => '8053', descr => 'datemultirange constructor',
+  proname => 'datemultirange', proisstrict => 'f',
+  prorettype => 'datemultirange', proargtypes => '',
+  prosrc => 'multirange_constructor0' },
+{ oid => '8150', descr => 'datemultirange constructor',
+  proname => 'datemultirange', proisstrict => 't',
+  prorettype => 'datemultirange', proargtypes => 'daterange',
+  prosrc => 'multirange_constructor1' },
+{ oid => '8054', descr => 'datemultirange constructor',
+  proname => 'datemultirange', provariadic => 'daterange', proisstrict => 'f',
+  prorettype => 'datemultirange', proargtypes => '_daterange',
+  proallargtypes => '{_daterange}', proargmodes => '{v}',
+  prosrc => 'multirange_constructor2' },
+{ oid => '8055', descr => 'int8multirange constructor',
+  proname => 'int8multirange', proisstrict => 'f',
+  prorettype => 'int8multirange', proargtypes => '',
+  prosrc => 'multirange_constructor0' },
+{ oid => '8151', descr => 'int8multirange constructor',
+  proname => 'int8multirange', proisstrict => 't',
+  prorettype => 'int8multirange', proargtypes => 'int8range',
+  prosrc => 'multirange_constructor1' },
+{ oid => '8056', descr => 'int8multirange constructor',
+  proname => 'int8multirange', provariadic => 'int8range', proisstrict => 'f',
+  prorettype => 'int8multirange', proargtypes => '_int8range',
+  proallargtypes => '{_int8range}', proargmodes => '{v}',
+  prosrc => 'multirange_constructor2' },
+{ oid => '8152', descr => 'anymultirange cast',
+  proname => 'anymultirange', proisstrict => 't',
+  prorettype => 'anymultirange', proargtypes => 'anyrange',
+  prosrc => 'multirange_constructor1' },
+{ oid => '8130', descr => 'aggregate transition function',
+  proname => 'range_agg_transfn', proisstrict => 'f', prorettype => 'internal',
+  proargtypes => 'internal anyrange', prosrc => 'range_agg_transfn' },
+{ oid => '8131', descr => 'aggregate final function',
+  proname => 'range_agg_finalfn', proisstrict => 'f', prorettype => 'anymultirange',
+  proargtypes => 'internal anyrange', prosrc => 'range_agg_finalfn' },
+{ oid => '8132', descr => 'combine aggregate input into a multirange',
+  proname => 'range_agg', prokind => 'a', proisstrict => 'f',
+  prorettype => 'anymultirange', proargtypes => 'anyrange',
+  prosrc => 'aggregate_dummy' },
+{ oid => '8135', descr => 'range aggregate by intersecting',
+  proname => 'multirange_intersect_agg_transfn', prorettype => 'anymultirange',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_intersect_agg_transfn'},
+{ oid => '8136', descr => 'range aggregate by intersecting',
+  proname => 'range_intersect_agg', prokind => 'a', proisstrict => 'f',
+  prorettype => 'anymultirange', proargtypes => 'anymultirange',
+  prosrc => 'aggregate_dummy'},
+
 # date, time, timestamp constructors
 { oid => '3846', descr => 'construct date',
   proname => 'make_date', prorettype => 'date', proargtypes => 'int4 int4 int4',
@@ -10123,6 +10386,14 @@
   proname => 'binary_upgrade_set_next_array_pg_type_oid', provolatile => 'v',
   proparallel => 'r', prorettype => 'void', proargtypes => 'oid',
   prosrc => 'binary_upgrade_set_next_array_pg_type_oid' },
+{ oid => '8144', descr => 'for use by pg_upgrade',
+  proname => 'binary_upgrade_set_next_multirange_pg_type_oid', provolatile => 'v',
+  proparallel => 'r', prorettype => 'void', proargtypes => 'oid',
+  prosrc => 'binary_upgrade_set_next_multirange_pg_type_oid' },
+{ oid => '8145', descr => 'for use by pg_upgrade',
+  proname => 'binary_upgrade_set_next_multirange_array_pg_type_oid', provolatile => 'v',
+  proparallel => 'r', prorettype => 'void', proargtypes => 'oid',
+  prosrc => 'binary_upgrade_set_next_multirange_array_pg_type_oid' },
 { oid => '3585', descr => 'for use by pg_upgrade',
   proname => 'binary_upgrade_set_next_toast_pg_type_oid', provolatile => 'v',
   proparallel => 'r', prorettype => 'void', proargtypes => 'oid',
diff --git a/src/include/catalog/pg_range.dat b/src/include/catalog/pg_range.dat
index 479754c245..10060255c9 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',
+  rngmultitypid => 'int4multirange' },
 { rngtypid => 'numrange', rngsubtype => 'numeric',
   rngsubopc => 'btree/numeric_ops', rngcanonical => '-',
-  rngsubdiff => 'numrange_subdiff' },
+  rngsubdiff => 'numrange_subdiff', rngmultitypid => 'nummultirange' },
 { rngtypid => 'tsrange', rngsubtype => 'timestamp',
   rngsubopc => 'btree/timestamp_ops', rngcanonical => '-',
-  rngsubdiff => 'tsrange_subdiff' },
+  rngsubdiff => 'tsrange_subdiff', rngmultitypid => 'tsmultirange' },
 { rngtypid => 'tstzrange', rngsubtype => 'timestamptz',
   rngsubopc => 'btree/timestamptz_ops', rngcanonical => '-',
-  rngsubdiff => 'tstzrange_subdiff' },
+  rngsubdiff => 'tstzrange_subdiff', rngmultitypid => 'tstzmultirange' },
 { rngtypid => 'daterange', rngsubtype => 'date', rngsubopc => 'btree/date_ops',
-  rngcanonical => 'daterange_canonical', rngsubdiff => 'daterange_subdiff' },
+  rngcanonical => 'daterange_canonical', rngsubdiff => 'daterange_subdiff',
+  rngmultitypid => 'datemultirange' },
 { rngtypid => 'int8range', rngsubtype => 'int8', rngsubopc => 'btree/int8_ops',
-  rngcanonical => 'int8range_canonical', rngsubdiff => 'int8range_subdiff' },
+  rngcanonical => 'int8range_canonical', rngsubdiff => 'int8range_subdiff',
+  rngmultitypid => 'int8multirange' },
 
 ]
diff --git a/src/include/catalog/pg_range.h b/src/include/catalog/pg_range.h
index 98172bb1b6..3efd463c6d 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			rngmultitypid 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 fe2c4eabb4..59bae8232f 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 e1a5ab3df3..600ac58c9d 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/commands/typecmds.h b/src/include/commands/typecmds.h
index fc18d64347..7d0a4a9ea5 100644
--- a/src/include/commands/typecmds.h
+++ b/src/include/commands/typecmds.h
@@ -29,6 +29,8 @@ extern ObjectAddress DefineRange(CreateRangeStmt *stmt);
 extern ObjectAddress AlterEnum(AlterEnumStmt *stmt);
 extern ObjectAddress DefineCompositeType(RangeVar *typevar, List *coldeflist);
 extern Oid	AssignTypeArrayOid(void);
+extern Oid	AssignTypeMultirangeOid(void);
+extern Oid	AssignTypeMultirangeArrayOid(void);
 
 extern ObjectAddress AlterDomainDefault(List *names, Node *defaultRaw);
 extern ObjectAddress AlterDomainNotNull(List *names, bool notNull);
diff --git a/src/include/utils/lsyscache.h b/src/include/utils/lsyscache.h
index a216d6793d..b5ff168160 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..8779bd1f6e
--- /dev/null
+++ b/src/include/utils/multirangetypes.h
@@ -0,0 +1,103 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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);
+extern bool multirange_contains_elem_internal(TypeCacheEntry *typcache, MultirangeType * mr,
+											  Datum elem);
+extern bool multirange_contains_range_internal(TypeCacheEntry *typcache, MultirangeType * mr,
+											   RangeType *r);
+extern bool multirange_contains_multirange_internal(TypeCacheEntry *typcache,
+													MultirangeType * mr1,
+													MultirangeType * mr2);
+extern bool range_overlaps_multirange_internal(TypeCacheEntry *typcache, RangeType *r,
+											   MultirangeType * mr);
+extern bool multirange_overlaps_multirange_internal(TypeCacheEntry *typcache,
+													MultirangeType * mr1,
+													MultirangeType * mr2);
+extern bool range_before_multirange_internal(TypeCacheEntry *typcache, RangeType *r,
+											 MultirangeType * mr);
+extern bool range_after_multirange_internal(TypeCacheEntry *typcache, RangeType *r,
+											MultirangeType * mr);
+extern bool multirange_before_multirange_internal(TypeCacheEntry *typcache,
+												  MultirangeType * mr1,
+												  MultirangeType * mr2);
+extern MultirangeType * multirange_minus_internal(Oid mltrngtypoid,
+												  TypeCacheEntry *rangetyp,
+												  int32 range_count1,
+												  RangeType **ranges1,
+												  int32 range_count2,
+												  RangeType **ranges2);
+extern MultirangeType * multirange_intersect_internal(Oid mltrngtypoid,
+													  TypeCacheEntry *rangetyp,
+													  int32 range_count1,
+													  RangeType **ranges1,
+													  int32 range_count2,
+													  RangeType **ranges2);
+
+/* 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 0cbbf09033..204aee4054 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)
 
@@ -92,13 +94,21 @@ typedef struct
  * prototypes for functions defined in rangetypes.c
  */
 
-extern bool range_contains_elem_internal(TypeCacheEntry *typcache, const RangeType *r, Datum val);
-
 /* internal versions of the above */
+extern int	range_cmp_internal(TypeCacheEntry *typcache, const RangeType *r1,
+							   const RangeType *r2);
+extern uint32 hash_range_internal(TypeCacheEntry *typcache, const RangeType *r);
+extern uint64 hash_range_extended_internal(TypeCacheEntry *typcache, const RangeType *r,
+										   Datum seed);
+extern Datum range_lower_internal(TypeCacheEntry *typcache, const RangeType *r1,
+								  bool *isnull);
+extern Datum range_upper_internal(TypeCacheEntry *typcache, const RangeType *r1,
+								  bool *isnull);
 extern bool range_eq_internal(TypeCacheEntry *typcache, const RangeType *r1,
 							  const RangeType *r2);
 extern bool range_ne_internal(TypeCacheEntry *typcache, const RangeType *r1,
 							  const RangeType *r2);
+extern bool range_contains_elem_internal(TypeCacheEntry *typcache, const RangeType *r, Datum val);
 extern bool range_contains_internal(TypeCacheEntry *typcache, const RangeType *r1,
 									const RangeType *r2);
 extern bool range_contained_by_internal(TypeCacheEntry *typcache, const RangeType *r1,
@@ -115,6 +125,12 @@ extern bool range_overleft_internal(TypeCacheEntry *typcache, const RangeType *r
 									const RangeType *r2);
 extern bool range_overright_internal(TypeCacheEntry *typcache, const RangeType *r1,
 									 const RangeType *r2);
+extern RangeType *range_union_internal(TypeCacheEntry *typcache, RangeType *r1,
+									   RangeType *r2, bool strict);
+extern RangeType *range_minus_internal(TypeCacheEntry *typcache, RangeType *r1,
+									   RangeType *r2);
+extern RangeType *range_intersect_internal(TypeCacheEntry *typcache, const RangeType *r1,
+										   const RangeType *r2);
 
 /* assorted support functions */
 extern TypeCacheEntry *range_get_typcache(FunctionCallInfo fcinfo,
@@ -125,6 +141,7 @@ extern void range_deserialize(TypeCacheEntry *typcache, const RangeType *range,
 							  RangeBound *lower, RangeBound *upper,
 							  bool *empty);
 extern char range_get_flags(const RangeType *range);
+extern bool range_has_flag(const RangeType *range, char flag);
 extern void range_set_contain_empty(RangeType *range);
 extern RangeType *make_range(TypeCacheEntry *typcache, RangeBound *lower,
 							 RangeBound *upper, bool empty);
@@ -132,8 +149,12 @@ extern int range_cmp_bounds(TypeCacheEntry *typcache, const RangeBound *b1,
 							const RangeBound *b2);
 extern int range_cmp_bound_values(TypeCacheEntry *typcache, const RangeBound *b1,
 								  const RangeBound *b2);
-extern bool bounds_adjacent(TypeCacheEntry *typcache, RangeBound bound1,
-							RangeBound bound2);
+extern int range_compare(const void *key1, const void *key2, void *arg);
+extern bool bounds_adjacent(TypeCacheEntry *typcache, const RangeBound bound1,
+							const RangeBound bound2);
 extern RangeType *make_empty_range(TypeCacheEntry *typcache);
+extern bool range_split_internal(TypeCacheEntry *typcache, const RangeType *r1,
+								 const RangeType *r2, RangeType **output1,
+								 RangeType **output2);
 
 #endif							/* RANGETYPES_H */
diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h
index f27b73d76d..bc51bbeb54 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 66ff17dbd5..fe240ea004 100644
--- a/src/include/utils/typcache.h
+++ b/src/include/utils/typcache.h
@@ -99,6 +99,11 @@ typedef struct TypeCacheEntry
 	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 66bf9f1211..e277329931 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/dependency.out b/src/test/regress/expected/dependency.out
index b4a11b8aa9..778699a961 100644
--- a/src/test/regress/expected/dependency.out
+++ b/src/test/regress/expected/dependency.out
@@ -141,6 +141,7 @@ owner of table deptest
 owner of function deptest_func()
 owner of type deptest_enum
 owner of type deptest_range
+owner of type deptest_multirange
 owner of table deptest2
 owner of sequence ss1
 owner of type deptest_t
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..4446f8e4c3
--- /dev/null
+++ b/src/test/regress/expected/multirangetypes.out
@@ -0,0 +1,2386 @@
+-- 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)
+
+--
+-- test casts, both a built-in range type and a user-defined one:
+--
+select 'empty'::int4range::int4multirange;
+ int4multirange 
+----------------
+ {}
+(1 row)
+
+select int4range(1, 3)::int4multirange;
+ int4range 
+-----------
+ {[1,3)}
+(1 row)
+
+select int4range(1, null)::int4multirange;
+ int4range 
+-----------
+ {[1,)}
+(1 row)
+
+select int4range(null, null)::int4multirange;
+ int4range 
+-----------
+ {(,)}
+(1 row)
+
+select 'empty'::textrange::textmultirange;
+ textmultirange 
+----------------
+ {}
+(1 row)
+
+select textrange('a', 'c')::textmultirange;
+ textrange 
+-----------
+ {[a,c)}
+(1 row)
+
+select textrange('a', null)::textmultirange;
+ textrange 
+-----------
+ {[a,)}
+(1 row)
+
+select textrange(null, null)::textmultirange;
+ textrange 
+-----------
+ {(,)}
+(1 row)
+
+--
+-- create some test data and test the operators
+--
+CREATE TABLE nummultirange_test (nmr NUMMULTIRANGE);
+CREATE INDEX nummultirange_test_btree ON nummultirange_test(nmr);
+INSERT INTO nummultirange_test VALUES('{}');
+INSERT INTO nummultirange_test VALUES('{[,)}');
+INSERT INTO nummultirange_test VALUES('{[3,]}');
+INSERT INTO nummultirange_test VALUES('{[,), [3,]}');
+INSERT INTO nummultirange_test VALUES('{[, 5)}');
+INSERT INTO nummultirange_test VALUES(nummultirange());
+INSERT INTO nummultirange_test VALUES(nummultirange(variadic '{}'::numrange[]));
+INSERT INTO nummultirange_test VALUES(nummultirange(numrange(1.1, 2.2)));
+INSERT INTO nummultirange_test VALUES('{empty}');
+INSERT INTO nummultirange_test VALUES(nummultirange(numrange(1.7, 1.7, '[]'), numrange(1.7, 1.9)));
+INSERT INTO nummultirange_test VALUES(nummultirange(numrange(1.7, 1.7, '[]'), numrange(1.9, 2.1)));
+SELECT nmr, isempty(nmr), lower(nmr), upper(nmr) FROM nummultirange_test ORDER BY nmr;
+          nmr          | isempty | lower | upper 
+-----------------------+---------+-------+-------
+ {}                    | t       |       |      
+ {}                    | t       |       |      
+ {}                    | t       |       |      
+ {}                    | t       |       |      
+ {(,5)}                | f       |       |     5
+ {(,)}                 | f       |       |      
+ {(,)}                 | f       |       |      
+ {[1.1,2.2)}           | f       |   1.1 |   2.2
+ {[1.7,1.7],[1.9,2.1)} | f       |   1.7 |   2.1
+ {[1.7,1.9)}           | f       |   1.7 |   1.9
+ {[3,)}                | f       |     3 |      
+(11 rows)
+
+SELECT nmr, lower_inc(nmr), lower_inf(nmr), upper_inc(nmr), upper_inf(nmr) FROM nummultirange_test ORDER BY nmr;
+          nmr          | lower_inc | lower_inf | upper_inc | upper_inf 
+-----------------------+-----------+-----------+-----------+-----------
+ {}                    | f         | f         | f         | f
+ {}                    | f         | f         | f         | f
+ {}                    | f         | f         | f         | f
+ {}                    | f         | f         | f         | f
+ {(,5)}                | f         | t         | f         | f
+ {(,)}                 | f         | t         | f         | t
+ {(,)}                 | f         | t         | f         | t
+ {[1.1,2.2)}           | t         | f         | f         | f
+ {[1.7,1.7],[1.9,2.1)} | t         | f         | f         | f
+ {[1.7,1.9)}           | t         | f         | f         | f
+ {[3,)}                | t         | f         | f         | t
+(11 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr = '{}';
+ nmr 
+-----
+ {}
+ {}
+ {}
+ {}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr = '{(,5)}';
+  nmr   
+--------
+ {(,5)}
+(1 row)
+
+SELECT * FROM nummultirange_test WHERE nmr = '{[3,)}';
+  nmr   
+--------
+ {[3,)}
+(1 row)
+
+SELECT * FROM nummultirange_test WHERE nmr = '{[1.7,1.7]}';
+ nmr 
+-----
+(0 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr = '{[1.7,1.7],[1.9,2.1)}';
+          nmr          
+-----------------------
+ {[1.7,1.7],[1.9,2.1)}
+(1 row)
+
+SELECT * FROM nummultirange_test WHERE nmr < '{}';
+ nmr 
+-----
+(0 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr < '{[-1000.0, -1000.0]}';
+  nmr   
+--------
+ {}
+ {(,)}
+ {(,)}
+ {(,5)}
+ {}
+ {}
+ {}
+(7 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr < '{[0.0, 1.0]}';
+  nmr   
+--------
+ {}
+ {(,)}
+ {(,)}
+ {(,5)}
+ {}
+ {}
+ {}
+(7 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr < '{[1000.0, 1001.0]}';
+          nmr          
+-----------------------
+ {}
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+ {}
+ {}
+ {[1.1,2.2)}
+ {}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(11 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr <= '{}';
+ nmr 
+-----
+ {}
+ {}
+ {}
+ {}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr <= '{[3,)}';
+          nmr          
+-----------------------
+ {}
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+ {}
+ {}
+ {[1.1,2.2)}
+ {}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(11 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr >= '{}';
+          nmr          
+-----------------------
+ {}
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+ {}
+ {}
+ {[1.1,2.2)}
+ {}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(11 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr >= '{[3,)}';
+  nmr   
+--------
+ {[3,)}
+(1 row)
+
+SELECT * FROM nummultirange_test WHERE nmr > '{}';
+          nmr          
+-----------------------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+ {[1.1,2.2)}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(7 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr > '{[-1000.0, -1000.0]}';
+          nmr          
+-----------------------
+ {[3,)}
+ {[1.1,2.2)}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr > '{[0.0, 1.0]}';
+          nmr          
+-----------------------
+ {[3,)}
+ {[1.1,2.2)}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr > '{[1000.0, 1001.0]}';
+ nmr 
+-----
+(0 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr <> '{}';
+          nmr          
+-----------------------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+ {[1.1,2.2)}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(7 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr <> '{(,5)}';
+          nmr          
+-----------------------
+ {}
+ {(,)}
+ {[3,)}
+ {(,)}
+ {}
+ {}
+ {[1.1,2.2)}
+ {}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(10 rows)
+
+select nummultirange(numrange(2.0, 1.0));
+ERROR:  range lower bound must be less than or equal to range upper bound
+select nummultirange(numrange(5.0, 6.0), numrange(1.0, 2.0));
+     nummultirange     
+-----------------------
+ {[1.0,2.0),[5.0,6.0)}
+(1 row)
+
+-- overlaps
+SELECT * FROM nummultirange_test WHERE range_overlaps_multirange(numrange(4.0, 4.2), nmr);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE numrange(4.0, 4.2) && nmr;
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE multirange_overlaps_range(nmr, numrange(4.0, 4.2));
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr && numrange(4.0, 4.2);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE multirange_overlaps_multirange(nmr, nummultirange(numrange(4.0, 4.2), numrange(6.0, 7.0)));
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr && nummultirange(numrange(4.0, 4.2), numrange(6.0, 7.0));
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr && nummultirange(numrange(6.0, 7.0));
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+(3 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr && nummultirange(numrange(6.0, 7.0), numrange(8.0, 9.0));
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+(3 rows)
+
+-- mr contains x
+SELECT * FROM nummultirange_test WHERE multirange_contains_elem(nmr, 4.0);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr @> 4.0;
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE multirange_contains_range(nmr, numrange(4.0, 4.2));
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr @> numrange(4.0, 4.2);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE multirange_contains_multirange(nmr, '{[4.0,4.2), [6.0, 8.0)}');
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+(3 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr @> '{[4.0,4.2), [6.0, 8.0)}'::nummultirange;
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+(3 rows)
+
+-- x is contained by mr
+SELECT * FROM nummultirange_test WHERE elem_contained_by_multirange(4.0, nmr);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE 4.0 <@ nmr;
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE range_contained_by_multirange(numrange(4.0, 4.2), nmr);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE numrange(4.0, 4.2) <@ nmr;
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE multirange_contained_by_multirange('{[4.0,4.2), [6.0, 8.0)}', nmr);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+(3 rows)
+
+SELECT * FROM nummultirange_test WHERE '{[4.0,4.2), [6.0, 8.0)}'::nummultirange <@ nmr;
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+(3 rows)
+
+-- overlaps
+SELECT 'empty'::numrange && nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT 'empty'::numrange && nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() && 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) && 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() && nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() && nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) && nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) && nummultirange(numrange(1,2), numrange(7,8));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(7,8)) && nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) && nummultirange(numrange(1,2), numrange(3.5,8));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(3.5,8)) && numrange(3,4);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(3.5,8)) && nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+-- contains
+SELECT nummultirange() @> nummultirange();
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange() @> 'empty'::numrange;
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(null,null)) @> numrange(1,2);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(null,null)) @> numrange(null,2);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(null,null)) @> numrange(2,null);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(null,5)) @> numrange(null,3);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(null,5)) @> numrange(null,8);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(5,null)) @> numrange(8,null);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(5,null)) @> numrange(3,null);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,5)) @> numrange(8,9);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,5)) @> numrange(3,9);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,5)) @> numrange(1,4);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,5)) @> numrange(1,5);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(-4,-2), numrange(1,5)) @> numrange(1,5);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,5), numrange(8,9)) @> numrange(1,5);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,5), numrange(8,9)) @> numrange(6,7);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,5), numrange(6,9)) @> numrange(6,7);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[1,5)}'::nummultirange @> '{[1,5)}';
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[-4,-2), [1,5)}'::nummultirange @> '{[1,5)}';
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[1,5), [8,9)}'::nummultirange @> '{[1,5)}';
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[1,5), [8,9)}'::nummultirange @> '{[6,7)}';
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT '{[1,5), [6,9)}'::nummultirange @> '{[6,7)}';
+ ?column? 
+----------
+ t
+(1 row)
+
+-- is contained by
+SELECT nummultirange() <@ nummultirange();
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT 'empty'::numrange <@ nummultirange();
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,2) <@ nummultirange(numrange(null,null));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(null,2) <@ nummultirange(numrange(null,null));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(2,null) <@ nummultirange(numrange(null,null));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(null,3) <@ nummultirange(numrange(null,5));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(null,8) <@ nummultirange(numrange(null,5));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(8,null) <@ nummultirange(numrange(5,null));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(3,null) <@ nummultirange(numrange(5,null));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(8,9) <@ nummultirange(numrange(1,5));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(3,9) <@ nummultirange(numrange(1,5));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(1,4) <@ nummultirange(numrange(1,5));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,5) <@ nummultirange(numrange(1,5));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,5) <@ nummultirange(numrange(-4,-2), numrange(1,5));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,5) <@ nummultirange(numrange(1,5), numrange(8,9));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(6,7) <@ nummultirange(numrange(1,5), numrange(8,9));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(6,7) <@ nummultirange(numrange(1,5), numrange(6,9));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[1,5)}' <@ '{[1,5)}'::nummultirange;
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[1,5)}' <@ '{[-4,-2), [1,5)}'::nummultirange;
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[1,5)}' <@ '{[1,5), [8,9)}'::nummultirange;
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[6,7)}' <@ '{[1,5), [8,9)}'::nummultirange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT '{[6,7)}' <@ '{[1,5), [6,9)}'::nummultirange;
+ ?column? 
+----------
+ t
+(1 row)
+
+-- overleft
+SELECT 'empty'::numrange &< nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT 'empty'::numrange &< nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() &< 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) &< 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() &< nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) &< nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() &< nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(6,7) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(1,2) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,4) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,6) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(3.5,6) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(6,7)) &< numrange(3,4);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) &< numrange(3,4);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,4)) &< numrange(3,4);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,6)) &< numrange(3,4);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3.5,6)) &< numrange(3,4);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(6,7)) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,4)) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,6)) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3.5,6)) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+-- overright
+SELECT nummultirange() &> 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) &> 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT 'empty'::numrange &> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT 'empty'::numrange &> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() &> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() &> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) &> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> numrange(6,7);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> numrange(1,2);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> numrange(1,4);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> numrange(1,6);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> numrange(3.5,6);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(3,4) &> nummultirange(numrange(6,7));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(3,4) &> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(3,4) &> nummultirange(numrange(1,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(3,4) &> nummultirange(numrange(1,6));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(3,4) &> nummultirange(numrange(3.5,6));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(6,7));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(1,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(1,6));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(3.5,6));
+ ?column? 
+----------
+ f
+(1 row)
+
+-- meets
+SELECT 'empty'::numrange -|- nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT 'empty'::numrange -|- nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() -|- 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() -|- nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() -|- nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(1,2) -|- nummultirange(numrange(2,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,2) -|- nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- numrange(2,4);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- numrange(3,4);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- nummultirange(numrange(2,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(5,6)) -|- nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(5,6)) -|- nummultirange(numrange(6,7));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(5,6)) -|- nummultirange(numrange(8,9));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- nummultirange(numrange(2,4), numrange(6,7));
+ ?column? 
+----------
+ t
+(1 row)
+
+-- strictly left
+select 'empty'::numrange << nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(1,2) << nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(1,2) << nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+select numrange(1,2) << nummultirange(numrange(0,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(1,2) << nummultirange(numrange(0,4), numrange(7,8));
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange() << 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange() << numrange(1,2);
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(3,4)) << numrange(3,6);
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(0,2)) << numrange(3,6);
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange(numrange(0,2), numrange(7,8)) << numrange(3,6);
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(-4,-2), numrange(0,2)) << numrange(3,6);
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange() << nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange() << nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(1,2)) << nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(1,2)) << nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(1,2)) << nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange(numrange(1,2)) << nummultirange(numrange(3,4), numrange(7,8));
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange(numrange(1,2), numrange(4,5)) << nummultirange(numrange(3,4), numrange(7,8));
+ ?column? 
+----------
+ f
+(1 row)
+
+-- strictly right
+select nummultirange() >> 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange() >> numrange(1,2);
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(3,4)) >> numrange(1,2);
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange(numrange(0,4)) >> numrange(1,2);
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(0,4), numrange(7,8)) >> numrange(1,2);
+ ?column? 
+----------
+ f
+(1 row)
+
+select 'empty'::numrange >> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(1,2) >> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(3,6) >> nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(3,6) >> nummultirange(numrange(0,2));
+ ?column? 
+----------
+ t
+(1 row)
+
+select numrange(3,6) >> nummultirange(numrange(0,2), numrange(7,8));
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(3,6) >> nummultirange(numrange(-4,-2), numrange(0,2));
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange() >> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(1,2)) >> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange() >> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(1,2)) >> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(3,4)) >> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange(numrange(3,4), numrange(7,8)) >> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange(numrange(3,4), numrange(7,8)) >> nummultirange(numrange(1,2), numrange(4,5));
+ ?column? 
+----------
+ f
+(1 row)
+
+-- union
+SELECT nummultirange() + nummultirange();
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange() + nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) + nummultirange();
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) + nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) + nummultirange(numrange(2,4));
+ ?column? 
+----------
+ {[1,4)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) + nummultirange(numrange(3,4));
+   ?column?    
+---------------
+ {[1,2),[3,4)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) + nummultirange(numrange(2,4));
+ ?column? 
+----------
+ {[1,5)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) + nummultirange(numrange(3,4));
+   ?column?    
+---------------
+ {[1,2),[3,5)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) + nummultirange(numrange(0,9));
+ ?column? 
+----------
+ {[0,9)}
+(1 row)
+
+-- merge
+SELECT range_merge(nummultirange());
+ range_merge 
+-------------
+ empty
+(1 row)
+
+SELECT range_merge(nummultirange(numrange(1,2)));
+ range_merge 
+-------------
+ [1,2)
+(1 row)
+
+SELECT range_merge(nummultirange(numrange(1,2), numrange(7,8)));
+ range_merge 
+-------------
+ [1,8)
+(1 row)
+
+-- minus
+SELECT nummultirange() - nummultirange();
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange() - nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) - nummultirange();
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(3,4)) - nummultirange();
+   ?column?    
+---------------
+ {[1,2),[3,4)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) - nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) - nummultirange(numrange(2,4));
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) - nummultirange(numrange(3,4));
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {[2,4)}
+(1 row)
+
+SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(2,3));
+   ?column?    
+---------------
+ {[1,2),[3,4)}
+(1 row)
+
+SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(0,8));
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(0,2));
+ ?column? 
+----------
+ {[2,4)}
+(1 row)
+
+SELECT nummultirange(numrange(1,8)) - nummultirange(numrange(0,2), numrange(3,4));
+   ?column?    
+---------------
+ {[2,3),[4,8)}
+(1 row)
+
+SELECT nummultirange(numrange(1,8)) - nummultirange(numrange(2,3), numrange(5,null));
+   ?column?    
+---------------
+ {[1,2),[3,5)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(-2,0));
+   ?column?    
+---------------
+ {[1,2),[4,5)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(2,4));
+   ?column?    
+---------------
+ {[1,2),[4,5)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(3,5));
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(0,9));
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange(numrange(1,3), numrange(4,5)) - nummultirange(numrange(2,9));
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(8,9));
+   ?column?    
+---------------
+ {[1,2),[4,5)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(-2,0), numrange(8,9));
+   ?column?    
+---------------
+ {[1,2),[4,5)}
+(1 row)
+
+-- intersection
+SELECT nummultirange() * nummultirange();
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange() * nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) * nummultirange();
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT '{[1,3)}'::nummultirange * '{[1,5)}'::nummultirange;
+ ?column? 
+----------
+ {[1,3)}
+(1 row)
+
+SELECT '{[1,3)}'::nummultirange * '{[0,5)}'::nummultirange;
+ ?column? 
+----------
+ {[1,3)}
+(1 row)
+
+SELECT '{[1,3)}'::nummultirange * '{[0,2)}'::nummultirange;
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT '{[1,3)}'::nummultirange * '{[2,5)}'::nummultirange;
+ ?column? 
+----------
+ {[2,3)}
+(1 row)
+
+SELECT '{[1,4)}'::nummultirange * '{[2,3)}'::nummultirange;
+ ?column? 
+----------
+ {[2,3)}
+(1 row)
+
+SELECT '{[1,4)}'::nummultirange * '{[0,2), [3,5)}'::nummultirange;
+   ?column?    
+---------------
+ {[1,2),[3,4)}
+(1 row)
+
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[0,8), [9,12)}'::nummultirange;
+       ?column?       
+----------------------
+ {[1,4),[7,8),[9,10)}
+(1 row)
+
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[9,12)}'::nummultirange;
+ ?column? 
+----------
+ {[9,10)}
+(1 row)
+
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[-5,-4), [5,6), [9,12)}'::nummultirange;
+ ?column? 
+----------
+ {[9,10)}
+(1 row)
+
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[0,2), [3,8), [9,12)}'::nummultirange;
+          ?column?          
+----------------------------
+ {[1,2),[3,4),[7,8),[9,10)}
+(1 row)
+
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[0,2), [3,8), [9,12)}'::nummultirange;
+          ?column?          
+----------------------------
+ {[1,2),[3,4),[7,8),[9,10)}
+(1 row)
+
+--
+-- range_agg function
+--
+create table reservations ( room_id integer not null, booked_during daterange );
+insert into reservations values
+-- 1: has a meets and a gap
+(1, daterange('2018-07-01', '2018-07-07')),
+(1, daterange('2018-07-07', '2018-07-14')),
+(1, daterange('2018-07-20', '2018-07-22')),
+-- 2: just a single row
+(2, daterange('2018-07-01', '2018-07-03')),
+-- 3: one null range
+(3, NULL),
+-- 4: two null ranges
+(4, NULL),
+(4, NULL),
+-- 5: a null range and a non-null range
+(5, NULL),
+(5, daterange('2018-07-01', '2018-07-03')),
+-- 6: has overlap
+(6, daterange('2018-07-01', '2018-07-07')),
+(6, daterange('2018-07-05', '2018-07-10')),
+-- 7: two ranges that meet: no gap or overlap
+(7, daterange('2018-07-01', '2018-07-07')),
+(7, daterange('2018-07-07', '2018-07-14')),
+-- 8: an empty range
+(8, 'empty'::daterange)
+;
+SELECT   room_id, range_agg(booked_during)
+FROM     reservations
+GROUP BY room_id
+ORDER BY room_id;
+ room_id |                     range_agg                     
+---------+---------------------------------------------------
+       1 | {[07-01-2018,07-14-2018),[07-20-2018,07-22-2018)}
+       2 | {[07-01-2018,07-03-2018)}
+       3 | 
+       4 | 
+       5 | {[07-01-2018,07-03-2018)}
+       6 | {[07-01-2018,07-10-2018)}
+       7 | {[07-01-2018,07-14-2018)}
+       8 | {}
+(8 rows)
+
+-- range_agg on a custom range type too
+SELECT  range_agg(r)
+FROM    (VALUES
+          ('[a,c]'::textrange),
+          ('[b,b]'::textrange),
+          ('[c,f]'::textrange),
+          ('[g,h)'::textrange),
+          ('[h,j)'::textrange)
+        ) t(r);
+   range_agg   
+---------------
+ {[a,f],[g,j)}
+(1 row)
+
+select range_intersect_agg(nmr) from nummultirange_test;
+ range_intersect_agg 
+---------------------
+ {}
+(1 row)
+
+select range_intersect_agg(nmr) from nummultirange_test where false;
+ range_intersect_agg 
+---------------------
+ 
+(1 row)
+
+-- test with just one input:
+select range_intersect_agg(nmr) from (values ('{[1,2]}'::nummultirange)) t(nmr);
+ range_intersect_agg 
+---------------------
+ {[1,2]}
+(1 row)
+
+select range_intersect_agg(nmr) from nummultirange_test where nmr @> 4.0;
+ range_intersect_agg 
+---------------------
+ {[3,5)}
+(1 row)
+
+create table nummultirange_test2(nmr nummultirange);
+create index nummultirange_test2_hash_idx on nummultirange_test2 using hash (nmr);
+INSERT INTO nummultirange_test2 VALUES('{[, 5)}');
+INSERT INTO nummultirange_test2 VALUES(nummultirange(numrange(1.1, 2.2)));
+INSERT INTO nummultirange_test2 VALUES(nummultirange(numrange(1.1, 2.2)));
+INSERT INTO nummultirange_test2 VALUES(nummultirange(numrange(1.1, 2.2,'()')));
+INSERT INTO nummultirange_test2 VALUES('{}');
+select * from nummultirange_test2 where nmr = '{}';
+ nmr 
+-----
+ {}
+(1 row)
+
+select * from nummultirange_test2 where nmr = nummultirange(numrange(1.1, 2.2));
+     nmr     
+-------------
+ {[1.1,2.2)}
+ {[1.1,2.2)}
+(2 rows)
+
+select * from nummultirange_test2 where nmr = nummultirange(numrange(1.1, 2.3));
+ nmr 
+-----
+(0 rows)
+
+set enable_nestloop=t;
+set enable_hashjoin=f;
+set enable_mergejoin=f;
+select * from nummultirange_test natural join nummultirange_test2 order by nmr;
+     nmr     
+-------------
+ {}
+ {}
+ {}
+ {}
+ {(,5)}
+ {[1.1,2.2)}
+ {[1.1,2.2)}
+(7 rows)
+
+set enable_nestloop=f;
+set enable_hashjoin=t;
+set enable_mergejoin=f;
+select * from nummultirange_test natural join nummultirange_test2 order by nmr;
+     nmr     
+-------------
+ {}
+ {}
+ {}
+ {}
+ {(,5)}
+ {[1.1,2.2)}
+ {[1.1,2.2)}
+(7 rows)
+
+set enable_nestloop=f;
+set enable_hashjoin=f;
+set enable_mergejoin=t;
+select * from nummultirange_test natural join nummultirange_test2 order by nmr;
+     nmr     
+-------------
+ {}
+ {}
+ {}
+ {}
+ {(,5)}
+ {[1.1,2.2)}
+ {[1.1,2.2)}
+(7 rows)
+
+set enable_nestloop to default;
+set enable_hashjoin to default;
+set enable_mergejoin to default;
+DROP TABLE nummultirange_test2;
+--
+-- Test user-defined multirange of floats
+--
+select '{[123.001, 5.e9)}'::float8multirange @> 888.882::float8;
+ ?column? 
+----------
+ t
+(1 row)
+
+create table float8multirange_test(f8mr float8multirange, i int);
+insert into float8multirange_test values(float8multirange(float8range(-100.00007, '1.111113e9')), 42);
+select * from float8multirange_test;
+           f8mr            | i  
+---------------------------+----
+ {[-100.00007,1111113000)} | 42
+(1 row)
+
+drop table float8multirange_test;
+--
+-- Test multirange types over domains
+--
+create domain mydomain as int4;
+create type mydomainrange as range(subtype=mydomain);
+select '{[4,50)}'::mydomainmultirange @> 7::mydomain;
+ ?column? 
+----------
+ t
+(1 row)
+
+drop domain mydomain cascade;
+NOTICE:  drop cascades to type mydomainrange
+--
+-- Test domains over multirange types
+--
+create domain restrictedmultirange as int4multirange check (upper(value) < 10);
+select '{[4,5)}'::restrictedmultirange @> 7;
+ ?column? 
+----------
+ f
+(1 row)
+
+select '{[4,50)}'::restrictedmultirange @> 7; -- should fail
+ERROR:  value for domain restrictedmultirange violates check constraint "restrictedmultirange_check"
+drop domain restrictedmultirange;
+--
+-- Test multiple multirange types over the same subtype
+--
+create type textrange1 as range(subtype=text, collation="C");
+create type textrange2 as range(subtype=text, collation="C");
+select textmultirange1(textrange2('a','Z'));  -- should fail
+ERROR:  function textmultirange1(textrange2) does not exist
+LINE 1: select textmultirange1(textrange2('a','Z'));
+               ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+select textmultirange1(textrange1('a','Z')) @> 'b'::text;
+ERROR:  range lower bound must be less than or equal to range upper bound
+select textmultirange2(textrange2('a','z')) @> 'b'::text;
+ ?column? 
+----------
+ t
+(1 row)
+
+drop type textrange1;
+drop type textrange2;
+--
+-- Test polymorphic type system
+--
+create function anyarray_anymultirange_func(a anyarray, r anymultirange)
+  returns anyelement as 'select $1[1] + lower($2);' language sql;
+select anyarray_anymultirange_func(ARRAY[1,2], int4multirange(int4range(10,20)));
+ anyarray_anymultirange_func 
+-----------------------------
+                          11
+(1 row)
+
+-- should fail
+select anyarray_anymultirange_func(ARRAY[1,2], nummultirange(numrange(10,20)));
+ERROR:  function anyarray_anymultirange_func(integer[], nummultirange) does not exist
+LINE 1: select anyarray_anymultirange_func(ARRAY[1,2], nummultirange...
+               ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+drop function anyarray_anymultirange_func(anyarray, anymultirange);
+-- should fail
+create function bogus_func(anyelement)
+  returns anymultirange as 'select int4multirange(int4range(1,10))' language sql;
+ERROR:  cannot determine result data type
+DETAIL:  A function returning "anyrange" or "anymultirange" must have at least one "anyrange" or "anymultirange" argument.
+-- should fail
+create function bogus_func(int)
+  returns anymultirange as 'select int4multirange(int4range(1,10))' language sql;
+ERROR:  cannot determine result data type
+DETAIL:  A function returning a polymorphic type must have at least one polymorphic argument.
+create function range_add_bounds(anymultirange)
+  returns anyelement as 'select lower($1) + upper($1)' language sql;
+select range_add_bounds(int4multirange(int4range(1, 17)));
+ range_add_bounds 
+------------------
+               18
+(1 row)
+
+select range_add_bounds(nummultirange(numrange(1.0001, 123.123)));
+ range_add_bounds 
+------------------
+         124.1231
+(1 row)
+
+create function multirangetypes_sql(q anymultirange, b anyarray, out c anyelement)
+  as $$ select upper($1) + $2[1] $$
+  language sql;
+select multirangetypes_sql(int4multirange(int4range(1,10)), ARRAY[2,20]);
+ multirangetypes_sql 
+---------------------
+                  12
+(1 row)
+
+select multirangetypes_sql(nummultirange(numrange(1,10)), ARRAY[2,20]);  -- match failure
+ERROR:  function multirangetypes_sql(nummultirange, integer[]) does not exist
+LINE 1: select multirangetypes_sql(nummultirange(numrange(1,10)), AR...
+               ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+--
+-- Arrays of multiranges
+--
+select ARRAY[nummultirange(numrange(1.1, 1.2)), nummultirange(numrange(12.3, 155.5))];
+              array               
+----------------------------------
+ {"{[1.1,1.2)}","{[12.3,155.5)}"}
+(1 row)
+
+create table i8mr_array (f1 int, f2 int8multirange[]);
+insert into i8mr_array values (42, array[int8multirange(int8range(1,10)), int8multirange(int8range(2,20))]);
+select * from i8mr_array;
+ f1 |           f2            
+----+-------------------------
+ 42 | {"{[1,10)}","{[2,20)}"}
+(1 row)
+
+drop table i8mr_array;
+--
+-- Multiranges of arrays
+--
+select arraymultirange(arrayrange(ARRAY[1,2], ARRAY[2,1]));
+   arraymultirange   
+---------------------
+ {["{1,2}","{2,1}")}
+(1 row)
+
+select arraymultirange(arrayrange(ARRAY[2,1], ARRAY[1,2]));  -- fail
+ERROR:  range lower bound must be less than or equal to range upper bound
+select array[1,1] <@ arraymultirange(arrayrange(array[1,2], array[2,1]));
+ ?column? 
+----------
+ f
+(1 row)
+
+select array[1,3] <@ arraymultirange(arrayrange(array[1,2], array[2,1]));
+ ?column? 
+----------
+ t
+(1 row)
+
+--
+-- Ranges of composites
+--
+create type two_ints as (a int, b int);
+create type two_ints_range as range (subtype = two_ints);
+-- with force_parallel_mode on, this exercises tqueue.c's range remapping
+select *, row_to_json(upper(t)) as u from
+  (values (two_ints_multirange(two_ints_range(row(1,2), row(3,4)))),
+          (two_ints_multirange(two_ints_range(row(5,6), row(7,8))))) v(t);
+          t          |       u       
+---------------------+---------------
+ {["(1,2)","(3,4)")} | {"a":3,"b":4}
+ {["(5,6)","(7,8)")} | {"a":7,"b":8}
+(2 rows)
+
+drop type two_ints cascade;
+NOTICE:  drop cascades to type two_ints_range
+--
+-- Check behavior when subtype lacks a hash function
+--
+set enable_sort = off;  -- try to make it pick a hash setop implementation
+select '{(2,5)}'::cashmultirange except select '{(5,6)}'::cashmultirange;
+ cashmultirange  
+-----------------
+ {($2.00,$5.00)}
+(1 row)
+
+reset enable_sort;
+--
+-- OUT/INOUT/TABLE functions
+--
+-- infer anymultirange from anymultirange
+create function mr_outparam_succeed(i anymultirange, out r anymultirange, out t text)
+  as $$ select $1, 'foo'::text $$ language sql;
+select * from mr_outparam_succeed(int4multirange(int4range(1,2)));
+    r    |  t  
+---------+-----
+ {[1,2)} | foo
+(1 row)
+
+-- infer anyarray from anymultirange
+create function mr_outparam_succeed2(i anymultirange, out r anyarray, out t text)
+  as $$ select ARRAY[upper($1)], 'foo'::text $$ language sql;
+select * from mr_outparam_succeed2(int4multirange(int4range(1,2)));
+  r  |  t  
+-----+-----
+ {2} | foo
+(1 row)
+
+-- infer anyrange from anymultirange
+create function mr_outparam_succeed3(i anymultirange, out r anyrange, out t text)
+  as $$ select range_merge($1), 'foo'::text $$ language sql;
+select * from mr_outparam_succeed3(int4multirange(int4range(1,2)));
+   r   |  t  
+-------+-----
+ [1,2) | foo
+(1 row)
+
+-- infer anymultirange from anyrange
+create function mr_outparam_succeed4(i anyrange, out r anymultirange, out t text)
+  as $$ select anymultirange($1), 'foo'::text $$ language sql;
+select * from mr_outparam_succeed4(int4range(1,2));
+    r    |  t  
+---------+-----
+ {[1,2)} | foo
+(1 row)
+
+-- infer anyelement from anymultirange
+create function mr_inoutparam_succeed(out i anyelement, inout r anymultirange)
+  as $$ select upper($1), $1 $$ language sql;
+select * from mr_inoutparam_succeed(int4multirange(int4range(1,2)));
+ i |    r    
+---+---------
+ 2 | {[1,2)}
+(1 row)
+
+-- infer anyelement+anymultirange from anyelement+anymultirange
+create function mr_table_succeed(i anyelement, r anymultirange) returns table(i anyelement, r anymultirange)
+  as $$ select $1, $2 $$ language sql;
+select * from mr_table_succeed(123, int4multirange(int4range(1,11)));
+  i  |    r     
+-----+----------
+ 123 | {[1,11)}
+(1 row)
+
+-- should fail
+create function mr_outparam_fail(i anyelement, out r anymultirange, out t text)
+  as $$ select '[1,10]', 'foo' $$ language sql;
+ERROR:  cannot determine result data type
+DETAIL:  A function returning "anyrange" or "anymultirange" must have at least one "anyrange" or "anymultirange" argument.
+--should fail
+create function mr_inoutparam_fail(inout i anyelement, out r anymultirange)
+  as $$ select $1, '[1,10]' $$ language sql;
+ERROR:  cannot determine result data type
+DETAIL:  A function returning "anyrange" or "anymultirange" must have at least one "anyrange" or "anymultirange" argument.
+--should fail
+create function mr_table_fail(i anyelement) returns table(i anyelement, r anymultirange)
+  as $$ select $1, '[1,10]' $$ language sql;
+ERROR:  cannot determine result data type
+DETAIL:  A function returning "anyrange" or "anymultirange" must have at least one "anyrange" or "anymultirange" argument.
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/rangetypes.out b/src/test/regress/expected/rangetypes.out
index 5ed6ae47ec..4a46a286a1 100644
--- a/src/test/regress/expected/rangetypes.out
+++ b/src/test/regress/expected/rangetypes.out
@@ -518,6 +518,24 @@ select numrange(1.0, 2.0) * numrange(2.5, 3.0);
  empty
 (1 row)
 
+select range_intersect_agg(nr) from numrange_test;
+ range_intersect_agg 
+---------------------
+ empty
+(1 row)
+
+select range_intersect_agg(nr) from numrange_test where false;
+ range_intersect_agg 
+---------------------
+ 
+(1 row)
+
+select range_intersect_agg(nr) from numrange_test where nr @> 4.0;
+ range_intersect_agg 
+---------------------
+ [3,5)
+(1 row)
+
 create table numrange_test2(nr numrange);
 create index numrange_test2_hash_idx on numrange_test2 using hash (nr);
 INSERT INTO numrange_test2 VALUES('[, 5)');
@@ -1284,7 +1302,7 @@ drop function anyarray_anyrange_func(anyarray, anyrange);
 create function bogus_func(anyelement)
   returns anyrange as 'select int4range(1,10)' language sql;
 ERROR:  cannot determine result data type
-DETAIL:  A function returning "anyrange" must have at least one "anyrange" argument.
+DETAIL:  A function returning "anyrange" or "anymultirange" must have at least one "anyrange" or "anymultirange" argument.
 -- should fail
 create function bogus_func(int)
   returns anyrange as 'select int4range(1,10)' language sql;
@@ -1395,6 +1413,7 @@ reset enable_sort;
 --
 -- OUT/INOUT/TABLE functions
 --
+-- infer anyrange from anyrange
 create function outparam_succeed(i anyrange, out r anyrange, out t text)
   as $$ select $1, 'foo'::text $$ language sql;
 select * from outparam_succeed(int4range(1,2));
@@ -1403,6 +1422,16 @@ select * from outparam_succeed(int4range(1,2));
  [1,2) | foo
 (1 row)
 
+-- infer anyarray from anyrange
+create function outparam_succeed2(i anyrange, out r anyarray, out t text)
+  as $$ select ARRAY[upper($1)], 'foo'::text $$ language sql;
+select * from outparam_succeed2(int4range(int4range(1,2)));
+  r  |  t  
+-----+-----
+ {2} | foo
+(1 row)
+
+-- infer anyelement from anyrange
 create function inoutparam_succeed(out i anyelement, inout r anyrange)
   as $$ select upper($1), $1 $$ language sql;
 select * from inoutparam_succeed(int4range(1,2));
@@ -1411,6 +1440,7 @@ select * from inoutparam_succeed(int4range(1,2));
  2 | [1,2)
 (1 row)
 
+-- infer anyelement+anyrange from anyelement+anyrange
 create function table_succeed(i anyelement, r anyrange) returns table(i anyelement, r anyrange)
   as $$ select $1, $2 $$ language sql;
 select * from table_succeed(123, int4range(1,11));
@@ -1423,14 +1453,14 @@ select * from table_succeed(123, int4range(1,11));
 create function outparam_fail(i anyelement, out r anyrange, out t text)
   as $$ select '[1,10]', 'foo' $$ language sql;
 ERROR:  cannot determine result data type
-DETAIL:  A function returning "anyrange" must have at least one "anyrange" argument.
+DETAIL:  A function returning "anyrange" or "anymultirange" must have at least one "anyrange" or "anymultirange" argument.
 --should fail
 create function inoutparam_fail(inout i anyelement, out r anyrange)
   as $$ select $1, '[1,10]' $$ language sql;
 ERROR:  cannot determine result data type
-DETAIL:  A function returning "anyrange" must have at least one "anyrange" argument.
+DETAIL:  A function returning "anyrange" or "anymultirange" must have at least one "anyrange" or "anymultirange" argument.
 --should fail
 create function table_fail(i anyelement) returns table(i anyelement, r anyrange)
   as $$ select $1, '[1,10]' $$ language sql;
 ERROR:  cannot determine result data type
-DETAIL:  A function returning "anyrange" must have at least one "anyrange" argument.
+DETAIL:  A function returning "anyrange" or "anymultirange" must have at least one "anyrange" or "anymultirange" argument.
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index 070de78e85..6f7fcf6326 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -92,6 +92,7 @@ num_exp_sqrt|t
 num_exp_sub|t
 num_input_test|f
 num_result|f
+nummultirange_test|t
 onek|t
 onek2|t
 path_tbl|f
@@ -172,6 +173,7 @@ quad_poly_tbl|t
 radix_text_tbl|t
 ramp|f
 real_city|f
+reservations|f
 road|t
 shighway|t
 slow_emp4000|f
diff --git a/src/test/regress/expected/type_sanity.out b/src/test/regress/expected/type_sanity.out
index cd7fc03b04..60a9b4d22f 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.rngmultitypid
+FROM pg_range p1
+WHERE p1.rngmultitypid IS NULL OR p1.rngmultitypid = 0;
+ rngtypid | rngsubtype | rngmultitypid 
+----------+------------+---------------
+(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..40f4ef35b0
--- /dev/null
+++ b/src/test/regress/sql/multirangetypes.sql
@@ -0,0 +1,625 @@
+-- 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'));
+
+--
+-- test casts, both a built-in range type and a user-defined one:
+--
+select 'empty'::int4range::int4multirange;
+select int4range(1, 3)::int4multirange;
+select int4range(1, null)::int4multirange;
+select int4range(null, null)::int4multirange;
+select 'empty'::textrange::textmultirange;
+select textrange('a', 'c')::textmultirange;
+select textrange('a', null)::textmultirange;
+select textrange(null, null)::textmultirange;
+
+--
+-- create some test data and test the operators
+--
+
+CREATE TABLE nummultirange_test (nmr NUMMULTIRANGE);
+CREATE INDEX nummultirange_test_btree ON nummultirange_test(nmr);
+
+INSERT INTO nummultirange_test VALUES('{}');
+INSERT INTO nummultirange_test VALUES('{[,)}');
+INSERT INTO nummultirange_test VALUES('{[3,]}');
+INSERT INTO nummultirange_test VALUES('{[,), [3,]}');
+INSERT INTO nummultirange_test VALUES('{[, 5)}');
+INSERT INTO nummultirange_test VALUES(nummultirange());
+INSERT INTO nummultirange_test VALUES(nummultirange(variadic '{}'::numrange[]));
+INSERT INTO nummultirange_test VALUES(nummultirange(numrange(1.1, 2.2)));
+INSERT INTO nummultirange_test VALUES('{empty}');
+INSERT INTO nummultirange_test VALUES(nummultirange(numrange(1.7, 1.7, '[]'), numrange(1.7, 1.9)));
+INSERT INTO nummultirange_test VALUES(nummultirange(numrange(1.7, 1.7, '[]'), numrange(1.9, 2.1)));
+
+SELECT nmr, isempty(nmr), lower(nmr), upper(nmr) FROM nummultirange_test ORDER BY nmr;
+SELECT nmr, lower_inc(nmr), lower_inf(nmr), upper_inc(nmr), upper_inf(nmr) FROM nummultirange_test ORDER BY nmr;
+
+SELECT * FROM nummultirange_test WHERE nmr = '{}';
+SELECT * FROM nummultirange_test WHERE nmr = '{(,5)}';
+SELECT * FROM nummultirange_test WHERE nmr = '{[3,)}';
+SELECT * FROM nummultirange_test WHERE nmr = '{[1.7,1.7]}';
+SELECT * FROM nummultirange_test WHERE nmr = '{[1.7,1.7],[1.9,2.1)}';
+SELECT * FROM nummultirange_test WHERE nmr < '{}';
+SELECT * FROM nummultirange_test WHERE nmr < '{[-1000.0, -1000.0]}';
+SELECT * FROM nummultirange_test WHERE nmr < '{[0.0, 1.0]}';
+SELECT * FROM nummultirange_test WHERE nmr < '{[1000.0, 1001.0]}';
+SELECT * FROM nummultirange_test WHERE nmr <= '{}';
+SELECT * FROM nummultirange_test WHERE nmr <= '{[3,)}';
+SELECT * FROM nummultirange_test WHERE nmr >= '{}';
+SELECT * FROM nummultirange_test WHERE nmr >= '{[3,)}';
+SELECT * FROM nummultirange_test WHERE nmr > '{}';
+SELECT * FROM nummultirange_test WHERE nmr > '{[-1000.0, -1000.0]}';
+SELECT * FROM nummultirange_test WHERE nmr > '{[0.0, 1.0]}';
+SELECT * FROM nummultirange_test WHERE nmr > '{[1000.0, 1001.0]}';
+SELECT * FROM nummultirange_test WHERE nmr <> '{}';
+SELECT * FROM nummultirange_test WHERE nmr <> '{(,5)}';
+
+select nummultirange(numrange(2.0, 1.0));
+select nummultirange(numrange(5.0, 6.0), numrange(1.0, 2.0));
+
+-- overlaps
+SELECT * FROM nummultirange_test WHERE range_overlaps_multirange(numrange(4.0, 4.2), nmr);
+SELECT * FROM nummultirange_test WHERE numrange(4.0, 4.2) && nmr;
+SELECT * FROM nummultirange_test WHERE multirange_overlaps_range(nmr, numrange(4.0, 4.2));
+SELECT * FROM nummultirange_test WHERE nmr && numrange(4.0, 4.2);
+SELECT * FROM nummultirange_test WHERE multirange_overlaps_multirange(nmr, nummultirange(numrange(4.0, 4.2), numrange(6.0, 7.0)));
+SELECT * FROM nummultirange_test WHERE nmr && nummultirange(numrange(4.0, 4.2), numrange(6.0, 7.0));
+SELECT * FROM nummultirange_test WHERE nmr && nummultirange(numrange(6.0, 7.0));
+SELECT * FROM nummultirange_test WHERE nmr && nummultirange(numrange(6.0, 7.0), numrange(8.0, 9.0));
+
+-- mr contains x
+SELECT * FROM nummultirange_test WHERE multirange_contains_elem(nmr, 4.0);
+SELECT * FROM nummultirange_test WHERE nmr @> 4.0;
+SELECT * FROM nummultirange_test WHERE multirange_contains_range(nmr, numrange(4.0, 4.2));
+SELECT * FROM nummultirange_test WHERE nmr @> numrange(4.0, 4.2);
+SELECT * FROM nummultirange_test WHERE multirange_contains_multirange(nmr, '{[4.0,4.2), [6.0, 8.0)}');
+SELECT * FROM nummultirange_test WHERE nmr @> '{[4.0,4.2), [6.0, 8.0)}'::nummultirange;
+
+-- x is contained by mr
+SELECT * FROM nummultirange_test WHERE elem_contained_by_multirange(4.0, nmr);
+SELECT * FROM nummultirange_test WHERE 4.0 <@ nmr;
+SELECT * FROM nummultirange_test WHERE range_contained_by_multirange(numrange(4.0, 4.2), nmr);
+SELECT * FROM nummultirange_test WHERE numrange(4.0, 4.2) <@ nmr;
+SELECT * FROM nummultirange_test WHERE multirange_contained_by_multirange('{[4.0,4.2), [6.0, 8.0)}', nmr);
+SELECT * FROM nummultirange_test WHERE '{[4.0,4.2), [6.0, 8.0)}'::nummultirange <@ nmr;
+
+-- overlaps
+SELECT 'empty'::numrange && nummultirange();
+SELECT 'empty'::numrange && nummultirange(numrange(1,2));
+SELECT nummultirange() && 'empty'::numrange;
+SELECT nummultirange(numrange(1,2)) && 'empty'::numrange;
+SELECT nummultirange() && nummultirange();
+SELECT nummultirange() && nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) && nummultirange();
+SELECT nummultirange(numrange(3,4)) && nummultirange(numrange(1,2), numrange(7,8));
+SELECT nummultirange(numrange(1,2), numrange(7,8)) && nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(3,4)) && nummultirange(numrange(1,2), numrange(3.5,8));
+SELECT nummultirange(numrange(1,2), numrange(3.5,8)) && numrange(3,4);
+SELECT nummultirange(numrange(1,2), numrange(3.5,8)) && nummultirange(numrange(3,4));
+
+-- contains
+SELECT nummultirange() @> nummultirange();
+SELECT nummultirange() @> 'empty'::numrange;
+SELECT nummultirange(numrange(null,null)) @> numrange(1,2);
+SELECT nummultirange(numrange(null,null)) @> numrange(null,2);
+SELECT nummultirange(numrange(null,null)) @> numrange(2,null);
+SELECT nummultirange(numrange(null,5)) @> numrange(null,3);
+SELECT nummultirange(numrange(null,5)) @> numrange(null,8);
+SELECT nummultirange(numrange(5,null)) @> numrange(8,null);
+SELECT nummultirange(numrange(5,null)) @> numrange(3,null);
+SELECT nummultirange(numrange(1,5)) @> numrange(8,9);
+SELECT nummultirange(numrange(1,5)) @> numrange(3,9);
+SELECT nummultirange(numrange(1,5)) @> numrange(1,4);
+SELECT nummultirange(numrange(1,5)) @> numrange(1,5);
+SELECT nummultirange(numrange(-4,-2), numrange(1,5)) @> numrange(1,5);
+SELECT nummultirange(numrange(1,5), numrange(8,9)) @> numrange(1,5);
+SELECT nummultirange(numrange(1,5), numrange(8,9)) @> numrange(6,7);
+SELECT nummultirange(numrange(1,5), numrange(6,9)) @> numrange(6,7);
+SELECT '{[1,5)}'::nummultirange @> '{[1,5)}';
+SELECT '{[-4,-2), [1,5)}'::nummultirange @> '{[1,5)}';
+SELECT '{[1,5), [8,9)}'::nummultirange @> '{[1,5)}';
+SELECT '{[1,5), [8,9)}'::nummultirange @> '{[6,7)}';
+SELECT '{[1,5), [6,9)}'::nummultirange @> '{[6,7)}';
+
+-- is contained by
+SELECT nummultirange() <@ nummultirange();
+SELECT 'empty'::numrange <@ nummultirange();
+SELECT numrange(1,2) <@ nummultirange(numrange(null,null));
+SELECT numrange(null,2) <@ nummultirange(numrange(null,null));
+SELECT numrange(2,null) <@ nummultirange(numrange(null,null));
+SELECT numrange(null,3) <@ nummultirange(numrange(null,5));
+SELECT numrange(null,8) <@ nummultirange(numrange(null,5));
+SELECT numrange(8,null) <@ nummultirange(numrange(5,null));
+SELECT numrange(3,null) <@ nummultirange(numrange(5,null));
+SELECT numrange(8,9) <@ nummultirange(numrange(1,5));
+SELECT numrange(3,9) <@ nummultirange(numrange(1,5));
+SELECT numrange(1,4) <@ nummultirange(numrange(1,5));
+SELECT numrange(1,5) <@ nummultirange(numrange(1,5));
+SELECT numrange(1,5) <@ nummultirange(numrange(-4,-2), numrange(1,5));
+SELECT numrange(1,5) <@ nummultirange(numrange(1,5), numrange(8,9));
+SELECT numrange(6,7) <@ nummultirange(numrange(1,5), numrange(8,9));
+SELECT numrange(6,7) <@ nummultirange(numrange(1,5), numrange(6,9));
+SELECT '{[1,5)}' <@ '{[1,5)}'::nummultirange;
+SELECT '{[1,5)}' <@ '{[-4,-2), [1,5)}'::nummultirange;
+SELECT '{[1,5)}' <@ '{[1,5), [8,9)}'::nummultirange;
+SELECT '{[6,7)}' <@ '{[1,5), [8,9)}'::nummultirange;
+SELECT '{[6,7)}' <@ '{[1,5), [6,9)}'::nummultirange;
+
+-- overleft
+SELECT 'empty'::numrange &< nummultirange();
+SELECT 'empty'::numrange &< nummultirange(numrange(1,2));
+SELECT nummultirange() &< 'empty'::numrange;
+SELECT nummultirange(numrange(1,2)) &< 'empty'::numrange;
+SELECT nummultirange() &< nummultirange();
+SELECT nummultirange(numrange(1,2)) &< nummultirange();
+SELECT nummultirange() &< nummultirange(numrange(1,2));
+SELECT numrange(6,7) &< nummultirange(numrange(3,4));
+SELECT numrange(1,2) &< nummultirange(numrange(3,4));
+SELECT numrange(1,4) &< nummultirange(numrange(3,4));
+SELECT numrange(1,6) &< nummultirange(numrange(3,4));
+SELECT numrange(3.5,6) &< nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(6,7)) &< numrange(3,4);
+SELECT nummultirange(numrange(1,2)) &< numrange(3,4);
+SELECT nummultirange(numrange(1,4)) &< numrange(3,4);
+SELECT nummultirange(numrange(1,6)) &< numrange(3,4);
+SELECT nummultirange(numrange(3.5,6)) &< numrange(3,4);
+SELECT nummultirange(numrange(6,7)) &< nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,2)) &< nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,4)) &< nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,6)) &< nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(3.5,6)) &< nummultirange(numrange(3,4));
+
+-- overright
+SELECT nummultirange() &> 'empty'::numrange;
+SELECT nummultirange(numrange(1,2)) &> 'empty'::numrange;
+SELECT 'empty'::numrange &> nummultirange();
+SELECT 'empty'::numrange &> nummultirange(numrange(1,2));
+SELECT nummultirange() &> nummultirange();
+SELECT nummultirange() &> nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) &> nummultirange();
+SELECT nummultirange(numrange(3,4)) &> numrange(6,7);
+SELECT nummultirange(numrange(3,4)) &> numrange(1,2);
+SELECT nummultirange(numrange(3,4)) &> numrange(1,4);
+SELECT nummultirange(numrange(3,4)) &> numrange(1,6);
+SELECT nummultirange(numrange(3,4)) &> numrange(3.5,6);
+SELECT numrange(3,4) &> nummultirange(numrange(6,7));
+SELECT numrange(3,4) &> nummultirange(numrange(1,2));
+SELECT numrange(3,4) &> nummultirange(numrange(1,4));
+SELECT numrange(3,4) &> nummultirange(numrange(1,6));
+SELECT numrange(3,4) &> nummultirange(numrange(3.5,6));
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(6,7));
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(1,4));
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(1,6));
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(3.5,6));
+
+-- meets
+SELECT 'empty'::numrange -|- nummultirange();
+SELECT 'empty'::numrange -|- nummultirange(numrange(1,2));
+SELECT nummultirange() -|- 'empty'::numrange;
+SELECT nummultirange(numrange(1,2)) -|- 'empty'::numrange;
+SELECT nummultirange() -|- nummultirange();
+SELECT nummultirange(numrange(1,2)) -|- nummultirange();
+SELECT nummultirange() -|- nummultirange(numrange(1,2));
+SELECT numrange(1,2) -|- nummultirange(numrange(2,4));
+SELECT numrange(1,2) -|- nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,2)) -|- numrange(2,4);
+SELECT nummultirange(numrange(1,2)) -|- numrange(3,4);
+SELECT nummultirange(numrange(1,2)) -|- nummultirange(numrange(2,4));
+SELECT nummultirange(numrange(1,2)) -|- nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,2), numrange(5,6)) -|- nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,2), numrange(5,6)) -|- nummultirange(numrange(6,7));
+SELECT nummultirange(numrange(1,2), numrange(5,6)) -|- nummultirange(numrange(8,9));
+SELECT nummultirange(numrange(1,2)) -|- nummultirange(numrange(2,4), numrange(6,7));
+
+-- strictly left
+select 'empty'::numrange << nummultirange();
+select numrange(1,2) << nummultirange();
+select numrange(1,2) << nummultirange(numrange(3,4));
+select numrange(1,2) << nummultirange(numrange(0,4));
+select numrange(1,2) << nummultirange(numrange(0,4), numrange(7,8));
+select nummultirange() << 'empty'::numrange;
+select nummultirange() << numrange(1,2);
+select nummultirange(numrange(3,4)) << numrange(3,6);
+select nummultirange(numrange(0,2)) << numrange(3,6);
+select nummultirange(numrange(0,2), numrange(7,8)) << numrange(3,6);
+select nummultirange(numrange(-4,-2), numrange(0,2)) << numrange(3,6);
+select nummultirange() << nummultirange();
+select nummultirange() << nummultirange(numrange(1,2));
+select nummultirange(numrange(1,2)) << nummultirange();
+select nummultirange(numrange(1,2)) << nummultirange(numrange(1,2));
+select nummultirange(numrange(1,2)) << nummultirange(numrange(3,4));
+select nummultirange(numrange(1,2)) << nummultirange(numrange(3,4), numrange(7,8));
+select nummultirange(numrange(1,2), numrange(4,5)) << nummultirange(numrange(3,4), numrange(7,8));
+
+-- strictly right
+select nummultirange() >> 'empty'::numrange;
+select nummultirange() >> numrange(1,2);
+select nummultirange(numrange(3,4)) >> numrange(1,2);
+select nummultirange(numrange(0,4)) >> numrange(1,2);
+select nummultirange(numrange(0,4), numrange(7,8)) >> numrange(1,2);
+select 'empty'::numrange >> nummultirange();
+select numrange(1,2) >> nummultirange();
+select numrange(3,6) >> nummultirange(numrange(3,4));
+select numrange(3,6) >> nummultirange(numrange(0,2));
+select numrange(3,6) >> nummultirange(numrange(0,2), numrange(7,8));
+select numrange(3,6) >> nummultirange(numrange(-4,-2), numrange(0,2));
+select nummultirange() >> nummultirange();
+select nummultirange(numrange(1,2)) >> nummultirange();
+select nummultirange() >> nummultirange(numrange(1,2));
+select nummultirange(numrange(1,2)) >> nummultirange(numrange(1,2));
+select nummultirange(numrange(3,4)) >> nummultirange(numrange(1,2));
+select nummultirange(numrange(3,4), numrange(7,8)) >> nummultirange(numrange(1,2));
+select nummultirange(numrange(3,4), numrange(7,8)) >> nummultirange(numrange(1,2), numrange(4,5));
+
+-- union
+SELECT nummultirange() + nummultirange();
+SELECT nummultirange() + nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) + nummultirange();
+SELECT nummultirange(numrange(1,2)) + nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) + nummultirange(numrange(2,4));
+SELECT nummultirange(numrange(1,2)) + nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) + nummultirange(numrange(2,4));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) + nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) + nummultirange(numrange(0,9));
+
+-- merge
+SELECT range_merge(nummultirange());
+SELECT range_merge(nummultirange(numrange(1,2)));
+SELECT range_merge(nummultirange(numrange(1,2), numrange(7,8)));
+
+-- minus
+SELECT nummultirange() - nummultirange();
+SELECT nummultirange() - nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) - nummultirange();
+SELECT nummultirange(numrange(1,2), numrange(3,4)) - nummultirange();
+SELECT nummultirange(numrange(1,2)) - nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) - nummultirange(numrange(2,4));
+SELECT nummultirange(numrange(1,2)) - nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(2,3));
+SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(0,8));
+SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(0,2));
+SELECT nummultirange(numrange(1,8)) - nummultirange(numrange(0,2), numrange(3,4));
+SELECT nummultirange(numrange(1,8)) - nummultirange(numrange(2,3), numrange(5,null));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(-2,0));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(2,4));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(3,5));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(0,9));
+SELECT nummultirange(numrange(1,3), numrange(4,5)) - nummultirange(numrange(2,9));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(8,9));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(-2,0), numrange(8,9));
+
+-- intersection
+SELECT nummultirange() * nummultirange();
+SELECT nummultirange() * nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) * nummultirange();
+SELECT '{[1,3)}'::nummultirange * '{[1,5)}'::nummultirange;
+SELECT '{[1,3)}'::nummultirange * '{[0,5)}'::nummultirange;
+SELECT '{[1,3)}'::nummultirange * '{[0,2)}'::nummultirange;
+SELECT '{[1,3)}'::nummultirange * '{[2,5)}'::nummultirange;
+SELECT '{[1,4)}'::nummultirange * '{[2,3)}'::nummultirange;
+SELECT '{[1,4)}'::nummultirange * '{[0,2), [3,5)}'::nummultirange;
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[0,8), [9,12)}'::nummultirange;
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[9,12)}'::nummultirange;
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[-5,-4), [5,6), [9,12)}'::nummultirange;
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[0,2), [3,8), [9,12)}'::nummultirange;
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[0,2), [3,8), [9,12)}'::nummultirange;
+
+--
+-- range_agg function
+--
+create table reservations ( room_id integer not null, booked_during daterange );
+insert into reservations values
+-- 1: has a meets and a gap
+(1, daterange('2018-07-01', '2018-07-07')),
+(1, daterange('2018-07-07', '2018-07-14')),
+(1, daterange('2018-07-20', '2018-07-22')),
+-- 2: just a single row
+(2, daterange('2018-07-01', '2018-07-03')),
+-- 3: one null range
+(3, NULL),
+-- 4: two null ranges
+(4, NULL),
+(4, NULL),
+-- 5: a null range and a non-null range
+(5, NULL),
+(5, daterange('2018-07-01', '2018-07-03')),
+-- 6: has overlap
+(6, daterange('2018-07-01', '2018-07-07')),
+(6, daterange('2018-07-05', '2018-07-10')),
+-- 7: two ranges that meet: no gap or overlap
+(7, daterange('2018-07-01', '2018-07-07')),
+(7, daterange('2018-07-07', '2018-07-14')),
+-- 8: an empty range
+(8, 'empty'::daterange)
+;
+SELECT   room_id, range_agg(booked_during)
+FROM     reservations
+GROUP BY room_id
+ORDER BY room_id;
+
+-- range_agg on a custom range type too
+SELECT  range_agg(r)
+FROM    (VALUES
+          ('[a,c]'::textrange),
+          ('[b,b]'::textrange),
+          ('[c,f]'::textrange),
+          ('[g,h)'::textrange),
+          ('[h,j)'::textrange)
+        ) t(r);
+
+select range_intersect_agg(nmr) from nummultirange_test;
+select range_intersect_agg(nmr) from nummultirange_test where false;
+-- test with just one input:
+select range_intersect_agg(nmr) from (values ('{[1,2]}'::nummultirange)) t(nmr);
+select range_intersect_agg(nmr) from nummultirange_test where nmr @> 4.0;
+
+create table nummultirange_test2(nmr nummultirange);
+create index nummultirange_test2_hash_idx on nummultirange_test2 using hash (nmr);
+
+INSERT INTO nummultirange_test2 VALUES('{[, 5)}');
+INSERT INTO nummultirange_test2 VALUES(nummultirange(numrange(1.1, 2.2)));
+INSERT INTO nummultirange_test2 VALUES(nummultirange(numrange(1.1, 2.2)));
+INSERT INTO nummultirange_test2 VALUES(nummultirange(numrange(1.1, 2.2,'()')));
+INSERT INTO nummultirange_test2 VALUES('{}');
+
+select * from nummultirange_test2 where nmr = '{}';
+select * from nummultirange_test2 where nmr = nummultirange(numrange(1.1, 2.2));
+select * from nummultirange_test2 where nmr = nummultirange(numrange(1.1, 2.3));
+
+set enable_nestloop=t;
+set enable_hashjoin=f;
+set enable_mergejoin=f;
+select * from nummultirange_test natural join nummultirange_test2 order by nmr;
+set enable_nestloop=f;
+set enable_hashjoin=t;
+set enable_mergejoin=f;
+select * from nummultirange_test natural join nummultirange_test2 order by nmr;
+set enable_nestloop=f;
+set enable_hashjoin=f;
+set enable_mergejoin=t;
+select * from nummultirange_test natural join nummultirange_test2 order by nmr;
+
+set enable_nestloop to default;
+set enable_hashjoin to default;
+set enable_mergejoin to default;
+
+DROP TABLE nummultirange_test2;
+
+--
+-- Test user-defined multirange of floats
+--
+
+select '{[123.001, 5.e9)}'::float8multirange @> 888.882::float8;
+create table float8multirange_test(f8mr float8multirange, i int);
+insert into float8multirange_test values(float8multirange(float8range(-100.00007, '1.111113e9')), 42);
+select * from float8multirange_test;
+drop table float8multirange_test;
+
+--
+-- Test multirange types over domains
+--
+
+create domain mydomain as int4;
+create type mydomainrange as range(subtype=mydomain);
+select '{[4,50)}'::mydomainmultirange @> 7::mydomain;
+drop domain mydomain cascade;
+
+--
+-- Test domains over multirange types
+--
+
+create domain restrictedmultirange as int4multirange check (upper(value) < 10);
+select '{[4,5)}'::restrictedmultirange @> 7;
+select '{[4,50)}'::restrictedmultirange @> 7; -- should fail
+drop domain restrictedmultirange;
+
+--
+-- Test multiple multirange types over the same subtype
+--
+
+create type textrange1 as range(subtype=text, collation="C");
+create type textrange2 as range(subtype=text, collation="C");
+
+select textmultirange1(textrange2('a','Z'));  -- should fail
+select textmultirange1(textrange1('a','Z')) @> 'b'::text;
+select textmultirange2(textrange2('a','z')) @> 'b'::text;
+
+drop type textrange1;
+drop type textrange2;
+
+--
+-- Test polymorphic type system
+--
+
+create function anyarray_anymultirange_func(a anyarray, r anymultirange)
+  returns anyelement as 'select $1[1] + lower($2);' language sql;
+
+select anyarray_anymultirange_func(ARRAY[1,2], int4multirange(int4range(10,20)));
+
+-- should fail
+select anyarray_anymultirange_func(ARRAY[1,2], nummultirange(numrange(10,20)));
+
+drop function anyarray_anymultirange_func(anyarray, anymultirange);
+
+-- should fail
+create function bogus_func(anyelement)
+  returns anymultirange as 'select int4multirange(int4range(1,10))' language sql;
+
+-- should fail
+create function bogus_func(int)
+  returns anymultirange as 'select int4multirange(int4range(1,10))' language sql;
+
+create function range_add_bounds(anymultirange)
+  returns anyelement as 'select lower($1) + upper($1)' language sql;
+
+select range_add_bounds(int4multirange(int4range(1, 17)));
+select range_add_bounds(nummultirange(numrange(1.0001, 123.123)));
+
+create function multirangetypes_sql(q anymultirange, b anyarray, out c anyelement)
+  as $$ select upper($1) + $2[1] $$
+  language sql;
+
+select multirangetypes_sql(int4multirange(int4range(1,10)), ARRAY[2,20]);
+select multirangetypes_sql(nummultirange(numrange(1,10)), ARRAY[2,20]);  -- match failure
+
+--
+-- Arrays of multiranges
+--
+
+select ARRAY[nummultirange(numrange(1.1, 1.2)), nummultirange(numrange(12.3, 155.5))];
+
+create table i8mr_array (f1 int, f2 int8multirange[]);
+insert into i8mr_array values (42, array[int8multirange(int8range(1,10)), int8multirange(int8range(2,20))]);
+select * from i8mr_array;
+drop table i8mr_array;
+
+--
+-- Multiranges of arrays
+--
+
+select arraymultirange(arrayrange(ARRAY[1,2], ARRAY[2,1]));
+select arraymultirange(arrayrange(ARRAY[2,1], ARRAY[1,2]));  -- fail
+
+select array[1,1] <@ arraymultirange(arrayrange(array[1,2], array[2,1]));
+select array[1,3] <@ arraymultirange(arrayrange(array[1,2], array[2,1]));
+
+--
+-- Ranges of composites
+--
+
+create type two_ints as (a int, b int);
+create type two_ints_range as range (subtype = two_ints);
+
+-- with force_parallel_mode on, this exercises tqueue.c's range remapping
+select *, row_to_json(upper(t)) as u from
+  (values (two_ints_multirange(two_ints_range(row(1,2), row(3,4)))),
+          (two_ints_multirange(two_ints_range(row(5,6), row(7,8))))) v(t);
+
+drop type two_ints cascade;
+
+--
+-- Check behavior when subtype lacks a hash function
+--
+
+set enable_sort = off;  -- try to make it pick a hash setop implementation
+
+select '{(2,5)}'::cashmultirange except select '{(5,6)}'::cashmultirange;
+
+reset enable_sort;
+
+--
+-- OUT/INOUT/TABLE functions
+--
+
+-- infer anymultirange from anymultirange
+create function mr_outparam_succeed(i anymultirange, out r anymultirange, out t text)
+  as $$ select $1, 'foo'::text $$ language sql;
+
+select * from mr_outparam_succeed(int4multirange(int4range(1,2)));
+
+-- infer anyarray from anymultirange
+create function mr_outparam_succeed2(i anymultirange, out r anyarray, out t text)
+  as $$ select ARRAY[upper($1)], 'foo'::text $$ language sql;
+
+select * from mr_outparam_succeed2(int4multirange(int4range(1,2)));
+
+-- infer anyrange from anymultirange
+create function mr_outparam_succeed3(i anymultirange, out r anyrange, out t text)
+  as $$ select range_merge($1), 'foo'::text $$ language sql;
+select * from mr_outparam_succeed3(int4multirange(int4range(1,2)));
+
+-- infer anymultirange from anyrange
+create function mr_outparam_succeed4(i anyrange, out r anymultirange, out t text)
+  as $$ select anymultirange($1), 'foo'::text $$ language sql;
+
+select * from mr_outparam_succeed4(int4range(1,2));
+
+-- infer anyelement from anymultirange
+create function mr_inoutparam_succeed(out i anyelement, inout r anymultirange)
+  as $$ select upper($1), $1 $$ language sql;
+
+select * from mr_inoutparam_succeed(int4multirange(int4range(1,2)));
+
+-- infer anyelement+anymultirange from anyelement+anymultirange
+create function mr_table_succeed(i anyelement, r anymultirange) returns table(i anyelement, r anymultirange)
+  as $$ select $1, $2 $$ language sql;
+
+select * from mr_table_succeed(123, int4multirange(int4range(1,11)));
+
+-- should fail
+create function mr_outparam_fail(i anyelement, out r anymultirange, out t text)
+  as $$ select '[1,10]', 'foo' $$ language sql;
+
+--should fail
+create function mr_inoutparam_fail(inout i anyelement, out r anymultirange)
+  as $$ select $1, '[1,10]' $$ language sql;
+
+--should fail
+create function mr_table_fail(i anyelement) returns table(i anyelement, r anymultirange)
+  as $$ select $1, '[1,10]' $$ language sql;
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/rangetypes.sql b/src/test/regress/sql/rangetypes.sql
index 2d0ec8964e..4e57352799 100644
--- a/src/test/regress/sql/rangetypes.sql
+++ b/src/test/regress/sql/rangetypes.sql
@@ -118,6 +118,10 @@ select numrange(1.0, 2.0) * numrange(2.0, 3.0);
 select numrange(1.0, 2.0) * numrange(1.5, 3.0);
 select numrange(1.0, 2.0) * numrange(2.5, 3.0);
 
+select range_intersect_agg(nr) from numrange_test;
+select range_intersect_agg(nr) from numrange_test where false;
+select range_intersect_agg(nr) from numrange_test where nr @> 4.0;
+
 create table numrange_test2(nr numrange);
 create index numrange_test2_hash_idx on numrange_test2 using hash (nr);
 
@@ -484,16 +488,25 @@ reset enable_sort;
 -- OUT/INOUT/TABLE functions
 --
 
+-- infer anyrange from anyrange
 create function outparam_succeed(i anyrange, out r anyrange, out t text)
   as $$ select $1, 'foo'::text $$ language sql;
 
 select * from outparam_succeed(int4range(1,2));
 
+-- infer anyarray from anyrange
+create function outparam_succeed2(i anyrange, out r anyarray, out t text)
+  as $$ select ARRAY[upper($1)], 'foo'::text $$ language sql;
+
+select * from outparam_succeed2(int4range(int4range(1,2)));
+
+-- infer anyelement from anyrange
 create function inoutparam_succeed(out i anyelement, inout r anyrange)
   as $$ select upper($1), $1 $$ language sql;
 
 select * from inoutparam_succeed(int4range(1,2));
 
+-- infer anyelement+anyrange from anyelement+anyrange
 create function table_succeed(i anyelement, r anyrange) returns table(i anyelement, r anyrange)
   as $$ select $1, $2 $$ language sql;
 
diff --git a/src/test/regress/sql/type_sanity.sql b/src/test/regress/sql/type_sanity.sql
index fed413bf98..2e34bc8b65 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.rngmultitypid
+FROM pg_range p1
+WHERE p1.rngmultitypid IS NULL OR p1.rngmultitypid = 0;
#75Pavel Stehule
pavel.stehule@gmail.com
In reply to: Paul A Jungwirth (#74)
Re: range_agg

pá 17. 1. 2020 v 21:08 odesílatel Paul A Jungwirth <
pj@illuminatedcomputing.com> napsal:

On Fri, Jan 10, 2020 at 1:38 AM Pavel Stehule <pavel.stehule@gmail.com>
wrote:

This still leaves the question of how best to format the docs for
these operators. I like being able to combine all the <@ variations
(e.g.) into one table row, but if that is too ugly I could give them
separate rows instead. Giving them all their own row consumes a lot of
vertical space though, and to me that makes the docs more tedious to
read & browse, so it's harder to grasp all the available range-related
operations at a glance.

I have similar opinion - maybe is better do documentation for range and

multirange separately. Sometimes there are still removed operators @+

I like keeping the range/multirange operators together since they are
so similar for both types, but if others disagree I'd be grateful for
more feedback.

ok

You're right that I left in a few references to the old @+ style
operators in the examples; I've fixed those.

If you can share TYPTYPE_RANGE in code for multiranges, then it should

be 'r'. If not, then it needs own value.

Okay. I think a new 'm' value is warranted because they are not
interchangeable.

I experimented with setting pg_type.typelem to the multirange's range
type, but it seemed to break a lot of things, and reading the code I
saw some places that treat a non-zero typelem as synonymous with being
an array. So I'm reluctant to make this change also, especially when
it is just as easy to query pg_range to get a multirange's range type.

ok, it is unhappy, but it is true. This note should be somewhere in

code, please

I've added a comment about this. I put it at the top of DefineRange
but let me know if that's the wrong place.

The attached file is also rebased on currrent master.

Can be nice to have a polymorphic function

multirange(anymultirange, anyrange) returns anymultirange. This functions
should to do multirange from $2 to type $1

It can enhance to using polymorphic types and simplify casting.

Usage

CREATE OR REPLACE FUNCTION diff(anymultirange, anyrange)
RETURNS anymultirange AS $$
SELECT $1 - multirange($1, $2)
$$ LANGUAGE sql;

when I tried to write this function in plpgsql I got

create or replace function multirange(anymultirange, anyrange) returns
anymultirange as $$
begin
execute format('select $2::%I', pg_typeof($1)) into $1;
return $1;
end;
$$ language plpgsql immutable strict;

ERROR: unrecognized typtype: 109
CONTEXT: compilation of PL/pgSQL function "multirange" near line 1

So probably some support in PL is missing

But all others looks very well

Regards

Pavel

Show quoted text

Thanks!
Paul

#76Paul A Jungwirth
pj@illuminatedcomputing.com
In reply to: Pavel Stehule (#75)
Re: range_agg

On Sat, Jan 18, 2020 at 7:20 AM Pavel Stehule <pavel.stehule@gmail.com> wrote:

Can be nice to have a polymorphic function

multirange(anymultirange, anyrange) returns anymultirange. This functions should to do multirange from $2 to type $1

It can enhance to using polymorphic types and simplify casting.

Thanks for taking another look! I actually have that already but it is
named anymultirange:

regression=# select anymultirange(int4range(1,2));
anymultirange
---------------
{[1,2)}
(1 row)

Will that work for you?

I think I only wrote that to satisfy some requirement of having an
anymultirange type, but I agree it could be useful. (I even used it in
the regress tests.) Maybe it's worth documenting too?

when I tried to write this function in plpgsql I got

create or replace function multirange(anymultirange, anyrange) returns anymultirange as $$
begin
execute format('select $2::%I', pg_typeof($1)) into $1;
return $1;
end;
$$ language plpgsql immutable strict;

ERROR: unrecognized typtype: 109
CONTEXT: compilation of PL/pgSQL function "multirange" near line 1

Hmm, I'll add a test for that and see if I can find the problem.

Thanks!
Paul

#77Pavel Stehule
pavel.stehule@gmail.com
In reply to: Paul A Jungwirth (#76)
Re: range_agg

so 18. 1. 2020 v 17:07 odesílatel Paul A Jungwirth <
pj@illuminatedcomputing.com> napsal:

On Sat, Jan 18, 2020 at 7:20 AM Pavel Stehule <pavel.stehule@gmail.com>
wrote:

Can be nice to have a polymorphic function

multirange(anymultirange, anyrange) returns anymultirange. This

functions should to do multirange from $2 to type $1

It can enhance to using polymorphic types and simplify casting.

Thanks for taking another look! I actually have that already but it is
named anymultirange:

regression=# select anymultirange(int4range(1,2));
anymultirange
---------------
{[1,2)}
(1 row)

Will that work for you?

It's better than I though

I think I only wrote that to satisfy some requirement of having an
anymultirange type, but I agree it could be useful. (I even used it in
the regress tests.) Maybe it's worth documenting too?

yes

when I tried to write this function in plpgsql I got

create or replace function multirange(anymultirange, anyrange) returns

anymultirange as $$

begin
execute format('select $2::%I', pg_typeof($1)) into $1;
return $1;
end;
$$ language plpgsql immutable strict;

ERROR: unrecognized typtype: 109
CONTEXT: compilation of PL/pgSQL function "multirange" near line 1

Hmm, I'll add a test for that and see if I can find the problem.

ok

Show quoted text

Thanks!
Paul

#78Pavel Stehule
pavel.stehule@gmail.com
In reply to: Pavel Stehule (#77)
Re: range_agg

so 18. 1. 2020 v 17:35 odesílatel Pavel Stehule <pavel.stehule@gmail.com>
napsal:

so 18. 1. 2020 v 17:07 odesílatel Paul A Jungwirth <
pj@illuminatedcomputing.com> napsal:

On Sat, Jan 18, 2020 at 7:20 AM Pavel Stehule <pavel.stehule@gmail.com>
wrote:

Can be nice to have a polymorphic function

multirange(anymultirange, anyrange) returns anymultirange. This

functions should to do multirange from $2 to type $1

It can enhance to using polymorphic types and simplify casting.

Thanks for taking another look! I actually have that already but it is
named anymultirange:

regression=# select anymultirange(int4range(1,2));
anymultirange
---------------
{[1,2)}
(1 row)

Will that work for you?

It's better than I though

I think I only wrote that to satisfy some requirement of having an
anymultirange type, but I agree it could be useful. (I even used it in
the regress tests.) Maybe it's worth documenting too?

Now, I think so name "anymultirange" is not good. Maybe better name is just
"multirange"

Show quoted text

yes

when I tried to write this function in plpgsql I got

create or replace function multirange(anymultirange, anyrange) returns

anymultirange as $$

begin
execute format('select $2::%I', pg_typeof($1)) into $1;
return $1;
end;
$$ language plpgsql immutable strict;

ERROR: unrecognized typtype: 109
CONTEXT: compilation of PL/pgSQL function "multirange" near line 1

Hmm, I'll add a test for that and see if I can find the problem.

ok

Thanks!
Paul

#79Paul A Jungwirth
pj@illuminatedcomputing.com
In reply to: Pavel Stehule (#78)
Re: range_agg

On Sun, Jan 19, 2020 at 12:10 AM Pavel Stehule <pavel.stehule@gmail.com> wrote:

Now, I think so name "anymultirange" is not good. Maybe better name is just "multirange"

Are you sure? This function exists to be a cast to an anymultirange,
and I thought the convention was to name cast functions after their
destination type. I can change it, but in my opinion anymultirange
follows the Postgres conventions better. But just let me know and I'll
do "multirange" instead!

Yours,
Paul

#80Tom Lane
tgl@sss.pgh.pa.us
In reply to: Paul A Jungwirth (#79)
Re: range_agg

Paul A Jungwirth <pj@illuminatedcomputing.com> writes:

On Sun, Jan 19, 2020 at 12:10 AM Pavel Stehule <pavel.stehule@gmail.com> wrote:

Now, I think so name "anymultirange" is not good. Maybe better name is just "multirange"

Are you sure? This function exists to be a cast to an anymultirange,
and I thought the convention was to name cast functions after their
destination type.

True for casts involving concrete types, mainly because we'd like
the identity "value::typename == typename(value)" to hold without
too much worry about whether the latter is a plain function call
or a special case. Not sure whether it makes as much sense for
polymorphics, since casting to a polymorphic type is pretty silly:
we do seem to allow you to do that, but it's a no-op.

I'm a little troubled by the notion that what you're talking about
here is not a no-op (if it were, you wouldn't need a function).
That seems like there's something fundamentally not quite right
either with the design or with how you're thinking about it.

As a comparison point, we sometimes describe subscripting as
being a polymorphic operation like

subscript(anyarray, integer) returns anyelement

It would be completely unhelpful to call that anyelement().
I feel like you might be making a similar mistake here.

Alternatively, consider this: a cast from some concrete multirange type
to anymultirange is a no-op, while any other sort of cast probably ought
to be casting to some particular concrete multirange type. That would
line up with the existing operations for plain ranges.

regards, tom lane

#81Pavel Stehule
pavel.stehule@gmail.com
In reply to: Tom Lane (#80)
Re: range_agg

po 20. 1. 2020 v 1:38 odesílatel Tom Lane <tgl@sss.pgh.pa.us> napsal:

Paul A Jungwirth <pj@illuminatedcomputing.com> writes:

On Sun, Jan 19, 2020 at 12:10 AM Pavel Stehule <pavel.stehule@gmail.com>

wrote:

Now, I think so name "anymultirange" is not good. Maybe better name is

just "multirange"

Are you sure? This function exists to be a cast to an anymultirange,
and I thought the convention was to name cast functions after their
destination type.

True for casts involving concrete types, mainly because we'd like
the identity "value::typename == typename(value)" to hold without
too much worry about whether the latter is a plain function call
or a special case. Not sure whether it makes as much sense for
polymorphics, since casting to a polymorphic type is pretty silly:
we do seem to allow you to do that, but it's a no-op.

I'm a little troubled by the notion that what you're talking about
here is not a no-op (if it were, you wouldn't need a function).
That seems like there's something fundamentally not quite right
either with the design or with how you're thinking about it.

I thinking about completeness of operations

I can to write

CREATE OR REPLACE FUNCTION fx(anyarray, anyelement)
RETURNS anyarray AS $$
SELECT $1 || ARRAY[$2]
$$ LANGUAGE sql;

I need to some functionality for moving a value to different category (it
is more generic than casting to specific type (that can hold category)

CREATE OR REPLACE FUNCTION fx(anymultirange, anyrange)
RETURNS anyrage AS $$
SELECT $1 + multirange($1)
$$ LANGUAGE sql;

is just a analogy.

Regards

Pavel

Show quoted text

As a comparison point, we sometimes describe subscripting as
being a polymorphic operation like

subscript(anyarray, integer) returns anyelement

It would be completely unhelpful to call that anyelement().
I feel like you might be making a similar mistake here.

Alternatively, consider this: a cast from some concrete multirange type
to anymultirange is a no-op, while any other sort of cast probably ought
to be casting to some particular concrete multirange type. That would
line up with the existing operations for plain ranges.

regards, tom lane

#82Paul A Jungwirth
pj@illuminatedcomputing.com
In reply to: Tom Lane (#80)
Re: range_agg

On Sun, Jan 19, 2020 at 4:38 PM Tom Lane <tgl@sss.pgh.pa.us> wrote:

True for casts involving concrete types, mainly because we'd like
the identity "value::typename == typename(value)" to hold without
too much worry about whether the latter is a plain function call
or a special case. Not sure whether it makes as much sense for
polymorphics, since casting to a polymorphic type is pretty silly:
we do seem to allow you to do that, but it's a no-op.

...

Alternatively, consider this: a cast from some concrete multirange type
to anymultirange is a no-op, while any other sort of cast probably ought
to be casting to some particular concrete multirange type. That would
line up with the existing operations for plain ranges.

I agree you wouldn't actually cast by saying x::anymultirange, and the
casts we define are already concrete, so instead you'd say
x::int4multirange. But I think having a polymorphic function to
convert from an anyrange to an anymultirange is useful so you can
write generic functions. I can see how calling it "anymultirange" may
be preferring the implementor perspective over the user perspective
though, and how simply "multirange" would be more empathetic. I don't
mind taking that approach.

Yours,
Paul

#83Paul A Jungwirth
pj@illuminatedcomputing.com
In reply to: Paul A Jungwirth (#82)
1 attachment(s)
Re: range_agg

On Sun, Jan 19, 2020 at 9:57 PM Paul A Jungwirth
<pj@illuminatedcomputing.com> wrote:

On Sun, Jan 19, 2020 at 4:38 PM Tom Lane <tgl@sss.pgh.pa.us> wrote:

True for casts involving concrete types, mainly because we'd like
the identity "value::typename == typename(value)" to hold without
too much worry about whether the latter is a plain function call
or a special case. Not sure whether it makes as much sense for
polymorphics, since casting to a polymorphic type is pretty silly:
we do seem to allow you to do that, but it's a no-op.

...

Alternatively, consider this: a cast from some concrete multirange type
to anymultirange is a no-op, while any other sort of cast probably ought
to be casting to some particular concrete multirange type. That would
line up with the existing operations for plain ranges.

I agree you wouldn't actually cast by saying x::anymultirange, and the
casts we define are already concrete, so instead you'd say
x::int4multirange. But I think having a polymorphic function to
convert from an anyrange to an anymultirange is useful so you can
write generic functions. I can see how calling it "anymultirange" may
be preferring the implementor perspective over the user perspective
though, and how simply "multirange" would be more empathetic. I don't
mind taking that approach.

Here is a patch with anymultirange(anyrange) renamed to
multirange(anyrange). I also rebased on the latest master, added
documentation about the multirange(anyrange) function, and slightly
adjusted the formatting of the range functions table.

Thanks,
Paul

Attachments:

v10-multirange.patchapplication/octet-stream; name=v10-multirange.patchDownload
diff --git a/doc/src/sgml/extend.sgml b/doc/src/sgml/extend.sgml
index a3046f22d0..7ea2f65778 100644
--- a/doc/src/sgml/extend.sgml
+++ b/doc/src/sgml/extend.sgml
@@ -229,9 +229,9 @@
    </indexterm>
 
     <para>
-     Five pseudo-types of special interest are <type>anyelement</type>,
+     Six pseudo-types of special interest are <type>anyelement</type>,
      <type>anyarray</type>, <type>anynonarray</type>, <type>anyenum</type>,
-     and <type>anyrange</type>,
+     <type>anyrange</type>, and <type>anymultirange</type>.
      which are collectively called <firstterm>polymorphic types</firstterm>.
      Any function declared using these types is said to be
      a <firstterm>polymorphic function</firstterm>.  A polymorphic function can
@@ -250,15 +250,15 @@
      position declared as <type>anyarray</type> can have any array data type,
      but similarly they must all be the same type.  And similarly,
      positions declared as <type>anyrange</type> must all be the same range
-     type.  Furthermore, if there are
+     type.  Likewise for <type>anymultirange</type>.
+    </para>
+
+    <para>
+     Furthermore, if there are
      positions declared <type>anyarray</type> and others declared
      <type>anyelement</type>, the actual array type in the
      <type>anyarray</type> positions must be an array whose elements are
      the same type appearing in the <type>anyelement</type> positions.
-     Similarly, if there are positions declared <type>anyrange</type>
-     and others declared <type>anyelement</type>, the actual range type in
-     the <type>anyrange</type> positions must be a range whose subtype is
-     the same type appearing in the <type>anyelement</type> positions.
      <type>anynonarray</type> is treated exactly the same as <type>anyelement</type>,
      but adds the additional constraint that the actual type must not be
      an array type.
@@ -268,6 +268,20 @@
     </para>
 
     <para>
+     Similarly, if there are positions declared <type>anyrange</type>
+     and others declared <type>anyelement</type>, the actual range type in
+     the <type>anyrange</type> positions must be a range whose subtype is
+     the same type appearing in the <type>anyelement</type> positions.
+     The type <type>anymultirange</type> accepts a multirange
+     whose contents match the other polymorphic types.
+     That is it must hold ranges matching <type>anyrange</type>
+     and base type elements matching <type>anyelement</type> (if either of
+     those are present). If <type>anyarray</type> is present its elements
+     must match the base type of the <type>anyrange</type> and/or the base
+     type of the ranges in an <type>anymultirange</type>.
+    </para>
+
+    <para>
      Thus, when more than one argument position is declared with a polymorphic
      type, the net effect is that only certain combinations of actual argument
      types are allowed.  For example, a function declared as
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 6c4359dc7b..687adef230 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -13501,7 +13501,7 @@ SELECT NULLIF(value, '(none)') ...
        <row>
         <entry>Operator</entry>
         <entry>Description</entry>
-        <entry>Example</entry>
+        <entry>Examples</entry>
         <entry>Result</entry>
        </row>
       </thead>
@@ -13972,12 +13972,14 @@ NULL baz</literallayout>(3 rows)</entry>
   <title>Range Functions and Operators</title>
 
   <para>
-   See <xref linkend="rangetypes"/> for an overview of range types.
+   See <xref linkend="rangetypes"/> for an overview of range and multirange types.
   </para>
 
   <para>
    <xref linkend="range-operators-table"/> shows the operators
-   available for range types.
+   available for range and multirange types.
+   Many of these operators will accept either a range or multirange
+   on either side.
   </para>
 
     <table id="range-operators-table">
@@ -13995,134 +13997,215 @@ NULL baz</literallayout>(3 rows)</entry>
        <row>
         <entry> <literal>=</literal> </entry>
         <entry>equal</entry>
-        <entry><literal>int4range(1,5) = '[1,4]'::int4range</literal></entry>
-        <entry><literal>t</literal></entry>
+        <entry><literallayout class="monospaced">int4range(1,5) = '[1,4]'::int4range
+'{[1,5)}'::int4multirange = '{[1,4]}'::int4multirange</literallayout></entry>
+        <entry><literallayout class="monospaced">t</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>&lt;&gt;</literal> </entry>
         <entry>not equal</entry>
-        <entry><literal>numrange(1.1,2.2) &lt;&gt; numrange(1.1,2.3)</literal></entry>
-        <entry><literal>t</literal></entry>
+        <entry><literallayout class="monospaced">numrange(1.1,2.2) &lt;&gt; numrange(1.1,2.3)
+'{[1.1,2.2)}'::nummultirange &lt;&gt; '{[1.1,2.3)}'::nummultirange</literallayout></entry>
+        <entry><literallayout class="monospaced">t</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>&lt;</literal> </entry>
         <entry>less than</entry>
-        <entry><literal>int4range(1,10) &lt; int4range(2,3)</literal></entry>
-        <entry><literal>t</literal></entry>
+        <entry><literallayout class="monospaced">int4range(1,10) &lt; int4range(2,3)
+'{[1,10)}'::int4multirange &lt; '{[2,3)}'::int4multirange</literallayout>
+        </entry>
+        <entry><literallayout class="monospaced">t</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>&gt;</literal> </entry>
         <entry>greater than</entry>
-        <entry><literal>int4range(1,10) &gt; int4range(1,5)</literal></entry>
-        <entry><literal>t</literal></entry>
+        <entry>
+          <literallayout class="monospaced">int4range(1,10) &gt; int4range(1,5)
+'{[1,10)}'::int4multirange &gt; '{[1,5)}'::int4multirange</literallayout>
+        </entry>
+        <entry><literallayout class="monospaced">t</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>&lt;=</literal> </entry>
         <entry>less than or equal</entry>
-        <entry><literal>numrange(1.1,2.2) &lt;= numrange(1.1,2.2)</literal></entry>
-        <entry><literal>t</literal></entry>
+        <entry>
+          <literallayout class="monospaced">numrange(1.1,2.2) &lt;= numrange(1.1,2.2)
+'{[1.1,2.2)}'::nummultirange &lt;= '{[1.1,2.2)}'::nummultirange</literallayout>
+        </entry>
+        <entry><literallayout class="monospaced">t</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>&gt;=</literal> </entry>
         <entry>greater than or equal</entry>
-        <entry><literal>numrange(1.1,2.2) &gt;= numrange(1.1,2.0)</literal></entry>
-        <entry><literal>t</literal></entry>
+        <entry>
+          <literallayout class="monospaced">numrange(1.1,2.2) &gt;= numrange(1.1,2.0)
+'{[1.1,2.2)}'::nummultirange &gt;= '{[1.1,2.0)}'::nummultirange</literallayout>
+        </entry>
+        <entry><literallayout class="monospaced">t</literallayout></entry>
+       </row>
+
+       <row>
+        <entry> <literal>@&gt;</literal> </entry>
+        <entry>contains multirange</entry>
+        <entry>
+          <literallayout class="monospaced">int4range(2,4) @&gt; '{[2,3)}'::int4multirange
+'{[2,4)}'::int4multirange @&gt; '{[2,3)}'::int4multirange</literallayout>
+        </entry>
+        <entry><literallayout class="monospaced">t</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>@&gt;</literal> </entry>
         <entry>contains range</entry>
-        <entry><literal>int4range(2,4) @&gt; int4range(2,3)</literal></entry>
-        <entry><literal>t</literal></entry>
+        <entry>
+          <literallayout class="monospaced">int4range(2,4) @&gt; int4range(2,3)
+'{[2,4)}'::int4multirange @&gt; int4range(2,3)</literallayout>
+        </entry>
+        <entry><literallayout class="monospaced">t</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>@&gt;</literal> </entry>
         <entry>contains element</entry>
-        <entry><literal>'[2011-01-01,2011-03-01)'::tsrange @&gt; '2011-01-10'::timestamp</literal></entry>
-        <entry><literal>t</literal></entry>
+        <entry>
+          <literallayout class="monospaced">'[2011-01-01,2011-03-01)'::tsrange @&gt; '2011-01-10'::timestamp
+'{[2011-01-01,2011-03-01)}'::tsmultirange @&gt; '2011-01-10'::timestamp</literallayout>
+        </entry>
+        <entry><literallayout class="monospaced">t</literallayout></entry>
+       </row>
+
+       <row>
+        <entry> <literal>&lt;@</literal> </entry>
+        <entry>multirange is contained by</entry>
+        <entry>
+          <literallayout class="monospaced">'{[2,4)}'::int4multirange &lt;@ int4range(1,7)
+'{[2,4)}'::int4multirange &lt;@ '{[1,7)}'::int4multirange</literallayout>
+        </entry>
+        <entry><literallayout class="monospaced">t</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>&lt;@</literal> </entry>
         <entry>range is contained by</entry>
-        <entry><literal>int4range(2,4) &lt;@ int4range(1,7)</literal></entry>
-        <entry><literal>t</literal></entry>
+        <entry>
+          <literallayout class="monospaced">int4range(2,4) &lt;@ int4range(1,7)
+int4range(2,4) &lt;@ '{[1,7)}'::int4multirange</literallayout>
+        </entry>
+        <entry><literallayout class="monospaced">t</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>&lt;@</literal> </entry>
         <entry>element is contained by</entry>
-        <entry><literal>42 &lt;@ int4range(1,7)</literal></entry>
-        <entry><literal>f</literal></entry>
+        <entry>
+          <literallayout class="monospaced">42 &lt;@ int4range(1,7)
+42 &lt;@ '{[1,7)}'::int4multirange</literallayout>
+        </entry>
+        <entry><literallayout class="monospaced">f</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>&amp;&amp;</literal> </entry>
         <entry>overlap (have points in common)</entry>
-        <entry><literal>int8range(3,7) &amp;&amp; int8range(4,12)</literal></entry>
-        <entry><literal>t</literal></entry>
+        <entry>
+          <literallayout class="monospaced">int8range(3,7) &amp;&amp; int8range(4,12)
+int8range(3,7) &amp;&amp; '{[4,12)}'::int8multirange
+'{[3,7)}'::int8multirange &amp;&amp; int8range(4,12)
+'{[3,7)}'::int8multirange &amp;&amp; '{[4,12)}'::int8multirange</literallayout>
+        </entry>
+        <entry><literallayout class="monospaced">t</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>&lt;&lt;</literal> </entry>
         <entry>strictly left of</entry>
-        <entry><literal>int8range(1,10) &lt;&lt; int8range(100,110)</literal></entry>
-        <entry><literal>t</literal></entry>
+        <entry>
+          <literallayout class="monospaced">int8range(1,10) &lt;&lt; int8range(100,110)
+int8range(1,10) &lt;&lt; '{[100,110)}'::int8multirange
+'{[1,10)}'::int8multirange &lt;&lt; int8range(100,110)
+'{[1,10)}'::int8multirange &lt;&lt; '{[100,110)}'::int8multirange</literallayout>
+        </entry>
+        <entry><literallayout class="monospaced">t</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>&gt;&gt;</literal> </entry>
         <entry>strictly right of</entry>
-        <entry><literal>int8range(50,60) &gt;&gt; int8range(20,30)</literal></entry>
-        <entry><literal>t</literal></entry>
+        <entry>
+          <literallayout class="monospaced">int8range(50,60) &gt;&gt; int8range(20,30)
+int8range(50,60) &gt;&gt; '{[20,30)}'::int8multirange
+'{[50,60)}'::int8multirange &gt;&gt; int8range(20,30)
+'{[50,60)}'::int8multirange &gt;&gt; '{[20,30)}'::int8multirange</literallayout>
+        </entry>
+        <entry><literallayout class="monospaced">t</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>&amp;&lt;</literal> </entry>
         <entry>does not extend to the right of</entry>
-        <entry><literal>int8range(1,20) &amp;&lt; int8range(18,20)</literal></entry>
-        <entry><literal>t</literal></entry>
+        <entry>
+          <literallayout class="monospaced">int8range(1,20) &amp;&lt; int8range(18,20)
+int8range(1,20) &amp;&lt; '{[18,20)}'::int8multirange
+'{[1,20)}'::int8multirange &amp;&lt; int8range(18,20)
+'{[1,20)}'::int8multirange &amp;&lt; '{[18,20)}'::int8multirange</literallayout>
+        </entry>
+        <entry><literallayout class="monospaced">t</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>&amp;&gt;</literal> </entry>
         <entry>does not extend to the left of</entry>
-        <entry><literal>int8range(7,20) &amp;&gt; int8range(5,10)</literal></entry>
-        <entry><literal>t</literal></entry>
+        <entry>
+          <literallayout class="monospaced">int8range(7,20) &amp;&gt; int8range(5,10)
+int8range(7,20) &amp;&gt; '{[5,10)}'::int8multirange
+'{[7,20)}'::int8multirange &amp;&gt; int8range(5,10)
+'{[7,20)}'::int8multirange &amp;&gt; '{[5,10)}'::int8multirange</literallayout>
+        </entry>
+        <entry><literallayout class="monospaced">t</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>-|-</literal> </entry>
         <entry>is adjacent to</entry>
-        <entry><literal>numrange(1.1,2.2) -|- numrange(2.2,3.3)</literal></entry>
-        <entry><literal>t</literal></entry>
+        <entry>
+          <literallayout class="monospaced">numrange(1.1,2.2) -|- numrange(2.2,3.3)
+numrange(1.1,2.2) -|- '{[2.2,3.3)}'::nummultirange
+'{[1.1,2.2)}'::nummultirange -|- numrange(2.2,3.3)
+'{[1.1,2.2)}'::nummultirange -|- '{[2.2,3.3)}'::nummultirange</literallayout>
+        </entry>
+        <entry><literallayout class="monospaced">t</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>+</literal> </entry>
         <entry>union</entry>
-        <entry><literal>numrange(5,15) + numrange(10,20)</literal></entry>
-        <entry><literal>[5,20)</literal></entry>
+        <entry><literallayout class="monospaced">numrange(5,15) + numrange(10,20)
+'{[5,10)}'::nummultirange + '{[15,20)}'::nummultirange</literallayout></entry>
+        <entry><literallayout class="monospaced">[5,20)
+{[5,10), [15,20)}</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>*</literal> </entry>
         <entry>intersection</entry>
-        <entry><literal>int8range(5,15) * int8range(10,20)</literal></entry>
-        <entry><literal>[10,15)</literal></entry>
+        <entry><literallayout class="monospaced">int8range(5,15) * int8range(10,20)
+'{[5,15)}'::int8multirange * '{[10,20)}'::int8multirange</literallayout></entry>
+        <entry><literallayout class="monospaced">[10,15)
+{[10,15)}</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>-</literal> </entry>
         <entry>difference</entry>
-        <entry><literal>int8range(5,15) - int8range(10,20)</literal></entry>
-        <entry><literal>[5,10)</literal></entry>
+        <entry><literallayout class="monospaced">int8range(5,15) - int8range(10,20)
+'{[5,20)}'::int8multirange - '{[10,15)}'::int8multirange</literallayout></entry>
+        <entry><literallayout class="monospaced">[5,10)
+{[5,10), [15,20)}</literallayout></entry>
        </row>
 
       </tbody>
@@ -14140,19 +14223,31 @@ NULL baz</literallayout>(3 rows)</entry>
 
   <para>
    The left-of/right-of/adjacent operators always return false when an empty
-   range is involved; that is, an empty range is not considered to be either
-   before or after any other range.
+   range or multirange is involved; that is, an empty range is not considered to
+   be either before or after any other range.
+  </para>
+
+  <para>
+   Elsewhere empty ranges and multiranges are treated as the additive identity:
+   anything unioned with an empty value is itself. Anything minus an empty
+   value is itself. An empty multirange has exactly the same points as an empty
+   range. Every range contains the empty range. Every multirange contains as many
+   empty ranges as you like.
   </para>
 
   <para>
    The union and difference operators will fail if the resulting range would
    need to contain two disjoint sub-ranges, as such a range cannot be
-   represented.
+   represented. There are separate operators for union and difference that take
+   multirange parameters and return a multirange, and they do not fail even if
+   their arguments are disjoint. So if you need a union or difference operation
+   for ranges that may be disjoint, you can avoid errors by first casting your
+   ranges to multiranges.
   </para>
 
   <para>
    <xref linkend="range-functions-table"/> shows the functions
-   available for use with range types.
+   available for use with range and multirange types.
   </para>
 
   <indexterm>
@@ -14204,6 +14299,17 @@ NULL baz</literallayout>(3 rows)</entry>
        <row>
         <entry>
          <literal>
+          <function>lower</function>(<type>anymultirange</type>)
+         </literal>
+        </entry>
+        <entry>multirange's element type</entry>
+        <entry>lower bound of multirange</entry>
+        <entry><literal>lower('{[1.1,2.2)}'::nummultirange)</literal></entry>
+        <entry><literal>1.1</literal></entry>
+       </row>
+       <row>
+        <entry>
+         <literal>
           <function>upper</function>(<type>anyrange</type>)
          </literal>
         </entry>
@@ -14215,6 +14321,17 @@ NULL baz</literallayout>(3 rows)</entry>
        <row>
         <entry>
          <literal>
+          <function>upper</function>(<type>anymultirange</type>)
+         </literal>
+        </entry>
+        <entry>multirange's element type</entry>
+        <entry>upper bound of multirange</entry>
+        <entry><literal>upper('{[1.1,2.2)}'::nummultirange)</literal></entry>
+        <entry><literal>2.2</literal></entry>
+       </row>
+       <row>
+        <entry>
+         <literal>
           <function>isempty</function>(<type>anyrange</type>)
          </literal>
         </entry>
@@ -14226,6 +14343,17 @@ NULL baz</literallayout>(3 rows)</entry>
        <row>
         <entry>
          <literal>
+          <function>isempty</function>(<type>anymultirange</type>)
+         </literal>
+        </entry>
+        <entry><type>boolean</type></entry>
+        <entry>is the multirange empty?</entry>
+        <entry><literal>isempty('{[1.1,2.2)}'::nummultirange)</literal></entry>
+        <entry><literal>false</literal></entry>
+       </row>
+       <row>
+        <entry>
+         <literal>
           <function>lower_inc</function>(<type>anyrange</type>)
          </literal>
         </entry>
@@ -14237,6 +14365,17 @@ NULL baz</literallayout>(3 rows)</entry>
        <row>
         <entry>
          <literal>
+          <function>lower_inc</function>(<type>anymultirange</type>)
+         </literal>
+        </entry>
+        <entry><type>boolean</type></entry>
+        <entry>is the lower bound inclusive?</entry>
+        <entry><literal>lower_inc('{[1.1,2.2)}'::nummultirange)</literal></entry>
+        <entry><literal>true</literal></entry>
+       </row>
+       <row>
+        <entry>
+         <literal>
           <function>upper_inc</function>(<type>anyrange</type>)
          </literal>
         </entry>
@@ -14248,6 +14387,17 @@ NULL baz</literallayout>(3 rows)</entry>
        <row>
         <entry>
          <literal>
+          <function>upper_inc</function>(<type>anymultirange</type>)
+         </literal>
+        </entry>
+        <entry><type>boolean</type></entry>
+        <entry>is the upper bound inclusive?</entry>
+        <entry><literal>upper_inc('{[1.1,2.2)}'::nummultirange)</literal></entry>
+        <entry><literal>false</literal></entry>
+       </row>
+       <row>
+        <entry>
+         <literal>
           <function>lower_inf</function>(<type>anyrange</type>)
          </literal>
         </entry>
@@ -14259,6 +14409,17 @@ NULL baz</literallayout>(3 rows)</entry>
        <row>
         <entry>
          <literal>
+          <function>lower_inf</function>(<type>anymultirange</type>)
+         </literal>
+        </entry>
+        <entry><type>boolean</type></entry>
+        <entry>is the lower bound infinite?</entry>
+        <entry><literal>lower_inf('{(,)}'::datemultirange)</literal></entry>
+        <entry><literal>true</literal></entry>
+       </row>
+       <row>
+        <entry>
+         <literal>
           <function>upper_inf</function>(<type>anyrange</type>)
          </literal>
         </entry>
@@ -14270,6 +14431,17 @@ NULL baz</literallayout>(3 rows)</entry>
        <row>
         <entry>
          <literal>
+          <function>upper_inf</function>(<type>anymultirange</type>)
+         </literal>
+        </entry>
+        <entry><type>boolean</type></entry>
+        <entry>is the upper bound infinite?</entry>
+        <entry><literal>upper_inf('{(,)}'::datemultirange)</literal></entry>
+        <entry><literal>true</literal></entry>
+       </row>
+       <row>
+        <entry>
+         <literal>
           <function>range_merge</function>(<type>anyrange</type>, <type>anyrange</type>)
          </literal>
         </entry>
@@ -14278,16 +14450,38 @@ NULL baz</literallayout>(3 rows)</entry>
         <entry><literal>range_merge('[1,2)'::int4range, '[3,4)'::int4range)</literal></entry>
         <entry><literal>[1,4)</literal></entry>
        </row>
+       <row>
+        <entry>
+         <literal>
+          <function>range_merge</function>(<type>anymultirange</type>)
+         </literal>
+        </entry>
+        <entry><type>anyrange</type></entry>
+        <entry>the smallest range which includes the entire multirange</entry>
+        <entry><literal>range_merge('{[1,2), [3,4)}'::int4multirange)</literal></entry>
+        <entry><literal>[1,4)</literal></entry>
+       </row>
+       <row>
+        <entry>
+         <literal>
+          <function>multirange</function>(<type>anyrange</type>)
+         </literal>
+        </entry>
+        <entry><type>anymultirange</type></entry>
+        <entry>a multirange containing just the given range</entry>
+        <entry><literal>multirange('[1,2)'::int4range)</literal></entry>
+        <entry><literal>{[1,2)}</literal></entry>
+       </row>
       </tbody>
      </tgroup>
     </table>
 
   <para>
    The <function>lower</function> and  <function>upper</function> functions return null
-   if the range is empty or the requested bound is infinite.
+   if the input range/multirange is empty or the requested bound is infinite.
    The <function>lower_inc</function>, <function>upper_inc</function>,
    <function>lower_inf</function>, and <function>upper_inf</function>
-   functions all return false for an empty range.
+   functions all return false for an empty range or multirange.
   </para>
   </sect1>
 
@@ -14609,6 +14803,44 @@ NULL baz</literallayout>(3 rows)</entry>
      <row>
       <entry>
        <indexterm>
+        <primary>range_agg</primary>
+       </indexterm>
+       <function>
+         range_agg(<replaceable class="parameter">expression</replaceable>)
+       </function>
+      </entry>
+      <entry>
+       <type>anyrange</type>
+      </entry>
+      <entry>
+       <type>multirange</type> of the argument range type
+      </entry>
+      <entry>Yes</entry>
+      <entry>union of the non-null input values</entry>
+     </row>
+
+     <row>
+      <entry>
+       <indexterm>
+        <primary>range_intersect_agg</primary>
+       </indexterm>
+       <function>
+         range_intersect_agg(<replaceable class="parameter">expression</replaceable>)
+       </function>
+      </entry>
+      <entry>
+       <type>anyrange</type>
+      </entry>
+      <entry>
+       <type>multirange</type> of the argument range type
+      </entry>
+      <entry>No</entry>
+      <entry>intersection of the non-null input values</entry>
+     </row>
+
+     <row>
+      <entry>
+       <indexterm>
         <primary>string_agg</primary>
        </indexterm>
        <function>
diff --git a/doc/src/sgml/rangetypes.sgml b/doc/src/sgml/rangetypes.sgml
index b75fb3a392..c8784bdce3 100644
--- a/doc/src/sgml/rangetypes.sgml
+++ b/doc/src/sgml/rangetypes.sgml
@@ -27,6 +27,13 @@
   ranges from an instrument, and so forth can also be useful.
  </para>
 
+ <para>
+  Every range type has a corresponding multirange type. A multirange is
+  an ordered list of non-continguous, non-empty, non-null ranges. Most
+  range operators also work on multiranges, and they have a few functions
+  of their own.
+ </para>
+
  <sect2 id="rangetypes-builtin">
   <title>Built-in Range Types</title>
 
@@ -232,10 +239,30 @@ SELECT '[4,4]'::int4range;
 SELECT '[4,4)'::int4range;
 </programlisting>
   </para>
+
+  <para>
+   The input for a multirange is curly brackets (<literal>{</literal> and
+   <literal>}</literal>) containing zero or more valid ranges,
+   separated by commas. Whitespace is permitted around the brackets and
+   commas. This is intended to be reminiscent of array syntax, although
+   multiranges are much simpler: they have just one dimension and there is
+   no need to quote their contents. (The bounds of their ranges may be
+   quoted as above however.)
+  </para>
+
+  <para>
+  Examples:
+<programlisting>
+SELECT '{}'::int4multirange;
+SELECT '{[3,7)}'::int4multirange;
+SELECT '{[3,7), [8,9)}'::int4multirange;
+</programlisting>
+  </para>
+
  </sect2>
 
  <sect2 id="rangetypes-construct">
-  <title>Constructing Ranges</title>
+  <title>Constructing Ranges and Multiranges</title>
 
   <para>
    Each range type has a constructor function with the same name as the range
@@ -269,6 +296,19 @@ SELECT int8range(1, 14, '(]');
 SELECT numrange(NULL, 2.2);
 </programlisting>
   </para>
+
+  <para>
+   Each range type also has a multirange constructor with the same name as the
+   multirange type.  The constructor function takes zero or more arguments
+   which are all ranges of the appropriate type.
+   For example:
+
+<programlisting>
+SELECT nummultirange();
+SELECT nummultirange(numrange(1.0, 14.0));
+SELECT nummultirange(numrange(1.0, 14.0), numrange(20.0, 25.0));
+</programlisting>
+  </para>
  </sect2>
 
  <sect2 id="rangetypes-discrete">
@@ -342,6 +382,11 @@ SELECT '[1.234, 5.678]'::floatrange;
   </para>
 
   <para>
+   When you define your own range you automatically get a corresponding
+   multirange type.
+  </para>
+
+  <para>
    Defining your own range type also allows you to specify a different
    subtype B-tree operator class or collation to use, so as to change the sort
    ordering that determines which values fall into a given range.
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index d5da81c801..eedd7eadb3 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -25,6 +25,7 @@ OBJS = \
 	objectaddress.o \
 	partition.o \
 	pg_aggregate.o \
+	pg_cast.o \
 	pg_collation.o \
 	pg_constraint.o \
 	pg_conversion.o \
diff --git a/src/backend/catalog/pg_cast.c b/src/backend/catalog/pg_cast.c
new file mode 100644
index 0000000000..50b44e3a96
--- /dev/null
+++ b/src/backend/catalog/pg_cast.c
@@ -0,0 +1,116 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_cast.c
+ *	  routines to support manipulation of the pg_cast relation
+ *
+ * Portions Copyright (c) 1996-2019, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ *	  src/backend/catalog/pg_cast.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "catalog/pg_cast.h"
+#include "access/table.h"
+#include "catalog/catalog.h"
+#include "catalog/dependency.h"
+#include "catalog/indexing.h"
+#include "catalog/objectaccess.h"
+#include "catalog/pg_proc.h"
+#include "catalog/pg_type.h"
+#include "tcop/tcopprot.h"
+#include "utils/builtins.h"
+#include "utils/rel.h"
+#include "utils/syscache.h"
+
+/*
+ * ----------------------------------------------------------------
+ *		CastCreate
+ * ----------------------------------------------------------------
+ */
+ObjectAddress
+CastCreate(Oid sourcetypeid, Oid targettypeid, Oid funcid, char castcontext,
+		   char castmethod, DependencyType behavior)
+{
+	Relation		relation;
+	HeapTuple		tuple;
+	Oid				castid;
+	Datum			values[Natts_pg_cast];
+	bool			nulls[Natts_pg_cast];
+	ObjectAddress	myself,
+					referenced;
+
+	relation = table_open(CastRelationId, RowExclusiveLock);
+
+	/*
+	 * Check for duplicate.  This is just to give a friendly error message,
+	 * the unique index would catch it anyway (so no need to sweat about race
+	 * conditions).
+	 */
+	tuple = SearchSysCache2(CASTSOURCETARGET,
+							ObjectIdGetDatum(sourcetypeid),
+							ObjectIdGetDatum(targettypeid));
+	if (HeapTupleIsValid(tuple))
+		ereport(ERROR,
+				(errcode(ERRCODE_DUPLICATE_OBJECT),
+				 errmsg("cast from type %s to type %s already exists",
+						format_type_be(sourcetypeid),
+						format_type_be(targettypeid))));
+
+	/* ready to go */
+	castid = GetNewOidWithIndex(relation, CastOidIndexId, Anum_pg_cast_oid);
+	values[Anum_pg_cast_oid - 1] = ObjectIdGetDatum(castid);
+	values[Anum_pg_cast_castsource - 1] = ObjectIdGetDatum(sourcetypeid);
+	values[Anum_pg_cast_casttarget - 1] = ObjectIdGetDatum(targettypeid);
+	values[Anum_pg_cast_castfunc - 1] = ObjectIdGetDatum(funcid);
+	values[Anum_pg_cast_castcontext - 1] = CharGetDatum(castcontext);
+	values[Anum_pg_cast_castmethod - 1] = CharGetDatum(castmethod);
+
+	MemSet(nulls, false, sizeof(nulls));
+
+	tuple = heap_form_tuple(RelationGetDescr(relation), values, nulls);
+
+	CatalogTupleInsert(relation, tuple);
+
+	/* make dependency entries */
+	myself.classId = CastRelationId;
+	myself.objectId = castid;
+	myself.objectSubId = 0;
+
+	/* dependency on source type */
+	referenced.classId = TypeRelationId;
+	referenced.objectId = sourcetypeid;
+	referenced.objectSubId = 0;
+	recordDependencyOn(&myself, &referenced, behavior);
+
+	/* dependency on target type */
+	referenced.classId = TypeRelationId;
+	referenced.objectId = targettypeid;
+	referenced.objectSubId = 0;
+	recordDependencyOn(&myself, &referenced, behavior);
+
+	/* dependency on function */
+	if (OidIsValid(funcid))
+	{
+		referenced.classId = ProcedureRelationId;
+		referenced.objectId = funcid;
+		referenced.objectSubId = 0;
+		recordDependencyOn(&myself, &referenced, behavior);
+	}
+
+	/* dependency on extension */
+	recordDependencyOnCurrentExtension(&myself, false);
+
+	/* Post creation hook for new cast */
+	InvokeObjectPostCreateHook(CastRelationId, castid, 0);
+
+	heap_freetuple(tuple);
+
+	table_close(relation, RowExclusiveLock);
+
+	return myself;
+}
diff --git a/src/backend/catalog/pg_proc.c b/src/backend/catalog/pg_proc.c
index 5194dcaac0..6efcac52a0 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,13 @@ 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 b5bc36c2bd..1217a6ddd0 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_rngmultitypid - 1] = ObjectIdGetDatum(multirangeTypeOid);
 
 	tup = heap_form_tuple(RelationGetDescr(pg_range), values, nulls);
 
@@ -100,6 +102,12 @@ 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 8d7572da51..d74697ef9c 100644
--- a/src/backend/catalog/pg_type.c
+++ b/src/backend/catalog/pg_type.c
@@ -27,6 +27,7 @@
 #include "catalog/pg_proc.h"
 #include "catalog/pg_type.h"
 #include "commands/typecmds.h"
+#include "mb/pg_wchar.h"
 #include "miscadmin.h"
 #include "parser/scansup.h"
 #include "utils/acl.h"
@@ -36,6 +37,9 @@
 #include "utils/rel.h"
 #include "utils/syscache.h"
 
+static char *makeUniqueTypeName(const char *typeName, Oid typeNamespace,
+								bool tryOriginal);
+
 /* Potentially set by pg_upgrade_support functions */
 Oid			binary_upgrade_next_pg_type_oid = InvalidOid;
 
@@ -783,31 +787,10 @@ RenameTypeInternal(Oid typeOid, const char *newTypeName, Oid typeNamespace)
 char *
 makeArrayTypeName(const char *typeName, Oid typeNamespace)
 {
-	char	   *arr = (char *) palloc(NAMEDATALEN);
-	int			namelen = strlen(typeName);
-	int			i;
-
-	/*
-	 * 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;
-	}
+	char	   *arr;
 
-	if (i >= NAMEDATALEN - 1)
+	arr = makeUniqueTypeName(typeName, typeNamespace, false);
+	if (arr == NULL)
 		ereport(ERROR,
 				(errcode(ERRCODE_DUPLICATE_OBJECT),
 				 errmsg("could not form array type name for type \"%s\"",
@@ -878,3 +861,91 @@ moveArrayTypeName(Oid typeOid, const char *typeName, Oid typeNamespace)
 
 	return true;
 }
+
+
+/*
+ * makeMultirangeTypeName
+ *	  - given a range type name, make a multirange type name for it
+ *
+ * caller is responsible for pfreeing the result
+ */
+char *
+makeMultirangeTypeName(const char *rangeTypeName, Oid typeNamespace)
+{
+	char	   *buf;
+	char	   *mrname;
+	char	   *rangestr;
+
+	/*
+	 * 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...
+	 */
+	rangestr = strstr(rangeTypeName, "range");
+	if (rangestr)
+	{
+		char	*prefix = pnstrdup(rangeTypeName, rangestr - rangeTypeName);
+
+		buf = psprintf("%s%s%s", prefix, "multi", rangestr);
+	}
+	else
+		buf = psprintf("%s_multirange", pnstrdup(rangeTypeName, NAMEDATALEN - 12));
+
+	/* clip it at NAMEDATALEN-1 bytes */
+	buf[pg_mbcliplen(buf, strlen(buf), NAMEDATALEN - 1)] = '\0';
+
+	mrname = makeUniqueTypeName(buf, typeNamespace, true);
+	if (mrname == NULL)
+		ereport(ERROR,
+				(errcode(ERRCODE_DUPLICATE_OBJECT),
+				 errmsg("could not form multirange type name for type \"%s\"",
+						rangeTypeName)));
+
+	return mrname;
+}
+
+/*
+ * makeUniqueTypeName
+ *		Generate a unique name for a prospective new type
+ *
+ * Given a typeName, return a new palloc'ed name by preprending underscores
+ * until a non-conflicting name results.
+ *
+ * If tryOriginal, first try with zero underscores.
+ */
+static char *
+makeUniqueTypeName(const char *typeName, Oid typeNamespace, bool tryOriginal)
+{
+	int			i;
+	int			namelen;
+	char		dest[NAMEDATALEN];
+
+	Assert(strlen(typeName) <= NAMEDATALEN - 1);
+
+	if (tryOriginal &&
+		!SearchSysCacheExists2(TYPENAMENSP,
+							   CStringGetDatum(typeName),
+							   ObjectIdGetDatum(typeNamespace)))
+		return pstrdup(typeName);
+
+	/*
+	 * The idea is to prepend underscores as needed until we make a name that
+	 * doesn't collide with anything ...
+	 */
+	namelen = strlen(typeName);
+	for (i = 1; i < NAMEDATALEN - 1; i++)
+	{
+		dest[i - 1] = '_';
+		strlcpy(dest + i, typeName, NAMEDATALEN - i);
+		if (namelen + i >= NAMEDATALEN)
+			truncate_identifier(dest, NAMEDATALEN, false);
+
+		if (!SearchSysCacheExists2(TYPENAMENSP,
+								   CStringGetDatum(dest),
+								   ObjectIdGetDatum(typeNamespace)))
+			return pstrdup(dest);
+	}
+
+	return NULL;
+}
diff --git a/src/backend/commands/functioncmds.c b/src/backend/commands/functioncmds.c
index c31c57e5e9..732487ff8d 100644
--- a/src/backend/commands/functioncmds.c
+++ b/src/backend/commands/functioncmds.c
@@ -1497,17 +1497,12 @@ CreateCast(CreateCastStmt *stmt)
 	char		sourcetyptype;
 	char		targettyptype;
 	Oid			funcid;
-	Oid			castid;
 	int			nargs;
 	char		castcontext;
 	char		castmethod;
-	Relation	relation;
 	HeapTuple	tuple;
-	Datum		values[Natts_pg_cast];
-	bool		nulls[Natts_pg_cast];
-	ObjectAddress myself,
-				referenced;
 	AclResult	aclresult;
+	ObjectAddress	myself;
 
 	sourcetypeid = typenameTypeId(NULL, stmt->sourcetype);
 	targettypeid = typenameTypeId(NULL, stmt->targettype);
@@ -1731,74 +1726,8 @@ CreateCast(CreateCastStmt *stmt)
 			break;
 	}
 
-	relation = table_open(CastRelationId, RowExclusiveLock);
-
-	/*
-	 * Check for duplicate.  This is just to give a friendly error message,
-	 * the unique index would catch it anyway (so no need to sweat about race
-	 * conditions).
-	 */
-	tuple = SearchSysCache2(CASTSOURCETARGET,
-							ObjectIdGetDatum(sourcetypeid),
-							ObjectIdGetDatum(targettypeid));
-	if (HeapTupleIsValid(tuple))
-		ereport(ERROR,
-				(errcode(ERRCODE_DUPLICATE_OBJECT),
-				 errmsg("cast from type %s to type %s already exists",
-						format_type_be(sourcetypeid),
-						format_type_be(targettypeid))));
-
-	/* ready to go */
-	castid = GetNewOidWithIndex(relation, CastOidIndexId, Anum_pg_cast_oid);
-	values[Anum_pg_cast_oid - 1] = ObjectIdGetDatum(castid);
-	values[Anum_pg_cast_castsource - 1] = ObjectIdGetDatum(sourcetypeid);
-	values[Anum_pg_cast_casttarget - 1] = ObjectIdGetDatum(targettypeid);
-	values[Anum_pg_cast_castfunc - 1] = ObjectIdGetDatum(funcid);
-	values[Anum_pg_cast_castcontext - 1] = CharGetDatum(castcontext);
-	values[Anum_pg_cast_castmethod - 1] = CharGetDatum(castmethod);
-
-	MemSet(nulls, false, sizeof(nulls));
-
-	tuple = heap_form_tuple(RelationGetDescr(relation), values, nulls);
-
-	CatalogTupleInsert(relation, tuple);
-
-	/* make dependency entries */
-	myself.classId = CastRelationId;
-	myself.objectId = castid;
-	myself.objectSubId = 0;
-
-	/* dependency on source type */
-	referenced.classId = TypeRelationId;
-	referenced.objectId = sourcetypeid;
-	referenced.objectSubId = 0;
-	recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
-
-	/* dependency on target type */
-	referenced.classId = TypeRelationId;
-	referenced.objectId = targettypeid;
-	referenced.objectSubId = 0;
-	recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
-
-	/* dependency on function */
-	if (OidIsValid(funcid))
-	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = funcid;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
-	}
-
-	/* dependency on extension */
-	recordDependencyOnCurrentExtension(&myself, false);
-
-	/* Post creation hook for new cast */
-	InvokeObjectPostCreateHook(CastRelationId, castid, 0);
-
-	heap_freetuple(tuple);
-
-	table_close(relation, RowExclusiveLock);
-
+	myself = CastCreate(sourcetypeid, targettypeid, funcid, castcontext,
+						castmethod, DEPENDENCY_NORMAL);
 	return myself;
 }
 
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index 52097363fd..a8dea8d58e 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -42,6 +42,7 @@
 #include "catalog/objectaccess.h"
 #include "catalog/pg_am.h"
 #include "catalog/pg_authid.h"
+#include "catalog/pg_cast.h"
 #include "catalog/pg_collation.h"
 #include "catalog/pg_constraint.h"
 #include "catalog/pg_depend.h"
@@ -85,9 +86,14 @@ typedef struct
 
 /* Potentially set by pg_upgrade_support functions */
 Oid			binary_upgrade_next_array_pg_type_oid = InvalidOid;
+Oid			binary_upgrade_next_mrng_pg_type_oid = InvalidOid;
+Oid			binary_upgrade_next_mrng_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 rangeOid,
+									   Oid rangeArrayOid, Oid *castFuncOid);
 static Oid	findTypeInputFunction(List *procname, Oid typeOid);
 static Oid	findTypeOutputFunction(List *procname, Oid typeOid);
 static Oid	findTypeReceiveFunction(List *procname, Oid typeOid);
@@ -811,7 +817,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",
@@ -1346,6 +1353,11 @@ checkEnumOwner(HeapTuple tup)
 /*
  * DefineRange
  *		Registers a new range type.
+ *
+ * Perhaps it might be worthwhile to set pg_type.typelem to the base type,
+ * and likewise on multiranges to set it to the range type. But having a
+ * non-zero typelem is treated elsewhere as a synonym for being an array,
+ * and users might have queries with that same assumption.
  */
 ObjectAddress
 DefineRange(CreateRangeStmt *stmt)
@@ -1354,7 +1366,11 @@ DefineRange(CreateRangeStmt *stmt)
 	Oid			typeNamespace;
 	Oid			typoid;
 	char	   *rangeArrayName;
+	char	   *multirangeTypeName;
+	char	   *multirangeArrayName;
 	Oid			rangeArrayOid;
+	Oid			multirangeOid;
+	Oid			multirangeArrayOid;
 	Oid			rangeSubtype = InvalidOid;
 	List	   *rangeSubOpclassName = NIL;
 	List	   *rangeCollationName = NIL;
@@ -1371,6 +1387,8 @@ DefineRange(CreateRangeStmt *stmt)
 	AclResult	aclresult;
 	ListCell   *lc;
 	ObjectAddress address;
+	ObjectAddress mltrngaddress;
+	Oid			castFuncOid;
 
 	/* Convert list of names to a name and namespace */
 	typeNamespace = QualifiedNameGetCreationNamespace(stmt->typeName,
@@ -1519,8 +1537,10 @@ DefineRange(CreateRangeStmt *stmt)
 	/* alignment must be 'i' or 'd' for ranges */
 	alignment = (subtypalign == 'd') ? 'd' : 'i';
 
-	/* Allocate OID for array type */
+	/* Allocate OID for array type, its multirange, and its multirange array */
 	rangeArrayOid = AssignTypeArrayOid();
+	multirangeOid = AssignTypeMultirangeOid();
+	multirangeArrayOid = AssignTypeMultirangeArrayOid();
 
 	/* Create the pg_type entry */
 	address =
@@ -1557,9 +1577,46 @@ 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(multirangeOid,	/* force assignment of this 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) */
+	Assert(multirangeOid == mltrngaddress.objectId);
+
 	/* Create the entry in pg_range */
 	RangeCreate(typoid, rangeSubtype, rangeCollation, rangeSubOpclass,
-				rangeCanonical, rangeSubtypeDiff);
+				rangeCanonical, rangeSubtypeDiff, multirangeOid);
 
 	/*
 	 * Create the array type that goes with it.
@@ -1600,8 +1657,53 @@ 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 */
+			   multirangeOid,	/* 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,
+							   multirangeOid, typoid, rangeArrayOid,
+							   &castFuncOid);
+
+	/* Create cast from the range type to its multirange type */
+	CastCreate(typoid, multirangeOid, castFuncOid, 'e', 'f', DEPENDENCY_INTERNAL);
+
+	pfree(multirangeTypeName);
+	pfree(multirangeArrayName);
 
 	return address;
 }
@@ -1679,6 +1781,147 @@ 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.
+ *
+ * Sets castFuncOid to the oid of the new constructor that can be used
+ * to cast from a range to a multirange.
+ */
+static void
+makeMultirangeConstructors(const char *name, Oid namespace,
+						   Oid multirangeOid, Oid rangeOid, Oid rangeArrayOid,
+						   Oid *castFuncOid)
+{
+	ObjectAddress myself,
+				referenced;
+	oidvector  *argtypes;
+	Datum		allParamTypes;
+	ArrayType  *allParameterTypes;
+	Datum		paramModes;
+	ArrayType  *parameterModes;
+
+	referenced.classId = TypeRelationId;
+	referenced.objectId = multirangeOid;
+	referenced.objectSubId = 0;
+
+	/* 0-arg constructor - for empty multiranges */
+	argtypes = buildoidvector(NULL, 0);
+	myself = ProcedureCreate(name,	/* name: same as multirange type */
+							 namespace,
+							 false,	/* replace */
+							 false,	/* returns set */
+							 multirangeOid,	/* return type */
+							 BOOTSTRAP_SUPERUSERID,	/* proowner */
+							 INTERNALlanguageId,	/* language */
+							 F_FMGR_INTERNAL_VALIDATOR,
+							 "multirange_constructor0",	/* prosrc */
+							 NULL,	/* probin */
+							 PROKIND_FUNCTION,
+							 false,	/* security_definer */
+							 false,	/* leakproof */
+							 false, /* isStrict */
+							 PROVOLATILE_IMMUTABLE,	/* volatility */
+							 PROPARALLEL_SAFE,	/* parallel safety */
+							 argtypes,	/* parameterTypes */
+							 PointerGetDatum(NULL),	/* allParameterTypes */
+							 PointerGetDatum(NULL),	/* parameterModes */
+							 PointerGetDatum(NULL),	/* parameterNames */
+							 NIL,	/* parameterDefaults */
+							 PointerGetDatum(NULL),	/* trftypes */
+							 PointerGetDatum(NULL),	/* proconfig */
+							 InvalidOid,	/* prosupport */
+							 1.0,	/* procost */
+							 0.0);	/* prorows */
+	/*
+	 * Make the constructor 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);
+	pfree(argtypes);
+
+	/*
+	 * 1-arg constructor - for casts
+	 *
+	 * In theory we shouldn't need both this and the vararg (n-arg) constructor,
+	 * but having a separate 1-arg function lets us define casts against it.
+	 */
+	argtypes = buildoidvector(&rangeOid, 1);
+	myself = ProcedureCreate(name,	/* name: same as multirange type */
+							 namespace,
+							 false,	/* replace */
+							 false,	/* returns set */
+							 multirangeOid,	/* return type */
+							 BOOTSTRAP_SUPERUSERID,	/* proowner */
+							 INTERNALlanguageId,	/* language */
+							 F_FMGR_INTERNAL_VALIDATOR,
+							 "multirange_constructor1",	/* prosrc */
+							 NULL,	/* probin */
+							 PROKIND_FUNCTION,
+							 false,	/* security_definer */
+							 false,	/* leakproof */
+							 true, /* isStrict */
+							 PROVOLATILE_IMMUTABLE,	/* volatility */
+							 PROPARALLEL_SAFE,	/* parallel safety */
+							 argtypes,	/* parameterTypes */
+							 PointerGetDatum(NULL),	/* allParameterTypes */
+							 PointerGetDatum(NULL),	/* parameterModes */
+							 PointerGetDatum(NULL),	/* parameterNames */
+							 NIL,	/* parameterDefaults */
+							 PointerGetDatum(NULL),	/* trftypes */
+							 PointerGetDatum(NULL),	/* proconfig */
+							 InvalidOid,	/* prosupport */
+							 1.0,	/* procost */
+							 0.0);	/* prorows */
+	/* ditto */
+	recordDependencyOn(&myself, &referenced, DEPENDENCY_INTERNAL);
+	pfree(argtypes);
+	*castFuncOid = myself.objectId;
+
+	/* n-arg constructor - vararg */
+	argtypes = buildoidvector(&rangeArrayOid, 1);
+	allParamTypes = ObjectIdGetDatum(rangeArrayOid);
+	allParameterTypes = construct_array(&allParamTypes,
+										1, OIDOID,
+										sizeof(Oid), true, 'i');
+	paramModes = CharGetDatum(FUNC_PARAM_VARIADIC);
+	parameterModes = construct_array(&paramModes, 1, CHAROID,
+									 1, true, 'c');
+	myself = ProcedureCreate(name,	/* name: same as multirange type */
+							 namespace,
+							 false,	/* replace */
+							 false,	/* returns set */
+							 multirangeOid,	/* return type */
+							 BOOTSTRAP_SUPERUSERID,	/* proowner */
+							 INTERNALlanguageId,	/* language */
+							 F_FMGR_INTERNAL_VALIDATOR,
+							 "multirange_constructor2",	/* prosrc */
+							 NULL,	/* probin */
+							 PROKIND_FUNCTION,
+							 false,	/* security_definer */
+							 false,	/* leakproof */
+							 false, /* isStrict */
+							 PROVOLATILE_IMMUTABLE,	/* volatility */
+							 PROPARALLEL_SAFE,	/* parallel safety */
+							 argtypes,	/* parameterTypes */
+							 PointerGetDatum(allParameterTypes),	/* allParameterTypes */
+							 PointerGetDatum(parameterModes),	/* parameterModes */
+							 PointerGetDatum(NULL),	/* parameterNames */
+							 NIL,	/* parameterDefaults */
+							 PointerGetDatum(NULL),	/* trftypes */
+							 PointerGetDatum(NULL),	/* proconfig */
+							 InvalidOid,	/* prosupport */
+							 1.0,	/* procost */
+							 0.0);	/* prorows */
+	/* ditto */
+	recordDependencyOn(&myself, &referenced, DEPENDENCY_INTERNAL);
+	pfree(argtypes);
+	pfree(allParameterTypes);
+	pfree(parameterModes);
+}
 
 /*
  * Find suitable I/O functions for a type.
@@ -2104,6 +2347,72 @@ AssignTypeArrayOid(void)
 	return type_array_oid;
 }
 
+/*
+ *	AssignTypeMultirangeOid
+ *
+ *	Pre-assign the range type's multirange OID for use in pg_type.oid
+ */
+Oid
+AssignTypeMultirangeOid(void)
+{
+	Oid			type_multirange_oid;
+
+	/* Use binary-upgrade override for pg_type.oid? */
+	if (IsBinaryUpgrade)
+	{
+		if (!OidIsValid(binary_upgrade_next_mrng_pg_type_oid))
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+					 errmsg("pg_type multirange OID value not set when in binary upgrade mode")));
+
+		type_multirange_oid = binary_upgrade_next_mrng_pg_type_oid;
+		binary_upgrade_next_mrng_pg_type_oid = InvalidOid;
+	}
+	else
+	{
+		Relation	pg_type = table_open(TypeRelationId, AccessShareLock);
+
+		type_multirange_oid = GetNewOidWithIndex(pg_type, TypeOidIndexId,
+												 Anum_pg_type_oid);
+		table_close(pg_type, AccessShareLock);
+	}
+
+	return type_multirange_oid;
+}
+
+/*
+ *	AssignTypeMultirangeArrayOid
+ *
+ *	Pre-assign the range type's multirange array OID for use in pg_type.typarray
+ */
+Oid
+AssignTypeMultirangeArrayOid(void)
+{
+	Oid			type_multirange_array_oid;
+
+	/* Use binary-upgrade override for pg_type.oid? */
+	if (IsBinaryUpgrade)
+	{
+		if (!OidIsValid(binary_upgrade_next_mrng_array_pg_type_oid))
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+					 errmsg("pg_type multirange array OID value not set when in binary upgrade mode")));
+
+		type_multirange_array_oid = binary_upgrade_next_mrng_array_pg_type_oid;
+		binary_upgrade_next_mrng_array_pg_type_oid = InvalidOid;
+	}
+	else
+	{
+		Relation	pg_type = table_open(TypeRelationId, AccessShareLock);
+
+		type_multirange_array_oid = GetNewOidWithIndex(pg_type, TypeOidIndexId,
+													   Anum_pg_type_oid);
+		table_close(pg_type, AccessShareLock);
+	}
+
+	return type_multirange_array_oid;
+}
+
 
 /*-------------------------------------------------------------------
  * DefineCompositeType
diff --git a/src/backend/parser/parse_coerce.c b/src/backend/parser/parse_coerce.c
index 929f758ef4..47d17f43a4 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
@@ -1481,6 +1483,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;
@@ -1527,6 +1531,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 */
@@ -1577,6 +1590,39 @@ check_generic_type_consistency(const Oid *actual_arg_types,
 			/* otherwise, they better match */
 			return false;
 		}
+		else
+			range_typelem = InvalidOid;	/* keep compiler quiet */
+	}
+
+	/* 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)
@@ -1680,13 +1726,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.
@@ -1744,7 +1794,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;
@@ -1762,6 +1812,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;
+		}
 	}
 
 	/*
@@ -1812,9 +1882,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
@@ -1846,6 +1919,71 @@ enforce_generic_type_consistency(const Oid *actual_arg_types,
 							   format_type_be(range_typeid),
 							   format_type_be(elem_typeid))));
 		}
+		else
+			range_typelem = InvalidOid;
+	}
+
+	/* 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))
@@ -1855,6 +1993,7 @@ enforce_generic_type_consistency(const Oid *actual_arg_types,
 			elem_typeid = ANYELEMENTOID;
 			array_typeid = ANYARRAYOID;
 			range_typeid = ANYRANGEOID;
+			multirange_typeid = ANYMULTIRANGEOID;
 		}
 		else
 		{
@@ -1927,6 +2066,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;
+			}
 		}
 	}
 
@@ -1958,6 +2108,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 ||
@@ -2006,8 +2172,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);
@@ -2019,11 +2184,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)
 		{
@@ -2051,6 +2242,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)
@@ -2059,6 +2263,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 */
@@ -2173,6 +2445,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..f1342181a0
--- /dev/null
+++ b/src/backend/utils/adt/multirangetypes.c
@@ -0,0 +1,2171 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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;
+	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 = NULL;
+	int32		range_str_len;
+	char	   *range_str_copy;
+
+	cache = get_multirange_io_data(fcinfo, mltrngtypoid, IOFunc_input);
+	rangetyp = cache->typcache->rngtype;
+
+	/* 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]);
+		uint32		range_len;
+		char	   *range_data;
+
+		range = PointerGetDatum(SendFunctionCall(&cache->proc, range));
+		range_len = VARSIZE(range) - VARHDRSZ;
+		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_constructor2(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));
+}
+
+/*
+ * Construct multirange value from a single range.
+ * It'd be nice if we could just use multirange_constructor2
+ * for this case, but we need a non-variadic single-arg function
+ * to let us define a CAST from a range to its multirange.
+ */
+Datum
+multirange_constructor1(PG_FUNCTION_ARGS)
+{
+	Oid			mltrngtypid = get_fn_expr_rettype(fcinfo->flinfo);
+	Oid			rngtypid;
+	TypeCacheEntry *typcache;
+	TypeCacheEntry *rangetyp;
+	RangeType *range;
+
+	typcache = multirange_get_typcache(fcinfo, mltrngtypid);
+	rangetyp = typcache->rngtype;
+
+	/*
+	 * 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")));
+
+	range = PG_GETARG_RANGE_P(0);
+
+	/* Make sure the range type matches. */
+	rngtypid = RangeTypeGetOid(range);
+	if (rngtypid != rangetyp->type_id)
+		ereport(ERROR, (errmsg("type %u does not match constructor type", rngtypid)));
+
+	PG_RETURN_MULTIRANGE_P(make_multirange(mltrngtypid, rangetyp, 1, &range));
+}
+
+/*
+ * 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 -> multirange type functions */
+
+/* multirange union */
+Datum
+multirange_union(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
+	MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count1;
+	int32		range_count2;
+	int32		range_count3;
+	RangeType **ranges1;
+	RangeType **ranges2;
+	RangeType **ranges3;
+
+	if (MultirangeIsEmpty(mr1))
+		PG_RETURN_MULTIRANGE_P(mr2);
+	if (MultirangeIsEmpty(mr2))
+		PG_RETURN_MULTIRANGE_P(mr1);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr1));
+
+	multirange_deserialize(mr1, &range_count1, &ranges1);
+	multirange_deserialize(mr2, &range_count2, &ranges2);
+
+	range_count3 = range_count1 + range_count2;
+	ranges3 = palloc0(range_count3 * sizeof(RangeType *));
+	memcpy(ranges3, ranges1, range_count1 * sizeof(RangeType *));
+	memcpy(ranges3 + range_count1, ranges2, range_count2 * sizeof(RangeType *));
+	PG_RETURN_MULTIRANGE_P(make_multirange(typcache->type_id, typcache->rngtype,
+										   range_count3, ranges3));
+}
+
+/* multirange minus */
+Datum
+multirange_minus(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
+	MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
+	Oid			mltrngtypoid = MultirangeTypeGetOid(mr1);
+	TypeCacheEntry *typcache;
+	TypeCacheEntry *rangetyp;
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+
+	typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
+	rangetyp = typcache->rngtype;
+
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		PG_RETURN_MULTIRANGE_P(mr1);
+
+	multirange_deserialize(mr1, &range_count1, &ranges1);
+	multirange_deserialize(mr2, &range_count2, &ranges2);
+
+	PG_RETURN_MULTIRANGE_P(multirange_minus_internal(mltrngtypoid,
+													 rangetyp,
+													 range_count1,
+													 ranges1,
+													 range_count2,
+													 ranges2));
+}
+
+MultirangeType *
+multirange_minus_internal(Oid mltrngtypoid, TypeCacheEntry *rangetyp,
+						 int32 range_count1, RangeType **ranges1,
+						 int32 range_count2, RangeType **ranges2)
+{
+	RangeType  *r1;
+	RangeType  *r2;
+	RangeType **ranges3;
+	int32		range_count3;
+	int32		i1;
+	int32		i2;
+
+	/*
+	 * Worst case: every range in ranges1 makes a different cut to some range
+	 * in ranges2.
+	 */
+	ranges3 = palloc0((range_count1 + range_count2) * sizeof(RangeType *));
+	range_count3 = 0;
+
+	/*
+	 * For each range in mr1, keep subtracting until it's gone or the ranges
+	 * in mr2 have passed it. After a subtraction we assign what's left back
+	 * to r1. The parallel progress through mr1 and mr2 is similar to
+	 * multirange_overlaps_multirange_internal.
+	 */
+	r2 = ranges2[0];
+	for (i1 = 0, i2 = 0; i1 < range_count1; i1++)
+	{
+		r1 = ranges1[i1];
+
+		/* Discard r2s while r2 << r1 */
+		while (r2 != NULL && range_before_internal(rangetyp, r2, r1))
+		{
+			r2 = ++i2 >= range_count2 ? NULL : ranges2[i2];
+		}
+
+		while (r2 != NULL)
+		{
+			if (range_split_internal(rangetyp, r1, r2, &ranges3[range_count3], &r1))
+			{
+				/*
+				 * If r2 takes a bite out of the middle of r1, we need two
+				 * outputs
+				 */
+				range_count3++;
+				r2 = ++i2 >= range_count2 ? NULL : ranges2[i2];
+
+			}
+			else if (range_overlaps_internal(rangetyp, r1, r2))
+			{
+				/*
+				 * If r2 overlaps r1, replace r1 with r1 - r2.
+				 */
+				r1 = range_minus_internal(rangetyp, r1, r2);
+
+				/*
+				 * If r2 goes past r1, then we need to stay with it, in case
+				 * it hits future r1s. Otherwise we need to keep r1, in case
+				 * future r2s hit it. Since we already subtracted, there's no
+				 * point in using the overright/overleft calls.
+				 */
+				if (RangeIsEmpty(r1) || range_before_internal(rangetyp, r1, r2))
+					break;
+				else
+					r2 = ++i2 >= range_count2 ? NULL : ranges2[i2];
+
+			}
+			else
+			{
+				/*
+				 * This and all future r2s are past r1, so keep them. Also
+				 * assign whatever is left of r1 to the result.
+				 */
+				break;
+			}
+		}
+
+		/*
+		 * Nothing else can remove anything from r1, so keep it. Even if r1 is
+		 * empty here, make_multirange will remove it.
+		 */
+		ranges3[range_count3++] = r1;
+	}
+
+	return make_multirange(mltrngtypoid, rangetyp, range_count3, ranges3);
+}
+
+/* multirange intersection */
+Datum
+multirange_intersect(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
+	MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
+	Oid			mltrngtypoid = MultirangeTypeGetOid(mr1);
+	TypeCacheEntry *typcache;
+	TypeCacheEntry *rangetyp;
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+
+	typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
+	rangetyp = typcache->rngtype;
+
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		PG_RETURN_MULTIRANGE_P(make_empty_multirange(mltrngtypoid, rangetyp));
+
+	multirange_deserialize(mr1, &range_count1, &ranges1);
+	multirange_deserialize(mr2, &range_count2, &ranges2);
+
+	PG_RETURN_MULTIRANGE_P(multirange_intersect_internal(mltrngtypoid,
+														 rangetyp,
+														 range_count1,
+														 ranges1,
+														 range_count2,
+														 ranges2));
+}
+
+MultirangeType *
+multirange_intersect_internal(Oid mltrngtypoid, TypeCacheEntry *rangetyp,
+							  int32 range_count1, RangeType **ranges1,
+							  int32 range_count2, RangeType **ranges2)
+{
+	RangeType  *r1;
+	RangeType  *r2;
+	RangeType **ranges3;
+	int32		range_count3;
+	int32		i1;
+	int32		i2;
+
+	if (range_count1 == 0 || range_count2 == 0)
+		return make_multirange(mltrngtypoid, rangetyp, 0, NULL);
+
+	/*-----------------------------------------------
+	 * Worst case is a stitching pattern like this:
+	 *
+	 * mr1: --- --- --- ---
+	 * mr2:   --- --- ---
+	 * mr3:   - - - - - -
+	 *
+	 * That seems to be range_count1 + range_count2 - 1,
+	 * but one extra won't hurt.
+	 *-----------------------------------------------
+	 */
+	ranges3 = palloc0((range_count1 + range_count2) * sizeof(RangeType *));
+	range_count3 = 0;
+
+	/*
+	 * For each range in mr1, keep intersecting until the ranges in mr2 have
+	 * passed it. The parallel progress through mr1 and mr2 is similar to
+	 * multirange_minus_multirange_internal, but we don't have to assign back
+	 * to r1.
+	 */
+	r2 = ranges2[0];
+	for (i1 = 0, i2 = 0; i1 < range_count1; i1++)
+	{
+		r1 = ranges1[i1];
+
+		/* Discard r2s while r2 << r1 */
+		while (r2 != NULL && range_before_internal(rangetyp, r2, r1))
+		{
+			r2 = ++i2 >= range_count2 ? NULL : ranges2[i2];
+		}
+
+		while (r2 != NULL)
+		{
+			if (range_overlaps_internal(rangetyp, r1, r2))
+			{
+				/* Keep the overlapping part */
+				ranges3[range_count3++] = range_intersect_internal(rangetyp, r1, r2);
+
+				/* If we "used up" all of r2, go to the next one... */
+				if (range_overleft_internal(rangetyp, r2, r1))
+					r2 = ++i2 >= range_count2 ? NULL : ranges2[i2];
+
+				/* ...otherwise go to the next r1 */
+				else
+					break;
+			}
+			else
+				/* We're past r1, so move to the next one */
+				break;
+		}
+
+		/* If we're out of r2s, there can be no more intersections */
+		if (r2 == NULL)
+			break;
+	}
+
+	return make_multirange(mltrngtypoid, rangetyp, range_count3, ranges3);
+}
+
+/*
+ * range_agg_transfn: combine adjacent/overlapping ranges.
+ *
+ * All we do here is gather the input ranges into an array
+ * so that the finalfn can sort and combine them.
+ */
+Datum
+range_agg_transfn(PG_FUNCTION_ARGS)
+{
+	MemoryContext aggContext;
+	Oid			rngtypoid;
+	ArrayBuildState *state;
+
+	if (!AggCheckCallContext(fcinfo, &aggContext))
+		elog(ERROR, "range_agg_transfn called in non-aggregate context");
+
+	rngtypoid = get_fn_expr_argtype(fcinfo->flinfo, 1);
+	if (!type_is_range(rngtypoid))
+		ereport(ERROR, (errmsg("range_agg must be called with a range")));
+
+	if (PG_ARGISNULL(0))
+		state = initArrayResult(rngtypoid, aggContext, false);
+	else
+		state = (ArrayBuildState *) PG_GETARG_POINTER(0);
+
+	/* skip NULLs */
+	if (!PG_ARGISNULL(1))
+		accumArrayResult(state, PG_GETARG_DATUM(1), false, rngtypoid, aggContext);
+
+	PG_RETURN_POINTER(state);
+}
+
+/*
+ * range_agg_finalfn: use our internal array to merge touching ranges.
+ */
+Datum
+range_agg_finalfn(PG_FUNCTION_ARGS)
+{
+	MemoryContext aggContext;
+	Oid			mltrngtypoid;
+	TypeCacheEntry *typcache;
+	ArrayBuildState *state;
+	int32		range_count;
+	RangeType **ranges;
+	int			i;
+
+	if (!AggCheckCallContext(fcinfo, &aggContext))
+		elog(ERROR, "range_agg_finalfn called in non-aggregate context");
+
+	state = PG_ARGISNULL(0) ? NULL : (ArrayBuildState *) PG_GETARG_POINTER(0);
+	if (state == NULL)
+		/* This shouldn't be possible, but just in case.... */
+		PG_RETURN_NULL();
+
+	/* Also return NULL if we had zero inputs, like other aggregates */
+	range_count = state->nelems;
+	if (range_count == 0)
+		PG_RETURN_NULL();
+
+	mltrngtypoid = get_fn_expr_rettype(fcinfo->flinfo);
+	typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
+
+	ranges = palloc0(range_count * sizeof(RangeType *));
+	for (i = 0; i < range_count; i++)
+		ranges[i] = DatumGetRangeTypeP(state->dvalues[i]);
+
+	PG_RETURN_MULTIRANGE_P(make_multirange(mltrngtypoid, typcache->rngtype, range_count, ranges));
+}
+
+Datum
+multirange_intersect_agg_transfn(PG_FUNCTION_ARGS)
+{
+	MemoryContext aggContext;
+	Oid			mltrngtypoid;
+	TypeCacheEntry *typcache;
+	MultirangeType *result;
+	MultirangeType *current;
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+
+	if (!AggCheckCallContext(fcinfo, &aggContext))
+		elog(ERROR, "multirange_intersect_agg_transfn called in non-aggregate context");
+
+	mltrngtypoid = get_fn_expr_argtype(fcinfo->flinfo, 1);
+	if (!type_is_multirange(mltrngtypoid))
+		ereport(ERROR, (errmsg("range_intersect_agg must be called with a multirange")));
+
+	typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
+
+	/* strictness ensures these are non-null */
+	result = PG_GETARG_MULTIRANGE_P(0);
+	current = PG_GETARG_MULTIRANGE_P(1);
+
+	multirange_deserialize(result, &range_count1, &ranges1);
+	multirange_deserialize(current, &range_count2, &ranges2);
+
+	result = multirange_intersect_internal(mltrngtypoid,
+										   typcache->rngtype,
+										   range_count1,
+										   ranges1,
+										   range_count2,
+										   ranges2);
+	PG_RETURN_RANGE_P(result);
+}
+
+
+/* multirange -> element type functions */
+
+/* extract lower bound value */
+Datum
+multirange_lower(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+	bool		isnull;
+	Datum		result;
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_NULL();
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	result = range_lower_internal(typcache->rngtype, ranges[0], &isnull);
+
+	if (isnull)
+		PG_RETURN_NULL();
+	else
+		PG_RETURN_DATUM(result);
+}
+
+/* extract upper bound value */
+Datum
+multirange_upper(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+	bool		isnull;
+	Datum		result;
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_NULL();
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	result = range_upper_internal(typcache->rngtype, ranges[range_count - 1], &isnull);
+
+	if (isnull)
+		PG_RETURN_NULL();
+	else
+		PG_RETURN_DATUM(result);
+}
+
+
+/* multirange -> bool functions */
+
+/* is multirange empty? */
+Datum
+multirange_empty(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+
+	PG_RETURN_BOOL(mr->rangeCount == 0);
+}
+
+/* is lower bound inclusive? */
+Datum
+multirange_lower_inc(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	int32		range_count;
+	RangeType **ranges;
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_has_flag(ranges[0], RANGE_LB_INC));
+}
+
+/* is upper bound inclusive? */
+Datum
+multirange_upper_inc(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	int32		range_count;
+	RangeType **ranges;
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_has_flag(ranges[range_count - 1], RANGE_UB_INC));
+}
+
+/* is lower bound infinite? */
+Datum
+multirange_lower_inf(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	int32		range_count;
+	RangeType **ranges;
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_has_flag(ranges[0], RANGE_LB_INF));
+}
+
+/* is upper bound infinite? */
+Datum
+multirange_upper_inf(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	int32		range_count;
+	RangeType **ranges;
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_has_flag(ranges[range_count - 1], RANGE_UB_INF));
+}
+
+
+
+/* multirange, element -> bool functions */
+
+/* contains? */
+Datum
+multirange_contains_elem(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	Datum		val = PG_GETARG_DATUM(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(multirange_contains_elem_internal(typcache, mr, val));
+}
+
+/* contained by? */
+Datum
+elem_contained_by_multirange(PG_FUNCTION_ARGS)
+{
+	Datum		val = PG_GETARG_DATUM(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(multirange_contains_elem_internal(typcache, mr, val));
+}
+
+/*
+ * Test whether multirange mr contains a specific element value.
+ */
+bool
+multirange_contains_elem_internal(TypeCacheEntry *typcache, MultirangeType * mr, Datum val)
+{
+	TypeCacheEntry *rangetyp;
+	int32		range_count;
+	RangeType **ranges;
+	RangeType  *r;
+	int			i;
+
+	rangetyp = typcache->rngtype;
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	for (i = 0; i < range_count; i++)
+	{
+		r = ranges[i];
+		if (range_contains_elem_internal(rangetyp, r, val))
+			return true;
+	}
+
+	return false;
+}
+
+/* multirange, range -> bool functions */
+
+/* contains? */
+Datum
+multirange_contains_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(multirange_contains_range_internal(typcache, mr, r));
+}
+
+/* contained by? */
+Datum
+range_contained_by_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(multirange_contains_range_internal(typcache, mr, r));
+}
+
+/*
+ * Test whether multirange mr contains a specific range r.
+ */
+bool
+multirange_contains_range_internal(TypeCacheEntry *typcache, MultirangeType * mr, RangeType *r)
+{
+	TypeCacheEntry *rangetyp;
+	int32		range_count;
+	RangeType **ranges;
+	RangeType  *mrr;
+	int			i;
+
+	rangetyp = typcache->rngtype;
+
+	/*
+	 * Every multirange contains an infinite number of empty ranges, even an
+	 * empty one.
+	 */
+	if (RangeIsEmpty(r))
+		return true;
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	for (i = 0; i < range_count; i++)
+	{
+		mrr = ranges[i];
+		if (range_contains_internal(rangetyp, mrr, r))
+			return true;
+	}
+
+	return false;
+}
+
+
+/* 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));
+}
+
+/* overlaps? */
+Datum
+range_overlaps_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(range_overlaps_multirange_internal(typcache, r, mr));
+}
+
+Datum
+multirange_overlaps_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(range_overlaps_multirange_internal(typcache, r, mr));
+}
+
+Datum
+multirange_overlaps_multirange(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_overlaps_multirange_internal(typcache, mr1, mr2));
+}
+
+bool
+range_overlaps_multirange_internal(TypeCacheEntry *typcache, RangeType *r, MultirangeType * mr)
+{
+	TypeCacheEntry *rangetyp;
+	int32		range_count;
+	int32		i;
+	RangeType **ranges;
+	RangeType  *mrr;
+
+	/*
+	 * Empties never overlap, even with empties. (This seems strange since
+	 * they *do* contain each other, but we want to follow how ranges work.)
+	 */
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		return false;
+
+	rangetyp = typcache->rngtype;
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	/* Scan through mrr and once it gets to r they either overlap or not */
+	mrr = ranges[0];
+
+	/* Discard mrrs while mrr << r */
+	i = 0;
+	while (range_before_internal(rangetyp, mrr, r))
+	{
+		if (++i >= range_count)
+			return false;
+		mrr = ranges[i];
+	}
+
+	/* Now either we overlap or we passed r */
+	return range_overlaps_internal(rangetyp, mrr, r);
+}
+
+bool
+multirange_overlaps_multirange_internal(TypeCacheEntry *typcache, MultirangeType * mr1,
+										MultirangeType * mr2)
+{
+	TypeCacheEntry *rangetyp;
+	int32		range_count1;
+	int32		range_count2;
+	int32		i1;
+	int32		i2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+	RangeType  *r1;
+	RangeType  *r2;
+
+	/*
+	 * Empties never overlap, even with empties. (This seems strange since
+	 * they *do* contain each other, but we want to follow how ranges work.)
+	 */
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		return false;
+
+	rangetyp = typcache->rngtype;
+
+	multirange_deserialize(mr1, &range_count1, &ranges1);
+	multirange_deserialize(mr2, &range_count2, &ranges2);
+
+	/*
+	 * Every range in mr1 gets a chance to overlap with the ranges in mr2, but
+	 * we can use their ordering to avoid O(n^2). This is similar to
+	 * range_overlaps_multirange where r1 : r2 :: mrr : r, but there if we
+	 * don't find an overlap with r we're done, and here if we don't find an
+	 * overlap with r2 we try the next r2.
+	 */
+	r1 = ranges1[0];
+	for (i1 = 0, i2 = 0; i2 < range_count2; i2++)
+	{
+		r2 = ranges2[i2];
+
+		/* Discard r1s while r1 << r2 */
+		while (range_before_internal(rangetyp, r1, r2))
+		{
+			if (++i1 >= range_count1)
+				return false;
+			r1 = ranges1[i1];
+		}
+
+		/*
+		 * If r1 && r2, we're done, otherwise we failed to find an overlap for
+		 * r2, so go to the next one.
+		 */
+		if (range_overlaps_internal(rangetyp, r1, r2))
+			return true;
+	}
+
+	/* We looked through all of mr2 without finding an overlap */
+	return false;
+}
+
+/* does not extend to right of? */
+Datum
+range_overleft_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_overleft_internal(typcache->rngtype, r, ranges[range_count - 1]));
+}
+
+Datum
+multirange_overleft_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_overleft_internal(typcache->rngtype, ranges[range_count - 1], r));
+}
+
+Datum
+multirange_overleft_multirange(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
+	MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr1));
+
+	multirange_deserialize(mr1, &range_count1, &ranges1);
+	multirange_deserialize(mr2, &range_count2, &ranges2);
+
+	PG_RETURN_BOOL(range_overleft_internal(typcache->rngtype, ranges1[range_count1 - 1], ranges2[range_count2 - 1]));
+}
+
+/* does not extend to left of? */
+Datum
+range_overright_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_overright_internal(typcache->rngtype, r, ranges[0]));
+}
+
+Datum
+multirange_overright_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_overright_internal(typcache->rngtype, ranges[0], r));
+}
+
+Datum
+multirange_overright_multirange(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
+	MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr1));
+
+	multirange_deserialize(mr1, &range_count1, &ranges1);
+	multirange_deserialize(mr2, &range_count2, &ranges2);
+
+	PG_RETURN_BOOL(range_overright_internal(typcache->rngtype, ranges1[0], ranges2[0]));
+}
+
+/* contains? */
+Datum
+multirange_contains_multirange(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_contains_multirange_internal(typcache, mr1, mr2));
+}
+
+/* contained by? */
+Datum
+multirange_contained_by_multirange(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_contains_multirange_internal(typcache, mr2, mr1));
+}
+
+/*
+ * Test whether multirange mr1 contains every range from another multirange mr2.
+ */
+bool
+multirange_contains_multirange_internal(TypeCacheEntry *typcache,
+										MultirangeType * mr1, MultirangeType * mr2)
+{
+	TypeCacheEntry *rangetyp;
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+	RangeType  *r1;
+	RangeType  *r2;
+	int			i1,
+				i2;
+
+	rangetyp = typcache->rngtype;
+
+	multirange_deserialize(mr1, &range_count1, &ranges1);
+	multirange_deserialize(mr2, &range_count2, &ranges2);
+
+	/*
+	 * We follow the same logic for empties as ranges: - an empty multirange
+	 * contains an empty range/multirange. - an empty multirange can't contain
+	 * any other range/multirange. - an empty multirange is contained by any
+	 * other range/multirange.
+	 */
+
+	if (range_count2 == 0)
+		return true;
+	if (range_count1 == 0)
+		return false;
+
+	/*
+	 * Every range in mr2 must be contained by some range in mr1. To avoid
+	 * O(n^2) we walk through both ranges in tandem.
+	 */
+	r1 = ranges1[0];
+	for (i1 = 0, i2 = 0; i2 < range_count2; i2++)
+	{
+		r2 = ranges2[i2];
+
+		/* Discard r1s while r1 << r2 */
+		while (range_before_internal(rangetyp, r1, r2))
+		{
+			if (++i1 >= range_count1)
+				return false;
+			r1 = ranges1[i1];
+		}
+
+		/*
+		 * If r1 @> r2, go to the next r2, otherwise return false (since every
+		 * r1[n] and r1[n+1] must have a gap). Note this will give weird
+		 * answers if you don't canonicalize, e.g. with a custom
+		 * int2multirange {[1,1], [2,2]} there is a "gap". But that is
+		 * consistent with other range operators, e.g. '[1,1]'::int2range -|-
+		 * '[2,2]'::int2range is false.
+		 */
+		if (!range_contains_internal(rangetyp, r1, r2))
+			return false;
+	}
+
+	/* All ranges in mr2 are satisfied */
+	return true;
+}
+
+/* strictly left of? */
+Datum
+range_before_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(range_before_multirange_internal(typcache, r, mr));
+}
+
+Datum
+multirange_before_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(range_after_multirange_internal(typcache, r, mr));
+}
+
+Datum
+multirange_before_multirange(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_before_multirange_internal(typcache, mr1, mr2));
+}
+
+/* strictly right of? */
+Datum
+range_after_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(range_after_multirange_internal(typcache, r, mr));
+}
+
+Datum
+multirange_after_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(range_before_multirange_internal(typcache, r, mr));
+}
+
+Datum
+multirange_after_multirange(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_before_multirange_internal(typcache, mr2, mr1));
+}
+
+/* strictly left of? (internal version) */
+bool
+range_before_multirange_internal(TypeCacheEntry *typcache, RangeType *r,
+								 MultirangeType * mr)
+{
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		return false;
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	return range_before_internal(typcache->rngtype, r, ranges[0]);
+}
+
+bool
+multirange_before_multirange_internal(TypeCacheEntry *typcache, MultirangeType * mr1,
+									  MultirangeType * mr2)
+{
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		return false;
+
+	multirange_deserialize(mr1, &range_count1, &ranges1);
+	multirange_deserialize(mr2, &range_count2, &ranges2);
+
+	return range_before_internal(typcache->rngtype, ranges1[range_count1 - 1],
+								 ranges2[0]);
+}
+
+/* strictly right of? (internal version) */
+bool
+range_after_multirange_internal(TypeCacheEntry *typcache, RangeType *r,
+								MultirangeType * mr)
+{
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		return false;
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	return range_after_internal(typcache->rngtype, r, ranges[range_count - 1]);
+}
+
+/* adjacent to? */
+Datum
+range_adjacent_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		return false;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_adjacent_internal(typcache->rngtype, r, ranges[0]));
+}
+
+Datum
+multirange_adjacent_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		return false;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_adjacent_internal(typcache->rngtype, ranges[range_count - 1], r));
+}
+
+Datum
+multirange_adjacent_multirange(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
+	MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		return false;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr1));
+
+	multirange_deserialize(mr1, &range_count1, &ranges1);
+	multirange_deserialize(mr2, &range_count2, &ranges2);
+
+	PG_RETURN_BOOL(range_adjacent_internal(typcache->rngtype, ranges1[range_count1 - 1], ranges2[0]));
+}
+
+/* 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);
+}
+
+/* multirange -> range functions */
+
+/* Find the smallest range that includes everything in the multirange */
+Datum
+range_merge_from_multirange(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	Oid			mltrngtypoid = MultirangeTypeGetOid(mr);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_RANGE_P(make_empty_range(typcache->rngtype));
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_RANGE_P(range_union_internal(typcache->rngtype, ranges[0],
+										   ranges[range_count - 1], false));
+}
+
+/* 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/pg_upgrade_support.c b/src/backend/utils/adt/pg_upgrade_support.c
index 8b26e1a93d..9362995741 100644
--- a/src/backend/utils/adt/pg_upgrade_support.c
+++ b/src/backend/utils/adt/pg_upgrade_support.c
@@ -52,6 +52,28 @@ binary_upgrade_set_next_array_pg_type_oid(PG_FUNCTION_ARGS)
 }
 
 Datum
+binary_upgrade_set_next_multirange_pg_type_oid(PG_FUNCTION_ARGS)
+{
+	Oid			typoid = PG_GETARG_OID(0);
+
+	CHECK_IS_BINARY_UPGRADE;
+	binary_upgrade_next_mrng_pg_type_oid = typoid;
+
+	PG_RETURN_VOID();
+}
+
+Datum
+binary_upgrade_set_next_multirange_array_pg_type_oid(PG_FUNCTION_ARGS)
+{
+	Oid			typoid = PG_GETARG_OID(0);
+
+	CHECK_IS_BINARY_UPGRADE;
+	binary_upgrade_next_mrng_array_pg_type_oid = typoid;
+
+	PG_RETURN_VOID();
+}
+
+Datum
 binary_upgrade_set_next_toast_pg_type_oid(PG_FUNCTION_ARGS)
 {
 	Oid			typoid = PG_GETARG_OID(0);
diff --git a/src/backend/utils/adt/pseudotypes.c b/src/backend/utils/adt/pseudotypes.c
index ad0e363c70..152e77f44c 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"
 
 
 /*
@@ -185,6 +186,30 @@ anyrange_out(PG_FUNCTION_ARGS)
 }
 
 /*
+ * 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.
  *
  * We allow this so that PL functions can return VOID without any special
diff --git a/src/backend/utils/adt/rangetypes.c b/src/backend/utils/adt/rangetypes.c
index 639e1dad6c..be1e044269 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
 {
@@ -431,19 +429,37 @@ range_lower(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
 	TypeCacheEntry *typcache;
+	bool		isnull;
+	Datum		result;
+
+	typcache = range_get_typcache(fcinfo, RangeTypeGetOid(r1));
+
+	result = range_lower_internal(typcache, r1, &isnull);
+
+	if (isnull)
+		PG_RETURN_NULL();
+	else
+		PG_RETURN_DATUM(result);
+}
+
+Datum
+range_lower_internal(TypeCacheEntry *typcache, const RangeType *r1, bool *isnull)
+{
 	RangeBound	lower;
 	RangeBound	upper;
 	bool		empty;
 
-	typcache = range_get_typcache(fcinfo, RangeTypeGetOid(r1));
-
 	range_deserialize(typcache, r1, &lower, &upper, &empty);
 
 	/* Return NULL if there's no finite lower bound */
 	if (empty || lower.infinite)
-		PG_RETURN_NULL();
+	{
+		*isnull = true;
+		return 0;
+	}
 
-	PG_RETURN_DATUM(lower.val);
+	*isnull = false;
+	return lower.val;
 }
 
 /* extract upper bound value */
@@ -452,19 +468,37 @@ range_upper(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
 	TypeCacheEntry *typcache;
+	bool		isnull;
+	Datum		result;
+
+	typcache = range_get_typcache(fcinfo, RangeTypeGetOid(r1));
+
+	result = range_upper_internal(typcache, r1, &isnull);
+
+	if (isnull)
+		PG_RETURN_NULL();
+	else
+		PG_RETURN_DATUM(result);
+}
+
+Datum
+range_upper_internal(TypeCacheEntry *typcache, const RangeType *r1, bool *isnull)
+{
 	RangeBound	lower;
 	RangeBound	upper;
 	bool		empty;
 
-	typcache = range_get_typcache(fcinfo, RangeTypeGetOid(r1));
-
 	range_deserialize(typcache, r1, &lower, &upper, &empty);
 
 	/* Return NULL if there's no finite upper bound */
 	if (empty || upper.infinite)
-		PG_RETURN_NULL();
+	{
+		*isnull = true;
+		return 0;
+	}
 
-	PG_RETURN_DATUM(upper.val);
+	*isnull = false;
+	return upper.val;
 }
 
 
@@ -475,9 +509,8 @@ Datum
 range_empty(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
-	char		flags = range_get_flags(r1);
 
-	PG_RETURN_BOOL(flags & RANGE_EMPTY);
+	PG_RETURN_BOOL(range_has_flag(r1, RANGE_EMPTY));
 }
 
 /* is lower bound inclusive? */
@@ -485,9 +518,8 @@ Datum
 range_lower_inc(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
-	char		flags = range_get_flags(r1);
 
-	PG_RETURN_BOOL(flags & RANGE_LB_INC);
+	PG_RETURN_BOOL(range_has_flag(r1, RANGE_LB_INC));
 }
 
 /* is upper bound inclusive? */
@@ -495,9 +527,8 @@ Datum
 range_upper_inc(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
-	char		flags = range_get_flags(r1);
 
-	PG_RETURN_BOOL(flags & RANGE_UB_INC);
+	PG_RETURN_BOOL(range_has_flag(r1, RANGE_UB_INC));
 }
 
 /* is lower bound infinite? */
@@ -505,9 +536,8 @@ Datum
 range_lower_inf(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
-	char		flags = range_get_flags(r1);
 
-	PG_RETURN_BOOL(flags & RANGE_LB_INF);
+	PG_RETURN_BOOL(range_has_flag(r1, RANGE_LB_INF));
 }
 
 /* is upper bound infinite? */
@@ -515,9 +545,8 @@ Datum
 range_upper_inf(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
-	char		flags = range_get_flags(r1);
 
-	PG_RETURN_BOOL(flags & RANGE_UB_INF);
+	PG_RETURN_BOOL(range_has_flag(r1, RANGE_UB_INF));
 }
 
 
@@ -957,7 +986,25 @@ range_minus(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
 	RangeType  *r2 = PG_GETARG_RANGE_P(1);
+	RangeType  *ret;
 	TypeCacheEntry *typcache;
+
+	/* 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));
+
+	ret = range_minus_internal(typcache, r1, r2);
+	if (ret)
+		PG_RETURN_RANGE_P(ret);
+	else
+		PG_RETURN_NULL();
+}
+
+RangeType *
+range_minus_internal(TypeCacheEntry *typcache, RangeType *r1, RangeType *r2)
+{
 	RangeBound	lower1,
 				lower2;
 	RangeBound	upper1,
@@ -969,18 +1016,12 @@ range_minus(PG_FUNCTION_ARGS)
 				cmp_u1l2,
 				cmp_u1u2;
 
-	/* 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));
-
 	range_deserialize(typcache, r1, &lower1, &upper1, &empty1);
 	range_deserialize(typcache, r2, &lower2, &upper2, &empty2);
 
 	/* if either is empty, r1 is the correct answer */
 	if (empty1 || empty2)
-		PG_RETURN_RANGE_P(r1);
+		return r1;
 
 	cmp_l1l2 = range_cmp_bounds(typcache, &lower1, &lower2);
 	cmp_l1u2 = range_cmp_bounds(typcache, &lower1, &upper2);
@@ -993,34 +1034,34 @@ range_minus(PG_FUNCTION_ARGS)
 				 errmsg("result of range difference would not be contiguous")));
 
 	if (cmp_l1u2 > 0 || cmp_u1l2 < 0)
-		PG_RETURN_RANGE_P(r1);
+		return r1;
 
 	if (cmp_l1l2 >= 0 && cmp_u1u2 <= 0)
-		PG_RETURN_RANGE_P(make_empty_range(typcache));
+		return make_empty_range(typcache);
 
 	if (cmp_l1l2 <= 0 && cmp_u1l2 >= 0 && cmp_u1u2 <= 0)
 	{
 		lower2.inclusive = !lower2.inclusive;
 		lower2.lower = false;	/* it will become the upper bound */
-		PG_RETURN_RANGE_P(make_range(typcache, &lower1, &lower2, false));
+		return make_range(typcache, &lower1, &lower2, false);
 	}
 
 	if (cmp_l1l2 >= 0 && cmp_u1u2 >= 0 && cmp_l1u2 <= 0)
 	{
 		upper2.inclusive = !upper2.inclusive;
 		upper2.lower = true;	/* it will become the lower bound */
-		PG_RETURN_RANGE_P(make_range(typcache, &upper2, &upper1, false));
+		return make_range(typcache, &upper2, &upper1, false);
 	}
 
 	elog(ERROR, "unexpected case in range_minus");
-	PG_RETURN_NULL();
+	return NULL;
 }
 
 /*
  * Set union.  If strict is true, it is an error that the two input ranges
  * are not adjacent or overlapping.
  */
-static RangeType *
+RangeType *
 range_union_internal(TypeCacheEntry *typcache, RangeType *r1, RangeType *r2,
 					 bool strict)
 {
@@ -1101,6 +1142,19 @@ range_intersect(PG_FUNCTION_ARGS)
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
 	RangeType  *r2 = PG_GETARG_RANGE_P(1);
 	TypeCacheEntry *typcache;
+
+	/* 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));
+
+	PG_RETURN_RANGE_P(range_intersect_internal(typcache, r1, r2));
+}
+
+RangeType *
+range_intersect_internal(TypeCacheEntry *typcache, const RangeType *r1, const RangeType *r2)
+{
 	RangeBound	lower1,
 				lower2;
 	RangeBound	upper1,
@@ -1110,17 +1164,11 @@ range_intersect(PG_FUNCTION_ARGS)
 	RangeBound *result_lower;
 	RangeBound *result_upper;
 
-	/* 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));
-
 	range_deserialize(typcache, r1, &lower1, &upper1, &empty1);
 	range_deserialize(typcache, r2, &lower2, &upper2, &empty2);
 
-	if (empty1 || empty2 || !DatumGetBool(range_overlaps(fcinfo)))
-		PG_RETURN_RANGE_P(make_empty_range(typcache));
+	if (empty1 || empty2 || !range_overlaps_internal(typcache, r1, r2))
+		return make_empty_range(typcache);
 
 	if (range_cmp_bounds(typcache, &lower1, &lower2) >= 0)
 		result_lower = &lower1;
@@ -1132,9 +1180,81 @@ range_intersect(PG_FUNCTION_ARGS)
 	else
 		result_upper = &upper2;
 
-	PG_RETURN_RANGE_P(make_range(typcache, result_lower, result_upper, false));
+	return make_range(typcache, result_lower, result_upper, false);
+}
+
+/* range, range -> range, range functions */
+
+/*
+ * range_split_internal - if r2 intersects the middle of r1, leaving non-empty
+ * ranges on both sides, then return true and set output1 and output2 to the
+ * results of r1 - r2 (in order). Otherwise return false and don't set output1
+ * or output2. Neither input range should be empty.
+ */
+bool
+range_split_internal(TypeCacheEntry *typcache, const RangeType *r1, const RangeType *r2,
+					 RangeType **output1, RangeType **output2)
+{
+	RangeBound	lower1,
+				lower2;
+	RangeBound	upper1,
+				upper2;
+	bool		empty1,
+				empty2;
+
+	range_deserialize(typcache, r1, &lower1, &upper1, &empty1);
+	range_deserialize(typcache, r2, &lower2, &upper2, &empty2);
+
+	if (range_cmp_bounds(typcache, &lower1, &lower2) < 0 &&
+		range_cmp_bounds(typcache, &upper1, &upper2) > 0)
+	{
+		/*
+		 * Need to invert inclusive/exclusive for the lower2 and upper2
+		 * points. They can't be infinite though. We're allowed to overwrite
+		 * these RangeBounds since they only exist locally.
+		 */
+		lower2.inclusive = !lower2.inclusive;
+		lower2.lower = false;
+		upper2.inclusive = !upper2.inclusive;
+		upper2.lower = true;
+
+		*output1 = make_range(typcache, &lower1, &lower2, false);
+		*output2 = make_range(typcache, &upper2, &upper1, false);
+		return true;
+	}
+
+	return false;
 }
 
+/* range -> range aggregate functions */
+
+Datum
+range_intersect_agg_transfn(PG_FUNCTION_ARGS)
+{
+	MemoryContext aggContext;
+	Oid			rngtypoid;
+	TypeCacheEntry *typcache;
+	RangeType  *result;
+	RangeType  *current;
+
+	if (!AggCheckCallContext(fcinfo, &aggContext))
+		elog(ERROR, "range_intersect_agg_transfn called in non-aggregate context");
+
+	rngtypoid = get_fn_expr_argtype(fcinfo->flinfo, 1);
+	if (!type_is_range(rngtypoid))
+		ereport(ERROR, (errmsg("range_intersect_agg must be called with a range")));
+
+	typcache = range_get_typcache(fcinfo, rngtypoid);
+
+	/* strictness ensures these are non-null */
+	result = PG_GETARG_RANGE_P(0);
+	current = PG_GETARG_RANGE_P(1);
+
+	result = range_intersect_internal(typcache, result, current);
+	PG_RETURN_RANGE_P(result);
+}
+
+
 /* Btree support */
 
 /* btree comparator */
@@ -1144,12 +1264,6 @@ range_cmp(PG_FUNCTION_ARGS)
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
 	RangeType  *r2 = PG_GETARG_RANGE_P(1);
 	TypeCacheEntry *typcache;
-	RangeBound	lower1,
-				lower2;
-	RangeBound	upper1,
-				upper2;
-	bool		empty1,
-				empty2;
 	int			cmp;
 
 	check_stack_depth();		/* recurses when subtype is a range type */
@@ -1160,6 +1274,28 @@ range_cmp(PG_FUNCTION_ARGS)
 
 	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, const RangeType *r1, const 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);
 
@@ -1177,10 +1313,7 @@ range_cmp(PG_FUNCTION_ARGS)
 			cmp = range_cmp_bounds(typcache, &upper1, &upper2);
 	}
 
-	PG_FREE_IF_COPY(r1, 0);
-	PG_FREE_IF_COPY(r2, 1);
-
-	PG_RETURN_INT32(cmp);
+	return cmp;
 }
 
 /* inequality operators using the range_cmp function */
@@ -1218,13 +1351,10 @@ range_gt(PG_FUNCTION_ARGS)
 
 /* Hash support */
 
-/* hash a range value */
-Datum
-hash_range(PG_FUNCTION_ARGS)
+uint32
+hash_range_internal(TypeCacheEntry *typcache, const RangeType *r)
 {
-	RangeType  *r = PG_GETARG_RANGE_P(0);
 	uint32		result;
-	TypeCacheEntry *typcache;
 	TypeCacheEntry *scache;
 	RangeBound	lower;
 	RangeBound	upper;
@@ -1233,10 +1363,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 +1404,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, const RangeType *r, Datum seed)
+{
+	uint64		result;
 	TypeCacheEntry *scache;
 	RangeBound	lower;
 	RangeBound	upper;
@@ -1300,10 +1433,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 +1471,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));
 }
 
 /*
@@ -1770,6 +1917,21 @@ range_get_flags(const RangeType *range)
 }
 
 /*
+ * range_has_flag: set whether a range has a specific flag.
+ *
+ * This lets expose some of our functions that just check flags
+ * to the rest of the code base (like to multiranges)
+ * without writing full-fledged *_internal versions.
+ */
+bool
+range_has_flag(const RangeType *r1, char flag)
+{
+	char		flags = range_get_flags(r1);
+
+	return flags & flag;
+}
+
+/*
  * range_set_contain_empty: set the RANGE_CONTAIN_EMPTY bit in the value.
  *
  * This is only needed in GiST operations, so we don't include a provision
@@ -1938,6 +2100,45 @@ range_cmp_bound_values(TypeCacheEntry *typcache, const RangeBound *b1,
 }
 
 /*
+ * 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.
  */
 RangeType *
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index 1e3e6d3a7e..2f6257c95f 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -2469,6 +2469,16 @@ type_is_range(Oid typid)
 }
 
 /*
+ * 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
  *
  *		Given the type OID, fetch its category and preferred-type status.
@@ -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->rngmultitypid;
+		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 53d9ddf159..fb9179c42a 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_rngmultitypid,
+			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 cdf6331a97..6677bc8bdc 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;
@@ -759,6 +777,16 @@ lookup_type_cache(Oid type_id, int flags)
 	}
 
 	/*
+	 * 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
 	 */
 	if ((flags & TYPECACHE_DOMAIN_BASE_INFO) &&
@@ -871,6 +899,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
  *
  * Note: we assume we're called in a relatively short-lived context, so it's
@@ -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 b7eee3da1d..defc801f7a 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,131 @@ resolve_polymorphic_tupdesc(TupleDesc tupdesc, oidvector *declared_args,
 				return false;
 			anyelement_type = subtype;
 		}
+
+		if (OidIsValid(anymultirange_type))
+		{
+			Oid			rngtype;
+			Oid			subtype;
+
+			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;
+			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;
+			Oid			subtype;
+
+			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;
+
+			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;
+			Oid			mltrngtype;
+			Oid			rngtype;
+
+			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;
+
+			mltrngtype = resolve_generic_type(ANYMULTIRANGEOID,
+											  anyrange_type,
+											  ANYRANGEOID);
+
+			/* check for inconsistent range and multirange results */
+			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 +761,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 +793,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 +856,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 +880,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 +906,132 @@ resolve_polymorphic_argtypes(int numargs, Oid *argtypes, char *argmodes,
 				return false;
 			anyelement_type = subtype;
 		}
+
+		if (OidIsValid(anymultirange_type))
+		{
+			Oid			rngtype;
+			Oid			subtype;
+
+			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;
+
+			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;
+			Oid			subtype;
+
+			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;
+
+			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;
+			Oid			mltrngtype;
+			Oid			rngtype;
+
+			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;
+
+			mltrngtype = resolve_generic_type(ANYMULTIRANGEOID,
+											  anyrange_type,
+											  ANYRANGEOID);
+
+			/* check for inconsistent range and multirange results */
+			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 +1051,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/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 799b6988b7..7237b5e52a 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -274,7 +274,8 @@ static void dumpSearchPath(Archive *AH);
 static void binary_upgrade_set_type_oids_by_type_oid(Archive *fout,
 													 PQExpBuffer upgrade_buffer,
 													 Oid pg_type_oid,
-													 bool force_array_type);
+													 bool force_array_type,
+													 bool include_multirange_type);
 static bool binary_upgrade_set_type_oids_by_rel_oid(Archive *fout,
 													PQExpBuffer upgrade_buffer, Oid pg_rel_oid);
 static void binary_upgrade_set_pg_class_oids(Archive *fout,
@@ -1583,7 +1584,7 @@ selectDumpableType(TypeInfo *tyinfo, Archive *fout)
 	}
 
 	/* skip auto-generated array types */
-	if (tyinfo->isArray)
+	if (tyinfo->isArray || tyinfo->isMultirange)
 	{
 		tyinfo->dobj.objType = DO_DUMMY_TYPE;
 
@@ -4275,15 +4276,49 @@ dumpSubscription(Archive *fout, SubscriptionInfo *subinfo)
 	free(qsubname);
 }
 
+static Oid
+get_next_possible_free_pg_type_oid(Archive *fout, PQExpBuffer upgrade_query)
+{
+	/*
+	 * If the old version didn't assign an array type, but the new version
+	 * does, we must select an unused type OID to assign.  This currently only
+	 * happens for domains, when upgrading pre-v11 to v11 and up.
+	 *
+	 * Note: local state here is kind of ugly, but we must have some, since we
+	 * mustn't choose the same unused OID more than once.
+	 */
+	static Oid	next_possible_free_oid = FirstNormalObjectId;
+	PGresult   *res;
+	bool		is_dup;
+
+	do
+	{
+		++next_possible_free_oid;
+		printfPQExpBuffer(upgrade_query,
+						  "SELECT EXISTS(SELECT 1 "
+						  "FROM pg_catalog.pg_type "
+						  "WHERE oid = '%u'::pg_catalog.oid);",
+						  next_possible_free_oid);
+		res = ExecuteSqlQueryForSingleRow(fout, upgrade_query->data);
+		is_dup = (PQgetvalue(res, 0, 0)[0] == 't');
+		PQclear(res);
+	} while (is_dup);
+
+	return next_possible_free_oid;
+}
+
 static void
 binary_upgrade_set_type_oids_by_type_oid(Archive *fout,
 										 PQExpBuffer upgrade_buffer,
 										 Oid pg_type_oid,
-										 bool force_array_type)
+										 bool force_array_type,
+										 bool include_multirange_type)
 {
 	PQExpBuffer upgrade_query = createPQExpBuffer();
 	PGresult   *res;
 	Oid			pg_type_array_oid;
+	Oid			pg_type_multirange_oid;
+	Oid			pg_type_multirange_array_oid;
 
 	appendPQExpBufferStr(upgrade_buffer, "\n-- For binary upgrade, must preserve pg_type oid\n");
 	appendPQExpBuffer(upgrade_buffer,
@@ -4304,33 +4339,7 @@ binary_upgrade_set_type_oids_by_type_oid(Archive *fout,
 	PQclear(res);
 
 	if (!OidIsValid(pg_type_array_oid) && force_array_type)
-	{
-		/*
-		 * If the old version didn't assign an array type, but the new version
-		 * does, we must select an unused type OID to assign.  This currently
-		 * only happens for domains, when upgrading pre-v11 to v11 and up.
-		 *
-		 * Note: local state here is kind of ugly, but we must have some,
-		 * since we mustn't choose the same unused OID more than once.
-		 */
-		static Oid	next_possible_free_oid = FirstNormalObjectId;
-		bool		is_dup;
-
-		do
-		{
-			++next_possible_free_oid;
-			printfPQExpBuffer(upgrade_query,
-							  "SELECT EXISTS(SELECT 1 "
-							  "FROM pg_catalog.pg_type "
-							  "WHERE oid = '%u'::pg_catalog.oid);",
-							  next_possible_free_oid);
-			res = ExecuteSqlQueryForSingleRow(fout, upgrade_query->data);
-			is_dup = (PQgetvalue(res, 0, 0)[0] == 't');
-			PQclear(res);
-		} while (is_dup);
-
-		pg_type_array_oid = next_possible_free_oid;
-	}
+		pg_type_array_oid = get_next_possible_free_pg_type_oid(fout, upgrade_query);
 
 	if (OidIsValid(pg_type_array_oid))
 	{
@@ -4341,6 +4350,46 @@ binary_upgrade_set_type_oids_by_type_oid(Archive *fout,
 						  pg_type_array_oid);
 	}
 
+	/*
+	 * Pre-set the multirange type oid and its own array type oid.
+	 */
+	if (include_multirange_type)
+	{
+		if (fout->remoteVersion >= 130000)
+		{
+			appendPQExpBuffer(upgrade_query,
+							  "SELECT t.oid, t.typarray "
+							  "FROM pg_catalog.pg_type t "
+							  "JOIN pg_catalog.pg_range r "
+							  "ON t.oid = r.rngmultitypid "
+							  "WHERE r.rngtypid = '%u'::pg_catalog.oid;",
+							  pg_type_oid);
+
+			res = ExecuteSqlQueryForSingleRow(fout, upgrade_query->data);
+
+			pg_type_multirange_oid = atooid(PQgetvalue(res, 0, PQfnumber(res, "oid")));
+			pg_type_multirange_array_oid = atooid(PQgetvalue(res, 0, PQfnumber(res, "typarray")));
+
+			PQclear(res);
+		}
+		else
+		{
+			pg_type_multirange_oid = get_next_possible_free_pg_type_oid(fout, upgrade_query);
+			pg_type_multirange_array_oid = get_next_possible_free_pg_type_oid(fout, upgrade_query);
+		}
+
+		appendPQExpBufferStr(upgrade_buffer,
+							 "\n-- For binary upgrade, must preserve multirange pg_type oid\n");
+		appendPQExpBuffer(upgrade_buffer,
+						  "SELECT pg_catalog.binary_upgrade_set_next_multirange_pg_type_oid('%u'::pg_catalog.oid);\n\n",
+						  pg_type_multirange_oid);
+		appendPQExpBufferStr(upgrade_buffer,
+							 "\n-- For binary upgrade, must preserve multirange pg_type array oid\n");
+		appendPQExpBuffer(upgrade_buffer,
+						  "SELECT pg_catalog.binary_upgrade_set_next_multirange_array_pg_type_oid('%u'::pg_catalog.oid);\n\n",
+						  pg_type_multirange_array_oid);
+	}
+
 	destroyPQExpBuffer(upgrade_query);
 }
 
@@ -4374,7 +4423,7 @@ binary_upgrade_set_type_oids_by_rel_oid(Archive *fout,
 	pg_type_oid = atooid(PQgetvalue(upgrade_res, 0, PQfnumber(upgrade_res, "crel")));
 
 	binary_upgrade_set_type_oids_by_type_oid(fout, upgrade_buffer,
-											 pg_type_oid, false);
+											 pg_type_oid, false, false);
 
 	if (!PQgetisnull(upgrade_res, 0, PQfnumber(upgrade_res, "trel")))
 	{
@@ -4923,6 +4972,11 @@ getTypes(Archive *fout, int *numTypes)
 		else
 			tyinfo[i].isArray = false;
 
+		if (tyinfo[i].typtype == 'm')
+			tyinfo[i].isMultirange = true;
+		else
+			tyinfo[i].isMultirange = false;
+
 		/* Decide whether we want to dump it */
 		selectDumpableType(&tyinfo[i], fout);
 
@@ -8037,9 +8091,12 @@ getProcLangs(Archive *fout, int *numProcLangs)
 
 /*
  * getCasts
- *	  get basic information about every cast in the system
+ *	  get basic information about most casts in the system
  *
  * numCasts is set to the number of casts read in
+ *
+ * Skip casts from a range to its multirange, since we'll create those
+ * automatically.
  */
 CastInfo *
 getCasts(Archive *fout, int *numCasts)
@@ -8057,7 +8114,20 @@ getCasts(Archive *fout, int *numCasts)
 	int			i_castcontext;
 	int			i_castmethod;
 
-	if (fout->remoteVersion >= 80400)
+	if (fout->remoteVersion >= 130000)
+	{
+		appendPQExpBufferStr(query, "SELECT tableoid, oid, "
+							 "castsource, casttarget, castfunc, castcontext, "
+							 "castmethod "
+							 "FROM pg_cast c "
+							 "WHERE NOT EXISTS ( "
+							 "SELECT 1 FROM pg_range r "
+							 "WHERE c.castsource = r.rngtypid "
+							 "AND c.casttarget = r.rngmultitypid "
+							 ") "
+							 "ORDER BY 3,4");
+	}
+	else if (fout->remoteVersion >= 80400)
 	{
 		appendPQExpBufferStr(query, "SELECT tableoid, oid, "
 							 "castsource, casttarget, castfunc, castcontext, "
@@ -10245,7 +10315,7 @@ dumpEnumType(Archive *fout, TypeInfo *tyinfo)
 	if (dopt->binary_upgrade)
 		binary_upgrade_set_type_oids_by_type_oid(fout, q,
 												 tyinfo->dobj.catId.oid,
-												 false);
+												 false, false);
 
 	appendPQExpBuffer(q, "CREATE TYPE %s AS ENUM (",
 					  qualtypname);
@@ -10371,7 +10441,7 @@ dumpRangeType(Archive *fout, TypeInfo *tyinfo)
 	if (dopt->binary_upgrade)
 		binary_upgrade_set_type_oids_by_type_oid(fout, q,
 												 tyinfo->dobj.catId.oid,
-												 false);
+												 false, true);
 
 	appendPQExpBuffer(q, "CREATE TYPE %s AS RANGE (",
 					  qualtypname);
@@ -10477,7 +10547,7 @@ dumpUndefinedType(Archive *fout, TypeInfo *tyinfo)
 	if (dopt->binary_upgrade)
 		binary_upgrade_set_type_oids_by_type_oid(fout, q,
 												 tyinfo->dobj.catId.oid,
-												 false);
+												 false, false);
 
 	appendPQExpBuffer(q, "CREATE TYPE %s;\n",
 					  qualtypname);
@@ -10682,7 +10752,7 @@ dumpBaseType(Archive *fout, TypeInfo *tyinfo)
 	if (dopt->binary_upgrade)
 		binary_upgrade_set_type_oids_by_type_oid(fout, q,
 												 tyinfo->dobj.catId.oid,
-												 false);
+												 false, false);
 
 	appendPQExpBuffer(q,
 					  "CREATE TYPE %s (\n"
@@ -10869,7 +10939,8 @@ dumpDomain(Archive *fout, TypeInfo *tyinfo)
 	if (dopt->binary_upgrade)
 		binary_upgrade_set_type_oids_by_type_oid(fout, q,
 												 tyinfo->dobj.catId.oid,
-												 true); /* force array type */
+												 true,		/* force array type */
+												 false);	/* force multirange type */
 
 	qtypname = pg_strdup(fmtId(tyinfo->dobj.name));
 	qualtypname = pg_strdup(fmtQualifiedDumpable(tyinfo));
@@ -11057,7 +11128,7 @@ dumpCompositeType(Archive *fout, TypeInfo *tyinfo)
 	{
 		binary_upgrade_set_type_oids_by_type_oid(fout, q,
 												 tyinfo->dobj.catId.oid,
-												 false);
+												 false, false);
 		binary_upgrade_set_pg_class_oids(fout, q, tyinfo->typrelid, false);
 	}
 
@@ -11331,7 +11402,7 @@ dumpShellType(Archive *fout, ShellTypeInfo *stinfo)
 	if (dopt->binary_upgrade)
 		binary_upgrade_set_type_oids_by_type_oid(fout, q,
 												 stinfo->baseType->dobj.catId.oid,
-												 false);
+												 false, false);
 
 	appendPQExpBuffer(q, "CREATE TYPE %s;\n",
 					  fmtQualifiedDumpable(stinfo));
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 21004e5078..63e0ba7116 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -175,6 +175,7 @@ typedef struct _typeInfo
 	char		typrelkind;		/* 'r', 'v', 'c', etc */
 	char		typtype;		/* 'b', 'c', etc */
 	bool		isArray;		/* true if auto-generated array type */
+	bool		isMultirange;	/* true if auto-generated multirange type */
 	bool		isDefined;		/* true if typisdefined */
 	/* If needed, we'll create a "shell type" entry for it; link that here: */
 	struct _shellTypeInfo *shellType;	/* shell-type entry, or NULL */
diff --git a/src/include/access/tupmacs.h b/src/include/access/tupmacs.h
index 0b205a0dc6..3c113fa3bd 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/binary_upgrade.h b/src/include/catalog/binary_upgrade.h
index 12d94fe1b3..01d95117cd 100644
--- a/src/include/catalog/binary_upgrade.h
+++ b/src/include/catalog/binary_upgrade.h
@@ -16,6 +16,8 @@
 
 extern PGDLLIMPORT Oid binary_upgrade_next_pg_type_oid;
 extern PGDLLIMPORT Oid binary_upgrade_next_array_pg_type_oid;
+extern PGDLLIMPORT Oid binary_upgrade_next_mrng_pg_type_oid;
+extern PGDLLIMPORT Oid binary_upgrade_next_mrng_array_pg_type_oid;
 extern PGDLLIMPORT Oid binary_upgrade_next_toast_pg_type_oid;
 
 extern PGDLLIMPORT Oid binary_upgrade_next_heap_pg_class_oid;
diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h
index 13e07fc314..48a6bf5598 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_rngmultitypid_index, 8001, on pg_range using btree(rngmultitypid 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_aggregate.dat b/src/include/catalog/pg_aggregate.dat
index ffabe275c0..03bab74253 100644
--- a/src/include/catalog/pg_aggregate.dat
+++ b/src/include/catalog/pg_aggregate.dat
@@ -538,6 +538,17 @@
   aggtransfn => 'bytea_string_agg_transfn',
   aggfinalfn => 'bytea_string_agg_finalfn', aggtranstype => 'internal' },
 
+# range
+{ aggfnoid => 'range_intersect_agg(anyrange)',
+  aggtransfn => 'range_intersect_agg_transfn',
+  aggcombinefn => 'range_intersect_agg_transfn', aggtranstype => 'anyrange' },
+{ aggfnoid => 'range_intersect_agg(anymultirange)',
+  aggtransfn => 'multirange_intersect_agg_transfn',
+  aggcombinefn => 'multirange_intersect_agg_transfn', aggtranstype => 'anymultirange' },
+{ aggfnoid => 'range_agg(anyrange)', aggtransfn => 'range_agg_transfn',
+  aggfinalfn => 'range_agg_finalfn', aggfinalextra => 't',
+  aggtranstype => 'internal' },
+
 # json
 { aggfnoid => 'json_agg', aggtransfn => 'json_agg_transfn',
   aggfinalfn => 'json_agg_finalfn', aggtranstype => 'internal' },
diff --git a/src/include/catalog/pg_amop.dat b/src/include/catalog/pg_amop.dat
index 11aaa519c8..827ac4f03c 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 c67768fcab..0dafd4ec63 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',
@@ -1203,12 +1210,13 @@
   amprocrighttype => 'anyrange', amprocnum => '4',
   amproc => 'brin_inclusion_union' },
 { amprocfamily => 'brin/range_inclusion_ops', amproclefttype => 'anyrange',
-  amprocrighttype => 'anyrange', amprocnum => '11', amproc => 'range_merge' },
+  amprocrighttype => 'anyrange', amprocnum => '11',
+  amproc => 'range_merge(anyrange,anyrange)' },
 { amprocfamily => 'brin/range_inclusion_ops', amproclefttype => 'anyrange',
   amprocrighttype => 'anyrange', amprocnum => '13',
   amproc => 'range_contains' },
 { amprocfamily => 'brin/range_inclusion_ops', amproclefttype => 'anyrange',
-  amprocrighttype => 'anyrange', amprocnum => '14', amproc => 'isempty' },
+  amprocrighttype => 'anyrange', amprocnum => '14', amproc => 'isempty(anyrange)' },
 
 # minmax pg_lsn
 { amprocfamily => 'brin/pg_lsn_minmax_ops', amproclefttype => 'pg_lsn',
diff --git a/src/include/catalog/pg_cast.dat b/src/include/catalog/pg_cast.dat
index 6ef8b8a4e7..14277c8fdf 100644
--- a/src/include/catalog/pg_cast.dat
+++ b/src/include/catalog/pg_cast.dat
@@ -512,4 +512,17 @@
 { castsource => 'jsonb', casttarget => 'float8', castfunc => 'float8(jsonb)',
   castcontext => 'e', castmethod => 'f' },
 
+# range to multirange
+{ castsource => 'int4range', casttarget => 'int4multirange', castfunc => 'int4multirange(int4range)',
+  castcontext => 'e', castmethod => 'f' },
+{ castsource => 'int8range', casttarget => 'int8multirange', castfunc => 'int8multirange(int8range)',
+  castcontext => 'e', castmethod => 'f' },
+{ castsource => 'numrange', casttarget => 'nummultirange', castfunc => 'nummultirange(numrange)',
+  castcontext => 'e', castmethod => 'f' },
+{ castsource => 'daterange', casttarget => 'datemultirange', castfunc => 'datemultirange(daterange)',
+  castcontext => 'e', castmethod => 'f' },
+{ castsource => 'tsrange', casttarget => 'tsmultirange', castfunc => 'tsmultirange(tsrange)',
+  castcontext => 'e', castmethod => 'f' },
+{ castsource => 'tstzrange', casttarget => 'tstzmultirange', castfunc => 'tstzmultirange(tstzrange)',
+  castcontext => 'e', castmethod => 'f' },
 ]
diff --git a/src/include/catalog/pg_cast.h b/src/include/catalog/pg_cast.h
index 1b81b52df6..f23ba28d9c 100644
--- a/src/include/catalog/pg_cast.h
+++ b/src/include/catalog/pg_cast.h
@@ -23,6 +23,8 @@
 #include "catalog/genbki.h"
 #include "catalog/pg_cast_d.h"
 
+#include "catalog/dependency.h"
+
 /* ----------------
  *		pg_cast definition.  cpp turns this into
  *		typedef struct FormData_pg_cast
@@ -87,4 +89,11 @@ typedef enum CoercionMethod
 
 #endif							/* EXPOSE_TO_CLIENT_CODE */
 
+extern ObjectAddress CastCreate(Oid sourcetypeid,
+								Oid targettypeid,
+								Oid funcid,
+								char castcontext,
+								char castmethod,
+								DependencyType behavior);
+
 #endif							/* PG_CAST_H */
diff --git a/src/include/catalog/pg_opclass.dat b/src/include/catalog/pg_opclass.dat
index ab2f50c9eb..eebebbcdb4 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 7c135da3b1..24f4a32203 100644
--- a/src/include/catalog/pg_operator.dat
+++ b/src/include/catalog/pg_operator.dat
@@ -3297,5 +3297,174 @@
   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' },
+{ oid => '8073', oid_symbol => 'OID_RANGE_OVERLAPS_MULTIRANGE_OP',
+  descr => 'overlaps',
+  oprname => '&&', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '&&(anymultirange,anyrange)',
+  oprcode => 'range_overlaps_multirange', oprrest => 'areasel',
+  oprjoin => 'areajoinsel' },
+{ oid => '8074', oid_symbol => 'OID_MULTIRANGE_OVERLAPS_RANGE_OP', descr => 'contains',
+  oprname => '&&', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcom => '&&(anyrange,anymultirange)',
+  oprcode => 'multirange_overlaps_range', oprrest => 'areasel',
+  oprjoin => 'areajoinsel' },
+{ oid => '8075', oid_symbol => 'OID_MULTIRANGE_OVERLAPS_MULTIRANGE_OP', descr => 'contains',
+  oprname => '&&', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '&&(anymultirange,anymultirange)',
+  oprcode => 'multirange_overlaps_multirange', oprrest => 'areasel',
+  oprjoin => 'areajoinsel' },
+{ oid => '8064', oid_symbol => 'OID_MULTIRANGE_CONTAINS_ELEM_OP',
+  descr => 'contains',
+  oprname => '@>', oprleft => 'anymultirange', oprright => 'anyelement',
+  oprresult => 'bool', oprcom => '<@(anyelement,anymultirange)',
+  oprcode => 'multirange_contains_elem', oprrest => 'contsel',
+  oprjoin => 'contjoinsel' },
+{ oid => '8065', oid_symbol => 'OID_MULTIRANGE_CONTAINS_RANGE_OP', descr => 'contains',
+  oprname => '@>', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcom => '<@(anyrange,anymultirange)',
+  oprcode => 'multirange_contains_range', oprrest => 'contsel',
+  oprjoin => 'contjoinsel' },
+{ oid => '8066', oid_symbol => 'OID_MULTIRANGE_CONTAINS_MULTIRANGE_OP', descr => 'contains',
+  oprname => '@>', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '<@(anymultirange,anymultirange)',
+  oprcode => 'multirange_contains_multirange', oprrest => 'contsel',
+  oprjoin => 'contjoinsel' },
+{ oid => '8067', oid_symbol => 'OID_MULTIRANGE_ELEM_CONTAINED_OP',
+  descr => 'is contained by',
+  oprname => '<@', oprleft => 'anyelement', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '@>(anymultirange,anyelement)',
+  oprcode => 'elem_contained_by_multirange', oprrest => 'contsel',
+  oprjoin => 'contjoinsel' },
+{ oid => '8068', oid_symbol => 'OID_MULTIRANGE_RANGE_CONTAINED_OP',
+  descr => 'is contained by',
+  oprname => '<@', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '@>(anymultirange,anyrange)',
+  oprcode => 'range_contained_by_multirange', oprrest => 'contsel',
+  oprjoin => 'contjoinsel' },
+{ oid => '8069', oid_symbol => 'OID_MULTIRANGE_MULTIRANGE_CONTAINED_OP',
+  descr => 'is contained by',
+  oprname => '<@', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '@>(anymultirange,anymultirange)',
+  oprcode => 'multirange_contained_by_multirange', oprrest => 'contsel',
+  oprjoin => 'contjoinsel' },
+{ oid => '8106', oid_symbol => 'OID_RANGE_OVERLAPS_LEFT_MULTIRANGE_OP',
+  descr => 'overlaps or is left of',
+  oprname => '&<', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcode => 'range_overleft_multirange', oprrest => 'scalarltsel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8107', oid_symbol => 'OID_MULTIRANGE_OVERLAPS_LEFT_RANGE_OP',
+  descr => 'overlaps or is left of',
+  oprname => '&<', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcode => 'multirange_overleft_range', oprrest => 'scalarltsel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8108', oid_symbol => 'OID_MULTIRANGE_OVERLAPS_LEFT_MULTIRANGE_OP',
+  descr => 'overlaps or is left of',
+  oprname => '&<', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcode => 'multirange_overleft_multirange', oprrest => 'scalarltsel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8109', oid_symbol => 'OID_RANGE_OVERLAPS_RIGHT_MULTIRANGE_OP',
+  descr => 'overlaps or is right of',
+  oprname => '&>', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcode => 'range_overright_multirange', oprrest => 'scalargtsel',
+  oprjoin => 'scalargtjoinsel' },
+{ oid => '8110', oid_symbol => 'OID_MULTIRANGE_OVERLAPS_RIGHT_RANGE_OP',
+  descr => 'overlaps or is right of',
+  oprname => '&>', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcode => 'multirange_overright_range', oprrest => 'scalargtsel',
+  oprjoin => 'scalargtjoinsel' },
+{ oid => '8111', oid_symbol => 'OID_MULTIRANGE_OVERLAPS_RIGHT_MULTIRANGE_OP',
+  descr => 'overlaps or is right of',
+  oprname => '&>', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcode => 'multirange_overright_multirange', oprrest => 'scalargtsel',
+  oprjoin => 'scalargtjoinsel' },
+{ oid => '8091', oid_symbol => 'OID_RANGE_ADJACENT_MULTIRANGE_OP', descr => 'is adjacent to',
+  oprname => '-|-', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '-|-(anymultirange,anyrange)',
+  oprcode => 'range_adjacent_multirange', oprrest => 'scalarltsel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8092', oid_symbol => 'OID_MULTIRANGE_ADJACENT_RANGE_OP', descr => 'is adjacent to',
+  oprname => '-|-', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcom => '-|-(anyrange,anymultirange)',
+  oprcode => 'multirange_adjacent_range', oprrest => 'scalarltsel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8093', oid_symbol => 'OID_MULTIRANGE_ADJACENT_MULTIRANGE_OP', descr => 'is adjacent to',
+  oprname => '-|-', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '-|-(anymultirange,anymultirange)',
+  oprcode => 'multirange_adjacent_multirange', oprrest => 'scalarltsel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8117', descr => 'multirange union',
+  oprname => '+', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'anymultirange', oprcom => '+(anymultirange,anymultirange)',
+  oprcode => 'multirange_union' },
+{ oid => '8123', descr => 'multirange minus',
+  oprname => '-', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'anymultirange', oprcode => 'multirange_minus' },
+{ oid => '8129', descr => 'multirange intersect',
+  oprname => '*', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'anymultirange', oprcom => '*(anymultirange,anymultirange)',
+  oprcode => 'multirange_intersect' },
+{ oid => '8082', oid_symbol => 'OID_RANGE_BEFORE_MULTIRANGE_OP', descr => 'is left of',
+  oprname => '<<', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '>>(anymultirange,anyrange)',
+  oprcode => 'range_before_multirange', oprrest => 'scalarltsel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8083', oid_symbol => 'OID_MULTIRANGE_BEFORE_RANGE_OP', descr => 'is left of',
+  oprname => '<<', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcom => '>>(anyrange,anymultirange)',
+  oprcode => 'multirange_before_range', oprrest => 'scalarltsel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8084', oid_symbol => 'OID_MULTIRANGE_BEFORE_MULTIRANGE_OP', descr => 'is left of',
+  oprname => '<<', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '>>(anymultirange,anymultirange)',
+  oprcode => 'multirange_before_multirange', oprrest => 'scalarltsel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8085', oid_symbol => 'OID_RANGE_AFTER_MULTIRANGE_OP', descr => 'is right of',
+  oprname => '>>', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '<<(anymultirange,anyrange)',
+  oprcode => 'range_after_multirange', oprrest => 'scalargtsel',
+  oprjoin => 'scalargtjoinsel' },
+{ oid => '8086', oid_symbol => 'OID_MULTIRANGE_AFTER_RANGE_OP', descr => 'is right of',
+  oprname => '>>', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcom => '<<(anyrange,anymultirange)',
+  oprcode => 'multirange_after_range', oprrest => 'scalargtsel',
+  oprjoin => 'scalargtjoinsel' },
+{ oid => '8087', oid_symbol => 'OID_MULTIRANGE_AFTER_MULTIRANGE_OP', descr => 'is right of',
+  oprname => '>>', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '<<(anymultirange,anymultirange)',
+  oprcode => 'multirange_after_multirange', oprrest => 'scalargtsel',
+  oprjoin => 'scalargtjoinsel' },
 
 ]
diff --git a/src/include/catalog/pg_opfamily.dat b/src/include/catalog/pg_opfamily.dat
index 26227df216..901fd7b303 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 fcf2a1214c..2c1bb373be 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -9624,6 +9624,10 @@
   descr => 'the smallest range which includes both of the given ranges',
   proname => 'range_merge', prorettype => 'anyrange',
   proargtypes => 'anyrange anyrange', prosrc => 'range_merge' },
+{ oid => '8143',
+  descr => 'the smallest range which includes the whole multirange',
+  proname => 'range_merge', prorettype => 'anyrange',
+  proargtypes => 'anymultirange', prosrc => 'range_merge_from_multirange' },
 { oid => '3868',
   proname => 'range_intersect', prorettype => 'anyrange',
   proargtypes => 'anyrange anyrange', prosrc => 'range_intersect' },
@@ -9673,6 +9677,13 @@
 { oid => '3169', descr => 'restriction selectivity for range operators',
   proname => 'rangesel', provolatile => 's', prorettype => 'float8',
   proargtypes => 'internal oid internal int4', prosrc => 'rangesel' },
+{ oid => '8133', descr => 'range aggregate by intersecting',
+  proname => 'range_intersect_agg_transfn', prorettype => 'anyrange',
+  proargtypes => 'anyrange anyrange', prosrc => 'range_intersect_agg_transfn'},
+{ oid => '8134', descr => 'range aggregate by intersecting',
+  proname => 'range_intersect_agg', prokind => 'a', proisstrict => 'f',
+  prorettype => 'anyrange', proargtypes => 'anyrange',
+  prosrc => 'aggregate_dummy'},
 
 { oid => '3914', descr => 'convert an int4 range to canonical form',
   proname => 'int4range_canonical', prorettype => 'int4range',
@@ -9741,6 +9752,258 @@
   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 => '8094', descr => 'lower bound of multirange',
+  proname => 'lower', prorettype => 'anyelement', proargtypes => 'anymultirange',
+  prosrc => 'multirange_lower' },
+{ oid => '8095', descr => 'upper bound of multirange',
+  proname => 'upper', prorettype => 'anyelement', proargtypes => 'anymultirange',
+  prosrc => 'multirange_upper' },
+{ oid => '8057', descr => 'is the multirange empty?',
+  proname => 'isempty', prorettype => 'bool',
+  proargtypes => 'anymultirange', prosrc => 'multirange_empty' },
+{ oid => '8096', descr => 'is the multirange\'s lower bound inclusive?',
+  proname => 'lower_inc', prorettype => 'bool', proargtypes => 'anymultirange',
+  prosrc => 'multirange_lower_inc' },
+{ oid => '8097', descr => 'is the multirange\'s upper bound inclusive?',
+  proname => 'upper_inc', prorettype => 'bool', proargtypes => 'anymultirange',
+  prosrc => 'multirange_upper_inc' },
+{ oid => '8098', descr => 'is the multirange\'s lower bound infinite?',
+  proname => 'lower_inf', prorettype => 'bool', proargtypes => 'anymultirange',
+  prosrc => 'multirange_lower_inf' },
+{ oid => '8099', descr => 'is the multirange\'s upper bound infinite?',
+  proname => 'upper_inf', prorettype => 'bool', proargtypes => 'anymultirange',
+  prosrc => 'multirange_upper_inf' },
+{ oid => '8008', descr => 'multirange typanalyze',
+  proname => 'multirange_typanalyze', provolatile => 's', prorettype => 'bool',
+  proargtypes => 'internal', prosrc => 'multirange_typanalyze' },
+{ oid => '8033',
+  proname => 'multirange_eq', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_eq' },
+{ oid => '8034',
+  proname => 'multirange_ne', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_ne' },
+{ oid => '8070',
+  proname => 'range_overlaps_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_overlaps_multirange' },
+{ oid => '8071',
+  proname => 'multirange_overlaps_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_overlaps_range' },
+{ oid => '8072',
+  proname => 'multirange_overlaps_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_overlaps_multirange' },
+{ oid => '8058',
+  proname => 'multirange_contains_elem', prorettype => 'bool',
+  proargtypes => 'anymultirange anyelement', prosrc => 'multirange_contains_elem' },
+{ oid => '8059',
+  proname => 'multirange_contains_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_contains_range' },
+{ oid => '8060',
+  proname => 'multirange_contains_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_contains_multirange' },
+{ oid => '8061',
+  proname => 'elem_contained_by_multirange', prorettype => 'bool',
+  proargtypes => 'anyelement anymultirange', prosrc => 'elem_contained_by_multirange' },
+{ oid => '8062',
+  proname => 'range_contained_by_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_contained_by_multirange' },
+{ oid => '8063',
+  proname => 'multirange_contained_by_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_contained_by_multirange' },
+{ oid => '8088',
+  proname => 'range_adjacent_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_adjacent_multirange' },
+{ oid => '8089',
+  proname => 'multirange_adjacent_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_adjacent_multirange' },
+{ oid => '8090',
+  proname => 'multirange_adjacent_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_adjacent_range' },
+{ oid => '8076',
+  proname => 'range_before_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_before_multirange' },
+{ oid => '8077',
+  proname => 'multirange_before_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_before_range' },
+{ oid => '8078',
+  proname => 'multirange_before_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_before_multirange' },
+{ oid => '8079',
+  proname => 'range_after_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_after_multirange' },
+{ oid => '8080',
+  proname => 'multirange_after_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_after_range' },
+{ oid => '8081',
+  proname => 'multirange_after_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_after_multirange' },
+{ oid => '8100',
+  proname => 'range_overleft_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_overleft_multirange' },
+{ oid => '8101',
+  proname => 'multirange_overleft_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_overleft_range' },
+{ oid => '8102',
+  proname => 'multirange_overleft_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_overleft_multirange' },
+{ oid => '8103',
+  proname => 'range_overright_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_overright_multirange' },
+{ oid => '8104',
+  proname => 'multirange_overright_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_overright_range' },
+{ oid => '8105',
+  proname => 'multirange_overright_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_overright_multirange' },
+{ oid => '8114',
+  proname => 'multirange_union', prorettype => 'anymultirange',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_union' },
+{ oid => '8120',
+  proname => 'multirange_minus', prorettype => 'anymultirange',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_minus' },
+{ oid => '8126',
+  proname => 'multirange_intersect', prorettype => 'anymultirange',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_intersect' },
+{ oid => '8022', descr => 'less-equal-greater',
+  proname => 'multirange_cmp', prorettype => 'int4',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_cmp' },
+{ oid => '8023',
+  proname => 'multirange_lt', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_lt' },
+{ oid => '8024',
+  proname => 'multirange_le', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_le' },
+{ oid => '8025',
+  proname => 'multirange_ge', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_ge' },
+{ oid => '8026',
+  proname => 'multirange_gt', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_gt' },
+{ oid => '8038', descr => 'hash a multirange',
+  proname => 'hash_multirange', prorettype => 'int4', proargtypes => 'anymultirange',
+  prosrc => 'hash_multirange' },
+{ oid => '8039', descr => 'hash a multirange',
+  proname => 'hash_multirange_extended', prorettype => 'int8',
+  proargtypes => 'anymultirange int8', prosrc => 'hash_multirange_extended' },
+
+{ oid => '8045', descr => 'int4multirange constructor',
+  proname => 'int4multirange', proisstrict => 'f',
+  prorettype => 'int4multirange', proargtypes => '',
+  prosrc => 'multirange_constructor0' },
+{ oid => '8146', descr => 'int4multirange constructor',
+  proname => 'int4multirange', proisstrict => 't',
+  prorettype => 'int4multirange', proargtypes => 'int4range',
+  prosrc => 'multirange_constructor1' },
+{ oid => '8046', descr => 'int4multirange constructor',
+  proname => 'int4multirange', provariadic => 'int4range', proisstrict => 'f',
+  prorettype => 'int4multirange', proargtypes => '_int4range',
+  proallargtypes => '{_int4range}', proargmodes => '{v}',
+  prosrc => 'multirange_constructor2' },
+{ oid => '8047', descr => 'nummultirange constructor',
+  proname => 'nummultirange', proisstrict => 'f',
+  prorettype => 'nummultirange', proargtypes => '',
+  prosrc => 'multirange_constructor0' },
+{ oid => '8147', descr => 'nummultirange constructor',
+  proname => 'nummultirange', proisstrict => 't',
+  prorettype => 'nummultirange', proargtypes => 'numrange',
+  prosrc => 'multirange_constructor1' },
+{ oid => '8048', descr => 'nummultirange constructor',
+  proname => 'nummultirange', provariadic => 'numrange', proisstrict => 'f',
+  prorettype => 'nummultirange', proargtypes => '_numrange',
+  proallargtypes => '{_numrange}', proargmodes => '{v}',
+  prosrc => 'multirange_constructor2' },
+{ oid => '8049', descr => 'tsmultirange constructor',
+  proname => 'tsmultirange', proisstrict => 'f',
+  prorettype => 'tsmultirange', proargtypes => '',
+  prosrc => 'multirange_constructor0' },
+{ oid => '8148', descr => 'tsmultirange constructor',
+  proname => 'tsmultirange', proisstrict => 't',
+  prorettype => 'tsmultirange', proargtypes => 'tsrange',
+  prosrc => 'multirange_constructor1' },
+{ oid => '8050', descr => 'tsmultirange constructor',
+  proname => 'tsmultirange', provariadic => 'tsrange', proisstrict => 'f',
+  prorettype => 'tsmultirange', proargtypes => '_tsrange',
+  proallargtypes => '{_tsrange}', proargmodes => '{v}',
+  prosrc => 'multirange_constructor2' },
+{ oid => '8051', descr => 'tstzmultirange constructor',
+  proname => 'tstzmultirange', proisstrict => 'f',
+  prorettype => 'tstzmultirange', proargtypes => '',
+  prosrc => 'multirange_constructor0' },
+{ oid => '8149', descr => 'tstzmultirange constructor',
+  proname => 'tstzmultirange', proisstrict => 't',
+  prorettype => 'tstzmultirange', proargtypes => 'tstzrange',
+  prosrc => 'multirange_constructor1' },
+{ oid => '8052', descr => 'tstzmultirange constructor',
+  proname => 'tstzmultirange', provariadic => 'tstzrange', proisstrict => 'f',
+  prorettype => 'tstzmultirange', proargtypes => '_tstzrange',
+  proallargtypes => '{_tstzrange}', proargmodes => '{v}',
+  prosrc => 'multirange_constructor2' },
+{ oid => '8053', descr => 'datemultirange constructor',
+  proname => 'datemultirange', proisstrict => 'f',
+  prorettype => 'datemultirange', proargtypes => '',
+  prosrc => 'multirange_constructor0' },
+{ oid => '8150', descr => 'datemultirange constructor',
+  proname => 'datemultirange', proisstrict => 't',
+  prorettype => 'datemultirange', proargtypes => 'daterange',
+  prosrc => 'multirange_constructor1' },
+{ oid => '8054', descr => 'datemultirange constructor',
+  proname => 'datemultirange', provariadic => 'daterange', proisstrict => 'f',
+  prorettype => 'datemultirange', proargtypes => '_daterange',
+  proallargtypes => '{_daterange}', proargmodes => '{v}',
+  prosrc => 'multirange_constructor2' },
+{ oid => '8055', descr => 'int8multirange constructor',
+  proname => 'int8multirange', proisstrict => 'f',
+  prorettype => 'int8multirange', proargtypes => '',
+  prosrc => 'multirange_constructor0' },
+{ oid => '8151', descr => 'int8multirange constructor',
+  proname => 'int8multirange', proisstrict => 't',
+  prorettype => 'int8multirange', proargtypes => 'int8range',
+  prosrc => 'multirange_constructor1' },
+{ oid => '8056', descr => 'int8multirange constructor',
+  proname => 'int8multirange', provariadic => 'int8range', proisstrict => 'f',
+  prorettype => 'int8multirange', proargtypes => '_int8range',
+  proallargtypes => '{_int8range}', proargmodes => '{v}',
+  prosrc => 'multirange_constructor2' },
+{ oid => '8152', descr => 'anymultirange cast',
+  proname => 'multirange', proisstrict => 't',
+  prorettype => 'anymultirange', proargtypes => 'anyrange',
+  prosrc => 'multirange_constructor1' },
+{ oid => '8130', descr => 'aggregate transition function',
+  proname => 'range_agg_transfn', proisstrict => 'f', prorettype => 'internal',
+  proargtypes => 'internal anyrange', prosrc => 'range_agg_transfn' },
+{ oid => '8131', descr => 'aggregate final function',
+  proname => 'range_agg_finalfn', proisstrict => 'f', prorettype => 'anymultirange',
+  proargtypes => 'internal anyrange', prosrc => 'range_agg_finalfn' },
+{ oid => '8132', descr => 'combine aggregate input into a multirange',
+  proname => 'range_agg', prokind => 'a', proisstrict => 'f',
+  prorettype => 'anymultirange', proargtypes => 'anyrange',
+  prosrc => 'aggregate_dummy' },
+{ oid => '8135', descr => 'range aggregate by intersecting',
+  proname => 'multirange_intersect_agg_transfn', prorettype => 'anymultirange',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_intersect_agg_transfn'},
+{ oid => '8136', descr => 'range aggregate by intersecting',
+  proname => 'range_intersect_agg', prokind => 'a', proisstrict => 'f',
+  prorettype => 'anymultirange', proargtypes => 'anymultirange',
+  prosrc => 'aggregate_dummy'},
+
 # date, time, timestamp constructors
 { oid => '3846', descr => 'construct date',
   proname => 'make_date', prorettype => 'date', proargtypes => 'int4 int4 int4',
@@ -10123,6 +10386,14 @@
   proname => 'binary_upgrade_set_next_array_pg_type_oid', provolatile => 'v',
   proparallel => 'r', prorettype => 'void', proargtypes => 'oid',
   prosrc => 'binary_upgrade_set_next_array_pg_type_oid' },
+{ oid => '8144', descr => 'for use by pg_upgrade',
+  proname => 'binary_upgrade_set_next_multirange_pg_type_oid', provolatile => 'v',
+  proparallel => 'r', prorettype => 'void', proargtypes => 'oid',
+  prosrc => 'binary_upgrade_set_next_multirange_pg_type_oid' },
+{ oid => '8145', descr => 'for use by pg_upgrade',
+  proname => 'binary_upgrade_set_next_multirange_array_pg_type_oid', provolatile => 'v',
+  proparallel => 'r', prorettype => 'void', proargtypes => 'oid',
+  prosrc => 'binary_upgrade_set_next_multirange_array_pg_type_oid' },
 { oid => '3585', descr => 'for use by pg_upgrade',
   proname => 'binary_upgrade_set_next_toast_pg_type_oid', provolatile => 'v',
   proparallel => 'r', prorettype => 'void', proargtypes => 'oid',
diff --git a/src/include/catalog/pg_range.dat b/src/include/catalog/pg_range.dat
index 479754c245..10060255c9 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',
+  rngmultitypid => 'int4multirange' },
 { rngtypid => 'numrange', rngsubtype => 'numeric',
   rngsubopc => 'btree/numeric_ops', rngcanonical => '-',
-  rngsubdiff => 'numrange_subdiff' },
+  rngsubdiff => 'numrange_subdiff', rngmultitypid => 'nummultirange' },
 { rngtypid => 'tsrange', rngsubtype => 'timestamp',
   rngsubopc => 'btree/timestamp_ops', rngcanonical => '-',
-  rngsubdiff => 'tsrange_subdiff' },
+  rngsubdiff => 'tsrange_subdiff', rngmultitypid => 'tsmultirange' },
 { rngtypid => 'tstzrange', rngsubtype => 'timestamptz',
   rngsubopc => 'btree/timestamptz_ops', rngcanonical => '-',
-  rngsubdiff => 'tstzrange_subdiff' },
+  rngsubdiff => 'tstzrange_subdiff', rngmultitypid => 'tstzmultirange' },
 { rngtypid => 'daterange', rngsubtype => 'date', rngsubopc => 'btree/date_ops',
-  rngcanonical => 'daterange_canonical', rngsubdiff => 'daterange_subdiff' },
+  rngcanonical => 'daterange_canonical', rngsubdiff => 'daterange_subdiff',
+  rngmultitypid => 'datemultirange' },
 { rngtypid => 'int8range', rngsubtype => 'int8', rngsubopc => 'btree/int8_ops',
-  rngcanonical => 'int8range_canonical', rngsubdiff => 'int8range_subdiff' },
+  rngcanonical => 'int8range_canonical', rngsubdiff => 'int8range_subdiff',
+  rngmultitypid => 'int8multirange' },
 
 ]
diff --git a/src/include/catalog/pg_range.h b/src/include/catalog/pg_range.h
index 98172bb1b6..3efd463c6d 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			rngmultitypid 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 fe2c4eabb4..59bae8232f 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 e1a5ab3df3..600ac58c9d 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/commands/typecmds.h b/src/include/commands/typecmds.h
index fc18d64347..7d0a4a9ea5 100644
--- a/src/include/commands/typecmds.h
+++ b/src/include/commands/typecmds.h
@@ -29,6 +29,8 @@ extern ObjectAddress DefineRange(CreateRangeStmt *stmt);
 extern ObjectAddress AlterEnum(AlterEnumStmt *stmt);
 extern ObjectAddress DefineCompositeType(RangeVar *typevar, List *coldeflist);
 extern Oid	AssignTypeArrayOid(void);
+extern Oid	AssignTypeMultirangeOid(void);
+extern Oid	AssignTypeMultirangeArrayOid(void);
 
 extern ObjectAddress AlterDomainDefault(List *names, Node *defaultRaw);
 extern ObjectAddress AlterDomainNotNull(List *names, bool notNull);
diff --git a/src/include/utils/lsyscache.h b/src/include/utils/lsyscache.h
index a216d6793d..b5ff168160 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..8779bd1f6e
--- /dev/null
+++ b/src/include/utils/multirangetypes.h
@@ -0,0 +1,103 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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);
+extern bool multirange_contains_elem_internal(TypeCacheEntry *typcache, MultirangeType * mr,
+											  Datum elem);
+extern bool multirange_contains_range_internal(TypeCacheEntry *typcache, MultirangeType * mr,
+											   RangeType *r);
+extern bool multirange_contains_multirange_internal(TypeCacheEntry *typcache,
+													MultirangeType * mr1,
+													MultirangeType * mr2);
+extern bool range_overlaps_multirange_internal(TypeCacheEntry *typcache, RangeType *r,
+											   MultirangeType * mr);
+extern bool multirange_overlaps_multirange_internal(TypeCacheEntry *typcache,
+													MultirangeType * mr1,
+													MultirangeType * mr2);
+extern bool range_before_multirange_internal(TypeCacheEntry *typcache, RangeType *r,
+											 MultirangeType * mr);
+extern bool range_after_multirange_internal(TypeCacheEntry *typcache, RangeType *r,
+											MultirangeType * mr);
+extern bool multirange_before_multirange_internal(TypeCacheEntry *typcache,
+												  MultirangeType * mr1,
+												  MultirangeType * mr2);
+extern MultirangeType * multirange_minus_internal(Oid mltrngtypoid,
+												  TypeCacheEntry *rangetyp,
+												  int32 range_count1,
+												  RangeType **ranges1,
+												  int32 range_count2,
+												  RangeType **ranges2);
+extern MultirangeType * multirange_intersect_internal(Oid mltrngtypoid,
+													  TypeCacheEntry *rangetyp,
+													  int32 range_count1,
+													  RangeType **ranges1,
+													  int32 range_count2,
+													  RangeType **ranges2);
+
+/* 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 0cbbf09033..204aee4054 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)
 
@@ -92,13 +94,21 @@ typedef struct
  * prototypes for functions defined in rangetypes.c
  */
 
-extern bool range_contains_elem_internal(TypeCacheEntry *typcache, const RangeType *r, Datum val);
-
 /* internal versions of the above */
+extern int	range_cmp_internal(TypeCacheEntry *typcache, const RangeType *r1,
+							   const RangeType *r2);
+extern uint32 hash_range_internal(TypeCacheEntry *typcache, const RangeType *r);
+extern uint64 hash_range_extended_internal(TypeCacheEntry *typcache, const RangeType *r,
+										   Datum seed);
+extern Datum range_lower_internal(TypeCacheEntry *typcache, const RangeType *r1,
+								  bool *isnull);
+extern Datum range_upper_internal(TypeCacheEntry *typcache, const RangeType *r1,
+								  bool *isnull);
 extern bool range_eq_internal(TypeCacheEntry *typcache, const RangeType *r1,
 							  const RangeType *r2);
 extern bool range_ne_internal(TypeCacheEntry *typcache, const RangeType *r1,
 							  const RangeType *r2);
+extern bool range_contains_elem_internal(TypeCacheEntry *typcache, const RangeType *r, Datum val);
 extern bool range_contains_internal(TypeCacheEntry *typcache, const RangeType *r1,
 									const RangeType *r2);
 extern bool range_contained_by_internal(TypeCacheEntry *typcache, const RangeType *r1,
@@ -115,6 +125,12 @@ extern bool range_overleft_internal(TypeCacheEntry *typcache, const RangeType *r
 									const RangeType *r2);
 extern bool range_overright_internal(TypeCacheEntry *typcache, const RangeType *r1,
 									 const RangeType *r2);
+extern RangeType *range_union_internal(TypeCacheEntry *typcache, RangeType *r1,
+									   RangeType *r2, bool strict);
+extern RangeType *range_minus_internal(TypeCacheEntry *typcache, RangeType *r1,
+									   RangeType *r2);
+extern RangeType *range_intersect_internal(TypeCacheEntry *typcache, const RangeType *r1,
+										   const RangeType *r2);
 
 /* assorted support functions */
 extern TypeCacheEntry *range_get_typcache(FunctionCallInfo fcinfo,
@@ -125,6 +141,7 @@ extern void range_deserialize(TypeCacheEntry *typcache, const RangeType *range,
 							  RangeBound *lower, RangeBound *upper,
 							  bool *empty);
 extern char range_get_flags(const RangeType *range);
+extern bool range_has_flag(const RangeType *range, char flag);
 extern void range_set_contain_empty(RangeType *range);
 extern RangeType *make_range(TypeCacheEntry *typcache, RangeBound *lower,
 							 RangeBound *upper, bool empty);
@@ -132,8 +149,12 @@ extern int range_cmp_bounds(TypeCacheEntry *typcache, const RangeBound *b1,
 							const RangeBound *b2);
 extern int range_cmp_bound_values(TypeCacheEntry *typcache, const RangeBound *b1,
 								  const RangeBound *b2);
-extern bool bounds_adjacent(TypeCacheEntry *typcache, RangeBound bound1,
-							RangeBound bound2);
+extern int range_compare(const void *key1, const void *key2, void *arg);
+extern bool bounds_adjacent(TypeCacheEntry *typcache, const RangeBound bound1,
+							const RangeBound bound2);
 extern RangeType *make_empty_range(TypeCacheEntry *typcache);
+extern bool range_split_internal(TypeCacheEntry *typcache, const RangeType *r1,
+								 const RangeType *r2, RangeType **output1,
+								 RangeType **output2);
 
 #endif							/* RANGETYPES_H */
diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h
index f27b73d76d..bc51bbeb54 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 66ff17dbd5..fe240ea004 100644
--- a/src/include/utils/typcache.h
+++ b/src/include/utils/typcache.h
@@ -99,6 +99,11 @@ typedef struct TypeCacheEntry
 	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 66bf9f1211..f61dc7e69a 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? */
@@ -2087,6 +2089,7 @@ build_datatype(HeapTuple typeTup, int32 typmod,
 		case TYPTYPE_BASE:
 		case TYPTYPE_ENUM:
 		case TYPTYPE_RANGE:
+		case TYPTYPE_MULTIRANGE:
 			typ->ttype = PLPGSQL_TTYPE_SCALAR;
 			break;
 		case TYPTYPE_COMPOSITE:
@@ -2494,6 +2497,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/dependency.out b/src/test/regress/expected/dependency.out
index b4a11b8aa9..778699a961 100644
--- a/src/test/regress/expected/dependency.out
+++ b/src/test/regress/expected/dependency.out
@@ -141,6 +141,7 @@ owner of table deptest
 owner of function deptest_func()
 owner of type deptest_enum
 owner of type deptest_range
+owner of type deptest_multirange
 owner of table deptest2
 owner of sequence ss1
 owner of type deptest_t
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..723f2408b0
--- /dev/null
+++ b/src/test/regress/expected/multirangetypes.out
@@ -0,0 +1,2395 @@
+-- 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)
+
+--
+-- test casts, both a built-in range type and a user-defined one:
+--
+select 'empty'::int4range::int4multirange;
+ int4multirange 
+----------------
+ {}
+(1 row)
+
+select int4range(1, 3)::int4multirange;
+ int4range 
+-----------
+ {[1,3)}
+(1 row)
+
+select int4range(1, null)::int4multirange;
+ int4range 
+-----------
+ {[1,)}
+(1 row)
+
+select int4range(null, null)::int4multirange;
+ int4range 
+-----------
+ {(,)}
+(1 row)
+
+select 'empty'::textrange::textmultirange;
+ textmultirange 
+----------------
+ {}
+(1 row)
+
+select textrange('a', 'c')::textmultirange;
+ textrange 
+-----------
+ {[a,c)}
+(1 row)
+
+select textrange('a', null)::textmultirange;
+ textrange 
+-----------
+ {[a,)}
+(1 row)
+
+select textrange(null, null)::textmultirange;
+ textrange 
+-----------
+ {(,)}
+(1 row)
+
+--
+-- create some test data and test the operators
+--
+CREATE TABLE nummultirange_test (nmr NUMMULTIRANGE);
+CREATE INDEX nummultirange_test_btree ON nummultirange_test(nmr);
+INSERT INTO nummultirange_test VALUES('{}');
+INSERT INTO nummultirange_test VALUES('{[,)}');
+INSERT INTO nummultirange_test VALUES('{[3,]}');
+INSERT INTO nummultirange_test VALUES('{[,), [3,]}');
+INSERT INTO nummultirange_test VALUES('{[, 5)}');
+INSERT INTO nummultirange_test VALUES(nummultirange());
+INSERT INTO nummultirange_test VALUES(nummultirange(variadic '{}'::numrange[]));
+INSERT INTO nummultirange_test VALUES(nummultirange(numrange(1.1, 2.2)));
+INSERT INTO nummultirange_test VALUES('{empty}');
+INSERT INTO nummultirange_test VALUES(nummultirange(numrange(1.7, 1.7, '[]'), numrange(1.7, 1.9)));
+INSERT INTO nummultirange_test VALUES(nummultirange(numrange(1.7, 1.7, '[]'), numrange(1.9, 2.1)));
+SELECT nmr, isempty(nmr), lower(nmr), upper(nmr) FROM nummultirange_test ORDER BY nmr;
+          nmr          | isempty | lower | upper 
+-----------------------+---------+-------+-------
+ {}                    | t       |       |      
+ {}                    | t       |       |      
+ {}                    | t       |       |      
+ {}                    | t       |       |      
+ {(,5)}                | f       |       |     5
+ {(,)}                 | f       |       |      
+ {(,)}                 | f       |       |      
+ {[1.1,2.2)}           | f       |   1.1 |   2.2
+ {[1.7,1.7],[1.9,2.1)} | f       |   1.7 |   2.1
+ {[1.7,1.9)}           | f       |   1.7 |   1.9
+ {[3,)}                | f       |     3 |      
+(11 rows)
+
+SELECT nmr, lower_inc(nmr), lower_inf(nmr), upper_inc(nmr), upper_inf(nmr) FROM nummultirange_test ORDER BY nmr;
+          nmr          | lower_inc | lower_inf | upper_inc | upper_inf 
+-----------------------+-----------+-----------+-----------+-----------
+ {}                    | f         | f         | f         | f
+ {}                    | f         | f         | f         | f
+ {}                    | f         | f         | f         | f
+ {}                    | f         | f         | f         | f
+ {(,5)}                | f         | t         | f         | f
+ {(,)}                 | f         | t         | f         | t
+ {(,)}                 | f         | t         | f         | t
+ {[1.1,2.2)}           | t         | f         | f         | f
+ {[1.7,1.7],[1.9,2.1)} | t         | f         | f         | f
+ {[1.7,1.9)}           | t         | f         | f         | f
+ {[3,)}                | t         | f         | f         | t
+(11 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr = '{}';
+ nmr 
+-----
+ {}
+ {}
+ {}
+ {}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr = '{(,5)}';
+  nmr   
+--------
+ {(,5)}
+(1 row)
+
+SELECT * FROM nummultirange_test WHERE nmr = '{[3,)}';
+  nmr   
+--------
+ {[3,)}
+(1 row)
+
+SELECT * FROM nummultirange_test WHERE nmr = '{[1.7,1.7]}';
+ nmr 
+-----
+(0 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr = '{[1.7,1.7],[1.9,2.1)}';
+          nmr          
+-----------------------
+ {[1.7,1.7],[1.9,2.1)}
+(1 row)
+
+SELECT * FROM nummultirange_test WHERE nmr < '{}';
+ nmr 
+-----
+(0 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr < '{[-1000.0, -1000.0]}';
+  nmr   
+--------
+ {}
+ {(,)}
+ {(,)}
+ {(,5)}
+ {}
+ {}
+ {}
+(7 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr < '{[0.0, 1.0]}';
+  nmr   
+--------
+ {}
+ {(,)}
+ {(,)}
+ {(,5)}
+ {}
+ {}
+ {}
+(7 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr < '{[1000.0, 1001.0]}';
+          nmr          
+-----------------------
+ {}
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+ {}
+ {}
+ {[1.1,2.2)}
+ {}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(11 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr <= '{}';
+ nmr 
+-----
+ {}
+ {}
+ {}
+ {}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr <= '{[3,)}';
+          nmr          
+-----------------------
+ {}
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+ {}
+ {}
+ {[1.1,2.2)}
+ {}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(11 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr >= '{}';
+          nmr          
+-----------------------
+ {}
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+ {}
+ {}
+ {[1.1,2.2)}
+ {}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(11 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr >= '{[3,)}';
+  nmr   
+--------
+ {[3,)}
+(1 row)
+
+SELECT * FROM nummultirange_test WHERE nmr > '{}';
+          nmr          
+-----------------------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+ {[1.1,2.2)}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(7 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr > '{[-1000.0, -1000.0]}';
+          nmr          
+-----------------------
+ {[3,)}
+ {[1.1,2.2)}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr > '{[0.0, 1.0]}';
+          nmr          
+-----------------------
+ {[3,)}
+ {[1.1,2.2)}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr > '{[1000.0, 1001.0]}';
+ nmr 
+-----
+(0 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr <> '{}';
+          nmr          
+-----------------------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+ {[1.1,2.2)}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(7 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr <> '{(,5)}';
+          nmr          
+-----------------------
+ {}
+ {(,)}
+ {[3,)}
+ {(,)}
+ {}
+ {}
+ {[1.1,2.2)}
+ {}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(10 rows)
+
+select nummultirange(numrange(2.0, 1.0));
+ERROR:  range lower bound must be less than or equal to range upper bound
+select nummultirange(numrange(5.0, 6.0), numrange(1.0, 2.0));
+     nummultirange     
+-----------------------
+ {[1.0,2.0),[5.0,6.0)}
+(1 row)
+
+-- overlaps
+SELECT * FROM nummultirange_test WHERE range_overlaps_multirange(numrange(4.0, 4.2), nmr);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE numrange(4.0, 4.2) && nmr;
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE multirange_overlaps_range(nmr, numrange(4.0, 4.2));
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr && numrange(4.0, 4.2);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE multirange_overlaps_multirange(nmr, nummultirange(numrange(4.0, 4.2), numrange(6.0, 7.0)));
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr && nummultirange(numrange(4.0, 4.2), numrange(6.0, 7.0));
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr && nummultirange(numrange(6.0, 7.0));
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+(3 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr && nummultirange(numrange(6.0, 7.0), numrange(8.0, 9.0));
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+(3 rows)
+
+-- mr contains x
+SELECT * FROM nummultirange_test WHERE multirange_contains_elem(nmr, 4.0);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr @> 4.0;
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE multirange_contains_range(nmr, numrange(4.0, 4.2));
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr @> numrange(4.0, 4.2);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE multirange_contains_multirange(nmr, '{[4.0,4.2), [6.0, 8.0)}');
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+(3 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr @> '{[4.0,4.2), [6.0, 8.0)}'::nummultirange;
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+(3 rows)
+
+-- x is contained by mr
+SELECT * FROM nummultirange_test WHERE elem_contained_by_multirange(4.0, nmr);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE 4.0 <@ nmr;
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE range_contained_by_multirange(numrange(4.0, 4.2), nmr);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE numrange(4.0, 4.2) <@ nmr;
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE multirange_contained_by_multirange('{[4.0,4.2), [6.0, 8.0)}', nmr);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+(3 rows)
+
+SELECT * FROM nummultirange_test WHERE '{[4.0,4.2), [6.0, 8.0)}'::nummultirange <@ nmr;
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+(3 rows)
+
+-- overlaps
+SELECT 'empty'::numrange && nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT 'empty'::numrange && nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() && 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) && 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() && nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() && nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) && nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) && nummultirange(numrange(1,2), numrange(7,8));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(7,8)) && nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) && nummultirange(numrange(1,2), numrange(3.5,8));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(3.5,8)) && numrange(3,4);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(3.5,8)) && nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+-- contains
+SELECT nummultirange() @> nummultirange();
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange() @> 'empty'::numrange;
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(null,null)) @> numrange(1,2);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(null,null)) @> numrange(null,2);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(null,null)) @> numrange(2,null);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(null,5)) @> numrange(null,3);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(null,5)) @> numrange(null,8);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(5,null)) @> numrange(8,null);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(5,null)) @> numrange(3,null);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,5)) @> numrange(8,9);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,5)) @> numrange(3,9);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,5)) @> numrange(1,4);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,5)) @> numrange(1,5);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(-4,-2), numrange(1,5)) @> numrange(1,5);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,5), numrange(8,9)) @> numrange(1,5);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,5), numrange(8,9)) @> numrange(6,7);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,5), numrange(6,9)) @> numrange(6,7);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[1,5)}'::nummultirange @> '{[1,5)}';
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[-4,-2), [1,5)}'::nummultirange @> '{[1,5)}';
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[1,5), [8,9)}'::nummultirange @> '{[1,5)}';
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[1,5), [8,9)}'::nummultirange @> '{[6,7)}';
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT '{[1,5), [6,9)}'::nummultirange @> '{[6,7)}';
+ ?column? 
+----------
+ t
+(1 row)
+
+-- is contained by
+SELECT nummultirange() <@ nummultirange();
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT 'empty'::numrange <@ nummultirange();
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,2) <@ nummultirange(numrange(null,null));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(null,2) <@ nummultirange(numrange(null,null));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(2,null) <@ nummultirange(numrange(null,null));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(null,3) <@ nummultirange(numrange(null,5));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(null,8) <@ nummultirange(numrange(null,5));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(8,null) <@ nummultirange(numrange(5,null));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(3,null) <@ nummultirange(numrange(5,null));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(8,9) <@ nummultirange(numrange(1,5));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(3,9) <@ nummultirange(numrange(1,5));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(1,4) <@ nummultirange(numrange(1,5));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,5) <@ nummultirange(numrange(1,5));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,5) <@ nummultirange(numrange(-4,-2), numrange(1,5));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,5) <@ nummultirange(numrange(1,5), numrange(8,9));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(6,7) <@ nummultirange(numrange(1,5), numrange(8,9));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(6,7) <@ nummultirange(numrange(1,5), numrange(6,9));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[1,5)}' <@ '{[1,5)}'::nummultirange;
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[1,5)}' <@ '{[-4,-2), [1,5)}'::nummultirange;
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[1,5)}' <@ '{[1,5), [8,9)}'::nummultirange;
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[6,7)}' <@ '{[1,5), [8,9)}'::nummultirange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT '{[6,7)}' <@ '{[1,5), [6,9)}'::nummultirange;
+ ?column? 
+----------
+ t
+(1 row)
+
+-- overleft
+SELECT 'empty'::numrange &< nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT 'empty'::numrange &< nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() &< 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) &< 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() &< nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) &< nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() &< nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(6,7) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(1,2) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,4) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,6) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(3.5,6) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(6,7)) &< numrange(3,4);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) &< numrange(3,4);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,4)) &< numrange(3,4);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,6)) &< numrange(3,4);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3.5,6)) &< numrange(3,4);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(6,7)) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,4)) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,6)) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3.5,6)) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+-- overright
+SELECT nummultirange() &> 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) &> 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT 'empty'::numrange &> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT 'empty'::numrange &> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() &> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() &> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) &> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> numrange(6,7);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> numrange(1,2);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> numrange(1,4);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> numrange(1,6);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> numrange(3.5,6);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(3,4) &> nummultirange(numrange(6,7));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(3,4) &> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(3,4) &> nummultirange(numrange(1,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(3,4) &> nummultirange(numrange(1,6));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(3,4) &> nummultirange(numrange(3.5,6));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(6,7));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(1,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(1,6));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(3.5,6));
+ ?column? 
+----------
+ f
+(1 row)
+
+-- meets
+SELECT 'empty'::numrange -|- nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT 'empty'::numrange -|- nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() -|- 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() -|- nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() -|- nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(1,2) -|- nummultirange(numrange(2,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,2) -|- nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- numrange(2,4);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- numrange(3,4);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- nummultirange(numrange(2,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(5,6)) -|- nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(5,6)) -|- nummultirange(numrange(6,7));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(5,6)) -|- nummultirange(numrange(8,9));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- nummultirange(numrange(2,4), numrange(6,7));
+ ?column? 
+----------
+ t
+(1 row)
+
+-- strictly left
+select 'empty'::numrange << nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(1,2) << nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(1,2) << nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+select numrange(1,2) << nummultirange(numrange(0,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(1,2) << nummultirange(numrange(0,4), numrange(7,8));
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange() << 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange() << numrange(1,2);
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(3,4)) << numrange(3,6);
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(0,2)) << numrange(3,6);
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange(numrange(0,2), numrange(7,8)) << numrange(3,6);
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(-4,-2), numrange(0,2)) << numrange(3,6);
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange() << nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange() << nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(1,2)) << nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(1,2)) << nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(1,2)) << nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange(numrange(1,2)) << nummultirange(numrange(3,4), numrange(7,8));
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange(numrange(1,2), numrange(4,5)) << nummultirange(numrange(3,4), numrange(7,8));
+ ?column? 
+----------
+ f
+(1 row)
+
+-- strictly right
+select nummultirange() >> 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange() >> numrange(1,2);
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(3,4)) >> numrange(1,2);
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange(numrange(0,4)) >> numrange(1,2);
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(0,4), numrange(7,8)) >> numrange(1,2);
+ ?column? 
+----------
+ f
+(1 row)
+
+select 'empty'::numrange >> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(1,2) >> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(3,6) >> nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(3,6) >> nummultirange(numrange(0,2));
+ ?column? 
+----------
+ t
+(1 row)
+
+select numrange(3,6) >> nummultirange(numrange(0,2), numrange(7,8));
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(3,6) >> nummultirange(numrange(-4,-2), numrange(0,2));
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange() >> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(1,2)) >> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange() >> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(1,2)) >> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(3,4)) >> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange(numrange(3,4), numrange(7,8)) >> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange(numrange(3,4), numrange(7,8)) >> nummultirange(numrange(1,2), numrange(4,5));
+ ?column? 
+----------
+ f
+(1 row)
+
+-- union
+SELECT nummultirange() + nummultirange();
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange() + nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) + nummultirange();
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) + nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) + nummultirange(numrange(2,4));
+ ?column? 
+----------
+ {[1,4)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) + nummultirange(numrange(3,4));
+   ?column?    
+---------------
+ {[1,2),[3,4)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) + nummultirange(numrange(2,4));
+ ?column? 
+----------
+ {[1,5)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) + nummultirange(numrange(3,4));
+   ?column?    
+---------------
+ {[1,2),[3,5)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) + nummultirange(numrange(0,9));
+ ?column? 
+----------
+ {[0,9)}
+(1 row)
+
+-- merge
+SELECT range_merge(nummultirange());
+ range_merge 
+-------------
+ empty
+(1 row)
+
+SELECT range_merge(nummultirange(numrange(1,2)));
+ range_merge 
+-------------
+ [1,2)
+(1 row)
+
+SELECT range_merge(nummultirange(numrange(1,2), numrange(7,8)));
+ range_merge 
+-------------
+ [1,8)
+(1 row)
+
+-- minus
+SELECT nummultirange() - nummultirange();
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange() - nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) - nummultirange();
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(3,4)) - nummultirange();
+   ?column?    
+---------------
+ {[1,2),[3,4)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) - nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) - nummultirange(numrange(2,4));
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) - nummultirange(numrange(3,4));
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {[2,4)}
+(1 row)
+
+SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(2,3));
+   ?column?    
+---------------
+ {[1,2),[3,4)}
+(1 row)
+
+SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(0,8));
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(0,2));
+ ?column? 
+----------
+ {[2,4)}
+(1 row)
+
+SELECT nummultirange(numrange(1,8)) - nummultirange(numrange(0,2), numrange(3,4));
+   ?column?    
+---------------
+ {[2,3),[4,8)}
+(1 row)
+
+SELECT nummultirange(numrange(1,8)) - nummultirange(numrange(2,3), numrange(5,null));
+   ?column?    
+---------------
+ {[1,2),[3,5)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(-2,0));
+   ?column?    
+---------------
+ {[1,2),[4,5)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(2,4));
+   ?column?    
+---------------
+ {[1,2),[4,5)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(3,5));
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(0,9));
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange(numrange(1,3), numrange(4,5)) - nummultirange(numrange(2,9));
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(8,9));
+   ?column?    
+---------------
+ {[1,2),[4,5)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(-2,0), numrange(8,9));
+   ?column?    
+---------------
+ {[1,2),[4,5)}
+(1 row)
+
+-- intersection
+SELECT nummultirange() * nummultirange();
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange() * nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) * nummultirange();
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT '{[1,3)}'::nummultirange * '{[1,5)}'::nummultirange;
+ ?column? 
+----------
+ {[1,3)}
+(1 row)
+
+SELECT '{[1,3)}'::nummultirange * '{[0,5)}'::nummultirange;
+ ?column? 
+----------
+ {[1,3)}
+(1 row)
+
+SELECT '{[1,3)}'::nummultirange * '{[0,2)}'::nummultirange;
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT '{[1,3)}'::nummultirange * '{[2,5)}'::nummultirange;
+ ?column? 
+----------
+ {[2,3)}
+(1 row)
+
+SELECT '{[1,4)}'::nummultirange * '{[2,3)}'::nummultirange;
+ ?column? 
+----------
+ {[2,3)}
+(1 row)
+
+SELECT '{[1,4)}'::nummultirange * '{[0,2), [3,5)}'::nummultirange;
+   ?column?    
+---------------
+ {[1,2),[3,4)}
+(1 row)
+
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[0,8), [9,12)}'::nummultirange;
+       ?column?       
+----------------------
+ {[1,4),[7,8),[9,10)}
+(1 row)
+
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[9,12)}'::nummultirange;
+ ?column? 
+----------
+ {[9,10)}
+(1 row)
+
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[-5,-4), [5,6), [9,12)}'::nummultirange;
+ ?column? 
+----------
+ {[9,10)}
+(1 row)
+
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[0,2), [3,8), [9,12)}'::nummultirange;
+          ?column?          
+----------------------------
+ {[1,2),[3,4),[7,8),[9,10)}
+(1 row)
+
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[0,2), [3,8), [9,12)}'::nummultirange;
+          ?column?          
+----------------------------
+ {[1,2),[3,4),[7,8),[9,10)}
+(1 row)
+
+--
+-- range_agg function
+--
+create table reservations ( room_id integer not null, booked_during daterange );
+insert into reservations values
+-- 1: has a meets and a gap
+(1, daterange('2018-07-01', '2018-07-07')),
+(1, daterange('2018-07-07', '2018-07-14')),
+(1, daterange('2018-07-20', '2018-07-22')),
+-- 2: just a single row
+(2, daterange('2018-07-01', '2018-07-03')),
+-- 3: one null range
+(3, NULL),
+-- 4: two null ranges
+(4, NULL),
+(4, NULL),
+-- 5: a null range and a non-null range
+(5, NULL),
+(5, daterange('2018-07-01', '2018-07-03')),
+-- 6: has overlap
+(6, daterange('2018-07-01', '2018-07-07')),
+(6, daterange('2018-07-05', '2018-07-10')),
+-- 7: two ranges that meet: no gap or overlap
+(7, daterange('2018-07-01', '2018-07-07')),
+(7, daterange('2018-07-07', '2018-07-14')),
+-- 8: an empty range
+(8, 'empty'::daterange)
+;
+SELECT   room_id, range_agg(booked_during)
+FROM     reservations
+GROUP BY room_id
+ORDER BY room_id;
+ room_id |                     range_agg                     
+---------+---------------------------------------------------
+       1 | {[07-01-2018,07-14-2018),[07-20-2018,07-22-2018)}
+       2 | {[07-01-2018,07-03-2018)}
+       3 | 
+       4 | 
+       5 | {[07-01-2018,07-03-2018)}
+       6 | {[07-01-2018,07-10-2018)}
+       7 | {[07-01-2018,07-14-2018)}
+       8 | {}
+(8 rows)
+
+-- range_agg on a custom range type too
+SELECT  range_agg(r)
+FROM    (VALUES
+          ('[a,c]'::textrange),
+          ('[b,b]'::textrange),
+          ('[c,f]'::textrange),
+          ('[g,h)'::textrange),
+          ('[h,j)'::textrange)
+        ) t(r);
+   range_agg   
+---------------
+ {[a,f],[g,j)}
+(1 row)
+
+select range_intersect_agg(nmr) from nummultirange_test;
+ range_intersect_agg 
+---------------------
+ {}
+(1 row)
+
+select range_intersect_agg(nmr) from nummultirange_test where false;
+ range_intersect_agg 
+---------------------
+ 
+(1 row)
+
+-- test with just one input:
+select range_intersect_agg(nmr) from (values ('{[1,2]}'::nummultirange)) t(nmr);
+ range_intersect_agg 
+---------------------
+ {[1,2]}
+(1 row)
+
+select range_intersect_agg(nmr) from nummultirange_test where nmr @> 4.0;
+ range_intersect_agg 
+---------------------
+ {[3,5)}
+(1 row)
+
+create table nummultirange_test2(nmr nummultirange);
+create index nummultirange_test2_hash_idx on nummultirange_test2 using hash (nmr);
+INSERT INTO nummultirange_test2 VALUES('{[, 5)}');
+INSERT INTO nummultirange_test2 VALUES(nummultirange(numrange(1.1, 2.2)));
+INSERT INTO nummultirange_test2 VALUES(nummultirange(numrange(1.1, 2.2)));
+INSERT INTO nummultirange_test2 VALUES(nummultirange(numrange(1.1, 2.2,'()')));
+INSERT INTO nummultirange_test2 VALUES('{}');
+select * from nummultirange_test2 where nmr = '{}';
+ nmr 
+-----
+ {}
+(1 row)
+
+select * from nummultirange_test2 where nmr = nummultirange(numrange(1.1, 2.2));
+     nmr     
+-------------
+ {[1.1,2.2)}
+ {[1.1,2.2)}
+(2 rows)
+
+select * from nummultirange_test2 where nmr = nummultirange(numrange(1.1, 2.3));
+ nmr 
+-----
+(0 rows)
+
+set enable_nestloop=t;
+set enable_hashjoin=f;
+set enable_mergejoin=f;
+select * from nummultirange_test natural join nummultirange_test2 order by nmr;
+     nmr     
+-------------
+ {}
+ {}
+ {}
+ {}
+ {(,5)}
+ {[1.1,2.2)}
+ {[1.1,2.2)}
+(7 rows)
+
+set enable_nestloop=f;
+set enable_hashjoin=t;
+set enable_mergejoin=f;
+select * from nummultirange_test natural join nummultirange_test2 order by nmr;
+     nmr     
+-------------
+ {}
+ {}
+ {}
+ {}
+ {(,5)}
+ {[1.1,2.2)}
+ {[1.1,2.2)}
+(7 rows)
+
+set enable_nestloop=f;
+set enable_hashjoin=f;
+set enable_mergejoin=t;
+select * from nummultirange_test natural join nummultirange_test2 order by nmr;
+     nmr     
+-------------
+ {}
+ {}
+ {}
+ {}
+ {(,5)}
+ {[1.1,2.2)}
+ {[1.1,2.2)}
+(7 rows)
+
+set enable_nestloop to default;
+set enable_hashjoin to default;
+set enable_mergejoin to default;
+DROP TABLE nummultirange_test2;
+--
+-- Test user-defined multirange of floats
+--
+select '{[123.001, 5.e9)}'::float8multirange @> 888.882::float8;
+ ?column? 
+----------
+ t
+(1 row)
+
+create table float8multirange_test(f8mr float8multirange, i int);
+insert into float8multirange_test values(float8multirange(float8range(-100.00007, '1.111113e9')), 42);
+select * from float8multirange_test;
+           f8mr            | i  
+---------------------------+----
+ {[-100.00007,1111113000)} | 42
+(1 row)
+
+drop table float8multirange_test;
+--
+-- Test multirange types over domains
+--
+create domain mydomain as int4;
+create type mydomainrange as range(subtype=mydomain);
+select '{[4,50)}'::mydomainmultirange @> 7::mydomain;
+ ?column? 
+----------
+ t
+(1 row)
+
+drop domain mydomain cascade;
+NOTICE:  drop cascades to type mydomainrange
+--
+-- Test domains over multirange types
+--
+create domain restrictedmultirange as int4multirange check (upper(value) < 10);
+select '{[4,5)}'::restrictedmultirange @> 7;
+ ?column? 
+----------
+ f
+(1 row)
+
+select '{[4,50)}'::restrictedmultirange @> 7; -- should fail
+ERROR:  value for domain restrictedmultirange violates check constraint "restrictedmultirange_check"
+drop domain restrictedmultirange;
+--
+-- Test multiple multirange types over the same subtype
+--
+create type textrange1 as range(subtype=text, collation="C");
+create type textrange2 as range(subtype=text, collation="C");
+select textmultirange1(textrange2('a','Z'));  -- should fail
+ERROR:  function textmultirange1(textrange2) does not exist
+LINE 1: select textmultirange1(textrange2('a','Z'));
+               ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+select textmultirange1(textrange1('a','Z')) @> 'b'::text;
+ERROR:  range lower bound must be less than or equal to range upper bound
+select textmultirange2(textrange2('a','z')) @> 'b'::text;
+ ?column? 
+----------
+ t
+(1 row)
+
+drop type textrange1;
+drop type textrange2;
+--
+-- Test polymorphic type system
+--
+create function anyarray_anymultirange_func(a anyarray, r anymultirange)
+  returns anyelement as 'select $1[1] + lower($2);' language sql;
+select anyarray_anymultirange_func(ARRAY[1,2], int4multirange(int4range(10,20)));
+ anyarray_anymultirange_func 
+-----------------------------
+                          11
+(1 row)
+
+-- should fail
+select anyarray_anymultirange_func(ARRAY[1,2], nummultirange(numrange(10,20)));
+ERROR:  function anyarray_anymultirange_func(integer[], nummultirange) does not exist
+LINE 1: select anyarray_anymultirange_func(ARRAY[1,2], nummultirange...
+               ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+drop function anyarray_anymultirange_func(anyarray, anymultirange);
+-- should fail
+create function bogus_func(anyelement)
+  returns anymultirange as 'select int4multirange(int4range(1,10))' language sql;
+ERROR:  cannot determine result data type
+DETAIL:  A function returning "anyrange" or "anymultirange" must have at least one "anyrange" or "anymultirange" argument.
+-- should fail
+create function bogus_func(int)
+  returns anymultirange as 'select int4multirange(int4range(1,10))' language sql;
+ERROR:  cannot determine result data type
+DETAIL:  A function returning a polymorphic type must have at least one polymorphic argument.
+create function range_add_bounds(anymultirange)
+  returns anyelement as 'select lower($1) + upper($1)' language sql;
+select range_add_bounds(int4multirange(int4range(1, 17)));
+ range_add_bounds 
+------------------
+               18
+(1 row)
+
+select range_add_bounds(nummultirange(numrange(1.0001, 123.123)));
+ range_add_bounds 
+------------------
+         124.1231
+(1 row)
+
+create function multirangetypes_sql(q anymultirange, b anyarray, out c anyelement)
+  as $$ select upper($1) + $2[1] $$
+  language sql;
+select multirangetypes_sql(int4multirange(int4range(1,10)), ARRAY[2,20]);
+ multirangetypes_sql 
+---------------------
+                  12
+(1 row)
+
+select multirangetypes_sql(nummultirange(numrange(1,10)), ARRAY[2,20]);  -- match failure
+ERROR:  function multirangetypes_sql(nummultirange, integer[]) does not exist
+LINE 1: select multirangetypes_sql(nummultirange(numrange(1,10)), AR...
+               ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+--
+-- Arrays of multiranges
+--
+select ARRAY[nummultirange(numrange(1.1, 1.2)), nummultirange(numrange(12.3, 155.5))];
+              array               
+----------------------------------
+ {"{[1.1,1.2)}","{[12.3,155.5)}"}
+(1 row)
+
+create table i8mr_array (f1 int, f2 int8multirange[]);
+insert into i8mr_array values (42, array[int8multirange(int8range(1,10)), int8multirange(int8range(2,20))]);
+select * from i8mr_array;
+ f1 |           f2            
+----+-------------------------
+ 42 | {"{[1,10)}","{[2,20)}"}
+(1 row)
+
+drop table i8mr_array;
+--
+-- Multiranges of arrays
+--
+select arraymultirange(arrayrange(ARRAY[1,2], ARRAY[2,1]));
+   arraymultirange   
+---------------------
+ {["{1,2}","{2,1}")}
+(1 row)
+
+select arraymultirange(arrayrange(ARRAY[2,1], ARRAY[1,2]));  -- fail
+ERROR:  range lower bound must be less than or equal to range upper bound
+select array[1,1] <@ arraymultirange(arrayrange(array[1,2], array[2,1]));
+ ?column? 
+----------
+ f
+(1 row)
+
+select array[1,3] <@ arraymultirange(arrayrange(array[1,2], array[2,1]));
+ ?column? 
+----------
+ t
+(1 row)
+
+--
+-- Ranges of composites
+--
+create type two_ints as (a int, b int);
+create type two_ints_range as range (subtype = two_ints);
+-- with force_parallel_mode on, this exercises tqueue.c's range remapping
+select *, row_to_json(upper(t)) as u from
+  (values (two_ints_multirange(two_ints_range(row(1,2), row(3,4)))),
+          (two_ints_multirange(two_ints_range(row(5,6), row(7,8))))) v(t);
+          t          |       u       
+---------------------+---------------
+ {["(1,2)","(3,4)")} | {"a":3,"b":4}
+ {["(5,6)","(7,8)")} | {"a":7,"b":8}
+(2 rows)
+
+drop type two_ints cascade;
+NOTICE:  drop cascades to type two_ints_range
+--
+-- Check behavior when subtype lacks a hash function
+--
+set enable_sort = off;  -- try to make it pick a hash setop implementation
+select '{(2,5)}'::cashmultirange except select '{(5,6)}'::cashmultirange;
+ cashmultirange  
+-----------------
+ {($2.00,$5.00)}
+(1 row)
+
+reset enable_sort;
+--
+-- OUT/INOUT/TABLE functions
+--
+-- infer anymultirange from anymultirange
+create function mr_outparam_succeed(i anymultirange, out r anymultirange, out t text)
+  as $$ select $1, 'foo'::text $$ language sql;
+select * from mr_outparam_succeed(int4multirange(int4range(1,2)));
+    r    |  t  
+---------+-----
+ {[1,2)} | foo
+(1 row)
+
+-- infer anyarray from anymultirange
+create function mr_outparam_succeed2(i anymultirange, out r anyarray, out t text)
+  as $$ select ARRAY[upper($1)], 'foo'::text $$ language sql;
+select * from mr_outparam_succeed2(int4multirange(int4range(1,2)));
+  r  |  t  
+-----+-----
+ {2} | foo
+(1 row)
+
+-- infer anyrange from anymultirange
+create function mr_outparam_succeed3(i anymultirange, out r anyrange, out t text)
+  as $$ select range_merge($1), 'foo'::text $$ language sql;
+select * from mr_outparam_succeed3(int4multirange(int4range(1,2)));
+   r   |  t  
+-------+-----
+ [1,2) | foo
+(1 row)
+
+-- infer anymultirange from anyrange
+create function mr_outparam_succeed4(i anyrange, out r anymultirange, out t text)
+  as $$ select multirange($1), 'foo'::text $$ language sql;
+select * from mr_outparam_succeed4(int4range(1,2));
+    r    |  t  
+---------+-----
+ {[1,2)} | foo
+(1 row)
+
+-- infer anyelement from anymultirange
+create function mr_inoutparam_succeed(out i anyelement, inout r anymultirange)
+  as $$ select upper($1), $1 $$ language sql;
+select * from mr_inoutparam_succeed(int4multirange(int4range(1,2)));
+ i |    r    
+---+---------
+ 2 | {[1,2)}
+(1 row)
+
+-- infer anyelement+anymultirange from anyelement+anymultirange
+create function mr_table_succeed(i anyelement, r anymultirange) returns table(i anyelement, r anymultirange)
+  as $$ select $1, $2 $$ language sql;
+select * from mr_table_succeed(123, int4multirange(int4range(1,11)));
+  i  |    r     
+-----+----------
+ 123 | {[1,11)}
+(1 row)
+
+-- use anymultirange in plpgsql
+create function mr_polymorphic(i anyrange) returns anymultirange
+  as $$ begin return multirange($1); end; $$ language plpgsql;
+select mr_polymorphic(int4range(1, 4));
+ mr_polymorphic 
+----------------
+ {[1,4)}
+(1 row)
+
+-- should fail
+create function mr_outparam_fail(i anyelement, out r anymultirange, out t text)
+  as $$ select '[1,10]', 'foo' $$ language sql;
+ERROR:  cannot determine result data type
+DETAIL:  A function returning "anyrange" or "anymultirange" must have at least one "anyrange" or "anymultirange" argument.
+--should fail
+create function mr_inoutparam_fail(inout i anyelement, out r anymultirange)
+  as $$ select $1, '[1,10]' $$ language sql;
+ERROR:  cannot determine result data type
+DETAIL:  A function returning "anyrange" or "anymultirange" must have at least one "anyrange" or "anymultirange" argument.
+--should fail
+create function mr_table_fail(i anyelement) returns table(i anyelement, r anymultirange)
+  as $$ select $1, '[1,10]' $$ language sql;
+ERROR:  cannot determine result data type
+DETAIL:  A function returning "anyrange" or "anymultirange" must have at least one "anyrange" or "anymultirange" argument.
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/rangetypes.out b/src/test/regress/expected/rangetypes.out
index 5ed6ae47ec..4a46a286a1 100644
--- a/src/test/regress/expected/rangetypes.out
+++ b/src/test/regress/expected/rangetypes.out
@@ -518,6 +518,24 @@ select numrange(1.0, 2.0) * numrange(2.5, 3.0);
  empty
 (1 row)
 
+select range_intersect_agg(nr) from numrange_test;
+ range_intersect_agg 
+---------------------
+ empty
+(1 row)
+
+select range_intersect_agg(nr) from numrange_test where false;
+ range_intersect_agg 
+---------------------
+ 
+(1 row)
+
+select range_intersect_agg(nr) from numrange_test where nr @> 4.0;
+ range_intersect_agg 
+---------------------
+ [3,5)
+(1 row)
+
 create table numrange_test2(nr numrange);
 create index numrange_test2_hash_idx on numrange_test2 using hash (nr);
 INSERT INTO numrange_test2 VALUES('[, 5)');
@@ -1284,7 +1302,7 @@ drop function anyarray_anyrange_func(anyarray, anyrange);
 create function bogus_func(anyelement)
   returns anyrange as 'select int4range(1,10)' language sql;
 ERROR:  cannot determine result data type
-DETAIL:  A function returning "anyrange" must have at least one "anyrange" argument.
+DETAIL:  A function returning "anyrange" or "anymultirange" must have at least one "anyrange" or "anymultirange" argument.
 -- should fail
 create function bogus_func(int)
   returns anyrange as 'select int4range(1,10)' language sql;
@@ -1395,6 +1413,7 @@ reset enable_sort;
 --
 -- OUT/INOUT/TABLE functions
 --
+-- infer anyrange from anyrange
 create function outparam_succeed(i anyrange, out r anyrange, out t text)
   as $$ select $1, 'foo'::text $$ language sql;
 select * from outparam_succeed(int4range(1,2));
@@ -1403,6 +1422,16 @@ select * from outparam_succeed(int4range(1,2));
  [1,2) | foo
 (1 row)
 
+-- infer anyarray from anyrange
+create function outparam_succeed2(i anyrange, out r anyarray, out t text)
+  as $$ select ARRAY[upper($1)], 'foo'::text $$ language sql;
+select * from outparam_succeed2(int4range(int4range(1,2)));
+  r  |  t  
+-----+-----
+ {2} | foo
+(1 row)
+
+-- infer anyelement from anyrange
 create function inoutparam_succeed(out i anyelement, inout r anyrange)
   as $$ select upper($1), $1 $$ language sql;
 select * from inoutparam_succeed(int4range(1,2));
@@ -1411,6 +1440,7 @@ select * from inoutparam_succeed(int4range(1,2));
  2 | [1,2)
 (1 row)
 
+-- infer anyelement+anyrange from anyelement+anyrange
 create function table_succeed(i anyelement, r anyrange) returns table(i anyelement, r anyrange)
   as $$ select $1, $2 $$ language sql;
 select * from table_succeed(123, int4range(1,11));
@@ -1423,14 +1453,14 @@ select * from table_succeed(123, int4range(1,11));
 create function outparam_fail(i anyelement, out r anyrange, out t text)
   as $$ select '[1,10]', 'foo' $$ language sql;
 ERROR:  cannot determine result data type
-DETAIL:  A function returning "anyrange" must have at least one "anyrange" argument.
+DETAIL:  A function returning "anyrange" or "anymultirange" must have at least one "anyrange" or "anymultirange" argument.
 --should fail
 create function inoutparam_fail(inout i anyelement, out r anyrange)
   as $$ select $1, '[1,10]' $$ language sql;
 ERROR:  cannot determine result data type
-DETAIL:  A function returning "anyrange" must have at least one "anyrange" argument.
+DETAIL:  A function returning "anyrange" or "anymultirange" must have at least one "anyrange" or "anymultirange" argument.
 --should fail
 create function table_fail(i anyelement) returns table(i anyelement, r anyrange)
   as $$ select $1, '[1,10]' $$ language sql;
 ERROR:  cannot determine result data type
-DETAIL:  A function returning "anyrange" must have at least one "anyrange" argument.
+DETAIL:  A function returning "anyrange" or "anymultirange" must have at least one "anyrange" or "anymultirange" argument.
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index 070de78e85..6f7fcf6326 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -92,6 +92,7 @@ num_exp_sqrt|t
 num_exp_sub|t
 num_input_test|f
 num_result|f
+nummultirange_test|t
 onek|t
 onek2|t
 path_tbl|f
@@ -172,6 +173,7 @@ quad_poly_tbl|t
 radix_text_tbl|t
 ramp|f
 real_city|f
+reservations|f
 road|t
 shighway|t
 slow_emp4000|f
diff --git a/src/test/regress/expected/type_sanity.out b/src/test/regress/expected/type_sanity.out
index cd7fc03b04..60a9b4d22f 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.rngmultitypid
+FROM pg_range p1
+WHERE p1.rngmultitypid IS NULL OR p1.rngmultitypid = 0;
+ rngtypid | rngsubtype | rngmultitypid 
+----------+------------+---------------
+(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..0f70919e1a
--- /dev/null
+++ b/src/test/regress/sql/multirangetypes.sql
@@ -0,0 +1,630 @@
+-- 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'));
+
+--
+-- test casts, both a built-in range type and a user-defined one:
+--
+select 'empty'::int4range::int4multirange;
+select int4range(1, 3)::int4multirange;
+select int4range(1, null)::int4multirange;
+select int4range(null, null)::int4multirange;
+select 'empty'::textrange::textmultirange;
+select textrange('a', 'c')::textmultirange;
+select textrange('a', null)::textmultirange;
+select textrange(null, null)::textmultirange;
+
+--
+-- create some test data and test the operators
+--
+
+CREATE TABLE nummultirange_test (nmr NUMMULTIRANGE);
+CREATE INDEX nummultirange_test_btree ON nummultirange_test(nmr);
+
+INSERT INTO nummultirange_test VALUES('{}');
+INSERT INTO nummultirange_test VALUES('{[,)}');
+INSERT INTO nummultirange_test VALUES('{[3,]}');
+INSERT INTO nummultirange_test VALUES('{[,), [3,]}');
+INSERT INTO nummultirange_test VALUES('{[, 5)}');
+INSERT INTO nummultirange_test VALUES(nummultirange());
+INSERT INTO nummultirange_test VALUES(nummultirange(variadic '{}'::numrange[]));
+INSERT INTO nummultirange_test VALUES(nummultirange(numrange(1.1, 2.2)));
+INSERT INTO nummultirange_test VALUES('{empty}');
+INSERT INTO nummultirange_test VALUES(nummultirange(numrange(1.7, 1.7, '[]'), numrange(1.7, 1.9)));
+INSERT INTO nummultirange_test VALUES(nummultirange(numrange(1.7, 1.7, '[]'), numrange(1.9, 2.1)));
+
+SELECT nmr, isempty(nmr), lower(nmr), upper(nmr) FROM nummultirange_test ORDER BY nmr;
+SELECT nmr, lower_inc(nmr), lower_inf(nmr), upper_inc(nmr), upper_inf(nmr) FROM nummultirange_test ORDER BY nmr;
+
+SELECT * FROM nummultirange_test WHERE nmr = '{}';
+SELECT * FROM nummultirange_test WHERE nmr = '{(,5)}';
+SELECT * FROM nummultirange_test WHERE nmr = '{[3,)}';
+SELECT * FROM nummultirange_test WHERE nmr = '{[1.7,1.7]}';
+SELECT * FROM nummultirange_test WHERE nmr = '{[1.7,1.7],[1.9,2.1)}';
+SELECT * FROM nummultirange_test WHERE nmr < '{}';
+SELECT * FROM nummultirange_test WHERE nmr < '{[-1000.0, -1000.0]}';
+SELECT * FROM nummultirange_test WHERE nmr < '{[0.0, 1.0]}';
+SELECT * FROM nummultirange_test WHERE nmr < '{[1000.0, 1001.0]}';
+SELECT * FROM nummultirange_test WHERE nmr <= '{}';
+SELECT * FROM nummultirange_test WHERE nmr <= '{[3,)}';
+SELECT * FROM nummultirange_test WHERE nmr >= '{}';
+SELECT * FROM nummultirange_test WHERE nmr >= '{[3,)}';
+SELECT * FROM nummultirange_test WHERE nmr > '{}';
+SELECT * FROM nummultirange_test WHERE nmr > '{[-1000.0, -1000.0]}';
+SELECT * FROM nummultirange_test WHERE nmr > '{[0.0, 1.0]}';
+SELECT * FROM nummultirange_test WHERE nmr > '{[1000.0, 1001.0]}';
+SELECT * FROM nummultirange_test WHERE nmr <> '{}';
+SELECT * FROM nummultirange_test WHERE nmr <> '{(,5)}';
+
+select nummultirange(numrange(2.0, 1.0));
+select nummultirange(numrange(5.0, 6.0), numrange(1.0, 2.0));
+
+-- overlaps
+SELECT * FROM nummultirange_test WHERE range_overlaps_multirange(numrange(4.0, 4.2), nmr);
+SELECT * FROM nummultirange_test WHERE numrange(4.0, 4.2) && nmr;
+SELECT * FROM nummultirange_test WHERE multirange_overlaps_range(nmr, numrange(4.0, 4.2));
+SELECT * FROM nummultirange_test WHERE nmr && numrange(4.0, 4.2);
+SELECT * FROM nummultirange_test WHERE multirange_overlaps_multirange(nmr, nummultirange(numrange(4.0, 4.2), numrange(6.0, 7.0)));
+SELECT * FROM nummultirange_test WHERE nmr && nummultirange(numrange(4.0, 4.2), numrange(6.0, 7.0));
+SELECT * FROM nummultirange_test WHERE nmr && nummultirange(numrange(6.0, 7.0));
+SELECT * FROM nummultirange_test WHERE nmr && nummultirange(numrange(6.0, 7.0), numrange(8.0, 9.0));
+
+-- mr contains x
+SELECT * FROM nummultirange_test WHERE multirange_contains_elem(nmr, 4.0);
+SELECT * FROM nummultirange_test WHERE nmr @> 4.0;
+SELECT * FROM nummultirange_test WHERE multirange_contains_range(nmr, numrange(4.0, 4.2));
+SELECT * FROM nummultirange_test WHERE nmr @> numrange(4.0, 4.2);
+SELECT * FROM nummultirange_test WHERE multirange_contains_multirange(nmr, '{[4.0,4.2), [6.0, 8.0)}');
+SELECT * FROM nummultirange_test WHERE nmr @> '{[4.0,4.2), [6.0, 8.0)}'::nummultirange;
+
+-- x is contained by mr
+SELECT * FROM nummultirange_test WHERE elem_contained_by_multirange(4.0, nmr);
+SELECT * FROM nummultirange_test WHERE 4.0 <@ nmr;
+SELECT * FROM nummultirange_test WHERE range_contained_by_multirange(numrange(4.0, 4.2), nmr);
+SELECT * FROM nummultirange_test WHERE numrange(4.0, 4.2) <@ nmr;
+SELECT * FROM nummultirange_test WHERE multirange_contained_by_multirange('{[4.0,4.2), [6.0, 8.0)}', nmr);
+SELECT * FROM nummultirange_test WHERE '{[4.0,4.2), [6.0, 8.0)}'::nummultirange <@ nmr;
+
+-- overlaps
+SELECT 'empty'::numrange && nummultirange();
+SELECT 'empty'::numrange && nummultirange(numrange(1,2));
+SELECT nummultirange() && 'empty'::numrange;
+SELECT nummultirange(numrange(1,2)) && 'empty'::numrange;
+SELECT nummultirange() && nummultirange();
+SELECT nummultirange() && nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) && nummultirange();
+SELECT nummultirange(numrange(3,4)) && nummultirange(numrange(1,2), numrange(7,8));
+SELECT nummultirange(numrange(1,2), numrange(7,8)) && nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(3,4)) && nummultirange(numrange(1,2), numrange(3.5,8));
+SELECT nummultirange(numrange(1,2), numrange(3.5,8)) && numrange(3,4);
+SELECT nummultirange(numrange(1,2), numrange(3.5,8)) && nummultirange(numrange(3,4));
+
+-- contains
+SELECT nummultirange() @> nummultirange();
+SELECT nummultirange() @> 'empty'::numrange;
+SELECT nummultirange(numrange(null,null)) @> numrange(1,2);
+SELECT nummultirange(numrange(null,null)) @> numrange(null,2);
+SELECT nummultirange(numrange(null,null)) @> numrange(2,null);
+SELECT nummultirange(numrange(null,5)) @> numrange(null,3);
+SELECT nummultirange(numrange(null,5)) @> numrange(null,8);
+SELECT nummultirange(numrange(5,null)) @> numrange(8,null);
+SELECT nummultirange(numrange(5,null)) @> numrange(3,null);
+SELECT nummultirange(numrange(1,5)) @> numrange(8,9);
+SELECT nummultirange(numrange(1,5)) @> numrange(3,9);
+SELECT nummultirange(numrange(1,5)) @> numrange(1,4);
+SELECT nummultirange(numrange(1,5)) @> numrange(1,5);
+SELECT nummultirange(numrange(-4,-2), numrange(1,5)) @> numrange(1,5);
+SELECT nummultirange(numrange(1,5), numrange(8,9)) @> numrange(1,5);
+SELECT nummultirange(numrange(1,5), numrange(8,9)) @> numrange(6,7);
+SELECT nummultirange(numrange(1,5), numrange(6,9)) @> numrange(6,7);
+SELECT '{[1,5)}'::nummultirange @> '{[1,5)}';
+SELECT '{[-4,-2), [1,5)}'::nummultirange @> '{[1,5)}';
+SELECT '{[1,5), [8,9)}'::nummultirange @> '{[1,5)}';
+SELECT '{[1,5), [8,9)}'::nummultirange @> '{[6,7)}';
+SELECT '{[1,5), [6,9)}'::nummultirange @> '{[6,7)}';
+
+-- is contained by
+SELECT nummultirange() <@ nummultirange();
+SELECT 'empty'::numrange <@ nummultirange();
+SELECT numrange(1,2) <@ nummultirange(numrange(null,null));
+SELECT numrange(null,2) <@ nummultirange(numrange(null,null));
+SELECT numrange(2,null) <@ nummultirange(numrange(null,null));
+SELECT numrange(null,3) <@ nummultirange(numrange(null,5));
+SELECT numrange(null,8) <@ nummultirange(numrange(null,5));
+SELECT numrange(8,null) <@ nummultirange(numrange(5,null));
+SELECT numrange(3,null) <@ nummultirange(numrange(5,null));
+SELECT numrange(8,9) <@ nummultirange(numrange(1,5));
+SELECT numrange(3,9) <@ nummultirange(numrange(1,5));
+SELECT numrange(1,4) <@ nummultirange(numrange(1,5));
+SELECT numrange(1,5) <@ nummultirange(numrange(1,5));
+SELECT numrange(1,5) <@ nummultirange(numrange(-4,-2), numrange(1,5));
+SELECT numrange(1,5) <@ nummultirange(numrange(1,5), numrange(8,9));
+SELECT numrange(6,7) <@ nummultirange(numrange(1,5), numrange(8,9));
+SELECT numrange(6,7) <@ nummultirange(numrange(1,5), numrange(6,9));
+SELECT '{[1,5)}' <@ '{[1,5)}'::nummultirange;
+SELECT '{[1,5)}' <@ '{[-4,-2), [1,5)}'::nummultirange;
+SELECT '{[1,5)}' <@ '{[1,5), [8,9)}'::nummultirange;
+SELECT '{[6,7)}' <@ '{[1,5), [8,9)}'::nummultirange;
+SELECT '{[6,7)}' <@ '{[1,5), [6,9)}'::nummultirange;
+
+-- overleft
+SELECT 'empty'::numrange &< nummultirange();
+SELECT 'empty'::numrange &< nummultirange(numrange(1,2));
+SELECT nummultirange() &< 'empty'::numrange;
+SELECT nummultirange(numrange(1,2)) &< 'empty'::numrange;
+SELECT nummultirange() &< nummultirange();
+SELECT nummultirange(numrange(1,2)) &< nummultirange();
+SELECT nummultirange() &< nummultirange(numrange(1,2));
+SELECT numrange(6,7) &< nummultirange(numrange(3,4));
+SELECT numrange(1,2) &< nummultirange(numrange(3,4));
+SELECT numrange(1,4) &< nummultirange(numrange(3,4));
+SELECT numrange(1,6) &< nummultirange(numrange(3,4));
+SELECT numrange(3.5,6) &< nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(6,7)) &< numrange(3,4);
+SELECT nummultirange(numrange(1,2)) &< numrange(3,4);
+SELECT nummultirange(numrange(1,4)) &< numrange(3,4);
+SELECT nummultirange(numrange(1,6)) &< numrange(3,4);
+SELECT nummultirange(numrange(3.5,6)) &< numrange(3,4);
+SELECT nummultirange(numrange(6,7)) &< nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,2)) &< nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,4)) &< nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,6)) &< nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(3.5,6)) &< nummultirange(numrange(3,4));
+
+-- overright
+SELECT nummultirange() &> 'empty'::numrange;
+SELECT nummultirange(numrange(1,2)) &> 'empty'::numrange;
+SELECT 'empty'::numrange &> nummultirange();
+SELECT 'empty'::numrange &> nummultirange(numrange(1,2));
+SELECT nummultirange() &> nummultirange();
+SELECT nummultirange() &> nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) &> nummultirange();
+SELECT nummultirange(numrange(3,4)) &> numrange(6,7);
+SELECT nummultirange(numrange(3,4)) &> numrange(1,2);
+SELECT nummultirange(numrange(3,4)) &> numrange(1,4);
+SELECT nummultirange(numrange(3,4)) &> numrange(1,6);
+SELECT nummultirange(numrange(3,4)) &> numrange(3.5,6);
+SELECT numrange(3,4) &> nummultirange(numrange(6,7));
+SELECT numrange(3,4) &> nummultirange(numrange(1,2));
+SELECT numrange(3,4) &> nummultirange(numrange(1,4));
+SELECT numrange(3,4) &> nummultirange(numrange(1,6));
+SELECT numrange(3,4) &> nummultirange(numrange(3.5,6));
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(6,7));
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(1,4));
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(1,6));
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(3.5,6));
+
+-- meets
+SELECT 'empty'::numrange -|- nummultirange();
+SELECT 'empty'::numrange -|- nummultirange(numrange(1,2));
+SELECT nummultirange() -|- 'empty'::numrange;
+SELECT nummultirange(numrange(1,2)) -|- 'empty'::numrange;
+SELECT nummultirange() -|- nummultirange();
+SELECT nummultirange(numrange(1,2)) -|- nummultirange();
+SELECT nummultirange() -|- nummultirange(numrange(1,2));
+SELECT numrange(1,2) -|- nummultirange(numrange(2,4));
+SELECT numrange(1,2) -|- nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,2)) -|- numrange(2,4);
+SELECT nummultirange(numrange(1,2)) -|- numrange(3,4);
+SELECT nummultirange(numrange(1,2)) -|- nummultirange(numrange(2,4));
+SELECT nummultirange(numrange(1,2)) -|- nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,2), numrange(5,6)) -|- nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,2), numrange(5,6)) -|- nummultirange(numrange(6,7));
+SELECT nummultirange(numrange(1,2), numrange(5,6)) -|- nummultirange(numrange(8,9));
+SELECT nummultirange(numrange(1,2)) -|- nummultirange(numrange(2,4), numrange(6,7));
+
+-- strictly left
+select 'empty'::numrange << nummultirange();
+select numrange(1,2) << nummultirange();
+select numrange(1,2) << nummultirange(numrange(3,4));
+select numrange(1,2) << nummultirange(numrange(0,4));
+select numrange(1,2) << nummultirange(numrange(0,4), numrange(7,8));
+select nummultirange() << 'empty'::numrange;
+select nummultirange() << numrange(1,2);
+select nummultirange(numrange(3,4)) << numrange(3,6);
+select nummultirange(numrange(0,2)) << numrange(3,6);
+select nummultirange(numrange(0,2), numrange(7,8)) << numrange(3,6);
+select nummultirange(numrange(-4,-2), numrange(0,2)) << numrange(3,6);
+select nummultirange() << nummultirange();
+select nummultirange() << nummultirange(numrange(1,2));
+select nummultirange(numrange(1,2)) << nummultirange();
+select nummultirange(numrange(1,2)) << nummultirange(numrange(1,2));
+select nummultirange(numrange(1,2)) << nummultirange(numrange(3,4));
+select nummultirange(numrange(1,2)) << nummultirange(numrange(3,4), numrange(7,8));
+select nummultirange(numrange(1,2), numrange(4,5)) << nummultirange(numrange(3,4), numrange(7,8));
+
+-- strictly right
+select nummultirange() >> 'empty'::numrange;
+select nummultirange() >> numrange(1,2);
+select nummultirange(numrange(3,4)) >> numrange(1,2);
+select nummultirange(numrange(0,4)) >> numrange(1,2);
+select nummultirange(numrange(0,4), numrange(7,8)) >> numrange(1,2);
+select 'empty'::numrange >> nummultirange();
+select numrange(1,2) >> nummultirange();
+select numrange(3,6) >> nummultirange(numrange(3,4));
+select numrange(3,6) >> nummultirange(numrange(0,2));
+select numrange(3,6) >> nummultirange(numrange(0,2), numrange(7,8));
+select numrange(3,6) >> nummultirange(numrange(-4,-2), numrange(0,2));
+select nummultirange() >> nummultirange();
+select nummultirange(numrange(1,2)) >> nummultirange();
+select nummultirange() >> nummultirange(numrange(1,2));
+select nummultirange(numrange(1,2)) >> nummultirange(numrange(1,2));
+select nummultirange(numrange(3,4)) >> nummultirange(numrange(1,2));
+select nummultirange(numrange(3,4), numrange(7,8)) >> nummultirange(numrange(1,2));
+select nummultirange(numrange(3,4), numrange(7,8)) >> nummultirange(numrange(1,2), numrange(4,5));
+
+-- union
+SELECT nummultirange() + nummultirange();
+SELECT nummultirange() + nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) + nummultirange();
+SELECT nummultirange(numrange(1,2)) + nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) + nummultirange(numrange(2,4));
+SELECT nummultirange(numrange(1,2)) + nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) + nummultirange(numrange(2,4));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) + nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) + nummultirange(numrange(0,9));
+
+-- merge
+SELECT range_merge(nummultirange());
+SELECT range_merge(nummultirange(numrange(1,2)));
+SELECT range_merge(nummultirange(numrange(1,2), numrange(7,8)));
+
+-- minus
+SELECT nummultirange() - nummultirange();
+SELECT nummultirange() - nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) - nummultirange();
+SELECT nummultirange(numrange(1,2), numrange(3,4)) - nummultirange();
+SELECT nummultirange(numrange(1,2)) - nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) - nummultirange(numrange(2,4));
+SELECT nummultirange(numrange(1,2)) - nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(2,3));
+SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(0,8));
+SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(0,2));
+SELECT nummultirange(numrange(1,8)) - nummultirange(numrange(0,2), numrange(3,4));
+SELECT nummultirange(numrange(1,8)) - nummultirange(numrange(2,3), numrange(5,null));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(-2,0));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(2,4));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(3,5));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(0,9));
+SELECT nummultirange(numrange(1,3), numrange(4,5)) - nummultirange(numrange(2,9));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(8,9));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(-2,0), numrange(8,9));
+
+-- intersection
+SELECT nummultirange() * nummultirange();
+SELECT nummultirange() * nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) * nummultirange();
+SELECT '{[1,3)}'::nummultirange * '{[1,5)}'::nummultirange;
+SELECT '{[1,3)}'::nummultirange * '{[0,5)}'::nummultirange;
+SELECT '{[1,3)}'::nummultirange * '{[0,2)}'::nummultirange;
+SELECT '{[1,3)}'::nummultirange * '{[2,5)}'::nummultirange;
+SELECT '{[1,4)}'::nummultirange * '{[2,3)}'::nummultirange;
+SELECT '{[1,4)}'::nummultirange * '{[0,2), [3,5)}'::nummultirange;
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[0,8), [9,12)}'::nummultirange;
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[9,12)}'::nummultirange;
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[-5,-4), [5,6), [9,12)}'::nummultirange;
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[0,2), [3,8), [9,12)}'::nummultirange;
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[0,2), [3,8), [9,12)}'::nummultirange;
+
+--
+-- range_agg function
+--
+create table reservations ( room_id integer not null, booked_during daterange );
+insert into reservations values
+-- 1: has a meets and a gap
+(1, daterange('2018-07-01', '2018-07-07')),
+(1, daterange('2018-07-07', '2018-07-14')),
+(1, daterange('2018-07-20', '2018-07-22')),
+-- 2: just a single row
+(2, daterange('2018-07-01', '2018-07-03')),
+-- 3: one null range
+(3, NULL),
+-- 4: two null ranges
+(4, NULL),
+(4, NULL),
+-- 5: a null range and a non-null range
+(5, NULL),
+(5, daterange('2018-07-01', '2018-07-03')),
+-- 6: has overlap
+(6, daterange('2018-07-01', '2018-07-07')),
+(6, daterange('2018-07-05', '2018-07-10')),
+-- 7: two ranges that meet: no gap or overlap
+(7, daterange('2018-07-01', '2018-07-07')),
+(7, daterange('2018-07-07', '2018-07-14')),
+-- 8: an empty range
+(8, 'empty'::daterange)
+;
+SELECT   room_id, range_agg(booked_during)
+FROM     reservations
+GROUP BY room_id
+ORDER BY room_id;
+
+-- range_agg on a custom range type too
+SELECT  range_agg(r)
+FROM    (VALUES
+          ('[a,c]'::textrange),
+          ('[b,b]'::textrange),
+          ('[c,f]'::textrange),
+          ('[g,h)'::textrange),
+          ('[h,j)'::textrange)
+        ) t(r);
+
+select range_intersect_agg(nmr) from nummultirange_test;
+select range_intersect_agg(nmr) from nummultirange_test where false;
+-- test with just one input:
+select range_intersect_agg(nmr) from (values ('{[1,2]}'::nummultirange)) t(nmr);
+select range_intersect_agg(nmr) from nummultirange_test where nmr @> 4.0;
+
+create table nummultirange_test2(nmr nummultirange);
+create index nummultirange_test2_hash_idx on nummultirange_test2 using hash (nmr);
+
+INSERT INTO nummultirange_test2 VALUES('{[, 5)}');
+INSERT INTO nummultirange_test2 VALUES(nummultirange(numrange(1.1, 2.2)));
+INSERT INTO nummultirange_test2 VALUES(nummultirange(numrange(1.1, 2.2)));
+INSERT INTO nummultirange_test2 VALUES(nummultirange(numrange(1.1, 2.2,'()')));
+INSERT INTO nummultirange_test2 VALUES('{}');
+
+select * from nummultirange_test2 where nmr = '{}';
+select * from nummultirange_test2 where nmr = nummultirange(numrange(1.1, 2.2));
+select * from nummultirange_test2 where nmr = nummultirange(numrange(1.1, 2.3));
+
+set enable_nestloop=t;
+set enable_hashjoin=f;
+set enable_mergejoin=f;
+select * from nummultirange_test natural join nummultirange_test2 order by nmr;
+set enable_nestloop=f;
+set enable_hashjoin=t;
+set enable_mergejoin=f;
+select * from nummultirange_test natural join nummultirange_test2 order by nmr;
+set enable_nestloop=f;
+set enable_hashjoin=f;
+set enable_mergejoin=t;
+select * from nummultirange_test natural join nummultirange_test2 order by nmr;
+
+set enable_nestloop to default;
+set enable_hashjoin to default;
+set enable_mergejoin to default;
+
+DROP TABLE nummultirange_test2;
+
+--
+-- Test user-defined multirange of floats
+--
+
+select '{[123.001, 5.e9)}'::float8multirange @> 888.882::float8;
+create table float8multirange_test(f8mr float8multirange, i int);
+insert into float8multirange_test values(float8multirange(float8range(-100.00007, '1.111113e9')), 42);
+select * from float8multirange_test;
+drop table float8multirange_test;
+
+--
+-- Test multirange types over domains
+--
+
+create domain mydomain as int4;
+create type mydomainrange as range(subtype=mydomain);
+select '{[4,50)}'::mydomainmultirange @> 7::mydomain;
+drop domain mydomain cascade;
+
+--
+-- Test domains over multirange types
+--
+
+create domain restrictedmultirange as int4multirange check (upper(value) < 10);
+select '{[4,5)}'::restrictedmultirange @> 7;
+select '{[4,50)}'::restrictedmultirange @> 7; -- should fail
+drop domain restrictedmultirange;
+
+--
+-- Test multiple multirange types over the same subtype
+--
+
+create type textrange1 as range(subtype=text, collation="C");
+create type textrange2 as range(subtype=text, collation="C");
+
+select textmultirange1(textrange2('a','Z'));  -- should fail
+select textmultirange1(textrange1('a','Z')) @> 'b'::text;
+select textmultirange2(textrange2('a','z')) @> 'b'::text;
+
+drop type textrange1;
+drop type textrange2;
+
+--
+-- Test polymorphic type system
+--
+
+create function anyarray_anymultirange_func(a anyarray, r anymultirange)
+  returns anyelement as 'select $1[1] + lower($2);' language sql;
+
+select anyarray_anymultirange_func(ARRAY[1,2], int4multirange(int4range(10,20)));
+
+-- should fail
+select anyarray_anymultirange_func(ARRAY[1,2], nummultirange(numrange(10,20)));
+
+drop function anyarray_anymultirange_func(anyarray, anymultirange);
+
+-- should fail
+create function bogus_func(anyelement)
+  returns anymultirange as 'select int4multirange(int4range(1,10))' language sql;
+
+-- should fail
+create function bogus_func(int)
+  returns anymultirange as 'select int4multirange(int4range(1,10))' language sql;
+
+create function range_add_bounds(anymultirange)
+  returns anyelement as 'select lower($1) + upper($1)' language sql;
+
+select range_add_bounds(int4multirange(int4range(1, 17)));
+select range_add_bounds(nummultirange(numrange(1.0001, 123.123)));
+
+create function multirangetypes_sql(q anymultirange, b anyarray, out c anyelement)
+  as $$ select upper($1) + $2[1] $$
+  language sql;
+
+select multirangetypes_sql(int4multirange(int4range(1,10)), ARRAY[2,20]);
+select multirangetypes_sql(nummultirange(numrange(1,10)), ARRAY[2,20]);  -- match failure
+
+--
+-- Arrays of multiranges
+--
+
+select ARRAY[nummultirange(numrange(1.1, 1.2)), nummultirange(numrange(12.3, 155.5))];
+
+create table i8mr_array (f1 int, f2 int8multirange[]);
+insert into i8mr_array values (42, array[int8multirange(int8range(1,10)), int8multirange(int8range(2,20))]);
+select * from i8mr_array;
+drop table i8mr_array;
+
+--
+-- Multiranges of arrays
+--
+
+select arraymultirange(arrayrange(ARRAY[1,2], ARRAY[2,1]));
+select arraymultirange(arrayrange(ARRAY[2,1], ARRAY[1,2]));  -- fail
+
+select array[1,1] <@ arraymultirange(arrayrange(array[1,2], array[2,1]));
+select array[1,3] <@ arraymultirange(arrayrange(array[1,2], array[2,1]));
+
+--
+-- Ranges of composites
+--
+
+create type two_ints as (a int, b int);
+create type two_ints_range as range (subtype = two_ints);
+
+-- with force_parallel_mode on, this exercises tqueue.c's range remapping
+select *, row_to_json(upper(t)) as u from
+  (values (two_ints_multirange(two_ints_range(row(1,2), row(3,4)))),
+          (two_ints_multirange(two_ints_range(row(5,6), row(7,8))))) v(t);
+
+drop type two_ints cascade;
+
+--
+-- Check behavior when subtype lacks a hash function
+--
+
+set enable_sort = off;  -- try to make it pick a hash setop implementation
+
+select '{(2,5)}'::cashmultirange except select '{(5,6)}'::cashmultirange;
+
+reset enable_sort;
+
+--
+-- OUT/INOUT/TABLE functions
+--
+
+-- infer anymultirange from anymultirange
+create function mr_outparam_succeed(i anymultirange, out r anymultirange, out t text)
+  as $$ select $1, 'foo'::text $$ language sql;
+
+select * from mr_outparam_succeed(int4multirange(int4range(1,2)));
+
+-- infer anyarray from anymultirange
+create function mr_outparam_succeed2(i anymultirange, out r anyarray, out t text)
+  as $$ select ARRAY[upper($1)], 'foo'::text $$ language sql;
+
+select * from mr_outparam_succeed2(int4multirange(int4range(1,2)));
+
+-- infer anyrange from anymultirange
+create function mr_outparam_succeed3(i anymultirange, out r anyrange, out t text)
+  as $$ select range_merge($1), 'foo'::text $$ language sql;
+select * from mr_outparam_succeed3(int4multirange(int4range(1,2)));
+
+-- infer anymultirange from anyrange
+create function mr_outparam_succeed4(i anyrange, out r anymultirange, out t text)
+  as $$ select multirange($1), 'foo'::text $$ language sql;
+
+select * from mr_outparam_succeed4(int4range(1,2));
+
+-- infer anyelement from anymultirange
+create function mr_inoutparam_succeed(out i anyelement, inout r anymultirange)
+  as $$ select upper($1), $1 $$ language sql;
+
+select * from mr_inoutparam_succeed(int4multirange(int4range(1,2)));
+
+-- infer anyelement+anymultirange from anyelement+anymultirange
+create function mr_table_succeed(i anyelement, r anymultirange) returns table(i anyelement, r anymultirange)
+  as $$ select $1, $2 $$ language sql;
+
+select * from mr_table_succeed(123, int4multirange(int4range(1,11)));
+
+-- use anymultirange in plpgsql
+create function mr_polymorphic(i anyrange) returns anymultirange
+  as $$ begin return multirange($1); end; $$ language plpgsql;
+select mr_polymorphic(int4range(1, 4));
+
+-- should fail
+create function mr_outparam_fail(i anyelement, out r anymultirange, out t text)
+  as $$ select '[1,10]', 'foo' $$ language sql;
+
+--should fail
+create function mr_inoutparam_fail(inout i anyelement, out r anymultirange)
+  as $$ select $1, '[1,10]' $$ language sql;
+
+--should fail
+create function mr_table_fail(i anyelement) returns table(i anyelement, r anymultirange)
+  as $$ select $1, '[1,10]' $$ language sql;
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/rangetypes.sql b/src/test/regress/sql/rangetypes.sql
index 2d0ec8964e..4e57352799 100644
--- a/src/test/regress/sql/rangetypes.sql
+++ b/src/test/regress/sql/rangetypes.sql
@@ -118,6 +118,10 @@ select numrange(1.0, 2.0) * numrange(2.0, 3.0);
 select numrange(1.0, 2.0) * numrange(1.5, 3.0);
 select numrange(1.0, 2.0) * numrange(2.5, 3.0);
 
+select range_intersect_agg(nr) from numrange_test;
+select range_intersect_agg(nr) from numrange_test where false;
+select range_intersect_agg(nr) from numrange_test where nr @> 4.0;
+
 create table numrange_test2(nr numrange);
 create index numrange_test2_hash_idx on numrange_test2 using hash (nr);
 
@@ -484,16 +488,25 @@ reset enable_sort;
 -- OUT/INOUT/TABLE functions
 --
 
+-- infer anyrange from anyrange
 create function outparam_succeed(i anyrange, out r anyrange, out t text)
   as $$ select $1, 'foo'::text $$ language sql;
 
 select * from outparam_succeed(int4range(1,2));
 
+-- infer anyarray from anyrange
+create function outparam_succeed2(i anyrange, out r anyarray, out t text)
+  as $$ select ARRAY[upper($1)], 'foo'::text $$ language sql;
+
+select * from outparam_succeed2(int4range(int4range(1,2)));
+
+-- infer anyelement from anyrange
 create function inoutparam_succeed(out i anyelement, inout r anyrange)
   as $$ select upper($1), $1 $$ language sql;
 
 select * from inoutparam_succeed(int4range(1,2));
 
+-- infer anyelement+anyrange from anyelement+anyrange
 create function table_succeed(i anyelement, r anyrange) returns table(i anyelement, r anyrange)
   as $$ select $1, $2 $$ language sql;
 
diff --git a/src/test/regress/sql/type_sanity.sql b/src/test/regress/sql/type_sanity.sql
index fed413bf98..2e34bc8b65 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.rngmultitypid
+FROM pg_range p1
+WHERE p1.rngmultitypid IS NULL OR p1.rngmultitypid = 0;
#84Pavel Stehule
pavel.stehule@gmail.com
In reply to: Paul A Jungwirth (#83)
Re: range_agg

Hi

st 22. 1. 2020 v 0:55 odesílatel Paul A Jungwirth <
pj@illuminatedcomputing.com> napsal:

On Sun, Jan 19, 2020 at 9:57 PM Paul A Jungwirth
<pj@illuminatedcomputing.com> wrote:

On Sun, Jan 19, 2020 at 4:38 PM Tom Lane <tgl@sss.pgh.pa.us> wrote:

True for casts involving concrete types, mainly because we'd like
the identity "value::typename == typename(value)" to hold without
too much worry about whether the latter is a plain function call
or a special case. Not sure whether it makes as much sense for
polymorphics, since casting to a polymorphic type is pretty silly:
we do seem to allow you to do that, but it's a no-op.

...

Alternatively, consider this: a cast from some concrete multirange type
to anymultirange is a no-op, while any other sort of cast probably

ought

to be casting to some particular concrete multirange type. That would
line up with the existing operations for plain ranges.

I agree you wouldn't actually cast by saying x::anymultirange, and the
casts we define are already concrete, so instead you'd say
x::int4multirange. But I think having a polymorphic function to
convert from an anyrange to an anymultirange is useful so you can
write generic functions. I can see how calling it "anymultirange" may
be preferring the implementor perspective over the user perspective
though, and how simply "multirange" would be more empathetic. I don't
mind taking that approach.

Here is a patch with anymultirange(anyrange) renamed to
multirange(anyrange). I also rebased on the latest master, added
documentation about the multirange(anyrange) function, and slightly
adjusted the formatting of the range functions table.

I think so this patch is ready for commiter.

All tests passed, the doc is good enough (the chapter name "Range functions
and Operators" should be renamed to "Range/multirange functions and
Operators"
The code formatting and comments looks well

Thank you for your work

Regards

Pavel

Show quoted text

Thanks,
Paul

#85Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Paul A Jungwirth (#83)
4 attachment(s)
Re: range_agg

I started to review this again. I'm down to figuring out whether the
typecache changes make sense; in doing so I realized that the syscaches
weren't perfectly defined (I think leftovers from when there was a
pg_multirange catalog, earlier in development), so I fixed that.

0001 is mostly Paul's v10 patch, rebased to current master; no
conflicts, I had to make a couple of small other adjustments to catch up
with current times.

The other patches are fairly obvious; changes in 0004 are described in
its commit message.

I'll continue to try to think through the typecache aspects of this
patch.

--
�lvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

Attachments:

v11-0001-Paul-s-v10.patchtext/x-diff; charset=us-asciiDownload
From 866c738f328be0adc3b1481ce0a5871d589110ea Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Tue, 11 Feb 2020 18:10:30 -0300
Subject: [PATCH v11 1/4] Paul's v10

---
 doc/src/sgml/extend.sgml                      |   28 +-
 doc/src/sgml/func.sgml                        |  326 ++-
 doc/src/sgml/rangetypes.sgml                  |   47 +-
 src/backend/catalog/Makefile                  |    1 +
 src/backend/catalog/pg_cast.c                 |  116 +
 src/backend/catalog/pg_proc.c                 |   17 +-
 src/backend/catalog/pg_range.c                |   10 +-
 src/backend/catalog/pg_type.c                 |  119 +-
 src/backend/commands/functioncmds.c           |   77 +-
 src/backend/commands/typecmds.c               |  315 ++-
 src/backend/parser/parse_coerce.c             |  307 ++-
 src/backend/utils/adt/Makefile                |    1 +
 src/backend/utils/adt/multirangetypes.c       | 2170 +++++++++++++++
 src/backend/utils/adt/pg_upgrade_support.c    |   22 +
 src/backend/utils/adt/pseudotypes.c           |   25 +
 src/backend/utils/adt/rangetypes.c            |  349 ++-
 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              |  290 +-
 src/bin/pg_dump/pg_dump.c                     |  151 +-
 src/bin/pg_dump/pg_dump.h                     |    1 +
 src/include/access/tupmacs.h                  |    4 +-
 src/include/catalog/binary_upgrade.h          |    2 +
 src/include/catalog/indexing.h                |    3 +
 src/include/catalog/pg_aggregate.dat          |   11 +
 src/include/catalog/pg_amop.dat               |   22 +
 src/include/catalog/pg_amproc.dat             |   12 +-
 src/include/catalog/pg_cast.dat               |   13 +
 src/include/catalog/pg_cast.h                 |    9 +
 src/include/catalog/pg_opclass.dat            |    4 +
 src/include/catalog/pg_operator.dat           |  169 ++
 src/include/catalog/pg_opfamily.dat           |    4 +
 src/include/catalog/pg_proc.dat               |  271 ++
 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/commands/typecmds.h               |    2 +
 src/include/utils/lsyscache.h                 |    3 +
 src/include/utils/multirangetypes.h           |  103 +
 src/include/utils/rangetypes.h                |   29 +-
 src/include/utils/syscache.h                  |    1 +
 src/include/utils/typcache.h                  |    6 +
 src/pl/plpgsql/src/pl_comp.c                  |    6 +
 src/test/regress/expected/dependency.out      |    1 +
 src/test/regress/expected/hash_func.out       |   13 +
 src/test/regress/expected/multirangetypes.out | 2395 +++++++++++++++++
 src/test/regress/expected/opr_sanity.out      |   26 +-
 src/test/regress/expected/rangetypes.out      |   38 +-
 src/test/regress/expected/sanity_check.out    |    2 +
 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      |  630 +++++
 src/test/regress/sql/opr_sanity.sql           |   18 +-
 src/test/regress/sql/rangetypes.sql           |   13 +
 src/test/regress/sql/type_sanity.sql          |   16 +-
 59 files changed, 8142 insertions(+), 365 deletions(-)
 create mode 100644 src/backend/catalog/pg_cast.c
 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/doc/src/sgml/extend.sgml b/doc/src/sgml/extend.sgml
index 9ec1af780b..92a1a254ac 100644
--- a/doc/src/sgml/extend.sgml
+++ b/doc/src/sgml/extend.sgml
@@ -229,9 +229,9 @@
    </indexterm>
 
     <para>
-     Five pseudo-types of special interest are <type>anyelement</type>,
+     Six pseudo-types of special interest are <type>anyelement</type>,
      <type>anyarray</type>, <type>anynonarray</type>, <type>anyenum</type>,
-     and <type>anyrange</type>,
+     <type>anyrange</type>, and <type>anymultirange</type>.
      which are collectively called <firstterm>polymorphic types</firstterm>.
      Any function declared using these types is said to be
      a <firstterm>polymorphic function</firstterm>.  A polymorphic function can
@@ -250,15 +250,15 @@
      position declared as <type>anyarray</type> can have any array data type,
      but similarly they must all be the same type.  And similarly,
      positions declared as <type>anyrange</type> must all be the same range
-     type.  Furthermore, if there are
+     type.  Likewise for <type>anymultirange</type>.
+    </para>
+
+    <para>
+     Furthermore, if there are
      positions declared <type>anyarray</type> and others declared
      <type>anyelement</type>, the actual array type in the
      <type>anyarray</type> positions must be an array whose elements are
      the same type appearing in the <type>anyelement</type> positions.
-     Similarly, if there are positions declared <type>anyrange</type>
-     and others declared <type>anyelement</type>, the actual range type in
-     the <type>anyrange</type> positions must be a range whose subtype is
-     the same type appearing in the <type>anyelement</type> positions.
      <type>anynonarray</type> is treated exactly the same as <type>anyelement</type>,
      but adds the additional constraint that the actual type must not be
      an array type.
@@ -267,6 +267,20 @@
      be an enum type.
     </para>
 
+    <para>
+     Similarly, if there are positions declared <type>anyrange</type>
+     and others declared <type>anyelement</type>, the actual range type in
+     the <type>anyrange</type> positions must be a range whose subtype is
+     the same type appearing in the <type>anyelement</type> positions.
+     The type <type>anymultirange</type> accepts a multirange
+     whose contents match the other polymorphic types.
+     That is it must hold ranges matching <type>anyrange</type>
+     and base type elements matching <type>anyelement</type> (if either of
+     those are present). If <type>anyarray</type> is present its elements
+     must match the base type of the <type>anyrange</type> and/or the base
+     type of the ranges in an <type>anymultirange</type>.
+    </para>
+
     <para>
      Thus, when more than one argument position is declared with a polymorphic
      type, the net effect is that only certain combinations of actual argument
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 28035f1635..fc26c3fb14 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -13535,7 +13535,7 @@ SELECT NULLIF(value, '(none)') ...
        <row>
         <entry>Operator</entry>
         <entry>Description</entry>
-        <entry>Example</entry>
+        <entry>Examples</entry>
         <entry>Result</entry>
        </row>
       </thead>
@@ -14006,12 +14006,14 @@ NULL baz</literallayout>(3 rows)</entry>
   <title>Range Functions and Operators</title>
 
   <para>
-   See <xref linkend="rangetypes"/> for an overview of range types.
+   See <xref linkend="rangetypes"/> for an overview of range and multirange types.
   </para>
 
   <para>
    <xref linkend="range-operators-table"/> shows the operators
-   available for range types.
+   available for range and multirange types.
+   Many of these operators will accept either a range or multirange
+   on either side.
   </para>
 
     <table id="range-operators-table">
@@ -14029,134 +14031,215 @@ NULL baz</literallayout>(3 rows)</entry>
        <row>
         <entry> <literal>=</literal> </entry>
         <entry>equal</entry>
-        <entry><literal>int4range(1,5) = '[1,4]'::int4range</literal></entry>
-        <entry><literal>t</literal></entry>
+        <entry><literallayout class="monospaced">int4range(1,5) = '[1,4]'::int4range
+'{[1,5)}'::int4multirange = '{[1,4]}'::int4multirange</literallayout></entry>
+        <entry><literallayout class="monospaced">t</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>&lt;&gt;</literal> </entry>
         <entry>not equal</entry>
-        <entry><literal>numrange(1.1,2.2) &lt;&gt; numrange(1.1,2.3)</literal></entry>
-        <entry><literal>t</literal></entry>
+        <entry><literallayout class="monospaced">numrange(1.1,2.2) &lt;&gt; numrange(1.1,2.3)
+'{[1.1,2.2)}'::nummultirange &lt;&gt; '{[1.1,2.3)}'::nummultirange</literallayout></entry>
+        <entry><literallayout class="monospaced">t</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>&lt;</literal> </entry>
         <entry>less than</entry>
-        <entry><literal>int4range(1,10) &lt; int4range(2,3)</literal></entry>
-        <entry><literal>t</literal></entry>
+        <entry><literallayout class="monospaced">int4range(1,10) &lt; int4range(2,3)
+'{[1,10)}'::int4multirange &lt; '{[2,3)}'::int4multirange</literallayout>
+        </entry>
+        <entry><literallayout class="monospaced">t</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>&gt;</literal> </entry>
         <entry>greater than</entry>
-        <entry><literal>int4range(1,10) &gt; int4range(1,5)</literal></entry>
-        <entry><literal>t</literal></entry>
+        <entry>
+          <literallayout class="monospaced">int4range(1,10) &gt; int4range(1,5)
+'{[1,10)}'::int4multirange &gt; '{[1,5)}'::int4multirange</literallayout>
+        </entry>
+        <entry><literallayout class="monospaced">t</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>&lt;=</literal> </entry>
         <entry>less than or equal</entry>
-        <entry><literal>numrange(1.1,2.2) &lt;= numrange(1.1,2.2)</literal></entry>
-        <entry><literal>t</literal></entry>
+        <entry>
+          <literallayout class="monospaced">numrange(1.1,2.2) &lt;= numrange(1.1,2.2)
+'{[1.1,2.2)}'::nummultirange &lt;= '{[1.1,2.2)}'::nummultirange</literallayout>
+        </entry>
+        <entry><literallayout class="monospaced">t</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>&gt;=</literal> </entry>
         <entry>greater than or equal</entry>
-        <entry><literal>numrange(1.1,2.2) &gt;= numrange(1.1,2.0)</literal></entry>
-        <entry><literal>t</literal></entry>
+        <entry>
+          <literallayout class="monospaced">numrange(1.1,2.2) &gt;= numrange(1.1,2.0)
+'{[1.1,2.2)}'::nummultirange &gt;= '{[1.1,2.0)}'::nummultirange</literallayout>
+        </entry>
+        <entry><literallayout class="monospaced">t</literallayout></entry>
+       </row>
+
+       <row>
+        <entry> <literal>@&gt;</literal> </entry>
+        <entry>contains multirange</entry>
+        <entry>
+          <literallayout class="monospaced">int4range(2,4) @&gt; '{[2,3)}'::int4multirange
+'{[2,4)}'::int4multirange @&gt; '{[2,3)}'::int4multirange</literallayout>
+        </entry>
+        <entry><literallayout class="monospaced">t</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>@&gt;</literal> </entry>
         <entry>contains range</entry>
-        <entry><literal>int4range(2,4) @&gt; int4range(2,3)</literal></entry>
-        <entry><literal>t</literal></entry>
+        <entry>
+          <literallayout class="monospaced">int4range(2,4) @&gt; int4range(2,3)
+'{[2,4)}'::int4multirange @&gt; int4range(2,3)</literallayout>
+        </entry>
+        <entry><literallayout class="monospaced">t</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>@&gt;</literal> </entry>
         <entry>contains element</entry>
-        <entry><literal>'[2011-01-01,2011-03-01)'::tsrange @&gt; '2011-01-10'::timestamp</literal></entry>
-        <entry><literal>t</literal></entry>
+        <entry>
+          <literallayout class="monospaced">'[2011-01-01,2011-03-01)'::tsrange @&gt; '2011-01-10'::timestamp
+'{[2011-01-01,2011-03-01)}'::tsmultirange @&gt; '2011-01-10'::timestamp</literallayout>
+        </entry>
+        <entry><literallayout class="monospaced">t</literallayout></entry>
+       </row>
+
+       <row>
+        <entry> <literal>&lt;@</literal> </entry>
+        <entry>multirange is contained by</entry>
+        <entry>
+          <literallayout class="monospaced">'{[2,4)}'::int4multirange &lt;@ int4range(1,7)
+'{[2,4)}'::int4multirange &lt;@ '{[1,7)}'::int4multirange</literallayout>
+        </entry>
+        <entry><literallayout class="monospaced">t</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>&lt;@</literal> </entry>
         <entry>range is contained by</entry>
-        <entry><literal>int4range(2,4) &lt;@ int4range(1,7)</literal></entry>
-        <entry><literal>t</literal></entry>
+        <entry>
+          <literallayout class="monospaced">int4range(2,4) &lt;@ int4range(1,7)
+int4range(2,4) &lt;@ '{[1,7)}'::int4multirange</literallayout>
+        </entry>
+        <entry><literallayout class="monospaced">t</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>&lt;@</literal> </entry>
         <entry>element is contained by</entry>
-        <entry><literal>42 &lt;@ int4range(1,7)</literal></entry>
-        <entry><literal>f</literal></entry>
+        <entry>
+          <literallayout class="monospaced">42 &lt;@ int4range(1,7)
+42 &lt;@ '{[1,7)}'::int4multirange</literallayout>
+        </entry>
+        <entry><literallayout class="monospaced">f</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>&amp;&amp;</literal> </entry>
         <entry>overlap (have points in common)</entry>
-        <entry><literal>int8range(3,7) &amp;&amp; int8range(4,12)</literal></entry>
-        <entry><literal>t</literal></entry>
+        <entry>
+          <literallayout class="monospaced">int8range(3,7) &amp;&amp; int8range(4,12)
+int8range(3,7) &amp;&amp; '{[4,12)}'::int8multirange
+'{[3,7)}'::int8multirange &amp;&amp; int8range(4,12)
+'{[3,7)}'::int8multirange &amp;&amp; '{[4,12)}'::int8multirange</literallayout>
+        </entry>
+        <entry><literallayout class="monospaced">t</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>&lt;&lt;</literal> </entry>
         <entry>strictly left of</entry>
-        <entry><literal>int8range(1,10) &lt;&lt; int8range(100,110)</literal></entry>
-        <entry><literal>t</literal></entry>
+        <entry>
+          <literallayout class="monospaced">int8range(1,10) &lt;&lt; int8range(100,110)
+int8range(1,10) &lt;&lt; '{[100,110)}'::int8multirange
+'{[1,10)}'::int8multirange &lt;&lt; int8range(100,110)
+'{[1,10)}'::int8multirange &lt;&lt; '{[100,110)}'::int8multirange</literallayout>
+        </entry>
+        <entry><literallayout class="monospaced">t</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>&gt;&gt;</literal> </entry>
         <entry>strictly right of</entry>
-        <entry><literal>int8range(50,60) &gt;&gt; int8range(20,30)</literal></entry>
-        <entry><literal>t</literal></entry>
+        <entry>
+          <literallayout class="monospaced">int8range(50,60) &gt;&gt; int8range(20,30)
+int8range(50,60) &gt;&gt; '{[20,30)}'::int8multirange
+'{[50,60)}'::int8multirange &gt;&gt; int8range(20,30)
+'{[50,60)}'::int8multirange &gt;&gt; '{[20,30)}'::int8multirange</literallayout>
+        </entry>
+        <entry><literallayout class="monospaced">t</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>&amp;&lt;</literal> </entry>
         <entry>does not extend to the right of</entry>
-        <entry><literal>int8range(1,20) &amp;&lt; int8range(18,20)</literal></entry>
-        <entry><literal>t</literal></entry>
+        <entry>
+          <literallayout class="monospaced">int8range(1,20) &amp;&lt; int8range(18,20)
+int8range(1,20) &amp;&lt; '{[18,20)}'::int8multirange
+'{[1,20)}'::int8multirange &amp;&lt; int8range(18,20)
+'{[1,20)}'::int8multirange &amp;&lt; '{[18,20)}'::int8multirange</literallayout>
+        </entry>
+        <entry><literallayout class="monospaced">t</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>&amp;&gt;</literal> </entry>
         <entry>does not extend to the left of</entry>
-        <entry><literal>int8range(7,20) &amp;&gt; int8range(5,10)</literal></entry>
-        <entry><literal>t</literal></entry>
+        <entry>
+          <literallayout class="monospaced">int8range(7,20) &amp;&gt; int8range(5,10)
+int8range(7,20) &amp;&gt; '{[5,10)}'::int8multirange
+'{[7,20)}'::int8multirange &amp;&gt; int8range(5,10)
+'{[7,20)}'::int8multirange &amp;&gt; '{[5,10)}'::int8multirange</literallayout>
+        </entry>
+        <entry><literallayout class="monospaced">t</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>-|-</literal> </entry>
         <entry>is adjacent to</entry>
-        <entry><literal>numrange(1.1,2.2) -|- numrange(2.2,3.3)</literal></entry>
-        <entry><literal>t</literal></entry>
+        <entry>
+          <literallayout class="monospaced">numrange(1.1,2.2) -|- numrange(2.2,3.3)
+numrange(1.1,2.2) -|- '{[2.2,3.3)}'::nummultirange
+'{[1.1,2.2)}'::nummultirange -|- numrange(2.2,3.3)
+'{[1.1,2.2)}'::nummultirange -|- '{[2.2,3.3)}'::nummultirange</literallayout>
+        </entry>
+        <entry><literallayout class="monospaced">t</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>+</literal> </entry>
         <entry>union</entry>
-        <entry><literal>numrange(5,15) + numrange(10,20)</literal></entry>
-        <entry><literal>[5,20)</literal></entry>
+        <entry><literallayout class="monospaced">numrange(5,15) + numrange(10,20)
+'{[5,10)}'::nummultirange + '{[15,20)}'::nummultirange</literallayout></entry>
+        <entry><literallayout class="monospaced">[5,20)
+{[5,10), [15,20)}</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>*</literal> </entry>
         <entry>intersection</entry>
-        <entry><literal>int8range(5,15) * int8range(10,20)</literal></entry>
-        <entry><literal>[10,15)</literal></entry>
+        <entry><literallayout class="monospaced">int8range(5,15) * int8range(10,20)
+'{[5,15)}'::int8multirange * '{[10,20)}'::int8multirange</literallayout></entry>
+        <entry><literallayout class="monospaced">[10,15)
+{[10,15)}</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>-</literal> </entry>
         <entry>difference</entry>
-        <entry><literal>int8range(5,15) - int8range(10,20)</literal></entry>
-        <entry><literal>[5,10)</literal></entry>
+        <entry><literallayout class="monospaced">int8range(5,15) - int8range(10,20)
+'{[5,20)}'::int8multirange - '{[10,15)}'::int8multirange</literallayout></entry>
+        <entry><literallayout class="monospaced">[5,10)
+{[5,10), [15,20)}</literallayout></entry>
        </row>
 
       </tbody>
@@ -14174,19 +14257,31 @@ NULL baz</literallayout>(3 rows)</entry>
 
   <para>
    The left-of/right-of/adjacent operators always return false when an empty
-   range is involved; that is, an empty range is not considered to be either
-   before or after any other range.
+   range or multirange is involved; that is, an empty range is not considered to
+   be either before or after any other range.
+  </para>
+
+  <para>
+   Elsewhere empty ranges and multiranges are treated as the additive identity:
+   anything unioned with an empty value is itself. Anything minus an empty
+   value is itself. An empty multirange has exactly the same points as an empty
+   range. Every range contains the empty range. Every multirange contains as many
+   empty ranges as you like.
   </para>
 
   <para>
    The union and difference operators will fail if the resulting range would
    need to contain two disjoint sub-ranges, as such a range cannot be
-   represented.
+   represented. There are separate operators for union and difference that take
+   multirange parameters and return a multirange, and they do not fail even if
+   their arguments are disjoint. So if you need a union or difference operation
+   for ranges that may be disjoint, you can avoid errors by first casting your
+   ranges to multiranges.
   </para>
 
   <para>
    <xref linkend="range-functions-table"/> shows the functions
-   available for use with range types.
+   available for use with range and multirange types.
   </para>
 
   <indexterm>
@@ -14235,6 +14330,17 @@ NULL baz</literallayout>(3 rows)</entry>
         <entry><literal>lower(numrange(1.1,2.2))</literal></entry>
         <entry><literal>1.1</literal></entry>
        </row>
+       <row>
+        <entry>
+         <literal>
+          <function>lower</function>(<type>anymultirange</type>)
+         </literal>
+        </entry>
+        <entry>multirange's element type</entry>
+        <entry>lower bound of multirange</entry>
+        <entry><literal>lower('{[1.1,2.2)}'::nummultirange)</literal></entry>
+        <entry><literal>1.1</literal></entry>
+       </row>
        <row>
         <entry>
          <literal>
@@ -14246,6 +14352,17 @@ NULL baz</literallayout>(3 rows)</entry>
         <entry><literal>upper(numrange(1.1,2.2))</literal></entry>
         <entry><literal>2.2</literal></entry>
        </row>
+       <row>
+        <entry>
+         <literal>
+          <function>upper</function>(<type>anymultirange</type>)
+         </literal>
+        </entry>
+        <entry>multirange's element type</entry>
+        <entry>upper bound of multirange</entry>
+        <entry><literal>upper('{[1.1,2.2)}'::nummultirange)</literal></entry>
+        <entry><literal>2.2</literal></entry>
+       </row>
        <row>
         <entry>
          <literal>
@@ -14257,6 +14374,17 @@ NULL baz</literallayout>(3 rows)</entry>
         <entry><literal>isempty(numrange(1.1,2.2))</literal></entry>
         <entry><literal>false</literal></entry>
        </row>
+       <row>
+        <entry>
+         <literal>
+          <function>isempty</function>(<type>anymultirange</type>)
+         </literal>
+        </entry>
+        <entry><type>boolean</type></entry>
+        <entry>is the multirange empty?</entry>
+        <entry><literal>isempty('{[1.1,2.2)}'::nummultirange)</literal></entry>
+        <entry><literal>false</literal></entry>
+       </row>
        <row>
         <entry>
          <literal>
@@ -14268,6 +14396,17 @@ NULL baz</literallayout>(3 rows)</entry>
         <entry><literal>lower_inc(numrange(1.1,2.2))</literal></entry>
         <entry><literal>true</literal></entry>
        </row>
+       <row>
+        <entry>
+         <literal>
+          <function>lower_inc</function>(<type>anymultirange</type>)
+         </literal>
+        </entry>
+        <entry><type>boolean</type></entry>
+        <entry>is the lower bound inclusive?</entry>
+        <entry><literal>lower_inc('{[1.1,2.2)}'::nummultirange)</literal></entry>
+        <entry><literal>true</literal></entry>
+       </row>
        <row>
         <entry>
          <literal>
@@ -14279,6 +14418,17 @@ NULL baz</literallayout>(3 rows)</entry>
         <entry><literal>upper_inc(numrange(1.1,2.2))</literal></entry>
         <entry><literal>false</literal></entry>
        </row>
+       <row>
+        <entry>
+         <literal>
+          <function>upper_inc</function>(<type>anymultirange</type>)
+         </literal>
+        </entry>
+        <entry><type>boolean</type></entry>
+        <entry>is the upper bound inclusive?</entry>
+        <entry><literal>upper_inc('{[1.1,2.2)}'::nummultirange)</literal></entry>
+        <entry><literal>false</literal></entry>
+       </row>
        <row>
         <entry>
          <literal>
@@ -14290,6 +14440,17 @@ NULL baz</literallayout>(3 rows)</entry>
         <entry><literal>lower_inf('(,)'::daterange)</literal></entry>
         <entry><literal>true</literal></entry>
        </row>
+       <row>
+        <entry>
+         <literal>
+          <function>lower_inf</function>(<type>anymultirange</type>)
+         </literal>
+        </entry>
+        <entry><type>boolean</type></entry>
+        <entry>is the lower bound infinite?</entry>
+        <entry><literal>lower_inf('{(,)}'::datemultirange)</literal></entry>
+        <entry><literal>true</literal></entry>
+       </row>
        <row>
         <entry>
          <literal>
@@ -14301,6 +14462,17 @@ NULL baz</literallayout>(3 rows)</entry>
         <entry><literal>upper_inf('(,)'::daterange)</literal></entry>
         <entry><literal>true</literal></entry>
        </row>
+       <row>
+        <entry>
+         <literal>
+          <function>upper_inf</function>(<type>anymultirange</type>)
+         </literal>
+        </entry>
+        <entry><type>boolean</type></entry>
+        <entry>is the upper bound infinite?</entry>
+        <entry><literal>upper_inf('{(,)}'::datemultirange)</literal></entry>
+        <entry><literal>true</literal></entry>
+       </row>
        <row>
         <entry>
          <literal>
@@ -14312,16 +14484,38 @@ NULL baz</literallayout>(3 rows)</entry>
         <entry><literal>range_merge('[1,2)'::int4range, '[3,4)'::int4range)</literal></entry>
         <entry><literal>[1,4)</literal></entry>
        </row>
+       <row>
+        <entry>
+         <literal>
+          <function>range_merge</function>(<type>anymultirange</type>)
+         </literal>
+        </entry>
+        <entry><type>anyrange</type></entry>
+        <entry>the smallest range which includes the entire multirange</entry>
+        <entry><literal>range_merge('{[1,2), [3,4)}'::int4multirange)</literal></entry>
+        <entry><literal>[1,4)</literal></entry>
+       </row>
+       <row>
+        <entry>
+         <literal>
+          <function>multirange</function>(<type>anyrange</type>)
+         </literal>
+        </entry>
+        <entry><type>anymultirange</type></entry>
+        <entry>a multirange containing just the given range</entry>
+        <entry><literal>multirange('[1,2)'::int4range)</literal></entry>
+        <entry><literal>{[1,2)}</literal></entry>
+       </row>
       </tbody>
      </tgroup>
     </table>
 
   <para>
    The <function>lower</function> and  <function>upper</function> functions return null
-   if the range is empty or the requested bound is infinite.
+   if the input range/multirange is empty or the requested bound is infinite.
    The <function>lower_inc</function>, <function>upper_inc</function>,
    <function>lower_inf</function>, and <function>upper_inf</function>
-   functions all return false for an empty range.
+   functions all return false for an empty range or multirange.
   </para>
   </sect1>
 
@@ -14640,6 +14834,44 @@ NULL baz</literallayout>(3 rows)</entry>
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <indexterm>
+        <primary>range_agg</primary>
+       </indexterm>
+       <function>
+         range_agg(<replaceable class="parameter">expression</replaceable>)
+       </function>
+      </entry>
+      <entry>
+       <type>anyrange</type>
+      </entry>
+      <entry>
+       <type>multirange</type> of the argument range type
+      </entry>
+      <entry>Yes</entry>
+      <entry>union of the non-null input values</entry>
+     </row>
+
+     <row>
+      <entry>
+       <indexterm>
+        <primary>range_intersect_agg</primary>
+       </indexterm>
+       <function>
+         range_intersect_agg(<replaceable class="parameter">expression</replaceable>)
+       </function>
+      </entry>
+      <entry>
+       <type>anyrange</type>
+      </entry>
+      <entry>
+       <type>multirange</type> of the argument range type
+      </entry>
+      <entry>No</entry>
+      <entry>intersection of the non-null input values</entry>
+     </row>
+
      <row>
       <entry>
        <indexterm>
diff --git a/doc/src/sgml/rangetypes.sgml b/doc/src/sgml/rangetypes.sgml
index b75fb3a392..c8784bdce3 100644
--- a/doc/src/sgml/rangetypes.sgml
+++ b/doc/src/sgml/rangetypes.sgml
@@ -27,6 +27,13 @@
   ranges from an instrument, and so forth can also be useful.
  </para>
 
+ <para>
+  Every range type has a corresponding multirange type. A multirange is
+  an ordered list of non-continguous, non-empty, non-null ranges. Most
+  range operators also work on multiranges, and they have a few functions
+  of their own.
+ </para>
+
  <sect2 id="rangetypes-builtin">
   <title>Built-in Range Types</title>
 
@@ -232,10 +239,30 @@ SELECT '[4,4]'::int4range;
 SELECT '[4,4)'::int4range;
 </programlisting>
   </para>
+
+  <para>
+   The input for a multirange is curly brackets (<literal>{</literal> and
+   <literal>}</literal>) containing zero or more valid ranges,
+   separated by commas. Whitespace is permitted around the brackets and
+   commas. This is intended to be reminiscent of array syntax, although
+   multiranges are much simpler: they have just one dimension and there is
+   no need to quote their contents. (The bounds of their ranges may be
+   quoted as above however.)
+  </para>
+
+  <para>
+  Examples:
+<programlisting>
+SELECT '{}'::int4multirange;
+SELECT '{[3,7)}'::int4multirange;
+SELECT '{[3,7), [8,9)}'::int4multirange;
+</programlisting>
+  </para>
+
  </sect2>
 
  <sect2 id="rangetypes-construct">
-  <title>Constructing Ranges</title>
+  <title>Constructing Ranges and Multiranges</title>
 
   <para>
    Each range type has a constructor function with the same name as the range
@@ -267,6 +294,19 @@ SELECT int8range(1, 14, '(]');
 
 -- Using NULL for either bound causes the range to be unbounded on that side.
 SELECT numrange(NULL, 2.2);
+</programlisting>
+  </para>
+
+  <para>
+   Each range type also has a multirange constructor with the same name as the
+   multirange type.  The constructor function takes zero or more arguments
+   which are all ranges of the appropriate type.
+   For example:
+
+<programlisting>
+SELECT nummultirange();
+SELECT nummultirange(numrange(1.0, 14.0));
+SELECT nummultirange(numrange(1.0, 14.0), numrange(20.0, 25.0));
 </programlisting>
   </para>
  </sect2>
@@ -341,6 +381,11 @@ SELECT '[1.234, 5.678]'::floatrange;
    function in this example.
   </para>
 
+  <para>
+   When you define your own range you automatically get a corresponding
+   multirange type.
+  </para>
+
   <para>
    Defining your own range type also allows you to specify a different
    subtype B-tree operator class or collation to use, so as to change the sort
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index f8f0b4841c..9499bb33e5 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -25,6 +25,7 @@ OBJS = \
 	objectaddress.o \
 	partition.o \
 	pg_aggregate.o \
+	pg_cast.o \
 	pg_collation.o \
 	pg_constraint.o \
 	pg_conversion.o \
diff --git a/src/backend/catalog/pg_cast.c b/src/backend/catalog/pg_cast.c
new file mode 100644
index 0000000000..50b44e3a96
--- /dev/null
+++ b/src/backend/catalog/pg_cast.c
@@ -0,0 +1,116 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_cast.c
+ *	  routines to support manipulation of the pg_cast relation
+ *
+ * Portions Copyright (c) 1996-2019, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ *	  src/backend/catalog/pg_cast.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "catalog/pg_cast.h"
+#include "access/table.h"
+#include "catalog/catalog.h"
+#include "catalog/dependency.h"
+#include "catalog/indexing.h"
+#include "catalog/objectaccess.h"
+#include "catalog/pg_proc.h"
+#include "catalog/pg_type.h"
+#include "tcop/tcopprot.h"
+#include "utils/builtins.h"
+#include "utils/rel.h"
+#include "utils/syscache.h"
+
+/*
+ * ----------------------------------------------------------------
+ *		CastCreate
+ * ----------------------------------------------------------------
+ */
+ObjectAddress
+CastCreate(Oid sourcetypeid, Oid targettypeid, Oid funcid, char castcontext,
+		   char castmethod, DependencyType behavior)
+{
+	Relation		relation;
+	HeapTuple		tuple;
+	Oid				castid;
+	Datum			values[Natts_pg_cast];
+	bool			nulls[Natts_pg_cast];
+	ObjectAddress	myself,
+					referenced;
+
+	relation = table_open(CastRelationId, RowExclusiveLock);
+
+	/*
+	 * Check for duplicate.  This is just to give a friendly error message,
+	 * the unique index would catch it anyway (so no need to sweat about race
+	 * conditions).
+	 */
+	tuple = SearchSysCache2(CASTSOURCETARGET,
+							ObjectIdGetDatum(sourcetypeid),
+							ObjectIdGetDatum(targettypeid));
+	if (HeapTupleIsValid(tuple))
+		ereport(ERROR,
+				(errcode(ERRCODE_DUPLICATE_OBJECT),
+				 errmsg("cast from type %s to type %s already exists",
+						format_type_be(sourcetypeid),
+						format_type_be(targettypeid))));
+
+	/* ready to go */
+	castid = GetNewOidWithIndex(relation, CastOidIndexId, Anum_pg_cast_oid);
+	values[Anum_pg_cast_oid - 1] = ObjectIdGetDatum(castid);
+	values[Anum_pg_cast_castsource - 1] = ObjectIdGetDatum(sourcetypeid);
+	values[Anum_pg_cast_casttarget - 1] = ObjectIdGetDatum(targettypeid);
+	values[Anum_pg_cast_castfunc - 1] = ObjectIdGetDatum(funcid);
+	values[Anum_pg_cast_castcontext - 1] = CharGetDatum(castcontext);
+	values[Anum_pg_cast_castmethod - 1] = CharGetDatum(castmethod);
+
+	MemSet(nulls, false, sizeof(nulls));
+
+	tuple = heap_form_tuple(RelationGetDescr(relation), values, nulls);
+
+	CatalogTupleInsert(relation, tuple);
+
+	/* make dependency entries */
+	myself.classId = CastRelationId;
+	myself.objectId = castid;
+	myself.objectSubId = 0;
+
+	/* dependency on source type */
+	referenced.classId = TypeRelationId;
+	referenced.objectId = sourcetypeid;
+	referenced.objectSubId = 0;
+	recordDependencyOn(&myself, &referenced, behavior);
+
+	/* dependency on target type */
+	referenced.classId = TypeRelationId;
+	referenced.objectId = targettypeid;
+	referenced.objectSubId = 0;
+	recordDependencyOn(&myself, &referenced, behavior);
+
+	/* dependency on function */
+	if (OidIsValid(funcid))
+	{
+		referenced.classId = ProcedureRelationId;
+		referenced.objectId = funcid;
+		referenced.objectSubId = 0;
+		recordDependencyOn(&myself, &referenced, behavior);
+	}
+
+	/* dependency on extension */
+	recordDependencyOnCurrentExtension(&myself, false);
+
+	/* Post creation hook for new cast */
+	InvokeObjectPostCreateHook(CastRelationId, castid, 0);
+
+	heap_freetuple(tuple);
+
+	table_close(relation, RowExclusiveLock);
+
+	return myself;
+}
diff --git a/src/backend/catalog/pg_proc.c b/src/backend/catalog/pg_proc.c
index 5194dcaac0..6efcac52a0 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,13 @@ 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 b5bc36c2bd..1217a6ddd0 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_rngmultitypid - 1] = ObjectIdGetDatum(multirangeTypeOid);
 
 	tup = heap_form_tuple(RelationGetDescr(pg_range), values, nulls);
 
@@ -100,6 +102,12 @@ 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 8d7572da51..d74697ef9c 100644
--- a/src/backend/catalog/pg_type.c
+++ b/src/backend/catalog/pg_type.c
@@ -27,6 +27,7 @@
 #include "catalog/pg_proc.h"
 #include "catalog/pg_type.h"
 #include "commands/typecmds.h"
+#include "mb/pg_wchar.h"
 #include "miscadmin.h"
 #include "parser/scansup.h"
 #include "utils/acl.h"
@@ -36,6 +37,9 @@
 #include "utils/rel.h"
 #include "utils/syscache.h"
 
+static char *makeUniqueTypeName(const char *typeName, Oid typeNamespace,
+								bool tryOriginal);
+
 /* Potentially set by pg_upgrade_support functions */
 Oid			binary_upgrade_next_pg_type_oid = InvalidOid;
 
@@ -783,31 +787,10 @@ RenameTypeInternal(Oid typeOid, const char *newTypeName, Oid typeNamespace)
 char *
 makeArrayTypeName(const char *typeName, Oid typeNamespace)
 {
-	char	   *arr = (char *) palloc(NAMEDATALEN);
-	int			namelen = strlen(typeName);
-	int			i;
+	char	   *arr;
 
-	/*
-	 * 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)
+	arr = makeUniqueTypeName(typeName, typeNamespace, false);
+	if (arr == NULL)
 		ereport(ERROR,
 				(errcode(ERRCODE_DUPLICATE_OBJECT),
 				 errmsg("could not form array type name for type \"%s\"",
@@ -878,3 +861,91 @@ moveArrayTypeName(Oid typeOid, const char *typeName, Oid typeNamespace)
 
 	return true;
 }
+
+
+/*
+ * makeMultirangeTypeName
+ *	  - given a range type name, make a multirange type name for it
+ *
+ * caller is responsible for pfreeing the result
+ */
+char *
+makeMultirangeTypeName(const char *rangeTypeName, Oid typeNamespace)
+{
+	char	   *buf;
+	char	   *mrname;
+	char	   *rangestr;
+
+	/*
+	 * 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...
+	 */
+	rangestr = strstr(rangeTypeName, "range");
+	if (rangestr)
+	{
+		char	*prefix = pnstrdup(rangeTypeName, rangestr - rangeTypeName);
+
+		buf = psprintf("%s%s%s", prefix, "multi", rangestr);
+	}
+	else
+		buf = psprintf("%s_multirange", pnstrdup(rangeTypeName, NAMEDATALEN - 12));
+
+	/* clip it at NAMEDATALEN-1 bytes */
+	buf[pg_mbcliplen(buf, strlen(buf), NAMEDATALEN - 1)] = '\0';
+
+	mrname = makeUniqueTypeName(buf, typeNamespace, true);
+	if (mrname == NULL)
+		ereport(ERROR,
+				(errcode(ERRCODE_DUPLICATE_OBJECT),
+				 errmsg("could not form multirange type name for type \"%s\"",
+						rangeTypeName)));
+
+	return mrname;
+}
+
+/*
+ * makeUniqueTypeName
+ *		Generate a unique name for a prospective new type
+ *
+ * Given a typeName, return a new palloc'ed name by preprending underscores
+ * until a non-conflicting name results.
+ *
+ * If tryOriginal, first try with zero underscores.
+ */
+static char *
+makeUniqueTypeName(const char *typeName, Oid typeNamespace, bool tryOriginal)
+{
+	int			i;
+	int			namelen;
+	char		dest[NAMEDATALEN];
+
+	Assert(strlen(typeName) <= NAMEDATALEN - 1);
+
+	if (tryOriginal &&
+		!SearchSysCacheExists2(TYPENAMENSP,
+							   CStringGetDatum(typeName),
+							   ObjectIdGetDatum(typeNamespace)))
+		return pstrdup(typeName);
+
+	/*
+	 * The idea is to prepend underscores as needed until we make a name that
+	 * doesn't collide with anything ...
+	 */
+	namelen = strlen(typeName);
+	for (i = 1; i < NAMEDATALEN - 1; i++)
+	{
+		dest[i - 1] = '_';
+		strlcpy(dest + i, typeName, NAMEDATALEN - i);
+		if (namelen + i >= NAMEDATALEN)
+			truncate_identifier(dest, NAMEDATALEN, false);
+
+		if (!SearchSysCacheExists2(TYPENAMENSP,
+								   CStringGetDatum(dest),
+								   ObjectIdGetDatum(typeNamespace)))
+			return pstrdup(dest);
+	}
+
+	return NULL;
+}
diff --git a/src/backend/commands/functioncmds.c b/src/backend/commands/functioncmds.c
index 540044b2d6..3c047fa5e1 100644
--- a/src/backend/commands/functioncmds.c
+++ b/src/backend/commands/functioncmds.c
@@ -1498,17 +1498,12 @@ CreateCast(CreateCastStmt *stmt)
 	char		sourcetyptype;
 	char		targettyptype;
 	Oid			funcid;
-	Oid			castid;
 	int			nargs;
 	char		castcontext;
 	char		castmethod;
-	Relation	relation;
 	HeapTuple	tuple;
-	Datum		values[Natts_pg_cast];
-	bool		nulls[Natts_pg_cast];
-	ObjectAddress myself,
-				referenced;
 	AclResult	aclresult;
+	ObjectAddress	myself;
 
 	sourcetypeid = typenameTypeId(NULL, stmt->sourcetype);
 	targettypeid = typenameTypeId(NULL, stmt->targettype);
@@ -1732,74 +1727,8 @@ CreateCast(CreateCastStmt *stmt)
 			break;
 	}
 
-	relation = table_open(CastRelationId, RowExclusiveLock);
-
-	/*
-	 * Check for duplicate.  This is just to give a friendly error message,
-	 * the unique index would catch it anyway (so no need to sweat about race
-	 * conditions).
-	 */
-	tuple = SearchSysCache2(CASTSOURCETARGET,
-							ObjectIdGetDatum(sourcetypeid),
-							ObjectIdGetDatum(targettypeid));
-	if (HeapTupleIsValid(tuple))
-		ereport(ERROR,
-				(errcode(ERRCODE_DUPLICATE_OBJECT),
-				 errmsg("cast from type %s to type %s already exists",
-						format_type_be(sourcetypeid),
-						format_type_be(targettypeid))));
-
-	/* ready to go */
-	castid = GetNewOidWithIndex(relation, CastOidIndexId, Anum_pg_cast_oid);
-	values[Anum_pg_cast_oid - 1] = ObjectIdGetDatum(castid);
-	values[Anum_pg_cast_castsource - 1] = ObjectIdGetDatum(sourcetypeid);
-	values[Anum_pg_cast_casttarget - 1] = ObjectIdGetDatum(targettypeid);
-	values[Anum_pg_cast_castfunc - 1] = ObjectIdGetDatum(funcid);
-	values[Anum_pg_cast_castcontext - 1] = CharGetDatum(castcontext);
-	values[Anum_pg_cast_castmethod - 1] = CharGetDatum(castmethod);
-
-	MemSet(nulls, false, sizeof(nulls));
-
-	tuple = heap_form_tuple(RelationGetDescr(relation), values, nulls);
-
-	CatalogTupleInsert(relation, tuple);
-
-	/* make dependency entries */
-	myself.classId = CastRelationId;
-	myself.objectId = castid;
-	myself.objectSubId = 0;
-
-	/* dependency on source type */
-	referenced.classId = TypeRelationId;
-	referenced.objectId = sourcetypeid;
-	referenced.objectSubId = 0;
-	recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
-
-	/* dependency on target type */
-	referenced.classId = TypeRelationId;
-	referenced.objectId = targettypeid;
-	referenced.objectSubId = 0;
-	recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
-
-	/* dependency on function */
-	if (OidIsValid(funcid))
-	{
-		referenced.classId = ProcedureRelationId;
-		referenced.objectId = funcid;
-		referenced.objectSubId = 0;
-		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
-	}
-
-	/* dependency on extension */
-	recordDependencyOnCurrentExtension(&myself, false);
-
-	/* Post creation hook for new cast */
-	InvokeObjectPostCreateHook(CastRelationId, castid, 0);
-
-	heap_freetuple(tuple);
-
-	table_close(relation, RowExclusiveLock);
-
+	myself = CastCreate(sourcetypeid, targettypeid, funcid, castcontext,
+						castmethod, DEPENDENCY_NORMAL);
 	return myself;
 }
 
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index 52097363fd..a8dea8d58e 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -42,6 +42,7 @@
 #include "catalog/objectaccess.h"
 #include "catalog/pg_am.h"
 #include "catalog/pg_authid.h"
+#include "catalog/pg_cast.h"
 #include "catalog/pg_collation.h"
 #include "catalog/pg_constraint.h"
 #include "catalog/pg_depend.h"
@@ -85,9 +86,14 @@ typedef struct
 
 /* Potentially set by pg_upgrade_support functions */
 Oid			binary_upgrade_next_array_pg_type_oid = InvalidOid;
+Oid			binary_upgrade_next_mrng_pg_type_oid = InvalidOid;
+Oid			binary_upgrade_next_mrng_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 rangeOid,
+									   Oid rangeArrayOid, Oid *castFuncOid);
 static Oid	findTypeInputFunction(List *procname, Oid typeOid);
 static Oid	findTypeOutputFunction(List *procname, Oid typeOid);
 static Oid	findTypeReceiveFunction(List *procname, Oid typeOid);
@@ -811,7 +817,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",
@@ -1346,6 +1353,11 @@ checkEnumOwner(HeapTuple tup)
 /*
  * DefineRange
  *		Registers a new range type.
+ *
+ * Perhaps it might be worthwhile to set pg_type.typelem to the base type,
+ * and likewise on multiranges to set it to the range type. But having a
+ * non-zero typelem is treated elsewhere as a synonym for being an array,
+ * and users might have queries with that same assumption.
  */
 ObjectAddress
 DefineRange(CreateRangeStmt *stmt)
@@ -1354,7 +1366,11 @@ DefineRange(CreateRangeStmt *stmt)
 	Oid			typeNamespace;
 	Oid			typoid;
 	char	   *rangeArrayName;
+	char	   *multirangeTypeName;
+	char	   *multirangeArrayName;
 	Oid			rangeArrayOid;
+	Oid			multirangeOid;
+	Oid			multirangeArrayOid;
 	Oid			rangeSubtype = InvalidOid;
 	List	   *rangeSubOpclassName = NIL;
 	List	   *rangeCollationName = NIL;
@@ -1371,6 +1387,8 @@ DefineRange(CreateRangeStmt *stmt)
 	AclResult	aclresult;
 	ListCell   *lc;
 	ObjectAddress address;
+	ObjectAddress mltrngaddress;
+	Oid			castFuncOid;
 
 	/* Convert list of names to a name and namespace */
 	typeNamespace = QualifiedNameGetCreationNamespace(stmt->typeName,
@@ -1519,8 +1537,10 @@ DefineRange(CreateRangeStmt *stmt)
 	/* alignment must be 'i' or 'd' for ranges */
 	alignment = (subtypalign == 'd') ? 'd' : 'i';
 
-	/* Allocate OID for array type */
+	/* Allocate OID for array type, its multirange, and its multirange array */
 	rangeArrayOid = AssignTypeArrayOid();
+	multirangeOid = AssignTypeMultirangeOid();
+	multirangeArrayOid = AssignTypeMultirangeArrayOid();
 
 	/* Create the pg_type entry */
 	address =
@@ -1557,9 +1577,46 @@ 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(multirangeOid,	/* force assignment of this 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) */
+	Assert(multirangeOid == mltrngaddress.objectId);
+
 	/* Create the entry in pg_range */
 	RangeCreate(typoid, rangeSubtype, rangeCollation, rangeSubOpclass,
-				rangeCanonical, rangeSubtypeDiff);
+				rangeCanonical, rangeSubtypeDiff, multirangeOid);
 
 	/*
 	 * Create the array type that goes with it.
@@ -1600,8 +1657,53 @@ 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 */
+			   multirangeOid,	/* 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,
+							   multirangeOid, typoid, rangeArrayOid,
+							   &castFuncOid);
+
+	/* Create cast from the range type to its multirange type */
+	CastCreate(typoid, multirangeOid, castFuncOid, 'e', 'f', DEPENDENCY_INTERNAL);
+
+	pfree(multirangeTypeName);
+	pfree(multirangeArrayName);
 
 	return address;
 }
@@ -1679,6 +1781,147 @@ 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.
+ *
+ * Sets castFuncOid to the oid of the new constructor that can be used
+ * to cast from a range to a multirange.
+ */
+static void
+makeMultirangeConstructors(const char *name, Oid namespace,
+						   Oid multirangeOid, Oid rangeOid, Oid rangeArrayOid,
+						   Oid *castFuncOid)
+{
+	ObjectAddress myself,
+				referenced;
+	oidvector  *argtypes;
+	Datum		allParamTypes;
+	ArrayType  *allParameterTypes;
+	Datum		paramModes;
+	ArrayType  *parameterModes;
+
+	referenced.classId = TypeRelationId;
+	referenced.objectId = multirangeOid;
+	referenced.objectSubId = 0;
+
+	/* 0-arg constructor - for empty multiranges */
+	argtypes = buildoidvector(NULL, 0);
+	myself = ProcedureCreate(name,	/* name: same as multirange type */
+							 namespace,
+							 false,	/* replace */
+							 false,	/* returns set */
+							 multirangeOid,	/* return type */
+							 BOOTSTRAP_SUPERUSERID,	/* proowner */
+							 INTERNALlanguageId,	/* language */
+							 F_FMGR_INTERNAL_VALIDATOR,
+							 "multirange_constructor0",	/* prosrc */
+							 NULL,	/* probin */
+							 PROKIND_FUNCTION,
+							 false,	/* security_definer */
+							 false,	/* leakproof */
+							 false, /* isStrict */
+							 PROVOLATILE_IMMUTABLE,	/* volatility */
+							 PROPARALLEL_SAFE,	/* parallel safety */
+							 argtypes,	/* parameterTypes */
+							 PointerGetDatum(NULL),	/* allParameterTypes */
+							 PointerGetDatum(NULL),	/* parameterModes */
+							 PointerGetDatum(NULL),	/* parameterNames */
+							 NIL,	/* parameterDefaults */
+							 PointerGetDatum(NULL),	/* trftypes */
+							 PointerGetDatum(NULL),	/* proconfig */
+							 InvalidOid,	/* prosupport */
+							 1.0,	/* procost */
+							 0.0);	/* prorows */
+	/*
+	 * Make the constructor 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);
+	pfree(argtypes);
+
+	/*
+	 * 1-arg constructor - for casts
+	 *
+	 * In theory we shouldn't need both this and the vararg (n-arg) constructor,
+	 * but having a separate 1-arg function lets us define casts against it.
+	 */
+	argtypes = buildoidvector(&rangeOid, 1);
+	myself = ProcedureCreate(name,	/* name: same as multirange type */
+							 namespace,
+							 false,	/* replace */
+							 false,	/* returns set */
+							 multirangeOid,	/* return type */
+							 BOOTSTRAP_SUPERUSERID,	/* proowner */
+							 INTERNALlanguageId,	/* language */
+							 F_FMGR_INTERNAL_VALIDATOR,
+							 "multirange_constructor1",	/* prosrc */
+							 NULL,	/* probin */
+							 PROKIND_FUNCTION,
+							 false,	/* security_definer */
+							 false,	/* leakproof */
+							 true, /* isStrict */
+							 PROVOLATILE_IMMUTABLE,	/* volatility */
+							 PROPARALLEL_SAFE,	/* parallel safety */
+							 argtypes,	/* parameterTypes */
+							 PointerGetDatum(NULL),	/* allParameterTypes */
+							 PointerGetDatum(NULL),	/* parameterModes */
+							 PointerGetDatum(NULL),	/* parameterNames */
+							 NIL,	/* parameterDefaults */
+							 PointerGetDatum(NULL),	/* trftypes */
+							 PointerGetDatum(NULL),	/* proconfig */
+							 InvalidOid,	/* prosupport */
+							 1.0,	/* procost */
+							 0.0);	/* prorows */
+	/* ditto */
+	recordDependencyOn(&myself, &referenced, DEPENDENCY_INTERNAL);
+	pfree(argtypes);
+	*castFuncOid = myself.objectId;
+
+	/* n-arg constructor - vararg */
+	argtypes = buildoidvector(&rangeArrayOid, 1);
+	allParamTypes = ObjectIdGetDatum(rangeArrayOid);
+	allParameterTypes = construct_array(&allParamTypes,
+										1, OIDOID,
+										sizeof(Oid), true, 'i');
+	paramModes = CharGetDatum(FUNC_PARAM_VARIADIC);
+	parameterModes = construct_array(&paramModes, 1, CHAROID,
+									 1, true, 'c');
+	myself = ProcedureCreate(name,	/* name: same as multirange type */
+							 namespace,
+							 false,	/* replace */
+							 false,	/* returns set */
+							 multirangeOid,	/* return type */
+							 BOOTSTRAP_SUPERUSERID,	/* proowner */
+							 INTERNALlanguageId,	/* language */
+							 F_FMGR_INTERNAL_VALIDATOR,
+							 "multirange_constructor2",	/* prosrc */
+							 NULL,	/* probin */
+							 PROKIND_FUNCTION,
+							 false,	/* security_definer */
+							 false,	/* leakproof */
+							 false, /* isStrict */
+							 PROVOLATILE_IMMUTABLE,	/* volatility */
+							 PROPARALLEL_SAFE,	/* parallel safety */
+							 argtypes,	/* parameterTypes */
+							 PointerGetDatum(allParameterTypes),	/* allParameterTypes */
+							 PointerGetDatum(parameterModes),	/* parameterModes */
+							 PointerGetDatum(NULL),	/* parameterNames */
+							 NIL,	/* parameterDefaults */
+							 PointerGetDatum(NULL),	/* trftypes */
+							 PointerGetDatum(NULL),	/* proconfig */
+							 InvalidOid,	/* prosupport */
+							 1.0,	/* procost */
+							 0.0);	/* prorows */
+	/* ditto */
+	recordDependencyOn(&myself, &referenced, DEPENDENCY_INTERNAL);
+	pfree(argtypes);
+	pfree(allParameterTypes);
+	pfree(parameterModes);
+}
 
 /*
  * Find suitable I/O functions for a type.
@@ -2104,6 +2347,72 @@ AssignTypeArrayOid(void)
 	return type_array_oid;
 }
 
+/*
+ *	AssignTypeMultirangeOid
+ *
+ *	Pre-assign the range type's multirange OID for use in pg_type.oid
+ */
+Oid
+AssignTypeMultirangeOid(void)
+{
+	Oid			type_multirange_oid;
+
+	/* Use binary-upgrade override for pg_type.oid? */
+	if (IsBinaryUpgrade)
+	{
+		if (!OidIsValid(binary_upgrade_next_mrng_pg_type_oid))
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+					 errmsg("pg_type multirange OID value not set when in binary upgrade mode")));
+
+		type_multirange_oid = binary_upgrade_next_mrng_pg_type_oid;
+		binary_upgrade_next_mrng_pg_type_oid = InvalidOid;
+	}
+	else
+	{
+		Relation	pg_type = table_open(TypeRelationId, AccessShareLock);
+
+		type_multirange_oid = GetNewOidWithIndex(pg_type, TypeOidIndexId,
+												 Anum_pg_type_oid);
+		table_close(pg_type, AccessShareLock);
+	}
+
+	return type_multirange_oid;
+}
+
+/*
+ *	AssignTypeMultirangeArrayOid
+ *
+ *	Pre-assign the range type's multirange array OID for use in pg_type.typarray
+ */
+Oid
+AssignTypeMultirangeArrayOid(void)
+{
+	Oid			type_multirange_array_oid;
+
+	/* Use binary-upgrade override for pg_type.oid? */
+	if (IsBinaryUpgrade)
+	{
+		if (!OidIsValid(binary_upgrade_next_mrng_array_pg_type_oid))
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+					 errmsg("pg_type multirange array OID value not set when in binary upgrade mode")));
+
+		type_multirange_array_oid = binary_upgrade_next_mrng_array_pg_type_oid;
+		binary_upgrade_next_mrng_array_pg_type_oid = InvalidOid;
+	}
+	else
+	{
+		Relation	pg_type = table_open(TypeRelationId, AccessShareLock);
+
+		type_multirange_array_oid = GetNewOidWithIndex(pg_type, TypeOidIndexId,
+													   Anum_pg_type_oid);
+		table_close(pg_type, AccessShareLock);
+	}
+
+	return type_multirange_array_oid;
+}
+
 
 /*-------------------------------------------------------------------
  * DefineCompositeType
diff --git a/src/backend/parser/parse_coerce.c b/src/backend/parser/parse_coerce.c
index 929f758ef4..47d17f43a4 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
@@ -1481,6 +1483,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;
@@ -1527,6 +1531,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 */
@@ -1577,6 +1590,39 @@ check_generic_type_consistency(const Oid *actual_arg_types,
 			/* otherwise, they better match */
 			return false;
 		}
+		else
+			range_typelem = InvalidOid;	/* keep compiler quiet */
+	}
+
+	/* 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)
@@ -1680,13 +1726,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.
@@ -1744,7 +1794,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;
@@ -1762,6 +1812,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;
+		}
 	}
 
 	/*
@@ -1812,9 +1882,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
@@ -1846,6 +1919,71 @@ enforce_generic_type_consistency(const Oid *actual_arg_types,
 							   format_type_be(range_typeid),
 							   format_type_be(elem_typeid))));
 		}
+		else
+			range_typelem = InvalidOid;
+	}
+
+	/* 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))
@@ -1855,6 +1993,7 @@ enforce_generic_type_consistency(const Oid *actual_arg_types,
 			elem_typeid = ANYELEMENTOID;
 			array_typeid = ANYARRAYOID;
 			range_typeid = ANYRANGEOID;
+			multirange_typeid = ANYMULTIRANGEOID;
 		}
 		else
 		{
@@ -1927,6 +2066,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;
+			}
 		}
 	}
 
@@ -1958,6 +2108,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 ||
@@ -2006,8 +2172,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);
@@ -2019,11 +2184,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)
 		{
@@ -2051,6 +2242,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)
@@ -2059,6 +2263,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 */
@@ -2173,6 +2445,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..f9dd0378cc
--- /dev/null
+++ b/src/backend/utils/adt/multirangetypes.c
@@ -0,0 +1,2170 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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/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;
+	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 = NULL;
+	int32		range_str_len;
+	char	   *range_str_copy;
+
+	cache = get_multirange_io_data(fcinfo, mltrngtypoid, IOFunc_input);
+	rangetyp = cache->typcache->rngtype;
+
+	/* 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]);
+		uint32		range_len;
+		char	   *range_data;
+
+		range = PointerGetDatum(SendFunctionCall(&cache->proc, range));
+		range_len = VARSIZE(range) - VARHDRSZ;
+		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_constructor2(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));
+}
+
+/*
+ * Construct multirange value from a single range.
+ * It'd be nice if we could just use multirange_constructor2
+ * for this case, but we need a non-variadic single-arg function
+ * to let us define a CAST from a range to its multirange.
+ */
+Datum
+multirange_constructor1(PG_FUNCTION_ARGS)
+{
+	Oid			mltrngtypid = get_fn_expr_rettype(fcinfo->flinfo);
+	Oid			rngtypid;
+	TypeCacheEntry *typcache;
+	TypeCacheEntry *rangetyp;
+	RangeType *range;
+
+	typcache = multirange_get_typcache(fcinfo, mltrngtypid);
+	rangetyp = typcache->rngtype;
+
+	/*
+	 * 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")));
+
+	range = PG_GETARG_RANGE_P(0);
+
+	/* Make sure the range type matches. */
+	rngtypid = RangeTypeGetOid(range);
+	if (rngtypid != rangetyp->type_id)
+		ereport(ERROR, (errmsg("type %u does not match constructor type", rngtypid)));
+
+	PG_RETURN_MULTIRANGE_P(make_multirange(mltrngtypid, rangetyp, 1, &range));
+}
+
+/*
+ * 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 -> multirange type functions */
+
+/* multirange union */
+Datum
+multirange_union(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
+	MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count1;
+	int32		range_count2;
+	int32		range_count3;
+	RangeType **ranges1;
+	RangeType **ranges2;
+	RangeType **ranges3;
+
+	if (MultirangeIsEmpty(mr1))
+		PG_RETURN_MULTIRANGE_P(mr2);
+	if (MultirangeIsEmpty(mr2))
+		PG_RETURN_MULTIRANGE_P(mr1);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr1));
+
+	multirange_deserialize(mr1, &range_count1, &ranges1);
+	multirange_deserialize(mr2, &range_count2, &ranges2);
+
+	range_count3 = range_count1 + range_count2;
+	ranges3 = palloc0(range_count3 * sizeof(RangeType *));
+	memcpy(ranges3, ranges1, range_count1 * sizeof(RangeType *));
+	memcpy(ranges3 + range_count1, ranges2, range_count2 * sizeof(RangeType *));
+	PG_RETURN_MULTIRANGE_P(make_multirange(typcache->type_id, typcache->rngtype,
+										   range_count3, ranges3));
+}
+
+/* multirange minus */
+Datum
+multirange_minus(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
+	MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
+	Oid			mltrngtypoid = MultirangeTypeGetOid(mr1);
+	TypeCacheEntry *typcache;
+	TypeCacheEntry *rangetyp;
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+
+	typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
+	rangetyp = typcache->rngtype;
+
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		PG_RETURN_MULTIRANGE_P(mr1);
+
+	multirange_deserialize(mr1, &range_count1, &ranges1);
+	multirange_deserialize(mr2, &range_count2, &ranges2);
+
+	PG_RETURN_MULTIRANGE_P(multirange_minus_internal(mltrngtypoid,
+													 rangetyp,
+													 range_count1,
+													 ranges1,
+													 range_count2,
+													 ranges2));
+}
+
+MultirangeType *
+multirange_minus_internal(Oid mltrngtypoid, TypeCacheEntry *rangetyp,
+						 int32 range_count1, RangeType **ranges1,
+						 int32 range_count2, RangeType **ranges2)
+{
+	RangeType  *r1;
+	RangeType  *r2;
+	RangeType **ranges3;
+	int32		range_count3;
+	int32		i1;
+	int32		i2;
+
+	/*
+	 * Worst case: every range in ranges1 makes a different cut to some range
+	 * in ranges2.
+	 */
+	ranges3 = palloc0((range_count1 + range_count2) * sizeof(RangeType *));
+	range_count3 = 0;
+
+	/*
+	 * For each range in mr1, keep subtracting until it's gone or the ranges
+	 * in mr2 have passed it. After a subtraction we assign what's left back
+	 * to r1. The parallel progress through mr1 and mr2 is similar to
+	 * multirange_overlaps_multirange_internal.
+	 */
+	r2 = ranges2[0];
+	for (i1 = 0, i2 = 0; i1 < range_count1; i1++)
+	{
+		r1 = ranges1[i1];
+
+		/* Discard r2s while r2 << r1 */
+		while (r2 != NULL && range_before_internal(rangetyp, r2, r1))
+		{
+			r2 = ++i2 >= range_count2 ? NULL : ranges2[i2];
+		}
+
+		while (r2 != NULL)
+		{
+			if (range_split_internal(rangetyp, r1, r2, &ranges3[range_count3], &r1))
+			{
+				/*
+				 * If r2 takes a bite out of the middle of r1, we need two
+				 * outputs
+				 */
+				range_count3++;
+				r2 = ++i2 >= range_count2 ? NULL : ranges2[i2];
+
+			}
+			else if (range_overlaps_internal(rangetyp, r1, r2))
+			{
+				/*
+				 * If r2 overlaps r1, replace r1 with r1 - r2.
+				 */
+				r1 = range_minus_internal(rangetyp, r1, r2);
+
+				/*
+				 * If r2 goes past r1, then we need to stay with it, in case
+				 * it hits future r1s. Otherwise we need to keep r1, in case
+				 * future r2s hit it. Since we already subtracted, there's no
+				 * point in using the overright/overleft calls.
+				 */
+				if (RangeIsEmpty(r1) || range_before_internal(rangetyp, r1, r2))
+					break;
+				else
+					r2 = ++i2 >= range_count2 ? NULL : ranges2[i2];
+
+			}
+			else
+			{
+				/*
+				 * This and all future r2s are past r1, so keep them. Also
+				 * assign whatever is left of r1 to the result.
+				 */
+				break;
+			}
+		}
+
+		/*
+		 * Nothing else can remove anything from r1, so keep it. Even if r1 is
+		 * empty here, make_multirange will remove it.
+		 */
+		ranges3[range_count3++] = r1;
+	}
+
+	return make_multirange(mltrngtypoid, rangetyp, range_count3, ranges3);
+}
+
+/* multirange intersection */
+Datum
+multirange_intersect(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
+	MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
+	Oid			mltrngtypoid = MultirangeTypeGetOid(mr1);
+	TypeCacheEntry *typcache;
+	TypeCacheEntry *rangetyp;
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+
+	typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
+	rangetyp = typcache->rngtype;
+
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		PG_RETURN_MULTIRANGE_P(make_empty_multirange(mltrngtypoid, rangetyp));
+
+	multirange_deserialize(mr1, &range_count1, &ranges1);
+	multirange_deserialize(mr2, &range_count2, &ranges2);
+
+	PG_RETURN_MULTIRANGE_P(multirange_intersect_internal(mltrngtypoid,
+														 rangetyp,
+														 range_count1,
+														 ranges1,
+														 range_count2,
+														 ranges2));
+}
+
+MultirangeType *
+multirange_intersect_internal(Oid mltrngtypoid, TypeCacheEntry *rangetyp,
+							  int32 range_count1, RangeType **ranges1,
+							  int32 range_count2, RangeType **ranges2)
+{
+	RangeType  *r1;
+	RangeType  *r2;
+	RangeType **ranges3;
+	int32		range_count3;
+	int32		i1;
+	int32		i2;
+
+	if (range_count1 == 0 || range_count2 == 0)
+		return make_multirange(mltrngtypoid, rangetyp, 0, NULL);
+
+	/*-----------------------------------------------
+	 * Worst case is a stitching pattern like this:
+	 *
+	 * mr1: --- --- --- ---
+	 * mr2:   --- --- ---
+	 * mr3:   - - - - - -
+	 *
+	 * That seems to be range_count1 + range_count2 - 1,
+	 * but one extra won't hurt.
+	 *-----------------------------------------------
+	 */
+	ranges3 = palloc0((range_count1 + range_count2) * sizeof(RangeType *));
+	range_count3 = 0;
+
+	/*
+	 * For each range in mr1, keep intersecting until the ranges in mr2 have
+	 * passed it. The parallel progress through mr1 and mr2 is similar to
+	 * multirange_minus_multirange_internal, but we don't have to assign back
+	 * to r1.
+	 */
+	r2 = ranges2[0];
+	for (i1 = 0, i2 = 0; i1 < range_count1; i1++)
+	{
+		r1 = ranges1[i1];
+
+		/* Discard r2s while r2 << r1 */
+		while (r2 != NULL && range_before_internal(rangetyp, r2, r1))
+		{
+			r2 = ++i2 >= range_count2 ? NULL : ranges2[i2];
+		}
+
+		while (r2 != NULL)
+		{
+			if (range_overlaps_internal(rangetyp, r1, r2))
+			{
+				/* Keep the overlapping part */
+				ranges3[range_count3++] = range_intersect_internal(rangetyp, r1, r2);
+
+				/* If we "used up" all of r2, go to the next one... */
+				if (range_overleft_internal(rangetyp, r2, r1))
+					r2 = ++i2 >= range_count2 ? NULL : ranges2[i2];
+
+				/* ...otherwise go to the next r1 */
+				else
+					break;
+			}
+			else
+				/* We're past r1, so move to the next one */
+				break;
+		}
+
+		/* If we're out of r2s, there can be no more intersections */
+		if (r2 == NULL)
+			break;
+	}
+
+	return make_multirange(mltrngtypoid, rangetyp, range_count3, ranges3);
+}
+
+/*
+ * range_agg_transfn: combine adjacent/overlapping ranges.
+ *
+ * All we do here is gather the input ranges into an array
+ * so that the finalfn can sort and combine them.
+ */
+Datum
+range_agg_transfn(PG_FUNCTION_ARGS)
+{
+	MemoryContext aggContext;
+	Oid			rngtypoid;
+	ArrayBuildState *state;
+
+	if (!AggCheckCallContext(fcinfo, &aggContext))
+		elog(ERROR, "range_agg_transfn called in non-aggregate context");
+
+	rngtypoid = get_fn_expr_argtype(fcinfo->flinfo, 1);
+	if (!type_is_range(rngtypoid))
+		ereport(ERROR, (errmsg("range_agg must be called with a range")));
+
+	if (PG_ARGISNULL(0))
+		state = initArrayResult(rngtypoid, aggContext, false);
+	else
+		state = (ArrayBuildState *) PG_GETARG_POINTER(0);
+
+	/* skip NULLs */
+	if (!PG_ARGISNULL(1))
+		accumArrayResult(state, PG_GETARG_DATUM(1), false, rngtypoid, aggContext);
+
+	PG_RETURN_POINTER(state);
+}
+
+/*
+ * range_agg_finalfn: use our internal array to merge touching ranges.
+ */
+Datum
+range_agg_finalfn(PG_FUNCTION_ARGS)
+{
+	MemoryContext aggContext;
+	Oid			mltrngtypoid;
+	TypeCacheEntry *typcache;
+	ArrayBuildState *state;
+	int32		range_count;
+	RangeType **ranges;
+	int			i;
+
+	if (!AggCheckCallContext(fcinfo, &aggContext))
+		elog(ERROR, "range_agg_finalfn called in non-aggregate context");
+
+	state = PG_ARGISNULL(0) ? NULL : (ArrayBuildState *) PG_GETARG_POINTER(0);
+	if (state == NULL)
+		/* This shouldn't be possible, but just in case.... */
+		PG_RETURN_NULL();
+
+	/* Also return NULL if we had zero inputs, like other aggregates */
+	range_count = state->nelems;
+	if (range_count == 0)
+		PG_RETURN_NULL();
+
+	mltrngtypoid = get_fn_expr_rettype(fcinfo->flinfo);
+	typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
+
+	ranges = palloc0(range_count * sizeof(RangeType *));
+	for (i = 0; i < range_count; i++)
+		ranges[i] = DatumGetRangeTypeP(state->dvalues[i]);
+
+	PG_RETURN_MULTIRANGE_P(make_multirange(mltrngtypoid, typcache->rngtype, range_count, ranges));
+}
+
+Datum
+multirange_intersect_agg_transfn(PG_FUNCTION_ARGS)
+{
+	MemoryContext aggContext;
+	Oid			mltrngtypoid;
+	TypeCacheEntry *typcache;
+	MultirangeType *result;
+	MultirangeType *current;
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+
+	if (!AggCheckCallContext(fcinfo, &aggContext))
+		elog(ERROR, "multirange_intersect_agg_transfn called in non-aggregate context");
+
+	mltrngtypoid = get_fn_expr_argtype(fcinfo->flinfo, 1);
+	if (!type_is_multirange(mltrngtypoid))
+		ereport(ERROR, (errmsg("range_intersect_agg must be called with a multirange")));
+
+	typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
+
+	/* strictness ensures these are non-null */
+	result = PG_GETARG_MULTIRANGE_P(0);
+	current = PG_GETARG_MULTIRANGE_P(1);
+
+	multirange_deserialize(result, &range_count1, &ranges1);
+	multirange_deserialize(current, &range_count2, &ranges2);
+
+	result = multirange_intersect_internal(mltrngtypoid,
+										   typcache->rngtype,
+										   range_count1,
+										   ranges1,
+										   range_count2,
+										   ranges2);
+	PG_RETURN_RANGE_P(result);
+}
+
+
+/* multirange -> element type functions */
+
+/* extract lower bound value */
+Datum
+multirange_lower(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+	bool		isnull;
+	Datum		result;
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_NULL();
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	result = range_lower_internal(typcache->rngtype, ranges[0], &isnull);
+
+	if (isnull)
+		PG_RETURN_NULL();
+	else
+		PG_RETURN_DATUM(result);
+}
+
+/* extract upper bound value */
+Datum
+multirange_upper(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+	bool		isnull;
+	Datum		result;
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_NULL();
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	result = range_upper_internal(typcache->rngtype, ranges[range_count - 1], &isnull);
+
+	if (isnull)
+		PG_RETURN_NULL();
+	else
+		PG_RETURN_DATUM(result);
+}
+
+
+/* multirange -> bool functions */
+
+/* is multirange empty? */
+Datum
+multirange_empty(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+
+	PG_RETURN_BOOL(mr->rangeCount == 0);
+}
+
+/* is lower bound inclusive? */
+Datum
+multirange_lower_inc(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	int32		range_count;
+	RangeType **ranges;
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_has_flag(ranges[0], RANGE_LB_INC));
+}
+
+/* is upper bound inclusive? */
+Datum
+multirange_upper_inc(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	int32		range_count;
+	RangeType **ranges;
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_has_flag(ranges[range_count - 1], RANGE_UB_INC));
+}
+
+/* is lower bound infinite? */
+Datum
+multirange_lower_inf(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	int32		range_count;
+	RangeType **ranges;
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_has_flag(ranges[0], RANGE_LB_INF));
+}
+
+/* is upper bound infinite? */
+Datum
+multirange_upper_inf(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	int32		range_count;
+	RangeType **ranges;
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_has_flag(ranges[range_count - 1], RANGE_UB_INF));
+}
+
+
+
+/* multirange, element -> bool functions */
+
+/* contains? */
+Datum
+multirange_contains_elem(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	Datum		val = PG_GETARG_DATUM(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(multirange_contains_elem_internal(typcache, mr, val));
+}
+
+/* contained by? */
+Datum
+elem_contained_by_multirange(PG_FUNCTION_ARGS)
+{
+	Datum		val = PG_GETARG_DATUM(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(multirange_contains_elem_internal(typcache, mr, val));
+}
+
+/*
+ * Test whether multirange mr contains a specific element value.
+ */
+bool
+multirange_contains_elem_internal(TypeCacheEntry *typcache, MultirangeType * mr, Datum val)
+{
+	TypeCacheEntry *rangetyp;
+	int32		range_count;
+	RangeType **ranges;
+	RangeType  *r;
+	int			i;
+
+	rangetyp = typcache->rngtype;
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	for (i = 0; i < range_count; i++)
+	{
+		r = ranges[i];
+		if (range_contains_elem_internal(rangetyp, r, val))
+			return true;
+	}
+
+	return false;
+}
+
+/* multirange, range -> bool functions */
+
+/* contains? */
+Datum
+multirange_contains_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(multirange_contains_range_internal(typcache, mr, r));
+}
+
+/* contained by? */
+Datum
+range_contained_by_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(multirange_contains_range_internal(typcache, mr, r));
+}
+
+/*
+ * Test whether multirange mr contains a specific range r.
+ */
+bool
+multirange_contains_range_internal(TypeCacheEntry *typcache, MultirangeType * mr, RangeType *r)
+{
+	TypeCacheEntry *rangetyp;
+	int32		range_count;
+	RangeType **ranges;
+	RangeType  *mrr;
+	int			i;
+
+	rangetyp = typcache->rngtype;
+
+	/*
+	 * Every multirange contains an infinite number of empty ranges, even an
+	 * empty one.
+	 */
+	if (RangeIsEmpty(r))
+		return true;
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	for (i = 0; i < range_count; i++)
+	{
+		mrr = ranges[i];
+		if (range_contains_internal(rangetyp, mrr, r))
+			return true;
+	}
+
+	return false;
+}
+
+
+/* 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));
+}
+
+/* overlaps? */
+Datum
+range_overlaps_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(range_overlaps_multirange_internal(typcache, r, mr));
+}
+
+Datum
+multirange_overlaps_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(range_overlaps_multirange_internal(typcache, r, mr));
+}
+
+Datum
+multirange_overlaps_multirange(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_overlaps_multirange_internal(typcache, mr1, mr2));
+}
+
+bool
+range_overlaps_multirange_internal(TypeCacheEntry *typcache, RangeType *r, MultirangeType * mr)
+{
+	TypeCacheEntry *rangetyp;
+	int32		range_count;
+	int32		i;
+	RangeType **ranges;
+	RangeType  *mrr;
+
+	/*
+	 * Empties never overlap, even with empties. (This seems strange since
+	 * they *do* contain each other, but we want to follow how ranges work.)
+	 */
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		return false;
+
+	rangetyp = typcache->rngtype;
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	/* Scan through mrr and once it gets to r they either overlap or not */
+	mrr = ranges[0];
+
+	/* Discard mrrs while mrr << r */
+	i = 0;
+	while (range_before_internal(rangetyp, mrr, r))
+	{
+		if (++i >= range_count)
+			return false;
+		mrr = ranges[i];
+	}
+
+	/* Now either we overlap or we passed r */
+	return range_overlaps_internal(rangetyp, mrr, r);
+}
+
+bool
+multirange_overlaps_multirange_internal(TypeCacheEntry *typcache, MultirangeType * mr1,
+										MultirangeType * mr2)
+{
+	TypeCacheEntry *rangetyp;
+	int32		range_count1;
+	int32		range_count2;
+	int32		i1;
+	int32		i2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+	RangeType  *r1;
+	RangeType  *r2;
+
+	/*
+	 * Empties never overlap, even with empties. (This seems strange since
+	 * they *do* contain each other, but we want to follow how ranges work.)
+	 */
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		return false;
+
+	rangetyp = typcache->rngtype;
+
+	multirange_deserialize(mr1, &range_count1, &ranges1);
+	multirange_deserialize(mr2, &range_count2, &ranges2);
+
+	/*
+	 * Every range in mr1 gets a chance to overlap with the ranges in mr2, but
+	 * we can use their ordering to avoid O(n^2). This is similar to
+	 * range_overlaps_multirange where r1 : r2 :: mrr : r, but there if we
+	 * don't find an overlap with r we're done, and here if we don't find an
+	 * overlap with r2 we try the next r2.
+	 */
+	r1 = ranges1[0];
+	for (i1 = 0, i2 = 0; i2 < range_count2; i2++)
+	{
+		r2 = ranges2[i2];
+
+		/* Discard r1s while r1 << r2 */
+		while (range_before_internal(rangetyp, r1, r2))
+		{
+			if (++i1 >= range_count1)
+				return false;
+			r1 = ranges1[i1];
+		}
+
+		/*
+		 * If r1 && r2, we're done, otherwise we failed to find an overlap for
+		 * r2, so go to the next one.
+		 */
+		if (range_overlaps_internal(rangetyp, r1, r2))
+			return true;
+	}
+
+	/* We looked through all of mr2 without finding an overlap */
+	return false;
+}
+
+/* does not extend to right of? */
+Datum
+range_overleft_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_overleft_internal(typcache->rngtype, r, ranges[range_count - 1]));
+}
+
+Datum
+multirange_overleft_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_overleft_internal(typcache->rngtype, ranges[range_count - 1], r));
+}
+
+Datum
+multirange_overleft_multirange(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
+	MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr1));
+
+	multirange_deserialize(mr1, &range_count1, &ranges1);
+	multirange_deserialize(mr2, &range_count2, &ranges2);
+
+	PG_RETURN_BOOL(range_overleft_internal(typcache->rngtype, ranges1[range_count1 - 1], ranges2[range_count2 - 1]));
+}
+
+/* does not extend to left of? */
+Datum
+range_overright_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_overright_internal(typcache->rngtype, r, ranges[0]));
+}
+
+Datum
+multirange_overright_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_overright_internal(typcache->rngtype, ranges[0], r));
+}
+
+Datum
+multirange_overright_multirange(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
+	MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr1));
+
+	multirange_deserialize(mr1, &range_count1, &ranges1);
+	multirange_deserialize(mr2, &range_count2, &ranges2);
+
+	PG_RETURN_BOOL(range_overright_internal(typcache->rngtype, ranges1[0], ranges2[0]));
+}
+
+/* contains? */
+Datum
+multirange_contains_multirange(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_contains_multirange_internal(typcache, mr1, mr2));
+}
+
+/* contained by? */
+Datum
+multirange_contained_by_multirange(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_contains_multirange_internal(typcache, mr2, mr1));
+}
+
+/*
+ * Test whether multirange mr1 contains every range from another multirange mr2.
+ */
+bool
+multirange_contains_multirange_internal(TypeCacheEntry *typcache,
+										MultirangeType * mr1, MultirangeType * mr2)
+{
+	TypeCacheEntry *rangetyp;
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+	RangeType  *r1;
+	RangeType  *r2;
+	int			i1,
+				i2;
+
+	rangetyp = typcache->rngtype;
+
+	multirange_deserialize(mr1, &range_count1, &ranges1);
+	multirange_deserialize(mr2, &range_count2, &ranges2);
+
+	/*
+	 * We follow the same logic for empties as ranges: - an empty multirange
+	 * contains an empty range/multirange. - an empty multirange can't contain
+	 * any other range/multirange. - an empty multirange is contained by any
+	 * other range/multirange.
+	 */
+
+	if (range_count2 == 0)
+		return true;
+	if (range_count1 == 0)
+		return false;
+
+	/*
+	 * Every range in mr2 must be contained by some range in mr1. To avoid
+	 * O(n^2) we walk through both ranges in tandem.
+	 */
+	r1 = ranges1[0];
+	for (i1 = 0, i2 = 0; i2 < range_count2; i2++)
+	{
+		r2 = ranges2[i2];
+
+		/* Discard r1s while r1 << r2 */
+		while (range_before_internal(rangetyp, r1, r2))
+		{
+			if (++i1 >= range_count1)
+				return false;
+			r1 = ranges1[i1];
+		}
+
+		/*
+		 * If r1 @> r2, go to the next r2, otherwise return false (since every
+		 * r1[n] and r1[n+1] must have a gap). Note this will give weird
+		 * answers if you don't canonicalize, e.g. with a custom
+		 * int2multirange {[1,1], [2,2]} there is a "gap". But that is
+		 * consistent with other range operators, e.g. '[1,1]'::int2range -|-
+		 * '[2,2]'::int2range is false.
+		 */
+		if (!range_contains_internal(rangetyp, r1, r2))
+			return false;
+	}
+
+	/* All ranges in mr2 are satisfied */
+	return true;
+}
+
+/* strictly left of? */
+Datum
+range_before_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(range_before_multirange_internal(typcache, r, mr));
+}
+
+Datum
+multirange_before_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(range_after_multirange_internal(typcache, r, mr));
+}
+
+Datum
+multirange_before_multirange(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_before_multirange_internal(typcache, mr1, mr2));
+}
+
+/* strictly right of? */
+Datum
+range_after_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(range_after_multirange_internal(typcache, r, mr));
+}
+
+Datum
+multirange_after_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(range_before_multirange_internal(typcache, r, mr));
+}
+
+Datum
+multirange_after_multirange(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_before_multirange_internal(typcache, mr2, mr1));
+}
+
+/* strictly left of? (internal version) */
+bool
+range_before_multirange_internal(TypeCacheEntry *typcache, RangeType *r,
+								 MultirangeType * mr)
+{
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		return false;
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	return range_before_internal(typcache->rngtype, r, ranges[0]);
+}
+
+bool
+multirange_before_multirange_internal(TypeCacheEntry *typcache, MultirangeType * mr1,
+									  MultirangeType * mr2)
+{
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		return false;
+
+	multirange_deserialize(mr1, &range_count1, &ranges1);
+	multirange_deserialize(mr2, &range_count2, &ranges2);
+
+	return range_before_internal(typcache->rngtype, ranges1[range_count1 - 1],
+								 ranges2[0]);
+}
+
+/* strictly right of? (internal version) */
+bool
+range_after_multirange_internal(TypeCacheEntry *typcache, RangeType *r,
+								MultirangeType * mr)
+{
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		return false;
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	return range_after_internal(typcache->rngtype, r, ranges[range_count - 1]);
+}
+
+/* adjacent to? */
+Datum
+range_adjacent_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		return false;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_adjacent_internal(typcache->rngtype, r, ranges[0]));
+}
+
+Datum
+multirange_adjacent_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		return false;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_adjacent_internal(typcache->rngtype, ranges[range_count - 1], r));
+}
+
+Datum
+multirange_adjacent_multirange(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
+	MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		return false;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr1));
+
+	multirange_deserialize(mr1, &range_count1, &ranges1);
+	multirange_deserialize(mr2, &range_count2, &ranges2);
+
+	PG_RETURN_BOOL(range_adjacent_internal(typcache->rngtype, ranges1[range_count1 - 1], ranges2[0]));
+}
+
+/* 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);
+}
+
+/* multirange -> range functions */
+
+/* Find the smallest range that includes everything in the multirange */
+Datum
+range_merge_from_multirange(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	Oid			mltrngtypoid = MultirangeTypeGetOid(mr);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_RANGE_P(make_empty_range(typcache->rngtype));
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_RANGE_P(range_union_internal(typcache->rngtype, ranges[0],
+										   ranges[range_count - 1], false));
+}
+
+/* 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/pg_upgrade_support.c b/src/backend/utils/adt/pg_upgrade_support.c
index 0d9e55cdcf..51b9ac907c 100644
--- a/src/backend/utils/adt/pg_upgrade_support.c
+++ b/src/backend/utils/adt/pg_upgrade_support.c
@@ -51,6 +51,28 @@ binary_upgrade_set_next_array_pg_type_oid(PG_FUNCTION_ARGS)
 	PG_RETURN_VOID();
 }
 
+Datum
+binary_upgrade_set_next_multirange_pg_type_oid(PG_FUNCTION_ARGS)
+{
+	Oid			typoid = PG_GETARG_OID(0);
+
+	CHECK_IS_BINARY_UPGRADE;
+	binary_upgrade_next_mrng_pg_type_oid = typoid;
+
+	PG_RETURN_VOID();
+}
+
+Datum
+binary_upgrade_set_next_multirange_array_pg_type_oid(PG_FUNCTION_ARGS)
+{
+	Oid			typoid = PG_GETARG_OID(0);
+
+	CHECK_IS_BINARY_UPGRADE;
+	binary_upgrade_next_mrng_array_pg_type_oid = typoid;
+
+	PG_RETURN_VOID();
+}
+
 Datum
 binary_upgrade_set_next_toast_pg_type_oid(PG_FUNCTION_ARGS)
 {
diff --git a/src/backend/utils/adt/pseudotypes.c b/src/backend/utils/adt/pseudotypes.c
index ad0e363c70..152e77f44c 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 b95132b714..37b3f40499 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
 {
@@ -431,19 +429,37 @@ range_lower(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
 	TypeCacheEntry *typcache;
+	bool		isnull;
+	Datum		result;
+
+	typcache = range_get_typcache(fcinfo, RangeTypeGetOid(r1));
+
+	result = range_lower_internal(typcache, r1, &isnull);
+
+	if (isnull)
+		PG_RETURN_NULL();
+	else
+		PG_RETURN_DATUM(result);
+}
+
+Datum
+range_lower_internal(TypeCacheEntry *typcache, const RangeType *r1, bool *isnull)
+{
 	RangeBound	lower;
 	RangeBound	upper;
 	bool		empty;
 
-	typcache = range_get_typcache(fcinfo, RangeTypeGetOid(r1));
-
 	range_deserialize(typcache, r1, &lower, &upper, &empty);
 
 	/* Return NULL if there's no finite lower bound */
 	if (empty || lower.infinite)
-		PG_RETURN_NULL();
+	{
+		*isnull = true;
+		return 0;
+	}
 
-	PG_RETURN_DATUM(lower.val);
+	*isnull = false;
+	return lower.val;
 }
 
 /* extract upper bound value */
@@ -452,19 +468,37 @@ range_upper(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
 	TypeCacheEntry *typcache;
+	bool		isnull;
+	Datum		result;
+
+	typcache = range_get_typcache(fcinfo, RangeTypeGetOid(r1));
+
+	result = range_upper_internal(typcache, r1, &isnull);
+
+	if (isnull)
+		PG_RETURN_NULL();
+	else
+		PG_RETURN_DATUM(result);
+}
+
+Datum
+range_upper_internal(TypeCacheEntry *typcache, const RangeType *r1, bool *isnull)
+{
 	RangeBound	lower;
 	RangeBound	upper;
 	bool		empty;
 
-	typcache = range_get_typcache(fcinfo, RangeTypeGetOid(r1));
-
 	range_deserialize(typcache, r1, &lower, &upper, &empty);
 
 	/* Return NULL if there's no finite upper bound */
 	if (empty || upper.infinite)
-		PG_RETURN_NULL();
+	{
+		*isnull = true;
+		return 0;
+	}
 
-	PG_RETURN_DATUM(upper.val);
+	*isnull = false;
+	return upper.val;
 }
 
 
@@ -475,9 +509,8 @@ Datum
 range_empty(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
-	char		flags = range_get_flags(r1);
 
-	PG_RETURN_BOOL(flags & RANGE_EMPTY);
+	PG_RETURN_BOOL(range_has_flag(r1, RANGE_EMPTY));
 }
 
 /* is lower bound inclusive? */
@@ -485,9 +518,8 @@ Datum
 range_lower_inc(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
-	char		flags = range_get_flags(r1);
 
-	PG_RETURN_BOOL(flags & RANGE_LB_INC);
+	PG_RETURN_BOOL(range_has_flag(r1, RANGE_LB_INC));
 }
 
 /* is upper bound inclusive? */
@@ -495,9 +527,8 @@ Datum
 range_upper_inc(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
-	char		flags = range_get_flags(r1);
 
-	PG_RETURN_BOOL(flags & RANGE_UB_INC);
+	PG_RETURN_BOOL(range_has_flag(r1, RANGE_UB_INC));
 }
 
 /* is lower bound infinite? */
@@ -505,9 +536,8 @@ Datum
 range_lower_inf(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
-	char		flags = range_get_flags(r1);
 
-	PG_RETURN_BOOL(flags & RANGE_LB_INF);
+	PG_RETURN_BOOL(range_has_flag(r1, RANGE_LB_INF));
 }
 
 /* is upper bound infinite? */
@@ -515,9 +545,8 @@ Datum
 range_upper_inf(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
-	char		flags = range_get_flags(r1);
 
-	PG_RETURN_BOOL(flags & RANGE_UB_INF);
+	PG_RETURN_BOOL(range_has_flag(r1, RANGE_UB_INF));
 }
 
 
@@ -957,7 +986,25 @@ range_minus(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
 	RangeType  *r2 = PG_GETARG_RANGE_P(1);
+	RangeType  *ret;
 	TypeCacheEntry *typcache;
+
+	/* 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));
+
+	ret = range_minus_internal(typcache, r1, r2);
+	if (ret)
+		PG_RETURN_RANGE_P(ret);
+	else
+		PG_RETURN_NULL();
+}
+
+RangeType *
+range_minus_internal(TypeCacheEntry *typcache, RangeType *r1, RangeType *r2)
+{
 	RangeBound	lower1,
 				lower2;
 	RangeBound	upper1,
@@ -969,18 +1016,12 @@ range_minus(PG_FUNCTION_ARGS)
 				cmp_u1l2,
 				cmp_u1u2;
 
-	/* 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));
-
 	range_deserialize(typcache, r1, &lower1, &upper1, &empty1);
 	range_deserialize(typcache, r2, &lower2, &upper2, &empty2);
 
 	/* if either is empty, r1 is the correct answer */
 	if (empty1 || empty2)
-		PG_RETURN_RANGE_P(r1);
+		return r1;
 
 	cmp_l1l2 = range_cmp_bounds(typcache, &lower1, &lower2);
 	cmp_l1u2 = range_cmp_bounds(typcache, &lower1, &upper2);
@@ -993,34 +1034,34 @@ range_minus(PG_FUNCTION_ARGS)
 				 errmsg("result of range difference would not be contiguous")));
 
 	if (cmp_l1u2 > 0 || cmp_u1l2 < 0)
-		PG_RETURN_RANGE_P(r1);
+		return r1;
 
 	if (cmp_l1l2 >= 0 && cmp_u1u2 <= 0)
-		PG_RETURN_RANGE_P(make_empty_range(typcache));
+		return make_empty_range(typcache);
 
 	if (cmp_l1l2 <= 0 && cmp_u1l2 >= 0 && cmp_u1u2 <= 0)
 	{
 		lower2.inclusive = !lower2.inclusive;
 		lower2.lower = false;	/* it will become the upper bound */
-		PG_RETURN_RANGE_P(make_range(typcache, &lower1, &lower2, false));
+		return make_range(typcache, &lower1, &lower2, false);
 	}
 
 	if (cmp_l1l2 >= 0 && cmp_u1u2 >= 0 && cmp_l1u2 <= 0)
 	{
 		upper2.inclusive = !upper2.inclusive;
 		upper2.lower = true;	/* it will become the lower bound */
-		PG_RETURN_RANGE_P(make_range(typcache, &upper2, &upper1, false));
+		return make_range(typcache, &upper2, &upper1, false);
 	}
 
 	elog(ERROR, "unexpected case in range_minus");
-	PG_RETURN_NULL();
+	return NULL;
 }
 
 /*
  * Set union.  If strict is true, it is an error that the two input ranges
  * are not adjacent or overlapping.
  */
-static RangeType *
+RangeType *
 range_union_internal(TypeCacheEntry *typcache, RangeType *r1, RangeType *r2,
 					 bool strict)
 {
@@ -1101,6 +1142,19 @@ range_intersect(PG_FUNCTION_ARGS)
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
 	RangeType  *r2 = PG_GETARG_RANGE_P(1);
 	TypeCacheEntry *typcache;
+
+	/* 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));
+
+	PG_RETURN_RANGE_P(range_intersect_internal(typcache, r1, r2));
+}
+
+RangeType *
+range_intersect_internal(TypeCacheEntry *typcache, const RangeType *r1, const RangeType *r2)
+{
 	RangeBound	lower1,
 				lower2;
 	RangeBound	upper1,
@@ -1110,17 +1164,11 @@ range_intersect(PG_FUNCTION_ARGS)
 	RangeBound *result_lower;
 	RangeBound *result_upper;
 
-	/* 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));
-
 	range_deserialize(typcache, r1, &lower1, &upper1, &empty1);
 	range_deserialize(typcache, r2, &lower2, &upper2, &empty2);
 
-	if (empty1 || empty2 || !DatumGetBool(range_overlaps(fcinfo)))
-		PG_RETURN_RANGE_P(make_empty_range(typcache));
+	if (empty1 || empty2 || !range_overlaps_internal(typcache, r1, r2))
+		return make_empty_range(typcache);
 
 	if (range_cmp_bounds(typcache, &lower1, &lower2) >= 0)
 		result_lower = &lower1;
@@ -1132,9 +1180,81 @@ range_intersect(PG_FUNCTION_ARGS)
 	else
 		result_upper = &upper2;
 
-	PG_RETURN_RANGE_P(make_range(typcache, result_lower, result_upper, false));
+	return make_range(typcache, result_lower, result_upper, false);
 }
 
+/* range, range -> range, range functions */
+
+/*
+ * range_split_internal - if r2 intersects the middle of r1, leaving non-empty
+ * ranges on both sides, then return true and set output1 and output2 to the
+ * results of r1 - r2 (in order). Otherwise return false and don't set output1
+ * or output2. Neither input range should be empty.
+ */
+bool
+range_split_internal(TypeCacheEntry *typcache, const RangeType *r1, const RangeType *r2,
+					 RangeType **output1, RangeType **output2)
+{
+	RangeBound	lower1,
+				lower2;
+	RangeBound	upper1,
+				upper2;
+	bool		empty1,
+				empty2;
+
+	range_deserialize(typcache, r1, &lower1, &upper1, &empty1);
+	range_deserialize(typcache, r2, &lower2, &upper2, &empty2);
+
+	if (range_cmp_bounds(typcache, &lower1, &lower2) < 0 &&
+		range_cmp_bounds(typcache, &upper1, &upper2) > 0)
+	{
+		/*
+		 * Need to invert inclusive/exclusive for the lower2 and upper2
+		 * points. They can't be infinite though. We're allowed to overwrite
+		 * these RangeBounds since they only exist locally.
+		 */
+		lower2.inclusive = !lower2.inclusive;
+		lower2.lower = false;
+		upper2.inclusive = !upper2.inclusive;
+		upper2.lower = true;
+
+		*output1 = make_range(typcache, &lower1, &lower2, false);
+		*output2 = make_range(typcache, &upper2, &upper1, false);
+		return true;
+	}
+
+	return false;
+}
+
+/* range -> range aggregate functions */
+
+Datum
+range_intersect_agg_transfn(PG_FUNCTION_ARGS)
+{
+	MemoryContext aggContext;
+	Oid			rngtypoid;
+	TypeCacheEntry *typcache;
+	RangeType  *result;
+	RangeType  *current;
+
+	if (!AggCheckCallContext(fcinfo, &aggContext))
+		elog(ERROR, "range_intersect_agg_transfn called in non-aggregate context");
+
+	rngtypoid = get_fn_expr_argtype(fcinfo->flinfo, 1);
+	if (!type_is_range(rngtypoid))
+		ereport(ERROR, (errmsg("range_intersect_agg must be called with a range")));
+
+	typcache = range_get_typcache(fcinfo, rngtypoid);
+
+	/* strictness ensures these are non-null */
+	result = PG_GETARG_RANGE_P(0);
+	current = PG_GETARG_RANGE_P(1);
+
+	result = range_intersect_internal(typcache, result, current);
+	PG_RETURN_RANGE_P(result);
+}
+
+
 /* Btree support */
 
 /* btree comparator */
@@ -1144,12 +1264,6 @@ range_cmp(PG_FUNCTION_ARGS)
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
 	RangeType  *r2 = PG_GETARG_RANGE_P(1);
 	TypeCacheEntry *typcache;
-	RangeBound	lower1,
-				lower2;
-	RangeBound	upper1,
-				upper2;
-	bool		empty1,
-				empty2;
 	int			cmp;
 
 	check_stack_depth();		/* recurses when subtype is a range type */
@@ -1160,6 +1274,28 @@ range_cmp(PG_FUNCTION_ARGS)
 
 	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, const RangeType *r1, const 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);
 
@@ -1177,10 +1313,7 @@ range_cmp(PG_FUNCTION_ARGS)
 			cmp = range_cmp_bounds(typcache, &upper1, &upper2);
 	}
 
-	PG_FREE_IF_COPY(r1, 0);
-	PG_FREE_IF_COPY(r2, 1);
-
-	PG_RETURN_INT32(cmp);
+	return cmp;
 }
 
 /* inequality operators using the range_cmp function */
@@ -1218,13 +1351,10 @@ range_gt(PG_FUNCTION_ARGS)
 
 /* Hash support */
 
-/* hash a range value */
-Datum
-hash_range(PG_FUNCTION_ARGS)
+uint32
+hash_range_internal(TypeCacheEntry *typcache, const RangeType *r)
 {
-	RangeType  *r = PG_GETARG_RANGE_P(0);
 	uint32		result;
-	TypeCacheEntry *typcache;
 	TypeCacheEntry *scache;
 	RangeBound	lower;
 	RangeBound	upper;
@@ -1233,10 +1363,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 +1404,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, const RangeType *r, Datum seed)
+{
+	uint64		result;
 	TypeCacheEntry *scache;
 	RangeBound	lower;
 	RangeBound	upper;
@@ -1300,10 +1433,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 +1471,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));
 }
 
 /*
@@ -1769,6 +1916,21 @@ range_get_flags(const RangeType *range)
 	return *((char *) range + VARSIZE(range) - 1);
 }
 
+/*
+ * range_has_flag: set whether a range has a specific flag.
+ *
+ * This lets expose some of our functions that just check flags
+ * to the rest of the code base (like to multiranges)
+ * without writing full-fledged *_internal versions.
+ */
+bool
+range_has_flag(const RangeType *r1, char flag)
+{
+	char		flags = range_get_flags(r1);
+
+	return flags & flag;
+}
+
 /*
  * range_set_contain_empty: set the RANGE_CONTAIN_EMPTY bit in the value.
  *
@@ -1937,6 +2099,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 fb0599f7f1..56a145c666 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
  *
@@ -3176,6 +3186,58 @@ get_range_collation(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->rngmultitypid;
+		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 53d9ddf159..fb9179c42a 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_rngmultitypid,
+			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 cdf6331a97..6677bc8bdc 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 b7eee3da1d..defc801f7a 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,131 @@ resolve_polymorphic_tupdesc(TupleDesc tupdesc, oidvector *declared_args,
 				return false;
 			anyelement_type = subtype;
 		}
+
+		if (OidIsValid(anymultirange_type))
+		{
+			Oid			rngtype;
+			Oid			subtype;
+
+			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;
+			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;
+			Oid			subtype;
+
+			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;
+
+			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;
+			Oid			mltrngtype;
+			Oid			rngtype;
+
+			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;
+
+			mltrngtype = resolve_generic_type(ANYMULTIRANGEOID,
+											  anyrange_type,
+											  ANYRANGEOID);
+
+			/* check for inconsistent range and multirange results */
+			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 +761,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 +793,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 +856,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 +880,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 +906,132 @@ resolve_polymorphic_argtypes(int numargs, Oid *argtypes, char *argmodes,
 				return false;
 			anyelement_type = subtype;
 		}
+
+		if (OidIsValid(anymultirange_type))
+		{
+			Oid			rngtype;
+			Oid			subtype;
+
+			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;
+
+			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;
+			Oid			subtype;
+
+			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;
+
+			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;
+			Oid			mltrngtype;
+			Oid			rngtype;
+
+			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;
+
+			mltrngtype = resolve_generic_type(ANYMULTIRANGEOID,
+											  anyrange_type,
+											  ANYRANGEOID);
+
+			/* check for inconsistent range and multirange results */
+			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 +1051,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/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index ef1539044f..1c01434c11 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -274,7 +274,8 @@ static void dumpSearchPath(Archive *AH);
 static void binary_upgrade_set_type_oids_by_type_oid(Archive *fout,
 													 PQExpBuffer upgrade_buffer,
 													 Oid pg_type_oid,
-													 bool force_array_type);
+													 bool force_array_type,
+													 bool include_multirange_type);
 static bool binary_upgrade_set_type_oids_by_rel_oid(Archive *fout,
 													PQExpBuffer upgrade_buffer, Oid pg_rel_oid);
 static void binary_upgrade_set_pg_class_oids(Archive *fout,
@@ -1584,7 +1585,7 @@ selectDumpableType(TypeInfo *tyinfo, Archive *fout)
 	}
 
 	/* skip auto-generated array types */
-	if (tyinfo->isArray)
+	if (tyinfo->isArray || tyinfo->isMultirange)
 	{
 		tyinfo->dobj.objType = DO_DUMMY_TYPE;
 
@@ -4291,15 +4292,49 @@ dumpSubscription(Archive *fout, SubscriptionInfo *subinfo)
 	free(qsubname);
 }
 
+static Oid
+get_next_possible_free_pg_type_oid(Archive *fout, PQExpBuffer upgrade_query)
+{
+	/*
+	 * If the old version didn't assign an array type, but the new version
+	 * does, we must select an unused type OID to assign.  This currently only
+	 * happens for domains, when upgrading pre-v11 to v11 and up.
+	 *
+	 * Note: local state here is kind of ugly, but we must have some, since we
+	 * mustn't choose the same unused OID more than once.
+	 */
+	static Oid	next_possible_free_oid = FirstNormalObjectId;
+	PGresult   *res;
+	bool		is_dup;
+
+	do
+	{
+		++next_possible_free_oid;
+		printfPQExpBuffer(upgrade_query,
+						  "SELECT EXISTS(SELECT 1 "
+						  "FROM pg_catalog.pg_type "
+						  "WHERE oid = '%u'::pg_catalog.oid);",
+						  next_possible_free_oid);
+		res = ExecuteSqlQueryForSingleRow(fout, upgrade_query->data);
+		is_dup = (PQgetvalue(res, 0, 0)[0] == 't');
+		PQclear(res);
+	} while (is_dup);
+
+	return next_possible_free_oid;
+}
+
 static void
 binary_upgrade_set_type_oids_by_type_oid(Archive *fout,
 										 PQExpBuffer upgrade_buffer,
 										 Oid pg_type_oid,
-										 bool force_array_type)
+										 bool force_array_type,
+										 bool include_multirange_type)
 {
 	PQExpBuffer upgrade_query = createPQExpBuffer();
 	PGresult   *res;
 	Oid			pg_type_array_oid;
+	Oid			pg_type_multirange_oid;
+	Oid			pg_type_multirange_array_oid;
 
 	appendPQExpBufferStr(upgrade_buffer, "\n-- For binary upgrade, must preserve pg_type oid\n");
 	appendPQExpBuffer(upgrade_buffer,
@@ -4320,33 +4355,7 @@ binary_upgrade_set_type_oids_by_type_oid(Archive *fout,
 	PQclear(res);
 
 	if (!OidIsValid(pg_type_array_oid) && force_array_type)
-	{
-		/*
-		 * If the old version didn't assign an array type, but the new version
-		 * does, we must select an unused type OID to assign.  This currently
-		 * only happens for domains, when upgrading pre-v11 to v11 and up.
-		 *
-		 * Note: local state here is kind of ugly, but we must have some,
-		 * since we mustn't choose the same unused OID more than once.
-		 */
-		static Oid	next_possible_free_oid = FirstNormalObjectId;
-		bool		is_dup;
-
-		do
-		{
-			++next_possible_free_oid;
-			printfPQExpBuffer(upgrade_query,
-							  "SELECT EXISTS(SELECT 1 "
-							  "FROM pg_catalog.pg_type "
-							  "WHERE oid = '%u'::pg_catalog.oid);",
-							  next_possible_free_oid);
-			res = ExecuteSqlQueryForSingleRow(fout, upgrade_query->data);
-			is_dup = (PQgetvalue(res, 0, 0)[0] == 't');
-			PQclear(res);
-		} while (is_dup);
-
-		pg_type_array_oid = next_possible_free_oid;
-	}
+		pg_type_array_oid = get_next_possible_free_pg_type_oid(fout, upgrade_query);
 
 	if (OidIsValid(pg_type_array_oid))
 	{
@@ -4357,6 +4366,46 @@ binary_upgrade_set_type_oids_by_type_oid(Archive *fout,
 						  pg_type_array_oid);
 	}
 
+	/*
+	 * Pre-set the multirange type oid and its own array type oid.
+	 */
+	if (include_multirange_type)
+	{
+		if (fout->remoteVersion >= 130000)
+		{
+			appendPQExpBuffer(upgrade_query,
+							  "SELECT t.oid, t.typarray "
+							  "FROM pg_catalog.pg_type t "
+							  "JOIN pg_catalog.pg_range r "
+							  "ON t.oid = r.rngmultitypid "
+							  "WHERE r.rngtypid = '%u'::pg_catalog.oid;",
+							  pg_type_oid);
+
+			res = ExecuteSqlQueryForSingleRow(fout, upgrade_query->data);
+
+			pg_type_multirange_oid = atooid(PQgetvalue(res, 0, PQfnumber(res, "oid")));
+			pg_type_multirange_array_oid = atooid(PQgetvalue(res, 0, PQfnumber(res, "typarray")));
+
+			PQclear(res);
+		}
+		else
+		{
+			pg_type_multirange_oid = get_next_possible_free_pg_type_oid(fout, upgrade_query);
+			pg_type_multirange_array_oid = get_next_possible_free_pg_type_oid(fout, upgrade_query);
+		}
+
+		appendPQExpBufferStr(upgrade_buffer,
+							 "\n-- For binary upgrade, must preserve multirange pg_type oid\n");
+		appendPQExpBuffer(upgrade_buffer,
+						  "SELECT pg_catalog.binary_upgrade_set_next_multirange_pg_type_oid('%u'::pg_catalog.oid);\n\n",
+						  pg_type_multirange_oid);
+		appendPQExpBufferStr(upgrade_buffer,
+							 "\n-- For binary upgrade, must preserve multirange pg_type array oid\n");
+		appendPQExpBuffer(upgrade_buffer,
+						  "SELECT pg_catalog.binary_upgrade_set_next_multirange_array_pg_type_oid('%u'::pg_catalog.oid);\n\n",
+						  pg_type_multirange_array_oid);
+	}
+
 	destroyPQExpBuffer(upgrade_query);
 }
 
@@ -4390,7 +4439,7 @@ binary_upgrade_set_type_oids_by_rel_oid(Archive *fout,
 	pg_type_oid = atooid(PQgetvalue(upgrade_res, 0, PQfnumber(upgrade_res, "crel")));
 
 	binary_upgrade_set_type_oids_by_type_oid(fout, upgrade_buffer,
-											 pg_type_oid, false);
+											 pg_type_oid, false, false);
 
 	if (!PQgetisnull(upgrade_res, 0, PQfnumber(upgrade_res, "trel")))
 	{
@@ -4939,6 +4988,11 @@ getTypes(Archive *fout, int *numTypes)
 		else
 			tyinfo[i].isArray = false;
 
+		if (tyinfo[i].typtype == 'm')
+			tyinfo[i].isMultirange = true;
+		else
+			tyinfo[i].isMultirange = false;
+
 		/* Decide whether we want to dump it */
 		selectDumpableType(&tyinfo[i], fout);
 
@@ -8053,9 +8107,12 @@ getProcLangs(Archive *fout, int *numProcLangs)
 
 /*
  * getCasts
- *	  get basic information about every cast in the system
+ *	  get basic information about most casts in the system
  *
  * numCasts is set to the number of casts read in
+ *
+ * Skip casts from a range to its multirange, since we'll create those
+ * automatically.
  */
 CastInfo *
 getCasts(Archive *fout, int *numCasts)
@@ -8073,7 +8130,20 @@ getCasts(Archive *fout, int *numCasts)
 	int			i_castcontext;
 	int			i_castmethod;
 
-	if (fout->remoteVersion >= 80400)
+	if (fout->remoteVersion >= 130000)
+	{
+		appendPQExpBufferStr(query, "SELECT tableoid, oid, "
+							 "castsource, casttarget, castfunc, castcontext, "
+							 "castmethod "
+							 "FROM pg_cast c "
+							 "WHERE NOT EXISTS ( "
+							 "SELECT 1 FROM pg_range r "
+							 "WHERE c.castsource = r.rngtypid "
+							 "AND c.casttarget = r.rngmultitypid "
+							 ") "
+							 "ORDER BY 3,4");
+	}
+	else if (fout->remoteVersion >= 80400)
 	{
 		appendPQExpBufferStr(query, "SELECT tableoid, oid, "
 							 "castsource, casttarget, castfunc, castcontext, "
@@ -10261,7 +10331,7 @@ dumpEnumType(Archive *fout, TypeInfo *tyinfo)
 	if (dopt->binary_upgrade)
 		binary_upgrade_set_type_oids_by_type_oid(fout, q,
 												 tyinfo->dobj.catId.oid,
-												 false);
+												 false, false);
 
 	appendPQExpBuffer(q, "CREATE TYPE %s AS ENUM (",
 					  qualtypname);
@@ -10387,7 +10457,7 @@ dumpRangeType(Archive *fout, TypeInfo *tyinfo)
 	if (dopt->binary_upgrade)
 		binary_upgrade_set_type_oids_by_type_oid(fout, q,
 												 tyinfo->dobj.catId.oid,
-												 false);
+												 false, true);
 
 	appendPQExpBuffer(q, "CREATE TYPE %s AS RANGE (",
 					  qualtypname);
@@ -10493,7 +10563,7 @@ dumpUndefinedType(Archive *fout, TypeInfo *tyinfo)
 	if (dopt->binary_upgrade)
 		binary_upgrade_set_type_oids_by_type_oid(fout, q,
 												 tyinfo->dobj.catId.oid,
-												 false);
+												 false, false);
 
 	appendPQExpBuffer(q, "CREATE TYPE %s;\n",
 					  qualtypname);
@@ -10698,7 +10768,7 @@ dumpBaseType(Archive *fout, TypeInfo *tyinfo)
 	if (dopt->binary_upgrade)
 		binary_upgrade_set_type_oids_by_type_oid(fout, q,
 												 tyinfo->dobj.catId.oid,
-												 false);
+												 false, false);
 
 	appendPQExpBuffer(q,
 					  "CREATE TYPE %s (\n"
@@ -10885,7 +10955,8 @@ dumpDomain(Archive *fout, TypeInfo *tyinfo)
 	if (dopt->binary_upgrade)
 		binary_upgrade_set_type_oids_by_type_oid(fout, q,
 												 tyinfo->dobj.catId.oid,
-												 true); /* force array type */
+												 true,		/* force array type */
+												 false);	/* force multirange type */
 
 	qtypname = pg_strdup(fmtId(tyinfo->dobj.name));
 	qualtypname = pg_strdup(fmtQualifiedDumpable(tyinfo));
@@ -11073,7 +11144,7 @@ dumpCompositeType(Archive *fout, TypeInfo *tyinfo)
 	{
 		binary_upgrade_set_type_oids_by_type_oid(fout, q,
 												 tyinfo->dobj.catId.oid,
-												 false);
+												 false, false);
 		binary_upgrade_set_pg_class_oids(fout, q, tyinfo->typrelid, false);
 	}
 
@@ -11347,7 +11418,7 @@ dumpShellType(Archive *fout, ShellTypeInfo *stinfo)
 	if (dopt->binary_upgrade)
 		binary_upgrade_set_type_oids_by_type_oid(fout, q,
 												 stinfo->baseType->dobj.catId.oid,
-												 false);
+												 false, false);
 
 	appendPQExpBuffer(q, "CREATE TYPE %s;\n",
 					  fmtQualifiedDumpable(stinfo));
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 21004e5078..63e0ba7116 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -175,6 +175,7 @@ typedef struct _typeInfo
 	char		typrelkind;		/* 'r', 'v', 'c', etc */
 	char		typtype;		/* 'b', 'c', etc */
 	bool		isArray;		/* true if auto-generated array type */
+	bool		isMultirange;	/* true if auto-generated multirange type */
 	bool		isDefined;		/* true if typisdefined */
 	/* If needed, we'll create a "shell type" entry for it; link that here: */
 	struct _shellTypeInfo *shellType;	/* shell-type entry, or NULL */
diff --git a/src/include/access/tupmacs.h b/src/include/access/tupmacs.h
index 0b205a0dc6..3c113fa3bd 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/binary_upgrade.h b/src/include/catalog/binary_upgrade.h
index 12d94fe1b3..01d95117cd 100644
--- a/src/include/catalog/binary_upgrade.h
+++ b/src/include/catalog/binary_upgrade.h
@@ -16,6 +16,8 @@
 
 extern PGDLLIMPORT Oid binary_upgrade_next_pg_type_oid;
 extern PGDLLIMPORT Oid binary_upgrade_next_array_pg_type_oid;
+extern PGDLLIMPORT Oid binary_upgrade_next_mrng_pg_type_oid;
+extern PGDLLIMPORT Oid binary_upgrade_next_mrng_array_pg_type_oid;
 extern PGDLLIMPORT Oid binary_upgrade_next_toast_pg_type_oid;
 
 extern PGDLLIMPORT Oid binary_upgrade_next_heap_pg_class_oid;
diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h
index 8be303870f..566f9364c5 100644
--- a/src/include/catalog/indexing.h
+++ b/src/include/catalog/indexing.h
@@ -327,6 +327,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_rngmultitypid_index, 8001, on pg_range using btree(rngmultitypid 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_aggregate.dat b/src/include/catalog/pg_aggregate.dat
index ffabe275c0..03bab74253 100644
--- a/src/include/catalog/pg_aggregate.dat
+++ b/src/include/catalog/pg_aggregate.dat
@@ -538,6 +538,17 @@
   aggtransfn => 'bytea_string_agg_transfn',
   aggfinalfn => 'bytea_string_agg_finalfn', aggtranstype => 'internal' },
 
+# range
+{ aggfnoid => 'range_intersect_agg(anyrange)',
+  aggtransfn => 'range_intersect_agg_transfn',
+  aggcombinefn => 'range_intersect_agg_transfn', aggtranstype => 'anyrange' },
+{ aggfnoid => 'range_intersect_agg(anymultirange)',
+  aggtransfn => 'multirange_intersect_agg_transfn',
+  aggcombinefn => 'multirange_intersect_agg_transfn', aggtranstype => 'anymultirange' },
+{ aggfnoid => 'range_agg(anyrange)', aggtransfn => 'range_agg_transfn',
+  aggfinalfn => 'range_agg_finalfn', aggfinalextra => 't',
+  aggtranstype => 'internal' },
+
 # json
 { aggfnoid => 'json_agg', aggtransfn => 'json_agg_transfn',
   aggfinalfn => 'json_agg_finalfn', aggtranstype => 'internal' },
diff --git a/src/include/catalog/pg_amop.dat b/src/include/catalog/pg_amop.dat
index 11aaa519c8..827ac4f03c 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 75c0152b66..cf0cac5641 100644
--- a/src/include/catalog/pg_amproc.dat
+++ b/src/include/catalog/pg_amproc.dat
@@ -285,6 +285,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' },
 
@@ -445,6 +447,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',
@@ -1263,12 +1270,13 @@
   amprocrighttype => 'anyrange', amprocnum => '4',
   amproc => 'brin_inclusion_union' },
 { amprocfamily => 'brin/range_inclusion_ops', amproclefttype => 'anyrange',
-  amprocrighttype => 'anyrange', amprocnum => '11', amproc => 'range_merge' },
+  amprocrighttype => 'anyrange', amprocnum => '11',
+  amproc => 'range_merge(anyrange,anyrange)' },
 { amprocfamily => 'brin/range_inclusion_ops', amproclefttype => 'anyrange',
   amprocrighttype => 'anyrange', amprocnum => '13',
   amproc => 'range_contains' },
 { amprocfamily => 'brin/range_inclusion_ops', amproclefttype => 'anyrange',
-  amprocrighttype => 'anyrange', amprocnum => '14', amproc => 'isempty' },
+  amprocrighttype => 'anyrange', amprocnum => '14', amproc => 'isempty(anyrange)' },
 
 # minmax pg_lsn
 { amprocfamily => 'brin/pg_lsn_minmax_ops', amproclefttype => 'pg_lsn',
diff --git a/src/include/catalog/pg_cast.dat b/src/include/catalog/pg_cast.dat
index 6ef8b8a4e7..14277c8fdf 100644
--- a/src/include/catalog/pg_cast.dat
+++ b/src/include/catalog/pg_cast.dat
@@ -512,4 +512,17 @@
 { castsource => 'jsonb', casttarget => 'float8', castfunc => 'float8(jsonb)',
   castcontext => 'e', castmethod => 'f' },
 
+# range to multirange
+{ castsource => 'int4range', casttarget => 'int4multirange', castfunc => 'int4multirange(int4range)',
+  castcontext => 'e', castmethod => 'f' },
+{ castsource => 'int8range', casttarget => 'int8multirange', castfunc => 'int8multirange(int8range)',
+  castcontext => 'e', castmethod => 'f' },
+{ castsource => 'numrange', casttarget => 'nummultirange', castfunc => 'nummultirange(numrange)',
+  castcontext => 'e', castmethod => 'f' },
+{ castsource => 'daterange', casttarget => 'datemultirange', castfunc => 'datemultirange(daterange)',
+  castcontext => 'e', castmethod => 'f' },
+{ castsource => 'tsrange', casttarget => 'tsmultirange', castfunc => 'tsmultirange(tsrange)',
+  castcontext => 'e', castmethod => 'f' },
+{ castsource => 'tstzrange', casttarget => 'tstzmultirange', castfunc => 'tstzmultirange(tstzrange)',
+  castcontext => 'e', castmethod => 'f' },
 ]
diff --git a/src/include/catalog/pg_cast.h b/src/include/catalog/pg_cast.h
index 1b81b52df6..f23ba28d9c 100644
--- a/src/include/catalog/pg_cast.h
+++ b/src/include/catalog/pg_cast.h
@@ -23,6 +23,8 @@
 #include "catalog/genbki.h"
 #include "catalog/pg_cast_d.h"
 
+#include "catalog/dependency.h"
+
 /* ----------------
  *		pg_cast definition.  cpp turns this into
  *		typedef struct FormData_pg_cast
@@ -87,4 +89,11 @@ typedef enum CoercionMethod
 
 #endif							/* EXPOSE_TO_CLIENT_CODE */
 
+extern ObjectAddress CastCreate(Oid sourcetypeid,
+								Oid targettypeid,
+								Oid funcid,
+								char castcontext,
+								char castmethod,
+								DependencyType behavior);
+
 #endif							/* PG_CAST_H */
diff --git a/src/include/catalog/pg_opclass.dat b/src/include/catalog/pg_opclass.dat
index ab2f50c9eb..eebebbcdb4 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 7c135da3b1..24f4a32203 100644
--- a/src/include/catalog/pg_operator.dat
+++ b/src/include/catalog/pg_operator.dat
@@ -3297,5 +3297,174 @@
   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' },
+{ oid => '8073', oid_symbol => 'OID_RANGE_OVERLAPS_MULTIRANGE_OP',
+  descr => 'overlaps',
+  oprname => '&&', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '&&(anymultirange,anyrange)',
+  oprcode => 'range_overlaps_multirange', oprrest => 'areasel',
+  oprjoin => 'areajoinsel' },
+{ oid => '8074', oid_symbol => 'OID_MULTIRANGE_OVERLAPS_RANGE_OP', descr => 'contains',
+  oprname => '&&', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcom => '&&(anyrange,anymultirange)',
+  oprcode => 'multirange_overlaps_range', oprrest => 'areasel',
+  oprjoin => 'areajoinsel' },
+{ oid => '8075', oid_symbol => 'OID_MULTIRANGE_OVERLAPS_MULTIRANGE_OP', descr => 'contains',
+  oprname => '&&', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '&&(anymultirange,anymultirange)',
+  oprcode => 'multirange_overlaps_multirange', oprrest => 'areasel',
+  oprjoin => 'areajoinsel' },
+{ oid => '8064', oid_symbol => 'OID_MULTIRANGE_CONTAINS_ELEM_OP',
+  descr => 'contains',
+  oprname => '@>', oprleft => 'anymultirange', oprright => 'anyelement',
+  oprresult => 'bool', oprcom => '<@(anyelement,anymultirange)',
+  oprcode => 'multirange_contains_elem', oprrest => 'contsel',
+  oprjoin => 'contjoinsel' },
+{ oid => '8065', oid_symbol => 'OID_MULTIRANGE_CONTAINS_RANGE_OP', descr => 'contains',
+  oprname => '@>', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcom => '<@(anyrange,anymultirange)',
+  oprcode => 'multirange_contains_range', oprrest => 'contsel',
+  oprjoin => 'contjoinsel' },
+{ oid => '8066', oid_symbol => 'OID_MULTIRANGE_CONTAINS_MULTIRANGE_OP', descr => 'contains',
+  oprname => '@>', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '<@(anymultirange,anymultirange)',
+  oprcode => 'multirange_contains_multirange', oprrest => 'contsel',
+  oprjoin => 'contjoinsel' },
+{ oid => '8067', oid_symbol => 'OID_MULTIRANGE_ELEM_CONTAINED_OP',
+  descr => 'is contained by',
+  oprname => '<@', oprleft => 'anyelement', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '@>(anymultirange,anyelement)',
+  oprcode => 'elem_contained_by_multirange', oprrest => 'contsel',
+  oprjoin => 'contjoinsel' },
+{ oid => '8068', oid_symbol => 'OID_MULTIRANGE_RANGE_CONTAINED_OP',
+  descr => 'is contained by',
+  oprname => '<@', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '@>(anymultirange,anyrange)',
+  oprcode => 'range_contained_by_multirange', oprrest => 'contsel',
+  oprjoin => 'contjoinsel' },
+{ oid => '8069', oid_symbol => 'OID_MULTIRANGE_MULTIRANGE_CONTAINED_OP',
+  descr => 'is contained by',
+  oprname => '<@', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '@>(anymultirange,anymultirange)',
+  oprcode => 'multirange_contained_by_multirange', oprrest => 'contsel',
+  oprjoin => 'contjoinsel' },
+{ oid => '8106', oid_symbol => 'OID_RANGE_OVERLAPS_LEFT_MULTIRANGE_OP',
+  descr => 'overlaps or is left of',
+  oprname => '&<', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcode => 'range_overleft_multirange', oprrest => 'scalarltsel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8107', oid_symbol => 'OID_MULTIRANGE_OVERLAPS_LEFT_RANGE_OP',
+  descr => 'overlaps or is left of',
+  oprname => '&<', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcode => 'multirange_overleft_range', oprrest => 'scalarltsel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8108', oid_symbol => 'OID_MULTIRANGE_OVERLAPS_LEFT_MULTIRANGE_OP',
+  descr => 'overlaps or is left of',
+  oprname => '&<', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcode => 'multirange_overleft_multirange', oprrest => 'scalarltsel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8109', oid_symbol => 'OID_RANGE_OVERLAPS_RIGHT_MULTIRANGE_OP',
+  descr => 'overlaps or is right of',
+  oprname => '&>', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcode => 'range_overright_multirange', oprrest => 'scalargtsel',
+  oprjoin => 'scalargtjoinsel' },
+{ oid => '8110', oid_symbol => 'OID_MULTIRANGE_OVERLAPS_RIGHT_RANGE_OP',
+  descr => 'overlaps or is right of',
+  oprname => '&>', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcode => 'multirange_overright_range', oprrest => 'scalargtsel',
+  oprjoin => 'scalargtjoinsel' },
+{ oid => '8111', oid_symbol => 'OID_MULTIRANGE_OVERLAPS_RIGHT_MULTIRANGE_OP',
+  descr => 'overlaps or is right of',
+  oprname => '&>', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcode => 'multirange_overright_multirange', oprrest => 'scalargtsel',
+  oprjoin => 'scalargtjoinsel' },
+{ oid => '8091', oid_symbol => 'OID_RANGE_ADJACENT_MULTIRANGE_OP', descr => 'is adjacent to',
+  oprname => '-|-', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '-|-(anymultirange,anyrange)',
+  oprcode => 'range_adjacent_multirange', oprrest => 'scalarltsel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8092', oid_symbol => 'OID_MULTIRANGE_ADJACENT_RANGE_OP', descr => 'is adjacent to',
+  oprname => '-|-', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcom => '-|-(anyrange,anymultirange)',
+  oprcode => 'multirange_adjacent_range', oprrest => 'scalarltsel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8093', oid_symbol => 'OID_MULTIRANGE_ADJACENT_MULTIRANGE_OP', descr => 'is adjacent to',
+  oprname => '-|-', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '-|-(anymultirange,anymultirange)',
+  oprcode => 'multirange_adjacent_multirange', oprrest => 'scalarltsel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8117', descr => 'multirange union',
+  oprname => '+', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'anymultirange', oprcom => '+(anymultirange,anymultirange)',
+  oprcode => 'multirange_union' },
+{ oid => '8123', descr => 'multirange minus',
+  oprname => '-', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'anymultirange', oprcode => 'multirange_minus' },
+{ oid => '8129', descr => 'multirange intersect',
+  oprname => '*', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'anymultirange', oprcom => '*(anymultirange,anymultirange)',
+  oprcode => 'multirange_intersect' },
+{ oid => '8082', oid_symbol => 'OID_RANGE_BEFORE_MULTIRANGE_OP', descr => 'is left of',
+  oprname => '<<', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '>>(anymultirange,anyrange)',
+  oprcode => 'range_before_multirange', oprrest => 'scalarltsel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8083', oid_symbol => 'OID_MULTIRANGE_BEFORE_RANGE_OP', descr => 'is left of',
+  oprname => '<<', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcom => '>>(anyrange,anymultirange)',
+  oprcode => 'multirange_before_range', oprrest => 'scalarltsel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8084', oid_symbol => 'OID_MULTIRANGE_BEFORE_MULTIRANGE_OP', descr => 'is left of',
+  oprname => '<<', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '>>(anymultirange,anymultirange)',
+  oprcode => 'multirange_before_multirange', oprrest => 'scalarltsel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8085', oid_symbol => 'OID_RANGE_AFTER_MULTIRANGE_OP', descr => 'is right of',
+  oprname => '>>', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '<<(anymultirange,anyrange)',
+  oprcode => 'range_after_multirange', oprrest => 'scalargtsel',
+  oprjoin => 'scalargtjoinsel' },
+{ oid => '8086', oid_symbol => 'OID_MULTIRANGE_AFTER_RANGE_OP', descr => 'is right of',
+  oprname => '>>', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcom => '<<(anyrange,anymultirange)',
+  oprcode => 'multirange_after_range', oprrest => 'scalargtsel',
+  oprjoin => 'scalargtjoinsel' },
+{ oid => '8087', oid_symbol => 'OID_MULTIRANGE_AFTER_MULTIRANGE_OP', descr => 'is right of',
+  oprname => '>>', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '<<(anymultirange,anymultirange)',
+  oprcode => 'multirange_after_multirange', oprrest => 'scalargtsel',
+  oprjoin => 'scalargtjoinsel' },
 
 ]
diff --git a/src/include/catalog/pg_opfamily.dat b/src/include/catalog/pg_opfamily.dat
index 26227df216..901fd7b303 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 07a86c7b7b..d89b3313bf 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -9649,6 +9649,10 @@
   descr => 'the smallest range which includes both of the given ranges',
   proname => 'range_merge', prorettype => 'anyrange',
   proargtypes => 'anyrange anyrange', prosrc => 'range_merge' },
+{ oid => '8143',
+  descr => 'the smallest range which includes the whole multirange',
+  proname => 'range_merge', prorettype => 'anyrange',
+  proargtypes => 'anymultirange', prosrc => 'range_merge_from_multirange' },
 { oid => '3868',
   proname => 'range_intersect', prorettype => 'anyrange',
   proargtypes => 'anyrange anyrange', prosrc => 'range_intersect' },
@@ -9698,6 +9702,13 @@
 { oid => '3169', descr => 'restriction selectivity for range operators',
   proname => 'rangesel', provolatile => 's', prorettype => 'float8',
   proargtypes => 'internal oid internal int4', prosrc => 'rangesel' },
+{ oid => '8133', descr => 'range aggregate by intersecting',
+  proname => 'range_intersect_agg_transfn', prorettype => 'anyrange',
+  proargtypes => 'anyrange anyrange', prosrc => 'range_intersect_agg_transfn'},
+{ oid => '8134', descr => 'range aggregate by intersecting',
+  proname => 'range_intersect_agg', prokind => 'a', proisstrict => 'f',
+  prorettype => 'anyrange', proargtypes => 'anyrange',
+  prosrc => 'aggregate_dummy'},
 
 { oid => '3914', descr => 'convert an int4 range to canonical form',
   proname => 'int4range_canonical', prorettype => 'int4range',
@@ -9766,6 +9777,258 @@
   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 => '8094', descr => 'lower bound of multirange',
+  proname => 'lower', prorettype => 'anyelement', proargtypes => 'anymultirange',
+  prosrc => 'multirange_lower' },
+{ oid => '8095', descr => 'upper bound of multirange',
+  proname => 'upper', prorettype => 'anyelement', proargtypes => 'anymultirange',
+  prosrc => 'multirange_upper' },
+{ oid => '8057', descr => 'is the multirange empty?',
+  proname => 'isempty', prorettype => 'bool',
+  proargtypes => 'anymultirange', prosrc => 'multirange_empty' },
+{ oid => '8096', descr => 'is the multirange\'s lower bound inclusive?',
+  proname => 'lower_inc', prorettype => 'bool', proargtypes => 'anymultirange',
+  prosrc => 'multirange_lower_inc' },
+{ oid => '8097', descr => 'is the multirange\'s upper bound inclusive?',
+  proname => 'upper_inc', prorettype => 'bool', proargtypes => 'anymultirange',
+  prosrc => 'multirange_upper_inc' },
+{ oid => '8098', descr => 'is the multirange\'s lower bound infinite?',
+  proname => 'lower_inf', prorettype => 'bool', proargtypes => 'anymultirange',
+  prosrc => 'multirange_lower_inf' },
+{ oid => '8099', descr => 'is the multirange\'s upper bound infinite?',
+  proname => 'upper_inf', prorettype => 'bool', proargtypes => 'anymultirange',
+  prosrc => 'multirange_upper_inf' },
+{ oid => '8008', descr => 'multirange typanalyze',
+  proname => 'multirange_typanalyze', provolatile => 's', prorettype => 'bool',
+  proargtypes => 'internal', prosrc => 'multirange_typanalyze' },
+{ oid => '8033',
+  proname => 'multirange_eq', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_eq' },
+{ oid => '8034',
+  proname => 'multirange_ne', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_ne' },
+{ oid => '8070',
+  proname => 'range_overlaps_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_overlaps_multirange' },
+{ oid => '8071',
+  proname => 'multirange_overlaps_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_overlaps_range' },
+{ oid => '8072',
+  proname => 'multirange_overlaps_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_overlaps_multirange' },
+{ oid => '8058',
+  proname => 'multirange_contains_elem', prorettype => 'bool',
+  proargtypes => 'anymultirange anyelement', prosrc => 'multirange_contains_elem' },
+{ oid => '8059',
+  proname => 'multirange_contains_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_contains_range' },
+{ oid => '8060',
+  proname => 'multirange_contains_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_contains_multirange' },
+{ oid => '8061',
+  proname => 'elem_contained_by_multirange', prorettype => 'bool',
+  proargtypes => 'anyelement anymultirange', prosrc => 'elem_contained_by_multirange' },
+{ oid => '8062',
+  proname => 'range_contained_by_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_contained_by_multirange' },
+{ oid => '8063',
+  proname => 'multirange_contained_by_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_contained_by_multirange' },
+{ oid => '8088',
+  proname => 'range_adjacent_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_adjacent_multirange' },
+{ oid => '8089',
+  proname => 'multirange_adjacent_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_adjacent_multirange' },
+{ oid => '8090',
+  proname => 'multirange_adjacent_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_adjacent_range' },
+{ oid => '8076',
+  proname => 'range_before_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_before_multirange' },
+{ oid => '8077',
+  proname => 'multirange_before_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_before_range' },
+{ oid => '8078',
+  proname => 'multirange_before_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_before_multirange' },
+{ oid => '8079',
+  proname => 'range_after_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_after_multirange' },
+{ oid => '8080',
+  proname => 'multirange_after_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_after_range' },
+{ oid => '8081',
+  proname => 'multirange_after_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_after_multirange' },
+{ oid => '8100',
+  proname => 'range_overleft_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_overleft_multirange' },
+{ oid => '8101',
+  proname => 'multirange_overleft_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_overleft_range' },
+{ oid => '8102',
+  proname => 'multirange_overleft_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_overleft_multirange' },
+{ oid => '8103',
+  proname => 'range_overright_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_overright_multirange' },
+{ oid => '8104',
+  proname => 'multirange_overright_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_overright_range' },
+{ oid => '8105',
+  proname => 'multirange_overright_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_overright_multirange' },
+{ oid => '8114',
+  proname => 'multirange_union', prorettype => 'anymultirange',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_union' },
+{ oid => '8120',
+  proname => 'multirange_minus', prorettype => 'anymultirange',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_minus' },
+{ oid => '8126',
+  proname => 'multirange_intersect', prorettype => 'anymultirange',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_intersect' },
+{ oid => '8022', descr => 'less-equal-greater',
+  proname => 'multirange_cmp', prorettype => 'int4',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_cmp' },
+{ oid => '8023',
+  proname => 'multirange_lt', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_lt' },
+{ oid => '8024',
+  proname => 'multirange_le', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_le' },
+{ oid => '8025',
+  proname => 'multirange_ge', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_ge' },
+{ oid => '8026',
+  proname => 'multirange_gt', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_gt' },
+{ oid => '8038', descr => 'hash a multirange',
+  proname => 'hash_multirange', prorettype => 'int4', proargtypes => 'anymultirange',
+  prosrc => 'hash_multirange' },
+{ oid => '8039', descr => 'hash a multirange',
+  proname => 'hash_multirange_extended', prorettype => 'int8',
+  proargtypes => 'anymultirange int8', prosrc => 'hash_multirange_extended' },
+
+{ oid => '8045', descr => 'int4multirange constructor',
+  proname => 'int4multirange', proisstrict => 'f',
+  prorettype => 'int4multirange', proargtypes => '',
+  prosrc => 'multirange_constructor0' },
+{ oid => '8146', descr => 'int4multirange constructor',
+  proname => 'int4multirange', proisstrict => 't',
+  prorettype => 'int4multirange', proargtypes => 'int4range',
+  prosrc => 'multirange_constructor1' },
+{ oid => '8046', descr => 'int4multirange constructor',
+  proname => 'int4multirange', provariadic => 'int4range', proisstrict => 'f',
+  prorettype => 'int4multirange', proargtypes => '_int4range',
+  proallargtypes => '{_int4range}', proargmodes => '{v}',
+  prosrc => 'multirange_constructor2' },
+{ oid => '8047', descr => 'nummultirange constructor',
+  proname => 'nummultirange', proisstrict => 'f',
+  prorettype => 'nummultirange', proargtypes => '',
+  prosrc => 'multirange_constructor0' },
+{ oid => '8147', descr => 'nummultirange constructor',
+  proname => 'nummultirange', proisstrict => 't',
+  prorettype => 'nummultirange', proargtypes => 'numrange',
+  prosrc => 'multirange_constructor1' },
+{ oid => '8048', descr => 'nummultirange constructor',
+  proname => 'nummultirange', provariadic => 'numrange', proisstrict => 'f',
+  prorettype => 'nummultirange', proargtypes => '_numrange',
+  proallargtypes => '{_numrange}', proargmodes => '{v}',
+  prosrc => 'multirange_constructor2' },
+{ oid => '8049', descr => 'tsmultirange constructor',
+  proname => 'tsmultirange', proisstrict => 'f',
+  prorettype => 'tsmultirange', proargtypes => '',
+  prosrc => 'multirange_constructor0' },
+{ oid => '8148', descr => 'tsmultirange constructor',
+  proname => 'tsmultirange', proisstrict => 't',
+  prorettype => 'tsmultirange', proargtypes => 'tsrange',
+  prosrc => 'multirange_constructor1' },
+{ oid => '8050', descr => 'tsmultirange constructor',
+  proname => 'tsmultirange', provariadic => 'tsrange', proisstrict => 'f',
+  prorettype => 'tsmultirange', proargtypes => '_tsrange',
+  proallargtypes => '{_tsrange}', proargmodes => '{v}',
+  prosrc => 'multirange_constructor2' },
+{ oid => '8051', descr => 'tstzmultirange constructor',
+  proname => 'tstzmultirange', proisstrict => 'f',
+  prorettype => 'tstzmultirange', proargtypes => '',
+  prosrc => 'multirange_constructor0' },
+{ oid => '8149', descr => 'tstzmultirange constructor',
+  proname => 'tstzmultirange', proisstrict => 't',
+  prorettype => 'tstzmultirange', proargtypes => 'tstzrange',
+  prosrc => 'multirange_constructor1' },
+{ oid => '8052', descr => 'tstzmultirange constructor',
+  proname => 'tstzmultirange', provariadic => 'tstzrange', proisstrict => 'f',
+  prorettype => 'tstzmultirange', proargtypes => '_tstzrange',
+  proallargtypes => '{_tstzrange}', proargmodes => '{v}',
+  prosrc => 'multirange_constructor2' },
+{ oid => '8053', descr => 'datemultirange constructor',
+  proname => 'datemultirange', proisstrict => 'f',
+  prorettype => 'datemultirange', proargtypes => '',
+  prosrc => 'multirange_constructor0' },
+{ oid => '8150', descr => 'datemultirange constructor',
+  proname => 'datemultirange', proisstrict => 't',
+  prorettype => 'datemultirange', proargtypes => 'daterange',
+  prosrc => 'multirange_constructor1' },
+{ oid => '8054', descr => 'datemultirange constructor',
+  proname => 'datemultirange', provariadic => 'daterange', proisstrict => 'f',
+  prorettype => 'datemultirange', proargtypes => '_daterange',
+  proallargtypes => '{_daterange}', proargmodes => '{v}',
+  prosrc => 'multirange_constructor2' },
+{ oid => '8055', descr => 'int8multirange constructor',
+  proname => 'int8multirange', proisstrict => 'f',
+  prorettype => 'int8multirange', proargtypes => '',
+  prosrc => 'multirange_constructor0' },
+{ oid => '8151', descr => 'int8multirange constructor',
+  proname => 'int8multirange', proisstrict => 't',
+  prorettype => 'int8multirange', proargtypes => 'int8range',
+  prosrc => 'multirange_constructor1' },
+{ oid => '8056', descr => 'int8multirange constructor',
+  proname => 'int8multirange', provariadic => 'int8range', proisstrict => 'f',
+  prorettype => 'int8multirange', proargtypes => '_int8range',
+  proallargtypes => '{_int8range}', proargmodes => '{v}',
+  prosrc => 'multirange_constructor2' },
+{ oid => '8152', descr => 'anymultirange cast',
+  proname => 'multirange', proisstrict => 't',
+  prorettype => 'anymultirange', proargtypes => 'anyrange',
+  prosrc => 'multirange_constructor1' },
+{ oid => '8130', descr => 'aggregate transition function',
+  proname => 'range_agg_transfn', proisstrict => 'f', prorettype => 'internal',
+  proargtypes => 'internal anyrange', prosrc => 'range_agg_transfn' },
+{ oid => '8131', descr => 'aggregate final function',
+  proname => 'range_agg_finalfn', proisstrict => 'f', prorettype => 'anymultirange',
+  proargtypes => 'internal anyrange', prosrc => 'range_agg_finalfn' },
+{ oid => '8132', descr => 'combine aggregate input into a multirange',
+  proname => 'range_agg', prokind => 'a', proisstrict => 'f',
+  prorettype => 'anymultirange', proargtypes => 'anyrange',
+  prosrc => 'aggregate_dummy' },
+{ oid => '8135', descr => 'range aggregate by intersecting',
+  proname => 'multirange_intersect_agg_transfn', prorettype => 'anymultirange',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_intersect_agg_transfn'},
+{ oid => '8136', descr => 'range aggregate by intersecting',
+  proname => 'range_intersect_agg', prokind => 'a', proisstrict => 'f',
+  prorettype => 'anymultirange', proargtypes => 'anymultirange',
+  prosrc => 'aggregate_dummy'},
+
 # date, time, timestamp constructors
 { oid => '3846', descr => 'construct date',
   proname => 'make_date', prorettype => 'date', proargtypes => 'int4 int4 int4',
@@ -10148,6 +10411,14 @@
   proname => 'binary_upgrade_set_next_array_pg_type_oid', provolatile => 'v',
   proparallel => 'r', prorettype => 'void', proargtypes => 'oid',
   prosrc => 'binary_upgrade_set_next_array_pg_type_oid' },
+{ oid => '8144', descr => 'for use by pg_upgrade',
+  proname => 'binary_upgrade_set_next_multirange_pg_type_oid', provolatile => 'v',
+  proparallel => 'r', prorettype => 'void', proargtypes => 'oid',
+  prosrc => 'binary_upgrade_set_next_multirange_pg_type_oid' },
+{ oid => '8145', descr => 'for use by pg_upgrade',
+  proname => 'binary_upgrade_set_next_multirange_array_pg_type_oid', provolatile => 'v',
+  proparallel => 'r', prorettype => 'void', proargtypes => 'oid',
+  prosrc => 'binary_upgrade_set_next_multirange_array_pg_type_oid' },
 { oid => '3585', descr => 'for use by pg_upgrade',
   proname => 'binary_upgrade_set_next_toast_pg_type_oid', provolatile => 'v',
   proparallel => 'r', prorettype => 'void', proargtypes => 'oid',
diff --git a/src/include/catalog/pg_range.dat b/src/include/catalog/pg_range.dat
index 479754c245..10060255c9 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',
+  rngmultitypid => 'int4multirange' },
 { rngtypid => 'numrange', rngsubtype => 'numeric',
   rngsubopc => 'btree/numeric_ops', rngcanonical => '-',
-  rngsubdiff => 'numrange_subdiff' },
+  rngsubdiff => 'numrange_subdiff', rngmultitypid => 'nummultirange' },
 { rngtypid => 'tsrange', rngsubtype => 'timestamp',
   rngsubopc => 'btree/timestamp_ops', rngcanonical => '-',
-  rngsubdiff => 'tsrange_subdiff' },
+  rngsubdiff => 'tsrange_subdiff', rngmultitypid => 'tsmultirange' },
 { rngtypid => 'tstzrange', rngsubtype => 'timestamptz',
   rngsubopc => 'btree/timestamptz_ops', rngcanonical => '-',
-  rngsubdiff => 'tstzrange_subdiff' },
+  rngsubdiff => 'tstzrange_subdiff', rngmultitypid => 'tstzmultirange' },
 { rngtypid => 'daterange', rngsubtype => 'date', rngsubopc => 'btree/date_ops',
-  rngcanonical => 'daterange_canonical', rngsubdiff => 'daterange_subdiff' },
+  rngcanonical => 'daterange_canonical', rngsubdiff => 'daterange_subdiff',
+  rngmultitypid => 'datemultirange' },
 { rngtypid => 'int8range', rngsubtype => 'int8', rngsubopc => 'btree/int8_ops',
-  rngcanonical => 'int8range_canonical', rngsubdiff => 'int8range_subdiff' },
+  rngcanonical => 'int8range_canonical', rngsubdiff => 'int8range_subdiff',
+  rngmultitypid => 'int8multirange' },
 
 ]
diff --git a/src/include/catalog/pg_range.h b/src/include/catalog/pg_range.h
index 98172bb1b6..3efd463c6d 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			rngmultitypid 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 4cf2b9df7b..beaa70dce6 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 e1a5ab3df3..600ac58c9d 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/commands/typecmds.h b/src/include/commands/typecmds.h
index fc18d64347..7d0a4a9ea5 100644
--- a/src/include/commands/typecmds.h
+++ b/src/include/commands/typecmds.h
@@ -29,6 +29,8 @@ extern ObjectAddress DefineRange(CreateRangeStmt *stmt);
 extern ObjectAddress AlterEnum(AlterEnumStmt *stmt);
 extern ObjectAddress DefineCompositeType(RangeVar *typevar, List *coldeflist);
 extern Oid	AssignTypeArrayOid(void);
+extern Oid	AssignTypeMultirangeOid(void);
+extern Oid	AssignTypeMultirangeArrayOid(void);
 
 extern ObjectAddress AlterDomainDefault(List *names, Node *defaultRaw);
 extern ObjectAddress AlterDomainNotNull(List *names, bool notNull);
diff --git a/src/include/utils/lsyscache.h b/src/include/utils/lsyscache.h
index 370f62a133..7d1674a3b6 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);
@@ -180,6 +181,8 @@ 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_collation(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..8779bd1f6e
--- /dev/null
+++ b/src/include/utils/multirangetypes.h
@@ -0,0 +1,103 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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);
+extern bool multirange_contains_elem_internal(TypeCacheEntry *typcache, MultirangeType * mr,
+											  Datum elem);
+extern bool multirange_contains_range_internal(TypeCacheEntry *typcache, MultirangeType * mr,
+											   RangeType *r);
+extern bool multirange_contains_multirange_internal(TypeCacheEntry *typcache,
+													MultirangeType * mr1,
+													MultirangeType * mr2);
+extern bool range_overlaps_multirange_internal(TypeCacheEntry *typcache, RangeType *r,
+											   MultirangeType * mr);
+extern bool multirange_overlaps_multirange_internal(TypeCacheEntry *typcache,
+													MultirangeType * mr1,
+													MultirangeType * mr2);
+extern bool range_before_multirange_internal(TypeCacheEntry *typcache, RangeType *r,
+											 MultirangeType * mr);
+extern bool range_after_multirange_internal(TypeCacheEntry *typcache, RangeType *r,
+											MultirangeType * mr);
+extern bool multirange_before_multirange_internal(TypeCacheEntry *typcache,
+												  MultirangeType * mr1,
+												  MultirangeType * mr2);
+extern MultirangeType * multirange_minus_internal(Oid mltrngtypoid,
+												  TypeCacheEntry *rangetyp,
+												  int32 range_count1,
+												  RangeType **ranges1,
+												  int32 range_count2,
+												  RangeType **ranges2);
+extern MultirangeType * multirange_intersect_internal(Oid mltrngtypoid,
+													  TypeCacheEntry *rangetyp,
+													  int32 range_count1,
+													  RangeType **ranges1,
+													  int32 range_count2,
+													  RangeType **ranges2);
+
+/* 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 0cbbf09033..204aee4054 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)
 
@@ -92,13 +94,21 @@ typedef struct
  * prototypes for functions defined in rangetypes.c
  */
 
-extern bool range_contains_elem_internal(TypeCacheEntry *typcache, const RangeType *r, Datum val);
-
 /* internal versions of the above */
+extern int	range_cmp_internal(TypeCacheEntry *typcache, const RangeType *r1,
+							   const RangeType *r2);
+extern uint32 hash_range_internal(TypeCacheEntry *typcache, const RangeType *r);
+extern uint64 hash_range_extended_internal(TypeCacheEntry *typcache, const RangeType *r,
+										   Datum seed);
+extern Datum range_lower_internal(TypeCacheEntry *typcache, const RangeType *r1,
+								  bool *isnull);
+extern Datum range_upper_internal(TypeCacheEntry *typcache, const RangeType *r1,
+								  bool *isnull);
 extern bool range_eq_internal(TypeCacheEntry *typcache, const RangeType *r1,
 							  const RangeType *r2);
 extern bool range_ne_internal(TypeCacheEntry *typcache, const RangeType *r1,
 							  const RangeType *r2);
+extern bool range_contains_elem_internal(TypeCacheEntry *typcache, const RangeType *r, Datum val);
 extern bool range_contains_internal(TypeCacheEntry *typcache, const RangeType *r1,
 									const RangeType *r2);
 extern bool range_contained_by_internal(TypeCacheEntry *typcache, const RangeType *r1,
@@ -115,6 +125,12 @@ extern bool range_overleft_internal(TypeCacheEntry *typcache, const RangeType *r
 									const RangeType *r2);
 extern bool range_overright_internal(TypeCacheEntry *typcache, const RangeType *r1,
 									 const RangeType *r2);
+extern RangeType *range_union_internal(TypeCacheEntry *typcache, RangeType *r1,
+									   RangeType *r2, bool strict);
+extern RangeType *range_minus_internal(TypeCacheEntry *typcache, RangeType *r1,
+									   RangeType *r2);
+extern RangeType *range_intersect_internal(TypeCacheEntry *typcache, const RangeType *r1,
+										   const RangeType *r2);
 
 /* assorted support functions */
 extern TypeCacheEntry *range_get_typcache(FunctionCallInfo fcinfo,
@@ -125,6 +141,7 @@ extern void range_deserialize(TypeCacheEntry *typcache, const RangeType *range,
 							  RangeBound *lower, RangeBound *upper,
 							  bool *empty);
 extern char range_get_flags(const RangeType *range);
+extern bool range_has_flag(const RangeType *range, char flag);
 extern void range_set_contain_empty(RangeType *range);
 extern RangeType *make_range(TypeCacheEntry *typcache, RangeBound *lower,
 							 RangeBound *upper, bool empty);
@@ -132,8 +149,12 @@ extern int range_cmp_bounds(TypeCacheEntry *typcache, const RangeBound *b1,
 							const RangeBound *b2);
 extern int range_cmp_bound_values(TypeCacheEntry *typcache, const RangeBound *b1,
 								  const RangeBound *b2);
-extern bool bounds_adjacent(TypeCacheEntry *typcache, RangeBound bound1,
-							RangeBound bound2);
+extern int range_compare(const void *key1, const void *key2, void *arg);
+extern bool bounds_adjacent(TypeCacheEntry *typcache, const RangeBound bound1,
+							const RangeBound bound2);
 extern RangeType *make_empty_range(TypeCacheEntry *typcache);
+extern bool range_split_internal(TypeCacheEntry *typcache, const RangeType *r1,
+								 const RangeType *r2, RangeType **output1,
+								 RangeType **output2);
 
 #endif							/* RANGETYPES_H */
diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h
index f27b73d76d..bc51bbeb54 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 66ff17dbd5..fe240ea004 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 4f6b36b145..d759bf3aee 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? */
@@ -2087,6 +2089,7 @@ build_datatype(HeapTuple typeTup, int32 typmod,
 		case TYPTYPE_BASE:
 		case TYPTYPE_ENUM:
 		case TYPTYPE_RANGE:
+		case TYPTYPE_MULTIRANGE:
 			typ->ttype = PLPGSQL_TTYPE_SCALAR;
 			break;
 		case TYPTYPE_COMPOSITE:
@@ -2501,6 +2504,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/dependency.out b/src/test/regress/expected/dependency.out
index b4a11b8aa9..778699a961 100644
--- a/src/test/regress/expected/dependency.out
+++ b/src/test/regress/expected/dependency.out
@@ -141,6 +141,7 @@ owner of table deptest
 owner of function deptest_func()
 owner of type deptest_enum
 owner of type deptest_range
+owner of type deptest_multirange
 owner of table deptest2
 owner of sequence ss1
 owner of type deptest_t
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..723f2408b0
--- /dev/null
+++ b/src/test/regress/expected/multirangetypes.out
@@ -0,0 +1,2395 @@
+-- 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)
+
+--
+-- test casts, both a built-in range type and a user-defined one:
+--
+select 'empty'::int4range::int4multirange;
+ int4multirange 
+----------------
+ {}
+(1 row)
+
+select int4range(1, 3)::int4multirange;
+ int4range 
+-----------
+ {[1,3)}
+(1 row)
+
+select int4range(1, null)::int4multirange;
+ int4range 
+-----------
+ {[1,)}
+(1 row)
+
+select int4range(null, null)::int4multirange;
+ int4range 
+-----------
+ {(,)}
+(1 row)
+
+select 'empty'::textrange::textmultirange;
+ textmultirange 
+----------------
+ {}
+(1 row)
+
+select textrange('a', 'c')::textmultirange;
+ textrange 
+-----------
+ {[a,c)}
+(1 row)
+
+select textrange('a', null)::textmultirange;
+ textrange 
+-----------
+ {[a,)}
+(1 row)
+
+select textrange(null, null)::textmultirange;
+ textrange 
+-----------
+ {(,)}
+(1 row)
+
+--
+-- create some test data and test the operators
+--
+CREATE TABLE nummultirange_test (nmr NUMMULTIRANGE);
+CREATE INDEX nummultirange_test_btree ON nummultirange_test(nmr);
+INSERT INTO nummultirange_test VALUES('{}');
+INSERT INTO nummultirange_test VALUES('{[,)}');
+INSERT INTO nummultirange_test VALUES('{[3,]}');
+INSERT INTO nummultirange_test VALUES('{[,), [3,]}');
+INSERT INTO nummultirange_test VALUES('{[, 5)}');
+INSERT INTO nummultirange_test VALUES(nummultirange());
+INSERT INTO nummultirange_test VALUES(nummultirange(variadic '{}'::numrange[]));
+INSERT INTO nummultirange_test VALUES(nummultirange(numrange(1.1, 2.2)));
+INSERT INTO nummultirange_test VALUES('{empty}');
+INSERT INTO nummultirange_test VALUES(nummultirange(numrange(1.7, 1.7, '[]'), numrange(1.7, 1.9)));
+INSERT INTO nummultirange_test VALUES(nummultirange(numrange(1.7, 1.7, '[]'), numrange(1.9, 2.1)));
+SELECT nmr, isempty(nmr), lower(nmr), upper(nmr) FROM nummultirange_test ORDER BY nmr;
+          nmr          | isempty | lower | upper 
+-----------------------+---------+-------+-------
+ {}                    | t       |       |      
+ {}                    | t       |       |      
+ {}                    | t       |       |      
+ {}                    | t       |       |      
+ {(,5)}                | f       |       |     5
+ {(,)}                 | f       |       |      
+ {(,)}                 | f       |       |      
+ {[1.1,2.2)}           | f       |   1.1 |   2.2
+ {[1.7,1.7],[1.9,2.1)} | f       |   1.7 |   2.1
+ {[1.7,1.9)}           | f       |   1.7 |   1.9
+ {[3,)}                | f       |     3 |      
+(11 rows)
+
+SELECT nmr, lower_inc(nmr), lower_inf(nmr), upper_inc(nmr), upper_inf(nmr) FROM nummultirange_test ORDER BY nmr;
+          nmr          | lower_inc | lower_inf | upper_inc | upper_inf 
+-----------------------+-----------+-----------+-----------+-----------
+ {}                    | f         | f         | f         | f
+ {}                    | f         | f         | f         | f
+ {}                    | f         | f         | f         | f
+ {}                    | f         | f         | f         | f
+ {(,5)}                | f         | t         | f         | f
+ {(,)}                 | f         | t         | f         | t
+ {(,)}                 | f         | t         | f         | t
+ {[1.1,2.2)}           | t         | f         | f         | f
+ {[1.7,1.7],[1.9,2.1)} | t         | f         | f         | f
+ {[1.7,1.9)}           | t         | f         | f         | f
+ {[3,)}                | t         | f         | f         | t
+(11 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr = '{}';
+ nmr 
+-----
+ {}
+ {}
+ {}
+ {}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr = '{(,5)}';
+  nmr   
+--------
+ {(,5)}
+(1 row)
+
+SELECT * FROM nummultirange_test WHERE nmr = '{[3,)}';
+  nmr   
+--------
+ {[3,)}
+(1 row)
+
+SELECT * FROM nummultirange_test WHERE nmr = '{[1.7,1.7]}';
+ nmr 
+-----
+(0 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr = '{[1.7,1.7],[1.9,2.1)}';
+          nmr          
+-----------------------
+ {[1.7,1.7],[1.9,2.1)}
+(1 row)
+
+SELECT * FROM nummultirange_test WHERE nmr < '{}';
+ nmr 
+-----
+(0 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr < '{[-1000.0, -1000.0]}';
+  nmr   
+--------
+ {}
+ {(,)}
+ {(,)}
+ {(,5)}
+ {}
+ {}
+ {}
+(7 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr < '{[0.0, 1.0]}';
+  nmr   
+--------
+ {}
+ {(,)}
+ {(,)}
+ {(,5)}
+ {}
+ {}
+ {}
+(7 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr < '{[1000.0, 1001.0]}';
+          nmr          
+-----------------------
+ {}
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+ {}
+ {}
+ {[1.1,2.2)}
+ {}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(11 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr <= '{}';
+ nmr 
+-----
+ {}
+ {}
+ {}
+ {}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr <= '{[3,)}';
+          nmr          
+-----------------------
+ {}
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+ {}
+ {}
+ {[1.1,2.2)}
+ {}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(11 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr >= '{}';
+          nmr          
+-----------------------
+ {}
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+ {}
+ {}
+ {[1.1,2.2)}
+ {}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(11 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr >= '{[3,)}';
+  nmr   
+--------
+ {[3,)}
+(1 row)
+
+SELECT * FROM nummultirange_test WHERE nmr > '{}';
+          nmr          
+-----------------------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+ {[1.1,2.2)}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(7 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr > '{[-1000.0, -1000.0]}';
+          nmr          
+-----------------------
+ {[3,)}
+ {[1.1,2.2)}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr > '{[0.0, 1.0]}';
+          nmr          
+-----------------------
+ {[3,)}
+ {[1.1,2.2)}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr > '{[1000.0, 1001.0]}';
+ nmr 
+-----
+(0 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr <> '{}';
+          nmr          
+-----------------------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+ {[1.1,2.2)}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(7 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr <> '{(,5)}';
+          nmr          
+-----------------------
+ {}
+ {(,)}
+ {[3,)}
+ {(,)}
+ {}
+ {}
+ {[1.1,2.2)}
+ {}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(10 rows)
+
+select nummultirange(numrange(2.0, 1.0));
+ERROR:  range lower bound must be less than or equal to range upper bound
+select nummultirange(numrange(5.0, 6.0), numrange(1.0, 2.0));
+     nummultirange     
+-----------------------
+ {[1.0,2.0),[5.0,6.0)}
+(1 row)
+
+-- overlaps
+SELECT * FROM nummultirange_test WHERE range_overlaps_multirange(numrange(4.0, 4.2), nmr);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE numrange(4.0, 4.2) && nmr;
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE multirange_overlaps_range(nmr, numrange(4.0, 4.2));
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr && numrange(4.0, 4.2);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE multirange_overlaps_multirange(nmr, nummultirange(numrange(4.0, 4.2), numrange(6.0, 7.0)));
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr && nummultirange(numrange(4.0, 4.2), numrange(6.0, 7.0));
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr && nummultirange(numrange(6.0, 7.0));
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+(3 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr && nummultirange(numrange(6.0, 7.0), numrange(8.0, 9.0));
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+(3 rows)
+
+-- mr contains x
+SELECT * FROM nummultirange_test WHERE multirange_contains_elem(nmr, 4.0);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr @> 4.0;
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE multirange_contains_range(nmr, numrange(4.0, 4.2));
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr @> numrange(4.0, 4.2);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE multirange_contains_multirange(nmr, '{[4.0,4.2), [6.0, 8.0)}');
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+(3 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr @> '{[4.0,4.2), [6.0, 8.0)}'::nummultirange;
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+(3 rows)
+
+-- x is contained by mr
+SELECT * FROM nummultirange_test WHERE elem_contained_by_multirange(4.0, nmr);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE 4.0 <@ nmr;
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE range_contained_by_multirange(numrange(4.0, 4.2), nmr);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE numrange(4.0, 4.2) <@ nmr;
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE multirange_contained_by_multirange('{[4.0,4.2), [6.0, 8.0)}', nmr);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+(3 rows)
+
+SELECT * FROM nummultirange_test WHERE '{[4.0,4.2), [6.0, 8.0)}'::nummultirange <@ nmr;
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+(3 rows)
+
+-- overlaps
+SELECT 'empty'::numrange && nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT 'empty'::numrange && nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() && 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) && 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() && nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() && nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) && nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) && nummultirange(numrange(1,2), numrange(7,8));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(7,8)) && nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) && nummultirange(numrange(1,2), numrange(3.5,8));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(3.5,8)) && numrange(3,4);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(3.5,8)) && nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+-- contains
+SELECT nummultirange() @> nummultirange();
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange() @> 'empty'::numrange;
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(null,null)) @> numrange(1,2);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(null,null)) @> numrange(null,2);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(null,null)) @> numrange(2,null);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(null,5)) @> numrange(null,3);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(null,5)) @> numrange(null,8);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(5,null)) @> numrange(8,null);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(5,null)) @> numrange(3,null);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,5)) @> numrange(8,9);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,5)) @> numrange(3,9);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,5)) @> numrange(1,4);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,5)) @> numrange(1,5);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(-4,-2), numrange(1,5)) @> numrange(1,5);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,5), numrange(8,9)) @> numrange(1,5);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,5), numrange(8,9)) @> numrange(6,7);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,5), numrange(6,9)) @> numrange(6,7);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[1,5)}'::nummultirange @> '{[1,5)}';
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[-4,-2), [1,5)}'::nummultirange @> '{[1,5)}';
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[1,5), [8,9)}'::nummultirange @> '{[1,5)}';
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[1,5), [8,9)}'::nummultirange @> '{[6,7)}';
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT '{[1,5), [6,9)}'::nummultirange @> '{[6,7)}';
+ ?column? 
+----------
+ t
+(1 row)
+
+-- is contained by
+SELECT nummultirange() <@ nummultirange();
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT 'empty'::numrange <@ nummultirange();
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,2) <@ nummultirange(numrange(null,null));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(null,2) <@ nummultirange(numrange(null,null));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(2,null) <@ nummultirange(numrange(null,null));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(null,3) <@ nummultirange(numrange(null,5));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(null,8) <@ nummultirange(numrange(null,5));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(8,null) <@ nummultirange(numrange(5,null));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(3,null) <@ nummultirange(numrange(5,null));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(8,9) <@ nummultirange(numrange(1,5));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(3,9) <@ nummultirange(numrange(1,5));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(1,4) <@ nummultirange(numrange(1,5));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,5) <@ nummultirange(numrange(1,5));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,5) <@ nummultirange(numrange(-4,-2), numrange(1,5));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,5) <@ nummultirange(numrange(1,5), numrange(8,9));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(6,7) <@ nummultirange(numrange(1,5), numrange(8,9));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(6,7) <@ nummultirange(numrange(1,5), numrange(6,9));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[1,5)}' <@ '{[1,5)}'::nummultirange;
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[1,5)}' <@ '{[-4,-2), [1,5)}'::nummultirange;
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[1,5)}' <@ '{[1,5), [8,9)}'::nummultirange;
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[6,7)}' <@ '{[1,5), [8,9)}'::nummultirange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT '{[6,7)}' <@ '{[1,5), [6,9)}'::nummultirange;
+ ?column? 
+----------
+ t
+(1 row)
+
+-- overleft
+SELECT 'empty'::numrange &< nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT 'empty'::numrange &< nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() &< 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) &< 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() &< nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) &< nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() &< nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(6,7) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(1,2) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,4) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,6) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(3.5,6) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(6,7)) &< numrange(3,4);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) &< numrange(3,4);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,4)) &< numrange(3,4);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,6)) &< numrange(3,4);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3.5,6)) &< numrange(3,4);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(6,7)) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,4)) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,6)) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3.5,6)) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+-- overright
+SELECT nummultirange() &> 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) &> 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT 'empty'::numrange &> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT 'empty'::numrange &> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() &> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() &> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) &> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> numrange(6,7);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> numrange(1,2);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> numrange(1,4);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> numrange(1,6);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> numrange(3.5,6);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(3,4) &> nummultirange(numrange(6,7));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(3,4) &> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(3,4) &> nummultirange(numrange(1,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(3,4) &> nummultirange(numrange(1,6));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(3,4) &> nummultirange(numrange(3.5,6));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(6,7));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(1,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(1,6));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(3.5,6));
+ ?column? 
+----------
+ f
+(1 row)
+
+-- meets
+SELECT 'empty'::numrange -|- nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT 'empty'::numrange -|- nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() -|- 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() -|- nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() -|- nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(1,2) -|- nummultirange(numrange(2,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,2) -|- nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- numrange(2,4);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- numrange(3,4);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- nummultirange(numrange(2,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(5,6)) -|- nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(5,6)) -|- nummultirange(numrange(6,7));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(5,6)) -|- nummultirange(numrange(8,9));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- nummultirange(numrange(2,4), numrange(6,7));
+ ?column? 
+----------
+ t
+(1 row)
+
+-- strictly left
+select 'empty'::numrange << nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(1,2) << nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(1,2) << nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+select numrange(1,2) << nummultirange(numrange(0,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(1,2) << nummultirange(numrange(0,4), numrange(7,8));
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange() << 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange() << numrange(1,2);
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(3,4)) << numrange(3,6);
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(0,2)) << numrange(3,6);
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange(numrange(0,2), numrange(7,8)) << numrange(3,6);
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(-4,-2), numrange(0,2)) << numrange(3,6);
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange() << nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange() << nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(1,2)) << nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(1,2)) << nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(1,2)) << nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange(numrange(1,2)) << nummultirange(numrange(3,4), numrange(7,8));
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange(numrange(1,2), numrange(4,5)) << nummultirange(numrange(3,4), numrange(7,8));
+ ?column? 
+----------
+ f
+(1 row)
+
+-- strictly right
+select nummultirange() >> 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange() >> numrange(1,2);
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(3,4)) >> numrange(1,2);
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange(numrange(0,4)) >> numrange(1,2);
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(0,4), numrange(7,8)) >> numrange(1,2);
+ ?column? 
+----------
+ f
+(1 row)
+
+select 'empty'::numrange >> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(1,2) >> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(3,6) >> nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(3,6) >> nummultirange(numrange(0,2));
+ ?column? 
+----------
+ t
+(1 row)
+
+select numrange(3,6) >> nummultirange(numrange(0,2), numrange(7,8));
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(3,6) >> nummultirange(numrange(-4,-2), numrange(0,2));
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange() >> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(1,2)) >> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange() >> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(1,2)) >> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(3,4)) >> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange(numrange(3,4), numrange(7,8)) >> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange(numrange(3,4), numrange(7,8)) >> nummultirange(numrange(1,2), numrange(4,5));
+ ?column? 
+----------
+ f
+(1 row)
+
+-- union
+SELECT nummultirange() + nummultirange();
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange() + nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) + nummultirange();
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) + nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) + nummultirange(numrange(2,4));
+ ?column? 
+----------
+ {[1,4)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) + nummultirange(numrange(3,4));
+   ?column?    
+---------------
+ {[1,2),[3,4)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) + nummultirange(numrange(2,4));
+ ?column? 
+----------
+ {[1,5)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) + nummultirange(numrange(3,4));
+   ?column?    
+---------------
+ {[1,2),[3,5)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) + nummultirange(numrange(0,9));
+ ?column? 
+----------
+ {[0,9)}
+(1 row)
+
+-- merge
+SELECT range_merge(nummultirange());
+ range_merge 
+-------------
+ empty
+(1 row)
+
+SELECT range_merge(nummultirange(numrange(1,2)));
+ range_merge 
+-------------
+ [1,2)
+(1 row)
+
+SELECT range_merge(nummultirange(numrange(1,2), numrange(7,8)));
+ range_merge 
+-------------
+ [1,8)
+(1 row)
+
+-- minus
+SELECT nummultirange() - nummultirange();
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange() - nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) - nummultirange();
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(3,4)) - nummultirange();
+   ?column?    
+---------------
+ {[1,2),[3,4)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) - nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) - nummultirange(numrange(2,4));
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) - nummultirange(numrange(3,4));
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {[2,4)}
+(1 row)
+
+SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(2,3));
+   ?column?    
+---------------
+ {[1,2),[3,4)}
+(1 row)
+
+SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(0,8));
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(0,2));
+ ?column? 
+----------
+ {[2,4)}
+(1 row)
+
+SELECT nummultirange(numrange(1,8)) - nummultirange(numrange(0,2), numrange(3,4));
+   ?column?    
+---------------
+ {[2,3),[4,8)}
+(1 row)
+
+SELECT nummultirange(numrange(1,8)) - nummultirange(numrange(2,3), numrange(5,null));
+   ?column?    
+---------------
+ {[1,2),[3,5)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(-2,0));
+   ?column?    
+---------------
+ {[1,2),[4,5)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(2,4));
+   ?column?    
+---------------
+ {[1,2),[4,5)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(3,5));
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(0,9));
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange(numrange(1,3), numrange(4,5)) - nummultirange(numrange(2,9));
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(8,9));
+   ?column?    
+---------------
+ {[1,2),[4,5)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(-2,0), numrange(8,9));
+   ?column?    
+---------------
+ {[1,2),[4,5)}
+(1 row)
+
+-- intersection
+SELECT nummultirange() * nummultirange();
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange() * nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) * nummultirange();
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT '{[1,3)}'::nummultirange * '{[1,5)}'::nummultirange;
+ ?column? 
+----------
+ {[1,3)}
+(1 row)
+
+SELECT '{[1,3)}'::nummultirange * '{[0,5)}'::nummultirange;
+ ?column? 
+----------
+ {[1,3)}
+(1 row)
+
+SELECT '{[1,3)}'::nummultirange * '{[0,2)}'::nummultirange;
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT '{[1,3)}'::nummultirange * '{[2,5)}'::nummultirange;
+ ?column? 
+----------
+ {[2,3)}
+(1 row)
+
+SELECT '{[1,4)}'::nummultirange * '{[2,3)}'::nummultirange;
+ ?column? 
+----------
+ {[2,3)}
+(1 row)
+
+SELECT '{[1,4)}'::nummultirange * '{[0,2), [3,5)}'::nummultirange;
+   ?column?    
+---------------
+ {[1,2),[3,4)}
+(1 row)
+
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[0,8), [9,12)}'::nummultirange;
+       ?column?       
+----------------------
+ {[1,4),[7,8),[9,10)}
+(1 row)
+
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[9,12)}'::nummultirange;
+ ?column? 
+----------
+ {[9,10)}
+(1 row)
+
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[-5,-4), [5,6), [9,12)}'::nummultirange;
+ ?column? 
+----------
+ {[9,10)}
+(1 row)
+
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[0,2), [3,8), [9,12)}'::nummultirange;
+          ?column?          
+----------------------------
+ {[1,2),[3,4),[7,8),[9,10)}
+(1 row)
+
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[0,2), [3,8), [9,12)}'::nummultirange;
+          ?column?          
+----------------------------
+ {[1,2),[3,4),[7,8),[9,10)}
+(1 row)
+
+--
+-- range_agg function
+--
+create table reservations ( room_id integer not null, booked_during daterange );
+insert into reservations values
+-- 1: has a meets and a gap
+(1, daterange('2018-07-01', '2018-07-07')),
+(1, daterange('2018-07-07', '2018-07-14')),
+(1, daterange('2018-07-20', '2018-07-22')),
+-- 2: just a single row
+(2, daterange('2018-07-01', '2018-07-03')),
+-- 3: one null range
+(3, NULL),
+-- 4: two null ranges
+(4, NULL),
+(4, NULL),
+-- 5: a null range and a non-null range
+(5, NULL),
+(5, daterange('2018-07-01', '2018-07-03')),
+-- 6: has overlap
+(6, daterange('2018-07-01', '2018-07-07')),
+(6, daterange('2018-07-05', '2018-07-10')),
+-- 7: two ranges that meet: no gap or overlap
+(7, daterange('2018-07-01', '2018-07-07')),
+(7, daterange('2018-07-07', '2018-07-14')),
+-- 8: an empty range
+(8, 'empty'::daterange)
+;
+SELECT   room_id, range_agg(booked_during)
+FROM     reservations
+GROUP BY room_id
+ORDER BY room_id;
+ room_id |                     range_agg                     
+---------+---------------------------------------------------
+       1 | {[07-01-2018,07-14-2018),[07-20-2018,07-22-2018)}
+       2 | {[07-01-2018,07-03-2018)}
+       3 | 
+       4 | 
+       5 | {[07-01-2018,07-03-2018)}
+       6 | {[07-01-2018,07-10-2018)}
+       7 | {[07-01-2018,07-14-2018)}
+       8 | {}
+(8 rows)
+
+-- range_agg on a custom range type too
+SELECT  range_agg(r)
+FROM    (VALUES
+          ('[a,c]'::textrange),
+          ('[b,b]'::textrange),
+          ('[c,f]'::textrange),
+          ('[g,h)'::textrange),
+          ('[h,j)'::textrange)
+        ) t(r);
+   range_agg   
+---------------
+ {[a,f],[g,j)}
+(1 row)
+
+select range_intersect_agg(nmr) from nummultirange_test;
+ range_intersect_agg 
+---------------------
+ {}
+(1 row)
+
+select range_intersect_agg(nmr) from nummultirange_test where false;
+ range_intersect_agg 
+---------------------
+ 
+(1 row)
+
+-- test with just one input:
+select range_intersect_agg(nmr) from (values ('{[1,2]}'::nummultirange)) t(nmr);
+ range_intersect_agg 
+---------------------
+ {[1,2]}
+(1 row)
+
+select range_intersect_agg(nmr) from nummultirange_test where nmr @> 4.0;
+ range_intersect_agg 
+---------------------
+ {[3,5)}
+(1 row)
+
+create table nummultirange_test2(nmr nummultirange);
+create index nummultirange_test2_hash_idx on nummultirange_test2 using hash (nmr);
+INSERT INTO nummultirange_test2 VALUES('{[, 5)}');
+INSERT INTO nummultirange_test2 VALUES(nummultirange(numrange(1.1, 2.2)));
+INSERT INTO nummultirange_test2 VALUES(nummultirange(numrange(1.1, 2.2)));
+INSERT INTO nummultirange_test2 VALUES(nummultirange(numrange(1.1, 2.2,'()')));
+INSERT INTO nummultirange_test2 VALUES('{}');
+select * from nummultirange_test2 where nmr = '{}';
+ nmr 
+-----
+ {}
+(1 row)
+
+select * from nummultirange_test2 where nmr = nummultirange(numrange(1.1, 2.2));
+     nmr     
+-------------
+ {[1.1,2.2)}
+ {[1.1,2.2)}
+(2 rows)
+
+select * from nummultirange_test2 where nmr = nummultirange(numrange(1.1, 2.3));
+ nmr 
+-----
+(0 rows)
+
+set enable_nestloop=t;
+set enable_hashjoin=f;
+set enable_mergejoin=f;
+select * from nummultirange_test natural join nummultirange_test2 order by nmr;
+     nmr     
+-------------
+ {}
+ {}
+ {}
+ {}
+ {(,5)}
+ {[1.1,2.2)}
+ {[1.1,2.2)}
+(7 rows)
+
+set enable_nestloop=f;
+set enable_hashjoin=t;
+set enable_mergejoin=f;
+select * from nummultirange_test natural join nummultirange_test2 order by nmr;
+     nmr     
+-------------
+ {}
+ {}
+ {}
+ {}
+ {(,5)}
+ {[1.1,2.2)}
+ {[1.1,2.2)}
+(7 rows)
+
+set enable_nestloop=f;
+set enable_hashjoin=f;
+set enable_mergejoin=t;
+select * from nummultirange_test natural join nummultirange_test2 order by nmr;
+     nmr     
+-------------
+ {}
+ {}
+ {}
+ {}
+ {(,5)}
+ {[1.1,2.2)}
+ {[1.1,2.2)}
+(7 rows)
+
+set enable_nestloop to default;
+set enable_hashjoin to default;
+set enable_mergejoin to default;
+DROP TABLE nummultirange_test2;
+--
+-- Test user-defined multirange of floats
+--
+select '{[123.001, 5.e9)}'::float8multirange @> 888.882::float8;
+ ?column? 
+----------
+ t
+(1 row)
+
+create table float8multirange_test(f8mr float8multirange, i int);
+insert into float8multirange_test values(float8multirange(float8range(-100.00007, '1.111113e9')), 42);
+select * from float8multirange_test;
+           f8mr            | i  
+---------------------------+----
+ {[-100.00007,1111113000)} | 42
+(1 row)
+
+drop table float8multirange_test;
+--
+-- Test multirange types over domains
+--
+create domain mydomain as int4;
+create type mydomainrange as range(subtype=mydomain);
+select '{[4,50)}'::mydomainmultirange @> 7::mydomain;
+ ?column? 
+----------
+ t
+(1 row)
+
+drop domain mydomain cascade;
+NOTICE:  drop cascades to type mydomainrange
+--
+-- Test domains over multirange types
+--
+create domain restrictedmultirange as int4multirange check (upper(value) < 10);
+select '{[4,5)}'::restrictedmultirange @> 7;
+ ?column? 
+----------
+ f
+(1 row)
+
+select '{[4,50)}'::restrictedmultirange @> 7; -- should fail
+ERROR:  value for domain restrictedmultirange violates check constraint "restrictedmultirange_check"
+drop domain restrictedmultirange;
+--
+-- Test multiple multirange types over the same subtype
+--
+create type textrange1 as range(subtype=text, collation="C");
+create type textrange2 as range(subtype=text, collation="C");
+select textmultirange1(textrange2('a','Z'));  -- should fail
+ERROR:  function textmultirange1(textrange2) does not exist
+LINE 1: select textmultirange1(textrange2('a','Z'));
+               ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+select textmultirange1(textrange1('a','Z')) @> 'b'::text;
+ERROR:  range lower bound must be less than or equal to range upper bound
+select textmultirange2(textrange2('a','z')) @> 'b'::text;
+ ?column? 
+----------
+ t
+(1 row)
+
+drop type textrange1;
+drop type textrange2;
+--
+-- Test polymorphic type system
+--
+create function anyarray_anymultirange_func(a anyarray, r anymultirange)
+  returns anyelement as 'select $1[1] + lower($2);' language sql;
+select anyarray_anymultirange_func(ARRAY[1,2], int4multirange(int4range(10,20)));
+ anyarray_anymultirange_func 
+-----------------------------
+                          11
+(1 row)
+
+-- should fail
+select anyarray_anymultirange_func(ARRAY[1,2], nummultirange(numrange(10,20)));
+ERROR:  function anyarray_anymultirange_func(integer[], nummultirange) does not exist
+LINE 1: select anyarray_anymultirange_func(ARRAY[1,2], nummultirange...
+               ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+drop function anyarray_anymultirange_func(anyarray, anymultirange);
+-- should fail
+create function bogus_func(anyelement)
+  returns anymultirange as 'select int4multirange(int4range(1,10))' language sql;
+ERROR:  cannot determine result data type
+DETAIL:  A function returning "anyrange" or "anymultirange" must have at least one "anyrange" or "anymultirange" argument.
+-- should fail
+create function bogus_func(int)
+  returns anymultirange as 'select int4multirange(int4range(1,10))' language sql;
+ERROR:  cannot determine result data type
+DETAIL:  A function returning a polymorphic type must have at least one polymorphic argument.
+create function range_add_bounds(anymultirange)
+  returns anyelement as 'select lower($1) + upper($1)' language sql;
+select range_add_bounds(int4multirange(int4range(1, 17)));
+ range_add_bounds 
+------------------
+               18
+(1 row)
+
+select range_add_bounds(nummultirange(numrange(1.0001, 123.123)));
+ range_add_bounds 
+------------------
+         124.1231
+(1 row)
+
+create function multirangetypes_sql(q anymultirange, b anyarray, out c anyelement)
+  as $$ select upper($1) + $2[1] $$
+  language sql;
+select multirangetypes_sql(int4multirange(int4range(1,10)), ARRAY[2,20]);
+ multirangetypes_sql 
+---------------------
+                  12
+(1 row)
+
+select multirangetypes_sql(nummultirange(numrange(1,10)), ARRAY[2,20]);  -- match failure
+ERROR:  function multirangetypes_sql(nummultirange, integer[]) does not exist
+LINE 1: select multirangetypes_sql(nummultirange(numrange(1,10)), AR...
+               ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+--
+-- Arrays of multiranges
+--
+select ARRAY[nummultirange(numrange(1.1, 1.2)), nummultirange(numrange(12.3, 155.5))];
+              array               
+----------------------------------
+ {"{[1.1,1.2)}","{[12.3,155.5)}"}
+(1 row)
+
+create table i8mr_array (f1 int, f2 int8multirange[]);
+insert into i8mr_array values (42, array[int8multirange(int8range(1,10)), int8multirange(int8range(2,20))]);
+select * from i8mr_array;
+ f1 |           f2            
+----+-------------------------
+ 42 | {"{[1,10)}","{[2,20)}"}
+(1 row)
+
+drop table i8mr_array;
+--
+-- Multiranges of arrays
+--
+select arraymultirange(arrayrange(ARRAY[1,2], ARRAY[2,1]));
+   arraymultirange   
+---------------------
+ {["{1,2}","{2,1}")}
+(1 row)
+
+select arraymultirange(arrayrange(ARRAY[2,1], ARRAY[1,2]));  -- fail
+ERROR:  range lower bound must be less than or equal to range upper bound
+select array[1,1] <@ arraymultirange(arrayrange(array[1,2], array[2,1]));
+ ?column? 
+----------
+ f
+(1 row)
+
+select array[1,3] <@ arraymultirange(arrayrange(array[1,2], array[2,1]));
+ ?column? 
+----------
+ t
+(1 row)
+
+--
+-- Ranges of composites
+--
+create type two_ints as (a int, b int);
+create type two_ints_range as range (subtype = two_ints);
+-- with force_parallel_mode on, this exercises tqueue.c's range remapping
+select *, row_to_json(upper(t)) as u from
+  (values (two_ints_multirange(two_ints_range(row(1,2), row(3,4)))),
+          (two_ints_multirange(two_ints_range(row(5,6), row(7,8))))) v(t);
+          t          |       u       
+---------------------+---------------
+ {["(1,2)","(3,4)")} | {"a":3,"b":4}
+ {["(5,6)","(7,8)")} | {"a":7,"b":8}
+(2 rows)
+
+drop type two_ints cascade;
+NOTICE:  drop cascades to type two_ints_range
+--
+-- Check behavior when subtype lacks a hash function
+--
+set enable_sort = off;  -- try to make it pick a hash setop implementation
+select '{(2,5)}'::cashmultirange except select '{(5,6)}'::cashmultirange;
+ cashmultirange  
+-----------------
+ {($2.00,$5.00)}
+(1 row)
+
+reset enable_sort;
+--
+-- OUT/INOUT/TABLE functions
+--
+-- infer anymultirange from anymultirange
+create function mr_outparam_succeed(i anymultirange, out r anymultirange, out t text)
+  as $$ select $1, 'foo'::text $$ language sql;
+select * from mr_outparam_succeed(int4multirange(int4range(1,2)));
+    r    |  t  
+---------+-----
+ {[1,2)} | foo
+(1 row)
+
+-- infer anyarray from anymultirange
+create function mr_outparam_succeed2(i anymultirange, out r anyarray, out t text)
+  as $$ select ARRAY[upper($1)], 'foo'::text $$ language sql;
+select * from mr_outparam_succeed2(int4multirange(int4range(1,2)));
+  r  |  t  
+-----+-----
+ {2} | foo
+(1 row)
+
+-- infer anyrange from anymultirange
+create function mr_outparam_succeed3(i anymultirange, out r anyrange, out t text)
+  as $$ select range_merge($1), 'foo'::text $$ language sql;
+select * from mr_outparam_succeed3(int4multirange(int4range(1,2)));
+   r   |  t  
+-------+-----
+ [1,2) | foo
+(1 row)
+
+-- infer anymultirange from anyrange
+create function mr_outparam_succeed4(i anyrange, out r anymultirange, out t text)
+  as $$ select multirange($1), 'foo'::text $$ language sql;
+select * from mr_outparam_succeed4(int4range(1,2));
+    r    |  t  
+---------+-----
+ {[1,2)} | foo
+(1 row)
+
+-- infer anyelement from anymultirange
+create function mr_inoutparam_succeed(out i anyelement, inout r anymultirange)
+  as $$ select upper($1), $1 $$ language sql;
+select * from mr_inoutparam_succeed(int4multirange(int4range(1,2)));
+ i |    r    
+---+---------
+ 2 | {[1,2)}
+(1 row)
+
+-- infer anyelement+anymultirange from anyelement+anymultirange
+create function mr_table_succeed(i anyelement, r anymultirange) returns table(i anyelement, r anymultirange)
+  as $$ select $1, $2 $$ language sql;
+select * from mr_table_succeed(123, int4multirange(int4range(1,11)));
+  i  |    r     
+-----+----------
+ 123 | {[1,11)}
+(1 row)
+
+-- use anymultirange in plpgsql
+create function mr_polymorphic(i anyrange) returns anymultirange
+  as $$ begin return multirange($1); end; $$ language plpgsql;
+select mr_polymorphic(int4range(1, 4));
+ mr_polymorphic 
+----------------
+ {[1,4)}
+(1 row)
+
+-- should fail
+create function mr_outparam_fail(i anyelement, out r anymultirange, out t text)
+  as $$ select '[1,10]', 'foo' $$ language sql;
+ERROR:  cannot determine result data type
+DETAIL:  A function returning "anyrange" or "anymultirange" must have at least one "anyrange" or "anymultirange" argument.
+--should fail
+create function mr_inoutparam_fail(inout i anyelement, out r anymultirange)
+  as $$ select $1, '[1,10]' $$ language sql;
+ERROR:  cannot determine result data type
+DETAIL:  A function returning "anyrange" or "anymultirange" must have at least one "anyrange" or "anymultirange" argument.
+--should fail
+create function mr_table_fail(i anyelement) returns table(i anyelement, r anymultirange)
+  as $$ select $1, '[1,10]' $$ language sql;
+ERROR:  cannot determine result data type
+DETAIL:  A function returning "anyrange" or "anymultirange" must have at least one "anyrange" or "anymultirange" argument.
diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out
index fb6c029e3d..f4b45a86b5 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
@@ -2139,13 +2154,14 @@ ORDER BY 1, 2, 3;
                     | float_ops        | float4_ops       | real
                     | float_ops        | float8_ops       | double precision
                     | jsonb_ops        | jsonb_ops        | jsonb
+                    | multirange_ops   | multirange_ops   | anymultirange
                     | numeric_ops      | numeric_ops      | numeric
                     | range_ops        | range_ops        | anyrange
                     | record_image_ops | record_image_ops | record
                     | record_ops       | record_ops       | record
                     | tsquery_ops      | tsquery_ops      | tsquery
                     | tsvector_ops     | tsvector_ops     | tsvector
-(14 rows)
+(15 rows)
 
 -- **************** pg_index ****************
 -- Look for illegal values in pg_index fields.
diff --git a/src/test/regress/expected/rangetypes.out b/src/test/regress/expected/rangetypes.out
index 220f2d96cb..6d00a3efab 100644
--- a/src/test/regress/expected/rangetypes.out
+++ b/src/test/regress/expected/rangetypes.out
@@ -518,6 +518,24 @@ select numrange(1.0, 2.0) * numrange(2.5, 3.0);
  empty
 (1 row)
 
+select range_intersect_agg(nr) from numrange_test;
+ range_intersect_agg 
+---------------------
+ empty
+(1 row)
+
+select range_intersect_agg(nr) from numrange_test where false;
+ range_intersect_agg 
+---------------------
+ 
+(1 row)
+
+select range_intersect_agg(nr) from numrange_test where nr @> 4.0;
+ range_intersect_agg 
+---------------------
+ [3,5)
+(1 row)
+
 create table numrange_test2(nr numrange);
 create index numrange_test2_hash_idx on numrange_test2 using hash (nr);
 INSERT INTO numrange_test2 VALUES('[, 5)');
@@ -1371,7 +1389,7 @@ drop function anyarray_anyrange_func(anyarray, anyrange);
 create function bogus_func(anyelement)
   returns anyrange as 'select int4range(1,10)' language sql;
 ERROR:  cannot determine result data type
-DETAIL:  A function returning "anyrange" must have at least one "anyrange" argument.
+DETAIL:  A function returning "anyrange" or "anymultirange" must have at least one "anyrange" or "anymultirange" argument.
 -- should fail
 create function bogus_func(int)
   returns anyrange as 'select int4range(1,10)' language sql;
@@ -1482,6 +1500,7 @@ reset enable_sort;
 --
 -- OUT/INOUT/TABLE functions
 --
+-- infer anyrange from anyrange
 create function outparam_succeed(i anyrange, out r anyrange, out t text)
   as $$ select $1, 'foo'::text $$ language sql;
 select * from outparam_succeed(int4range(1,2));
@@ -1490,6 +1509,16 @@ select * from outparam_succeed(int4range(1,2));
  [1,2) | foo
 (1 row)
 
+-- infer anyarray from anyrange
+create function outparam_succeed2(i anyrange, out r anyarray, out t text)
+  as $$ select ARRAY[upper($1)], 'foo'::text $$ language sql;
+select * from outparam_succeed2(int4range(int4range(1,2)));
+  r  |  t  
+-----+-----
+ {2} | foo
+(1 row)
+
+-- infer anyelement from anyrange
 create function inoutparam_succeed(out i anyelement, inout r anyrange)
   as $$ select upper($1), $1 $$ language sql;
 select * from inoutparam_succeed(int4range(1,2));
@@ -1498,6 +1527,7 @@ select * from inoutparam_succeed(int4range(1,2));
  2 | [1,2)
 (1 row)
 
+-- infer anyelement+anyrange from anyelement+anyrange
 create function table_succeed(i anyelement, r anyrange) returns table(i anyelement, r anyrange)
   as $$ select $1, $2 $$ language sql;
 select * from table_succeed(123, int4range(1,11));
@@ -1510,14 +1540,14 @@ select * from table_succeed(123, int4range(1,11));
 create function outparam_fail(i anyelement, out r anyrange, out t text)
   as $$ select '[1,10]', 'foo' $$ language sql;
 ERROR:  cannot determine result data type
-DETAIL:  A function returning "anyrange" must have at least one "anyrange" argument.
+DETAIL:  A function returning "anyrange" or "anymultirange" must have at least one "anyrange" or "anymultirange" argument.
 --should fail
 create function inoutparam_fail(inout i anyelement, out r anyrange)
   as $$ select $1, '[1,10]' $$ language sql;
 ERROR:  cannot determine result data type
-DETAIL:  A function returning "anyrange" must have at least one "anyrange" argument.
+DETAIL:  A function returning "anyrange" or "anymultirange" must have at least one "anyrange" or "anymultirange" argument.
 --should fail
 create function table_fail(i anyelement) returns table(i anyelement, r anyrange)
   as $$ select $1, '[1,10]' $$ language sql;
 ERROR:  cannot determine result data type
-DETAIL:  A function returning "anyrange" must have at least one "anyrange" argument.
+DETAIL:  A function returning "anyrange" or "anymultirange" must have at least one "anyrange" or "anymultirange" argument.
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index 192445878d..d9ce961be2 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -92,6 +92,7 @@ num_exp_sqrt|t
 num_exp_sub|t
 num_input_test|f
 num_result|f
+nummultirange_test|t
 numrange_test|t
 onek|t
 onek2|t
@@ -172,6 +173,7 @@ quad_poly_tbl|t
 radix_text_tbl|t
 ramp|f
 real_city|f
+reservations|f
 road|t
 shighway|t
 slow_emp4000|f
diff --git a/src/test/regress/expected/type_sanity.out b/src/test/regress/expected/type_sanity.out
index cd7fc03b04..60a9b4d22f 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.rngmultitypid
+FROM pg_range p1
+WHERE p1.rngmultitypid IS NULL OR p1.rngmultitypid = 0;
+ rngtypid | rngsubtype | rngmultitypid 
+----------+------------+---------------
+(0 rows)
+
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index d2b17dd3ea..5355da7b01 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 acba391332..1f677f63af 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..0f70919e1a
--- /dev/null
+++ b/src/test/regress/sql/multirangetypes.sql
@@ -0,0 +1,630 @@
+-- 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'));
+
+--
+-- test casts, both a built-in range type and a user-defined one:
+--
+select 'empty'::int4range::int4multirange;
+select int4range(1, 3)::int4multirange;
+select int4range(1, null)::int4multirange;
+select int4range(null, null)::int4multirange;
+select 'empty'::textrange::textmultirange;
+select textrange('a', 'c')::textmultirange;
+select textrange('a', null)::textmultirange;
+select textrange(null, null)::textmultirange;
+
+--
+-- create some test data and test the operators
+--
+
+CREATE TABLE nummultirange_test (nmr NUMMULTIRANGE);
+CREATE INDEX nummultirange_test_btree ON nummultirange_test(nmr);
+
+INSERT INTO nummultirange_test VALUES('{}');
+INSERT INTO nummultirange_test VALUES('{[,)}');
+INSERT INTO nummultirange_test VALUES('{[3,]}');
+INSERT INTO nummultirange_test VALUES('{[,), [3,]}');
+INSERT INTO nummultirange_test VALUES('{[, 5)}');
+INSERT INTO nummultirange_test VALUES(nummultirange());
+INSERT INTO nummultirange_test VALUES(nummultirange(variadic '{}'::numrange[]));
+INSERT INTO nummultirange_test VALUES(nummultirange(numrange(1.1, 2.2)));
+INSERT INTO nummultirange_test VALUES('{empty}');
+INSERT INTO nummultirange_test VALUES(nummultirange(numrange(1.7, 1.7, '[]'), numrange(1.7, 1.9)));
+INSERT INTO nummultirange_test VALUES(nummultirange(numrange(1.7, 1.7, '[]'), numrange(1.9, 2.1)));
+
+SELECT nmr, isempty(nmr), lower(nmr), upper(nmr) FROM nummultirange_test ORDER BY nmr;
+SELECT nmr, lower_inc(nmr), lower_inf(nmr), upper_inc(nmr), upper_inf(nmr) FROM nummultirange_test ORDER BY nmr;
+
+SELECT * FROM nummultirange_test WHERE nmr = '{}';
+SELECT * FROM nummultirange_test WHERE nmr = '{(,5)}';
+SELECT * FROM nummultirange_test WHERE nmr = '{[3,)}';
+SELECT * FROM nummultirange_test WHERE nmr = '{[1.7,1.7]}';
+SELECT * FROM nummultirange_test WHERE nmr = '{[1.7,1.7],[1.9,2.1)}';
+SELECT * FROM nummultirange_test WHERE nmr < '{}';
+SELECT * FROM nummultirange_test WHERE nmr < '{[-1000.0, -1000.0]}';
+SELECT * FROM nummultirange_test WHERE nmr < '{[0.0, 1.0]}';
+SELECT * FROM nummultirange_test WHERE nmr < '{[1000.0, 1001.0]}';
+SELECT * FROM nummultirange_test WHERE nmr <= '{}';
+SELECT * FROM nummultirange_test WHERE nmr <= '{[3,)}';
+SELECT * FROM nummultirange_test WHERE nmr >= '{}';
+SELECT * FROM nummultirange_test WHERE nmr >= '{[3,)}';
+SELECT * FROM nummultirange_test WHERE nmr > '{}';
+SELECT * FROM nummultirange_test WHERE nmr > '{[-1000.0, -1000.0]}';
+SELECT * FROM nummultirange_test WHERE nmr > '{[0.0, 1.0]}';
+SELECT * FROM nummultirange_test WHERE nmr > '{[1000.0, 1001.0]}';
+SELECT * FROM nummultirange_test WHERE nmr <> '{}';
+SELECT * FROM nummultirange_test WHERE nmr <> '{(,5)}';
+
+select nummultirange(numrange(2.0, 1.0));
+select nummultirange(numrange(5.0, 6.0), numrange(1.0, 2.0));
+
+-- overlaps
+SELECT * FROM nummultirange_test WHERE range_overlaps_multirange(numrange(4.0, 4.2), nmr);
+SELECT * FROM nummultirange_test WHERE numrange(4.0, 4.2) && nmr;
+SELECT * FROM nummultirange_test WHERE multirange_overlaps_range(nmr, numrange(4.0, 4.2));
+SELECT * FROM nummultirange_test WHERE nmr && numrange(4.0, 4.2);
+SELECT * FROM nummultirange_test WHERE multirange_overlaps_multirange(nmr, nummultirange(numrange(4.0, 4.2), numrange(6.0, 7.0)));
+SELECT * FROM nummultirange_test WHERE nmr && nummultirange(numrange(4.0, 4.2), numrange(6.0, 7.0));
+SELECT * FROM nummultirange_test WHERE nmr && nummultirange(numrange(6.0, 7.0));
+SELECT * FROM nummultirange_test WHERE nmr && nummultirange(numrange(6.0, 7.0), numrange(8.0, 9.0));
+
+-- mr contains x
+SELECT * FROM nummultirange_test WHERE multirange_contains_elem(nmr, 4.0);
+SELECT * FROM nummultirange_test WHERE nmr @> 4.0;
+SELECT * FROM nummultirange_test WHERE multirange_contains_range(nmr, numrange(4.0, 4.2));
+SELECT * FROM nummultirange_test WHERE nmr @> numrange(4.0, 4.2);
+SELECT * FROM nummultirange_test WHERE multirange_contains_multirange(nmr, '{[4.0,4.2), [6.0, 8.0)}');
+SELECT * FROM nummultirange_test WHERE nmr @> '{[4.0,4.2), [6.0, 8.0)}'::nummultirange;
+
+-- x is contained by mr
+SELECT * FROM nummultirange_test WHERE elem_contained_by_multirange(4.0, nmr);
+SELECT * FROM nummultirange_test WHERE 4.0 <@ nmr;
+SELECT * FROM nummultirange_test WHERE range_contained_by_multirange(numrange(4.0, 4.2), nmr);
+SELECT * FROM nummultirange_test WHERE numrange(4.0, 4.2) <@ nmr;
+SELECT * FROM nummultirange_test WHERE multirange_contained_by_multirange('{[4.0,4.2), [6.0, 8.0)}', nmr);
+SELECT * FROM nummultirange_test WHERE '{[4.0,4.2), [6.0, 8.0)}'::nummultirange <@ nmr;
+
+-- overlaps
+SELECT 'empty'::numrange && nummultirange();
+SELECT 'empty'::numrange && nummultirange(numrange(1,2));
+SELECT nummultirange() && 'empty'::numrange;
+SELECT nummultirange(numrange(1,2)) && 'empty'::numrange;
+SELECT nummultirange() && nummultirange();
+SELECT nummultirange() && nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) && nummultirange();
+SELECT nummultirange(numrange(3,4)) && nummultirange(numrange(1,2), numrange(7,8));
+SELECT nummultirange(numrange(1,2), numrange(7,8)) && nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(3,4)) && nummultirange(numrange(1,2), numrange(3.5,8));
+SELECT nummultirange(numrange(1,2), numrange(3.5,8)) && numrange(3,4);
+SELECT nummultirange(numrange(1,2), numrange(3.5,8)) && nummultirange(numrange(3,4));
+
+-- contains
+SELECT nummultirange() @> nummultirange();
+SELECT nummultirange() @> 'empty'::numrange;
+SELECT nummultirange(numrange(null,null)) @> numrange(1,2);
+SELECT nummultirange(numrange(null,null)) @> numrange(null,2);
+SELECT nummultirange(numrange(null,null)) @> numrange(2,null);
+SELECT nummultirange(numrange(null,5)) @> numrange(null,3);
+SELECT nummultirange(numrange(null,5)) @> numrange(null,8);
+SELECT nummultirange(numrange(5,null)) @> numrange(8,null);
+SELECT nummultirange(numrange(5,null)) @> numrange(3,null);
+SELECT nummultirange(numrange(1,5)) @> numrange(8,9);
+SELECT nummultirange(numrange(1,5)) @> numrange(3,9);
+SELECT nummultirange(numrange(1,5)) @> numrange(1,4);
+SELECT nummultirange(numrange(1,5)) @> numrange(1,5);
+SELECT nummultirange(numrange(-4,-2), numrange(1,5)) @> numrange(1,5);
+SELECT nummultirange(numrange(1,5), numrange(8,9)) @> numrange(1,5);
+SELECT nummultirange(numrange(1,5), numrange(8,9)) @> numrange(6,7);
+SELECT nummultirange(numrange(1,5), numrange(6,9)) @> numrange(6,7);
+SELECT '{[1,5)}'::nummultirange @> '{[1,5)}';
+SELECT '{[-4,-2), [1,5)}'::nummultirange @> '{[1,5)}';
+SELECT '{[1,5), [8,9)}'::nummultirange @> '{[1,5)}';
+SELECT '{[1,5), [8,9)}'::nummultirange @> '{[6,7)}';
+SELECT '{[1,5), [6,9)}'::nummultirange @> '{[6,7)}';
+
+-- is contained by
+SELECT nummultirange() <@ nummultirange();
+SELECT 'empty'::numrange <@ nummultirange();
+SELECT numrange(1,2) <@ nummultirange(numrange(null,null));
+SELECT numrange(null,2) <@ nummultirange(numrange(null,null));
+SELECT numrange(2,null) <@ nummultirange(numrange(null,null));
+SELECT numrange(null,3) <@ nummultirange(numrange(null,5));
+SELECT numrange(null,8) <@ nummultirange(numrange(null,5));
+SELECT numrange(8,null) <@ nummultirange(numrange(5,null));
+SELECT numrange(3,null) <@ nummultirange(numrange(5,null));
+SELECT numrange(8,9) <@ nummultirange(numrange(1,5));
+SELECT numrange(3,9) <@ nummultirange(numrange(1,5));
+SELECT numrange(1,4) <@ nummultirange(numrange(1,5));
+SELECT numrange(1,5) <@ nummultirange(numrange(1,5));
+SELECT numrange(1,5) <@ nummultirange(numrange(-4,-2), numrange(1,5));
+SELECT numrange(1,5) <@ nummultirange(numrange(1,5), numrange(8,9));
+SELECT numrange(6,7) <@ nummultirange(numrange(1,5), numrange(8,9));
+SELECT numrange(6,7) <@ nummultirange(numrange(1,5), numrange(6,9));
+SELECT '{[1,5)}' <@ '{[1,5)}'::nummultirange;
+SELECT '{[1,5)}' <@ '{[-4,-2), [1,5)}'::nummultirange;
+SELECT '{[1,5)}' <@ '{[1,5), [8,9)}'::nummultirange;
+SELECT '{[6,7)}' <@ '{[1,5), [8,9)}'::nummultirange;
+SELECT '{[6,7)}' <@ '{[1,5), [6,9)}'::nummultirange;
+
+-- overleft
+SELECT 'empty'::numrange &< nummultirange();
+SELECT 'empty'::numrange &< nummultirange(numrange(1,2));
+SELECT nummultirange() &< 'empty'::numrange;
+SELECT nummultirange(numrange(1,2)) &< 'empty'::numrange;
+SELECT nummultirange() &< nummultirange();
+SELECT nummultirange(numrange(1,2)) &< nummultirange();
+SELECT nummultirange() &< nummultirange(numrange(1,2));
+SELECT numrange(6,7) &< nummultirange(numrange(3,4));
+SELECT numrange(1,2) &< nummultirange(numrange(3,4));
+SELECT numrange(1,4) &< nummultirange(numrange(3,4));
+SELECT numrange(1,6) &< nummultirange(numrange(3,4));
+SELECT numrange(3.5,6) &< nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(6,7)) &< numrange(3,4);
+SELECT nummultirange(numrange(1,2)) &< numrange(3,4);
+SELECT nummultirange(numrange(1,4)) &< numrange(3,4);
+SELECT nummultirange(numrange(1,6)) &< numrange(3,4);
+SELECT nummultirange(numrange(3.5,6)) &< numrange(3,4);
+SELECT nummultirange(numrange(6,7)) &< nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,2)) &< nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,4)) &< nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,6)) &< nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(3.5,6)) &< nummultirange(numrange(3,4));
+
+-- overright
+SELECT nummultirange() &> 'empty'::numrange;
+SELECT nummultirange(numrange(1,2)) &> 'empty'::numrange;
+SELECT 'empty'::numrange &> nummultirange();
+SELECT 'empty'::numrange &> nummultirange(numrange(1,2));
+SELECT nummultirange() &> nummultirange();
+SELECT nummultirange() &> nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) &> nummultirange();
+SELECT nummultirange(numrange(3,4)) &> numrange(6,7);
+SELECT nummultirange(numrange(3,4)) &> numrange(1,2);
+SELECT nummultirange(numrange(3,4)) &> numrange(1,4);
+SELECT nummultirange(numrange(3,4)) &> numrange(1,6);
+SELECT nummultirange(numrange(3,4)) &> numrange(3.5,6);
+SELECT numrange(3,4) &> nummultirange(numrange(6,7));
+SELECT numrange(3,4) &> nummultirange(numrange(1,2));
+SELECT numrange(3,4) &> nummultirange(numrange(1,4));
+SELECT numrange(3,4) &> nummultirange(numrange(1,6));
+SELECT numrange(3,4) &> nummultirange(numrange(3.5,6));
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(6,7));
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(1,4));
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(1,6));
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(3.5,6));
+
+-- meets
+SELECT 'empty'::numrange -|- nummultirange();
+SELECT 'empty'::numrange -|- nummultirange(numrange(1,2));
+SELECT nummultirange() -|- 'empty'::numrange;
+SELECT nummultirange(numrange(1,2)) -|- 'empty'::numrange;
+SELECT nummultirange() -|- nummultirange();
+SELECT nummultirange(numrange(1,2)) -|- nummultirange();
+SELECT nummultirange() -|- nummultirange(numrange(1,2));
+SELECT numrange(1,2) -|- nummultirange(numrange(2,4));
+SELECT numrange(1,2) -|- nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,2)) -|- numrange(2,4);
+SELECT nummultirange(numrange(1,2)) -|- numrange(3,4);
+SELECT nummultirange(numrange(1,2)) -|- nummultirange(numrange(2,4));
+SELECT nummultirange(numrange(1,2)) -|- nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,2), numrange(5,6)) -|- nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,2), numrange(5,6)) -|- nummultirange(numrange(6,7));
+SELECT nummultirange(numrange(1,2), numrange(5,6)) -|- nummultirange(numrange(8,9));
+SELECT nummultirange(numrange(1,2)) -|- nummultirange(numrange(2,4), numrange(6,7));
+
+-- strictly left
+select 'empty'::numrange << nummultirange();
+select numrange(1,2) << nummultirange();
+select numrange(1,2) << nummultirange(numrange(3,4));
+select numrange(1,2) << nummultirange(numrange(0,4));
+select numrange(1,2) << nummultirange(numrange(0,4), numrange(7,8));
+select nummultirange() << 'empty'::numrange;
+select nummultirange() << numrange(1,2);
+select nummultirange(numrange(3,4)) << numrange(3,6);
+select nummultirange(numrange(0,2)) << numrange(3,6);
+select nummultirange(numrange(0,2), numrange(7,8)) << numrange(3,6);
+select nummultirange(numrange(-4,-2), numrange(0,2)) << numrange(3,6);
+select nummultirange() << nummultirange();
+select nummultirange() << nummultirange(numrange(1,2));
+select nummultirange(numrange(1,2)) << nummultirange();
+select nummultirange(numrange(1,2)) << nummultirange(numrange(1,2));
+select nummultirange(numrange(1,2)) << nummultirange(numrange(3,4));
+select nummultirange(numrange(1,2)) << nummultirange(numrange(3,4), numrange(7,8));
+select nummultirange(numrange(1,2), numrange(4,5)) << nummultirange(numrange(3,4), numrange(7,8));
+
+-- strictly right
+select nummultirange() >> 'empty'::numrange;
+select nummultirange() >> numrange(1,2);
+select nummultirange(numrange(3,4)) >> numrange(1,2);
+select nummultirange(numrange(0,4)) >> numrange(1,2);
+select nummultirange(numrange(0,4), numrange(7,8)) >> numrange(1,2);
+select 'empty'::numrange >> nummultirange();
+select numrange(1,2) >> nummultirange();
+select numrange(3,6) >> nummultirange(numrange(3,4));
+select numrange(3,6) >> nummultirange(numrange(0,2));
+select numrange(3,6) >> nummultirange(numrange(0,2), numrange(7,8));
+select numrange(3,6) >> nummultirange(numrange(-4,-2), numrange(0,2));
+select nummultirange() >> nummultirange();
+select nummultirange(numrange(1,2)) >> nummultirange();
+select nummultirange() >> nummultirange(numrange(1,2));
+select nummultirange(numrange(1,2)) >> nummultirange(numrange(1,2));
+select nummultirange(numrange(3,4)) >> nummultirange(numrange(1,2));
+select nummultirange(numrange(3,4), numrange(7,8)) >> nummultirange(numrange(1,2));
+select nummultirange(numrange(3,4), numrange(7,8)) >> nummultirange(numrange(1,2), numrange(4,5));
+
+-- union
+SELECT nummultirange() + nummultirange();
+SELECT nummultirange() + nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) + nummultirange();
+SELECT nummultirange(numrange(1,2)) + nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) + nummultirange(numrange(2,4));
+SELECT nummultirange(numrange(1,2)) + nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) + nummultirange(numrange(2,4));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) + nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) + nummultirange(numrange(0,9));
+
+-- merge
+SELECT range_merge(nummultirange());
+SELECT range_merge(nummultirange(numrange(1,2)));
+SELECT range_merge(nummultirange(numrange(1,2), numrange(7,8)));
+
+-- minus
+SELECT nummultirange() - nummultirange();
+SELECT nummultirange() - nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) - nummultirange();
+SELECT nummultirange(numrange(1,2), numrange(3,4)) - nummultirange();
+SELECT nummultirange(numrange(1,2)) - nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) - nummultirange(numrange(2,4));
+SELECT nummultirange(numrange(1,2)) - nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(2,3));
+SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(0,8));
+SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(0,2));
+SELECT nummultirange(numrange(1,8)) - nummultirange(numrange(0,2), numrange(3,4));
+SELECT nummultirange(numrange(1,8)) - nummultirange(numrange(2,3), numrange(5,null));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(-2,0));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(2,4));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(3,5));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(0,9));
+SELECT nummultirange(numrange(1,3), numrange(4,5)) - nummultirange(numrange(2,9));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(8,9));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(-2,0), numrange(8,9));
+
+-- intersection
+SELECT nummultirange() * nummultirange();
+SELECT nummultirange() * nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) * nummultirange();
+SELECT '{[1,3)}'::nummultirange * '{[1,5)}'::nummultirange;
+SELECT '{[1,3)}'::nummultirange * '{[0,5)}'::nummultirange;
+SELECT '{[1,3)}'::nummultirange * '{[0,2)}'::nummultirange;
+SELECT '{[1,3)}'::nummultirange * '{[2,5)}'::nummultirange;
+SELECT '{[1,4)}'::nummultirange * '{[2,3)}'::nummultirange;
+SELECT '{[1,4)}'::nummultirange * '{[0,2), [3,5)}'::nummultirange;
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[0,8), [9,12)}'::nummultirange;
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[9,12)}'::nummultirange;
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[-5,-4), [5,6), [9,12)}'::nummultirange;
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[0,2), [3,8), [9,12)}'::nummultirange;
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[0,2), [3,8), [9,12)}'::nummultirange;
+
+--
+-- range_agg function
+--
+create table reservations ( room_id integer not null, booked_during daterange );
+insert into reservations values
+-- 1: has a meets and a gap
+(1, daterange('2018-07-01', '2018-07-07')),
+(1, daterange('2018-07-07', '2018-07-14')),
+(1, daterange('2018-07-20', '2018-07-22')),
+-- 2: just a single row
+(2, daterange('2018-07-01', '2018-07-03')),
+-- 3: one null range
+(3, NULL),
+-- 4: two null ranges
+(4, NULL),
+(4, NULL),
+-- 5: a null range and a non-null range
+(5, NULL),
+(5, daterange('2018-07-01', '2018-07-03')),
+-- 6: has overlap
+(6, daterange('2018-07-01', '2018-07-07')),
+(6, daterange('2018-07-05', '2018-07-10')),
+-- 7: two ranges that meet: no gap or overlap
+(7, daterange('2018-07-01', '2018-07-07')),
+(7, daterange('2018-07-07', '2018-07-14')),
+-- 8: an empty range
+(8, 'empty'::daterange)
+;
+SELECT   room_id, range_agg(booked_during)
+FROM     reservations
+GROUP BY room_id
+ORDER BY room_id;
+
+-- range_agg on a custom range type too
+SELECT  range_agg(r)
+FROM    (VALUES
+          ('[a,c]'::textrange),
+          ('[b,b]'::textrange),
+          ('[c,f]'::textrange),
+          ('[g,h)'::textrange),
+          ('[h,j)'::textrange)
+        ) t(r);
+
+select range_intersect_agg(nmr) from nummultirange_test;
+select range_intersect_agg(nmr) from nummultirange_test where false;
+-- test with just one input:
+select range_intersect_agg(nmr) from (values ('{[1,2]}'::nummultirange)) t(nmr);
+select range_intersect_agg(nmr) from nummultirange_test where nmr @> 4.0;
+
+create table nummultirange_test2(nmr nummultirange);
+create index nummultirange_test2_hash_idx on nummultirange_test2 using hash (nmr);
+
+INSERT INTO nummultirange_test2 VALUES('{[, 5)}');
+INSERT INTO nummultirange_test2 VALUES(nummultirange(numrange(1.1, 2.2)));
+INSERT INTO nummultirange_test2 VALUES(nummultirange(numrange(1.1, 2.2)));
+INSERT INTO nummultirange_test2 VALUES(nummultirange(numrange(1.1, 2.2,'()')));
+INSERT INTO nummultirange_test2 VALUES('{}');
+
+select * from nummultirange_test2 where nmr = '{}';
+select * from nummultirange_test2 where nmr = nummultirange(numrange(1.1, 2.2));
+select * from nummultirange_test2 where nmr = nummultirange(numrange(1.1, 2.3));
+
+set enable_nestloop=t;
+set enable_hashjoin=f;
+set enable_mergejoin=f;
+select * from nummultirange_test natural join nummultirange_test2 order by nmr;
+set enable_nestloop=f;
+set enable_hashjoin=t;
+set enable_mergejoin=f;
+select * from nummultirange_test natural join nummultirange_test2 order by nmr;
+set enable_nestloop=f;
+set enable_hashjoin=f;
+set enable_mergejoin=t;
+select * from nummultirange_test natural join nummultirange_test2 order by nmr;
+
+set enable_nestloop to default;
+set enable_hashjoin to default;
+set enable_mergejoin to default;
+
+DROP TABLE nummultirange_test2;
+
+--
+-- Test user-defined multirange of floats
+--
+
+select '{[123.001, 5.e9)}'::float8multirange @> 888.882::float8;
+create table float8multirange_test(f8mr float8multirange, i int);
+insert into float8multirange_test values(float8multirange(float8range(-100.00007, '1.111113e9')), 42);
+select * from float8multirange_test;
+drop table float8multirange_test;
+
+--
+-- Test multirange types over domains
+--
+
+create domain mydomain as int4;
+create type mydomainrange as range(subtype=mydomain);
+select '{[4,50)}'::mydomainmultirange @> 7::mydomain;
+drop domain mydomain cascade;
+
+--
+-- Test domains over multirange types
+--
+
+create domain restrictedmultirange as int4multirange check (upper(value) < 10);
+select '{[4,5)}'::restrictedmultirange @> 7;
+select '{[4,50)}'::restrictedmultirange @> 7; -- should fail
+drop domain restrictedmultirange;
+
+--
+-- Test multiple multirange types over the same subtype
+--
+
+create type textrange1 as range(subtype=text, collation="C");
+create type textrange2 as range(subtype=text, collation="C");
+
+select textmultirange1(textrange2('a','Z'));  -- should fail
+select textmultirange1(textrange1('a','Z')) @> 'b'::text;
+select textmultirange2(textrange2('a','z')) @> 'b'::text;
+
+drop type textrange1;
+drop type textrange2;
+
+--
+-- Test polymorphic type system
+--
+
+create function anyarray_anymultirange_func(a anyarray, r anymultirange)
+  returns anyelement as 'select $1[1] + lower($2);' language sql;
+
+select anyarray_anymultirange_func(ARRAY[1,2], int4multirange(int4range(10,20)));
+
+-- should fail
+select anyarray_anymultirange_func(ARRAY[1,2], nummultirange(numrange(10,20)));
+
+drop function anyarray_anymultirange_func(anyarray, anymultirange);
+
+-- should fail
+create function bogus_func(anyelement)
+  returns anymultirange as 'select int4multirange(int4range(1,10))' language sql;
+
+-- should fail
+create function bogus_func(int)
+  returns anymultirange as 'select int4multirange(int4range(1,10))' language sql;
+
+create function range_add_bounds(anymultirange)
+  returns anyelement as 'select lower($1) + upper($1)' language sql;
+
+select range_add_bounds(int4multirange(int4range(1, 17)));
+select range_add_bounds(nummultirange(numrange(1.0001, 123.123)));
+
+create function multirangetypes_sql(q anymultirange, b anyarray, out c anyelement)
+  as $$ select upper($1) + $2[1] $$
+  language sql;
+
+select multirangetypes_sql(int4multirange(int4range(1,10)), ARRAY[2,20]);
+select multirangetypes_sql(nummultirange(numrange(1,10)), ARRAY[2,20]);  -- match failure
+
+--
+-- Arrays of multiranges
+--
+
+select ARRAY[nummultirange(numrange(1.1, 1.2)), nummultirange(numrange(12.3, 155.5))];
+
+create table i8mr_array (f1 int, f2 int8multirange[]);
+insert into i8mr_array values (42, array[int8multirange(int8range(1,10)), int8multirange(int8range(2,20))]);
+select * from i8mr_array;
+drop table i8mr_array;
+
+--
+-- Multiranges of arrays
+--
+
+select arraymultirange(arrayrange(ARRAY[1,2], ARRAY[2,1]));
+select arraymultirange(arrayrange(ARRAY[2,1], ARRAY[1,2]));  -- fail
+
+select array[1,1] <@ arraymultirange(arrayrange(array[1,2], array[2,1]));
+select array[1,3] <@ arraymultirange(arrayrange(array[1,2], array[2,1]));
+
+--
+-- Ranges of composites
+--
+
+create type two_ints as (a int, b int);
+create type two_ints_range as range (subtype = two_ints);
+
+-- with force_parallel_mode on, this exercises tqueue.c's range remapping
+select *, row_to_json(upper(t)) as u from
+  (values (two_ints_multirange(two_ints_range(row(1,2), row(3,4)))),
+          (two_ints_multirange(two_ints_range(row(5,6), row(7,8))))) v(t);
+
+drop type two_ints cascade;
+
+--
+-- Check behavior when subtype lacks a hash function
+--
+
+set enable_sort = off;  -- try to make it pick a hash setop implementation
+
+select '{(2,5)}'::cashmultirange except select '{(5,6)}'::cashmultirange;
+
+reset enable_sort;
+
+--
+-- OUT/INOUT/TABLE functions
+--
+
+-- infer anymultirange from anymultirange
+create function mr_outparam_succeed(i anymultirange, out r anymultirange, out t text)
+  as $$ select $1, 'foo'::text $$ language sql;
+
+select * from mr_outparam_succeed(int4multirange(int4range(1,2)));
+
+-- infer anyarray from anymultirange
+create function mr_outparam_succeed2(i anymultirange, out r anyarray, out t text)
+  as $$ select ARRAY[upper($1)], 'foo'::text $$ language sql;
+
+select * from mr_outparam_succeed2(int4multirange(int4range(1,2)));
+
+-- infer anyrange from anymultirange
+create function mr_outparam_succeed3(i anymultirange, out r anyrange, out t text)
+  as $$ select range_merge($1), 'foo'::text $$ language sql;
+select * from mr_outparam_succeed3(int4multirange(int4range(1,2)));
+
+-- infer anymultirange from anyrange
+create function mr_outparam_succeed4(i anyrange, out r anymultirange, out t text)
+  as $$ select multirange($1), 'foo'::text $$ language sql;
+
+select * from mr_outparam_succeed4(int4range(1,2));
+
+-- infer anyelement from anymultirange
+create function mr_inoutparam_succeed(out i anyelement, inout r anymultirange)
+  as $$ select upper($1), $1 $$ language sql;
+
+select * from mr_inoutparam_succeed(int4multirange(int4range(1,2)));
+
+-- infer anyelement+anymultirange from anyelement+anymultirange
+create function mr_table_succeed(i anyelement, r anymultirange) returns table(i anyelement, r anymultirange)
+  as $$ select $1, $2 $$ language sql;
+
+select * from mr_table_succeed(123, int4multirange(int4range(1,11)));
+
+-- use anymultirange in plpgsql
+create function mr_polymorphic(i anyrange) returns anymultirange
+  as $$ begin return multirange($1); end; $$ language plpgsql;
+select mr_polymorphic(int4range(1, 4));
+
+-- should fail
+create function mr_outparam_fail(i anyelement, out r anymultirange, out t text)
+  as $$ select '[1,10]', 'foo' $$ language sql;
+
+--should fail
+create function mr_inoutparam_fail(inout i anyelement, out r anymultirange)
+  as $$ select $1, '[1,10]' $$ language sql;
+
+--should fail
+create function mr_table_fail(i anyelement) returns table(i anyelement, r anymultirange)
+  as $$ select $1, '[1,10]' $$ language sql;
diff --git a/src/test/regress/sql/opr_sanity.sql b/src/test/regress/sql/opr_sanity.sql
index 8351b6469a..1ac7a55925 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/rangetypes.sql b/src/test/regress/sql/rangetypes.sql
index 72d80bc9d4..07c9bcf6d3 100644
--- a/src/test/regress/sql/rangetypes.sql
+++ b/src/test/regress/sql/rangetypes.sql
@@ -118,6 +118,10 @@ select numrange(1.0, 2.0) * numrange(2.0, 3.0);
 select numrange(1.0, 2.0) * numrange(1.5, 3.0);
 select numrange(1.0, 2.0) * numrange(2.5, 3.0);
 
+select range_intersect_agg(nr) from numrange_test;
+select range_intersect_agg(nr) from numrange_test where false;
+select range_intersect_agg(nr) from numrange_test where nr @> 4.0;
+
 create table numrange_test2(nr numrange);
 create index numrange_test2_hash_idx on numrange_test2 using hash (nr);
 
@@ -512,16 +516,25 @@ reset enable_sort;
 -- OUT/INOUT/TABLE functions
 --
 
+-- infer anyrange from anyrange
 create function outparam_succeed(i anyrange, out r anyrange, out t text)
   as $$ select $1, 'foo'::text $$ language sql;
 
 select * from outparam_succeed(int4range(1,2));
 
+-- infer anyarray from anyrange
+create function outparam_succeed2(i anyrange, out r anyarray, out t text)
+  as $$ select ARRAY[upper($1)], 'foo'::text $$ language sql;
+
+select * from outparam_succeed2(int4range(int4range(1,2)));
+
+-- infer anyelement from anyrange
 create function inoutparam_succeed(out i anyelement, inout r anyrange)
   as $$ select upper($1), $1 $$ language sql;
 
 select * from inoutparam_succeed(int4range(1,2));
 
+-- infer anyelement+anyrange from anyelement+anyrange
 create function table_succeed(i anyelement, r anyrange) returns table(i anyelement, r anyrange)
   as $$ select $1, $2 $$ language sql;
 
diff --git a/src/test/regress/sql/type_sanity.sql b/src/test/regress/sql/type_sanity.sql
index fed413bf98..2e34bc8b65 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.rngmultitypid
+FROM pg_range p1
+WHERE p1.rngmultitypid IS NULL OR p1.rngmultitypid = 0;
-- 
2.20.1

v11-0002-silence-compiler-warning.patchtext/x-diff; charset=us-asciiDownload
From b7938e64f45e6b87695357570100118d9cf64c3f Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Thu, 28 Nov 2019 19:08:46 -0300
Subject: [PATCH v11 2/4] silence compiler warning

---
 src/backend/parser/parse_coerce.c | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/src/backend/parser/parse_coerce.c b/src/backend/parser/parse_coerce.c
index 47d17f43a4..ff1310c8f4 100644
--- a/src/backend/parser/parse_coerce.c
+++ b/src/backend/parser/parse_coerce.c
@@ -1593,6 +1593,8 @@ check_generic_type_consistency(const Oid *actual_arg_types,
 		else
 			range_typelem = InvalidOid;	/* keep compiler quiet */
 	}
+	else
+		range_typelem = InvalidOid; /* keep compiler quiet */
 
 	/* Get the element type based on the multirange type, if we have one */
 	if (OidIsValid(multirange_typeid))
-- 
2.20.1

v11-0003-whitespace-tweaks.patchtext/x-diff; charset=us-asciiDownload
From 5e25fb6f93f0b646b85c502aeb7adfa7f5cd334c Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Tue, 3 Mar 2020 15:09:56 -0300
Subject: [PATCH v11 3/4] whitespace tweaks

---
 src/backend/utils/adt/multirangetypes.c | 15 +++++++--------
 src/include/catalog/pg_range.h          |  6 +++---
 2 files changed, 10 insertions(+), 11 deletions(-)

diff --git a/src/backend/utils/adt/multirangetypes.c b/src/backend/utils/adt/multirangetypes.c
index f9dd0378cc..0c9afd5448 100644
--- a/src/backend/utils/adt/multirangetypes.c
+++ b/src/backend/utils/adt/multirangetypes.c
@@ -174,12 +174,11 @@ multirange_in(PG_FUNCTION_ARGS)
 					if (range_capacity == range_count)
 					{
 						range_capacity *= 2;
-						ranges = (RangeType **) repalloc(ranges,
-														 range_capacity * sizeof(RangeType *));
+						ranges = (RangeType **)
+							repalloc(ranges, range_capacity * sizeof(RangeType *));
 					}
 					ranges_seen++;
-					range = DatumGetRangeTypeP(
-											   InputFunctionCall(&cache->proc, range_str_copy,
+					range = DatumGetRangeTypeP(InputFunctionCall(&cache->proc, range_str_copy,
 																 cache->typioparam, typmod));
 					if (!RangeIsEmpty(range))
 						ranges[range_count++] = range;
@@ -376,11 +375,11 @@ multirange_typanalyze(PG_FUNCTION_ARGS)
  * pointer to a type cache entry.
  */
 static MultirangeIOData *
-get_multirange_io_data(FunctionCallInfo fcinfo, Oid mltrngtypid, IOFuncSelector func)
+get_multirange_io_data(FunctionCallInfo fcinfo, Oid rngtypid, IOFuncSelector func)
 {
 	MultirangeIOData *cache = (MultirangeIOData *) fcinfo->flinfo->fn_extra;
 
-	if (cache == NULL || cache->typcache->type_id != mltrngtypid)
+	if (cache == NULL || cache->typcache->type_id != rngtypid)
 	{
 		int16		typlen;
 		bool		typbyval;
@@ -389,9 +388,9 @@ get_multirange_io_data(FunctionCallInfo fcinfo, Oid mltrngtypid, IOFuncSelector
 
 		cache = (MultirangeIOData *) MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
 														sizeof(MultirangeIOData));
-		cache->typcache = lookup_type_cache(mltrngtypid, TYPECACHE_MULTIRANGE_INFO);
+		cache->typcache = lookup_type_cache(rngtypid, TYPECACHE_MULTIRANGE_INFO);
 		if (cache->typcache->rngtype == NULL)
-			elog(ERROR, "type %u is not a multirange type", mltrngtypid);
+			elog(ERROR, "type %u is not a multirange type", rngtypid);
 
 		/* get_type_io_data does more than we need, but is convenient */
 		get_type_io_data(cache->typcache->rngtype->type_id,
diff --git a/src/include/catalog/pg_range.h b/src/include/catalog/pg_range.h
index 3efd463c6d..60a8fde518 100644
--- a/src/include/catalog/pg_range.h
+++ b/src/include/catalog/pg_range.h
@@ -34,6 +34,9 @@ CATALOG(pg_range,3541,RangeRelationId)
 	/* OID of range's element type (subtype) */
 	Oid			rngsubtype BKI_LOOKUP(pg_type);
 
+	/* OID of the range's multirange type */
+	Oid			rngmultitypid BKI_LOOKUP(pg_type);
+
 	/* collation for this range type, or 0 */
 	Oid			rngcollation BKI_DEFAULT(0);
 
@@ -45,9 +48,6 @@ 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			rngmultitypid BKI_LOOKUP(pg_type);
 } FormData_pg_range;
 
 /* ----------------
-- 
2.20.1

v11-0004-Various-tweaks-mostly-naming.patchtext/x-diff; charset=us-asciiDownload
From 1d4292c051526d6d4704ce2a95597a1da82bebbb Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Tue, 11 Feb 2020 20:12:20 -0300
Subject: [PATCH v11 4/4] Various tweaks, mostly naming

Got rid of TYPCATEGORY_MULTIRANGE. It doesn't serve any purpose that I
can see, so I just switched that to TYPCATEGORY_RANGE.

load_multirangetype_info() pointlessly open-coded
get_range_multirange_subtype().  Simplify.

Name changes:

get_multirange_subtype -> get_range_multirange_subtype
  This is more in line with how we name lsyscache.c functions, given
  that the cache it serves is on pg_range.  (Apparently in earlier
  versions of the patch there was a pg_multirange catalog, but not
  anymore)

MULTIRANGETYPE -> RANGEMULTIRANGE (the syscache)
  Mostly the same reasons as above.  The naming also makes us move it
  next to RANGETYPE; they're both in pg_range.  (It was in the place
  where pg_multirange would be, alphabetically.)
---
 src/backend/commands/typecmds.c     |  2 +-
 src/backend/parser/parse_coerce.c   | 10 +++++-----
 src/backend/utils/cache/lsyscache.c | 10 ++++------
 src/backend/utils/cache/syscache.c  | 23 ++++++++++++-----------
 src/backend/utils/cache/typcache.c  | 15 ++-------------
 src/backend/utils/fmgr/funcapi.c    |  4 ++--
 src/include/catalog/pg_type.dat     | 12 ++++++------
 src/include/catalog/pg_type.h       |  1 -
 src/include/utils/lsyscache.h       |  2 +-
 src/include/utils/syscache.h        |  2 +-
 src/include/utils/typcache.h        |  2 +-
 11 files changed, 35 insertions(+), 48 deletions(-)

diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index a8dea8d58e..bd792234f8 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -1589,7 +1589,7 @@ DefineRange(CreateRangeStmt *stmt)
 				   GetUserId(), /* owner's ID */
 				   -1,			/* internal size (always varlena) */
 				   TYPTYPE_MULTIRANGE,	/* type-type (multirange type) */
-				   TYPCATEGORY_MULTIRANGE,	/* type-category (multirange type) */
+				   TYPCATEGORY_RANGE,	/* type-category (range type) */
 				   false,		/* multirange types are never preferred */
 				   DEFAULT_TYPDELIM,	/* array element delimiter */
 				   F_MULTIRANGE_IN, /* input procedure */
diff --git a/src/backend/parser/parse_coerce.c b/src/backend/parser/parse_coerce.c
index ff1310c8f4..27bfc4bf8c 100644
--- a/src/backend/parser/parse_coerce.c
+++ b/src/backend/parser/parse_coerce.c
@@ -1599,7 +1599,7 @@ 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);
+		multirange_typelem = get_range_multirange_subtype(multirange_typeid);
 		if (!OidIsValid(multirange_typelem))
 			return false;		/* should be a multirange, but isn't */
 
@@ -1938,7 +1938,7 @@ enforce_generic_type_consistency(const Oid *actual_arg_types,
 		}
 		else
 		{
-			multirange_typelem = get_multirange_subtype(multirange_typeid);
+			multirange_typelem = get_range_multirange_subtype(multirange_typeid);
 			if (!OidIsValid(multirange_typelem))
 				ereport(ERROR,
 						(errcode(ERRCODE_DATATYPE_MISMATCH),
@@ -2202,7 +2202,7 @@ resolve_generic_type(Oid declared_type,
 		else if (context_declared_type == ANYMULTIRANGEOID)
 		{
 			Oid			context_base_type = getBaseType(context_actual_type);
-			Oid			multirange_typelem = get_multirange_subtype(context_base_type);
+			Oid			multirange_typelem = get_range_multirange_subtype(context_base_type);
 			Oid			range_typelem = get_range_subtype(multirange_typelem);
 			Oid			array_typeid = get_array_type(range_typelem);
 
@@ -2248,7 +2248,7 @@ resolve_generic_type(Oid declared_type,
 		{
 			/* 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);
+			Oid			multirange_typelem = get_range_multirange_subtype(context_base_type);
 
 			if (!OidIsValid(multirange_typelem))
 				ereport(ERROR,
@@ -2281,7 +2281,7 @@ resolve_generic_type(Oid declared_type,
 		else if (context_declared_type == ANYMULTIRANGEOID)
 		{
 			Oid			context_base_type = getBaseType(context_actual_type);
-			Oid			multirange_typelem = get_multirange_subtype(context_base_type);
+			Oid			multirange_typelem = get_range_multirange_subtype(context_base_type);
 
 			if (!OidIsValid(multirange_typelem))
 				ereport(ERROR,
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index 56a145c666..7ba93c43d7 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -3133,7 +3133,7 @@ get_namespace_name_or_temp(Oid nspid)
 		return get_namespace_name(nspid);
 }
 
-/*				---------- PG_RANGE CACHE ----------				 */
+/*				---------- PG_RANGE CACHES ----------				 */
 
 /*
  * get_range_subtype
@@ -3211,20 +3211,18 @@ get_range_multirange(Oid rangeOid)
 		return InvalidOid;
 }
 
-/*				---------- PG_MULTIRANGE CACHE ----------				 */
-
 /*
- * get_multirange_subtype
+ * get_range_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)
+get_range_multirange_subtype(Oid multirangeOid)
 {
 	HeapTuple	tp;
 
-	tp = SearchSysCache1(MULTIRANGETYPE, ObjectIdGetDatum(multirangeOid));
+	tp = SearchSysCache1(RANGEMULTIRANGE, ObjectIdGetDatum(multirangeOid));
 	if (HeapTupleIsValid(tp))
 	{
 		Form_pg_range rngtup = (Form_pg_range) GETSTRUCT(tp);
diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c
index fb9179c42a..7654331a59 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -508,17 +508,6 @@ static const struct cachedesc cacheinfo[] = {
 		},
 		4
 	},
-	{RangeRelationId,			/* MULTIRANGE */
-		RangeMultirangeTypidIndexId,
-		1,
-		{
-			Anum_pg_range_rngmultitypid,
-			0,
-			0,
-			0
-		},
-		4
-	},
 	{NamespaceRelationId,		/* NAMESPACENAME */
 		NamespaceNameIndexId,
 		1,
@@ -662,6 +651,18 @@ static const struct cachedesc cacheinfo[] = {
 		},
 		64
 	},
+	{RangeRelationId,			/* RANGEMULTIRANGE */
+		RangeMultirangeTypidIndexId,
+		1,
+		{
+			Anum_pg_range_rngmultitypid,
+			0,
+			0,
+			0
+		},
+		4
+	},
+
 	{RangeRelationId,			/* RANGETYPE */
 		RangeTypidIndexId,
 		1,
diff --git a/src/backend/utils/cache/typcache.c b/src/backend/utils/cache/typcache.c
index 6677bc8bdc..9383b24f65 100644
--- a/src/backend/utils/cache/typcache.c
+++ b/src/backend/utils/cache/typcache.c
@@ -897,7 +897,6 @@ load_rangetype_info(TypeCacheEntry *typentry)
 	typentry->rngelemtype = lookup_type_cache(subtypeOid, 0);
 }
 
-
 /*
  * load_multirangetype_info --- helper routine to set up multirange type
  * information
@@ -905,26 +904,16 @@ load_rangetype_info(TypeCacheEntry *typentry)
 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))
+	rangetypeOid = get_range_multirange_subtype(typentry->type_id);
+	if (!OidIsValid(rangetypeOid))
 		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
  *
diff --git a/src/backend/utils/fmgr/funcapi.c b/src/backend/utils/fmgr/funcapi.c
index defc801f7a..afc617939f 100644
--- a/src/backend/utils/fmgr/funcapi.c
+++ b/src/backend/utils/fmgr/funcapi.c
@@ -683,7 +683,7 @@ resolve_polymorphic_tupdesc(TupleDesc tupdesc, oidvector *declared_args,
 											  ANYRANGEOID);
 
 			/* check for inconsistent range and multirange results */
-			rngtype = get_multirange_subtype(mltrngtype);
+			rngtype = get_range_multirange_subtype(mltrngtype);
 
 			if (OidIsValid(anyrange_type) && anyrange_type != rngtype)
 				return false;
@@ -1020,7 +1020,7 @@ resolve_polymorphic_argtypes(int numargs, Oid *argtypes, char *argmodes,
 											  ANYRANGEOID);
 
 			/* check for inconsistent range and multirange results */
-			rngtype = get_multirange_subtype(mltrngtype);
+			rngtype = get_range_multirange_subtype(mltrngtype);
 
 			if (OidIsValid(anyrange_type) && anyrange_type != rngtype)
 				return false;
diff --git a/src/include/catalog/pg_type.dat b/src/include/catalog/pg_type.dat
index beaa70dce6..7d4bf4c5d8 100644
--- a/src/include/catalog/pg_type.dat
+++ b/src/include/catalog/pg_type.dat
@@ -489,34 +489,34 @@
 # 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',
+  typcategory => 'R', 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',
+  typcategory => 'R', 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',
+  typcategory => 'R', 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',
+  typcategory => 'R', 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',
+  typcategory => 'R', 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',
+  typcategory => 'R', typinput => 'multirange_in', typoutput => 'multirange_out',
   typreceive => 'multirange_recv', typsend => 'multirange_send',
   typanalyze => 'multirange_typanalyze', typalign => 'd', typstorage => 'x' },
 
diff --git a/src/include/catalog/pg_type.h b/src/include/catalog/pg_type.h
index 600ac58c9d..c113320b9c 100644
--- a/src/include/catalog/pg_type.h
+++ b/src/include/catalog/pg_type.h
@@ -270,7 +270,6 @@ 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'
diff --git a/src/include/utils/lsyscache.h b/src/include/utils/lsyscache.h
index 7d1674a3b6..fffd4c5a45 100644
--- a/src/include/utils/lsyscache.h
+++ b/src/include/utils/lsyscache.h
@@ -182,7 +182,7 @@ extern char *get_namespace_name_or_temp(Oid nspid);
 extern Oid	get_range_subtype(Oid rangeOid);
 extern Oid	get_range_collation(Oid rangeOid);
 extern Oid	get_range_multirange(Oid rangeOid);
-extern Oid	get_multirange_subtype(Oid multirangeOid);
+extern Oid	get_range_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/syscache.h b/src/include/utils/syscache.h
index bc51bbeb54..e8f393520a 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -66,7 +66,6 @@ enum SysCacheIdentifier
 	INDEXRELID,
 	LANGNAME,
 	LANGOID,
-	MULTIRANGETYPE,
 	NAMESPACENAME,
 	NAMESPACEOID,
 	OPERNAMENSP,
@@ -80,6 +79,7 @@ enum SysCacheIdentifier
 	PUBLICATIONOID,
 	PUBLICATIONREL,
 	PUBLICATIONRELMAP,
+	RANGEMULTIRANGE,
 	RANGETYPE,
 	RELNAMENSP,
 	RELOID,
diff --git a/src/include/utils/typcache.h b/src/include/utils/typcache.h
index fe240ea004..9957be4640 100644
--- a/src/include/utils/typcache.h
+++ b/src/include/utils/typcache.h
@@ -101,7 +101,7 @@ typedef struct TypeCacheEntry
 	/*
 	 * Fields computed when TYPCACHE_MULTIRANGE_INFO is required.
 	 */
-	struct TypeCacheEntry *rngtype;
+	struct TypeCacheEntry *rngtype;		/* underlying range type */
 
 	/*
 	 * Domain's base type and typmod if it's a domain type.  Zeroes if not
-- 
2.20.1

#86Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Alvaro Herrera (#85)
Re: range_agg

I came across an interesting thing, namely multirange_canonicalize()'s
use of qsort_arg with a callback of range_compare(). range_compare()
calls range_deserialize() (non-trivial parsing) for each input range;
multirange_canonicalize() later does a few extra deserialize calls of
its own. Call me a premature optimization guy if you will, but I think
it makes sense to have a different struct (let's call it
"InMemoryRange") which stores the parsed representation of each range;
then we can deserialize all ranges up front, and use that as many times
as needed, without having to deserialize each range every time.

While I'm at this, why not name the new file simply multiranges.c
instead of multirangetypes.c?

--
�lvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

#87Paul Jungwirth
pj@illuminatedcomputing.com
In reply to: Alvaro Herrera (#86)
Re: range_agg

Thanks for looking at this again!

On 3/4/20 1:33 PM, Alvaro Herrera wrote:

I came across an interesting thing, namely multirange_canonicalize()'s
use of qsort_arg with a callback of range_compare(). range_compare()
calls range_deserialize() (non-trivial parsing) for each input range;
multirange_canonicalize() later does a few extra deserialize calls of
its own. Call me a premature optimization guy if you will, but I think
it makes sense to have a different struct (let's call it
"InMemoryRange") which stores the parsed representation of each range;
then we can deserialize all ranges up front, and use that as many times
as needed, without having to deserialize each range every time.

I don't know, this sounds like a drastic change. I agree that
multirange_deserialize and range_deserialize do a lot of copying (not
really any parsing though, and they both assume their inputs are already
de-TOASTED). But they are used very extensively, so if you wanted to
remove them you'd have to rewrite a lot.

I interpreted the intention of range_deserialize to be a way to keep the
range struct fairly "private" and give a standard interface to
extracting its attributes. Its motive seems akin to deconstruct_array.
So I wrote multirange_deserialize to follow that principle. Both
functions also handle memory alignment issues for you. With
multirange_deserialize, there isn't actually much structure (just the
list of ranges), so perhaps you could more easily omit it and give
callers direct access into the multirange contents. That still seems
risky though, and less well encapsulated.

My preference would be to see if these functions are really a
performance problem first, and only redo the in-memory structures if
they are. Also that seems like something you could do as a separate
project. (I wouldn't mind working on it myself, although I'd prefer to
do actual temporal database features first.) There are no
backwards-compatibility concerns to changing the in-memory structure,
right? (Even if there are, it's too late to avoid them for ranges.)

While I'm at this, why not name the new file simply multiranges.c
instead of multirangetypes.c?

As someone who doesn't do a lot of Postgres hacking, I tried to follow
the approach in rangetypes.c as closely as I could, especially for
naming things. So I named the file multirangetypes.c because there was
already rangetypes.c. But also I can see how the "types" emphasizes that
ranges and multiranges are not concrete types themselves, but more like
abstract data types or generics (like arrays).

Yours,

--
Paul ~{:-)
pj@illuminatedcomputing.com

#88Tom Lane
tgl@sss.pgh.pa.us
In reply to: Alvaro Herrera (#85)
Re: range_agg

Alvaro Herrera <alvherre@2ndquadrant.com> writes:

[ v11 patches ]

The cfbot isn't too happy with this; it's getting differently-ordered
results than you apparently did for the list of owned objects in
dependency.out's DROP OWNED BY test. Not sure why that should be ---
it seems like af6550d34 should have ensured that there's only one
possible ordering.

However, what I'm on about right at the moment is that I don't think
there should be any delta in that test at all. As far as I can see,
the design idea here is that multiranges will be automatically created
over range types, and the user doesn't need to do that. To my mind,
that means that they're an implementation detail and should not show up as
separately-owned objects, any more than an autogenerated array type does.
So somewhere there's a missing bit of code, or more than one missing bit,
to make multiranges act as derived types, the way arrays are.

regards, tom lane

#89Tom Lane
tgl@sss.pgh.pa.us
In reply to: Tom Lane (#88)
Re: range_agg

I wrote:

However, what I'm on about right at the moment is that I don't think
there should be any delta in that test at all. As far as I can see,
the design idea here is that multiranges will be automatically created
over range types, and the user doesn't need to do that. To my mind,
that means that they're an implementation detail and should not show up as
separately-owned objects, any more than an autogenerated array type does.

Actually ... have you given any thought to just deciding that ranges and
multiranges are the same type? That is, any range can now potentially
contain multiple segments? That would eliminate a whole lot of the
tedious infrastructure hacking involved in this patch, and let you focus
on the actually-useful functionality.

It's possible that this is a bad idea. It bears a lot of similarity,
I guess, to the way that Postgres doesn't consider arrays of different
dimensionality to be distinct types. That has some advantages but it
surely also has downsides. I think on the whole the advantages win,
and I feel like that might also be the case here.

The gating requirement for this would be to make sure that a plain
range and a multirange can be told apart by contents. The first idea that
comes to mind is to repurpose the allegedly-unused RANGE_xB_NULL bits in
the flag byte at the end of the datum. If one of them is set, then it's a
multirange, and we use a different interpretation of the bytes between the
type OID and the flag byte.

Assuming that that's ok, it seems like we could consider the traditional
range functions like lower() and upper() to report on the first or last
range bound in a multirange --- essentially, they ignore any "holes"
that exist inside the range. And the new functions for multiranges
act much like array slicing, in that they give you back pieces of a range
that aren't actually of a distinct type.

regards, tom lane

#90Tom Lane
tgl@sss.pgh.pa.us
In reply to: Tom Lane (#89)
Re: range_agg

I wrote:

Actually ... have you given any thought to just deciding that ranges and
multiranges are the same type? That is, any range can now potentially
contain multiple segments? That would eliminate a whole lot of the
tedious infrastructure hacking involved in this patch, and let you focus
on the actually-useful functionality.

Also, this would allow us to remove at least one ugly misfeature:

regression=# select '[1,2]'::int4range + '[3,10)'::int4range;
?column?
----------
[1,10)
(1 row)

regression=# select '[1,2]'::int4range + '[4,10)'::int4range;
ERROR: result of range union would not be contiguous

If the result of range_union can be a multirange as easily as not,
we would no longer have to throw an error here.

regards, tom lane

#91Pavel Stehule
pavel.stehule@gmail.com
In reply to: Tom Lane (#90)
Re: range_agg

so 7. 3. 2020 v 22:20 odesílatel Tom Lane <tgl@sss.pgh.pa.us> napsal:

I wrote:

Actually ... have you given any thought to just deciding that ranges and
multiranges are the same type? That is, any range can now potentially
contain multiple segments? That would eliminate a whole lot of the
tedious infrastructure hacking involved in this patch, and let you focus
on the actually-useful functionality.

Also, this would allow us to remove at least one ugly misfeature:

regression=# select '[1,2]'::int4range + '[3,10)'::int4range;
?column?
----------
[1,10)
(1 row)

regression=# select '[1,2]'::int4range + '[4,10)'::int4range;
ERROR: result of range union would not be contiguous

If the result of range_union can be a multirange as easily as not,
we would no longer have to throw an error here.

I think this behave is correct. Sometimes you should to get only one range
- and this check is a protection against not continuous range.

if you expect multirange, then do

select '[1,2]'::int4range::multirange + '[4,10)'::int4range;

Regards

Pavel

Show quoted text

regards, tom lane

#92David Fetter
david@fetter.org
In reply to: Tom Lane (#89)
Re: range_agg

On Sat, Mar 07, 2020 at 04:06:32PM -0500, Tom Lane wrote:

I wrote:

However, what I'm on about right at the moment is that I don't think
there should be any delta in that test at all. As far as I can see,
the design idea here is that multiranges will be automatically created
over range types, and the user doesn't need to do that. To my mind,
that means that they're an implementation detail and should not show up as
separately-owned objects, any more than an autogenerated array type does.

Actually ... have you given any thought to just deciding that ranges and
multiranges are the same type? That is, any range can now potentially
contain multiple segments? That would eliminate a whole lot of the
tedious infrastructure hacking involved in this patch, and let you focus
on the actually-useful functionality.

If we're changing range types rather than constructing a new
multi-range layer atop them, I think it would be helpful to have some
way to figure out quickly whether this new range type was contiguous.
One way to do that would be to include a "range cardinality" in the
data structure which be the number of left ends in it.

One of the things I'd pictured doing with multiranges was along the
lines of a "full coverage" constraint like "During a shift, there can
be no interval that's not covered," which would correspond to a "range
cardinality" of 1.

I confess I'm getting a little twitchy about the idea of eliding the
cases of "one" and "many", though.

Assuming that that's ok, it seems like we could consider the traditional
range functions like lower() and upper() to report on the first or last
range bound in a multirange --- essentially, they ignore any "holes"
that exist inside the range. And the new functions for multiranges
act much like array slicing, in that they give you back pieces of a range
that aren't actually of a distinct type.

So new functions along the lines of lowers(), uppers(), opennesses(),
etc.? I guess this could be extended as needs emerge.

There's another use case not yet covered here that could make this
even more complex, we should probably plan for it: multi-ranges with
weights.

For example,

SELECT weighted_range_union(r)
FROM (VALUES('[0,1)'::float8range), ('[0,3)'), '('[2,5)')) AS t(r)

would yield something along the lines of:

(([0,1),1), ([1,3),2), ([3,5),1))

and wedging that into the range type seems messy. Each range would
then have a cardinality, and each range within would have a weight,
all of which would be an increasingly heavy burden on the common case
where there's just a single range.

Enhancing a separate multirange type to have weights seems like a
cleaner path forward.

Given that, I'm -1 on mushing multi-ranges into a special case of
ranges, or /vice versa/.

Best,
David.
--
David Fetter <david(at)fetter(dot)org> http://fetter.org/
Phone: +1 415 235 3778

Remember to vote!
Consider donating to Postgres: http://www.postgresql.org/about/donate

#93Tom Lane
tgl@sss.pgh.pa.us
In reply to: David Fetter (#92)
Re: range_agg

David Fetter <david@fetter.org> writes:

There's another use case not yet covered here that could make this
even more complex, we should probably plan for it: multi-ranges with
weights.

I'm inclined to reject that as completely out of scope. The core
argument for unifying multiranges with ranges, if you ask me, is
to make the data type closed under union. Weights are from some
other universe.

regards, tom lane

#94David Fetter
david@fetter.org
In reply to: Tom Lane (#93)
Re: range_agg

On Sat, Mar 07, 2020 at 06:45:44PM -0500, Tom Lane wrote:

David Fetter <david@fetter.org> writes:

There's another use case not yet covered here that could make this
even more complex, we should probably plan for it: multi-ranges
with weights.

I'm inclined to reject that as completely out of scope. The core
argument for unifying multiranges with ranges, if you ask me, is to
make the data type closed under union. Weights are from some other
universe.

I don't think they are. SQL databases are super useful because they do
bags in addition to sets, so set union isn't the only, or maybe even
the most important, operation over which ranges ought to be closed.

Best,
David.
--
David Fetter <david(at)fetter(dot)org> http://fetter.org/
Phone: +1 415 235 3778

Remember to vote!
Consider donating to Postgres: http://www.postgresql.org/about/donate

#95Isaac Morland
isaac.morland@gmail.com
In reply to: Pavel Stehule (#91)
Re: range_agg

On Sat, 7 Mar 2020 at 16:27, Pavel Stehule <pavel.stehule@gmail.com> wrote:

so 7. 3. 2020 v 22:20 odesílatel Tom Lane <tgl@sss.pgh.pa.us> napsal:

I wrote:

Actually ... have you given any thought to just deciding that ranges and
multiranges are the same type? That is, any range can now potentially
contain multiple segments? That would eliminate a whole lot of the
tedious infrastructure hacking involved in this patch, and let you focus
on the actually-useful functionality.

I think this behave is correct. Sometimes you should to get only one range
- and this check is a protection against not continuous range.

if you expect multirange, then do

select '[1,2]'::int4range::multirange + '[4,10)'::int4range;

Definitely agreed that range and multirange (or whatever it's called)
should be different. In the work I do I have a number of uses for ranges,
but not (yet) for multiranges. I want to be able to declare a column as
range and be sure that it is just a single range, and then call lower() and
upper() on it and be sure to get just one value in each case; and if I
accidentally try to take the union of ranges where the union isn’t another
range, I want to get an error rather than calculate some weird (in my
context) multirange.

On a related note, I was thinking about this and I don’t think I like
range_agg as a name at all. I know we have array_agg and string_agg but
surely shouldn’t this be called union_agg, and shouldn’t there also be an
intersect_agg? I mean, taking the union isn’t the only possible aggregate
on ranges or multiranges.

#96Tom Lane
tgl@sss.pgh.pa.us
In reply to: Isaac Morland (#95)
Re: range_agg

Isaac Morland <isaac.morland@gmail.com> writes:

so 7. 3. 2020 v 22:20 odesílatel Tom Lane <tgl@sss.pgh.pa.us> napsal:

Actually ... have you given any thought to just deciding that ranges and
multiranges are the same type? That is, any range can now potentially
contain multiple segments?

Definitely agreed that range and multirange (or whatever it's called)
should be different. In the work I do I have a number of uses for ranges,
but not (yet) for multiranges. I want to be able to declare a column as
range and be sure that it is just a single range, and then call lower() and
upper() on it and be sure to get just one value in each case; and if I
accidentally try to take the union of ranges where the union isn’t another
range, I want to get an error rather than calculate some weird (in my
context) multirange.

I do not find that argument convincing at all. Surely you could put
that constraint on your column using "CHECK (numranges(VALUE) <= 1)"
or some such notation.

Also, you're attacking a straw man with respect to lower() and upper();
I did not suggest changing them to return arrays, but rather interpreting
them as returning the lowest or highest endpoint, which I think would be
transparent in most cases. (There would obviously need to be some other
functions that could dissect a multirange more completely.)

The real problem with the proposal as it stands, I think, is exactly
that range union has failure conditions and you have to use some other
operator if you want to get a successful result always. That's an
enormously ugly kluge, and if we'd done it right the first time nobody
would have objected.

Bottom line is that I don't think that we should add a pile of new moving
parts to the type system just because people are afraid of change;
arguably, that's *more* change (and more risk of bugs), not less.
Unifying the types would, for example, get rid of the pesky question
of what promoting a range to multirange should look like exactly,
because it'd be a no-op.

regards, tom lane

#97David G. Johnston
david.g.johnston@gmail.com
In reply to: Alvaro Herrera (#66)
Re: range_agg

On Fri, Dec 20, 2019 at 10:43 AM Alvaro Herrera <alvherre@2ndquadrant.com>
wrote:

I took the liberty of rebasing this series on top of recent branch
master.

In the tests there is:

+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)
+

Aside from the comment they are identical so I'm confused as to why both
tests exist - though I suspect it has to do with the fact that the expected
result would be {[a,b]} since text is discrete.

Also, the current patch set seems a bit undecided on whether it wants to be
truly a multi-range or a range that can report non-contiguous components.
Specifically,

+select '{[a,d), [b,f]}'::textmultirange;
+ textmultirange
+----------------
+ {[a,f]}
+(1 row)

There is a an argument that a multi-range should output {[a,d),[b,f]}. IMO
its arguable that a multi-range container should not try and reduce the
number of contained ranges at all. If that is indeed a desire, which seems
like it is, that feature alone goes a long way to support wanting to just
merge the desired functionality into the existing range type, where the
final output has the minimum number of contiguous ranges possible, rather
than having a separate multirange type.

David J.

#98Robert Haas
robertmhaas@gmail.com
In reply to: Tom Lane (#89)
Re: range_agg

On Sat, Mar 7, 2020 at 4:06 PM Tom Lane <tgl@sss.pgh.pa.us> wrote:

It's possible that this is a bad idea. It bears a lot of similarity,
I guess, to the way that Postgres doesn't consider arrays of different
dimensionality to be distinct types. That has some advantages but it
surely also has downsides. I think on the whole the advantages win,
and I feel like that might also be the case here.

Personally, I'm pretty unhappy with the fact that the array system
conflates arrays with different numbers of dimensions. Like, you end
up having to write array_upper(X, 1) instead of just array_upper(X),
and then you're still left wondering whether whatever you wrote is
going to blow up if somebody sneaks a multidimensional array in there,
or for that matter, an array with a non-standard lower bound. There's
lots of little things like that, where the decision to decorate the
array type with these extra frammishes makes it harder to use for
everybody even though most people don't use (or even want) those
features.

So count me as +1 for keeping range and multirange separate.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

#99Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Alvaro Herrera (#85)
Re: range_agg

I wonder what's the point of multirange arrays. Is there a reason we
create those?

--
�lvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

#100Tom Lane
tgl@sss.pgh.pa.us
In reply to: Alvaro Herrera (#99)
Re: range_agg

Alvaro Herrera <alvherre@2ndquadrant.com> writes:

I wonder what's the point of multirange arrays. Is there a reason we
create those?

That's what we thought about arrays of composites to start with,
too.

regards, tom lane

#101Jeff Davis
pgsql@j-davis.com
In reply to: Tom Lane (#89)
Re: range_agg

On Sat, 2020-03-07 at 16:06 -0500, Tom Lane wrote:

Actually ... have you given any thought to just deciding that ranges
and
multiranges are the same type?

It has come up in a number of conversations, but I'm not sure if it was
discussed on this list.

I think on the whole the advantages win,
and I feel like that might also be the case here.

Some things to think about:

1. Ranges are common -- at least implicitly -- in a lot of
applications/systems. It's pretty easy to represent extrernal data as
ranges in postgres, and also to represent postgres ranges in external
systems. But I can see multiranges causing friction around a lot of
common tasks, like displaying in a UI. If you only expect ranges, you
can add a CHECK constraint, so this is annoying but not necessarily a
deal-breaker.

2. There are existing client libraries[1]https://sfackler.github.io/rust-postgres-range/doc/v0.8.2/postgres_range/ that support range types and
transform them to types within the host language. Obviously, those
would need to be updated to expect multiple ranges.

3. It seems like we would want some kind of base "range" type. When you
try to break a multirange down into constituent ranges, what type would
those pieces be? (Aside: how do you get the constituent ranges?)

I'm thinking more about casting to see if there's a possible compromise
there.

Regards,
Jeff Davis

[1]: https://sfackler.github.io/rust-postgres-range/doc/v0.8.2/postgres_range/
https://sfackler.github.io/rust-postgres-range/doc/v0.8.2/postgres_range/

#102David Fetter
david@fetter.org
In reply to: Jeff Davis (#101)
Re: range_agg

On Mon, Mar 09, 2020 at 06:34:04PM -0700, Jeff Davis wrote:

On Sat, 2020-03-07 at 16:06 -0500, Tom Lane wrote:

Actually ... have you given any thought to just deciding that ranges
and
multiranges are the same type?

It has come up in a number of conversations, but I'm not sure if it was
discussed on this list.

I think on the whole the advantages win,
and I feel like that might also be the case here.

Some things to think about:

1. Ranges are common -- at least implicitly -- in a lot of
applications/systems. It's pretty easy to represent extrernal data as
ranges in postgres, and also to represent postgres ranges in external
systems. But I can see multiranges causing friction around a lot of
common tasks, like displaying in a UI. If you only expect ranges, you
can add a CHECK constraint, so this is annoying but not necessarily a
deal-breaker.

It could become well and truly burdensome in a UI or an API. The
difference between one, as ranges are now, and many, as multi-ranges
would be if we shoehorn them into the range type, are pretty annoying
to deal with.

2. There are existing client libraries[1] that support range types and
transform them to types within the host language. Obviously, those
would need to be updated to expect multiple ranges.

The type systems that would support such types might get unhappy with
us if we started messing with some of the properties like
contiguousness.

3. It seems like we would want some kind of base "range" type. When you
try to break a multirange down into constituent ranges, what type would
those pieces be? (Aside: how do you get the constituent ranges?)

I'm thinking more about casting to see if there's a possible compromise
there.

I think the right compromise is to recognize that the closure of a set
(ranges) over an operation (set union) may well be a different set
(multi-ranges). Other operations have already been proposed, complete
with concrete use cases that could really make PostgreSQL stand out.

That we don't have an obvious choice of "most correct" operation over
which to close ranges makes it even bigger a potential foot-gun
when we choose one arbitrarily and declare it to be the canonical one.

Best,
David.
--
David Fetter <david(at)fetter(dot)org> http://fetter.org/
Phone: +1 415 235 3778

Remember to vote!
Consider donating to Postgres: http://www.postgresql.org/about/donate

#103Paul A Jungwirth
pj@illuminatedcomputing.com
In reply to: David Fetter (#102)
Re: range_agg

Thanks everyone for offering some thoughts on this!

Tom Lane <tgl@sss.pgh.pa.us> wrote:

have you given any thought to just deciding that ranges and
multiranges are the same type?

I can see how it might be nice to have just one type to think about.
Still I think keeping them separate makes sense. Other folks have
brought up several reasons already. Just to chime in:

Tom Lane <tgl@sss.pgh.pa.us> wrote:

Isaac Morland <isaac.morland@gmail.com> writes:

Definitely agreed that range and multirange (or whatever it's called)
should be different. In the work I do I have a number of uses for ranges,
but not (yet) for multiranges. I want to be able to declare a column as
range and be sure that it is just a single range, and then call lower() and
upper() on it and be sure to get just one value in each case; and if I
accidentally try to take the union of ranges where the union isn’t another
range, I want to get an error rather than calculate some weird (in my
context) multirange.

I do not find that argument convincing at all. Surely you could put
that constraint on your column using "CHECK (numranges(VALUE) <= 1)"
or some such notation.

A check constraint works for columns, but there are other contexts
where you'd like to restrict things to just a contiguous range, e.g.
user-defined functions and intermediate results in queries. Basic
ranges seem a lot simpler to think about, so I can appreciate how
letting any range be a multirange adds a heavy cognitive burden. I
think a lot of people will share Isaac's opinion here.

Tom Lane <tgl@sss.pgh.pa.us> wrote:

Also, this would allow us to remove at least one ugly misfeature:

regression=# select '[1,2]'::int4range + '[3,10)'::int4range;
?column?
----------
[1,10)
(1 row)

regression=# select '[1,2]'::int4range + '[4,10)'::int4range;
ERROR: result of range union would not be contiguous

Because of backwards compatibility we can't really change +/-/* not to
raise (right?), so if we joined ranges and multiranges we'd need to
add operators with a different name. I was calling those @+/@-/@*
before, but that was considered too unintuitive and undiscoverable.
Having two types lets us use the nicer operator names.

Tom Lane <tgl@sss.pgh.pa.us> wrote:

it seems like we could consider the traditional
range functions like lower() and upper() to report on the first or last
range bound in a multirange

I tried to keep functions/operators similar, so already lower(mr) =
lower(r) and upper(mr) = upper(r).
I think *conceptually* it's good to make ranges & multiranges as
interchangable as possible, but that doesn't mean they have to be the
same type.

Adding multiranges-as-ranges also raises questions about their string
format. If a multirange is {[1,2), [4,5)} would you only print the
curly braces when there is more than one element?

I don't *think* allowing non-contiguous ranges would break how we use
them in GiST indexes or exclusion constraints, but maybe someone can
think of some problem I can't. It's one place to be wary anyway. At
the very least it would make those things slower I expect.

On a few other issues people have raised recently:

Alvaro Herrera <alvherre@2ndquadrant.com> writes:

I wonder what's the point of multirange arrays. Is there a reason we
create those?

We have arrays of everything else, so why not have them for
multiranges? We don't have to identify specific use cases here,
although I can see how you'd want to call array_agg/UNNEST on some
multiranges, e.g. (Actually I really want to add an UNNEST that
*takes* a multirange, but that could be a follow-on commit.) If
nothing else I think omitting arrays of multiranges would be a strange
irregularity in the type system.

David G. Johnston <david.g.johnston@gmail.com> wrote:

In the tests there is:

+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)
+

Aside from the comment they are identical so I'm confused as to why both tests exist - though I suspect it has to do with the fact that the expected result would be {[a,b]} since text is discrete.

Those tests are for basic string parsing (multirange_in), so one is
testing {A,B} and the other {A, B} (with a space after the comma).
(There are some tests right above those that also have blank spaces,
but they only output a single element in the multirange result.)

David G. Johnston <david.g.johnston@gmail.com> wrote:

Also, the current patch set seems a bit undecided on whether it wants to be truly a multi-range or a range that can report non-contiguous components. Specifically,

+select '{[a,d), [b,f]}'::textmultirange;
+ textmultirange
+----------------
+ {[a,f]}
+(1 row)

Without a canonicalization function, we can't know that [a,a] touches
[b,b], but we *can* know that [a,d) touches [b,f). Or even:

regression=# select '{[a,b), [b,b]}'::textmultirange;
textmultirange
----------------
{[a,b]}
(1 row)

So I'm joining ranges whenever we know they touch. I think this is
consistent with existing range operators, e.g.:

regression=# select '[a,a]'::textrange -|- '[b,b]';
?column?
----------
f

regression=# select '[a,b)'::textrange -|- '[b,b]';
?column?
----------
t

David G. Johnston <david.g.johnston@gmail.com> wrote:

There is a an argument that a multi-range should output {[a,d),[b,f]}. IMO its arguable that a multi-range container should not try and reduce the number of contained ranges at all.

Automatically combining touching ranges seems very desirable to me,
and one of the motivations to building a multirange type instead of
just using an array of ranges. Mathematically {[1,2), [2,3)} is
equivalent to {[1,3)}, and merging the touching elements keeps things
easier to read/understand and faster. Ranges themselves have a
canonical form too. Not canonicalizing raises a lot of questions, like
when are two "equivalent" ranges equal? And when you compose
operators/functions, do you keep all the internally-introduced splits,
or somehow preserve only splits that were present in your top-level
inputs? If you really want a list of possibly-touching ranges, I would
use an array for that. Why even have a range_agg (instead of just
array_agg'ing the ranges) if you're not going to merge the inputs?

Isaac Morland <isaac.morland@gmail.com> writes:

On a related note, I was thinking about this and I don’t think I like
range_agg as a name at all. I know we have array_agg and string_agg but surely
shouldn’t this be called union_agg, and shouldn’t there also be an
intersect_agg? I mean, taking the union isn’t the only possible aggregate on
ranges or multiranges.

The patch does include a range_intersect_agg already. Since there are
so many set-like things in SQL, I don't think the unqualified
union_agg/intersect_agg are appropriate to give to ranges alone. And
the existing ${type}_agg functions are all "additive": json_agg,
json_object_agg, array_agg, string_agg, xmlagg. So I think range_agg
is the least-surprising name for this behavior. I'm not even the first
person to call it that, as you can see from [1]https://git.proteus-tech.com/open-source/django-postgres/blob/fa91cf9b43ce942e84b1a9be22f445f3515ca360/postgres/sql/range_agg.sql.

David Fetter <david@fetter.org> writes:

One way to do that would be to include a "range cardinality" in the
data structure which be the number of left ends in it.

I agree that is probably useful enough to add to this patch. I'll work on it.

David Fetter <david@fetter.org> writes:

There's another use case not yet covered here that could make this
even more complex, we should probably plan for it: multi-ranges with
weights.

Several people have asked me about this. I think it would need to be a
separate type though, e.g. weighted_multirange. Personally I wouldn't
mind working on it eventually, but I don't think it needs to be part
of this initial patch. Possibly it could even be an extension. In lieu
of a real type you also have an array-of-ranges, which is what I
originally proposed range_agg to return.

Finally, I think I mentioned this a long time ago, but I'm still not
sure if this patch needs work around these things:

- gist opclass
- spgist opclass
- typanalyze
- selectivity

I'd love for a real Postgres expert to tell me "No, we can add that
later" or "Yes, you have to add that now." Even better if they can
offer some help, because I'm not sure I understand those areas well
enough to do it myself.

Thanks all,
Paul

[1]: https://git.proteus-tech.com/open-source/django-postgres/blob/fa91cf9b43ce942e84b1a9be22f445f3515ca360/postgres/sql/range_agg.sql

#104Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Paul A Jungwirth (#103)
Re: range_agg

Hello Paul, thanks for the thorough response to all these points.

Regarding the merge of multiranges with ranges, I had also thought of
that at some point and was leaning towards doing that, but after the
latest responses I think the arguments against it are sensible; and now
there's a clear majority for keeping them separate.

I'll be posting an updated version of the patch later today.

I was a bit scared bit this part:

On 2020-Mar-11, Paul A Jungwirth wrote:

Finally, I think I mentioned this a long time ago, but I'm still not
sure if this patch needs work around these things:

- gist opclass
- spgist opclass
- typanalyze
- selectivity

I'd love for a real Postgres expert to tell me "No, we can add that
later" or "Yes, you have to add that now."

While I think that the gist and spgist opclass are in the "very nice to
have but still optional" category, the other two items seem mandatory
(but I'm not 100% certain about that, TBH). I'm not sure we have time
to get those ready during this commitfest.

... thinking about gist+spgist, I think they could be written
identically to those for ranges, using the lowest (first) lower bound
and the higher (last) upper bound.

... thinking about selectivity, I think the way to write that is to
first compute the selectivity for the range across the first lower bound
and the last upper bound, and then subtract that for the "negative"
space between the contained ranges.

I have no immediate thoughts about typanalyze. I suppose it should be
somehow based on the implementation for ranges ... maybe a first-cut is
to construct fake ranges covering the whole multirange (as above) and
just use the ranges implementation (compute_range_stats).

--
�lvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

#105Paul A Jungwirth
pj@illuminatedcomputing.com
In reply to: Alvaro Herrera (#104)
Re: range_agg

On Thu, Mar 12, 2020 at 5:38 AM Alvaro Herrera <alvherre@2ndquadrant.com> wrote:

... thinking about gist+spgist, I think they could be written
identically to those for ranges, using the lowest (first) lower bound
and the higher (last) upper bound.

... thinking about selectivity, I think the way to write that is to
first compute the selectivity for the range across the first lower bound
and the last upper bound, and then subtract that for the "negative"
space between the contained ranges.

I have no immediate thoughts about typanalyze. I suppose it should be
somehow based on the implementation for ranges ... maybe a first-cut is
to construct fake ranges covering the whole multirange (as above) and
just use the ranges implementation (compute_range_stats).

Thanks, this is pretty much what I was thinking too, but I'm really
glad to have someone who knows better confirm it. I can get started on
these right away, and I'll let folks know if I need any help. When I
looked at this last fall there was a lot I didn't understand. More or
less using the existing ranges implementation should be a big help
though.

Paul

#106Paul A Jungwirth
pj@illuminatedcomputing.com
In reply to: Alvaro Herrera (#85)
Re: range_agg

On Wed, Mar 11, 2020 at 4:39 PM Paul A Jungwirth
<pj@illuminatedcomputing.com> wrote:

On Sat, Mar 7, 2020 at 12:20 PM Tom Lane <tgl@sss.pgh.pa.us> wrote:

Alvaro Herrera <alvherre@2ndquadrant.com> writes:

[ v11 patches ]

The cfbot isn't too happy with this; it's getting differently-ordered
results than you apparently did for the list of owned objects in
dependency.out's DROP OWNED BY test. Not sure why that should be ---
it seems like af6550d34 should have ensured that there's only one
possible ordering.

Oh, my last email left out the most important part. :-) Is this
failure online somewhere so I can take a look at it and fix it?

Looks like I sent this just to Tom before. This is something I need to
fix, right?

Regards,
Paul

#107Tom Lane
tgl@sss.pgh.pa.us
In reply to: Paul A Jungwirth (#106)
Re: range_agg

Paul A Jungwirth <pj@illuminatedcomputing.com> writes:

On Wed, Mar 11, 2020 at 4:39 PM Paul A Jungwirth
<pj@illuminatedcomputing.com> wrote:

Oh, my last email left out the most important part. :-) Is this
failure online somewhere so I can take a look at it and fix it?

Look for your patch(es) at

http://commitfest.cputube.org

Right now it's not even applying, presumably because Alvaro already
pushed some pieces, so you need to rebase. But when it was applying,
one or both of the test builds was failing.

regards, tom lane

#108Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Tom Lane (#107)
5 attachment(s)
Re: range_agg

On 2020-Mar-13, Tom Lane wrote:

Right now it's not even applying, presumably because Alvaro already
pushed some pieces, so you need to rebase. But when it was applying,
one or both of the test builds was failing.

Here's the rebased version.

I just realized I didn't include the API change I proposed in
/messages/by-id/20200306200343.GA625@alvherre.pgsql ...

--
�lvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

Attachments:

v12-0001-Paul-s-main-patch.patchtext/x-diff; charset=us-asciiDownload
From f48d715059681286ae7a9d3d962f1f7fe3cd0c03 Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Mon, 9 Mar 2020 17:20:04 -0300
Subject: [PATCH v12 1/5] Paul's main patch

with minor changes by Alvaro
---
 doc/src/sgml/extend.sgml                      |   28 +-
 doc/src/sgml/func.sgml                        |  326 ++-
 doc/src/sgml/rangetypes.sgml                  |   47 +-
 src/backend/catalog/pg_proc.c                 |   17 +-
 src/backend/catalog/pg_range.c                |   10 +-
 src/backend/catalog/pg_type.c                 |  119 +-
 src/backend/commands/typecmds.c               |  314 ++-
 src/backend/parser/parse_coerce.c             |  309 ++-
 src/backend/utils/adt/Makefile                |    1 +
 src/backend/utils/adt/multirangetypes.c       | 2169 +++++++++++++++
 src/backend/utils/adt/pg_upgrade_support.c    |   22 +
 src/backend/utils/adt/pseudotypes.c           |   25 +
 src/backend/utils/adt/rangetypes.c            |  349 ++-
 src/backend/utils/cache/lsyscache.c           |   62 +-
 src/backend/utils/cache/syscache.c            |   12 +
 src/backend/utils/cache/typcache.c            |   98 +-
 src/backend/utils/fmgr/funcapi.c              |  290 +-
 src/bin/pg_dump/pg_dump.c                     |  150 +-
 src/bin/pg_dump/pg_dump.h                     |    1 +
 src/include/access/tupmacs.h                  |    4 +-
 src/include/catalog/binary_upgrade.h          |    2 +
 src/include/catalog/indexing.h                |    3 +
 src/include/catalog/pg_aggregate.dat          |   11 +
 src/include/catalog/pg_amop.dat               |   22 +
 src/include/catalog/pg_amproc.dat             |   12 +-
 src/include/catalog/pg_cast.dat               |   13 +
 src/include/catalog/pg_opclass.dat            |    4 +
 src/include/catalog/pg_operator.dat           |  169 ++
 src/include/catalog/pg_opfamily.dat           |    4 +
 src/include/catalog/pg_proc.dat               |  271 ++
 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                 |    7 +-
 src/include/commands/typecmds.h               |    2 +
 src/include/utils/lsyscache.h                 |    3 +
 src/include/utils/multirangetypes.h           |  103 +
 src/include/utils/rangetypes.h                |   29 +-
 src/include/utils/syscache.h                  |    1 +
 src/include/utils/typcache.h                  |    6 +
 src/pl/plpgsql/src/pl_comp.c                  |    6 +
 src/test/regress/expected/dependency.out      |    1 +
 src/test/regress/expected/hash_func.out       |   13 +
 src/test/regress/expected/multirangetypes.out | 2395 +++++++++++++++++
 src/test/regress/expected/opr_sanity.out      |   26 +-
 src/test/regress/expected/rangetypes.out      |   38 +-
 src/test/regress/expected/sanity_check.out    |    2 +
 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      |  630 +++++
 src/test/regress/sql/opr_sanity.sql           |   18 +-
 src/test/regress/sql/rangetypes.sql           |   13 +
 src/test/regress/sql/type_sanity.sql          |   16 +-
 55 files changed, 8000 insertions(+), 292 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/doc/src/sgml/extend.sgml b/doc/src/sgml/extend.sgml
index 9ec1af780b..92a1a254ac 100644
--- a/doc/src/sgml/extend.sgml
+++ b/doc/src/sgml/extend.sgml
@@ -229,9 +229,9 @@
    </indexterm>
 
     <para>
-     Five pseudo-types of special interest are <type>anyelement</type>,
+     Six pseudo-types of special interest are <type>anyelement</type>,
      <type>anyarray</type>, <type>anynonarray</type>, <type>anyenum</type>,
-     and <type>anyrange</type>,
+     <type>anyrange</type>, and <type>anymultirange</type>.
      which are collectively called <firstterm>polymorphic types</firstterm>.
      Any function declared using these types is said to be
      a <firstterm>polymorphic function</firstterm>.  A polymorphic function can
@@ -250,15 +250,15 @@
      position declared as <type>anyarray</type> can have any array data type,
      but similarly they must all be the same type.  And similarly,
      positions declared as <type>anyrange</type> must all be the same range
-     type.  Furthermore, if there are
+     type.  Likewise for <type>anymultirange</type>.
+    </para>
+
+    <para>
+     Furthermore, if there are
      positions declared <type>anyarray</type> and others declared
      <type>anyelement</type>, the actual array type in the
      <type>anyarray</type> positions must be an array whose elements are
      the same type appearing in the <type>anyelement</type> positions.
-     Similarly, if there are positions declared <type>anyrange</type>
-     and others declared <type>anyelement</type>, the actual range type in
-     the <type>anyrange</type> positions must be a range whose subtype is
-     the same type appearing in the <type>anyelement</type> positions.
      <type>anynonarray</type> is treated exactly the same as <type>anyelement</type>,
      but adds the additional constraint that the actual type must not be
      an array type.
@@ -267,6 +267,20 @@
      be an enum type.
     </para>
 
+    <para>
+     Similarly, if there are positions declared <type>anyrange</type>
+     and others declared <type>anyelement</type>, the actual range type in
+     the <type>anyrange</type> positions must be a range whose subtype is
+     the same type appearing in the <type>anyelement</type> positions.
+     The type <type>anymultirange</type> accepts a multirange
+     whose contents match the other polymorphic types.
+     That is it must hold ranges matching <type>anyrange</type>
+     and base type elements matching <type>anyelement</type> (if either of
+     those are present). If <type>anyarray</type> is present its elements
+     must match the base type of the <type>anyrange</type> and/or the base
+     type of the ranges in an <type>anymultirange</type>.
+    </para>
+
     <para>
      Thus, when more than one argument position is declared with a polymorphic
      type, the net effect is that only certain combinations of actual argument
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 323366feb6..cfe2c23e65 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -13546,7 +13546,7 @@ SELECT NULLIF(value, '(none)') ...
        <row>
         <entry>Operator</entry>
         <entry>Description</entry>
-        <entry>Example</entry>
+        <entry>Examples</entry>
         <entry>Result</entry>
        </row>
       </thead>
@@ -14017,12 +14017,14 @@ NULL baz</literallayout>(3 rows)</entry>
   <title>Range Functions and Operators</title>
 
   <para>
-   See <xref linkend="rangetypes"/> for an overview of range types.
+   See <xref linkend="rangetypes"/> for an overview of range and multirange types.
   </para>
 
   <para>
    <xref linkend="range-operators-table"/> shows the operators
-   available for range types.
+   available for range and multirange types.
+   Many of these operators will accept either a range or multirange
+   on either side.
   </para>
 
     <table id="range-operators-table">
@@ -14040,134 +14042,215 @@ NULL baz</literallayout>(3 rows)</entry>
        <row>
         <entry> <literal>=</literal> </entry>
         <entry>equal</entry>
-        <entry><literal>int4range(1,5) = '[1,4]'::int4range</literal></entry>
-        <entry><literal>t</literal></entry>
+        <entry><literallayout class="monospaced">int4range(1,5) = '[1,4]'::int4range
+'{[1,5)}'::int4multirange = '{[1,4]}'::int4multirange</literallayout></entry>
+        <entry><literallayout class="monospaced">t</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>&lt;&gt;</literal> </entry>
         <entry>not equal</entry>
-        <entry><literal>numrange(1.1,2.2) &lt;&gt; numrange(1.1,2.3)</literal></entry>
-        <entry><literal>t</literal></entry>
+        <entry><literallayout class="monospaced">numrange(1.1,2.2) &lt;&gt; numrange(1.1,2.3)
+'{[1.1,2.2)}'::nummultirange &lt;&gt; '{[1.1,2.3)}'::nummultirange</literallayout></entry>
+        <entry><literallayout class="monospaced">t</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>&lt;</literal> </entry>
         <entry>less than</entry>
-        <entry><literal>int4range(1,10) &lt; int4range(2,3)</literal></entry>
-        <entry><literal>t</literal></entry>
+        <entry><literallayout class="monospaced">int4range(1,10) &lt; int4range(2,3)
+'{[1,10)}'::int4multirange &lt; '{[2,3)}'::int4multirange</literallayout>
+        </entry>
+        <entry><literallayout class="monospaced">t</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>&gt;</literal> </entry>
         <entry>greater than</entry>
-        <entry><literal>int4range(1,10) &gt; int4range(1,5)</literal></entry>
-        <entry><literal>t</literal></entry>
+        <entry>
+          <literallayout class="monospaced">int4range(1,10) &gt; int4range(1,5)
+'{[1,10)}'::int4multirange &gt; '{[1,5)}'::int4multirange</literallayout>
+        </entry>
+        <entry><literallayout class="monospaced">t</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>&lt;=</literal> </entry>
         <entry>less than or equal</entry>
-        <entry><literal>numrange(1.1,2.2) &lt;= numrange(1.1,2.2)</literal></entry>
-        <entry><literal>t</literal></entry>
+        <entry>
+          <literallayout class="monospaced">numrange(1.1,2.2) &lt;= numrange(1.1,2.2)
+'{[1.1,2.2)}'::nummultirange &lt;= '{[1.1,2.2)}'::nummultirange</literallayout>
+        </entry>
+        <entry><literallayout class="monospaced">t</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>&gt;=</literal> </entry>
         <entry>greater than or equal</entry>
-        <entry><literal>numrange(1.1,2.2) &gt;= numrange(1.1,2.0)</literal></entry>
-        <entry><literal>t</literal></entry>
+        <entry>
+          <literallayout class="monospaced">numrange(1.1,2.2) &gt;= numrange(1.1,2.0)
+'{[1.1,2.2)}'::nummultirange &gt;= '{[1.1,2.0)}'::nummultirange</literallayout>
+        </entry>
+        <entry><literallayout class="monospaced">t</literallayout></entry>
+       </row>
+
+       <row>
+        <entry> <literal>@&gt;</literal> </entry>
+        <entry>contains multirange</entry>
+        <entry>
+          <literallayout class="monospaced">int4range(2,4) @&gt; '{[2,3)}'::int4multirange
+'{[2,4)}'::int4multirange @&gt; '{[2,3)}'::int4multirange</literallayout>
+        </entry>
+        <entry><literallayout class="monospaced">t</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>@&gt;</literal> </entry>
         <entry>contains range</entry>
-        <entry><literal>int4range(2,4) @&gt; int4range(2,3)</literal></entry>
-        <entry><literal>t</literal></entry>
+        <entry>
+          <literallayout class="monospaced">int4range(2,4) @&gt; int4range(2,3)
+'{[2,4)}'::int4multirange @&gt; int4range(2,3)</literallayout>
+        </entry>
+        <entry><literallayout class="monospaced">t</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>@&gt;</literal> </entry>
         <entry>contains element</entry>
-        <entry><literal>'[2011-01-01,2011-03-01)'::tsrange @&gt; '2011-01-10'::timestamp</literal></entry>
-        <entry><literal>t</literal></entry>
+        <entry>
+          <literallayout class="monospaced">'[2011-01-01,2011-03-01)'::tsrange @&gt; '2011-01-10'::timestamp
+'{[2011-01-01,2011-03-01)}'::tsmultirange @&gt; '2011-01-10'::timestamp</literallayout>
+        </entry>
+        <entry><literallayout class="monospaced">t</literallayout></entry>
+       </row>
+
+       <row>
+        <entry> <literal>&lt;@</literal> </entry>
+        <entry>multirange is contained by</entry>
+        <entry>
+          <literallayout class="monospaced">'{[2,4)}'::int4multirange &lt;@ int4range(1,7)
+'{[2,4)}'::int4multirange &lt;@ '{[1,7)}'::int4multirange</literallayout>
+        </entry>
+        <entry><literallayout class="monospaced">t</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>&lt;@</literal> </entry>
         <entry>range is contained by</entry>
-        <entry><literal>int4range(2,4) &lt;@ int4range(1,7)</literal></entry>
-        <entry><literal>t</literal></entry>
+        <entry>
+          <literallayout class="monospaced">int4range(2,4) &lt;@ int4range(1,7)
+int4range(2,4) &lt;@ '{[1,7)}'::int4multirange</literallayout>
+        </entry>
+        <entry><literallayout class="monospaced">t</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>&lt;@</literal> </entry>
         <entry>element is contained by</entry>
-        <entry><literal>42 &lt;@ int4range(1,7)</literal></entry>
-        <entry><literal>f</literal></entry>
+        <entry>
+          <literallayout class="monospaced">42 &lt;@ int4range(1,7)
+42 &lt;@ '{[1,7)}'::int4multirange</literallayout>
+        </entry>
+        <entry><literallayout class="monospaced">f</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>&amp;&amp;</literal> </entry>
         <entry>overlap (have points in common)</entry>
-        <entry><literal>int8range(3,7) &amp;&amp; int8range(4,12)</literal></entry>
-        <entry><literal>t</literal></entry>
+        <entry>
+          <literallayout class="monospaced">int8range(3,7) &amp;&amp; int8range(4,12)
+int8range(3,7) &amp;&amp; '{[4,12)}'::int8multirange
+'{[3,7)}'::int8multirange &amp;&amp; int8range(4,12)
+'{[3,7)}'::int8multirange &amp;&amp; '{[4,12)}'::int8multirange</literallayout>
+        </entry>
+        <entry><literallayout class="monospaced">t</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>&lt;&lt;</literal> </entry>
         <entry>strictly left of</entry>
-        <entry><literal>int8range(1,10) &lt;&lt; int8range(100,110)</literal></entry>
-        <entry><literal>t</literal></entry>
+        <entry>
+          <literallayout class="monospaced">int8range(1,10) &lt;&lt; int8range(100,110)
+int8range(1,10) &lt;&lt; '{[100,110)}'::int8multirange
+'{[1,10)}'::int8multirange &lt;&lt; int8range(100,110)
+'{[1,10)}'::int8multirange &lt;&lt; '{[100,110)}'::int8multirange</literallayout>
+        </entry>
+        <entry><literallayout class="monospaced">t</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>&gt;&gt;</literal> </entry>
         <entry>strictly right of</entry>
-        <entry><literal>int8range(50,60) &gt;&gt; int8range(20,30)</literal></entry>
-        <entry><literal>t</literal></entry>
+        <entry>
+          <literallayout class="monospaced">int8range(50,60) &gt;&gt; int8range(20,30)
+int8range(50,60) &gt;&gt; '{[20,30)}'::int8multirange
+'{[50,60)}'::int8multirange &gt;&gt; int8range(20,30)
+'{[50,60)}'::int8multirange &gt;&gt; '{[20,30)}'::int8multirange</literallayout>
+        </entry>
+        <entry><literallayout class="monospaced">t</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>&amp;&lt;</literal> </entry>
         <entry>does not extend to the right of</entry>
-        <entry><literal>int8range(1,20) &amp;&lt; int8range(18,20)</literal></entry>
-        <entry><literal>t</literal></entry>
+        <entry>
+          <literallayout class="monospaced">int8range(1,20) &amp;&lt; int8range(18,20)
+int8range(1,20) &amp;&lt; '{[18,20)}'::int8multirange
+'{[1,20)}'::int8multirange &amp;&lt; int8range(18,20)
+'{[1,20)}'::int8multirange &amp;&lt; '{[18,20)}'::int8multirange</literallayout>
+        </entry>
+        <entry><literallayout class="monospaced">t</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>&amp;&gt;</literal> </entry>
         <entry>does not extend to the left of</entry>
-        <entry><literal>int8range(7,20) &amp;&gt; int8range(5,10)</literal></entry>
-        <entry><literal>t</literal></entry>
+        <entry>
+          <literallayout class="monospaced">int8range(7,20) &amp;&gt; int8range(5,10)
+int8range(7,20) &amp;&gt; '{[5,10)}'::int8multirange
+'{[7,20)}'::int8multirange &amp;&gt; int8range(5,10)
+'{[7,20)}'::int8multirange &amp;&gt; '{[5,10)}'::int8multirange</literallayout>
+        </entry>
+        <entry><literallayout class="monospaced">t</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>-|-</literal> </entry>
         <entry>is adjacent to</entry>
-        <entry><literal>numrange(1.1,2.2) -|- numrange(2.2,3.3)</literal></entry>
-        <entry><literal>t</literal></entry>
+        <entry>
+          <literallayout class="monospaced">numrange(1.1,2.2) -|- numrange(2.2,3.3)
+numrange(1.1,2.2) -|- '{[2.2,3.3)}'::nummultirange
+'{[1.1,2.2)}'::nummultirange -|- numrange(2.2,3.3)
+'{[1.1,2.2)}'::nummultirange -|- '{[2.2,3.3)}'::nummultirange</literallayout>
+        </entry>
+        <entry><literallayout class="monospaced">t</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>+</literal> </entry>
         <entry>union</entry>
-        <entry><literal>numrange(5,15) + numrange(10,20)</literal></entry>
-        <entry><literal>[5,20)</literal></entry>
+        <entry><literallayout class="monospaced">numrange(5,15) + numrange(10,20)
+'{[5,10)}'::nummultirange + '{[15,20)}'::nummultirange</literallayout></entry>
+        <entry><literallayout class="monospaced">[5,20)
+{[5,10), [15,20)}</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>*</literal> </entry>
         <entry>intersection</entry>
-        <entry><literal>int8range(5,15) * int8range(10,20)</literal></entry>
-        <entry><literal>[10,15)</literal></entry>
+        <entry><literallayout class="monospaced">int8range(5,15) * int8range(10,20)
+'{[5,15)}'::int8multirange * '{[10,20)}'::int8multirange</literallayout></entry>
+        <entry><literallayout class="monospaced">[10,15)
+{[10,15)}</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>-</literal> </entry>
         <entry>difference</entry>
-        <entry><literal>int8range(5,15) - int8range(10,20)</literal></entry>
-        <entry><literal>[5,10)</literal></entry>
+        <entry><literallayout class="monospaced">int8range(5,15) - int8range(10,20)
+'{[5,20)}'::int8multirange - '{[10,15)}'::int8multirange</literallayout></entry>
+        <entry><literallayout class="monospaced">[5,10)
+{[5,10), [15,20)}</literallayout></entry>
        </row>
 
       </tbody>
@@ -14185,19 +14268,31 @@ NULL baz</literallayout>(3 rows)</entry>
 
   <para>
    The left-of/right-of/adjacent operators always return false when an empty
-   range is involved; that is, an empty range is not considered to be either
-   before or after any other range.
+   range or multirange is involved; that is, an empty range is not considered to
+   be either before or after any other range.
+  </para>
+
+  <para>
+   Elsewhere empty ranges and multiranges are treated as the additive identity:
+   anything unioned with an empty value is itself. Anything minus an empty
+   value is itself. An empty multirange has exactly the same points as an empty
+   range. Every range contains the empty range. Every multirange contains as many
+   empty ranges as you like.
   </para>
 
   <para>
    The union and difference operators will fail if the resulting range would
    need to contain two disjoint sub-ranges, as such a range cannot be
-   represented.
+   represented. There are separate operators for union and difference that take
+   multirange parameters and return a multirange, and they do not fail even if
+   their arguments are disjoint. So if you need a union or difference operation
+   for ranges that may be disjoint, you can avoid errors by first casting your
+   ranges to multiranges.
   </para>
 
   <para>
    <xref linkend="range-functions-table"/> shows the functions
-   available for use with range types.
+   available for use with range and multirange types.
   </para>
 
   <indexterm>
@@ -14246,6 +14341,17 @@ NULL baz</literallayout>(3 rows)</entry>
         <entry><literal>lower(numrange(1.1,2.2))</literal></entry>
         <entry><literal>1.1</literal></entry>
        </row>
+       <row>
+        <entry>
+         <literal>
+          <function>lower</function>(<type>anymultirange</type>)
+         </literal>
+        </entry>
+        <entry>multirange's element type</entry>
+        <entry>lower bound of multirange</entry>
+        <entry><literal>lower('{[1.1,2.2)}'::nummultirange)</literal></entry>
+        <entry><literal>1.1</literal></entry>
+       </row>
        <row>
         <entry>
          <literal>
@@ -14257,6 +14363,17 @@ NULL baz</literallayout>(3 rows)</entry>
         <entry><literal>upper(numrange(1.1,2.2))</literal></entry>
         <entry><literal>2.2</literal></entry>
        </row>
+       <row>
+        <entry>
+         <literal>
+          <function>upper</function>(<type>anymultirange</type>)
+         </literal>
+        </entry>
+        <entry>multirange's element type</entry>
+        <entry>upper bound of multirange</entry>
+        <entry><literal>upper('{[1.1,2.2)}'::nummultirange)</literal></entry>
+        <entry><literal>2.2</literal></entry>
+       </row>
        <row>
         <entry>
          <literal>
@@ -14268,6 +14385,17 @@ NULL baz</literallayout>(3 rows)</entry>
         <entry><literal>isempty(numrange(1.1,2.2))</literal></entry>
         <entry><literal>false</literal></entry>
        </row>
+       <row>
+        <entry>
+         <literal>
+          <function>isempty</function>(<type>anymultirange</type>)
+         </literal>
+        </entry>
+        <entry><type>boolean</type></entry>
+        <entry>is the multirange empty?</entry>
+        <entry><literal>isempty('{[1.1,2.2)}'::nummultirange)</literal></entry>
+        <entry><literal>false</literal></entry>
+       </row>
        <row>
         <entry>
          <literal>
@@ -14279,6 +14407,17 @@ NULL baz</literallayout>(3 rows)</entry>
         <entry><literal>lower_inc(numrange(1.1,2.2))</literal></entry>
         <entry><literal>true</literal></entry>
        </row>
+       <row>
+        <entry>
+         <literal>
+          <function>lower_inc</function>(<type>anymultirange</type>)
+         </literal>
+        </entry>
+        <entry><type>boolean</type></entry>
+        <entry>is the lower bound inclusive?</entry>
+        <entry><literal>lower_inc('{[1.1,2.2)}'::nummultirange)</literal></entry>
+        <entry><literal>true</literal></entry>
+       </row>
        <row>
         <entry>
          <literal>
@@ -14290,6 +14429,17 @@ NULL baz</literallayout>(3 rows)</entry>
         <entry><literal>upper_inc(numrange(1.1,2.2))</literal></entry>
         <entry><literal>false</literal></entry>
        </row>
+       <row>
+        <entry>
+         <literal>
+          <function>upper_inc</function>(<type>anymultirange</type>)
+         </literal>
+        </entry>
+        <entry><type>boolean</type></entry>
+        <entry>is the upper bound inclusive?</entry>
+        <entry><literal>upper_inc('{[1.1,2.2)}'::nummultirange)</literal></entry>
+        <entry><literal>false</literal></entry>
+       </row>
        <row>
         <entry>
          <literal>
@@ -14301,6 +14451,17 @@ NULL baz</literallayout>(3 rows)</entry>
         <entry><literal>lower_inf('(,)'::daterange)</literal></entry>
         <entry><literal>true</literal></entry>
        </row>
+       <row>
+        <entry>
+         <literal>
+          <function>lower_inf</function>(<type>anymultirange</type>)
+         </literal>
+        </entry>
+        <entry><type>boolean</type></entry>
+        <entry>is the lower bound infinite?</entry>
+        <entry><literal>lower_inf('{(,)}'::datemultirange)</literal></entry>
+        <entry><literal>true</literal></entry>
+       </row>
        <row>
         <entry>
          <literal>
@@ -14312,6 +14473,17 @@ NULL baz</literallayout>(3 rows)</entry>
         <entry><literal>upper_inf('(,)'::daterange)</literal></entry>
         <entry><literal>true</literal></entry>
        </row>
+       <row>
+        <entry>
+         <literal>
+          <function>upper_inf</function>(<type>anymultirange</type>)
+         </literal>
+        </entry>
+        <entry><type>boolean</type></entry>
+        <entry>is the upper bound infinite?</entry>
+        <entry><literal>upper_inf('{(,)}'::datemultirange)</literal></entry>
+        <entry><literal>true</literal></entry>
+       </row>
        <row>
         <entry>
          <literal>
@@ -14323,16 +14495,38 @@ NULL baz</literallayout>(3 rows)</entry>
         <entry><literal>range_merge('[1,2)'::int4range, '[3,4)'::int4range)</literal></entry>
         <entry><literal>[1,4)</literal></entry>
        </row>
+       <row>
+        <entry>
+         <literal>
+          <function>range_merge</function>(<type>anymultirange</type>)
+         </literal>
+        </entry>
+        <entry><type>anyrange</type></entry>
+        <entry>the smallest range which includes the entire multirange</entry>
+        <entry><literal>range_merge('{[1,2), [3,4)}'::int4multirange)</literal></entry>
+        <entry><literal>[1,4)</literal></entry>
+       </row>
+       <row>
+        <entry>
+         <literal>
+          <function>multirange</function>(<type>anyrange</type>)
+         </literal>
+        </entry>
+        <entry><type>anymultirange</type></entry>
+        <entry>a multirange containing just the given range</entry>
+        <entry><literal>multirange('[1,2)'::int4range)</literal></entry>
+        <entry><literal>{[1,2)}</literal></entry>
+       </row>
       </tbody>
      </tgroup>
     </table>
 
   <para>
    The <function>lower</function> and  <function>upper</function> functions return null
-   if the range is empty or the requested bound is infinite.
+   if the input range/multirange is empty or the requested bound is infinite.
    The <function>lower_inc</function>, <function>upper_inc</function>,
    <function>lower_inf</function>, and <function>upper_inf</function>
-   functions all return false for an empty range.
+   functions all return false for an empty range or multirange.
   </para>
   </sect1>
 
@@ -14651,6 +14845,44 @@ NULL baz</literallayout>(3 rows)</entry>
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <indexterm>
+        <primary>range_agg</primary>
+       </indexterm>
+       <function>
+         range_agg(<replaceable class="parameter">expression</replaceable>)
+       </function>
+      </entry>
+      <entry>
+       <type>anyrange</type>
+      </entry>
+      <entry>
+       <type>multirange</type> of the argument range type
+      </entry>
+      <entry>Yes</entry>
+      <entry>union of the non-null input values</entry>
+     </row>
+
+     <row>
+      <entry>
+       <indexterm>
+        <primary>range_intersect_agg</primary>
+       </indexterm>
+       <function>
+         range_intersect_agg(<replaceable class="parameter">expression</replaceable>)
+       </function>
+      </entry>
+      <entry>
+       <type>anyrange</type>
+      </entry>
+      <entry>
+       <type>multirange</type> of the argument range type
+      </entry>
+      <entry>No</entry>
+      <entry>intersection of the non-null input values</entry>
+     </row>
+
      <row>
       <entry>
        <indexterm>
diff --git a/doc/src/sgml/rangetypes.sgml b/doc/src/sgml/rangetypes.sgml
index b75fb3a392..c8784bdce3 100644
--- a/doc/src/sgml/rangetypes.sgml
+++ b/doc/src/sgml/rangetypes.sgml
@@ -27,6 +27,13 @@
   ranges from an instrument, and so forth can also be useful.
  </para>
 
+ <para>
+  Every range type has a corresponding multirange type. A multirange is
+  an ordered list of non-continguous, non-empty, non-null ranges. Most
+  range operators also work on multiranges, and they have a few functions
+  of their own.
+ </para>
+
  <sect2 id="rangetypes-builtin">
   <title>Built-in Range Types</title>
 
@@ -232,10 +239,30 @@ SELECT '[4,4]'::int4range;
 SELECT '[4,4)'::int4range;
 </programlisting>
   </para>
+
+  <para>
+   The input for a multirange is curly brackets (<literal>{</literal> and
+   <literal>}</literal>) containing zero or more valid ranges,
+   separated by commas. Whitespace is permitted around the brackets and
+   commas. This is intended to be reminiscent of array syntax, although
+   multiranges are much simpler: they have just one dimension and there is
+   no need to quote their contents. (The bounds of their ranges may be
+   quoted as above however.)
+  </para>
+
+  <para>
+  Examples:
+<programlisting>
+SELECT '{}'::int4multirange;
+SELECT '{[3,7)}'::int4multirange;
+SELECT '{[3,7), [8,9)}'::int4multirange;
+</programlisting>
+  </para>
+
  </sect2>
 
  <sect2 id="rangetypes-construct">
-  <title>Constructing Ranges</title>
+  <title>Constructing Ranges and Multiranges</title>
 
   <para>
    Each range type has a constructor function with the same name as the range
@@ -267,6 +294,19 @@ SELECT int8range(1, 14, '(]');
 
 -- Using NULL for either bound causes the range to be unbounded on that side.
 SELECT numrange(NULL, 2.2);
+</programlisting>
+  </para>
+
+  <para>
+   Each range type also has a multirange constructor with the same name as the
+   multirange type.  The constructor function takes zero or more arguments
+   which are all ranges of the appropriate type.
+   For example:
+
+<programlisting>
+SELECT nummultirange();
+SELECT nummultirange(numrange(1.0, 14.0));
+SELECT nummultirange(numrange(1.0, 14.0), numrange(20.0, 25.0));
 </programlisting>
   </para>
  </sect2>
@@ -341,6 +381,11 @@ SELECT '[1.234, 5.678]'::floatrange;
    function in this example.
   </para>
 
+  <para>
+   When you define your own range you automatically get a corresponding
+   multirange type.
+  </para>
+
   <para>
    Defining your own range type also allows you to specify a different
    subtype B-tree operator class or collation to use, so as to change the sort
diff --git a/src/backend/catalog/pg_proc.c b/src/backend/catalog/pg_proc.c
index 423fd79d94..fb375cd3bf 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,13 @@ 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 b5bc36c2bd..1217a6ddd0 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_rngmultitypid - 1] = ObjectIdGetDatum(multirangeTypeOid);
 
 	tup = heap_form_tuple(RelationGetDescr(pg_range), values, nulls);
 
@@ -100,6 +102,12 @@ 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 cd56714968..c359f27c74 100644
--- a/src/backend/catalog/pg_type.c
+++ b/src/backend/catalog/pg_type.c
@@ -27,6 +27,7 @@
 #include "catalog/pg_proc.h"
 #include "catalog/pg_type.h"
 #include "commands/typecmds.h"
+#include "mb/pg_wchar.h"
 #include "miscadmin.h"
 #include "parser/scansup.h"
 #include "utils/acl.h"
@@ -36,6 +37,9 @@
 #include "utils/rel.h"
 #include "utils/syscache.h"
 
+static char *makeUniqueTypeName(const char *typeName, Oid typeNamespace,
+								bool tryOriginal);
+
 /* Potentially set by pg_upgrade_support functions */
 Oid			binary_upgrade_next_pg_type_oid = InvalidOid;
 
@@ -809,31 +813,10 @@ RenameTypeInternal(Oid typeOid, const char *newTypeName, Oid typeNamespace)
 char *
 makeArrayTypeName(const char *typeName, Oid typeNamespace)
 {
-	char	   *arr = (char *) palloc(NAMEDATALEN);
-	int			namelen = strlen(typeName);
-	int			i;
+	char	   *arr;
 
-	/*
-	 * 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)
+	arr = makeUniqueTypeName(typeName, typeNamespace, false);
+	if (arr == NULL)
 		ereport(ERROR,
 				(errcode(ERRCODE_DUPLICATE_OBJECT),
 				 errmsg("could not form array type name for type \"%s\"",
@@ -904,3 +887,91 @@ moveArrayTypeName(Oid typeOid, const char *typeName, Oid typeNamespace)
 
 	return true;
 }
+
+
+/*
+ * makeMultirangeTypeName
+ *	  - given a range type name, make a multirange type name for it
+ *
+ * caller is responsible for pfreeing the result
+ */
+char *
+makeMultirangeTypeName(const char *rangeTypeName, Oid typeNamespace)
+{
+	char	   *buf;
+	char	   *mrname;
+	char	   *rangestr;
+
+	/*
+	 * 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...
+	 */
+	rangestr = strstr(rangeTypeName, "range");
+	if (rangestr)
+	{
+		char	*prefix = pnstrdup(rangeTypeName, rangestr - rangeTypeName);
+
+		buf = psprintf("%s%s%s", prefix, "multi", rangestr);
+	}
+	else
+		buf = psprintf("%s_multirange", pnstrdup(rangeTypeName, NAMEDATALEN - 12));
+
+	/* clip it at NAMEDATALEN-1 bytes */
+	buf[pg_mbcliplen(buf, strlen(buf), NAMEDATALEN - 1)] = '\0';
+
+	mrname = makeUniqueTypeName(buf, typeNamespace, true);
+	if (mrname == NULL)
+		ereport(ERROR,
+				(errcode(ERRCODE_DUPLICATE_OBJECT),
+				 errmsg("could not form multirange type name for type \"%s\"",
+						rangeTypeName)));
+
+	return mrname;
+}
+
+/*
+ * makeUniqueTypeName
+ *		Generate a unique name for a prospective new type
+ *
+ * Given a typeName, return a new palloc'ed name by preprending underscores
+ * until a non-conflicting name results.
+ *
+ * If tryOriginal, first try with zero underscores.
+ */
+static char *
+makeUniqueTypeName(const char *typeName, Oid typeNamespace, bool tryOriginal)
+{
+	int			i;
+	int			namelen;
+	char		dest[NAMEDATALEN];
+
+	Assert(strlen(typeName) <= NAMEDATALEN - 1);
+
+	if (tryOriginal &&
+		!SearchSysCacheExists2(TYPENAMENSP,
+							   CStringGetDatum(typeName),
+							   ObjectIdGetDatum(typeNamespace)))
+		return pstrdup(typeName);
+
+	/*
+	 * The idea is to prepend underscores as needed until we make a name that
+	 * doesn't collide with anything ...
+	 */
+	namelen = strlen(typeName);
+	for (i = 1; i < NAMEDATALEN - 1; i++)
+	{
+		dest[i - 1] = '_';
+		strlcpy(dest + i, typeName, NAMEDATALEN - i);
+		if (namelen + i >= NAMEDATALEN)
+			truncate_identifier(dest, NAMEDATALEN, false);
+
+		if (!SearchSysCacheExists2(TYPENAMENSP,
+								   CStringGetDatum(dest),
+								   ObjectIdGetDatum(typeNamespace)))
+			return pstrdup(dest);
+	}
+
+	return NULL;
+}
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index 8891b1d564..dfb2edd3d0 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -105,9 +105,14 @@ typedef struct
 
 /* Potentially set by pg_upgrade_support functions */
 Oid			binary_upgrade_next_array_pg_type_oid = InvalidOid;
+Oid			binary_upgrade_next_mrng_pg_type_oid = InvalidOid;
+Oid			binary_upgrade_next_mrng_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 rangeOid,
+									   Oid rangeArrayOid, Oid *castFuncOid);
 static Oid	findTypeInputFunction(List *procname, Oid typeOid);
 static Oid	findTypeOutputFunction(List *procname, Oid typeOid);
 static Oid	findTypeReceiveFunction(List *procname, Oid typeOid);
@@ -738,7 +743,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",
@@ -1279,6 +1285,11 @@ checkEnumOwner(HeapTuple tup)
 /*
  * DefineRange
  *		Registers a new range type.
+ *
+ * Perhaps it might be worthwhile to set pg_type.typelem to the base type,
+ * and likewise on multiranges to set it to the range type. But having a
+ * non-zero typelem is treated elsewhere as a synonym for being an array,
+ * and users might have queries with that same assumption.
  */
 ObjectAddress
 DefineRange(CreateRangeStmt *stmt)
@@ -1287,7 +1298,11 @@ DefineRange(CreateRangeStmt *stmt)
 	Oid			typeNamespace;
 	Oid			typoid;
 	char	   *rangeArrayName;
+	char	   *multirangeTypeName;
+	char	   *multirangeArrayName;
 	Oid			rangeArrayOid;
+	Oid			multirangeOid;
+	Oid			multirangeArrayOid;
 	Oid			rangeSubtype = InvalidOid;
 	List	   *rangeSubOpclassName = NIL;
 	List	   *rangeCollationName = NIL;
@@ -1304,6 +1319,8 @@ DefineRange(CreateRangeStmt *stmt)
 	AclResult	aclresult;
 	ListCell   *lc;
 	ObjectAddress address;
+	ObjectAddress mltrngaddress;
+	Oid			castFuncOid;
 
 	/* Convert list of names to a name and namespace */
 	typeNamespace = QualifiedNameGetCreationNamespace(stmt->typeName,
@@ -1452,8 +1469,10 @@ DefineRange(CreateRangeStmt *stmt)
 	/* alignment must be TYPALIGN_INT or TYPALIGN_DOUBLE for ranges */
 	alignment = (subtypalign == TYPALIGN_DOUBLE) ? TYPALIGN_DOUBLE : TYPALIGN_INT;
 
-	/* Allocate OID for array type */
+	/* Allocate OID for array type, its multirange, and its multirange array */
 	rangeArrayOid = AssignTypeArrayOid();
+	multirangeOid = AssignTypeMultirangeOid();
+	multirangeArrayOid = AssignTypeMultirangeArrayOid();
 
 	/* Create the pg_type entry */
 	address =
@@ -1491,9 +1510,46 @@ DefineRange(CreateRangeStmt *stmt)
 	Assert(typoid == InvalidOid || typoid == address.objectId);
 	typoid = address.objectId;
 
+	/* Create the multirange that goes with it */
+	multirangeTypeName = makeMultirangeTypeName(typeName, typeNamespace);
+
+	mltrngaddress =
+		TypeCreate(multirangeOid,	/* force assignment of this 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_RANGE,	/* type-category (range 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) */
+	Assert(multirangeOid == mltrngaddress.objectId);
+
 	/* Create the entry in pg_range */
 	RangeCreate(typoid, rangeSubtype, rangeCollation, rangeSubOpclass,
-				rangeCanonical, rangeSubtypeDiff);
+				rangeCanonical, rangeSubtypeDiff, multirangeOid);
 
 	/*
 	 * Create the array type that goes with it.
@@ -1534,8 +1590,53 @@ 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 */
+			   multirangeOid,	/* 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,
+							   multirangeOid, typoid, rangeArrayOid,
+							   &castFuncOid);
+
+	/* Create cast from the range type to its multirange type */
+	CastCreate(typoid, multirangeOid, castFuncOid, 'e', 'f', DEPENDENCY_INTERNAL);
+
+	pfree(multirangeTypeName);
+	pfree(multirangeArrayName);
 
 	return address;
 }
@@ -1613,6 +1714,147 @@ 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.
+ *
+ * Sets castFuncOid to the oid of the new constructor that can be used
+ * to cast from a range to a multirange.
+ */
+static void
+makeMultirangeConstructors(const char *name, Oid namespace,
+						   Oid multirangeOid, Oid rangeOid, Oid rangeArrayOid,
+						   Oid *castFuncOid)
+{
+	ObjectAddress myself,
+				referenced;
+	oidvector  *argtypes;
+	Datum		allParamTypes;
+	ArrayType  *allParameterTypes;
+	Datum		paramModes;
+	ArrayType  *parameterModes;
+
+	referenced.classId = TypeRelationId;
+	referenced.objectId = multirangeOid;
+	referenced.objectSubId = 0;
+
+	/* 0-arg constructor - for empty multiranges */
+	argtypes = buildoidvector(NULL, 0);
+	myself = ProcedureCreate(name,	/* name: same as multirange type */
+							 namespace,
+							 false,	/* replace */
+							 false,	/* returns set */
+							 multirangeOid,	/* return type */
+							 BOOTSTRAP_SUPERUSERID,	/* proowner */
+							 INTERNALlanguageId,	/* language */
+							 F_FMGR_INTERNAL_VALIDATOR,
+							 "multirange_constructor0",	/* prosrc */
+							 NULL,	/* probin */
+							 PROKIND_FUNCTION,
+							 false,	/* security_definer */
+							 false,	/* leakproof */
+							 false, /* isStrict */
+							 PROVOLATILE_IMMUTABLE,	/* volatility */
+							 PROPARALLEL_SAFE,	/* parallel safety */
+							 argtypes,	/* parameterTypes */
+							 PointerGetDatum(NULL),	/* allParameterTypes */
+							 PointerGetDatum(NULL),	/* parameterModes */
+							 PointerGetDatum(NULL),	/* parameterNames */
+							 NIL,	/* parameterDefaults */
+							 PointerGetDatum(NULL),	/* trftypes */
+							 PointerGetDatum(NULL),	/* proconfig */
+							 InvalidOid,	/* prosupport */
+							 1.0,	/* procost */
+							 0.0);	/* prorows */
+	/*
+	 * Make the constructor 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);
+	pfree(argtypes);
+
+	/*
+	 * 1-arg constructor - for casts
+	 *
+	 * In theory we shouldn't need both this and the vararg (n-arg) constructor,
+	 * but having a separate 1-arg function lets us define casts against it.
+	 */
+	argtypes = buildoidvector(&rangeOid, 1);
+	myself = ProcedureCreate(name,	/* name: same as multirange type */
+							 namespace,
+							 false,	/* replace */
+							 false,	/* returns set */
+							 multirangeOid,	/* return type */
+							 BOOTSTRAP_SUPERUSERID,	/* proowner */
+							 INTERNALlanguageId,	/* language */
+							 F_FMGR_INTERNAL_VALIDATOR,
+							 "multirange_constructor1",	/* prosrc */
+							 NULL,	/* probin */
+							 PROKIND_FUNCTION,
+							 false,	/* security_definer */
+							 false,	/* leakproof */
+							 true, /* isStrict */
+							 PROVOLATILE_IMMUTABLE,	/* volatility */
+							 PROPARALLEL_SAFE,	/* parallel safety */
+							 argtypes,	/* parameterTypes */
+							 PointerGetDatum(NULL),	/* allParameterTypes */
+							 PointerGetDatum(NULL),	/* parameterModes */
+							 PointerGetDatum(NULL),	/* parameterNames */
+							 NIL,	/* parameterDefaults */
+							 PointerGetDatum(NULL),	/* trftypes */
+							 PointerGetDatum(NULL),	/* proconfig */
+							 InvalidOid,	/* prosupport */
+							 1.0,	/* procost */
+							 0.0);	/* prorows */
+	/* ditto */
+	recordDependencyOn(&myself, &referenced, DEPENDENCY_INTERNAL);
+	pfree(argtypes);
+	*castFuncOid = myself.objectId;
+
+	/* n-arg constructor - vararg */
+	argtypes = buildoidvector(&rangeArrayOid, 1);
+	allParamTypes = ObjectIdGetDatum(rangeArrayOid);
+	allParameterTypes = construct_array(&allParamTypes,
+										1, OIDOID,
+										sizeof(Oid), true, 'i');
+	paramModes = CharGetDatum(FUNC_PARAM_VARIADIC);
+	parameterModes = construct_array(&paramModes, 1, CHAROID,
+									 1, true, 'c');
+	myself = ProcedureCreate(name,	/* name: same as multirange type */
+							 namespace,
+							 false,	/* replace */
+							 false,	/* returns set */
+							 multirangeOid,	/* return type */
+							 BOOTSTRAP_SUPERUSERID,	/* proowner */
+							 INTERNALlanguageId,	/* language */
+							 F_FMGR_INTERNAL_VALIDATOR,
+							 "multirange_constructor2",	/* prosrc */
+							 NULL,	/* probin */
+							 PROKIND_FUNCTION,
+							 false,	/* security_definer */
+							 false,	/* leakproof */
+							 false, /* isStrict */
+							 PROVOLATILE_IMMUTABLE,	/* volatility */
+							 PROPARALLEL_SAFE,	/* parallel safety */
+							 argtypes,	/* parameterTypes */
+							 PointerGetDatum(allParameterTypes),	/* allParameterTypes */
+							 PointerGetDatum(parameterModes),	/* parameterModes */
+							 PointerGetDatum(NULL),	/* parameterNames */
+							 NIL,	/* parameterDefaults */
+							 PointerGetDatum(NULL),	/* trftypes */
+							 PointerGetDatum(NULL),	/* proconfig */
+							 InvalidOid,	/* prosupport */
+							 1.0,	/* procost */
+							 0.0);	/* prorows */
+	/* ditto */
+	recordDependencyOn(&myself, &referenced, DEPENDENCY_INTERNAL);
+	pfree(argtypes);
+	pfree(allParameterTypes);
+	pfree(parameterModes);
+}
 
 /*
  * Find suitable I/O functions for a type.
@@ -2045,6 +2287,72 @@ AssignTypeArrayOid(void)
 	return type_array_oid;
 }
 
+/*
+ *	AssignTypeMultirangeOid
+ *
+ *	Pre-assign the range type's multirange OID for use in pg_type.oid
+ */
+Oid
+AssignTypeMultirangeOid(void)
+{
+	Oid			type_multirange_oid;
+
+	/* Use binary-upgrade override for pg_type.oid? */
+	if (IsBinaryUpgrade)
+	{
+		if (!OidIsValid(binary_upgrade_next_mrng_pg_type_oid))
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+					 errmsg("pg_type multirange OID value not set when in binary upgrade mode")));
+
+		type_multirange_oid = binary_upgrade_next_mrng_pg_type_oid;
+		binary_upgrade_next_mrng_pg_type_oid = InvalidOid;
+	}
+	else
+	{
+		Relation	pg_type = table_open(TypeRelationId, AccessShareLock);
+
+		type_multirange_oid = GetNewOidWithIndex(pg_type, TypeOidIndexId,
+												 Anum_pg_type_oid);
+		table_close(pg_type, AccessShareLock);
+	}
+
+	return type_multirange_oid;
+}
+
+/*
+ *	AssignTypeMultirangeArrayOid
+ *
+ *	Pre-assign the range type's multirange array OID for use in pg_type.typarray
+ */
+Oid
+AssignTypeMultirangeArrayOid(void)
+{
+	Oid			type_multirange_array_oid;
+
+	/* Use binary-upgrade override for pg_type.oid? */
+	if (IsBinaryUpgrade)
+	{
+		if (!OidIsValid(binary_upgrade_next_mrng_array_pg_type_oid))
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+					 errmsg("pg_type multirange array OID value not set when in binary upgrade mode")));
+
+		type_multirange_array_oid = binary_upgrade_next_mrng_array_pg_type_oid;
+		binary_upgrade_next_mrng_array_pg_type_oid = InvalidOid;
+	}
+	else
+	{
+		Relation	pg_type = table_open(TypeRelationId, AccessShareLock);
+
+		type_multirange_array_oid = GetNewOidWithIndex(pg_type, TypeOidIndexId,
+													   Anum_pg_type_oid);
+		table_close(pg_type, AccessShareLock);
+	}
+
+	return type_multirange_array_oid;
+}
+
 
 /*-------------------------------------------------------------------
  * DefineCompositeType
diff --git a/src/backend/parser/parse_coerce.c b/src/backend/parser/parse_coerce.c
index 929f758ef4..27bfc4bf8c 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
@@ -1481,6 +1483,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;
@@ -1527,6 +1531,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 */
@@ -1577,6 +1590,41 @@ check_generic_type_consistency(const Oid *actual_arg_types,
 			/* otherwise, they better match */
 			return false;
 		}
+		else
+			range_typelem = InvalidOid;	/* keep compiler quiet */
+	}
+	else
+		range_typelem = InvalidOid; /* keep compiler quiet */
+
+	/* Get the element type based on the multirange type, if we have one */
+	if (OidIsValid(multirange_typeid))
+	{
+		multirange_typelem = get_range_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)
@@ -1680,13 +1728,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.
@@ -1744,7 +1796,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;
@@ -1762,6 +1814,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;
+		}
 	}
 
 	/*
@@ -1812,9 +1884,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
@@ -1846,6 +1921,71 @@ enforce_generic_type_consistency(const Oid *actual_arg_types,
 							   format_type_be(range_typeid),
 							   format_type_be(elem_typeid))));
 		}
+		else
+			range_typelem = InvalidOid;
+	}
+
+	/* 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_range_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))
@@ -1855,6 +1995,7 @@ enforce_generic_type_consistency(const Oid *actual_arg_types,
 			elem_typeid = ANYELEMENTOID;
 			array_typeid = ANYARRAYOID;
 			range_typeid = ANYRANGEOID;
+			multirange_typeid = ANYMULTIRANGEOID;
 		}
 		else
 		{
@@ -1927,6 +2068,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;
+			}
 		}
 	}
 
@@ -1958,6 +2110,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 ||
@@ -2006,8 +2174,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);
@@ -2019,11 +2186,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_range_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)
 		{
@@ -2051,6 +2244,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_range_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)
@@ -2059,6 +2265,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_range_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 */
@@ -2173,6 +2447,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..0c9afd5448
--- /dev/null
+++ b/src/backend/utils/adt/multirangetypes.c
@@ -0,0 +1,2169 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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/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;
+	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 = NULL;
+	int32		range_str_len;
+	char	   *range_str_copy;
+
+	cache = get_multirange_io_data(fcinfo, mltrngtypoid, IOFunc_input);
+	rangetyp = cache->typcache->rngtype;
+
+	/* 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]);
+		uint32		range_len;
+		char	   *range_data;
+
+		range = PointerGetDatum(SendFunctionCall(&cache->proc, range));
+		range_len = VARSIZE(range) - VARHDRSZ;
+		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 rngtypid, IOFuncSelector func)
+{
+	MultirangeIOData *cache = (MultirangeIOData *) fcinfo->flinfo->fn_extra;
+
+	if (cache == NULL || cache->typcache->type_id != rngtypid)
+	{
+		int16		typlen;
+		bool		typbyval;
+		char		typalign;
+		char		typdelim;
+
+		cache = (MultirangeIOData *) MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
+														sizeof(MultirangeIOData));
+		cache->typcache = lookup_type_cache(rngtypid, TYPECACHE_MULTIRANGE_INFO);
+		if (cache->typcache->rngtype == NULL)
+			elog(ERROR, "type %u is not a multirange type", rngtypid);
+
+		/* 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_constructor2(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));
+}
+
+/*
+ * Construct multirange value from a single range.
+ * It'd be nice if we could just use multirange_constructor2
+ * for this case, but we need a non-variadic single-arg function
+ * to let us define a CAST from a range to its multirange.
+ */
+Datum
+multirange_constructor1(PG_FUNCTION_ARGS)
+{
+	Oid			mltrngtypid = get_fn_expr_rettype(fcinfo->flinfo);
+	Oid			rngtypid;
+	TypeCacheEntry *typcache;
+	TypeCacheEntry *rangetyp;
+	RangeType *range;
+
+	typcache = multirange_get_typcache(fcinfo, mltrngtypid);
+	rangetyp = typcache->rngtype;
+
+	/*
+	 * 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")));
+
+	range = PG_GETARG_RANGE_P(0);
+
+	/* Make sure the range type matches. */
+	rngtypid = RangeTypeGetOid(range);
+	if (rngtypid != rangetyp->type_id)
+		ereport(ERROR, (errmsg("type %u does not match constructor type", rngtypid)));
+
+	PG_RETURN_MULTIRANGE_P(make_multirange(mltrngtypid, rangetyp, 1, &range));
+}
+
+/*
+ * 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 -> multirange type functions */
+
+/* multirange union */
+Datum
+multirange_union(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
+	MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count1;
+	int32		range_count2;
+	int32		range_count3;
+	RangeType **ranges1;
+	RangeType **ranges2;
+	RangeType **ranges3;
+
+	if (MultirangeIsEmpty(mr1))
+		PG_RETURN_MULTIRANGE_P(mr2);
+	if (MultirangeIsEmpty(mr2))
+		PG_RETURN_MULTIRANGE_P(mr1);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr1));
+
+	multirange_deserialize(mr1, &range_count1, &ranges1);
+	multirange_deserialize(mr2, &range_count2, &ranges2);
+
+	range_count3 = range_count1 + range_count2;
+	ranges3 = palloc0(range_count3 * sizeof(RangeType *));
+	memcpy(ranges3, ranges1, range_count1 * sizeof(RangeType *));
+	memcpy(ranges3 + range_count1, ranges2, range_count2 * sizeof(RangeType *));
+	PG_RETURN_MULTIRANGE_P(make_multirange(typcache->type_id, typcache->rngtype,
+										   range_count3, ranges3));
+}
+
+/* multirange minus */
+Datum
+multirange_minus(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
+	MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
+	Oid			mltrngtypoid = MultirangeTypeGetOid(mr1);
+	TypeCacheEntry *typcache;
+	TypeCacheEntry *rangetyp;
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+
+	typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
+	rangetyp = typcache->rngtype;
+
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		PG_RETURN_MULTIRANGE_P(mr1);
+
+	multirange_deserialize(mr1, &range_count1, &ranges1);
+	multirange_deserialize(mr2, &range_count2, &ranges2);
+
+	PG_RETURN_MULTIRANGE_P(multirange_minus_internal(mltrngtypoid,
+													 rangetyp,
+													 range_count1,
+													 ranges1,
+													 range_count2,
+													 ranges2));
+}
+
+MultirangeType *
+multirange_minus_internal(Oid mltrngtypoid, TypeCacheEntry *rangetyp,
+						 int32 range_count1, RangeType **ranges1,
+						 int32 range_count2, RangeType **ranges2)
+{
+	RangeType  *r1;
+	RangeType  *r2;
+	RangeType **ranges3;
+	int32		range_count3;
+	int32		i1;
+	int32		i2;
+
+	/*
+	 * Worst case: every range in ranges1 makes a different cut to some range
+	 * in ranges2.
+	 */
+	ranges3 = palloc0((range_count1 + range_count2) * sizeof(RangeType *));
+	range_count3 = 0;
+
+	/*
+	 * For each range in mr1, keep subtracting until it's gone or the ranges
+	 * in mr2 have passed it. After a subtraction we assign what's left back
+	 * to r1. The parallel progress through mr1 and mr2 is similar to
+	 * multirange_overlaps_multirange_internal.
+	 */
+	r2 = ranges2[0];
+	for (i1 = 0, i2 = 0; i1 < range_count1; i1++)
+	{
+		r1 = ranges1[i1];
+
+		/* Discard r2s while r2 << r1 */
+		while (r2 != NULL && range_before_internal(rangetyp, r2, r1))
+		{
+			r2 = ++i2 >= range_count2 ? NULL : ranges2[i2];
+		}
+
+		while (r2 != NULL)
+		{
+			if (range_split_internal(rangetyp, r1, r2, &ranges3[range_count3], &r1))
+			{
+				/*
+				 * If r2 takes a bite out of the middle of r1, we need two
+				 * outputs
+				 */
+				range_count3++;
+				r2 = ++i2 >= range_count2 ? NULL : ranges2[i2];
+
+			}
+			else if (range_overlaps_internal(rangetyp, r1, r2))
+			{
+				/*
+				 * If r2 overlaps r1, replace r1 with r1 - r2.
+				 */
+				r1 = range_minus_internal(rangetyp, r1, r2);
+
+				/*
+				 * If r2 goes past r1, then we need to stay with it, in case
+				 * it hits future r1s. Otherwise we need to keep r1, in case
+				 * future r2s hit it. Since we already subtracted, there's no
+				 * point in using the overright/overleft calls.
+				 */
+				if (RangeIsEmpty(r1) || range_before_internal(rangetyp, r1, r2))
+					break;
+				else
+					r2 = ++i2 >= range_count2 ? NULL : ranges2[i2];
+
+			}
+			else
+			{
+				/*
+				 * This and all future r2s are past r1, so keep them. Also
+				 * assign whatever is left of r1 to the result.
+				 */
+				break;
+			}
+		}
+
+		/*
+		 * Nothing else can remove anything from r1, so keep it. Even if r1 is
+		 * empty here, make_multirange will remove it.
+		 */
+		ranges3[range_count3++] = r1;
+	}
+
+	return make_multirange(mltrngtypoid, rangetyp, range_count3, ranges3);
+}
+
+/* multirange intersection */
+Datum
+multirange_intersect(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
+	MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
+	Oid			mltrngtypoid = MultirangeTypeGetOid(mr1);
+	TypeCacheEntry *typcache;
+	TypeCacheEntry *rangetyp;
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+
+	typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
+	rangetyp = typcache->rngtype;
+
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		PG_RETURN_MULTIRANGE_P(make_empty_multirange(mltrngtypoid, rangetyp));
+
+	multirange_deserialize(mr1, &range_count1, &ranges1);
+	multirange_deserialize(mr2, &range_count2, &ranges2);
+
+	PG_RETURN_MULTIRANGE_P(multirange_intersect_internal(mltrngtypoid,
+														 rangetyp,
+														 range_count1,
+														 ranges1,
+														 range_count2,
+														 ranges2));
+}
+
+MultirangeType *
+multirange_intersect_internal(Oid mltrngtypoid, TypeCacheEntry *rangetyp,
+							  int32 range_count1, RangeType **ranges1,
+							  int32 range_count2, RangeType **ranges2)
+{
+	RangeType  *r1;
+	RangeType  *r2;
+	RangeType **ranges3;
+	int32		range_count3;
+	int32		i1;
+	int32		i2;
+
+	if (range_count1 == 0 || range_count2 == 0)
+		return make_multirange(mltrngtypoid, rangetyp, 0, NULL);
+
+	/*-----------------------------------------------
+	 * Worst case is a stitching pattern like this:
+	 *
+	 * mr1: --- --- --- ---
+	 * mr2:   --- --- ---
+	 * mr3:   - - - - - -
+	 *
+	 * That seems to be range_count1 + range_count2 - 1,
+	 * but one extra won't hurt.
+	 *-----------------------------------------------
+	 */
+	ranges3 = palloc0((range_count1 + range_count2) * sizeof(RangeType *));
+	range_count3 = 0;
+
+	/*
+	 * For each range in mr1, keep intersecting until the ranges in mr2 have
+	 * passed it. The parallel progress through mr1 and mr2 is similar to
+	 * multirange_minus_multirange_internal, but we don't have to assign back
+	 * to r1.
+	 */
+	r2 = ranges2[0];
+	for (i1 = 0, i2 = 0; i1 < range_count1; i1++)
+	{
+		r1 = ranges1[i1];
+
+		/* Discard r2s while r2 << r1 */
+		while (r2 != NULL && range_before_internal(rangetyp, r2, r1))
+		{
+			r2 = ++i2 >= range_count2 ? NULL : ranges2[i2];
+		}
+
+		while (r2 != NULL)
+		{
+			if (range_overlaps_internal(rangetyp, r1, r2))
+			{
+				/* Keep the overlapping part */
+				ranges3[range_count3++] = range_intersect_internal(rangetyp, r1, r2);
+
+				/* If we "used up" all of r2, go to the next one... */
+				if (range_overleft_internal(rangetyp, r2, r1))
+					r2 = ++i2 >= range_count2 ? NULL : ranges2[i2];
+
+				/* ...otherwise go to the next r1 */
+				else
+					break;
+			}
+			else
+				/* We're past r1, so move to the next one */
+				break;
+		}
+
+		/* If we're out of r2s, there can be no more intersections */
+		if (r2 == NULL)
+			break;
+	}
+
+	return make_multirange(mltrngtypoid, rangetyp, range_count3, ranges3);
+}
+
+/*
+ * range_agg_transfn: combine adjacent/overlapping ranges.
+ *
+ * All we do here is gather the input ranges into an array
+ * so that the finalfn can sort and combine them.
+ */
+Datum
+range_agg_transfn(PG_FUNCTION_ARGS)
+{
+	MemoryContext aggContext;
+	Oid			rngtypoid;
+	ArrayBuildState *state;
+
+	if (!AggCheckCallContext(fcinfo, &aggContext))
+		elog(ERROR, "range_agg_transfn called in non-aggregate context");
+
+	rngtypoid = get_fn_expr_argtype(fcinfo->flinfo, 1);
+	if (!type_is_range(rngtypoid))
+		ereport(ERROR, (errmsg("range_agg must be called with a range")));
+
+	if (PG_ARGISNULL(0))
+		state = initArrayResult(rngtypoid, aggContext, false);
+	else
+		state = (ArrayBuildState *) PG_GETARG_POINTER(0);
+
+	/* skip NULLs */
+	if (!PG_ARGISNULL(1))
+		accumArrayResult(state, PG_GETARG_DATUM(1), false, rngtypoid, aggContext);
+
+	PG_RETURN_POINTER(state);
+}
+
+/*
+ * range_agg_finalfn: use our internal array to merge touching ranges.
+ */
+Datum
+range_agg_finalfn(PG_FUNCTION_ARGS)
+{
+	MemoryContext aggContext;
+	Oid			mltrngtypoid;
+	TypeCacheEntry *typcache;
+	ArrayBuildState *state;
+	int32		range_count;
+	RangeType **ranges;
+	int			i;
+
+	if (!AggCheckCallContext(fcinfo, &aggContext))
+		elog(ERROR, "range_agg_finalfn called in non-aggregate context");
+
+	state = PG_ARGISNULL(0) ? NULL : (ArrayBuildState *) PG_GETARG_POINTER(0);
+	if (state == NULL)
+		/* This shouldn't be possible, but just in case.... */
+		PG_RETURN_NULL();
+
+	/* Also return NULL if we had zero inputs, like other aggregates */
+	range_count = state->nelems;
+	if (range_count == 0)
+		PG_RETURN_NULL();
+
+	mltrngtypoid = get_fn_expr_rettype(fcinfo->flinfo);
+	typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
+
+	ranges = palloc0(range_count * sizeof(RangeType *));
+	for (i = 0; i < range_count; i++)
+		ranges[i] = DatumGetRangeTypeP(state->dvalues[i]);
+
+	PG_RETURN_MULTIRANGE_P(make_multirange(mltrngtypoid, typcache->rngtype, range_count, ranges));
+}
+
+Datum
+multirange_intersect_agg_transfn(PG_FUNCTION_ARGS)
+{
+	MemoryContext aggContext;
+	Oid			mltrngtypoid;
+	TypeCacheEntry *typcache;
+	MultirangeType *result;
+	MultirangeType *current;
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+
+	if (!AggCheckCallContext(fcinfo, &aggContext))
+		elog(ERROR, "multirange_intersect_agg_transfn called in non-aggregate context");
+
+	mltrngtypoid = get_fn_expr_argtype(fcinfo->flinfo, 1);
+	if (!type_is_multirange(mltrngtypoid))
+		ereport(ERROR, (errmsg("range_intersect_agg must be called with a multirange")));
+
+	typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
+
+	/* strictness ensures these are non-null */
+	result = PG_GETARG_MULTIRANGE_P(0);
+	current = PG_GETARG_MULTIRANGE_P(1);
+
+	multirange_deserialize(result, &range_count1, &ranges1);
+	multirange_deserialize(current, &range_count2, &ranges2);
+
+	result = multirange_intersect_internal(mltrngtypoid,
+										   typcache->rngtype,
+										   range_count1,
+										   ranges1,
+										   range_count2,
+										   ranges2);
+	PG_RETURN_RANGE_P(result);
+}
+
+
+/* multirange -> element type functions */
+
+/* extract lower bound value */
+Datum
+multirange_lower(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+	bool		isnull;
+	Datum		result;
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_NULL();
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	result = range_lower_internal(typcache->rngtype, ranges[0], &isnull);
+
+	if (isnull)
+		PG_RETURN_NULL();
+	else
+		PG_RETURN_DATUM(result);
+}
+
+/* extract upper bound value */
+Datum
+multirange_upper(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+	bool		isnull;
+	Datum		result;
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_NULL();
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	result = range_upper_internal(typcache->rngtype, ranges[range_count - 1], &isnull);
+
+	if (isnull)
+		PG_RETURN_NULL();
+	else
+		PG_RETURN_DATUM(result);
+}
+
+
+/* multirange -> bool functions */
+
+/* is multirange empty? */
+Datum
+multirange_empty(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+
+	PG_RETURN_BOOL(mr->rangeCount == 0);
+}
+
+/* is lower bound inclusive? */
+Datum
+multirange_lower_inc(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	int32		range_count;
+	RangeType **ranges;
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_has_flag(ranges[0], RANGE_LB_INC));
+}
+
+/* is upper bound inclusive? */
+Datum
+multirange_upper_inc(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	int32		range_count;
+	RangeType **ranges;
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_has_flag(ranges[range_count - 1], RANGE_UB_INC));
+}
+
+/* is lower bound infinite? */
+Datum
+multirange_lower_inf(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	int32		range_count;
+	RangeType **ranges;
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_has_flag(ranges[0], RANGE_LB_INF));
+}
+
+/* is upper bound infinite? */
+Datum
+multirange_upper_inf(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	int32		range_count;
+	RangeType **ranges;
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_has_flag(ranges[range_count - 1], RANGE_UB_INF));
+}
+
+
+
+/* multirange, element -> bool functions */
+
+/* contains? */
+Datum
+multirange_contains_elem(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	Datum		val = PG_GETARG_DATUM(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(multirange_contains_elem_internal(typcache, mr, val));
+}
+
+/* contained by? */
+Datum
+elem_contained_by_multirange(PG_FUNCTION_ARGS)
+{
+	Datum		val = PG_GETARG_DATUM(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(multirange_contains_elem_internal(typcache, mr, val));
+}
+
+/*
+ * Test whether multirange mr contains a specific element value.
+ */
+bool
+multirange_contains_elem_internal(TypeCacheEntry *typcache, MultirangeType * mr, Datum val)
+{
+	TypeCacheEntry *rangetyp;
+	int32		range_count;
+	RangeType **ranges;
+	RangeType  *r;
+	int			i;
+
+	rangetyp = typcache->rngtype;
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	for (i = 0; i < range_count; i++)
+	{
+		r = ranges[i];
+		if (range_contains_elem_internal(rangetyp, r, val))
+			return true;
+	}
+
+	return false;
+}
+
+/* multirange, range -> bool functions */
+
+/* contains? */
+Datum
+multirange_contains_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(multirange_contains_range_internal(typcache, mr, r));
+}
+
+/* contained by? */
+Datum
+range_contained_by_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(multirange_contains_range_internal(typcache, mr, r));
+}
+
+/*
+ * Test whether multirange mr contains a specific range r.
+ */
+bool
+multirange_contains_range_internal(TypeCacheEntry *typcache, MultirangeType * mr, RangeType *r)
+{
+	TypeCacheEntry *rangetyp;
+	int32		range_count;
+	RangeType **ranges;
+	RangeType  *mrr;
+	int			i;
+
+	rangetyp = typcache->rngtype;
+
+	/*
+	 * Every multirange contains an infinite number of empty ranges, even an
+	 * empty one.
+	 */
+	if (RangeIsEmpty(r))
+		return true;
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	for (i = 0; i < range_count; i++)
+	{
+		mrr = ranges[i];
+		if (range_contains_internal(rangetyp, mrr, r))
+			return true;
+	}
+
+	return false;
+}
+
+
+/* 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));
+}
+
+/* overlaps? */
+Datum
+range_overlaps_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(range_overlaps_multirange_internal(typcache, r, mr));
+}
+
+Datum
+multirange_overlaps_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(range_overlaps_multirange_internal(typcache, r, mr));
+}
+
+Datum
+multirange_overlaps_multirange(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_overlaps_multirange_internal(typcache, mr1, mr2));
+}
+
+bool
+range_overlaps_multirange_internal(TypeCacheEntry *typcache, RangeType *r, MultirangeType * mr)
+{
+	TypeCacheEntry *rangetyp;
+	int32		range_count;
+	int32		i;
+	RangeType **ranges;
+	RangeType  *mrr;
+
+	/*
+	 * Empties never overlap, even with empties. (This seems strange since
+	 * they *do* contain each other, but we want to follow how ranges work.)
+	 */
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		return false;
+
+	rangetyp = typcache->rngtype;
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	/* Scan through mrr and once it gets to r they either overlap or not */
+	mrr = ranges[0];
+
+	/* Discard mrrs while mrr << r */
+	i = 0;
+	while (range_before_internal(rangetyp, mrr, r))
+	{
+		if (++i >= range_count)
+			return false;
+		mrr = ranges[i];
+	}
+
+	/* Now either we overlap or we passed r */
+	return range_overlaps_internal(rangetyp, mrr, r);
+}
+
+bool
+multirange_overlaps_multirange_internal(TypeCacheEntry *typcache, MultirangeType * mr1,
+										MultirangeType * mr2)
+{
+	TypeCacheEntry *rangetyp;
+	int32		range_count1;
+	int32		range_count2;
+	int32		i1;
+	int32		i2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+	RangeType  *r1;
+	RangeType  *r2;
+
+	/*
+	 * Empties never overlap, even with empties. (This seems strange since
+	 * they *do* contain each other, but we want to follow how ranges work.)
+	 */
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		return false;
+
+	rangetyp = typcache->rngtype;
+
+	multirange_deserialize(mr1, &range_count1, &ranges1);
+	multirange_deserialize(mr2, &range_count2, &ranges2);
+
+	/*
+	 * Every range in mr1 gets a chance to overlap with the ranges in mr2, but
+	 * we can use their ordering to avoid O(n^2). This is similar to
+	 * range_overlaps_multirange where r1 : r2 :: mrr : r, but there if we
+	 * don't find an overlap with r we're done, and here if we don't find an
+	 * overlap with r2 we try the next r2.
+	 */
+	r1 = ranges1[0];
+	for (i1 = 0, i2 = 0; i2 < range_count2; i2++)
+	{
+		r2 = ranges2[i2];
+
+		/* Discard r1s while r1 << r2 */
+		while (range_before_internal(rangetyp, r1, r2))
+		{
+			if (++i1 >= range_count1)
+				return false;
+			r1 = ranges1[i1];
+		}
+
+		/*
+		 * If r1 && r2, we're done, otherwise we failed to find an overlap for
+		 * r2, so go to the next one.
+		 */
+		if (range_overlaps_internal(rangetyp, r1, r2))
+			return true;
+	}
+
+	/* We looked through all of mr2 without finding an overlap */
+	return false;
+}
+
+/* does not extend to right of? */
+Datum
+range_overleft_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_overleft_internal(typcache->rngtype, r, ranges[range_count - 1]));
+}
+
+Datum
+multirange_overleft_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_overleft_internal(typcache->rngtype, ranges[range_count - 1], r));
+}
+
+Datum
+multirange_overleft_multirange(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
+	MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr1));
+
+	multirange_deserialize(mr1, &range_count1, &ranges1);
+	multirange_deserialize(mr2, &range_count2, &ranges2);
+
+	PG_RETURN_BOOL(range_overleft_internal(typcache->rngtype, ranges1[range_count1 - 1], ranges2[range_count2 - 1]));
+}
+
+/* does not extend to left of? */
+Datum
+range_overright_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_overright_internal(typcache->rngtype, r, ranges[0]));
+}
+
+Datum
+multirange_overright_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_overright_internal(typcache->rngtype, ranges[0], r));
+}
+
+Datum
+multirange_overright_multirange(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
+	MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr1));
+
+	multirange_deserialize(mr1, &range_count1, &ranges1);
+	multirange_deserialize(mr2, &range_count2, &ranges2);
+
+	PG_RETURN_BOOL(range_overright_internal(typcache->rngtype, ranges1[0], ranges2[0]));
+}
+
+/* contains? */
+Datum
+multirange_contains_multirange(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_contains_multirange_internal(typcache, mr1, mr2));
+}
+
+/* contained by? */
+Datum
+multirange_contained_by_multirange(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_contains_multirange_internal(typcache, mr2, mr1));
+}
+
+/*
+ * Test whether multirange mr1 contains every range from another multirange mr2.
+ */
+bool
+multirange_contains_multirange_internal(TypeCacheEntry *typcache,
+										MultirangeType * mr1, MultirangeType * mr2)
+{
+	TypeCacheEntry *rangetyp;
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+	RangeType  *r1;
+	RangeType  *r2;
+	int			i1,
+				i2;
+
+	rangetyp = typcache->rngtype;
+
+	multirange_deserialize(mr1, &range_count1, &ranges1);
+	multirange_deserialize(mr2, &range_count2, &ranges2);
+
+	/*
+	 * We follow the same logic for empties as ranges: - an empty multirange
+	 * contains an empty range/multirange. - an empty multirange can't contain
+	 * any other range/multirange. - an empty multirange is contained by any
+	 * other range/multirange.
+	 */
+
+	if (range_count2 == 0)
+		return true;
+	if (range_count1 == 0)
+		return false;
+
+	/*
+	 * Every range in mr2 must be contained by some range in mr1. To avoid
+	 * O(n^2) we walk through both ranges in tandem.
+	 */
+	r1 = ranges1[0];
+	for (i1 = 0, i2 = 0; i2 < range_count2; i2++)
+	{
+		r2 = ranges2[i2];
+
+		/* Discard r1s while r1 << r2 */
+		while (range_before_internal(rangetyp, r1, r2))
+		{
+			if (++i1 >= range_count1)
+				return false;
+			r1 = ranges1[i1];
+		}
+
+		/*
+		 * If r1 @> r2, go to the next r2, otherwise return false (since every
+		 * r1[n] and r1[n+1] must have a gap). Note this will give weird
+		 * answers if you don't canonicalize, e.g. with a custom
+		 * int2multirange {[1,1], [2,2]} there is a "gap". But that is
+		 * consistent with other range operators, e.g. '[1,1]'::int2range -|-
+		 * '[2,2]'::int2range is false.
+		 */
+		if (!range_contains_internal(rangetyp, r1, r2))
+			return false;
+	}
+
+	/* All ranges in mr2 are satisfied */
+	return true;
+}
+
+/* strictly left of? */
+Datum
+range_before_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(range_before_multirange_internal(typcache, r, mr));
+}
+
+Datum
+multirange_before_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(range_after_multirange_internal(typcache, r, mr));
+}
+
+Datum
+multirange_before_multirange(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_before_multirange_internal(typcache, mr1, mr2));
+}
+
+/* strictly right of? */
+Datum
+range_after_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(range_after_multirange_internal(typcache, r, mr));
+}
+
+Datum
+multirange_after_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(range_before_multirange_internal(typcache, r, mr));
+}
+
+Datum
+multirange_after_multirange(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_before_multirange_internal(typcache, mr2, mr1));
+}
+
+/* strictly left of? (internal version) */
+bool
+range_before_multirange_internal(TypeCacheEntry *typcache, RangeType *r,
+								 MultirangeType * mr)
+{
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		return false;
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	return range_before_internal(typcache->rngtype, r, ranges[0]);
+}
+
+bool
+multirange_before_multirange_internal(TypeCacheEntry *typcache, MultirangeType * mr1,
+									  MultirangeType * mr2)
+{
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		return false;
+
+	multirange_deserialize(mr1, &range_count1, &ranges1);
+	multirange_deserialize(mr2, &range_count2, &ranges2);
+
+	return range_before_internal(typcache->rngtype, ranges1[range_count1 - 1],
+								 ranges2[0]);
+}
+
+/* strictly right of? (internal version) */
+bool
+range_after_multirange_internal(TypeCacheEntry *typcache, RangeType *r,
+								MultirangeType * mr)
+{
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		return false;
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	return range_after_internal(typcache->rngtype, r, ranges[range_count - 1]);
+}
+
+/* adjacent to? */
+Datum
+range_adjacent_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		return false;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_adjacent_internal(typcache->rngtype, r, ranges[0]));
+}
+
+Datum
+multirange_adjacent_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		return false;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_adjacent_internal(typcache->rngtype, ranges[range_count - 1], r));
+}
+
+Datum
+multirange_adjacent_multirange(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
+	MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		return false;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr1));
+
+	multirange_deserialize(mr1, &range_count1, &ranges1);
+	multirange_deserialize(mr2, &range_count2, &ranges2);
+
+	PG_RETURN_BOOL(range_adjacent_internal(typcache->rngtype, ranges1[range_count1 - 1], ranges2[0]));
+}
+
+/* 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);
+}
+
+/* multirange -> range functions */
+
+/* Find the smallest range that includes everything in the multirange */
+Datum
+range_merge_from_multirange(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	Oid			mltrngtypoid = MultirangeTypeGetOid(mr);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_RANGE_P(make_empty_range(typcache->rngtype));
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_RANGE_P(range_union_internal(typcache->rngtype, ranges[0],
+										   ranges[range_count - 1], false));
+}
+
+/* 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/pg_upgrade_support.c b/src/backend/utils/adt/pg_upgrade_support.c
index 18f2ee8226..248ea7f44a 100644
--- a/src/backend/utils/adt/pg_upgrade_support.c
+++ b/src/backend/utils/adt/pg_upgrade_support.c
@@ -51,6 +51,28 @@ binary_upgrade_set_next_array_pg_type_oid(PG_FUNCTION_ARGS)
 	PG_RETURN_VOID();
 }
 
+Datum
+binary_upgrade_set_next_multirange_pg_type_oid(PG_FUNCTION_ARGS)
+{
+	Oid			typoid = PG_GETARG_OID(0);
+
+	CHECK_IS_BINARY_UPGRADE;
+	binary_upgrade_next_mrng_pg_type_oid = typoid;
+
+	PG_RETURN_VOID();
+}
+
+Datum
+binary_upgrade_set_next_multirange_array_pg_type_oid(PG_FUNCTION_ARGS)
+{
+	Oid			typoid = PG_GETARG_OID(0);
+
+	CHECK_IS_BINARY_UPGRADE;
+	binary_upgrade_next_mrng_array_pg_type_oid = typoid;
+
+	PG_RETURN_VOID();
+}
+
 Datum
 binary_upgrade_set_next_toast_pg_type_oid(PG_FUNCTION_ARGS)
 {
diff --git a/src/backend/utils/adt/pseudotypes.c b/src/backend/utils/adt/pseudotypes.c
index 4653fc33e6..c909c9dc8c 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 01ad8bc240..9d1ca13e32 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
 {
@@ -431,19 +429,37 @@ range_lower(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
 	TypeCacheEntry *typcache;
+	bool		isnull;
+	Datum		result;
+
+	typcache = range_get_typcache(fcinfo, RangeTypeGetOid(r1));
+
+	result = range_lower_internal(typcache, r1, &isnull);
+
+	if (isnull)
+		PG_RETURN_NULL();
+	else
+		PG_RETURN_DATUM(result);
+}
+
+Datum
+range_lower_internal(TypeCacheEntry *typcache, const RangeType *r1, bool *isnull)
+{
 	RangeBound	lower;
 	RangeBound	upper;
 	bool		empty;
 
-	typcache = range_get_typcache(fcinfo, RangeTypeGetOid(r1));
-
 	range_deserialize(typcache, r1, &lower, &upper, &empty);
 
 	/* Return NULL if there's no finite lower bound */
 	if (empty || lower.infinite)
-		PG_RETURN_NULL();
+	{
+		*isnull = true;
+		return 0;
+	}
 
-	PG_RETURN_DATUM(lower.val);
+	*isnull = false;
+	return lower.val;
 }
 
 /* extract upper bound value */
@@ -452,19 +468,37 @@ range_upper(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
 	TypeCacheEntry *typcache;
+	bool		isnull;
+	Datum		result;
+
+	typcache = range_get_typcache(fcinfo, RangeTypeGetOid(r1));
+
+	result = range_upper_internal(typcache, r1, &isnull);
+
+	if (isnull)
+		PG_RETURN_NULL();
+	else
+		PG_RETURN_DATUM(result);
+}
+
+Datum
+range_upper_internal(TypeCacheEntry *typcache, const RangeType *r1, bool *isnull)
+{
 	RangeBound	lower;
 	RangeBound	upper;
 	bool		empty;
 
-	typcache = range_get_typcache(fcinfo, RangeTypeGetOid(r1));
-
 	range_deserialize(typcache, r1, &lower, &upper, &empty);
 
 	/* Return NULL if there's no finite upper bound */
 	if (empty || upper.infinite)
-		PG_RETURN_NULL();
+	{
+		*isnull = true;
+		return 0;
+	}
 
-	PG_RETURN_DATUM(upper.val);
+	*isnull = false;
+	return upper.val;
 }
 
 
@@ -475,9 +509,8 @@ Datum
 range_empty(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
-	char		flags = range_get_flags(r1);
 
-	PG_RETURN_BOOL(flags & RANGE_EMPTY);
+	PG_RETURN_BOOL(range_has_flag(r1, RANGE_EMPTY));
 }
 
 /* is lower bound inclusive? */
@@ -485,9 +518,8 @@ Datum
 range_lower_inc(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
-	char		flags = range_get_flags(r1);
 
-	PG_RETURN_BOOL(flags & RANGE_LB_INC);
+	PG_RETURN_BOOL(range_has_flag(r1, RANGE_LB_INC));
 }
 
 /* is upper bound inclusive? */
@@ -495,9 +527,8 @@ Datum
 range_upper_inc(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
-	char		flags = range_get_flags(r1);
 
-	PG_RETURN_BOOL(flags & RANGE_UB_INC);
+	PG_RETURN_BOOL(range_has_flag(r1, RANGE_UB_INC));
 }
 
 /* is lower bound infinite? */
@@ -505,9 +536,8 @@ Datum
 range_lower_inf(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
-	char		flags = range_get_flags(r1);
 
-	PG_RETURN_BOOL(flags & RANGE_LB_INF);
+	PG_RETURN_BOOL(range_has_flag(r1, RANGE_LB_INF));
 }
 
 /* is upper bound infinite? */
@@ -515,9 +545,8 @@ Datum
 range_upper_inf(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
-	char		flags = range_get_flags(r1);
 
-	PG_RETURN_BOOL(flags & RANGE_UB_INF);
+	PG_RETURN_BOOL(range_has_flag(r1, RANGE_UB_INF));
 }
 
 
@@ -957,7 +986,25 @@ range_minus(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
 	RangeType  *r2 = PG_GETARG_RANGE_P(1);
+	RangeType  *ret;
 	TypeCacheEntry *typcache;
+
+	/* 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));
+
+	ret = range_minus_internal(typcache, r1, r2);
+	if (ret)
+		PG_RETURN_RANGE_P(ret);
+	else
+		PG_RETURN_NULL();
+}
+
+RangeType *
+range_minus_internal(TypeCacheEntry *typcache, RangeType *r1, RangeType *r2)
+{
 	RangeBound	lower1,
 				lower2;
 	RangeBound	upper1,
@@ -969,18 +1016,12 @@ range_minus(PG_FUNCTION_ARGS)
 				cmp_u1l2,
 				cmp_u1u2;
 
-	/* 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));
-
 	range_deserialize(typcache, r1, &lower1, &upper1, &empty1);
 	range_deserialize(typcache, r2, &lower2, &upper2, &empty2);
 
 	/* if either is empty, r1 is the correct answer */
 	if (empty1 || empty2)
-		PG_RETURN_RANGE_P(r1);
+		return r1;
 
 	cmp_l1l2 = range_cmp_bounds(typcache, &lower1, &lower2);
 	cmp_l1u2 = range_cmp_bounds(typcache, &lower1, &upper2);
@@ -993,34 +1034,34 @@ range_minus(PG_FUNCTION_ARGS)
 				 errmsg("result of range difference would not be contiguous")));
 
 	if (cmp_l1u2 > 0 || cmp_u1l2 < 0)
-		PG_RETURN_RANGE_P(r1);
+		return r1;
 
 	if (cmp_l1l2 >= 0 && cmp_u1u2 <= 0)
-		PG_RETURN_RANGE_P(make_empty_range(typcache));
+		return make_empty_range(typcache);
 
 	if (cmp_l1l2 <= 0 && cmp_u1l2 >= 0 && cmp_u1u2 <= 0)
 	{
 		lower2.inclusive = !lower2.inclusive;
 		lower2.lower = false;	/* it will become the upper bound */
-		PG_RETURN_RANGE_P(make_range(typcache, &lower1, &lower2, false));
+		return make_range(typcache, &lower1, &lower2, false);
 	}
 
 	if (cmp_l1l2 >= 0 && cmp_u1u2 >= 0 && cmp_l1u2 <= 0)
 	{
 		upper2.inclusive = !upper2.inclusive;
 		upper2.lower = true;	/* it will become the lower bound */
-		PG_RETURN_RANGE_P(make_range(typcache, &upper2, &upper1, false));
+		return make_range(typcache, &upper2, &upper1, false);
 	}
 
 	elog(ERROR, "unexpected case in range_minus");
-	PG_RETURN_NULL();
+	return NULL;
 }
 
 /*
  * Set union.  If strict is true, it is an error that the two input ranges
  * are not adjacent or overlapping.
  */
-static RangeType *
+RangeType *
 range_union_internal(TypeCacheEntry *typcache, RangeType *r1, RangeType *r2,
 					 bool strict)
 {
@@ -1101,6 +1142,19 @@ range_intersect(PG_FUNCTION_ARGS)
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
 	RangeType  *r2 = PG_GETARG_RANGE_P(1);
 	TypeCacheEntry *typcache;
+
+	/* 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));
+
+	PG_RETURN_RANGE_P(range_intersect_internal(typcache, r1, r2));
+}
+
+RangeType *
+range_intersect_internal(TypeCacheEntry *typcache, const RangeType *r1, const RangeType *r2)
+{
 	RangeBound	lower1,
 				lower2;
 	RangeBound	upper1,
@@ -1110,17 +1164,11 @@ range_intersect(PG_FUNCTION_ARGS)
 	RangeBound *result_lower;
 	RangeBound *result_upper;
 
-	/* 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));
-
 	range_deserialize(typcache, r1, &lower1, &upper1, &empty1);
 	range_deserialize(typcache, r2, &lower2, &upper2, &empty2);
 
-	if (empty1 || empty2 || !DatumGetBool(range_overlaps(fcinfo)))
-		PG_RETURN_RANGE_P(make_empty_range(typcache));
+	if (empty1 || empty2 || !range_overlaps_internal(typcache, r1, r2))
+		return make_empty_range(typcache);
 
 	if (range_cmp_bounds(typcache, &lower1, &lower2) >= 0)
 		result_lower = &lower1;
@@ -1132,9 +1180,81 @@ range_intersect(PG_FUNCTION_ARGS)
 	else
 		result_upper = &upper2;
 
-	PG_RETURN_RANGE_P(make_range(typcache, result_lower, result_upper, false));
+	return make_range(typcache, result_lower, result_upper, false);
 }
 
+/* range, range -> range, range functions */
+
+/*
+ * range_split_internal - if r2 intersects the middle of r1, leaving non-empty
+ * ranges on both sides, then return true and set output1 and output2 to the
+ * results of r1 - r2 (in order). Otherwise return false and don't set output1
+ * or output2. Neither input range should be empty.
+ */
+bool
+range_split_internal(TypeCacheEntry *typcache, const RangeType *r1, const RangeType *r2,
+					 RangeType **output1, RangeType **output2)
+{
+	RangeBound	lower1,
+				lower2;
+	RangeBound	upper1,
+				upper2;
+	bool		empty1,
+				empty2;
+
+	range_deserialize(typcache, r1, &lower1, &upper1, &empty1);
+	range_deserialize(typcache, r2, &lower2, &upper2, &empty2);
+
+	if (range_cmp_bounds(typcache, &lower1, &lower2) < 0 &&
+		range_cmp_bounds(typcache, &upper1, &upper2) > 0)
+	{
+		/*
+		 * Need to invert inclusive/exclusive for the lower2 and upper2
+		 * points. They can't be infinite though. We're allowed to overwrite
+		 * these RangeBounds since they only exist locally.
+		 */
+		lower2.inclusive = !lower2.inclusive;
+		lower2.lower = false;
+		upper2.inclusive = !upper2.inclusive;
+		upper2.lower = true;
+
+		*output1 = make_range(typcache, &lower1, &lower2, false);
+		*output2 = make_range(typcache, &upper2, &upper1, false);
+		return true;
+	}
+
+	return false;
+}
+
+/* range -> range aggregate functions */
+
+Datum
+range_intersect_agg_transfn(PG_FUNCTION_ARGS)
+{
+	MemoryContext aggContext;
+	Oid			rngtypoid;
+	TypeCacheEntry *typcache;
+	RangeType  *result;
+	RangeType  *current;
+
+	if (!AggCheckCallContext(fcinfo, &aggContext))
+		elog(ERROR, "range_intersect_agg_transfn called in non-aggregate context");
+
+	rngtypoid = get_fn_expr_argtype(fcinfo->flinfo, 1);
+	if (!type_is_range(rngtypoid))
+		ereport(ERROR, (errmsg("range_intersect_agg must be called with a range")));
+
+	typcache = range_get_typcache(fcinfo, rngtypoid);
+
+	/* strictness ensures these are non-null */
+	result = PG_GETARG_RANGE_P(0);
+	current = PG_GETARG_RANGE_P(1);
+
+	result = range_intersect_internal(typcache, result, current);
+	PG_RETURN_RANGE_P(result);
+}
+
+
 /* Btree support */
 
 /* btree comparator */
@@ -1144,12 +1264,6 @@ range_cmp(PG_FUNCTION_ARGS)
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
 	RangeType  *r2 = PG_GETARG_RANGE_P(1);
 	TypeCacheEntry *typcache;
-	RangeBound	lower1,
-				lower2;
-	RangeBound	upper1,
-				upper2;
-	bool		empty1,
-				empty2;
 	int			cmp;
 
 	check_stack_depth();		/* recurses when subtype is a range type */
@@ -1160,6 +1274,28 @@ range_cmp(PG_FUNCTION_ARGS)
 
 	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, const RangeType *r1, const 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);
 
@@ -1177,10 +1313,7 @@ range_cmp(PG_FUNCTION_ARGS)
 			cmp = range_cmp_bounds(typcache, &upper1, &upper2);
 	}
 
-	PG_FREE_IF_COPY(r1, 0);
-	PG_FREE_IF_COPY(r2, 1);
-
-	PG_RETURN_INT32(cmp);
+	return cmp;
 }
 
 /* inequality operators using the range_cmp function */
@@ -1218,13 +1351,10 @@ range_gt(PG_FUNCTION_ARGS)
 
 /* Hash support */
 
-/* hash a range value */
-Datum
-hash_range(PG_FUNCTION_ARGS)
+uint32
+hash_range_internal(TypeCacheEntry *typcache, const RangeType *r)
 {
-	RangeType  *r = PG_GETARG_RANGE_P(0);
 	uint32		result;
-	TypeCacheEntry *typcache;
 	TypeCacheEntry *scache;
 	RangeBound	lower;
 	RangeBound	upper;
@@ -1233,10 +1363,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 +1404,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, const RangeType *r, Datum seed)
+{
+	uint64		result;
 	TypeCacheEntry *scache;
 	RangeBound	lower;
 	RangeBound	upper;
@@ -1300,10 +1433,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 +1471,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));
 }
 
 /*
@@ -1769,6 +1916,21 @@ range_get_flags(const RangeType *range)
 	return *((char *) range + VARSIZE(range) - 1);
 }
 
+/*
+ * range_has_flag: set whether a range has a specific flag.
+ *
+ * This lets expose some of our functions that just check flags
+ * to the rest of the code base (like to multiranges)
+ * without writing full-fledged *_internal versions.
+ */
+bool
+range_has_flag(const RangeType *r1, char flag)
+{
+	char		flags = range_get_flags(r1);
+
+	return flags & flag;
+}
+
 /*
  * range_set_contain_empty: set the RANGE_CONTAIN_EMPTY bit in the value.
  *
@@ -1937,6 +2099,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 27bbb58f56..c6c079c623 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -2494,6 +2494,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
  *
@@ -3149,7 +3159,7 @@ get_namespace_name_or_temp(Oid nspid)
 		return get_namespace_name(nspid);
 }
 
-/*				---------- PG_RANGE CACHE ----------				 */
+/*				---------- PG_RANGE CACHES ----------				 */
 
 /*
  * get_range_subtype
@@ -3202,6 +3212,56 @@ get_range_collation(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->rngmultitypid;
+		ReleaseSysCache(tp);
+		return result;
+	}
+	else
+		return InvalidOid;
+}
+
+/*
+ * get_range_multirange_subtype
+ *		Returns the subtype of a given multirange type
+ *
+ * Returns InvalidOid if the type is not a multirange type.
+ */
+Oid
+get_range_multirange_subtype(Oid multirangeOid)
+{
+	HeapTuple	tp;
+
+	tp = SearchSysCache1(RANGEMULTIRANGE, 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 53d9ddf159..7654331a59 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -651,6 +651,18 @@ static const struct cachedesc cacheinfo[] = {
 		},
 		64
 	},
+	{RangeRelationId,			/* RANGEMULTIRANGE */
+		RangeMultirangeTypidIndexId,
+		1,
+		{
+			Anum_pg_range_rngmultitypid,
+			0,
+			0,
+			0
+		},
+		4
+	},
+
 	{RangeRelationId,			/* RANGETYPE */
 		RangeTypidIndexId,
 		1,
diff --git a/src/backend/utils/cache/typcache.c b/src/backend/utils/cache/typcache.c
index 854f133f9b..160846cf10 100644
--- a/src/backend/utils/cache/typcache.c
+++ b/src/backend/utils/cache/typcache.c
@@ -286,6 +286,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);
@@ -302,6 +303,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 TypeCacheTypCallback(Datum arg, int cacheid, uint32 hashvalue);
 static void TypeCacheOpcCallback(Datum arg, int cacheid, uint32 hashvalue);
@@ -553,8 +557,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))
@@ -591,7 +595,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))
@@ -616,7 +620,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))
@@ -641,7 +645,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))
@@ -693,6 +697,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;
@@ -737,6 +748,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;
@@ -816,6 +834,16 @@ lookup_type_cache(Oid type_id, int flags)
 			(void) lookup_type_cache(typentry->rngelemtype->type_id, 0);
 	}
 
+	/*
+	 * 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
 	 */
@@ -927,6 +955,22 @@ load_rangetype_info(TypeCacheEntry *typentry)
 	typentry->rngelemtype = lookup_type_cache(subtypeOid, 0);
 }
 
+/*
+ * load_multirangetype_info --- helper routine to set up multirange type
+ * information
+ */
+static void
+load_multirangetype_info(TypeCacheEntry *typentry)
+{
+	Oid			rangetypeOid;
+
+	rangetypeOid = get_range_multirange_subtype(typentry->type_id);
+	if (!OidIsValid(rangetypeOid))
+		elog(ERROR, "cache lookup failed for multirange type %u",
+			 typentry->type_id);
+
+	typentry->rngtype = lookup_type_cache(rangetypeOid, TYPECACHE_RANGE_INFO);
+}
 
 /*
  * load_domaintype_info --- helper routine to set up domain constraint info
@@ -1527,11 +1571,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
@@ -1574,6 +1618,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 0201e4f8d3..b10cd99144 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,131 @@ resolve_polymorphic_tupdesc(TupleDesc tupdesc, oidvector *declared_args,
 				return false;
 			anyelement_type = subtype;
 		}
+
+		if (OidIsValid(anymultirange_type))
+		{
+			Oid			rngtype;
+			Oid			subtype;
+
+			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;
+			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;
+			Oid			subtype;
+
+			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;
+
+			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;
+			Oid			mltrngtype;
+			Oid			rngtype;
+
+			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;
+
+			mltrngtype = resolve_generic_type(ANYMULTIRANGEOID,
+											  anyrange_type,
+											  ANYRANGEOID);
+
+			/* check for inconsistent range and multirange results */
+			rngtype = get_range_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 +761,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 +793,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 +856,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 +880,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 +906,132 @@ resolve_polymorphic_argtypes(int numargs, Oid *argtypes, char *argmodes,
 				return false;
 			anyelement_type = subtype;
 		}
+
+		if (OidIsValid(anymultirange_type))
+		{
+			Oid			rngtype;
+			Oid			subtype;
+
+			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;
+
+			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;
+			Oid			subtype;
+
+			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;
+
+			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;
+			Oid			mltrngtype;
+			Oid			rngtype;
+
+			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;
+
+			mltrngtype = resolve_generic_type(ANYMULTIRANGEOID,
+											  anyrange_type,
+											  ANYRANGEOID);
+
+			/* check for inconsistent range and multirange results */
+			rngtype = get_range_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 +1051,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/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 28312e14ef..ba53ac13ba 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -271,7 +271,8 @@ static void dumpSearchPath(Archive *AH);
 static void binary_upgrade_set_type_oids_by_type_oid(Archive *fout,
 													 PQExpBuffer upgrade_buffer,
 													 Oid pg_type_oid,
-													 bool force_array_type);
+													 bool force_array_type,
+													 bool include_multirange_type);
 static bool binary_upgrade_set_type_oids_by_rel_oid(Archive *fout,
 													PQExpBuffer upgrade_buffer, Oid pg_rel_oid);
 static void binary_upgrade_set_pg_class_oids(Archive *fout,
@@ -1580,7 +1581,7 @@ selectDumpableType(TypeInfo *tyinfo, Archive *fout)
 	}
 
 	/* skip auto-generated array types */
-	if (tyinfo->isArray)
+	if (tyinfo->isArray || tyinfo->isMultirange)
 	{
 		tyinfo->dobj.objType = DO_DUMMY_TYPE;
 
@@ -4339,16 +4340,49 @@ append_depends_on_extension(Archive *fout,
 	}
 }
 
+static Oid
+get_next_possible_free_pg_type_oid(Archive *fout, PQExpBuffer upgrade_query)
+{
+	/*
+	 * If the old version didn't assign an array type, but the new version
+	 * does, we must select an unused type OID to assign.  This currently only
+	 * happens for domains, when upgrading pre-v11 to v11 and up.
+	 *
+	 * Note: local state here is kind of ugly, but we must have some, since we
+	 * mustn't choose the same unused OID more than once.
+	 */
+	static Oid	next_possible_free_oid = FirstNormalObjectId;
+	PGresult   *res;
+	bool		is_dup;
+
+	do
+	{
+		++next_possible_free_oid;
+		printfPQExpBuffer(upgrade_query,
+						  "SELECT EXISTS(SELECT 1 "
+						  "FROM pg_catalog.pg_type "
+						  "WHERE oid = '%u'::pg_catalog.oid);",
+						  next_possible_free_oid);
+		res = ExecuteSqlQueryForSingleRow(fout, upgrade_query->data);
+		is_dup = (PQgetvalue(res, 0, 0)[0] == 't');
+		PQclear(res);
+	} while (is_dup);
+
+	return next_possible_free_oid;
+}
 
 static void
 binary_upgrade_set_type_oids_by_type_oid(Archive *fout,
 										 PQExpBuffer upgrade_buffer,
 										 Oid pg_type_oid,
-										 bool force_array_type)
+										 bool force_array_type,
+										 bool include_multirange_type)
 {
 	PQExpBuffer upgrade_query = createPQExpBuffer();
 	PGresult   *res;
 	Oid			pg_type_array_oid;
+	Oid			pg_type_multirange_oid;
+	Oid			pg_type_multirange_array_oid;
 
 	appendPQExpBufferStr(upgrade_buffer, "\n-- For binary upgrade, must preserve pg_type oid\n");
 	appendPQExpBuffer(upgrade_buffer,
@@ -4369,33 +4403,7 @@ binary_upgrade_set_type_oids_by_type_oid(Archive *fout,
 	PQclear(res);
 
 	if (!OidIsValid(pg_type_array_oid) && force_array_type)
-	{
-		/*
-		 * If the old version didn't assign an array type, but the new version
-		 * does, we must select an unused type OID to assign.  This currently
-		 * only happens for domains, when upgrading pre-v11 to v11 and up.
-		 *
-		 * Note: local state here is kind of ugly, but we must have some,
-		 * since we mustn't choose the same unused OID more than once.
-		 */
-		static Oid	next_possible_free_oid = FirstNormalObjectId;
-		bool		is_dup;
-
-		do
-		{
-			++next_possible_free_oid;
-			printfPQExpBuffer(upgrade_query,
-							  "SELECT EXISTS(SELECT 1 "
-							  "FROM pg_catalog.pg_type "
-							  "WHERE oid = '%u'::pg_catalog.oid);",
-							  next_possible_free_oid);
-			res = ExecuteSqlQueryForSingleRow(fout, upgrade_query->data);
-			is_dup = (PQgetvalue(res, 0, 0)[0] == 't');
-			PQclear(res);
-		} while (is_dup);
-
-		pg_type_array_oid = next_possible_free_oid;
-	}
+		pg_type_array_oid = get_next_possible_free_pg_type_oid(fout, upgrade_query);
 
 	if (OidIsValid(pg_type_array_oid))
 	{
@@ -4406,6 +4414,46 @@ binary_upgrade_set_type_oids_by_type_oid(Archive *fout,
 						  pg_type_array_oid);
 	}
 
+	/*
+	 * Pre-set the multirange type oid and its own array type oid.
+	 */
+	if (include_multirange_type)
+	{
+		if (fout->remoteVersion >= 130000)
+		{
+			appendPQExpBuffer(upgrade_query,
+							  "SELECT t.oid, t.typarray "
+							  "FROM pg_catalog.pg_type t "
+							  "JOIN pg_catalog.pg_range r "
+							  "ON t.oid = r.rngmultitypid "
+							  "WHERE r.rngtypid = '%u'::pg_catalog.oid;",
+							  pg_type_oid);
+
+			res = ExecuteSqlQueryForSingleRow(fout, upgrade_query->data);
+
+			pg_type_multirange_oid = atooid(PQgetvalue(res, 0, PQfnumber(res, "oid")));
+			pg_type_multirange_array_oid = atooid(PQgetvalue(res, 0, PQfnumber(res, "typarray")));
+
+			PQclear(res);
+		}
+		else
+		{
+			pg_type_multirange_oid = get_next_possible_free_pg_type_oid(fout, upgrade_query);
+			pg_type_multirange_array_oid = get_next_possible_free_pg_type_oid(fout, upgrade_query);
+		}
+
+		appendPQExpBufferStr(upgrade_buffer,
+							 "\n-- For binary upgrade, must preserve multirange pg_type oid\n");
+		appendPQExpBuffer(upgrade_buffer,
+						  "SELECT pg_catalog.binary_upgrade_set_next_multirange_pg_type_oid('%u'::pg_catalog.oid);\n\n",
+						  pg_type_multirange_oid);
+		appendPQExpBufferStr(upgrade_buffer,
+							 "\n-- For binary upgrade, must preserve multirange pg_type array oid\n");
+		appendPQExpBuffer(upgrade_buffer,
+						  "SELECT pg_catalog.binary_upgrade_set_next_multirange_array_pg_type_oid('%u'::pg_catalog.oid);\n\n",
+						  pg_type_multirange_array_oid);
+	}
+
 	destroyPQExpBuffer(upgrade_query);
 }
 
@@ -4439,7 +4487,7 @@ binary_upgrade_set_type_oids_by_rel_oid(Archive *fout,
 	pg_type_oid = atooid(PQgetvalue(upgrade_res, 0, PQfnumber(upgrade_res, "crel")));
 
 	binary_upgrade_set_type_oids_by_type_oid(fout, upgrade_buffer,
-											 pg_type_oid, false);
+											 pg_type_oid, false, false);
 
 	if (!PQgetisnull(upgrade_res, 0, PQfnumber(upgrade_res, "trel")))
 	{
@@ -4988,6 +5036,11 @@ getTypes(Archive *fout, int *numTypes)
 		else
 			tyinfo[i].isArray = false;
 
+		if (tyinfo[i].typtype == 'm')
+			tyinfo[i].isMultirange = true;
+		else
+			tyinfo[i].isMultirange = false;
+
 		/* Decide whether we want to dump it */
 		selectDumpableType(&tyinfo[i], fout);
 
@@ -8102,9 +8155,12 @@ getProcLangs(Archive *fout, int *numProcLangs)
 
 /*
  * getCasts
- *	  get basic information about every cast in the system
+ *	  get basic information about most casts in the system
  *
  * numCasts is set to the number of casts read in
+ *
+ * Skip casts from a range to its multirange, since we'll create those
+ * automatically.
  */
 CastInfo *
 getCasts(Archive *fout, int *numCasts)
@@ -8122,7 +8178,20 @@ getCasts(Archive *fout, int *numCasts)
 	int			i_castcontext;
 	int			i_castmethod;
 
-	if (fout->remoteVersion >= 80400)
+	if (fout->remoteVersion >= 130000)
+	{
+		appendPQExpBufferStr(query, "SELECT tableoid, oid, "
+							 "castsource, casttarget, castfunc, castcontext, "
+							 "castmethod "
+							 "FROM pg_cast c "
+							 "WHERE NOT EXISTS ( "
+							 "SELECT 1 FROM pg_range r "
+							 "WHERE c.castsource = r.rngtypid "
+							 "AND c.casttarget = r.rngmultitypid "
+							 ") "
+							 "ORDER BY 3,4");
+	}
+	else if (fout->remoteVersion >= 80400)
 	{
 		appendPQExpBufferStr(query, "SELECT tableoid, oid, "
 							 "castsource, casttarget, castfunc, castcontext, "
@@ -10310,7 +10379,7 @@ dumpEnumType(Archive *fout, TypeInfo *tyinfo)
 	if (dopt->binary_upgrade)
 		binary_upgrade_set_type_oids_by_type_oid(fout, q,
 												 tyinfo->dobj.catId.oid,
-												 false);
+												 false, false);
 
 	appendPQExpBuffer(q, "CREATE TYPE %s AS ENUM (",
 					  qualtypname);
@@ -10436,7 +10505,7 @@ dumpRangeType(Archive *fout, TypeInfo *tyinfo)
 	if (dopt->binary_upgrade)
 		binary_upgrade_set_type_oids_by_type_oid(fout, q,
 												 tyinfo->dobj.catId.oid,
-												 false);
+												 false, true);
 
 	appendPQExpBuffer(q, "CREATE TYPE %s AS RANGE (",
 					  qualtypname);
@@ -10542,7 +10611,7 @@ dumpUndefinedType(Archive *fout, TypeInfo *tyinfo)
 	if (dopt->binary_upgrade)
 		binary_upgrade_set_type_oids_by_type_oid(fout, q,
 												 tyinfo->dobj.catId.oid,
-												 false);
+												 false, false);
 
 	appendPQExpBuffer(q, "CREATE TYPE %s;\n",
 					  qualtypname);
@@ -10747,7 +10816,7 @@ dumpBaseType(Archive *fout, TypeInfo *tyinfo)
 	if (dopt->binary_upgrade)
 		binary_upgrade_set_type_oids_by_type_oid(fout, q,
 												 tyinfo->dobj.catId.oid,
-												 false);
+												 false, false);
 
 	appendPQExpBuffer(q,
 					  "CREATE TYPE %s (\n"
@@ -10934,7 +11003,8 @@ dumpDomain(Archive *fout, TypeInfo *tyinfo)
 	if (dopt->binary_upgrade)
 		binary_upgrade_set_type_oids_by_type_oid(fout, q,
 												 tyinfo->dobj.catId.oid,
-												 true); /* force array type */
+												 true,		/* force array type */
+												 false);	/* force multirange type */
 
 	qtypname = pg_strdup(fmtId(tyinfo->dobj.name));
 	qualtypname = pg_strdup(fmtQualifiedDumpable(tyinfo));
@@ -11122,7 +11192,7 @@ dumpCompositeType(Archive *fout, TypeInfo *tyinfo)
 	{
 		binary_upgrade_set_type_oids_by_type_oid(fout, q,
 												 tyinfo->dobj.catId.oid,
-												 false);
+												 false, false);
 		binary_upgrade_set_pg_class_oids(fout, q, tyinfo->typrelid, false);
 	}
 
@@ -11396,7 +11466,7 @@ dumpShellType(Archive *fout, ShellTypeInfo *stinfo)
 	if (dopt->binary_upgrade)
 		binary_upgrade_set_type_oids_by_type_oid(fout, q,
 												 stinfo->baseType->dobj.catId.oid,
-												 false);
+												 false, false);
 
 	appendPQExpBuffer(q, "CREATE TYPE %s;\n",
 					  fmtQualifiedDumpable(stinfo));
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index e0c6444ef6..90a93b4cb2 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -176,6 +176,7 @@ typedef struct _typeInfo
 	char		typrelkind;		/* 'r', 'v', 'c', etc */
 	char		typtype;		/* 'b', 'c', etc */
 	bool		isArray;		/* true if auto-generated array type */
+	bool		isMultirange;	/* true if auto-generated multirange type */
 	bool		isDefined;		/* true if typisdefined */
 	/* If needed, we'll create a "shell type" entry for it; link that here: */
 	struct _shellTypeInfo *shellType;	/* shell-type entry, or NULL */
diff --git a/src/include/access/tupmacs.h b/src/include/access/tupmacs.h
index 70157cf90a..c262265b95 100644
--- a/src/include/access/tupmacs.h
+++ b/src/include/access/tupmacs.h
@@ -139,8 +139,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/binary_upgrade.h b/src/include/catalog/binary_upgrade.h
index 12d94fe1b3..01d95117cd 100644
--- a/src/include/catalog/binary_upgrade.h
+++ b/src/include/catalog/binary_upgrade.h
@@ -16,6 +16,8 @@
 
 extern PGDLLIMPORT Oid binary_upgrade_next_pg_type_oid;
 extern PGDLLIMPORT Oid binary_upgrade_next_array_pg_type_oid;
+extern PGDLLIMPORT Oid binary_upgrade_next_mrng_pg_type_oid;
+extern PGDLLIMPORT Oid binary_upgrade_next_mrng_array_pg_type_oid;
 extern PGDLLIMPORT Oid binary_upgrade_next_toast_pg_type_oid;
 
 extern PGDLLIMPORT Oid binary_upgrade_next_heap_pg_class_oid;
diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h
index 8be303870f..566f9364c5 100644
--- a/src/include/catalog/indexing.h
+++ b/src/include/catalog/indexing.h
@@ -327,6 +327,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_rngmultitypid_index, 8001, on pg_range using btree(rngmultitypid 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_aggregate.dat b/src/include/catalog/pg_aggregate.dat
index ffabe275c0..03bab74253 100644
--- a/src/include/catalog/pg_aggregate.dat
+++ b/src/include/catalog/pg_aggregate.dat
@@ -538,6 +538,17 @@
   aggtransfn => 'bytea_string_agg_transfn',
   aggfinalfn => 'bytea_string_agg_finalfn', aggtranstype => 'internal' },
 
+# range
+{ aggfnoid => 'range_intersect_agg(anyrange)',
+  aggtransfn => 'range_intersect_agg_transfn',
+  aggcombinefn => 'range_intersect_agg_transfn', aggtranstype => 'anyrange' },
+{ aggfnoid => 'range_intersect_agg(anymultirange)',
+  aggtransfn => 'multirange_intersect_agg_transfn',
+  aggcombinefn => 'multirange_intersect_agg_transfn', aggtranstype => 'anymultirange' },
+{ aggfnoid => 'range_agg(anyrange)', aggtransfn => 'range_agg_transfn',
+  aggfinalfn => 'range_agg_finalfn', aggfinalextra => 't',
+  aggtranstype => 'internal' },
+
 # json
 { aggfnoid => 'json_agg', aggtransfn => 'json_agg_transfn',
   aggfinalfn => 'json_agg_finalfn', aggtranstype => 'internal' },
diff --git a/src/include/catalog/pg_amop.dat b/src/include/catalog/pg_amop.dat
index 11aaa519c8..827ac4f03c 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 75c0152b66..cf0cac5641 100644
--- a/src/include/catalog/pg_amproc.dat
+++ b/src/include/catalog/pg_amproc.dat
@@ -285,6 +285,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' },
 
@@ -445,6 +447,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',
@@ -1263,12 +1270,13 @@
   amprocrighttype => 'anyrange', amprocnum => '4',
   amproc => 'brin_inclusion_union' },
 { amprocfamily => 'brin/range_inclusion_ops', amproclefttype => 'anyrange',
-  amprocrighttype => 'anyrange', amprocnum => '11', amproc => 'range_merge' },
+  amprocrighttype => 'anyrange', amprocnum => '11',
+  amproc => 'range_merge(anyrange,anyrange)' },
 { amprocfamily => 'brin/range_inclusion_ops', amproclefttype => 'anyrange',
   amprocrighttype => 'anyrange', amprocnum => '13',
   amproc => 'range_contains' },
 { amprocfamily => 'brin/range_inclusion_ops', amproclefttype => 'anyrange',
-  amprocrighttype => 'anyrange', amprocnum => '14', amproc => 'isempty' },
+  amprocrighttype => 'anyrange', amprocnum => '14', amproc => 'isempty(anyrange)' },
 
 # minmax pg_lsn
 { amprocfamily => 'brin/pg_lsn_minmax_ops', amproclefttype => 'pg_lsn',
diff --git a/src/include/catalog/pg_cast.dat b/src/include/catalog/pg_cast.dat
index 6ef8b8a4e7..14277c8fdf 100644
--- a/src/include/catalog/pg_cast.dat
+++ b/src/include/catalog/pg_cast.dat
@@ -512,4 +512,17 @@
 { castsource => 'jsonb', casttarget => 'float8', castfunc => 'float8(jsonb)',
   castcontext => 'e', castmethod => 'f' },
 
+# range to multirange
+{ castsource => 'int4range', casttarget => 'int4multirange', castfunc => 'int4multirange(int4range)',
+  castcontext => 'e', castmethod => 'f' },
+{ castsource => 'int8range', casttarget => 'int8multirange', castfunc => 'int8multirange(int8range)',
+  castcontext => 'e', castmethod => 'f' },
+{ castsource => 'numrange', casttarget => 'nummultirange', castfunc => 'nummultirange(numrange)',
+  castcontext => 'e', castmethod => 'f' },
+{ castsource => 'daterange', casttarget => 'datemultirange', castfunc => 'datemultirange(daterange)',
+  castcontext => 'e', castmethod => 'f' },
+{ castsource => 'tsrange', casttarget => 'tsmultirange', castfunc => 'tsmultirange(tsrange)',
+  castcontext => 'e', castmethod => 'f' },
+{ castsource => 'tstzrange', casttarget => 'tstzmultirange', castfunc => 'tstzmultirange(tstzrange)',
+  castcontext => 'e', castmethod => 'f' },
 ]
diff --git a/src/include/catalog/pg_opclass.dat b/src/include/catalog/pg_opclass.dat
index ab2f50c9eb..eebebbcdb4 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 7c135da3b1..24f4a32203 100644
--- a/src/include/catalog/pg_operator.dat
+++ b/src/include/catalog/pg_operator.dat
@@ -3297,5 +3297,174 @@
   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' },
+{ oid => '8073', oid_symbol => 'OID_RANGE_OVERLAPS_MULTIRANGE_OP',
+  descr => 'overlaps',
+  oprname => '&&', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '&&(anymultirange,anyrange)',
+  oprcode => 'range_overlaps_multirange', oprrest => 'areasel',
+  oprjoin => 'areajoinsel' },
+{ oid => '8074', oid_symbol => 'OID_MULTIRANGE_OVERLAPS_RANGE_OP', descr => 'contains',
+  oprname => '&&', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcom => '&&(anyrange,anymultirange)',
+  oprcode => 'multirange_overlaps_range', oprrest => 'areasel',
+  oprjoin => 'areajoinsel' },
+{ oid => '8075', oid_symbol => 'OID_MULTIRANGE_OVERLAPS_MULTIRANGE_OP', descr => 'contains',
+  oprname => '&&', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '&&(anymultirange,anymultirange)',
+  oprcode => 'multirange_overlaps_multirange', oprrest => 'areasel',
+  oprjoin => 'areajoinsel' },
+{ oid => '8064', oid_symbol => 'OID_MULTIRANGE_CONTAINS_ELEM_OP',
+  descr => 'contains',
+  oprname => '@>', oprleft => 'anymultirange', oprright => 'anyelement',
+  oprresult => 'bool', oprcom => '<@(anyelement,anymultirange)',
+  oprcode => 'multirange_contains_elem', oprrest => 'contsel',
+  oprjoin => 'contjoinsel' },
+{ oid => '8065', oid_symbol => 'OID_MULTIRANGE_CONTAINS_RANGE_OP', descr => 'contains',
+  oprname => '@>', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcom => '<@(anyrange,anymultirange)',
+  oprcode => 'multirange_contains_range', oprrest => 'contsel',
+  oprjoin => 'contjoinsel' },
+{ oid => '8066', oid_symbol => 'OID_MULTIRANGE_CONTAINS_MULTIRANGE_OP', descr => 'contains',
+  oprname => '@>', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '<@(anymultirange,anymultirange)',
+  oprcode => 'multirange_contains_multirange', oprrest => 'contsel',
+  oprjoin => 'contjoinsel' },
+{ oid => '8067', oid_symbol => 'OID_MULTIRANGE_ELEM_CONTAINED_OP',
+  descr => 'is contained by',
+  oprname => '<@', oprleft => 'anyelement', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '@>(anymultirange,anyelement)',
+  oprcode => 'elem_contained_by_multirange', oprrest => 'contsel',
+  oprjoin => 'contjoinsel' },
+{ oid => '8068', oid_symbol => 'OID_MULTIRANGE_RANGE_CONTAINED_OP',
+  descr => 'is contained by',
+  oprname => '<@', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '@>(anymultirange,anyrange)',
+  oprcode => 'range_contained_by_multirange', oprrest => 'contsel',
+  oprjoin => 'contjoinsel' },
+{ oid => '8069', oid_symbol => 'OID_MULTIRANGE_MULTIRANGE_CONTAINED_OP',
+  descr => 'is contained by',
+  oprname => '<@', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '@>(anymultirange,anymultirange)',
+  oprcode => 'multirange_contained_by_multirange', oprrest => 'contsel',
+  oprjoin => 'contjoinsel' },
+{ oid => '8106', oid_symbol => 'OID_RANGE_OVERLAPS_LEFT_MULTIRANGE_OP',
+  descr => 'overlaps or is left of',
+  oprname => '&<', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcode => 'range_overleft_multirange', oprrest => 'scalarltsel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8107', oid_symbol => 'OID_MULTIRANGE_OVERLAPS_LEFT_RANGE_OP',
+  descr => 'overlaps or is left of',
+  oprname => '&<', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcode => 'multirange_overleft_range', oprrest => 'scalarltsel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8108', oid_symbol => 'OID_MULTIRANGE_OVERLAPS_LEFT_MULTIRANGE_OP',
+  descr => 'overlaps or is left of',
+  oprname => '&<', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcode => 'multirange_overleft_multirange', oprrest => 'scalarltsel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8109', oid_symbol => 'OID_RANGE_OVERLAPS_RIGHT_MULTIRANGE_OP',
+  descr => 'overlaps or is right of',
+  oprname => '&>', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcode => 'range_overright_multirange', oprrest => 'scalargtsel',
+  oprjoin => 'scalargtjoinsel' },
+{ oid => '8110', oid_symbol => 'OID_MULTIRANGE_OVERLAPS_RIGHT_RANGE_OP',
+  descr => 'overlaps or is right of',
+  oprname => '&>', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcode => 'multirange_overright_range', oprrest => 'scalargtsel',
+  oprjoin => 'scalargtjoinsel' },
+{ oid => '8111', oid_symbol => 'OID_MULTIRANGE_OVERLAPS_RIGHT_MULTIRANGE_OP',
+  descr => 'overlaps or is right of',
+  oprname => '&>', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcode => 'multirange_overright_multirange', oprrest => 'scalargtsel',
+  oprjoin => 'scalargtjoinsel' },
+{ oid => '8091', oid_symbol => 'OID_RANGE_ADJACENT_MULTIRANGE_OP', descr => 'is adjacent to',
+  oprname => '-|-', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '-|-(anymultirange,anyrange)',
+  oprcode => 'range_adjacent_multirange', oprrest => 'scalarltsel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8092', oid_symbol => 'OID_MULTIRANGE_ADJACENT_RANGE_OP', descr => 'is adjacent to',
+  oprname => '-|-', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcom => '-|-(anyrange,anymultirange)',
+  oprcode => 'multirange_adjacent_range', oprrest => 'scalarltsel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8093', oid_symbol => 'OID_MULTIRANGE_ADJACENT_MULTIRANGE_OP', descr => 'is adjacent to',
+  oprname => '-|-', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '-|-(anymultirange,anymultirange)',
+  oprcode => 'multirange_adjacent_multirange', oprrest => 'scalarltsel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8117', descr => 'multirange union',
+  oprname => '+', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'anymultirange', oprcom => '+(anymultirange,anymultirange)',
+  oprcode => 'multirange_union' },
+{ oid => '8123', descr => 'multirange minus',
+  oprname => '-', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'anymultirange', oprcode => 'multirange_minus' },
+{ oid => '8129', descr => 'multirange intersect',
+  oprname => '*', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'anymultirange', oprcom => '*(anymultirange,anymultirange)',
+  oprcode => 'multirange_intersect' },
+{ oid => '8082', oid_symbol => 'OID_RANGE_BEFORE_MULTIRANGE_OP', descr => 'is left of',
+  oprname => '<<', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '>>(anymultirange,anyrange)',
+  oprcode => 'range_before_multirange', oprrest => 'scalarltsel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8083', oid_symbol => 'OID_MULTIRANGE_BEFORE_RANGE_OP', descr => 'is left of',
+  oprname => '<<', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcom => '>>(anyrange,anymultirange)',
+  oprcode => 'multirange_before_range', oprrest => 'scalarltsel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8084', oid_symbol => 'OID_MULTIRANGE_BEFORE_MULTIRANGE_OP', descr => 'is left of',
+  oprname => '<<', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '>>(anymultirange,anymultirange)',
+  oprcode => 'multirange_before_multirange', oprrest => 'scalarltsel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8085', oid_symbol => 'OID_RANGE_AFTER_MULTIRANGE_OP', descr => 'is right of',
+  oprname => '>>', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '<<(anymultirange,anyrange)',
+  oprcode => 'range_after_multirange', oprrest => 'scalargtsel',
+  oprjoin => 'scalargtjoinsel' },
+{ oid => '8086', oid_symbol => 'OID_MULTIRANGE_AFTER_RANGE_OP', descr => 'is right of',
+  oprname => '>>', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcom => '<<(anyrange,anymultirange)',
+  oprcode => 'multirange_after_range', oprrest => 'scalargtsel',
+  oprjoin => 'scalargtjoinsel' },
+{ oid => '8087', oid_symbol => 'OID_MULTIRANGE_AFTER_MULTIRANGE_OP', descr => 'is right of',
+  oprname => '>>', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '<<(anymultirange,anymultirange)',
+  oprcode => 'multirange_after_multirange', oprrest => 'scalargtsel',
+  oprjoin => 'scalargtjoinsel' },
 
 ]
diff --git a/src/include/catalog/pg_opfamily.dat b/src/include/catalog/pg_opfamily.dat
index 26227df216..901fd7b303 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 7fb574f9dc..27f25d989b 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -9643,6 +9643,10 @@
   descr => 'the smallest range which includes both of the given ranges',
   proname => 'range_merge', prorettype => 'anyrange',
   proargtypes => 'anyrange anyrange', prosrc => 'range_merge' },
+{ oid => '8143',
+  descr => 'the smallest range which includes the whole multirange',
+  proname => 'range_merge', prorettype => 'anyrange',
+  proargtypes => 'anymultirange', prosrc => 'range_merge_from_multirange' },
 { oid => '3868',
   proname => 'range_intersect', prorettype => 'anyrange',
   proargtypes => 'anyrange anyrange', prosrc => 'range_intersect' },
@@ -9692,6 +9696,13 @@
 { oid => '3169', descr => 'restriction selectivity for range operators',
   proname => 'rangesel', provolatile => 's', prorettype => 'float8',
   proargtypes => 'internal oid internal int4', prosrc => 'rangesel' },
+{ oid => '8133', descr => 'range aggregate by intersecting',
+  proname => 'range_intersect_agg_transfn', prorettype => 'anyrange',
+  proargtypes => 'anyrange anyrange', prosrc => 'range_intersect_agg_transfn'},
+{ oid => '8134', descr => 'range aggregate by intersecting',
+  proname => 'range_intersect_agg', prokind => 'a', proisstrict => 'f',
+  prorettype => 'anyrange', proargtypes => 'anyrange',
+  prosrc => 'aggregate_dummy'},
 
 { oid => '3914', descr => 'convert an int4 range to canonical form',
   proname => 'int4range_canonical', prorettype => 'int4range',
@@ -9760,6 +9771,258 @@
   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 => '8094', descr => 'lower bound of multirange',
+  proname => 'lower', prorettype => 'anyelement', proargtypes => 'anymultirange',
+  prosrc => 'multirange_lower' },
+{ oid => '8095', descr => 'upper bound of multirange',
+  proname => 'upper', prorettype => 'anyelement', proargtypes => 'anymultirange',
+  prosrc => 'multirange_upper' },
+{ oid => '8057', descr => 'is the multirange empty?',
+  proname => 'isempty', prorettype => 'bool',
+  proargtypes => 'anymultirange', prosrc => 'multirange_empty' },
+{ oid => '8096', descr => 'is the multirange\'s lower bound inclusive?',
+  proname => 'lower_inc', prorettype => 'bool', proargtypes => 'anymultirange',
+  prosrc => 'multirange_lower_inc' },
+{ oid => '8097', descr => 'is the multirange\'s upper bound inclusive?',
+  proname => 'upper_inc', prorettype => 'bool', proargtypes => 'anymultirange',
+  prosrc => 'multirange_upper_inc' },
+{ oid => '8098', descr => 'is the multirange\'s lower bound infinite?',
+  proname => 'lower_inf', prorettype => 'bool', proargtypes => 'anymultirange',
+  prosrc => 'multirange_lower_inf' },
+{ oid => '8099', descr => 'is the multirange\'s upper bound infinite?',
+  proname => 'upper_inf', prorettype => 'bool', proargtypes => 'anymultirange',
+  prosrc => 'multirange_upper_inf' },
+{ oid => '8008', descr => 'multirange typanalyze',
+  proname => 'multirange_typanalyze', provolatile => 's', prorettype => 'bool',
+  proargtypes => 'internal', prosrc => 'multirange_typanalyze' },
+{ oid => '8033',
+  proname => 'multirange_eq', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_eq' },
+{ oid => '8034',
+  proname => 'multirange_ne', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_ne' },
+{ oid => '8070',
+  proname => 'range_overlaps_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_overlaps_multirange' },
+{ oid => '8071',
+  proname => 'multirange_overlaps_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_overlaps_range' },
+{ oid => '8072',
+  proname => 'multirange_overlaps_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_overlaps_multirange' },
+{ oid => '8058',
+  proname => 'multirange_contains_elem', prorettype => 'bool',
+  proargtypes => 'anymultirange anyelement', prosrc => 'multirange_contains_elem' },
+{ oid => '8059',
+  proname => 'multirange_contains_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_contains_range' },
+{ oid => '8060',
+  proname => 'multirange_contains_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_contains_multirange' },
+{ oid => '8061',
+  proname => 'elem_contained_by_multirange', prorettype => 'bool',
+  proargtypes => 'anyelement anymultirange', prosrc => 'elem_contained_by_multirange' },
+{ oid => '8062',
+  proname => 'range_contained_by_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_contained_by_multirange' },
+{ oid => '8063',
+  proname => 'multirange_contained_by_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_contained_by_multirange' },
+{ oid => '8088',
+  proname => 'range_adjacent_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_adjacent_multirange' },
+{ oid => '8089',
+  proname => 'multirange_adjacent_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_adjacent_multirange' },
+{ oid => '8090',
+  proname => 'multirange_adjacent_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_adjacent_range' },
+{ oid => '8076',
+  proname => 'range_before_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_before_multirange' },
+{ oid => '8077',
+  proname => 'multirange_before_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_before_range' },
+{ oid => '8078',
+  proname => 'multirange_before_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_before_multirange' },
+{ oid => '8079',
+  proname => 'range_after_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_after_multirange' },
+{ oid => '8080',
+  proname => 'multirange_after_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_after_range' },
+{ oid => '8081',
+  proname => 'multirange_after_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_after_multirange' },
+{ oid => '8100',
+  proname => 'range_overleft_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_overleft_multirange' },
+{ oid => '8101',
+  proname => 'multirange_overleft_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_overleft_range' },
+{ oid => '8102',
+  proname => 'multirange_overleft_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_overleft_multirange' },
+{ oid => '8103',
+  proname => 'range_overright_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_overright_multirange' },
+{ oid => '8104',
+  proname => 'multirange_overright_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_overright_range' },
+{ oid => '8105',
+  proname => 'multirange_overright_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_overright_multirange' },
+{ oid => '8114',
+  proname => 'multirange_union', prorettype => 'anymultirange',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_union' },
+{ oid => '8120',
+  proname => 'multirange_minus', prorettype => 'anymultirange',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_minus' },
+{ oid => '8126',
+  proname => 'multirange_intersect', prorettype => 'anymultirange',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_intersect' },
+{ oid => '8022', descr => 'less-equal-greater',
+  proname => 'multirange_cmp', prorettype => 'int4',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_cmp' },
+{ oid => '8023',
+  proname => 'multirange_lt', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_lt' },
+{ oid => '8024',
+  proname => 'multirange_le', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_le' },
+{ oid => '8025',
+  proname => 'multirange_ge', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_ge' },
+{ oid => '8026',
+  proname => 'multirange_gt', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_gt' },
+{ oid => '8038', descr => 'hash a multirange',
+  proname => 'hash_multirange', prorettype => 'int4', proargtypes => 'anymultirange',
+  prosrc => 'hash_multirange' },
+{ oid => '8039', descr => 'hash a multirange',
+  proname => 'hash_multirange_extended', prorettype => 'int8',
+  proargtypes => 'anymultirange int8', prosrc => 'hash_multirange_extended' },
+
+{ oid => '8045', descr => 'int4multirange constructor',
+  proname => 'int4multirange', proisstrict => 'f',
+  prorettype => 'int4multirange', proargtypes => '',
+  prosrc => 'multirange_constructor0' },
+{ oid => '8146', descr => 'int4multirange constructor',
+  proname => 'int4multirange', proisstrict => 't',
+  prorettype => 'int4multirange', proargtypes => 'int4range',
+  prosrc => 'multirange_constructor1' },
+{ oid => '8046', descr => 'int4multirange constructor',
+  proname => 'int4multirange', provariadic => 'int4range', proisstrict => 'f',
+  prorettype => 'int4multirange', proargtypes => '_int4range',
+  proallargtypes => '{_int4range}', proargmodes => '{v}',
+  prosrc => 'multirange_constructor2' },
+{ oid => '8047', descr => 'nummultirange constructor',
+  proname => 'nummultirange', proisstrict => 'f',
+  prorettype => 'nummultirange', proargtypes => '',
+  prosrc => 'multirange_constructor0' },
+{ oid => '8147', descr => 'nummultirange constructor',
+  proname => 'nummultirange', proisstrict => 't',
+  prorettype => 'nummultirange', proargtypes => 'numrange',
+  prosrc => 'multirange_constructor1' },
+{ oid => '8048', descr => 'nummultirange constructor',
+  proname => 'nummultirange', provariadic => 'numrange', proisstrict => 'f',
+  prorettype => 'nummultirange', proargtypes => '_numrange',
+  proallargtypes => '{_numrange}', proargmodes => '{v}',
+  prosrc => 'multirange_constructor2' },
+{ oid => '8049', descr => 'tsmultirange constructor',
+  proname => 'tsmultirange', proisstrict => 'f',
+  prorettype => 'tsmultirange', proargtypes => '',
+  prosrc => 'multirange_constructor0' },
+{ oid => '8148', descr => 'tsmultirange constructor',
+  proname => 'tsmultirange', proisstrict => 't',
+  prorettype => 'tsmultirange', proargtypes => 'tsrange',
+  prosrc => 'multirange_constructor1' },
+{ oid => '8050', descr => 'tsmultirange constructor',
+  proname => 'tsmultirange', provariadic => 'tsrange', proisstrict => 'f',
+  prorettype => 'tsmultirange', proargtypes => '_tsrange',
+  proallargtypes => '{_tsrange}', proargmodes => '{v}',
+  prosrc => 'multirange_constructor2' },
+{ oid => '8051', descr => 'tstzmultirange constructor',
+  proname => 'tstzmultirange', proisstrict => 'f',
+  prorettype => 'tstzmultirange', proargtypes => '',
+  prosrc => 'multirange_constructor0' },
+{ oid => '8149', descr => 'tstzmultirange constructor',
+  proname => 'tstzmultirange', proisstrict => 't',
+  prorettype => 'tstzmultirange', proargtypes => 'tstzrange',
+  prosrc => 'multirange_constructor1' },
+{ oid => '8052', descr => 'tstzmultirange constructor',
+  proname => 'tstzmultirange', provariadic => 'tstzrange', proisstrict => 'f',
+  prorettype => 'tstzmultirange', proargtypes => '_tstzrange',
+  proallargtypes => '{_tstzrange}', proargmodes => '{v}',
+  prosrc => 'multirange_constructor2' },
+{ oid => '8053', descr => 'datemultirange constructor',
+  proname => 'datemultirange', proisstrict => 'f',
+  prorettype => 'datemultirange', proargtypes => '',
+  prosrc => 'multirange_constructor0' },
+{ oid => '8150', descr => 'datemultirange constructor',
+  proname => 'datemultirange', proisstrict => 't',
+  prorettype => 'datemultirange', proargtypes => 'daterange',
+  prosrc => 'multirange_constructor1' },
+{ oid => '8054', descr => 'datemultirange constructor',
+  proname => 'datemultirange', provariadic => 'daterange', proisstrict => 'f',
+  prorettype => 'datemultirange', proargtypes => '_daterange',
+  proallargtypes => '{_daterange}', proargmodes => '{v}',
+  prosrc => 'multirange_constructor2' },
+{ oid => '8055', descr => 'int8multirange constructor',
+  proname => 'int8multirange', proisstrict => 'f',
+  prorettype => 'int8multirange', proargtypes => '',
+  prosrc => 'multirange_constructor0' },
+{ oid => '8151', descr => 'int8multirange constructor',
+  proname => 'int8multirange', proisstrict => 't',
+  prorettype => 'int8multirange', proargtypes => 'int8range',
+  prosrc => 'multirange_constructor1' },
+{ oid => '8056', descr => 'int8multirange constructor',
+  proname => 'int8multirange', provariadic => 'int8range', proisstrict => 'f',
+  prorettype => 'int8multirange', proargtypes => '_int8range',
+  proallargtypes => '{_int8range}', proargmodes => '{v}',
+  prosrc => 'multirange_constructor2' },
+{ oid => '8152', descr => 'anymultirange cast',
+  proname => 'multirange', proisstrict => 't',
+  prorettype => 'anymultirange', proargtypes => 'anyrange',
+  prosrc => 'multirange_constructor1' },
+{ oid => '8130', descr => 'aggregate transition function',
+  proname => 'range_agg_transfn', proisstrict => 'f', prorettype => 'internal',
+  proargtypes => 'internal anyrange', prosrc => 'range_agg_transfn' },
+{ oid => '8131', descr => 'aggregate final function',
+  proname => 'range_agg_finalfn', proisstrict => 'f', prorettype => 'anymultirange',
+  proargtypes => 'internal anyrange', prosrc => 'range_agg_finalfn' },
+{ oid => '8132', descr => 'combine aggregate input into a multirange',
+  proname => 'range_agg', prokind => 'a', proisstrict => 'f',
+  prorettype => 'anymultirange', proargtypes => 'anyrange',
+  prosrc => 'aggregate_dummy' },
+{ oid => '8135', descr => 'range aggregate by intersecting',
+  proname => 'multirange_intersect_agg_transfn', prorettype => 'anymultirange',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_intersect_agg_transfn'},
+{ oid => '8136', descr => 'range aggregate by intersecting',
+  proname => 'range_intersect_agg', prokind => 'a', proisstrict => 'f',
+  prorettype => 'anymultirange', proargtypes => 'anymultirange',
+  prosrc => 'aggregate_dummy'},
+
 # date, time, timestamp constructors
 { oid => '3846', descr => 'construct date',
   proname => 'make_date', prorettype => 'date', proargtypes => 'int4 int4 int4',
@@ -10142,6 +10405,14 @@
   proname => 'binary_upgrade_set_next_array_pg_type_oid', provolatile => 'v',
   proparallel => 'r', prorettype => 'void', proargtypes => 'oid',
   prosrc => 'binary_upgrade_set_next_array_pg_type_oid' },
+{ oid => '8144', descr => 'for use by pg_upgrade',
+  proname => 'binary_upgrade_set_next_multirange_pg_type_oid', provolatile => 'v',
+  proparallel => 'r', prorettype => 'void', proargtypes => 'oid',
+  prosrc => 'binary_upgrade_set_next_multirange_pg_type_oid' },
+{ oid => '8145', descr => 'for use by pg_upgrade',
+  proname => 'binary_upgrade_set_next_multirange_array_pg_type_oid', provolatile => 'v',
+  proparallel => 'r', prorettype => 'void', proargtypes => 'oid',
+  prosrc => 'binary_upgrade_set_next_multirange_array_pg_type_oid' },
 { oid => '3585', descr => 'for use by pg_upgrade',
   proname => 'binary_upgrade_set_next_toast_pg_type_oid', provolatile => 'v',
   proparallel => 'r', prorettype => 'void', proargtypes => 'oid',
diff --git a/src/include/catalog/pg_range.dat b/src/include/catalog/pg_range.dat
index 479754c245..10060255c9 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',
+  rngmultitypid => 'int4multirange' },
 { rngtypid => 'numrange', rngsubtype => 'numeric',
   rngsubopc => 'btree/numeric_ops', rngcanonical => '-',
-  rngsubdiff => 'numrange_subdiff' },
+  rngsubdiff => 'numrange_subdiff', rngmultitypid => 'nummultirange' },
 { rngtypid => 'tsrange', rngsubtype => 'timestamp',
   rngsubopc => 'btree/timestamp_ops', rngcanonical => '-',
-  rngsubdiff => 'tsrange_subdiff' },
+  rngsubdiff => 'tsrange_subdiff', rngmultitypid => 'tsmultirange' },
 { rngtypid => 'tstzrange', rngsubtype => 'timestamptz',
   rngsubopc => 'btree/timestamptz_ops', rngcanonical => '-',
-  rngsubdiff => 'tstzrange_subdiff' },
+  rngsubdiff => 'tstzrange_subdiff', rngmultitypid => 'tstzmultirange' },
 { rngtypid => 'daterange', rngsubtype => 'date', rngsubopc => 'btree/date_ops',
-  rngcanonical => 'daterange_canonical', rngsubdiff => 'daterange_subdiff' },
+  rngcanonical => 'daterange_canonical', rngsubdiff => 'daterange_subdiff',
+  rngmultitypid => 'datemultirange' },
 { rngtypid => 'int8range', rngsubtype => 'int8', rngsubopc => 'btree/int8_ops',
-  rngcanonical => 'int8range_canonical', rngsubdiff => 'int8range_subdiff' },
+  rngcanonical => 'int8range_canonical', rngsubdiff => 'int8range_subdiff',
+  rngmultitypid => 'int8multirange' },
 
 ]
diff --git a/src/include/catalog/pg_range.h b/src/include/catalog/pg_range.h
index 98172bb1b6..60a8fde518 100644
--- a/src/include/catalog/pg_range.h
+++ b/src/include/catalog/pg_range.h
@@ -34,6 +34,9 @@ CATALOG(pg_range,3541,RangeRelationId)
 	/* OID of range's element type (subtype) */
 	Oid			rngsubtype BKI_LOOKUP(pg_type);
 
+	/* OID of the range's multirange type */
+	Oid			rngmultitypid BKI_LOOKUP(pg_type);
+
 	/* collation for this range type, or 0 */
 	Oid			rngcollation BKI_DEFAULT(0);
 
@@ -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 b00597d6ff..6e1cb796f1 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 => 'R', 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 => 'R', 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 => 'R', 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 => 'R', 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 => 'R', 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 => 'R', 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
@@ -590,5 +624,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 97890946c5..d18f19f55a 100644
--- a/src/include/catalog/pg_type.h
+++ b/src/include/catalog/pg_type.h
@@ -263,6 +263,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 */
 
@@ -299,7 +300,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 */
 
@@ -358,4 +360,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/commands/typecmds.h b/src/include/commands/typecmds.h
index 0162bc2ffe..80e3a5e399 100644
--- a/src/include/commands/typecmds.h
+++ b/src/include/commands/typecmds.h
@@ -29,6 +29,8 @@ extern ObjectAddress DefineRange(CreateRangeStmt *stmt);
 extern ObjectAddress AlterEnum(AlterEnumStmt *stmt);
 extern ObjectAddress DefineCompositeType(RangeVar *typevar, List *coldeflist);
 extern Oid	AssignTypeArrayOid(void);
+extern Oid	AssignTypeMultirangeOid(void);
+extern Oid	AssignTypeMultirangeArrayOid(void);
 
 extern ObjectAddress AlterDomainDefault(List *names, Node *defaultRaw);
 extern ObjectAddress AlterDomainNotNull(List *names, bool notNull);
diff --git a/src/include/utils/lsyscache.h b/src/include/utils/lsyscache.h
index 4e646c55e9..80410dcd04 100644
--- a/src/include/utils/lsyscache.h
+++ b/src/include/utils/lsyscache.h
@@ -155,6 +155,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);
@@ -181,6 +182,8 @@ 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_collation(Oid rangeOid);
+extern Oid	get_range_multirange(Oid rangeOid);
+extern Oid	get_range_multirange_subtype(Oid multirangeOid);
 extern Oid	get_index_column_opclass(Oid index_oid, int attno);
 extern bool	get_index_isreplident(Oid index_oid);
 extern bool get_index_isvalid(Oid index_oid);
diff --git a/src/include/utils/multirangetypes.h b/src/include/utils/multirangetypes.h
new file mode 100644
index 0000000000..8779bd1f6e
--- /dev/null
+++ b/src/include/utils/multirangetypes.h
@@ -0,0 +1,103 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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);
+extern bool multirange_contains_elem_internal(TypeCacheEntry *typcache, MultirangeType * mr,
+											  Datum elem);
+extern bool multirange_contains_range_internal(TypeCacheEntry *typcache, MultirangeType * mr,
+											   RangeType *r);
+extern bool multirange_contains_multirange_internal(TypeCacheEntry *typcache,
+													MultirangeType * mr1,
+													MultirangeType * mr2);
+extern bool range_overlaps_multirange_internal(TypeCacheEntry *typcache, RangeType *r,
+											   MultirangeType * mr);
+extern bool multirange_overlaps_multirange_internal(TypeCacheEntry *typcache,
+													MultirangeType * mr1,
+													MultirangeType * mr2);
+extern bool range_before_multirange_internal(TypeCacheEntry *typcache, RangeType *r,
+											 MultirangeType * mr);
+extern bool range_after_multirange_internal(TypeCacheEntry *typcache, RangeType *r,
+											MultirangeType * mr);
+extern bool multirange_before_multirange_internal(TypeCacheEntry *typcache,
+												  MultirangeType * mr1,
+												  MultirangeType * mr2);
+extern MultirangeType * multirange_minus_internal(Oid mltrngtypoid,
+												  TypeCacheEntry *rangetyp,
+												  int32 range_count1,
+												  RangeType **ranges1,
+												  int32 range_count2,
+												  RangeType **ranges2);
+extern MultirangeType * multirange_intersect_internal(Oid mltrngtypoid,
+													  TypeCacheEntry *rangetyp,
+													  int32 range_count1,
+													  RangeType **ranges1,
+													  int32 range_count2,
+													  RangeType **ranges2);
+
+/* 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 0cbbf09033..204aee4054 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)
 
@@ -92,13 +94,21 @@ typedef struct
  * prototypes for functions defined in rangetypes.c
  */
 
-extern bool range_contains_elem_internal(TypeCacheEntry *typcache, const RangeType *r, Datum val);
-
 /* internal versions of the above */
+extern int	range_cmp_internal(TypeCacheEntry *typcache, const RangeType *r1,
+							   const RangeType *r2);
+extern uint32 hash_range_internal(TypeCacheEntry *typcache, const RangeType *r);
+extern uint64 hash_range_extended_internal(TypeCacheEntry *typcache, const RangeType *r,
+										   Datum seed);
+extern Datum range_lower_internal(TypeCacheEntry *typcache, const RangeType *r1,
+								  bool *isnull);
+extern Datum range_upper_internal(TypeCacheEntry *typcache, const RangeType *r1,
+								  bool *isnull);
 extern bool range_eq_internal(TypeCacheEntry *typcache, const RangeType *r1,
 							  const RangeType *r2);
 extern bool range_ne_internal(TypeCacheEntry *typcache, const RangeType *r1,
 							  const RangeType *r2);
+extern bool range_contains_elem_internal(TypeCacheEntry *typcache, const RangeType *r, Datum val);
 extern bool range_contains_internal(TypeCacheEntry *typcache, const RangeType *r1,
 									const RangeType *r2);
 extern bool range_contained_by_internal(TypeCacheEntry *typcache, const RangeType *r1,
@@ -115,6 +125,12 @@ extern bool range_overleft_internal(TypeCacheEntry *typcache, const RangeType *r
 									const RangeType *r2);
 extern bool range_overright_internal(TypeCacheEntry *typcache, const RangeType *r1,
 									 const RangeType *r2);
+extern RangeType *range_union_internal(TypeCacheEntry *typcache, RangeType *r1,
+									   RangeType *r2, bool strict);
+extern RangeType *range_minus_internal(TypeCacheEntry *typcache, RangeType *r1,
+									   RangeType *r2);
+extern RangeType *range_intersect_internal(TypeCacheEntry *typcache, const RangeType *r1,
+										   const RangeType *r2);
 
 /* assorted support functions */
 extern TypeCacheEntry *range_get_typcache(FunctionCallInfo fcinfo,
@@ -125,6 +141,7 @@ extern void range_deserialize(TypeCacheEntry *typcache, const RangeType *range,
 							  RangeBound *lower, RangeBound *upper,
 							  bool *empty);
 extern char range_get_flags(const RangeType *range);
+extern bool range_has_flag(const RangeType *range, char flag);
 extern void range_set_contain_empty(RangeType *range);
 extern RangeType *make_range(TypeCacheEntry *typcache, RangeBound *lower,
 							 RangeBound *upper, bool empty);
@@ -132,8 +149,12 @@ extern int range_cmp_bounds(TypeCacheEntry *typcache, const RangeBound *b1,
 							const RangeBound *b2);
 extern int range_cmp_bound_values(TypeCacheEntry *typcache, const RangeBound *b1,
 								  const RangeBound *b2);
-extern bool bounds_adjacent(TypeCacheEntry *typcache, RangeBound bound1,
-							RangeBound bound2);
+extern int range_compare(const void *key1, const void *key2, void *arg);
+extern bool bounds_adjacent(TypeCacheEntry *typcache, const RangeBound bound1,
+							const RangeBound bound2);
 extern RangeType *make_empty_range(TypeCacheEntry *typcache);
+extern bool range_split_internal(TypeCacheEntry *typcache, const RangeType *r1,
+								 const RangeType *r2, RangeType **output1,
+								 RangeType **output2);
 
 #endif							/* RANGETYPES_H */
diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h
index f27b73d76d..e8f393520a 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -79,6 +79,7 @@ enum SysCacheIdentifier
 	PUBLICATIONOID,
 	PUBLICATIONREL,
 	PUBLICATIONRELMAP,
+	RANGEMULTIRANGE,
 	RANGETYPE,
 	RELNAMENSP,
 	RELOID,
diff --git a/src/include/utils/typcache.h b/src/include/utils/typcache.h
index cdd20e56d7..f40a462b22 100644
--- a/src/include/utils/typcache.h
+++ b/src/include/utils/typcache.h
@@ -100,6 +100,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;		/* underlying range type */
+
 	/*
 	 * Domain's base type and typmod if it's a domain type.  Zeroes if not
 	 * domain, or if information hasn't been requested.
@@ -143,6 +148,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 c8e43e684f..2d59021d68 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? */
@@ -2087,6 +2089,7 @@ build_datatype(HeapTuple typeTup, int32 typmod,
 		case TYPTYPE_BASE:
 		case TYPTYPE_ENUM:
 		case TYPTYPE_RANGE:
+		case TYPTYPE_MULTIRANGE:
 			typ->ttype = PLPGSQL_TTYPE_SCALAR;
 			break;
 		case TYPTYPE_COMPOSITE:
@@ -2501,6 +2504,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/dependency.out b/src/test/regress/expected/dependency.out
index b4a11b8aa9..778699a961 100644
--- a/src/test/regress/expected/dependency.out
+++ b/src/test/regress/expected/dependency.out
@@ -141,6 +141,7 @@ owner of table deptest
 owner of function deptest_func()
 owner of type deptest_enum
 owner of type deptest_range
+owner of type deptest_multirange
 owner of table deptest2
 owner of sequence ss1
 owner of type deptest_t
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..723f2408b0
--- /dev/null
+++ b/src/test/regress/expected/multirangetypes.out
@@ -0,0 +1,2395 @@
+-- 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)
+
+--
+-- test casts, both a built-in range type and a user-defined one:
+--
+select 'empty'::int4range::int4multirange;
+ int4multirange 
+----------------
+ {}
+(1 row)
+
+select int4range(1, 3)::int4multirange;
+ int4range 
+-----------
+ {[1,3)}
+(1 row)
+
+select int4range(1, null)::int4multirange;
+ int4range 
+-----------
+ {[1,)}
+(1 row)
+
+select int4range(null, null)::int4multirange;
+ int4range 
+-----------
+ {(,)}
+(1 row)
+
+select 'empty'::textrange::textmultirange;
+ textmultirange 
+----------------
+ {}
+(1 row)
+
+select textrange('a', 'c')::textmultirange;
+ textrange 
+-----------
+ {[a,c)}
+(1 row)
+
+select textrange('a', null)::textmultirange;
+ textrange 
+-----------
+ {[a,)}
+(1 row)
+
+select textrange(null, null)::textmultirange;
+ textrange 
+-----------
+ {(,)}
+(1 row)
+
+--
+-- create some test data and test the operators
+--
+CREATE TABLE nummultirange_test (nmr NUMMULTIRANGE);
+CREATE INDEX nummultirange_test_btree ON nummultirange_test(nmr);
+INSERT INTO nummultirange_test VALUES('{}');
+INSERT INTO nummultirange_test VALUES('{[,)}');
+INSERT INTO nummultirange_test VALUES('{[3,]}');
+INSERT INTO nummultirange_test VALUES('{[,), [3,]}');
+INSERT INTO nummultirange_test VALUES('{[, 5)}');
+INSERT INTO nummultirange_test VALUES(nummultirange());
+INSERT INTO nummultirange_test VALUES(nummultirange(variadic '{}'::numrange[]));
+INSERT INTO nummultirange_test VALUES(nummultirange(numrange(1.1, 2.2)));
+INSERT INTO nummultirange_test VALUES('{empty}');
+INSERT INTO nummultirange_test VALUES(nummultirange(numrange(1.7, 1.7, '[]'), numrange(1.7, 1.9)));
+INSERT INTO nummultirange_test VALUES(nummultirange(numrange(1.7, 1.7, '[]'), numrange(1.9, 2.1)));
+SELECT nmr, isempty(nmr), lower(nmr), upper(nmr) FROM nummultirange_test ORDER BY nmr;
+          nmr          | isempty | lower | upper 
+-----------------------+---------+-------+-------
+ {}                    | t       |       |      
+ {}                    | t       |       |      
+ {}                    | t       |       |      
+ {}                    | t       |       |      
+ {(,5)}                | f       |       |     5
+ {(,)}                 | f       |       |      
+ {(,)}                 | f       |       |      
+ {[1.1,2.2)}           | f       |   1.1 |   2.2
+ {[1.7,1.7],[1.9,2.1)} | f       |   1.7 |   2.1
+ {[1.7,1.9)}           | f       |   1.7 |   1.9
+ {[3,)}                | f       |     3 |      
+(11 rows)
+
+SELECT nmr, lower_inc(nmr), lower_inf(nmr), upper_inc(nmr), upper_inf(nmr) FROM nummultirange_test ORDER BY nmr;
+          nmr          | lower_inc | lower_inf | upper_inc | upper_inf 
+-----------------------+-----------+-----------+-----------+-----------
+ {}                    | f         | f         | f         | f
+ {}                    | f         | f         | f         | f
+ {}                    | f         | f         | f         | f
+ {}                    | f         | f         | f         | f
+ {(,5)}                | f         | t         | f         | f
+ {(,)}                 | f         | t         | f         | t
+ {(,)}                 | f         | t         | f         | t
+ {[1.1,2.2)}           | t         | f         | f         | f
+ {[1.7,1.7],[1.9,2.1)} | t         | f         | f         | f
+ {[1.7,1.9)}           | t         | f         | f         | f
+ {[3,)}                | t         | f         | f         | t
+(11 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr = '{}';
+ nmr 
+-----
+ {}
+ {}
+ {}
+ {}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr = '{(,5)}';
+  nmr   
+--------
+ {(,5)}
+(1 row)
+
+SELECT * FROM nummultirange_test WHERE nmr = '{[3,)}';
+  nmr   
+--------
+ {[3,)}
+(1 row)
+
+SELECT * FROM nummultirange_test WHERE nmr = '{[1.7,1.7]}';
+ nmr 
+-----
+(0 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr = '{[1.7,1.7],[1.9,2.1)}';
+          nmr          
+-----------------------
+ {[1.7,1.7],[1.9,2.1)}
+(1 row)
+
+SELECT * FROM nummultirange_test WHERE nmr < '{}';
+ nmr 
+-----
+(0 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr < '{[-1000.0, -1000.0]}';
+  nmr   
+--------
+ {}
+ {(,)}
+ {(,)}
+ {(,5)}
+ {}
+ {}
+ {}
+(7 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr < '{[0.0, 1.0]}';
+  nmr   
+--------
+ {}
+ {(,)}
+ {(,)}
+ {(,5)}
+ {}
+ {}
+ {}
+(7 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr < '{[1000.0, 1001.0]}';
+          nmr          
+-----------------------
+ {}
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+ {}
+ {}
+ {[1.1,2.2)}
+ {}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(11 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr <= '{}';
+ nmr 
+-----
+ {}
+ {}
+ {}
+ {}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr <= '{[3,)}';
+          nmr          
+-----------------------
+ {}
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+ {}
+ {}
+ {[1.1,2.2)}
+ {}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(11 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr >= '{}';
+          nmr          
+-----------------------
+ {}
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+ {}
+ {}
+ {[1.1,2.2)}
+ {}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(11 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr >= '{[3,)}';
+  nmr   
+--------
+ {[3,)}
+(1 row)
+
+SELECT * FROM nummultirange_test WHERE nmr > '{}';
+          nmr          
+-----------------------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+ {[1.1,2.2)}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(7 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr > '{[-1000.0, -1000.0]}';
+          nmr          
+-----------------------
+ {[3,)}
+ {[1.1,2.2)}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr > '{[0.0, 1.0]}';
+          nmr          
+-----------------------
+ {[3,)}
+ {[1.1,2.2)}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr > '{[1000.0, 1001.0]}';
+ nmr 
+-----
+(0 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr <> '{}';
+          nmr          
+-----------------------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+ {[1.1,2.2)}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(7 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr <> '{(,5)}';
+          nmr          
+-----------------------
+ {}
+ {(,)}
+ {[3,)}
+ {(,)}
+ {}
+ {}
+ {[1.1,2.2)}
+ {}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(10 rows)
+
+select nummultirange(numrange(2.0, 1.0));
+ERROR:  range lower bound must be less than or equal to range upper bound
+select nummultirange(numrange(5.0, 6.0), numrange(1.0, 2.0));
+     nummultirange     
+-----------------------
+ {[1.0,2.0),[5.0,6.0)}
+(1 row)
+
+-- overlaps
+SELECT * FROM nummultirange_test WHERE range_overlaps_multirange(numrange(4.0, 4.2), nmr);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE numrange(4.0, 4.2) && nmr;
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE multirange_overlaps_range(nmr, numrange(4.0, 4.2));
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr && numrange(4.0, 4.2);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE multirange_overlaps_multirange(nmr, nummultirange(numrange(4.0, 4.2), numrange(6.0, 7.0)));
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr && nummultirange(numrange(4.0, 4.2), numrange(6.0, 7.0));
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr && nummultirange(numrange(6.0, 7.0));
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+(3 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr && nummultirange(numrange(6.0, 7.0), numrange(8.0, 9.0));
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+(3 rows)
+
+-- mr contains x
+SELECT * FROM nummultirange_test WHERE multirange_contains_elem(nmr, 4.0);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr @> 4.0;
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE multirange_contains_range(nmr, numrange(4.0, 4.2));
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr @> numrange(4.0, 4.2);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE multirange_contains_multirange(nmr, '{[4.0,4.2), [6.0, 8.0)}');
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+(3 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr @> '{[4.0,4.2), [6.0, 8.0)}'::nummultirange;
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+(3 rows)
+
+-- x is contained by mr
+SELECT * FROM nummultirange_test WHERE elem_contained_by_multirange(4.0, nmr);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE 4.0 <@ nmr;
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE range_contained_by_multirange(numrange(4.0, 4.2), nmr);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE numrange(4.0, 4.2) <@ nmr;
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE multirange_contained_by_multirange('{[4.0,4.2), [6.0, 8.0)}', nmr);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+(3 rows)
+
+SELECT * FROM nummultirange_test WHERE '{[4.0,4.2), [6.0, 8.0)}'::nummultirange <@ nmr;
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+(3 rows)
+
+-- overlaps
+SELECT 'empty'::numrange && nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT 'empty'::numrange && nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() && 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) && 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() && nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() && nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) && nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) && nummultirange(numrange(1,2), numrange(7,8));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(7,8)) && nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) && nummultirange(numrange(1,2), numrange(3.5,8));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(3.5,8)) && numrange(3,4);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(3.5,8)) && nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+-- contains
+SELECT nummultirange() @> nummultirange();
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange() @> 'empty'::numrange;
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(null,null)) @> numrange(1,2);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(null,null)) @> numrange(null,2);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(null,null)) @> numrange(2,null);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(null,5)) @> numrange(null,3);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(null,5)) @> numrange(null,8);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(5,null)) @> numrange(8,null);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(5,null)) @> numrange(3,null);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,5)) @> numrange(8,9);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,5)) @> numrange(3,9);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,5)) @> numrange(1,4);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,5)) @> numrange(1,5);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(-4,-2), numrange(1,5)) @> numrange(1,5);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,5), numrange(8,9)) @> numrange(1,5);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,5), numrange(8,9)) @> numrange(6,7);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,5), numrange(6,9)) @> numrange(6,7);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[1,5)}'::nummultirange @> '{[1,5)}';
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[-4,-2), [1,5)}'::nummultirange @> '{[1,5)}';
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[1,5), [8,9)}'::nummultirange @> '{[1,5)}';
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[1,5), [8,9)}'::nummultirange @> '{[6,7)}';
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT '{[1,5), [6,9)}'::nummultirange @> '{[6,7)}';
+ ?column? 
+----------
+ t
+(1 row)
+
+-- is contained by
+SELECT nummultirange() <@ nummultirange();
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT 'empty'::numrange <@ nummultirange();
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,2) <@ nummultirange(numrange(null,null));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(null,2) <@ nummultirange(numrange(null,null));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(2,null) <@ nummultirange(numrange(null,null));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(null,3) <@ nummultirange(numrange(null,5));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(null,8) <@ nummultirange(numrange(null,5));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(8,null) <@ nummultirange(numrange(5,null));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(3,null) <@ nummultirange(numrange(5,null));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(8,9) <@ nummultirange(numrange(1,5));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(3,9) <@ nummultirange(numrange(1,5));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(1,4) <@ nummultirange(numrange(1,5));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,5) <@ nummultirange(numrange(1,5));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,5) <@ nummultirange(numrange(-4,-2), numrange(1,5));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,5) <@ nummultirange(numrange(1,5), numrange(8,9));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(6,7) <@ nummultirange(numrange(1,5), numrange(8,9));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(6,7) <@ nummultirange(numrange(1,5), numrange(6,9));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[1,5)}' <@ '{[1,5)}'::nummultirange;
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[1,5)}' <@ '{[-4,-2), [1,5)}'::nummultirange;
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[1,5)}' <@ '{[1,5), [8,9)}'::nummultirange;
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[6,7)}' <@ '{[1,5), [8,9)}'::nummultirange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT '{[6,7)}' <@ '{[1,5), [6,9)}'::nummultirange;
+ ?column? 
+----------
+ t
+(1 row)
+
+-- overleft
+SELECT 'empty'::numrange &< nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT 'empty'::numrange &< nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() &< 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) &< 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() &< nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) &< nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() &< nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(6,7) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(1,2) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,4) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,6) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(3.5,6) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(6,7)) &< numrange(3,4);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) &< numrange(3,4);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,4)) &< numrange(3,4);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,6)) &< numrange(3,4);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3.5,6)) &< numrange(3,4);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(6,7)) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,4)) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,6)) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3.5,6)) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+-- overright
+SELECT nummultirange() &> 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) &> 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT 'empty'::numrange &> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT 'empty'::numrange &> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() &> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() &> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) &> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> numrange(6,7);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> numrange(1,2);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> numrange(1,4);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> numrange(1,6);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> numrange(3.5,6);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(3,4) &> nummultirange(numrange(6,7));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(3,4) &> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(3,4) &> nummultirange(numrange(1,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(3,4) &> nummultirange(numrange(1,6));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(3,4) &> nummultirange(numrange(3.5,6));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(6,7));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(1,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(1,6));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(3.5,6));
+ ?column? 
+----------
+ f
+(1 row)
+
+-- meets
+SELECT 'empty'::numrange -|- nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT 'empty'::numrange -|- nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() -|- 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() -|- nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() -|- nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(1,2) -|- nummultirange(numrange(2,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,2) -|- nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- numrange(2,4);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- numrange(3,4);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- nummultirange(numrange(2,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(5,6)) -|- nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(5,6)) -|- nummultirange(numrange(6,7));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(5,6)) -|- nummultirange(numrange(8,9));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- nummultirange(numrange(2,4), numrange(6,7));
+ ?column? 
+----------
+ t
+(1 row)
+
+-- strictly left
+select 'empty'::numrange << nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(1,2) << nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(1,2) << nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+select numrange(1,2) << nummultirange(numrange(0,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(1,2) << nummultirange(numrange(0,4), numrange(7,8));
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange() << 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange() << numrange(1,2);
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(3,4)) << numrange(3,6);
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(0,2)) << numrange(3,6);
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange(numrange(0,2), numrange(7,8)) << numrange(3,6);
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(-4,-2), numrange(0,2)) << numrange(3,6);
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange() << nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange() << nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(1,2)) << nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(1,2)) << nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(1,2)) << nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange(numrange(1,2)) << nummultirange(numrange(3,4), numrange(7,8));
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange(numrange(1,2), numrange(4,5)) << nummultirange(numrange(3,4), numrange(7,8));
+ ?column? 
+----------
+ f
+(1 row)
+
+-- strictly right
+select nummultirange() >> 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange() >> numrange(1,2);
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(3,4)) >> numrange(1,2);
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange(numrange(0,4)) >> numrange(1,2);
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(0,4), numrange(7,8)) >> numrange(1,2);
+ ?column? 
+----------
+ f
+(1 row)
+
+select 'empty'::numrange >> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(1,2) >> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(3,6) >> nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(3,6) >> nummultirange(numrange(0,2));
+ ?column? 
+----------
+ t
+(1 row)
+
+select numrange(3,6) >> nummultirange(numrange(0,2), numrange(7,8));
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(3,6) >> nummultirange(numrange(-4,-2), numrange(0,2));
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange() >> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(1,2)) >> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange() >> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(1,2)) >> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(3,4)) >> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange(numrange(3,4), numrange(7,8)) >> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange(numrange(3,4), numrange(7,8)) >> nummultirange(numrange(1,2), numrange(4,5));
+ ?column? 
+----------
+ f
+(1 row)
+
+-- union
+SELECT nummultirange() + nummultirange();
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange() + nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) + nummultirange();
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) + nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) + nummultirange(numrange(2,4));
+ ?column? 
+----------
+ {[1,4)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) + nummultirange(numrange(3,4));
+   ?column?    
+---------------
+ {[1,2),[3,4)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) + nummultirange(numrange(2,4));
+ ?column? 
+----------
+ {[1,5)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) + nummultirange(numrange(3,4));
+   ?column?    
+---------------
+ {[1,2),[3,5)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) + nummultirange(numrange(0,9));
+ ?column? 
+----------
+ {[0,9)}
+(1 row)
+
+-- merge
+SELECT range_merge(nummultirange());
+ range_merge 
+-------------
+ empty
+(1 row)
+
+SELECT range_merge(nummultirange(numrange(1,2)));
+ range_merge 
+-------------
+ [1,2)
+(1 row)
+
+SELECT range_merge(nummultirange(numrange(1,2), numrange(7,8)));
+ range_merge 
+-------------
+ [1,8)
+(1 row)
+
+-- minus
+SELECT nummultirange() - nummultirange();
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange() - nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) - nummultirange();
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(3,4)) - nummultirange();
+   ?column?    
+---------------
+ {[1,2),[3,4)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) - nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) - nummultirange(numrange(2,4));
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) - nummultirange(numrange(3,4));
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {[2,4)}
+(1 row)
+
+SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(2,3));
+   ?column?    
+---------------
+ {[1,2),[3,4)}
+(1 row)
+
+SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(0,8));
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(0,2));
+ ?column? 
+----------
+ {[2,4)}
+(1 row)
+
+SELECT nummultirange(numrange(1,8)) - nummultirange(numrange(0,2), numrange(3,4));
+   ?column?    
+---------------
+ {[2,3),[4,8)}
+(1 row)
+
+SELECT nummultirange(numrange(1,8)) - nummultirange(numrange(2,3), numrange(5,null));
+   ?column?    
+---------------
+ {[1,2),[3,5)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(-2,0));
+   ?column?    
+---------------
+ {[1,2),[4,5)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(2,4));
+   ?column?    
+---------------
+ {[1,2),[4,5)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(3,5));
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(0,9));
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange(numrange(1,3), numrange(4,5)) - nummultirange(numrange(2,9));
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(8,9));
+   ?column?    
+---------------
+ {[1,2),[4,5)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(-2,0), numrange(8,9));
+   ?column?    
+---------------
+ {[1,2),[4,5)}
+(1 row)
+
+-- intersection
+SELECT nummultirange() * nummultirange();
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange() * nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) * nummultirange();
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT '{[1,3)}'::nummultirange * '{[1,5)}'::nummultirange;
+ ?column? 
+----------
+ {[1,3)}
+(1 row)
+
+SELECT '{[1,3)}'::nummultirange * '{[0,5)}'::nummultirange;
+ ?column? 
+----------
+ {[1,3)}
+(1 row)
+
+SELECT '{[1,3)}'::nummultirange * '{[0,2)}'::nummultirange;
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT '{[1,3)}'::nummultirange * '{[2,5)}'::nummultirange;
+ ?column? 
+----------
+ {[2,3)}
+(1 row)
+
+SELECT '{[1,4)}'::nummultirange * '{[2,3)}'::nummultirange;
+ ?column? 
+----------
+ {[2,3)}
+(1 row)
+
+SELECT '{[1,4)}'::nummultirange * '{[0,2), [3,5)}'::nummultirange;
+   ?column?    
+---------------
+ {[1,2),[3,4)}
+(1 row)
+
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[0,8), [9,12)}'::nummultirange;
+       ?column?       
+----------------------
+ {[1,4),[7,8),[9,10)}
+(1 row)
+
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[9,12)}'::nummultirange;
+ ?column? 
+----------
+ {[9,10)}
+(1 row)
+
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[-5,-4), [5,6), [9,12)}'::nummultirange;
+ ?column? 
+----------
+ {[9,10)}
+(1 row)
+
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[0,2), [3,8), [9,12)}'::nummultirange;
+          ?column?          
+----------------------------
+ {[1,2),[3,4),[7,8),[9,10)}
+(1 row)
+
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[0,2), [3,8), [9,12)}'::nummultirange;
+          ?column?          
+----------------------------
+ {[1,2),[3,4),[7,8),[9,10)}
+(1 row)
+
+--
+-- range_agg function
+--
+create table reservations ( room_id integer not null, booked_during daterange );
+insert into reservations values
+-- 1: has a meets and a gap
+(1, daterange('2018-07-01', '2018-07-07')),
+(1, daterange('2018-07-07', '2018-07-14')),
+(1, daterange('2018-07-20', '2018-07-22')),
+-- 2: just a single row
+(2, daterange('2018-07-01', '2018-07-03')),
+-- 3: one null range
+(3, NULL),
+-- 4: two null ranges
+(4, NULL),
+(4, NULL),
+-- 5: a null range and a non-null range
+(5, NULL),
+(5, daterange('2018-07-01', '2018-07-03')),
+-- 6: has overlap
+(6, daterange('2018-07-01', '2018-07-07')),
+(6, daterange('2018-07-05', '2018-07-10')),
+-- 7: two ranges that meet: no gap or overlap
+(7, daterange('2018-07-01', '2018-07-07')),
+(7, daterange('2018-07-07', '2018-07-14')),
+-- 8: an empty range
+(8, 'empty'::daterange)
+;
+SELECT   room_id, range_agg(booked_during)
+FROM     reservations
+GROUP BY room_id
+ORDER BY room_id;
+ room_id |                     range_agg                     
+---------+---------------------------------------------------
+       1 | {[07-01-2018,07-14-2018),[07-20-2018,07-22-2018)}
+       2 | {[07-01-2018,07-03-2018)}
+       3 | 
+       4 | 
+       5 | {[07-01-2018,07-03-2018)}
+       6 | {[07-01-2018,07-10-2018)}
+       7 | {[07-01-2018,07-14-2018)}
+       8 | {}
+(8 rows)
+
+-- range_agg on a custom range type too
+SELECT  range_agg(r)
+FROM    (VALUES
+          ('[a,c]'::textrange),
+          ('[b,b]'::textrange),
+          ('[c,f]'::textrange),
+          ('[g,h)'::textrange),
+          ('[h,j)'::textrange)
+        ) t(r);
+   range_agg   
+---------------
+ {[a,f],[g,j)}
+(1 row)
+
+select range_intersect_agg(nmr) from nummultirange_test;
+ range_intersect_agg 
+---------------------
+ {}
+(1 row)
+
+select range_intersect_agg(nmr) from nummultirange_test where false;
+ range_intersect_agg 
+---------------------
+ 
+(1 row)
+
+-- test with just one input:
+select range_intersect_agg(nmr) from (values ('{[1,2]}'::nummultirange)) t(nmr);
+ range_intersect_agg 
+---------------------
+ {[1,2]}
+(1 row)
+
+select range_intersect_agg(nmr) from nummultirange_test where nmr @> 4.0;
+ range_intersect_agg 
+---------------------
+ {[3,5)}
+(1 row)
+
+create table nummultirange_test2(nmr nummultirange);
+create index nummultirange_test2_hash_idx on nummultirange_test2 using hash (nmr);
+INSERT INTO nummultirange_test2 VALUES('{[, 5)}');
+INSERT INTO nummultirange_test2 VALUES(nummultirange(numrange(1.1, 2.2)));
+INSERT INTO nummultirange_test2 VALUES(nummultirange(numrange(1.1, 2.2)));
+INSERT INTO nummultirange_test2 VALUES(nummultirange(numrange(1.1, 2.2,'()')));
+INSERT INTO nummultirange_test2 VALUES('{}');
+select * from nummultirange_test2 where nmr = '{}';
+ nmr 
+-----
+ {}
+(1 row)
+
+select * from nummultirange_test2 where nmr = nummultirange(numrange(1.1, 2.2));
+     nmr     
+-------------
+ {[1.1,2.2)}
+ {[1.1,2.2)}
+(2 rows)
+
+select * from nummultirange_test2 where nmr = nummultirange(numrange(1.1, 2.3));
+ nmr 
+-----
+(0 rows)
+
+set enable_nestloop=t;
+set enable_hashjoin=f;
+set enable_mergejoin=f;
+select * from nummultirange_test natural join nummultirange_test2 order by nmr;
+     nmr     
+-------------
+ {}
+ {}
+ {}
+ {}
+ {(,5)}
+ {[1.1,2.2)}
+ {[1.1,2.2)}
+(7 rows)
+
+set enable_nestloop=f;
+set enable_hashjoin=t;
+set enable_mergejoin=f;
+select * from nummultirange_test natural join nummultirange_test2 order by nmr;
+     nmr     
+-------------
+ {}
+ {}
+ {}
+ {}
+ {(,5)}
+ {[1.1,2.2)}
+ {[1.1,2.2)}
+(7 rows)
+
+set enable_nestloop=f;
+set enable_hashjoin=f;
+set enable_mergejoin=t;
+select * from nummultirange_test natural join nummultirange_test2 order by nmr;
+     nmr     
+-------------
+ {}
+ {}
+ {}
+ {}
+ {(,5)}
+ {[1.1,2.2)}
+ {[1.1,2.2)}
+(7 rows)
+
+set enable_nestloop to default;
+set enable_hashjoin to default;
+set enable_mergejoin to default;
+DROP TABLE nummultirange_test2;
+--
+-- Test user-defined multirange of floats
+--
+select '{[123.001, 5.e9)}'::float8multirange @> 888.882::float8;
+ ?column? 
+----------
+ t
+(1 row)
+
+create table float8multirange_test(f8mr float8multirange, i int);
+insert into float8multirange_test values(float8multirange(float8range(-100.00007, '1.111113e9')), 42);
+select * from float8multirange_test;
+           f8mr            | i  
+---------------------------+----
+ {[-100.00007,1111113000)} | 42
+(1 row)
+
+drop table float8multirange_test;
+--
+-- Test multirange types over domains
+--
+create domain mydomain as int4;
+create type mydomainrange as range(subtype=mydomain);
+select '{[4,50)}'::mydomainmultirange @> 7::mydomain;
+ ?column? 
+----------
+ t
+(1 row)
+
+drop domain mydomain cascade;
+NOTICE:  drop cascades to type mydomainrange
+--
+-- Test domains over multirange types
+--
+create domain restrictedmultirange as int4multirange check (upper(value) < 10);
+select '{[4,5)}'::restrictedmultirange @> 7;
+ ?column? 
+----------
+ f
+(1 row)
+
+select '{[4,50)}'::restrictedmultirange @> 7; -- should fail
+ERROR:  value for domain restrictedmultirange violates check constraint "restrictedmultirange_check"
+drop domain restrictedmultirange;
+--
+-- Test multiple multirange types over the same subtype
+--
+create type textrange1 as range(subtype=text, collation="C");
+create type textrange2 as range(subtype=text, collation="C");
+select textmultirange1(textrange2('a','Z'));  -- should fail
+ERROR:  function textmultirange1(textrange2) does not exist
+LINE 1: select textmultirange1(textrange2('a','Z'));
+               ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+select textmultirange1(textrange1('a','Z')) @> 'b'::text;
+ERROR:  range lower bound must be less than or equal to range upper bound
+select textmultirange2(textrange2('a','z')) @> 'b'::text;
+ ?column? 
+----------
+ t
+(1 row)
+
+drop type textrange1;
+drop type textrange2;
+--
+-- Test polymorphic type system
+--
+create function anyarray_anymultirange_func(a anyarray, r anymultirange)
+  returns anyelement as 'select $1[1] + lower($2);' language sql;
+select anyarray_anymultirange_func(ARRAY[1,2], int4multirange(int4range(10,20)));
+ anyarray_anymultirange_func 
+-----------------------------
+                          11
+(1 row)
+
+-- should fail
+select anyarray_anymultirange_func(ARRAY[1,2], nummultirange(numrange(10,20)));
+ERROR:  function anyarray_anymultirange_func(integer[], nummultirange) does not exist
+LINE 1: select anyarray_anymultirange_func(ARRAY[1,2], nummultirange...
+               ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+drop function anyarray_anymultirange_func(anyarray, anymultirange);
+-- should fail
+create function bogus_func(anyelement)
+  returns anymultirange as 'select int4multirange(int4range(1,10))' language sql;
+ERROR:  cannot determine result data type
+DETAIL:  A function returning "anyrange" or "anymultirange" must have at least one "anyrange" or "anymultirange" argument.
+-- should fail
+create function bogus_func(int)
+  returns anymultirange as 'select int4multirange(int4range(1,10))' language sql;
+ERROR:  cannot determine result data type
+DETAIL:  A function returning a polymorphic type must have at least one polymorphic argument.
+create function range_add_bounds(anymultirange)
+  returns anyelement as 'select lower($1) + upper($1)' language sql;
+select range_add_bounds(int4multirange(int4range(1, 17)));
+ range_add_bounds 
+------------------
+               18
+(1 row)
+
+select range_add_bounds(nummultirange(numrange(1.0001, 123.123)));
+ range_add_bounds 
+------------------
+         124.1231
+(1 row)
+
+create function multirangetypes_sql(q anymultirange, b anyarray, out c anyelement)
+  as $$ select upper($1) + $2[1] $$
+  language sql;
+select multirangetypes_sql(int4multirange(int4range(1,10)), ARRAY[2,20]);
+ multirangetypes_sql 
+---------------------
+                  12
+(1 row)
+
+select multirangetypes_sql(nummultirange(numrange(1,10)), ARRAY[2,20]);  -- match failure
+ERROR:  function multirangetypes_sql(nummultirange, integer[]) does not exist
+LINE 1: select multirangetypes_sql(nummultirange(numrange(1,10)), AR...
+               ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+--
+-- Arrays of multiranges
+--
+select ARRAY[nummultirange(numrange(1.1, 1.2)), nummultirange(numrange(12.3, 155.5))];
+              array               
+----------------------------------
+ {"{[1.1,1.2)}","{[12.3,155.5)}"}
+(1 row)
+
+create table i8mr_array (f1 int, f2 int8multirange[]);
+insert into i8mr_array values (42, array[int8multirange(int8range(1,10)), int8multirange(int8range(2,20))]);
+select * from i8mr_array;
+ f1 |           f2            
+----+-------------------------
+ 42 | {"{[1,10)}","{[2,20)}"}
+(1 row)
+
+drop table i8mr_array;
+--
+-- Multiranges of arrays
+--
+select arraymultirange(arrayrange(ARRAY[1,2], ARRAY[2,1]));
+   arraymultirange   
+---------------------
+ {["{1,2}","{2,1}")}
+(1 row)
+
+select arraymultirange(arrayrange(ARRAY[2,1], ARRAY[1,2]));  -- fail
+ERROR:  range lower bound must be less than or equal to range upper bound
+select array[1,1] <@ arraymultirange(arrayrange(array[1,2], array[2,1]));
+ ?column? 
+----------
+ f
+(1 row)
+
+select array[1,3] <@ arraymultirange(arrayrange(array[1,2], array[2,1]));
+ ?column? 
+----------
+ t
+(1 row)
+
+--
+-- Ranges of composites
+--
+create type two_ints as (a int, b int);
+create type two_ints_range as range (subtype = two_ints);
+-- with force_parallel_mode on, this exercises tqueue.c's range remapping
+select *, row_to_json(upper(t)) as u from
+  (values (two_ints_multirange(two_ints_range(row(1,2), row(3,4)))),
+          (two_ints_multirange(two_ints_range(row(5,6), row(7,8))))) v(t);
+          t          |       u       
+---------------------+---------------
+ {["(1,2)","(3,4)")} | {"a":3,"b":4}
+ {["(5,6)","(7,8)")} | {"a":7,"b":8}
+(2 rows)
+
+drop type two_ints cascade;
+NOTICE:  drop cascades to type two_ints_range
+--
+-- Check behavior when subtype lacks a hash function
+--
+set enable_sort = off;  -- try to make it pick a hash setop implementation
+select '{(2,5)}'::cashmultirange except select '{(5,6)}'::cashmultirange;
+ cashmultirange  
+-----------------
+ {($2.00,$5.00)}
+(1 row)
+
+reset enable_sort;
+--
+-- OUT/INOUT/TABLE functions
+--
+-- infer anymultirange from anymultirange
+create function mr_outparam_succeed(i anymultirange, out r anymultirange, out t text)
+  as $$ select $1, 'foo'::text $$ language sql;
+select * from mr_outparam_succeed(int4multirange(int4range(1,2)));
+    r    |  t  
+---------+-----
+ {[1,2)} | foo
+(1 row)
+
+-- infer anyarray from anymultirange
+create function mr_outparam_succeed2(i anymultirange, out r anyarray, out t text)
+  as $$ select ARRAY[upper($1)], 'foo'::text $$ language sql;
+select * from mr_outparam_succeed2(int4multirange(int4range(1,2)));
+  r  |  t  
+-----+-----
+ {2} | foo
+(1 row)
+
+-- infer anyrange from anymultirange
+create function mr_outparam_succeed3(i anymultirange, out r anyrange, out t text)
+  as $$ select range_merge($1), 'foo'::text $$ language sql;
+select * from mr_outparam_succeed3(int4multirange(int4range(1,2)));
+   r   |  t  
+-------+-----
+ [1,2) | foo
+(1 row)
+
+-- infer anymultirange from anyrange
+create function mr_outparam_succeed4(i anyrange, out r anymultirange, out t text)
+  as $$ select multirange($1), 'foo'::text $$ language sql;
+select * from mr_outparam_succeed4(int4range(1,2));
+    r    |  t  
+---------+-----
+ {[1,2)} | foo
+(1 row)
+
+-- infer anyelement from anymultirange
+create function mr_inoutparam_succeed(out i anyelement, inout r anymultirange)
+  as $$ select upper($1), $1 $$ language sql;
+select * from mr_inoutparam_succeed(int4multirange(int4range(1,2)));
+ i |    r    
+---+---------
+ 2 | {[1,2)}
+(1 row)
+
+-- infer anyelement+anymultirange from anyelement+anymultirange
+create function mr_table_succeed(i anyelement, r anymultirange) returns table(i anyelement, r anymultirange)
+  as $$ select $1, $2 $$ language sql;
+select * from mr_table_succeed(123, int4multirange(int4range(1,11)));
+  i  |    r     
+-----+----------
+ 123 | {[1,11)}
+(1 row)
+
+-- use anymultirange in plpgsql
+create function mr_polymorphic(i anyrange) returns anymultirange
+  as $$ begin return multirange($1); end; $$ language plpgsql;
+select mr_polymorphic(int4range(1, 4));
+ mr_polymorphic 
+----------------
+ {[1,4)}
+(1 row)
+
+-- should fail
+create function mr_outparam_fail(i anyelement, out r anymultirange, out t text)
+  as $$ select '[1,10]', 'foo' $$ language sql;
+ERROR:  cannot determine result data type
+DETAIL:  A function returning "anyrange" or "anymultirange" must have at least one "anyrange" or "anymultirange" argument.
+--should fail
+create function mr_inoutparam_fail(inout i anyelement, out r anymultirange)
+  as $$ select $1, '[1,10]' $$ language sql;
+ERROR:  cannot determine result data type
+DETAIL:  A function returning "anyrange" or "anymultirange" must have at least one "anyrange" or "anymultirange" argument.
+--should fail
+create function mr_table_fail(i anyelement) returns table(i anyelement, r anymultirange)
+  as $$ select $1, '[1,10]' $$ language sql;
+ERROR:  cannot determine result data type
+DETAIL:  A function returning "anyrange" or "anymultirange" must have at least one "anyrange" or "anymultirange" argument.
diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out
index 40468e8f49..1514f3af7c 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
@@ -2139,13 +2154,14 @@ ORDER BY 1, 2, 3;
                     | float_ops        | float4_ops       | real
                     | float_ops        | float8_ops       | double precision
                     | jsonb_ops        | jsonb_ops        | jsonb
+                    | multirange_ops   | multirange_ops   | anymultirange
                     | numeric_ops      | numeric_ops      | numeric
                     | range_ops        | range_ops        | anyrange
                     | record_image_ops | record_image_ops | record
                     | record_ops       | record_ops       | record
                     | tsquery_ops      | tsquery_ops      | tsquery
                     | tsvector_ops     | tsvector_ops     | tsvector
-(14 rows)
+(15 rows)
 
 -- **************** pg_index ****************
 -- Look for illegal values in pg_index fields.
diff --git a/src/test/regress/expected/rangetypes.out b/src/test/regress/expected/rangetypes.out
index 220f2d96cb..6d00a3efab 100644
--- a/src/test/regress/expected/rangetypes.out
+++ b/src/test/regress/expected/rangetypes.out
@@ -518,6 +518,24 @@ select numrange(1.0, 2.0) * numrange(2.5, 3.0);
  empty
 (1 row)
 
+select range_intersect_agg(nr) from numrange_test;
+ range_intersect_agg 
+---------------------
+ empty
+(1 row)
+
+select range_intersect_agg(nr) from numrange_test where false;
+ range_intersect_agg 
+---------------------
+ 
+(1 row)
+
+select range_intersect_agg(nr) from numrange_test where nr @> 4.0;
+ range_intersect_agg 
+---------------------
+ [3,5)
+(1 row)
+
 create table numrange_test2(nr numrange);
 create index numrange_test2_hash_idx on numrange_test2 using hash (nr);
 INSERT INTO numrange_test2 VALUES('[, 5)');
@@ -1371,7 +1389,7 @@ drop function anyarray_anyrange_func(anyarray, anyrange);
 create function bogus_func(anyelement)
   returns anyrange as 'select int4range(1,10)' language sql;
 ERROR:  cannot determine result data type
-DETAIL:  A function returning "anyrange" must have at least one "anyrange" argument.
+DETAIL:  A function returning "anyrange" or "anymultirange" must have at least one "anyrange" or "anymultirange" argument.
 -- should fail
 create function bogus_func(int)
   returns anyrange as 'select int4range(1,10)' language sql;
@@ -1482,6 +1500,7 @@ reset enable_sort;
 --
 -- OUT/INOUT/TABLE functions
 --
+-- infer anyrange from anyrange
 create function outparam_succeed(i anyrange, out r anyrange, out t text)
   as $$ select $1, 'foo'::text $$ language sql;
 select * from outparam_succeed(int4range(1,2));
@@ -1490,6 +1509,16 @@ select * from outparam_succeed(int4range(1,2));
  [1,2) | foo
 (1 row)
 
+-- infer anyarray from anyrange
+create function outparam_succeed2(i anyrange, out r anyarray, out t text)
+  as $$ select ARRAY[upper($1)], 'foo'::text $$ language sql;
+select * from outparam_succeed2(int4range(int4range(1,2)));
+  r  |  t  
+-----+-----
+ {2} | foo
+(1 row)
+
+-- infer anyelement from anyrange
 create function inoutparam_succeed(out i anyelement, inout r anyrange)
   as $$ select upper($1), $1 $$ language sql;
 select * from inoutparam_succeed(int4range(1,2));
@@ -1498,6 +1527,7 @@ select * from inoutparam_succeed(int4range(1,2));
  2 | [1,2)
 (1 row)
 
+-- infer anyelement+anyrange from anyelement+anyrange
 create function table_succeed(i anyelement, r anyrange) returns table(i anyelement, r anyrange)
   as $$ select $1, $2 $$ language sql;
 select * from table_succeed(123, int4range(1,11));
@@ -1510,14 +1540,14 @@ select * from table_succeed(123, int4range(1,11));
 create function outparam_fail(i anyelement, out r anyrange, out t text)
   as $$ select '[1,10]', 'foo' $$ language sql;
 ERROR:  cannot determine result data type
-DETAIL:  A function returning "anyrange" must have at least one "anyrange" argument.
+DETAIL:  A function returning "anyrange" or "anymultirange" must have at least one "anyrange" or "anymultirange" argument.
 --should fail
 create function inoutparam_fail(inout i anyelement, out r anyrange)
   as $$ select $1, '[1,10]' $$ language sql;
 ERROR:  cannot determine result data type
-DETAIL:  A function returning "anyrange" must have at least one "anyrange" argument.
+DETAIL:  A function returning "anyrange" or "anymultirange" must have at least one "anyrange" or "anymultirange" argument.
 --should fail
 create function table_fail(i anyelement) returns table(i anyelement, r anyrange)
   as $$ select $1, '[1,10]' $$ language sql;
 ERROR:  cannot determine result data type
-DETAIL:  A function returning "anyrange" must have at least one "anyrange" argument.
+DETAIL:  A function returning "anyrange" or "anymultirange" must have at least one "anyrange" or "anymultirange" argument.
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index 192445878d..d9ce961be2 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -92,6 +92,7 @@ num_exp_sqrt|t
 num_exp_sub|t
 num_input_test|f
 num_result|f
+nummultirange_test|t
 numrange_test|t
 onek|t
 onek2|t
@@ -172,6 +173,7 @@ quad_poly_tbl|t
 radix_text_tbl|t
 ramp|f
 real_city|f
+reservations|f
 road|t
 shighway|t
 slow_emp4000|f
diff --git a/src/test/regress/expected/type_sanity.out b/src/test/regress/expected/type_sanity.out
index cd7fc03b04..60a9b4d22f 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.rngmultitypid
+FROM pg_range p1
+WHERE p1.rngmultitypid IS NULL OR p1.rngmultitypid = 0;
+ rngtypid | rngsubtype | rngmultitypid 
+----------+------------+---------------
+(0 rows)
+
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index d2b17dd3ea..5355da7b01 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 acba391332..1f677f63af 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..0f70919e1a
--- /dev/null
+++ b/src/test/regress/sql/multirangetypes.sql
@@ -0,0 +1,630 @@
+-- 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'));
+
+--
+-- test casts, both a built-in range type and a user-defined one:
+--
+select 'empty'::int4range::int4multirange;
+select int4range(1, 3)::int4multirange;
+select int4range(1, null)::int4multirange;
+select int4range(null, null)::int4multirange;
+select 'empty'::textrange::textmultirange;
+select textrange('a', 'c')::textmultirange;
+select textrange('a', null)::textmultirange;
+select textrange(null, null)::textmultirange;
+
+--
+-- create some test data and test the operators
+--
+
+CREATE TABLE nummultirange_test (nmr NUMMULTIRANGE);
+CREATE INDEX nummultirange_test_btree ON nummultirange_test(nmr);
+
+INSERT INTO nummultirange_test VALUES('{}');
+INSERT INTO nummultirange_test VALUES('{[,)}');
+INSERT INTO nummultirange_test VALUES('{[3,]}');
+INSERT INTO nummultirange_test VALUES('{[,), [3,]}');
+INSERT INTO nummultirange_test VALUES('{[, 5)}');
+INSERT INTO nummultirange_test VALUES(nummultirange());
+INSERT INTO nummultirange_test VALUES(nummultirange(variadic '{}'::numrange[]));
+INSERT INTO nummultirange_test VALUES(nummultirange(numrange(1.1, 2.2)));
+INSERT INTO nummultirange_test VALUES('{empty}');
+INSERT INTO nummultirange_test VALUES(nummultirange(numrange(1.7, 1.7, '[]'), numrange(1.7, 1.9)));
+INSERT INTO nummultirange_test VALUES(nummultirange(numrange(1.7, 1.7, '[]'), numrange(1.9, 2.1)));
+
+SELECT nmr, isempty(nmr), lower(nmr), upper(nmr) FROM nummultirange_test ORDER BY nmr;
+SELECT nmr, lower_inc(nmr), lower_inf(nmr), upper_inc(nmr), upper_inf(nmr) FROM nummultirange_test ORDER BY nmr;
+
+SELECT * FROM nummultirange_test WHERE nmr = '{}';
+SELECT * FROM nummultirange_test WHERE nmr = '{(,5)}';
+SELECT * FROM nummultirange_test WHERE nmr = '{[3,)}';
+SELECT * FROM nummultirange_test WHERE nmr = '{[1.7,1.7]}';
+SELECT * FROM nummultirange_test WHERE nmr = '{[1.7,1.7],[1.9,2.1)}';
+SELECT * FROM nummultirange_test WHERE nmr < '{}';
+SELECT * FROM nummultirange_test WHERE nmr < '{[-1000.0, -1000.0]}';
+SELECT * FROM nummultirange_test WHERE nmr < '{[0.0, 1.0]}';
+SELECT * FROM nummultirange_test WHERE nmr < '{[1000.0, 1001.0]}';
+SELECT * FROM nummultirange_test WHERE nmr <= '{}';
+SELECT * FROM nummultirange_test WHERE nmr <= '{[3,)}';
+SELECT * FROM nummultirange_test WHERE nmr >= '{}';
+SELECT * FROM nummultirange_test WHERE nmr >= '{[3,)}';
+SELECT * FROM nummultirange_test WHERE nmr > '{}';
+SELECT * FROM nummultirange_test WHERE nmr > '{[-1000.0, -1000.0]}';
+SELECT * FROM nummultirange_test WHERE nmr > '{[0.0, 1.0]}';
+SELECT * FROM nummultirange_test WHERE nmr > '{[1000.0, 1001.0]}';
+SELECT * FROM nummultirange_test WHERE nmr <> '{}';
+SELECT * FROM nummultirange_test WHERE nmr <> '{(,5)}';
+
+select nummultirange(numrange(2.0, 1.0));
+select nummultirange(numrange(5.0, 6.0), numrange(1.0, 2.0));
+
+-- overlaps
+SELECT * FROM nummultirange_test WHERE range_overlaps_multirange(numrange(4.0, 4.2), nmr);
+SELECT * FROM nummultirange_test WHERE numrange(4.0, 4.2) && nmr;
+SELECT * FROM nummultirange_test WHERE multirange_overlaps_range(nmr, numrange(4.0, 4.2));
+SELECT * FROM nummultirange_test WHERE nmr && numrange(4.0, 4.2);
+SELECT * FROM nummultirange_test WHERE multirange_overlaps_multirange(nmr, nummultirange(numrange(4.0, 4.2), numrange(6.0, 7.0)));
+SELECT * FROM nummultirange_test WHERE nmr && nummultirange(numrange(4.0, 4.2), numrange(6.0, 7.0));
+SELECT * FROM nummultirange_test WHERE nmr && nummultirange(numrange(6.0, 7.0));
+SELECT * FROM nummultirange_test WHERE nmr && nummultirange(numrange(6.0, 7.0), numrange(8.0, 9.0));
+
+-- mr contains x
+SELECT * FROM nummultirange_test WHERE multirange_contains_elem(nmr, 4.0);
+SELECT * FROM nummultirange_test WHERE nmr @> 4.0;
+SELECT * FROM nummultirange_test WHERE multirange_contains_range(nmr, numrange(4.0, 4.2));
+SELECT * FROM nummultirange_test WHERE nmr @> numrange(4.0, 4.2);
+SELECT * FROM nummultirange_test WHERE multirange_contains_multirange(nmr, '{[4.0,4.2), [6.0, 8.0)}');
+SELECT * FROM nummultirange_test WHERE nmr @> '{[4.0,4.2), [6.0, 8.0)}'::nummultirange;
+
+-- x is contained by mr
+SELECT * FROM nummultirange_test WHERE elem_contained_by_multirange(4.0, nmr);
+SELECT * FROM nummultirange_test WHERE 4.0 <@ nmr;
+SELECT * FROM nummultirange_test WHERE range_contained_by_multirange(numrange(4.0, 4.2), nmr);
+SELECT * FROM nummultirange_test WHERE numrange(4.0, 4.2) <@ nmr;
+SELECT * FROM nummultirange_test WHERE multirange_contained_by_multirange('{[4.0,4.2), [6.0, 8.0)}', nmr);
+SELECT * FROM nummultirange_test WHERE '{[4.0,4.2), [6.0, 8.0)}'::nummultirange <@ nmr;
+
+-- overlaps
+SELECT 'empty'::numrange && nummultirange();
+SELECT 'empty'::numrange && nummultirange(numrange(1,2));
+SELECT nummultirange() && 'empty'::numrange;
+SELECT nummultirange(numrange(1,2)) && 'empty'::numrange;
+SELECT nummultirange() && nummultirange();
+SELECT nummultirange() && nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) && nummultirange();
+SELECT nummultirange(numrange(3,4)) && nummultirange(numrange(1,2), numrange(7,8));
+SELECT nummultirange(numrange(1,2), numrange(7,8)) && nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(3,4)) && nummultirange(numrange(1,2), numrange(3.5,8));
+SELECT nummultirange(numrange(1,2), numrange(3.5,8)) && numrange(3,4);
+SELECT nummultirange(numrange(1,2), numrange(3.5,8)) && nummultirange(numrange(3,4));
+
+-- contains
+SELECT nummultirange() @> nummultirange();
+SELECT nummultirange() @> 'empty'::numrange;
+SELECT nummultirange(numrange(null,null)) @> numrange(1,2);
+SELECT nummultirange(numrange(null,null)) @> numrange(null,2);
+SELECT nummultirange(numrange(null,null)) @> numrange(2,null);
+SELECT nummultirange(numrange(null,5)) @> numrange(null,3);
+SELECT nummultirange(numrange(null,5)) @> numrange(null,8);
+SELECT nummultirange(numrange(5,null)) @> numrange(8,null);
+SELECT nummultirange(numrange(5,null)) @> numrange(3,null);
+SELECT nummultirange(numrange(1,5)) @> numrange(8,9);
+SELECT nummultirange(numrange(1,5)) @> numrange(3,9);
+SELECT nummultirange(numrange(1,5)) @> numrange(1,4);
+SELECT nummultirange(numrange(1,5)) @> numrange(1,5);
+SELECT nummultirange(numrange(-4,-2), numrange(1,5)) @> numrange(1,5);
+SELECT nummultirange(numrange(1,5), numrange(8,9)) @> numrange(1,5);
+SELECT nummultirange(numrange(1,5), numrange(8,9)) @> numrange(6,7);
+SELECT nummultirange(numrange(1,5), numrange(6,9)) @> numrange(6,7);
+SELECT '{[1,5)}'::nummultirange @> '{[1,5)}';
+SELECT '{[-4,-2), [1,5)}'::nummultirange @> '{[1,5)}';
+SELECT '{[1,5), [8,9)}'::nummultirange @> '{[1,5)}';
+SELECT '{[1,5), [8,9)}'::nummultirange @> '{[6,7)}';
+SELECT '{[1,5), [6,9)}'::nummultirange @> '{[6,7)}';
+
+-- is contained by
+SELECT nummultirange() <@ nummultirange();
+SELECT 'empty'::numrange <@ nummultirange();
+SELECT numrange(1,2) <@ nummultirange(numrange(null,null));
+SELECT numrange(null,2) <@ nummultirange(numrange(null,null));
+SELECT numrange(2,null) <@ nummultirange(numrange(null,null));
+SELECT numrange(null,3) <@ nummultirange(numrange(null,5));
+SELECT numrange(null,8) <@ nummultirange(numrange(null,5));
+SELECT numrange(8,null) <@ nummultirange(numrange(5,null));
+SELECT numrange(3,null) <@ nummultirange(numrange(5,null));
+SELECT numrange(8,9) <@ nummultirange(numrange(1,5));
+SELECT numrange(3,9) <@ nummultirange(numrange(1,5));
+SELECT numrange(1,4) <@ nummultirange(numrange(1,5));
+SELECT numrange(1,5) <@ nummultirange(numrange(1,5));
+SELECT numrange(1,5) <@ nummultirange(numrange(-4,-2), numrange(1,5));
+SELECT numrange(1,5) <@ nummultirange(numrange(1,5), numrange(8,9));
+SELECT numrange(6,7) <@ nummultirange(numrange(1,5), numrange(8,9));
+SELECT numrange(6,7) <@ nummultirange(numrange(1,5), numrange(6,9));
+SELECT '{[1,5)}' <@ '{[1,5)}'::nummultirange;
+SELECT '{[1,5)}' <@ '{[-4,-2), [1,5)}'::nummultirange;
+SELECT '{[1,5)}' <@ '{[1,5), [8,9)}'::nummultirange;
+SELECT '{[6,7)}' <@ '{[1,5), [8,9)}'::nummultirange;
+SELECT '{[6,7)}' <@ '{[1,5), [6,9)}'::nummultirange;
+
+-- overleft
+SELECT 'empty'::numrange &< nummultirange();
+SELECT 'empty'::numrange &< nummultirange(numrange(1,2));
+SELECT nummultirange() &< 'empty'::numrange;
+SELECT nummultirange(numrange(1,2)) &< 'empty'::numrange;
+SELECT nummultirange() &< nummultirange();
+SELECT nummultirange(numrange(1,2)) &< nummultirange();
+SELECT nummultirange() &< nummultirange(numrange(1,2));
+SELECT numrange(6,7) &< nummultirange(numrange(3,4));
+SELECT numrange(1,2) &< nummultirange(numrange(3,4));
+SELECT numrange(1,4) &< nummultirange(numrange(3,4));
+SELECT numrange(1,6) &< nummultirange(numrange(3,4));
+SELECT numrange(3.5,6) &< nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(6,7)) &< numrange(3,4);
+SELECT nummultirange(numrange(1,2)) &< numrange(3,4);
+SELECT nummultirange(numrange(1,4)) &< numrange(3,4);
+SELECT nummultirange(numrange(1,6)) &< numrange(3,4);
+SELECT nummultirange(numrange(3.5,6)) &< numrange(3,4);
+SELECT nummultirange(numrange(6,7)) &< nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,2)) &< nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,4)) &< nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,6)) &< nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(3.5,6)) &< nummultirange(numrange(3,4));
+
+-- overright
+SELECT nummultirange() &> 'empty'::numrange;
+SELECT nummultirange(numrange(1,2)) &> 'empty'::numrange;
+SELECT 'empty'::numrange &> nummultirange();
+SELECT 'empty'::numrange &> nummultirange(numrange(1,2));
+SELECT nummultirange() &> nummultirange();
+SELECT nummultirange() &> nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) &> nummultirange();
+SELECT nummultirange(numrange(3,4)) &> numrange(6,7);
+SELECT nummultirange(numrange(3,4)) &> numrange(1,2);
+SELECT nummultirange(numrange(3,4)) &> numrange(1,4);
+SELECT nummultirange(numrange(3,4)) &> numrange(1,6);
+SELECT nummultirange(numrange(3,4)) &> numrange(3.5,6);
+SELECT numrange(3,4) &> nummultirange(numrange(6,7));
+SELECT numrange(3,4) &> nummultirange(numrange(1,2));
+SELECT numrange(3,4) &> nummultirange(numrange(1,4));
+SELECT numrange(3,4) &> nummultirange(numrange(1,6));
+SELECT numrange(3,4) &> nummultirange(numrange(3.5,6));
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(6,7));
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(1,4));
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(1,6));
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(3.5,6));
+
+-- meets
+SELECT 'empty'::numrange -|- nummultirange();
+SELECT 'empty'::numrange -|- nummultirange(numrange(1,2));
+SELECT nummultirange() -|- 'empty'::numrange;
+SELECT nummultirange(numrange(1,2)) -|- 'empty'::numrange;
+SELECT nummultirange() -|- nummultirange();
+SELECT nummultirange(numrange(1,2)) -|- nummultirange();
+SELECT nummultirange() -|- nummultirange(numrange(1,2));
+SELECT numrange(1,2) -|- nummultirange(numrange(2,4));
+SELECT numrange(1,2) -|- nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,2)) -|- numrange(2,4);
+SELECT nummultirange(numrange(1,2)) -|- numrange(3,4);
+SELECT nummultirange(numrange(1,2)) -|- nummultirange(numrange(2,4));
+SELECT nummultirange(numrange(1,2)) -|- nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,2), numrange(5,6)) -|- nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,2), numrange(5,6)) -|- nummultirange(numrange(6,7));
+SELECT nummultirange(numrange(1,2), numrange(5,6)) -|- nummultirange(numrange(8,9));
+SELECT nummultirange(numrange(1,2)) -|- nummultirange(numrange(2,4), numrange(6,7));
+
+-- strictly left
+select 'empty'::numrange << nummultirange();
+select numrange(1,2) << nummultirange();
+select numrange(1,2) << nummultirange(numrange(3,4));
+select numrange(1,2) << nummultirange(numrange(0,4));
+select numrange(1,2) << nummultirange(numrange(0,4), numrange(7,8));
+select nummultirange() << 'empty'::numrange;
+select nummultirange() << numrange(1,2);
+select nummultirange(numrange(3,4)) << numrange(3,6);
+select nummultirange(numrange(0,2)) << numrange(3,6);
+select nummultirange(numrange(0,2), numrange(7,8)) << numrange(3,6);
+select nummultirange(numrange(-4,-2), numrange(0,2)) << numrange(3,6);
+select nummultirange() << nummultirange();
+select nummultirange() << nummultirange(numrange(1,2));
+select nummultirange(numrange(1,2)) << nummultirange();
+select nummultirange(numrange(1,2)) << nummultirange(numrange(1,2));
+select nummultirange(numrange(1,2)) << nummultirange(numrange(3,4));
+select nummultirange(numrange(1,2)) << nummultirange(numrange(3,4), numrange(7,8));
+select nummultirange(numrange(1,2), numrange(4,5)) << nummultirange(numrange(3,4), numrange(7,8));
+
+-- strictly right
+select nummultirange() >> 'empty'::numrange;
+select nummultirange() >> numrange(1,2);
+select nummultirange(numrange(3,4)) >> numrange(1,2);
+select nummultirange(numrange(0,4)) >> numrange(1,2);
+select nummultirange(numrange(0,4), numrange(7,8)) >> numrange(1,2);
+select 'empty'::numrange >> nummultirange();
+select numrange(1,2) >> nummultirange();
+select numrange(3,6) >> nummultirange(numrange(3,4));
+select numrange(3,6) >> nummultirange(numrange(0,2));
+select numrange(3,6) >> nummultirange(numrange(0,2), numrange(7,8));
+select numrange(3,6) >> nummultirange(numrange(-4,-2), numrange(0,2));
+select nummultirange() >> nummultirange();
+select nummultirange(numrange(1,2)) >> nummultirange();
+select nummultirange() >> nummultirange(numrange(1,2));
+select nummultirange(numrange(1,2)) >> nummultirange(numrange(1,2));
+select nummultirange(numrange(3,4)) >> nummultirange(numrange(1,2));
+select nummultirange(numrange(3,4), numrange(7,8)) >> nummultirange(numrange(1,2));
+select nummultirange(numrange(3,4), numrange(7,8)) >> nummultirange(numrange(1,2), numrange(4,5));
+
+-- union
+SELECT nummultirange() + nummultirange();
+SELECT nummultirange() + nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) + nummultirange();
+SELECT nummultirange(numrange(1,2)) + nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) + nummultirange(numrange(2,4));
+SELECT nummultirange(numrange(1,2)) + nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) + nummultirange(numrange(2,4));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) + nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) + nummultirange(numrange(0,9));
+
+-- merge
+SELECT range_merge(nummultirange());
+SELECT range_merge(nummultirange(numrange(1,2)));
+SELECT range_merge(nummultirange(numrange(1,2), numrange(7,8)));
+
+-- minus
+SELECT nummultirange() - nummultirange();
+SELECT nummultirange() - nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) - nummultirange();
+SELECT nummultirange(numrange(1,2), numrange(3,4)) - nummultirange();
+SELECT nummultirange(numrange(1,2)) - nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) - nummultirange(numrange(2,4));
+SELECT nummultirange(numrange(1,2)) - nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(2,3));
+SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(0,8));
+SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(0,2));
+SELECT nummultirange(numrange(1,8)) - nummultirange(numrange(0,2), numrange(3,4));
+SELECT nummultirange(numrange(1,8)) - nummultirange(numrange(2,3), numrange(5,null));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(-2,0));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(2,4));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(3,5));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(0,9));
+SELECT nummultirange(numrange(1,3), numrange(4,5)) - nummultirange(numrange(2,9));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(8,9));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(-2,0), numrange(8,9));
+
+-- intersection
+SELECT nummultirange() * nummultirange();
+SELECT nummultirange() * nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) * nummultirange();
+SELECT '{[1,3)}'::nummultirange * '{[1,5)}'::nummultirange;
+SELECT '{[1,3)}'::nummultirange * '{[0,5)}'::nummultirange;
+SELECT '{[1,3)}'::nummultirange * '{[0,2)}'::nummultirange;
+SELECT '{[1,3)}'::nummultirange * '{[2,5)}'::nummultirange;
+SELECT '{[1,4)}'::nummultirange * '{[2,3)}'::nummultirange;
+SELECT '{[1,4)}'::nummultirange * '{[0,2), [3,5)}'::nummultirange;
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[0,8), [9,12)}'::nummultirange;
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[9,12)}'::nummultirange;
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[-5,-4), [5,6), [9,12)}'::nummultirange;
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[0,2), [3,8), [9,12)}'::nummultirange;
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[0,2), [3,8), [9,12)}'::nummultirange;
+
+--
+-- range_agg function
+--
+create table reservations ( room_id integer not null, booked_during daterange );
+insert into reservations values
+-- 1: has a meets and a gap
+(1, daterange('2018-07-01', '2018-07-07')),
+(1, daterange('2018-07-07', '2018-07-14')),
+(1, daterange('2018-07-20', '2018-07-22')),
+-- 2: just a single row
+(2, daterange('2018-07-01', '2018-07-03')),
+-- 3: one null range
+(3, NULL),
+-- 4: two null ranges
+(4, NULL),
+(4, NULL),
+-- 5: a null range and a non-null range
+(5, NULL),
+(5, daterange('2018-07-01', '2018-07-03')),
+-- 6: has overlap
+(6, daterange('2018-07-01', '2018-07-07')),
+(6, daterange('2018-07-05', '2018-07-10')),
+-- 7: two ranges that meet: no gap or overlap
+(7, daterange('2018-07-01', '2018-07-07')),
+(7, daterange('2018-07-07', '2018-07-14')),
+-- 8: an empty range
+(8, 'empty'::daterange)
+;
+SELECT   room_id, range_agg(booked_during)
+FROM     reservations
+GROUP BY room_id
+ORDER BY room_id;
+
+-- range_agg on a custom range type too
+SELECT  range_agg(r)
+FROM    (VALUES
+          ('[a,c]'::textrange),
+          ('[b,b]'::textrange),
+          ('[c,f]'::textrange),
+          ('[g,h)'::textrange),
+          ('[h,j)'::textrange)
+        ) t(r);
+
+select range_intersect_agg(nmr) from nummultirange_test;
+select range_intersect_agg(nmr) from nummultirange_test where false;
+-- test with just one input:
+select range_intersect_agg(nmr) from (values ('{[1,2]}'::nummultirange)) t(nmr);
+select range_intersect_agg(nmr) from nummultirange_test where nmr @> 4.0;
+
+create table nummultirange_test2(nmr nummultirange);
+create index nummultirange_test2_hash_idx on nummultirange_test2 using hash (nmr);
+
+INSERT INTO nummultirange_test2 VALUES('{[, 5)}');
+INSERT INTO nummultirange_test2 VALUES(nummultirange(numrange(1.1, 2.2)));
+INSERT INTO nummultirange_test2 VALUES(nummultirange(numrange(1.1, 2.2)));
+INSERT INTO nummultirange_test2 VALUES(nummultirange(numrange(1.1, 2.2,'()')));
+INSERT INTO nummultirange_test2 VALUES('{}');
+
+select * from nummultirange_test2 where nmr = '{}';
+select * from nummultirange_test2 where nmr = nummultirange(numrange(1.1, 2.2));
+select * from nummultirange_test2 where nmr = nummultirange(numrange(1.1, 2.3));
+
+set enable_nestloop=t;
+set enable_hashjoin=f;
+set enable_mergejoin=f;
+select * from nummultirange_test natural join nummultirange_test2 order by nmr;
+set enable_nestloop=f;
+set enable_hashjoin=t;
+set enable_mergejoin=f;
+select * from nummultirange_test natural join nummultirange_test2 order by nmr;
+set enable_nestloop=f;
+set enable_hashjoin=f;
+set enable_mergejoin=t;
+select * from nummultirange_test natural join nummultirange_test2 order by nmr;
+
+set enable_nestloop to default;
+set enable_hashjoin to default;
+set enable_mergejoin to default;
+
+DROP TABLE nummultirange_test2;
+
+--
+-- Test user-defined multirange of floats
+--
+
+select '{[123.001, 5.e9)}'::float8multirange @> 888.882::float8;
+create table float8multirange_test(f8mr float8multirange, i int);
+insert into float8multirange_test values(float8multirange(float8range(-100.00007, '1.111113e9')), 42);
+select * from float8multirange_test;
+drop table float8multirange_test;
+
+--
+-- Test multirange types over domains
+--
+
+create domain mydomain as int4;
+create type mydomainrange as range(subtype=mydomain);
+select '{[4,50)}'::mydomainmultirange @> 7::mydomain;
+drop domain mydomain cascade;
+
+--
+-- Test domains over multirange types
+--
+
+create domain restrictedmultirange as int4multirange check (upper(value) < 10);
+select '{[4,5)}'::restrictedmultirange @> 7;
+select '{[4,50)}'::restrictedmultirange @> 7; -- should fail
+drop domain restrictedmultirange;
+
+--
+-- Test multiple multirange types over the same subtype
+--
+
+create type textrange1 as range(subtype=text, collation="C");
+create type textrange2 as range(subtype=text, collation="C");
+
+select textmultirange1(textrange2('a','Z'));  -- should fail
+select textmultirange1(textrange1('a','Z')) @> 'b'::text;
+select textmultirange2(textrange2('a','z')) @> 'b'::text;
+
+drop type textrange1;
+drop type textrange2;
+
+--
+-- Test polymorphic type system
+--
+
+create function anyarray_anymultirange_func(a anyarray, r anymultirange)
+  returns anyelement as 'select $1[1] + lower($2);' language sql;
+
+select anyarray_anymultirange_func(ARRAY[1,2], int4multirange(int4range(10,20)));
+
+-- should fail
+select anyarray_anymultirange_func(ARRAY[1,2], nummultirange(numrange(10,20)));
+
+drop function anyarray_anymultirange_func(anyarray, anymultirange);
+
+-- should fail
+create function bogus_func(anyelement)
+  returns anymultirange as 'select int4multirange(int4range(1,10))' language sql;
+
+-- should fail
+create function bogus_func(int)
+  returns anymultirange as 'select int4multirange(int4range(1,10))' language sql;
+
+create function range_add_bounds(anymultirange)
+  returns anyelement as 'select lower($1) + upper($1)' language sql;
+
+select range_add_bounds(int4multirange(int4range(1, 17)));
+select range_add_bounds(nummultirange(numrange(1.0001, 123.123)));
+
+create function multirangetypes_sql(q anymultirange, b anyarray, out c anyelement)
+  as $$ select upper($1) + $2[1] $$
+  language sql;
+
+select multirangetypes_sql(int4multirange(int4range(1,10)), ARRAY[2,20]);
+select multirangetypes_sql(nummultirange(numrange(1,10)), ARRAY[2,20]);  -- match failure
+
+--
+-- Arrays of multiranges
+--
+
+select ARRAY[nummultirange(numrange(1.1, 1.2)), nummultirange(numrange(12.3, 155.5))];
+
+create table i8mr_array (f1 int, f2 int8multirange[]);
+insert into i8mr_array values (42, array[int8multirange(int8range(1,10)), int8multirange(int8range(2,20))]);
+select * from i8mr_array;
+drop table i8mr_array;
+
+--
+-- Multiranges of arrays
+--
+
+select arraymultirange(arrayrange(ARRAY[1,2], ARRAY[2,1]));
+select arraymultirange(arrayrange(ARRAY[2,1], ARRAY[1,2]));  -- fail
+
+select array[1,1] <@ arraymultirange(arrayrange(array[1,2], array[2,1]));
+select array[1,3] <@ arraymultirange(arrayrange(array[1,2], array[2,1]));
+
+--
+-- Ranges of composites
+--
+
+create type two_ints as (a int, b int);
+create type two_ints_range as range (subtype = two_ints);
+
+-- with force_parallel_mode on, this exercises tqueue.c's range remapping
+select *, row_to_json(upper(t)) as u from
+  (values (two_ints_multirange(two_ints_range(row(1,2), row(3,4)))),
+          (two_ints_multirange(two_ints_range(row(5,6), row(7,8))))) v(t);
+
+drop type two_ints cascade;
+
+--
+-- Check behavior when subtype lacks a hash function
+--
+
+set enable_sort = off;  -- try to make it pick a hash setop implementation
+
+select '{(2,5)}'::cashmultirange except select '{(5,6)}'::cashmultirange;
+
+reset enable_sort;
+
+--
+-- OUT/INOUT/TABLE functions
+--
+
+-- infer anymultirange from anymultirange
+create function mr_outparam_succeed(i anymultirange, out r anymultirange, out t text)
+  as $$ select $1, 'foo'::text $$ language sql;
+
+select * from mr_outparam_succeed(int4multirange(int4range(1,2)));
+
+-- infer anyarray from anymultirange
+create function mr_outparam_succeed2(i anymultirange, out r anyarray, out t text)
+  as $$ select ARRAY[upper($1)], 'foo'::text $$ language sql;
+
+select * from mr_outparam_succeed2(int4multirange(int4range(1,2)));
+
+-- infer anyrange from anymultirange
+create function mr_outparam_succeed3(i anymultirange, out r anyrange, out t text)
+  as $$ select range_merge($1), 'foo'::text $$ language sql;
+select * from mr_outparam_succeed3(int4multirange(int4range(1,2)));
+
+-- infer anymultirange from anyrange
+create function mr_outparam_succeed4(i anyrange, out r anymultirange, out t text)
+  as $$ select multirange($1), 'foo'::text $$ language sql;
+
+select * from mr_outparam_succeed4(int4range(1,2));
+
+-- infer anyelement from anymultirange
+create function mr_inoutparam_succeed(out i anyelement, inout r anymultirange)
+  as $$ select upper($1), $1 $$ language sql;
+
+select * from mr_inoutparam_succeed(int4multirange(int4range(1,2)));
+
+-- infer anyelement+anymultirange from anyelement+anymultirange
+create function mr_table_succeed(i anyelement, r anymultirange) returns table(i anyelement, r anymultirange)
+  as $$ select $1, $2 $$ language sql;
+
+select * from mr_table_succeed(123, int4multirange(int4range(1,11)));
+
+-- use anymultirange in plpgsql
+create function mr_polymorphic(i anyrange) returns anymultirange
+  as $$ begin return multirange($1); end; $$ language plpgsql;
+select mr_polymorphic(int4range(1, 4));
+
+-- should fail
+create function mr_outparam_fail(i anyelement, out r anymultirange, out t text)
+  as $$ select '[1,10]', 'foo' $$ language sql;
+
+--should fail
+create function mr_inoutparam_fail(inout i anyelement, out r anymultirange)
+  as $$ select $1, '[1,10]' $$ language sql;
+
+--should fail
+create function mr_table_fail(i anyelement) returns table(i anyelement, r anymultirange)
+  as $$ select $1, '[1,10]' $$ language sql;
diff --git a/src/test/regress/sql/opr_sanity.sql b/src/test/regress/sql/opr_sanity.sql
index f06f245db3..4110a56ad2 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/rangetypes.sql b/src/test/regress/sql/rangetypes.sql
index 72d80bc9d4..07c9bcf6d3 100644
--- a/src/test/regress/sql/rangetypes.sql
+++ b/src/test/regress/sql/rangetypes.sql
@@ -118,6 +118,10 @@ select numrange(1.0, 2.0) * numrange(2.0, 3.0);
 select numrange(1.0, 2.0) * numrange(1.5, 3.0);
 select numrange(1.0, 2.0) * numrange(2.5, 3.0);
 
+select range_intersect_agg(nr) from numrange_test;
+select range_intersect_agg(nr) from numrange_test where false;
+select range_intersect_agg(nr) from numrange_test where nr @> 4.0;
+
 create table numrange_test2(nr numrange);
 create index numrange_test2_hash_idx on numrange_test2 using hash (nr);
 
@@ -512,16 +516,25 @@ reset enable_sort;
 -- OUT/INOUT/TABLE functions
 --
 
+-- infer anyrange from anyrange
 create function outparam_succeed(i anyrange, out r anyrange, out t text)
   as $$ select $1, 'foo'::text $$ language sql;
 
 select * from outparam_succeed(int4range(1,2));
 
+-- infer anyarray from anyrange
+create function outparam_succeed2(i anyrange, out r anyarray, out t text)
+  as $$ select ARRAY[upper($1)], 'foo'::text $$ language sql;
+
+select * from outparam_succeed2(int4range(int4range(1,2)));
+
+-- infer anyelement from anyrange
 create function inoutparam_succeed(out i anyelement, inout r anyrange)
   as $$ select upper($1), $1 $$ language sql;
 
 select * from inoutparam_succeed(int4range(1,2));
 
+-- infer anyelement+anyrange from anyelement+anyrange
 create function table_succeed(i anyelement, r anyrange) returns table(i anyelement, r anyrange)
   as $$ select $1, $2 $$ language sql;
 
diff --git a/src/test/regress/sql/type_sanity.sql b/src/test/regress/sql/type_sanity.sql
index fed413bf98..2e34bc8b65 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.rngmultitypid
+FROM pg_range p1
+WHERE p1.rngmultitypid IS NULL OR p1.rngmultitypid = 0;
-- 
2.20.1

v12-0002-match-a77315fdf2a197a925e670be2d8b376c4ac02efc.patchtext/x-diff; charset=us-asciiDownload
From 83f022a920132acbbd4f0ccfa690e7a2cd8baa66 Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Thu, 5 Mar 2020 12:04:46 -0300
Subject: [PATCH v12 2/5] match a77315fdf2a197a925e670be2d8b376c4ac02efc

---
 src/backend/utils/adt/multirangetypes.c | 61 ++++++++++++-------------
 src/backend/utils/adt/rangetypes.c      |  2 +
 src/include/utils/typcache.h            |  2 +-
 3 files changed, 31 insertions(+), 34 deletions(-)

diff --git a/src/backend/utils/adt/multirangetypes.c b/src/backend/utils/adt/multirangetypes.c
index 0c9afd5448..4d45aa581b 100644
--- a/src/backend/utils/adt/multirangetypes.c
+++ b/src/backend/utils/adt/multirangetypes.c
@@ -40,10 +40,9 @@
 typedef struct MultirangeIOData
 {
 	TypeCacheEntry *typcache;	/* multirange type's typcache entry */
-	Oid			typiofunc;		/* range type's I/O function */
+	FmgrInfo	typioproc;		/* range type's I/O proc */
 	Oid			typioparam;		/* range type's I/O parameter */
-	FmgrInfo	proc;			/* lookup result for typiofunc */
-}			MultirangeIOData;
+} MultirangeIOData;
 
 typedef enum
 {
@@ -54,10 +53,10 @@ typedef enum
 	MULTIRANGE_IN_RANGE_QUOTED_ESCAPED,
 	MULTIRANGE_AFTER_RANGE,
 	MULTIRANGE_FINISHED,
-}			MultirangeParseState;
+} MultirangeParseState;
 
-static MultirangeIOData * get_multirange_io_data(FunctionCallInfo fcinfo, Oid rngtypid,
-												 IOFuncSelector func);
+static MultirangeIOData *get_multirange_io_data(FunctionCallInfo fcinfo, Oid rngtypid,
+												IOFuncSelector func);
 static int32 multirange_canonicalize(TypeCacheEntry *rangetyp, int32 input_range_count,
 									 RangeType **ranges);
 
@@ -178,8 +177,10 @@ multirange_in(PG_FUNCTION_ARGS)
 							repalloc(ranges, range_capacity * sizeof(RangeType *));
 					}
 					ranges_seen++;
-					range = DatumGetRangeTypeP(InputFunctionCall(&cache->proc, range_str_copy,
-																 cache->typioparam, typmod));
+					range = DatumGetRangeTypeP(InputFunctionCall(&cache->typioproc,
+																 range_str_copy,
+																 cache->typioparam,
+																 typmod));
 					if (!RangeIsEmpty(range))
 						ranges[range_count++] = range;
 					parse_state = MULTIRANGE_AFTER_RANGE;
@@ -266,7 +267,7 @@ multirange_out(PG_FUNCTION_ARGS)
 		if (range_count > 0)
 			appendStringInfoChar(&buf, ',');
 		range = (RangeType *) ptr;
-		rangeStr = OutputFunctionCall(&cache->proc, RangeTypePGetDatum(range));
+		rangeStr = OutputFunctionCall(&cache->typioproc, RangeTypePGetDatum(range));
 		appendStringInfoString(&buf, rangeStr);
 		ptr += MAXALIGN(VARSIZE(range));
 		range_count++;
@@ -311,8 +312,7 @@ multirange_recv(PG_FUNCTION_ARGS)
 		initStringInfo(&range_buf);
 		appendBinaryStringInfo(&range_buf, range_data, range_len);
 
-		ranges[i] = DatumGetRangeTypeP(
-									   ReceiveFunctionCall(&cache->proc,
+		ranges[i] = DatumGetRangeTypeP(ReceiveFunctionCall(&cache->typioproc,
 														   &range_buf,
 														   cache->typioparam,
 														   typmod));
@@ -346,16 +346,13 @@ multirange_send(PG_FUNCTION_ARGS)
 	multirange_deserialize(multirange, &range_count, &ranges);
 	for (i = 0; i < range_count; i++)
 	{
-		Datum		range = RangeTypePGetDatum(ranges[i]);
-		uint32		range_len;
-		char	   *range_data;
+		Datum		range;
 
-		range = PointerGetDatum(SendFunctionCall(&cache->proc, range));
-		range_len = VARSIZE(range) - VARHDRSZ;
-		range_data = VARDATA(range);
+		range = RangeTypePGetDatum(ranges[i]);
+		range = PointerGetDatum(SendFunctionCall(&cache->typioproc, range));
 
-		pq_sendint32(buf, range_len);
-		pq_sendbytes(buf, range_data, range_len);
+		pq_sendint32(buf, VARSIZE(range) - VARHDRSZ);
+		pq_sendbytes(buf, VARDATA(range), VARSIZE(range) - VARHDRSZ);
 	}
 
 	PG_RETURN_BYTEA_P(pq_endtypsend(buf));
@@ -381,6 +378,7 @@ get_multirange_io_data(FunctionCallInfo fcinfo, Oid rngtypid, IOFuncSelector fun
 
 	if (cache == NULL || cache->typcache->type_id != rngtypid)
 	{
+		Oid			typiofunc;
 		int16		typlen;
 		bool		typbyval;
 		char		typalign;
@@ -400,9 +398,9 @@ get_multirange_io_data(FunctionCallInfo fcinfo, Oid rngtypid, IOFuncSelector fun
 						 &typalign,
 						 &typdelim,
 						 &cache->typioparam,
-						 &cache->typiofunc);
+						 &typiofunc);
 
-		if (!OidIsValid(cache->typiofunc))
+		if (!OidIsValid(typiofunc))
 		{
 			/* this could only happen for receive or send */
 			if (func == IOFunc_receive)
@@ -416,7 +414,7 @@ get_multirange_io_data(FunctionCallInfo fcinfo, Oid rngtypid, IOFuncSelector fun
 						 errmsg("no binary output function available for type %s",
 								format_type_be(cache->typcache->rngtype->type_id))));
 		}
-		fmgr_info_cxt(cache->typiofunc, &cache->proc,
+		fmgr_info_cxt(typiofunc, &cache->typioproc,
 					  fcinfo->flinfo->fn_mcxt);
 
 		fcinfo->flinfo->fn_extra = (void *) cache;
@@ -473,7 +471,6 @@ make_multirange(Oid mltrngtypoid, TypeCacheEntry *rangetyp, int32 range_count,
 				RangeType **ranges)
 {
 	MultirangeType *multirange;
-	RangeType  *range;
 	int			i;
 	int32		bytelen;
 	Pointer		ptr;
@@ -489,10 +486,7 @@ make_multirange(Oid mltrngtypoid, TypeCacheEntry *rangetyp, int32 range_count,
 
 	/* Count space for all ranges */
 	for (i = 0; i < range_count; i++)
-	{
-		range = ranges[i];
-		bytelen += MAXALIGN(VARSIZE(range));
-	}
+		bytelen += MAXALIGN(VARSIZE(ranges[i]));
 
 	/* Note: zero-fill is required here, just as in heap tuples */
 	multirange = palloc0(bytelen);
@@ -505,19 +499,20 @@ make_multirange(Oid mltrngtypoid, TypeCacheEntry *rangetyp, int32 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));
+		memcpy(ptr, ranges[i], VARSIZE(ranges[i]));
+		ptr += MAXALIGN(VARSIZE(ranges[i]));
 	}
 
 	return multirange;
 }
 
 /*
- * Converts a list of any ranges you like into a list that is sorted and merged.
+ * Converts a list of arbitrary ranges 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.
+ *
+ * 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
diff --git a/src/backend/utils/adt/rangetypes.c b/src/backend/utils/adt/rangetypes.c
index 9d1ca13e32..7077bb0309 100644
--- a/src/backend/utils/adt/rangetypes.c
+++ b/src/backend/utils/adt/rangetypes.c
@@ -2100,6 +2100,8 @@ range_cmp_bound_values(TypeCacheEntry *typcache, const RangeBound *b1,
 }
 
 /*
+ * qsort callback for sorting ranges.
+ *
  * Compares two ranges so we can qsort them.
  * This expects that you give qsort a RangeType **,
  * so the RangeTypes can be in diverse locations,
diff --git a/src/include/utils/typcache.h b/src/include/utils/typcache.h
index f40a462b22..33f332a141 100644
--- a/src/include/utils/typcache.h
+++ b/src/include/utils/typcache.h
@@ -103,7 +103,7 @@ typedef struct TypeCacheEntry
 	/*
 	 * Fields computed when TYPCACHE_MULTIRANGE_INFO is required.
 	 */
-	struct TypeCacheEntry *rngtype;		/* underlying range type */
+	struct TypeCacheEntry *rngtype;	/* multirange's range underlying type */
 
 	/*
 	 * Domain's base type and typmod if it's a domain type.  Zeroes if not
-- 
2.20.1

v12-0003-whitespace-varlena-struct-change-comment-on-qsor.patchtext/x-diff; charset=us-asciiDownload
From dfc480de3c3e16219bc9f177d1b0edbedb90fc91 Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Fri, 6 Mar 2020 19:03:25 -0300
Subject: [PATCH v12 3/5] whitespace, varlena struct change, comment on qsort
 cb

---
 src/backend/utils/adt/multirangetypes.c | 11 ++++++++---
 src/backend/utils/adt/rangetypes.c      |  7 +++----
 src/include/utils/multirangetypes.h     |  2 +-
 3 files changed, 12 insertions(+), 8 deletions(-)

diff --git a/src/backend/utils/adt/multirangetypes.c b/src/backend/utils/adt/multirangetypes.c
index 4d45aa581b..a7bc2336b2 100644
--- a/src/backend/utils/adt/multirangetypes.c
+++ b/src/backend/utils/adt/multirangetypes.c
@@ -1605,7 +1605,8 @@ multirange_overleft_range(PG_FUNCTION_ARGS)
 
 	multirange_deserialize(mr, &range_count, &ranges);
 
-	PG_RETURN_BOOL(range_overleft_internal(typcache->rngtype, ranges[range_count - 1], r));
+	PG_RETURN_BOOL(range_overleft_internal(typcache->rngtype,
+										   ranges[range_count - 1], r));
 }
 
 Datum
@@ -1627,7 +1628,9 @@ multirange_overleft_multirange(PG_FUNCTION_ARGS)
 	multirange_deserialize(mr1, &range_count1, &ranges1);
 	multirange_deserialize(mr2, &range_count2, &ranges2);
 
-	PG_RETURN_BOOL(range_overleft_internal(typcache->rngtype, ranges1[range_count1 - 1], ranges2[range_count2 - 1]));
+	PG_RETURN_BOOL(range_overleft_internal(typcache->rngtype,
+										   ranges1[range_count1 - 1],
+										   ranges2[range_count2 - 1]));
 }
 
 /* does not extend to left of? */
@@ -1688,7 +1691,9 @@ multirange_overright_multirange(PG_FUNCTION_ARGS)
 	multirange_deserialize(mr1, &range_count1, &ranges1);
 	multirange_deserialize(mr2, &range_count2, &ranges2);
 
-	PG_RETURN_BOOL(range_overright_internal(typcache->rngtype, ranges1[0], ranges2[0]));
+	PG_RETURN_BOOL(range_overright_internal(typcache->rngtype,
+											ranges1[0],
+											ranges2[0]));
 }
 
 /* contains? */
diff --git a/src/backend/utils/adt/rangetypes.c b/src/backend/utils/adt/rangetypes.c
index 7077bb0309..0af3c67296 100644
--- a/src/backend/utils/adt/rangetypes.c
+++ b/src/backend/utils/adt/rangetypes.c
@@ -2102,10 +2102,9 @@ range_cmp_bound_values(TypeCacheEntry *typcache, const RangeBound *b1,
 /*
  * qsort callback for sorting ranges.
  *
- * 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.
+ * Two empty ranges compare equal; an empty range sorts to the left of any
+ * non-empty range.  Two non-empty ranges are sorted by lower bound first
+ * and by upper bound next.
  */
 int
 range_compare(const void *key1, const void *key2, void *arg)
diff --git a/src/include/utils/multirangetypes.h b/src/include/utils/multirangetypes.h
index 8779bd1f6e..98a8878ca0 100644
--- a/src/include/utils/multirangetypes.h
+++ b/src/include/utils/multirangetypes.h
@@ -24,7 +24,7 @@
  */
 typedef struct
 {
-	char		vl_len_[4];		/* varlena header (do not touch directly!) */
+	int32		vl_len_;		/* varlena header (do not touch directly!) */
 	Oid			multirangetypid;	/* multirange type's own OID */
 	uint32		rangeCount;		/* the number of ranges */
 
-- 
2.20.1

v12-0004-Fix-ereport-elog-calls.patchtext/x-diff; charset=us-asciiDownload
From 59d7e4c2a677225d4d609e8016ba316166fd291c Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Mon, 9 Mar 2020 16:22:46 -0300
Subject: [PATCH v12 4/5] Fix ereport/elog calls

---
 src/backend/utils/adt/multirangetypes.c | 37 ++++++++++++++++++-------
 1 file changed, 27 insertions(+), 10 deletions(-)

diff --git a/src/backend/utils/adt/multirangetypes.c b/src/backend/utils/adt/multirangetypes.c
index a7bc2336b2..e58ab5baf3 100644
--- a/src/backend/utils/adt/multirangetypes.c
+++ b/src/backend/utils/adt/multirangetypes.c
@@ -223,7 +223,7 @@ multirange_in(PG_FUNCTION_ARGS)
 				parse_state = MULTIRANGE_IN_RANGE_QUOTED;
 				break;
 			default:
-				elog(ERROR, "Unknown parse state: %d", parse_state);
+				elog(ERROR, "unknown parse state: %d", parse_state);
 		}
 	}
 
@@ -656,17 +656,23 @@ multirange_constructor2(PG_FUNCTION_ARGS)
 	 */
 
 	if (PG_ARGISNULL(0))
-		ereport(ERROR, (errmsg("Can't construct multirange with a NULL input")));
+		ereport(ERROR,
+				(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+				 errmsg("multirange values cannot contain NULL members")));
 
 	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")));
+		ereport(ERROR,
+				(errcode(ERRCODE_CARDINALITY_VIOLATION),
+				 errmsg("multiranges cannot be constructed from multi-dimensional arrays")));
 
 	rngtypid = ARR_ELEMTYPE(rangeArray);
 	if (rngtypid != rangetyp->type_id)
-		ereport(ERROR, (errmsg("type %u does not match constructor type", rngtypid)));
+		ereport(ERROR,
+				(errcode(ERRCODE_DATATYPE_MISMATCH),
+				 errmsg("type %u does not match constructor type", rngtypid)));
 
 	/*
 	 * Be careful: we can still be called with zero ranges, like this:
@@ -686,7 +692,9 @@ multirange_constructor2(PG_FUNCTION_ARGS)
 		for (i = 0; i < range_count; i++)
 		{
 			if (nulls[i])
-				ereport(ERROR, (errmsg("Can't construct multirange with a NULL element")));
+				ereport(ERROR,
+						(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+						 errmsg("multirange values cannot contain NULL members")));
 
 			/* make_multirange will do its own copy */
 			ranges[i] = DatumGetRangeTypeP(elements[i]);
@@ -720,14 +728,18 @@ multirange_constructor1(PG_FUNCTION_ARGS)
 	 */
 
 	if (PG_ARGISNULL(0))
-		ereport(ERROR, (errmsg("Can't construct multirange with a NULL input")));
+				ereport(ERROR,
+						(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+						 errmsg("multirange values cannot contain NULL members")));
 
 	range = PG_GETARG_RANGE_P(0);
 
 	/* Make sure the range type matches. */
 	rngtypid = RangeTypeGetOid(range);
 	if (rngtypid != rangetyp->type_id)
-		ereport(ERROR, (errmsg("type %u does not match constructor type", rngtypid)));
+		ereport(ERROR,
+				(errcode(ERRCODE_DATATYPE_MISMATCH),
+				 errmsg("type %u does not match constructor type", rngtypid)));
 
 	PG_RETURN_MULTIRANGE_P(make_multirange(mltrngtypid, rangetyp, 1, &range));
 }
@@ -752,7 +764,8 @@ multirange_constructor0(PG_FUNCTION_ARGS)
 	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")));
+		elog(ERROR,		/* can't happen */
+			 "niladic multirange constructor must not receive arguments");
 }
 
 
@@ -1031,7 +1044,9 @@ range_agg_transfn(PG_FUNCTION_ARGS)
 
 	rngtypoid = get_fn_expr_argtype(fcinfo->flinfo, 1);
 	if (!type_is_range(rngtypoid))
-		ereport(ERROR, (errmsg("range_agg must be called with a range")));
+		ereport(ERROR,
+				(errcode(ERRCODE_DATATYPE_MISMATCH),
+				 errmsg("range_agg must be called with a range")));
 
 	if (PG_ARGISNULL(0))
 		state = initArrayResult(rngtypoid, aggContext, false);
@@ -1100,7 +1115,9 @@ multirange_intersect_agg_transfn(PG_FUNCTION_ARGS)
 
 	mltrngtypoid = get_fn_expr_argtype(fcinfo->flinfo, 1);
 	if (!type_is_multirange(mltrngtypoid))
-		ereport(ERROR, (errmsg("range_intersect_agg must be called with a multirange")));
+		ereport(ERROR,
+				(errcode(ERRCODE_DATATYPE_MISMATCH),
+				 errmsg("range_intersect_agg must be called with a multirange")));
 
 	typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
 
-- 
2.20.1

v12-0005-pgindent-stuff.patchtext/x-diff; charset=us-asciiDownload
From 52ed3748aaedb8ae0689d32e07be21791f1f9e7a Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Mon, 9 Mar 2020 16:23:20 -0300
Subject: [PATCH v12 5/5] pgindent stuff

---
 src/backend/utils/adt/multirangetypes.c | 32 ++++++-------
 src/include/utils/multirangetypes.h     | 64 ++++++++++++-------------
 src/tools/pgindent/typedefs.list        |  3 ++
 3 files changed, 51 insertions(+), 48 deletions(-)

diff --git a/src/backend/utils/adt/multirangetypes.c b/src/backend/utils/adt/multirangetypes.c
index e58ab5baf3..5542265819 100644
--- a/src/backend/utils/adt/multirangetypes.c
+++ b/src/backend/utils/adt/multirangetypes.c
@@ -576,7 +576,7 @@ multirange_canonicalize(TypeCacheEntry *rangetyp, int32 input_range_count,
  * short varlena header.
  */
 void
-multirange_deserialize(MultirangeType * multirange,
+multirange_deserialize(MultirangeType *multirange,
 					   int32 *range_count, RangeType ***ranges)
 {
 	RangeType  *r;
@@ -717,7 +717,7 @@ multirange_constructor1(PG_FUNCTION_ARGS)
 	Oid			rngtypid;
 	TypeCacheEntry *typcache;
 	TypeCacheEntry *rangetyp;
-	RangeType *range;
+	RangeType  *range;
 
 	typcache = multirange_get_typcache(fcinfo, mltrngtypid);
 	rangetyp = typcache->rngtype;
@@ -836,8 +836,8 @@ multirange_minus(PG_FUNCTION_ARGS)
 
 MultirangeType *
 multirange_minus_internal(Oid mltrngtypoid, TypeCacheEntry *rangetyp,
-						 int32 range_count1, RangeType **ranges1,
-						 int32 range_count2, RangeType **ranges2)
+						  int32 range_count1, RangeType **ranges1,
+						  int32 range_count2, RangeType **ranges2)
 {
 	RangeType  *r1;
 	RangeType  *r2;
@@ -1302,7 +1302,7 @@ elem_contained_by_multirange(PG_FUNCTION_ARGS)
  * Test whether multirange mr contains a specific element value.
  */
 bool
-multirange_contains_elem_internal(TypeCacheEntry *typcache, MultirangeType * mr, Datum val)
+multirange_contains_elem_internal(TypeCacheEntry *typcache, MultirangeType *mr, Datum val)
 {
 	TypeCacheEntry *rangetyp;
 	int32		range_count;
@@ -1356,7 +1356,7 @@ range_contained_by_multirange(PG_FUNCTION_ARGS)
  * Test whether multirange mr contains a specific range r.
  */
 bool
-multirange_contains_range_internal(TypeCacheEntry *typcache, MultirangeType * mr, RangeType *r)
+multirange_contains_range_internal(TypeCacheEntry *typcache, MultirangeType *mr, RangeType *r)
 {
 	TypeCacheEntry *rangetyp;
 	int32		range_count;
@@ -1390,7 +1390,7 @@ multirange_contains_range_internal(TypeCacheEntry *typcache, MultirangeType * mr
 
 /* equality (internal version) */
 bool
-multirange_eq_internal(TypeCacheEntry *typcache, MultirangeType * mr1, MultirangeType * mr2)
+multirange_eq_internal(TypeCacheEntry *typcache, MultirangeType *mr1, MultirangeType *mr2)
 {
 	int32		range_count_1;
 	int32		range_count_2;
@@ -1437,7 +1437,7 @@ multirange_eq(PG_FUNCTION_ARGS)
 
 /* inequality (internal version) */
 bool
-multirange_ne_internal(TypeCacheEntry *typcache, MultirangeType * mr1, MultirangeType * mr2)
+multirange_ne_internal(TypeCacheEntry *typcache, MultirangeType *mr1, MultirangeType *mr2)
 {
 	return (!multirange_eq_internal(typcache, mr1, mr2));
 }
@@ -1493,7 +1493,7 @@ multirange_overlaps_multirange(PG_FUNCTION_ARGS)
 }
 
 bool
-range_overlaps_multirange_internal(TypeCacheEntry *typcache, RangeType *r, MultirangeType * mr)
+range_overlaps_multirange_internal(TypeCacheEntry *typcache, RangeType *r, MultirangeType *mr)
 {
 	TypeCacheEntry *rangetyp;
 	int32		range_count;
@@ -1529,8 +1529,8 @@ range_overlaps_multirange_internal(TypeCacheEntry *typcache, RangeType *r, Multi
 }
 
 bool
-multirange_overlaps_multirange_internal(TypeCacheEntry *typcache, MultirangeType * mr1,
-										MultirangeType * mr2)
+multirange_overlaps_multirange_internal(TypeCacheEntry *typcache, MultirangeType *mr1,
+										MultirangeType *mr2)
 {
 	TypeCacheEntry *rangetyp;
 	int32		range_count1;
@@ -1744,7 +1744,7 @@ multirange_contained_by_multirange(PG_FUNCTION_ARGS)
  */
 bool
 multirange_contains_multirange_internal(TypeCacheEntry *typcache,
-										MultirangeType * mr1, MultirangeType * mr2)
+										MultirangeType *mr1, MultirangeType *mr2)
 {
 	TypeCacheEntry *rangetyp;
 	int32		range_count1;
@@ -1883,7 +1883,7 @@ multirange_after_multirange(PG_FUNCTION_ARGS)
 /* strictly left of? (internal version) */
 bool
 range_before_multirange_internal(TypeCacheEntry *typcache, RangeType *r,
-								 MultirangeType * mr)
+								 MultirangeType *mr)
 {
 	int32		range_count;
 	RangeType **ranges;
@@ -1897,8 +1897,8 @@ range_before_multirange_internal(TypeCacheEntry *typcache, RangeType *r,
 }
 
 bool
-multirange_before_multirange_internal(TypeCacheEntry *typcache, MultirangeType * mr1,
-									  MultirangeType * mr2)
+multirange_before_multirange_internal(TypeCacheEntry *typcache, MultirangeType *mr1,
+									  MultirangeType *mr2)
 {
 	int32		range_count1;
 	int32		range_count2;
@@ -1918,7 +1918,7 @@ multirange_before_multirange_internal(TypeCacheEntry *typcache, MultirangeType *
 /* strictly right of? (internal version) */
 bool
 range_after_multirange_internal(TypeCacheEntry *typcache, RangeType *r,
-								MultirangeType * mr)
+								MultirangeType *mr)
 {
 	int32		range_count;
 	RangeType **ranges;
diff --git a/src/include/utils/multirangetypes.h b/src/include/utils/multirangetypes.h
index 98a8878ca0..469444776c 100644
--- a/src/include/utils/multirangetypes.h
+++ b/src/include/utils/multirangetypes.h
@@ -34,7 +34,7 @@ typedef struct
 	 * because even their base types can be varlena. So we can't really index
 	 * into this list.
 	 */
-}			MultirangeType;
+} MultirangeType;
 
 /* Use this macro in preference to fetching multirangetypid field directly */
 #define MultirangeTypeGetOid(mr)	((mr)->multirangetypid)
@@ -55,49 +55,49 @@ typedef struct
  */
 
 /* 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);
-extern bool multirange_contains_elem_internal(TypeCacheEntry *typcache, MultirangeType * mr,
+extern bool multirange_eq_internal(TypeCacheEntry *typcache, MultirangeType *mr1,
+								   MultirangeType *mr2);
+extern bool multirange_ne_internal(TypeCacheEntry *typcache, MultirangeType *mr1,
+								   MultirangeType *mr2);
+extern bool multirange_contains_elem_internal(TypeCacheEntry *typcache, MultirangeType *mr,
 											  Datum elem);
-extern bool multirange_contains_range_internal(TypeCacheEntry *typcache, MultirangeType * mr,
+extern bool multirange_contains_range_internal(TypeCacheEntry *typcache, MultirangeType *mr,
 											   RangeType *r);
 extern bool multirange_contains_multirange_internal(TypeCacheEntry *typcache,
-													MultirangeType * mr1,
-													MultirangeType * mr2);
+													MultirangeType *mr1,
+													MultirangeType *mr2);
 extern bool range_overlaps_multirange_internal(TypeCacheEntry *typcache, RangeType *r,
-											   MultirangeType * mr);
+											   MultirangeType *mr);
 extern bool multirange_overlaps_multirange_internal(TypeCacheEntry *typcache,
-													MultirangeType * mr1,
-													MultirangeType * mr2);
+													MultirangeType *mr1,
+													MultirangeType *mr2);
 extern bool range_before_multirange_internal(TypeCacheEntry *typcache, RangeType *r,
-											 MultirangeType * mr);
+											 MultirangeType *mr);
 extern bool range_after_multirange_internal(TypeCacheEntry *typcache, RangeType *r,
-											MultirangeType * mr);
+											MultirangeType *mr);
 extern bool multirange_before_multirange_internal(TypeCacheEntry *typcache,
-												  MultirangeType * mr1,
-												  MultirangeType * mr2);
-extern MultirangeType * multirange_minus_internal(Oid mltrngtypoid,
-												  TypeCacheEntry *rangetyp,
-												  int32 range_count1,
-												  RangeType **ranges1,
-												  int32 range_count2,
-												  RangeType **ranges2);
-extern MultirangeType * multirange_intersect_internal(Oid mltrngtypoid,
-													  TypeCacheEntry *rangetyp,
-													  int32 range_count1,
-													  RangeType **ranges1,
-													  int32 range_count2,
-													  RangeType **ranges2);
+												  MultirangeType *mr1,
+												  MultirangeType *mr2);
+extern MultirangeType *multirange_minus_internal(Oid mltrngtypoid,
+												 TypeCacheEntry *rangetyp,
+												 int32 range_count1,
+												 RangeType **ranges1,
+												 int32 range_count2,
+												 RangeType **ranges2);
+extern MultirangeType *multirange_intersect_internal(Oid mltrngtypoid,
+													 TypeCacheEntry *rangetyp,
+													 int32 range_count1,
+													 RangeType **ranges1,
+													 int32 range_count2,
+													 RangeType **ranges2);
 
 /* assorted support functions */
 extern TypeCacheEntry *multirange_get_typcache(FunctionCallInfo fcinfo,
 											   Oid mltrngtypid);
-extern void multirange_deserialize(MultirangeType * range,
+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);
+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/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index e216de9570..c23ce28af1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1354,6 +1354,9 @@ MultiXactMember
 MultiXactOffset
 MultiXactStateData
 MultiXactStatus
+MultirangeIOData
+MultirangeParseState
+MultirangeType
 MyData
 NDBOX
 NODE
-- 
2.20.1

#109Paul A Jungwirth
pj@illuminatedcomputing.com
In reply to: Alvaro Herrera (#108)
Re: range_agg

On Fri, Mar 13, 2020 at 2:39 PM Alvaro Herrera <alvherre@2ndquadrant.com> wrote:

Here's the rebased version.

I just realized I didn't include the API change I proposed in
/messages/by-id/20200306200343.GA625@alvherre.pgsql ...

Thanks for your help with this Alvaro!

I was just adding your changes to my own branch and I noticed your
v12-0001 has different parameter names here:

diff --git a/src/backend/utils/adt/multirangetypes.c
b/src/backend/utils/adt/multirangetypes.c
index f9dd0378cc..0c9afd5448 100644
--- a/src/backend/utils/adt/multirangetypes.c
+++ b/src/backend/utils/adt/multirangetypes.c
@@ -376,11 +375,11 @@ multirange_typanalyze(PG_FUNCTION_ARGS)
  * pointer to a type cache entry.
  */
 static MultirangeIOData *
-get_multirange_io_data(FunctionCallInfo fcinfo, Oid mltrngtypid,
IOFuncSelector func)
+get_multirange_io_data(FunctionCallInfo fcinfo, Oid rngtypid,
IOFuncSelector func)
 {
     MultirangeIOData *cache = (MultirangeIOData *) fcinfo->flinfo->fn_extra;
-    if (cache == NULL || cache->typcache->type_id != mltrngtypid)
+    if (cache == NULL || cache->typcache->type_id != rngtypid)
     {
         int16       typlen;
         bool        typbyval;
@@ -389,9 +388,9 @@ get_multirange_io_data(FunctionCallInfo fcinfo,
Oid mltrngtypid, IOFuncSelector

cache = (MultirangeIOData *)
MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,

sizeof(MultirangeIOData));
-        cache->typcache = lookup_type_cache(mltrngtypid,
TYPECACHE_MULTIRANGE_INFO);
+        cache->typcache = lookup_type_cache(rngtypid,
TYPECACHE_MULTIRANGE_INFO);
         if (cache->typcache->rngtype == NULL)
-            elog(ERROR, "type %u is not a multirange type", mltrngtypid);
+            elog(ERROR, "type %u is not a multirange type", rngtypid);

/* get_type_io_data does more than we need, but is convenient */
get_type_io_data(cache->typcache->rngtype->type_id,

I'm pretty sure mltrngtypid is the correct name here. Right? Let me
know if I'm missing something. :-)

Yours,
Paul

#110Paul A Jungwirth
pj@illuminatedcomputing.com
In reply to: Tom Lane (#107)
1 attachment(s)
Re: range_agg

On Fri, Mar 13, 2020 at 10:06 AM Tom Lane <tgl@sss.pgh.pa.us> wrote:

Paul A Jungwirth <pj@illuminatedcomputing.com> writes:

On Wed, Mar 11, 2020 at 4:39 PM Paul A Jungwirth
<pj@illuminatedcomputing.com> wrote:

Oh, my last email left out the most important part. :-) Is this
failure online somewhere so I can take a look at it and fix it?

Look for your patch(es) at

http://commitfest.cputube.org

Right now it's not even applying, presumably because Alvaro already
pushed some pieces, so you need to rebase. But when it was applying,
one or both of the test builds was failing.

Here are all Alvaro's changes rolled into one patch, along with the
get_multirange_io_data parameter renamed to mltrngtypid, and this
small fix to the dependency regression test:
diff --git a/src/test/regress/expected/dependency.out
b/src/test/regress/expected/dependency.out
index 778699a961..8232795148 100644
--- a/src/test/regress/expected/dependency.out
+++ b/src/test/regress/expected/dependency.out
@@ -140,8 +140,8 @@ owner of sequence deptest_a_seq
 owner of table deptest
 owner of function deptest_func()
 owner of type deptest_enum
-owner of type deptest_range
 owner of type deptest_multirange
+owner of type deptest_range
 owner of table deptest2
 owner of sequence ss1
 owner of type deptest_t

I think that should fix the cfbot failure.

Yours,
Paul

Attachments:

v13-multiranges.patchapplication/octet-stream; name=v13-multiranges.patchDownload
diff --git a/doc/src/sgml/extend.sgml b/doc/src/sgml/extend.sgml
index 9ec1af780b..92a1a254ac 100644
--- a/doc/src/sgml/extend.sgml
+++ b/doc/src/sgml/extend.sgml
@@ -229,9 +229,9 @@
    </indexterm>
 
     <para>
-     Five pseudo-types of special interest are <type>anyelement</type>,
+     Six pseudo-types of special interest are <type>anyelement</type>,
      <type>anyarray</type>, <type>anynonarray</type>, <type>anyenum</type>,
-     and <type>anyrange</type>,
+     <type>anyrange</type>, and <type>anymultirange</type>.
      which are collectively called <firstterm>polymorphic types</firstterm>.
      Any function declared using these types is said to be
      a <firstterm>polymorphic function</firstterm>.  A polymorphic function can
@@ -250,15 +250,15 @@
      position declared as <type>anyarray</type> can have any array data type,
      but similarly they must all be the same type.  And similarly,
      positions declared as <type>anyrange</type> must all be the same range
-     type.  Furthermore, if there are
+     type.  Likewise for <type>anymultirange</type>.
+    </para>
+
+    <para>
+     Furthermore, if there are
      positions declared <type>anyarray</type> and others declared
      <type>anyelement</type>, the actual array type in the
      <type>anyarray</type> positions must be an array whose elements are
      the same type appearing in the <type>anyelement</type> positions.
-     Similarly, if there are positions declared <type>anyrange</type>
-     and others declared <type>anyelement</type>, the actual range type in
-     the <type>anyrange</type> positions must be a range whose subtype is
-     the same type appearing in the <type>anyelement</type> positions.
      <type>anynonarray</type> is treated exactly the same as <type>anyelement</type>,
      but adds the additional constraint that the actual type must not be
      an array type.
@@ -268,6 +268,20 @@
     </para>
 
     <para>
+     Similarly, if there are positions declared <type>anyrange</type>
+     and others declared <type>anyelement</type>, the actual range type in
+     the <type>anyrange</type> positions must be a range whose subtype is
+     the same type appearing in the <type>anyelement</type> positions.
+     The type <type>anymultirange</type> accepts a multirange
+     whose contents match the other polymorphic types.
+     That is it must hold ranges matching <type>anyrange</type>
+     and base type elements matching <type>anyelement</type> (if either of
+     those are present). If <type>anyarray</type> is present its elements
+     must match the base type of the <type>anyrange</type> and/or the base
+     type of the ranges in an <type>anymultirange</type>.
+    </para>
+
+    <para>
      Thus, when more than one argument position is declared with a polymorphic
      type, the net effect is that only certain combinations of actual argument
      types are allowed.  For example, a function declared as
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 323366feb6..cfe2c23e65 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -13546,7 +13546,7 @@ SELECT NULLIF(value, '(none)') ...
        <row>
         <entry>Operator</entry>
         <entry>Description</entry>
-        <entry>Example</entry>
+        <entry>Examples</entry>
         <entry>Result</entry>
        </row>
       </thead>
@@ -14017,12 +14017,14 @@ NULL baz</literallayout>(3 rows)</entry>
   <title>Range Functions and Operators</title>
 
   <para>
-   See <xref linkend="rangetypes"/> for an overview of range types.
+   See <xref linkend="rangetypes"/> for an overview of range and multirange types.
   </para>
 
   <para>
    <xref linkend="range-operators-table"/> shows the operators
-   available for range types.
+   available for range and multirange types.
+   Many of these operators will accept either a range or multirange
+   on either side.
   </para>
 
     <table id="range-operators-table">
@@ -14040,134 +14042,215 @@ NULL baz</literallayout>(3 rows)</entry>
        <row>
         <entry> <literal>=</literal> </entry>
         <entry>equal</entry>
-        <entry><literal>int4range(1,5) = '[1,4]'::int4range</literal></entry>
-        <entry><literal>t</literal></entry>
+        <entry><literallayout class="monospaced">int4range(1,5) = '[1,4]'::int4range
+'{[1,5)}'::int4multirange = '{[1,4]}'::int4multirange</literallayout></entry>
+        <entry><literallayout class="monospaced">t</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>&lt;&gt;</literal> </entry>
         <entry>not equal</entry>
-        <entry><literal>numrange(1.1,2.2) &lt;&gt; numrange(1.1,2.3)</literal></entry>
-        <entry><literal>t</literal></entry>
+        <entry><literallayout class="monospaced">numrange(1.1,2.2) &lt;&gt; numrange(1.1,2.3)
+'{[1.1,2.2)}'::nummultirange &lt;&gt; '{[1.1,2.3)}'::nummultirange</literallayout></entry>
+        <entry><literallayout class="monospaced">t</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>&lt;</literal> </entry>
         <entry>less than</entry>
-        <entry><literal>int4range(1,10) &lt; int4range(2,3)</literal></entry>
-        <entry><literal>t</literal></entry>
+        <entry><literallayout class="monospaced">int4range(1,10) &lt; int4range(2,3)
+'{[1,10)}'::int4multirange &lt; '{[2,3)}'::int4multirange</literallayout>
+        </entry>
+        <entry><literallayout class="monospaced">t</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>&gt;</literal> </entry>
         <entry>greater than</entry>
-        <entry><literal>int4range(1,10) &gt; int4range(1,5)</literal></entry>
-        <entry><literal>t</literal></entry>
+        <entry>
+          <literallayout class="monospaced">int4range(1,10) &gt; int4range(1,5)
+'{[1,10)}'::int4multirange &gt; '{[1,5)}'::int4multirange</literallayout>
+        </entry>
+        <entry><literallayout class="monospaced">t</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>&lt;=</literal> </entry>
         <entry>less than or equal</entry>
-        <entry><literal>numrange(1.1,2.2) &lt;= numrange(1.1,2.2)</literal></entry>
-        <entry><literal>t</literal></entry>
+        <entry>
+          <literallayout class="monospaced">numrange(1.1,2.2) &lt;= numrange(1.1,2.2)
+'{[1.1,2.2)}'::nummultirange &lt;= '{[1.1,2.2)}'::nummultirange</literallayout>
+        </entry>
+        <entry><literallayout class="monospaced">t</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>&gt;=</literal> </entry>
         <entry>greater than or equal</entry>
-        <entry><literal>numrange(1.1,2.2) &gt;= numrange(1.1,2.0)</literal></entry>
-        <entry><literal>t</literal></entry>
+        <entry>
+          <literallayout class="monospaced">numrange(1.1,2.2) &gt;= numrange(1.1,2.0)
+'{[1.1,2.2)}'::nummultirange &gt;= '{[1.1,2.0)}'::nummultirange</literallayout>
+        </entry>
+        <entry><literallayout class="monospaced">t</literallayout></entry>
+       </row>
+
+       <row>
+        <entry> <literal>@&gt;</literal> </entry>
+        <entry>contains multirange</entry>
+        <entry>
+          <literallayout class="monospaced">int4range(2,4) @&gt; '{[2,3)}'::int4multirange
+'{[2,4)}'::int4multirange @&gt; '{[2,3)}'::int4multirange</literallayout>
+        </entry>
+        <entry><literallayout class="monospaced">t</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>@&gt;</literal> </entry>
         <entry>contains range</entry>
-        <entry><literal>int4range(2,4) @&gt; int4range(2,3)</literal></entry>
-        <entry><literal>t</literal></entry>
+        <entry>
+          <literallayout class="monospaced">int4range(2,4) @&gt; int4range(2,3)
+'{[2,4)}'::int4multirange @&gt; int4range(2,3)</literallayout>
+        </entry>
+        <entry><literallayout class="monospaced">t</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>@&gt;</literal> </entry>
         <entry>contains element</entry>
-        <entry><literal>'[2011-01-01,2011-03-01)'::tsrange @&gt; '2011-01-10'::timestamp</literal></entry>
-        <entry><literal>t</literal></entry>
+        <entry>
+          <literallayout class="monospaced">'[2011-01-01,2011-03-01)'::tsrange @&gt; '2011-01-10'::timestamp
+'{[2011-01-01,2011-03-01)}'::tsmultirange @&gt; '2011-01-10'::timestamp</literallayout>
+        </entry>
+        <entry><literallayout class="monospaced">t</literallayout></entry>
+       </row>
+
+       <row>
+        <entry> <literal>&lt;@</literal> </entry>
+        <entry>multirange is contained by</entry>
+        <entry>
+          <literallayout class="monospaced">'{[2,4)}'::int4multirange &lt;@ int4range(1,7)
+'{[2,4)}'::int4multirange &lt;@ '{[1,7)}'::int4multirange</literallayout>
+        </entry>
+        <entry><literallayout class="monospaced">t</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>&lt;@</literal> </entry>
         <entry>range is contained by</entry>
-        <entry><literal>int4range(2,4) &lt;@ int4range(1,7)</literal></entry>
-        <entry><literal>t</literal></entry>
+        <entry>
+          <literallayout class="monospaced">int4range(2,4) &lt;@ int4range(1,7)
+int4range(2,4) &lt;@ '{[1,7)}'::int4multirange</literallayout>
+        </entry>
+        <entry><literallayout class="monospaced">t</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>&lt;@</literal> </entry>
         <entry>element is contained by</entry>
-        <entry><literal>42 &lt;@ int4range(1,7)</literal></entry>
-        <entry><literal>f</literal></entry>
+        <entry>
+          <literallayout class="monospaced">42 &lt;@ int4range(1,7)
+42 &lt;@ '{[1,7)}'::int4multirange</literallayout>
+        </entry>
+        <entry><literallayout class="monospaced">f</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>&amp;&amp;</literal> </entry>
         <entry>overlap (have points in common)</entry>
-        <entry><literal>int8range(3,7) &amp;&amp; int8range(4,12)</literal></entry>
-        <entry><literal>t</literal></entry>
+        <entry>
+          <literallayout class="monospaced">int8range(3,7) &amp;&amp; int8range(4,12)
+int8range(3,7) &amp;&amp; '{[4,12)}'::int8multirange
+'{[3,7)}'::int8multirange &amp;&amp; int8range(4,12)
+'{[3,7)}'::int8multirange &amp;&amp; '{[4,12)}'::int8multirange</literallayout>
+        </entry>
+        <entry><literallayout class="monospaced">t</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>&lt;&lt;</literal> </entry>
         <entry>strictly left of</entry>
-        <entry><literal>int8range(1,10) &lt;&lt; int8range(100,110)</literal></entry>
-        <entry><literal>t</literal></entry>
+        <entry>
+          <literallayout class="monospaced">int8range(1,10) &lt;&lt; int8range(100,110)
+int8range(1,10) &lt;&lt; '{[100,110)}'::int8multirange
+'{[1,10)}'::int8multirange &lt;&lt; int8range(100,110)
+'{[1,10)}'::int8multirange &lt;&lt; '{[100,110)}'::int8multirange</literallayout>
+        </entry>
+        <entry><literallayout class="monospaced">t</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>&gt;&gt;</literal> </entry>
         <entry>strictly right of</entry>
-        <entry><literal>int8range(50,60) &gt;&gt; int8range(20,30)</literal></entry>
-        <entry><literal>t</literal></entry>
+        <entry>
+          <literallayout class="monospaced">int8range(50,60) &gt;&gt; int8range(20,30)
+int8range(50,60) &gt;&gt; '{[20,30)}'::int8multirange
+'{[50,60)}'::int8multirange &gt;&gt; int8range(20,30)
+'{[50,60)}'::int8multirange &gt;&gt; '{[20,30)}'::int8multirange</literallayout>
+        </entry>
+        <entry><literallayout class="monospaced">t</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>&amp;&lt;</literal> </entry>
         <entry>does not extend to the right of</entry>
-        <entry><literal>int8range(1,20) &amp;&lt; int8range(18,20)</literal></entry>
-        <entry><literal>t</literal></entry>
+        <entry>
+          <literallayout class="monospaced">int8range(1,20) &amp;&lt; int8range(18,20)
+int8range(1,20) &amp;&lt; '{[18,20)}'::int8multirange
+'{[1,20)}'::int8multirange &amp;&lt; int8range(18,20)
+'{[1,20)}'::int8multirange &amp;&lt; '{[18,20)}'::int8multirange</literallayout>
+        </entry>
+        <entry><literallayout class="monospaced">t</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>&amp;&gt;</literal> </entry>
         <entry>does not extend to the left of</entry>
-        <entry><literal>int8range(7,20) &amp;&gt; int8range(5,10)</literal></entry>
-        <entry><literal>t</literal></entry>
+        <entry>
+          <literallayout class="monospaced">int8range(7,20) &amp;&gt; int8range(5,10)
+int8range(7,20) &amp;&gt; '{[5,10)}'::int8multirange
+'{[7,20)}'::int8multirange &amp;&gt; int8range(5,10)
+'{[7,20)}'::int8multirange &amp;&gt; '{[5,10)}'::int8multirange</literallayout>
+        </entry>
+        <entry><literallayout class="monospaced">t</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>-|-</literal> </entry>
         <entry>is adjacent to</entry>
-        <entry><literal>numrange(1.1,2.2) -|- numrange(2.2,3.3)</literal></entry>
-        <entry><literal>t</literal></entry>
+        <entry>
+          <literallayout class="monospaced">numrange(1.1,2.2) -|- numrange(2.2,3.3)
+numrange(1.1,2.2) -|- '{[2.2,3.3)}'::nummultirange
+'{[1.1,2.2)}'::nummultirange -|- numrange(2.2,3.3)
+'{[1.1,2.2)}'::nummultirange -|- '{[2.2,3.3)}'::nummultirange</literallayout>
+        </entry>
+        <entry><literallayout class="monospaced">t</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>+</literal> </entry>
         <entry>union</entry>
-        <entry><literal>numrange(5,15) + numrange(10,20)</literal></entry>
-        <entry><literal>[5,20)</literal></entry>
+        <entry><literallayout class="monospaced">numrange(5,15) + numrange(10,20)
+'{[5,10)}'::nummultirange + '{[15,20)}'::nummultirange</literallayout></entry>
+        <entry><literallayout class="monospaced">[5,20)
+{[5,10), [15,20)}</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>*</literal> </entry>
         <entry>intersection</entry>
-        <entry><literal>int8range(5,15) * int8range(10,20)</literal></entry>
-        <entry><literal>[10,15)</literal></entry>
+        <entry><literallayout class="monospaced">int8range(5,15) * int8range(10,20)
+'{[5,15)}'::int8multirange * '{[10,20)}'::int8multirange</literallayout></entry>
+        <entry><literallayout class="monospaced">[10,15)
+{[10,15)}</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>-</literal> </entry>
         <entry>difference</entry>
-        <entry><literal>int8range(5,15) - int8range(10,20)</literal></entry>
-        <entry><literal>[5,10)</literal></entry>
+        <entry><literallayout class="monospaced">int8range(5,15) - int8range(10,20)
+'{[5,20)}'::int8multirange - '{[10,15)}'::int8multirange</literallayout></entry>
+        <entry><literallayout class="monospaced">[5,10)
+{[5,10), [15,20)}</literallayout></entry>
        </row>
 
       </tbody>
@@ -14185,19 +14268,31 @@ NULL baz</literallayout>(3 rows)</entry>
 
   <para>
    The left-of/right-of/adjacent operators always return false when an empty
-   range is involved; that is, an empty range is not considered to be either
-   before or after any other range.
+   range or multirange is involved; that is, an empty range is not considered to
+   be either before or after any other range.
+  </para>
+
+  <para>
+   Elsewhere empty ranges and multiranges are treated as the additive identity:
+   anything unioned with an empty value is itself. Anything minus an empty
+   value is itself. An empty multirange has exactly the same points as an empty
+   range. Every range contains the empty range. Every multirange contains as many
+   empty ranges as you like.
   </para>
 
   <para>
    The union and difference operators will fail if the resulting range would
    need to contain two disjoint sub-ranges, as such a range cannot be
-   represented.
+   represented. There are separate operators for union and difference that take
+   multirange parameters and return a multirange, and they do not fail even if
+   their arguments are disjoint. So if you need a union or difference operation
+   for ranges that may be disjoint, you can avoid errors by first casting your
+   ranges to multiranges.
   </para>
 
   <para>
    <xref linkend="range-functions-table"/> shows the functions
-   available for use with range types.
+   available for use with range and multirange types.
   </para>
 
   <indexterm>
@@ -14249,6 +14344,17 @@ NULL baz</literallayout>(3 rows)</entry>
        <row>
         <entry>
          <literal>
+          <function>lower</function>(<type>anymultirange</type>)
+         </literal>
+        </entry>
+        <entry>multirange's element type</entry>
+        <entry>lower bound of multirange</entry>
+        <entry><literal>lower('{[1.1,2.2)}'::nummultirange)</literal></entry>
+        <entry><literal>1.1</literal></entry>
+       </row>
+       <row>
+        <entry>
+         <literal>
           <function>upper</function>(<type>anyrange</type>)
          </literal>
         </entry>
@@ -14260,6 +14366,17 @@ NULL baz</literallayout>(3 rows)</entry>
        <row>
         <entry>
          <literal>
+          <function>upper</function>(<type>anymultirange</type>)
+         </literal>
+        </entry>
+        <entry>multirange's element type</entry>
+        <entry>upper bound of multirange</entry>
+        <entry><literal>upper('{[1.1,2.2)}'::nummultirange)</literal></entry>
+        <entry><literal>2.2</literal></entry>
+       </row>
+       <row>
+        <entry>
+         <literal>
           <function>isempty</function>(<type>anyrange</type>)
          </literal>
         </entry>
@@ -14271,6 +14388,17 @@ NULL baz</literallayout>(3 rows)</entry>
        <row>
         <entry>
          <literal>
+          <function>isempty</function>(<type>anymultirange</type>)
+         </literal>
+        </entry>
+        <entry><type>boolean</type></entry>
+        <entry>is the multirange empty?</entry>
+        <entry><literal>isempty('{[1.1,2.2)}'::nummultirange)</literal></entry>
+        <entry><literal>false</literal></entry>
+       </row>
+       <row>
+        <entry>
+         <literal>
           <function>lower_inc</function>(<type>anyrange</type>)
          </literal>
         </entry>
@@ -14282,6 +14410,17 @@ NULL baz</literallayout>(3 rows)</entry>
        <row>
         <entry>
          <literal>
+          <function>lower_inc</function>(<type>anymultirange</type>)
+         </literal>
+        </entry>
+        <entry><type>boolean</type></entry>
+        <entry>is the lower bound inclusive?</entry>
+        <entry><literal>lower_inc('{[1.1,2.2)}'::nummultirange)</literal></entry>
+        <entry><literal>true</literal></entry>
+       </row>
+       <row>
+        <entry>
+         <literal>
           <function>upper_inc</function>(<type>anyrange</type>)
          </literal>
         </entry>
@@ -14293,6 +14432,17 @@ NULL baz</literallayout>(3 rows)</entry>
        <row>
         <entry>
          <literal>
+          <function>upper_inc</function>(<type>anymultirange</type>)
+         </literal>
+        </entry>
+        <entry><type>boolean</type></entry>
+        <entry>is the upper bound inclusive?</entry>
+        <entry><literal>upper_inc('{[1.1,2.2)}'::nummultirange)</literal></entry>
+        <entry><literal>false</literal></entry>
+       </row>
+       <row>
+        <entry>
+         <literal>
           <function>lower_inf</function>(<type>anyrange</type>)
          </literal>
         </entry>
@@ -14304,6 +14454,17 @@ NULL baz</literallayout>(3 rows)</entry>
        <row>
         <entry>
          <literal>
+          <function>lower_inf</function>(<type>anymultirange</type>)
+         </literal>
+        </entry>
+        <entry><type>boolean</type></entry>
+        <entry>is the lower bound infinite?</entry>
+        <entry><literal>lower_inf('{(,)}'::datemultirange)</literal></entry>
+        <entry><literal>true</literal></entry>
+       </row>
+       <row>
+        <entry>
+         <literal>
           <function>upper_inf</function>(<type>anyrange</type>)
          </literal>
         </entry>
@@ -14315,6 +14476,17 @@ NULL baz</literallayout>(3 rows)</entry>
        <row>
         <entry>
          <literal>
+          <function>upper_inf</function>(<type>anymultirange</type>)
+         </literal>
+        </entry>
+        <entry><type>boolean</type></entry>
+        <entry>is the upper bound infinite?</entry>
+        <entry><literal>upper_inf('{(,)}'::datemultirange)</literal></entry>
+        <entry><literal>true</literal></entry>
+       </row>
+       <row>
+        <entry>
+         <literal>
           <function>range_merge</function>(<type>anyrange</type>, <type>anyrange</type>)
          </literal>
         </entry>
@@ -14323,16 +14495,38 @@ NULL baz</literallayout>(3 rows)</entry>
         <entry><literal>range_merge('[1,2)'::int4range, '[3,4)'::int4range)</literal></entry>
         <entry><literal>[1,4)</literal></entry>
        </row>
+       <row>
+        <entry>
+         <literal>
+          <function>range_merge</function>(<type>anymultirange</type>)
+         </literal>
+        </entry>
+        <entry><type>anyrange</type></entry>
+        <entry>the smallest range which includes the entire multirange</entry>
+        <entry><literal>range_merge('{[1,2), [3,4)}'::int4multirange)</literal></entry>
+        <entry><literal>[1,4)</literal></entry>
+       </row>
+       <row>
+        <entry>
+         <literal>
+          <function>multirange</function>(<type>anyrange</type>)
+         </literal>
+        </entry>
+        <entry><type>anymultirange</type></entry>
+        <entry>a multirange containing just the given range</entry>
+        <entry><literal>multirange('[1,2)'::int4range)</literal></entry>
+        <entry><literal>{[1,2)}</literal></entry>
+       </row>
       </tbody>
      </tgroup>
     </table>
 
   <para>
    The <function>lower</function> and  <function>upper</function> functions return null
-   if the range is empty or the requested bound is infinite.
+   if the input range/multirange is empty or the requested bound is infinite.
    The <function>lower_inc</function>, <function>upper_inc</function>,
    <function>lower_inf</function>, and <function>upper_inf</function>
-   functions all return false for an empty range.
+   functions all return false for an empty range or multirange.
   </para>
   </sect1>
 
@@ -14654,6 +14848,44 @@ NULL baz</literallayout>(3 rows)</entry>
      <row>
       <entry>
        <indexterm>
+        <primary>range_agg</primary>
+       </indexterm>
+       <function>
+         range_agg(<replaceable class="parameter">expression</replaceable>)
+       </function>
+      </entry>
+      <entry>
+       <type>anyrange</type>
+      </entry>
+      <entry>
+       <type>multirange</type> of the argument range type
+      </entry>
+      <entry>Yes</entry>
+      <entry>union of the non-null input values</entry>
+     </row>
+
+     <row>
+      <entry>
+       <indexterm>
+        <primary>range_intersect_agg</primary>
+       </indexterm>
+       <function>
+         range_intersect_agg(<replaceable class="parameter">expression</replaceable>)
+       </function>
+      </entry>
+      <entry>
+       <type>anyrange</type>
+      </entry>
+      <entry>
+       <type>multirange</type> of the argument range type
+      </entry>
+      <entry>No</entry>
+      <entry>intersection of the non-null input values</entry>
+     </row>
+
+     <row>
+      <entry>
+       <indexterm>
         <primary>string_agg</primary>
        </indexterm>
        <function>
diff --git a/doc/src/sgml/rangetypes.sgml b/doc/src/sgml/rangetypes.sgml
index b75fb3a392..c8784bdce3 100644
--- a/doc/src/sgml/rangetypes.sgml
+++ b/doc/src/sgml/rangetypes.sgml
@@ -27,6 +27,13 @@
   ranges from an instrument, and so forth can also be useful.
  </para>
 
+ <para>
+  Every range type has a corresponding multirange type. A multirange is
+  an ordered list of non-continguous, non-empty, non-null ranges. Most
+  range operators also work on multiranges, and they have a few functions
+  of their own.
+ </para>
+
  <sect2 id="rangetypes-builtin">
   <title>Built-in Range Types</title>
 
@@ -232,10 +239,30 @@ SELECT '[4,4]'::int4range;
 SELECT '[4,4)'::int4range;
 </programlisting>
   </para>
+
+  <para>
+   The input for a multirange is curly brackets (<literal>{</literal> and
+   <literal>}</literal>) containing zero or more valid ranges,
+   separated by commas. Whitespace is permitted around the brackets and
+   commas. This is intended to be reminiscent of array syntax, although
+   multiranges are much simpler: they have just one dimension and there is
+   no need to quote their contents. (The bounds of their ranges may be
+   quoted as above however.)
+  </para>
+
+  <para>
+  Examples:
+<programlisting>
+SELECT '{}'::int4multirange;
+SELECT '{[3,7)}'::int4multirange;
+SELECT '{[3,7), [8,9)}'::int4multirange;
+</programlisting>
+  </para>
+
  </sect2>
 
  <sect2 id="rangetypes-construct">
-  <title>Constructing Ranges</title>
+  <title>Constructing Ranges and Multiranges</title>
 
   <para>
    Each range type has a constructor function with the same name as the range
@@ -269,6 +296,19 @@ SELECT int8range(1, 14, '(]');
 SELECT numrange(NULL, 2.2);
 </programlisting>
   </para>
+
+  <para>
+   Each range type also has a multirange constructor with the same name as the
+   multirange type.  The constructor function takes zero or more arguments
+   which are all ranges of the appropriate type.
+   For example:
+
+<programlisting>
+SELECT nummultirange();
+SELECT nummultirange(numrange(1.0, 14.0));
+SELECT nummultirange(numrange(1.0, 14.0), numrange(20.0, 25.0));
+</programlisting>
+  </para>
  </sect2>
 
  <sect2 id="rangetypes-discrete">
@@ -342,6 +382,11 @@ SELECT '[1.234, 5.678]'::floatrange;
   </para>
 
   <para>
+   When you define your own range you automatically get a corresponding
+   multirange type.
+  </para>
+
+  <para>
    Defining your own range type also allows you to specify a different
    subtype B-tree operator class or collation to use, so as to change the sort
    ordering that determines which values fall into a given range.
diff --git a/src/backend/catalog/pg_proc.c b/src/backend/catalog/pg_proc.c
index 423fd79d94..fb375cd3bf 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,13 @@ 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 b5bc36c2bd..1217a6ddd0 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_rngmultitypid - 1] = ObjectIdGetDatum(multirangeTypeOid);
 
 	tup = heap_form_tuple(RelationGetDescr(pg_range), values, nulls);
 
@@ -100,6 +102,12 @@ 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 cd56714968..c359f27c74 100644
--- a/src/backend/catalog/pg_type.c
+++ b/src/backend/catalog/pg_type.c
@@ -27,6 +27,7 @@
 #include "catalog/pg_proc.h"
 #include "catalog/pg_type.h"
 #include "commands/typecmds.h"
+#include "mb/pg_wchar.h"
 #include "miscadmin.h"
 #include "parser/scansup.h"
 #include "utils/acl.h"
@@ -36,6 +37,9 @@
 #include "utils/rel.h"
 #include "utils/syscache.h"
 
+static char *makeUniqueTypeName(const char *typeName, Oid typeNamespace,
+								bool tryOriginal);
+
 /* Potentially set by pg_upgrade_support functions */
 Oid			binary_upgrade_next_pg_type_oid = InvalidOid;
 
@@ -809,31 +813,10 @@ RenameTypeInternal(Oid typeOid, const char *newTypeName, Oid typeNamespace)
 char *
 makeArrayTypeName(const char *typeName, Oid typeNamespace)
 {
-	char	   *arr = (char *) palloc(NAMEDATALEN);
-	int			namelen = strlen(typeName);
-	int			i;
-
-	/*
-	 * 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;
-	}
+	char	   *arr;
 
-	if (i >= NAMEDATALEN - 1)
+	arr = makeUniqueTypeName(typeName, typeNamespace, false);
+	if (arr == NULL)
 		ereport(ERROR,
 				(errcode(ERRCODE_DUPLICATE_OBJECT),
 				 errmsg("could not form array type name for type \"%s\"",
@@ -904,3 +887,91 @@ moveArrayTypeName(Oid typeOid, const char *typeName, Oid typeNamespace)
 
 	return true;
 }
+
+
+/*
+ * makeMultirangeTypeName
+ *	  - given a range type name, make a multirange type name for it
+ *
+ * caller is responsible for pfreeing the result
+ */
+char *
+makeMultirangeTypeName(const char *rangeTypeName, Oid typeNamespace)
+{
+	char	   *buf;
+	char	   *mrname;
+	char	   *rangestr;
+
+	/*
+	 * 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...
+	 */
+	rangestr = strstr(rangeTypeName, "range");
+	if (rangestr)
+	{
+		char	*prefix = pnstrdup(rangeTypeName, rangestr - rangeTypeName);
+
+		buf = psprintf("%s%s%s", prefix, "multi", rangestr);
+	}
+	else
+		buf = psprintf("%s_multirange", pnstrdup(rangeTypeName, NAMEDATALEN - 12));
+
+	/* clip it at NAMEDATALEN-1 bytes */
+	buf[pg_mbcliplen(buf, strlen(buf), NAMEDATALEN - 1)] = '\0';
+
+	mrname = makeUniqueTypeName(buf, typeNamespace, true);
+	if (mrname == NULL)
+		ereport(ERROR,
+				(errcode(ERRCODE_DUPLICATE_OBJECT),
+				 errmsg("could not form multirange type name for type \"%s\"",
+						rangeTypeName)));
+
+	return mrname;
+}
+
+/*
+ * makeUniqueTypeName
+ *		Generate a unique name for a prospective new type
+ *
+ * Given a typeName, return a new palloc'ed name by preprending underscores
+ * until a non-conflicting name results.
+ *
+ * If tryOriginal, first try with zero underscores.
+ */
+static char *
+makeUniqueTypeName(const char *typeName, Oid typeNamespace, bool tryOriginal)
+{
+	int			i;
+	int			namelen;
+	char		dest[NAMEDATALEN];
+
+	Assert(strlen(typeName) <= NAMEDATALEN - 1);
+
+	if (tryOriginal &&
+		!SearchSysCacheExists2(TYPENAMENSP,
+							   CStringGetDatum(typeName),
+							   ObjectIdGetDatum(typeNamespace)))
+		return pstrdup(typeName);
+
+	/*
+	 * The idea is to prepend underscores as needed until we make a name that
+	 * doesn't collide with anything ...
+	 */
+	namelen = strlen(typeName);
+	for (i = 1; i < NAMEDATALEN - 1; i++)
+	{
+		dest[i - 1] = '_';
+		strlcpy(dest + i, typeName, NAMEDATALEN - i);
+		if (namelen + i >= NAMEDATALEN)
+			truncate_identifier(dest, NAMEDATALEN, false);
+
+		if (!SearchSysCacheExists2(TYPENAMENSP,
+								   CStringGetDatum(dest),
+								   ObjectIdGetDatum(typeNamespace)))
+			return pstrdup(dest);
+	}
+
+	return NULL;
+}
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index 8891b1d564..dfb2edd3d0 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -105,9 +105,14 @@ typedef struct
 
 /* Potentially set by pg_upgrade_support functions */
 Oid			binary_upgrade_next_array_pg_type_oid = InvalidOid;
+Oid			binary_upgrade_next_mrng_pg_type_oid = InvalidOid;
+Oid			binary_upgrade_next_mrng_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 rangeOid,
+									   Oid rangeArrayOid, Oid *castFuncOid);
 static Oid	findTypeInputFunction(List *procname, Oid typeOid);
 static Oid	findTypeOutputFunction(List *procname, Oid typeOid);
 static Oid	findTypeReceiveFunction(List *procname, Oid typeOid);
@@ -738,7 +743,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",
@@ -1279,6 +1285,11 @@ checkEnumOwner(HeapTuple tup)
 /*
  * DefineRange
  *		Registers a new range type.
+ *
+ * Perhaps it might be worthwhile to set pg_type.typelem to the base type,
+ * and likewise on multiranges to set it to the range type. But having a
+ * non-zero typelem is treated elsewhere as a synonym for being an array,
+ * and users might have queries with that same assumption.
  */
 ObjectAddress
 DefineRange(CreateRangeStmt *stmt)
@@ -1287,7 +1298,11 @@ DefineRange(CreateRangeStmt *stmt)
 	Oid			typeNamespace;
 	Oid			typoid;
 	char	   *rangeArrayName;
+	char	   *multirangeTypeName;
+	char	   *multirangeArrayName;
 	Oid			rangeArrayOid;
+	Oid			multirangeOid;
+	Oid			multirangeArrayOid;
 	Oid			rangeSubtype = InvalidOid;
 	List	   *rangeSubOpclassName = NIL;
 	List	   *rangeCollationName = NIL;
@@ -1304,6 +1319,8 @@ DefineRange(CreateRangeStmt *stmt)
 	AclResult	aclresult;
 	ListCell   *lc;
 	ObjectAddress address;
+	ObjectAddress mltrngaddress;
+	Oid			castFuncOid;
 
 	/* Convert list of names to a name and namespace */
 	typeNamespace = QualifiedNameGetCreationNamespace(stmt->typeName,
@@ -1452,8 +1469,10 @@ DefineRange(CreateRangeStmt *stmt)
 	/* alignment must be TYPALIGN_INT or TYPALIGN_DOUBLE for ranges */
 	alignment = (subtypalign == TYPALIGN_DOUBLE) ? TYPALIGN_DOUBLE : TYPALIGN_INT;
 
-	/* Allocate OID for array type */
+	/* Allocate OID for array type, its multirange, and its multirange array */
 	rangeArrayOid = AssignTypeArrayOid();
+	multirangeOid = AssignTypeMultirangeOid();
+	multirangeArrayOid = AssignTypeMultirangeArrayOid();
 
 	/* Create the pg_type entry */
 	address =
@@ -1491,9 +1510,46 @@ DefineRange(CreateRangeStmt *stmt)
 	Assert(typoid == InvalidOid || typoid == address.objectId);
 	typoid = address.objectId;
 
+	/* Create the multirange that goes with it */
+	multirangeTypeName = makeMultirangeTypeName(typeName, typeNamespace);
+
+	mltrngaddress =
+		TypeCreate(multirangeOid,	/* force assignment of this 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_RANGE,	/* type-category (range 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) */
+	Assert(multirangeOid == mltrngaddress.objectId);
+
 	/* Create the entry in pg_range */
 	RangeCreate(typoid, rangeSubtype, rangeCollation, rangeSubOpclass,
-				rangeCanonical, rangeSubtypeDiff);
+				rangeCanonical, rangeSubtypeDiff, multirangeOid);
 
 	/*
 	 * Create the array type that goes with it.
@@ -1534,8 +1590,53 @@ 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 */
+			   multirangeOid,	/* 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,
+							   multirangeOid, typoid, rangeArrayOid,
+							   &castFuncOid);
+
+	/* Create cast from the range type to its multirange type */
+	CastCreate(typoid, multirangeOid, castFuncOid, 'e', 'f', DEPENDENCY_INTERNAL);
+
+	pfree(multirangeTypeName);
+	pfree(multirangeArrayName);
 
 	return address;
 }
@@ -1613,6 +1714,147 @@ 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.
+ *
+ * Sets castFuncOid to the oid of the new constructor that can be used
+ * to cast from a range to a multirange.
+ */
+static void
+makeMultirangeConstructors(const char *name, Oid namespace,
+						   Oid multirangeOid, Oid rangeOid, Oid rangeArrayOid,
+						   Oid *castFuncOid)
+{
+	ObjectAddress myself,
+				referenced;
+	oidvector  *argtypes;
+	Datum		allParamTypes;
+	ArrayType  *allParameterTypes;
+	Datum		paramModes;
+	ArrayType  *parameterModes;
+
+	referenced.classId = TypeRelationId;
+	referenced.objectId = multirangeOid;
+	referenced.objectSubId = 0;
+
+	/* 0-arg constructor - for empty multiranges */
+	argtypes = buildoidvector(NULL, 0);
+	myself = ProcedureCreate(name,	/* name: same as multirange type */
+							 namespace,
+							 false,	/* replace */
+							 false,	/* returns set */
+							 multirangeOid,	/* return type */
+							 BOOTSTRAP_SUPERUSERID,	/* proowner */
+							 INTERNALlanguageId,	/* language */
+							 F_FMGR_INTERNAL_VALIDATOR,
+							 "multirange_constructor0",	/* prosrc */
+							 NULL,	/* probin */
+							 PROKIND_FUNCTION,
+							 false,	/* security_definer */
+							 false,	/* leakproof */
+							 false, /* isStrict */
+							 PROVOLATILE_IMMUTABLE,	/* volatility */
+							 PROPARALLEL_SAFE,	/* parallel safety */
+							 argtypes,	/* parameterTypes */
+							 PointerGetDatum(NULL),	/* allParameterTypes */
+							 PointerGetDatum(NULL),	/* parameterModes */
+							 PointerGetDatum(NULL),	/* parameterNames */
+							 NIL,	/* parameterDefaults */
+							 PointerGetDatum(NULL),	/* trftypes */
+							 PointerGetDatum(NULL),	/* proconfig */
+							 InvalidOid,	/* prosupport */
+							 1.0,	/* procost */
+							 0.0);	/* prorows */
+	/*
+	 * Make the constructor 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);
+	pfree(argtypes);
+
+	/*
+	 * 1-arg constructor - for casts
+	 *
+	 * In theory we shouldn't need both this and the vararg (n-arg) constructor,
+	 * but having a separate 1-arg function lets us define casts against it.
+	 */
+	argtypes = buildoidvector(&rangeOid, 1);
+	myself = ProcedureCreate(name,	/* name: same as multirange type */
+							 namespace,
+							 false,	/* replace */
+							 false,	/* returns set */
+							 multirangeOid,	/* return type */
+							 BOOTSTRAP_SUPERUSERID,	/* proowner */
+							 INTERNALlanguageId,	/* language */
+							 F_FMGR_INTERNAL_VALIDATOR,
+							 "multirange_constructor1",	/* prosrc */
+							 NULL,	/* probin */
+							 PROKIND_FUNCTION,
+							 false,	/* security_definer */
+							 false,	/* leakproof */
+							 true, /* isStrict */
+							 PROVOLATILE_IMMUTABLE,	/* volatility */
+							 PROPARALLEL_SAFE,	/* parallel safety */
+							 argtypes,	/* parameterTypes */
+							 PointerGetDatum(NULL),	/* allParameterTypes */
+							 PointerGetDatum(NULL),	/* parameterModes */
+							 PointerGetDatum(NULL),	/* parameterNames */
+							 NIL,	/* parameterDefaults */
+							 PointerGetDatum(NULL),	/* trftypes */
+							 PointerGetDatum(NULL),	/* proconfig */
+							 InvalidOid,	/* prosupport */
+							 1.0,	/* procost */
+							 0.0);	/* prorows */
+	/* ditto */
+	recordDependencyOn(&myself, &referenced, DEPENDENCY_INTERNAL);
+	pfree(argtypes);
+	*castFuncOid = myself.objectId;
+
+	/* n-arg constructor - vararg */
+	argtypes = buildoidvector(&rangeArrayOid, 1);
+	allParamTypes = ObjectIdGetDatum(rangeArrayOid);
+	allParameterTypes = construct_array(&allParamTypes,
+										1, OIDOID,
+										sizeof(Oid), true, 'i');
+	paramModes = CharGetDatum(FUNC_PARAM_VARIADIC);
+	parameterModes = construct_array(&paramModes, 1, CHAROID,
+									 1, true, 'c');
+	myself = ProcedureCreate(name,	/* name: same as multirange type */
+							 namespace,
+							 false,	/* replace */
+							 false,	/* returns set */
+							 multirangeOid,	/* return type */
+							 BOOTSTRAP_SUPERUSERID,	/* proowner */
+							 INTERNALlanguageId,	/* language */
+							 F_FMGR_INTERNAL_VALIDATOR,
+							 "multirange_constructor2",	/* prosrc */
+							 NULL,	/* probin */
+							 PROKIND_FUNCTION,
+							 false,	/* security_definer */
+							 false,	/* leakproof */
+							 false, /* isStrict */
+							 PROVOLATILE_IMMUTABLE,	/* volatility */
+							 PROPARALLEL_SAFE,	/* parallel safety */
+							 argtypes,	/* parameterTypes */
+							 PointerGetDatum(allParameterTypes),	/* allParameterTypes */
+							 PointerGetDatum(parameterModes),	/* parameterModes */
+							 PointerGetDatum(NULL),	/* parameterNames */
+							 NIL,	/* parameterDefaults */
+							 PointerGetDatum(NULL),	/* trftypes */
+							 PointerGetDatum(NULL),	/* proconfig */
+							 InvalidOid,	/* prosupport */
+							 1.0,	/* procost */
+							 0.0);	/* prorows */
+	/* ditto */
+	recordDependencyOn(&myself, &referenced, DEPENDENCY_INTERNAL);
+	pfree(argtypes);
+	pfree(allParameterTypes);
+	pfree(parameterModes);
+}
 
 /*
  * Find suitable I/O functions for a type.
@@ -2045,6 +2287,72 @@ AssignTypeArrayOid(void)
 	return type_array_oid;
 }
 
+/*
+ *	AssignTypeMultirangeOid
+ *
+ *	Pre-assign the range type's multirange OID for use in pg_type.oid
+ */
+Oid
+AssignTypeMultirangeOid(void)
+{
+	Oid			type_multirange_oid;
+
+	/* Use binary-upgrade override for pg_type.oid? */
+	if (IsBinaryUpgrade)
+	{
+		if (!OidIsValid(binary_upgrade_next_mrng_pg_type_oid))
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+					 errmsg("pg_type multirange OID value not set when in binary upgrade mode")));
+
+		type_multirange_oid = binary_upgrade_next_mrng_pg_type_oid;
+		binary_upgrade_next_mrng_pg_type_oid = InvalidOid;
+	}
+	else
+	{
+		Relation	pg_type = table_open(TypeRelationId, AccessShareLock);
+
+		type_multirange_oid = GetNewOidWithIndex(pg_type, TypeOidIndexId,
+												 Anum_pg_type_oid);
+		table_close(pg_type, AccessShareLock);
+	}
+
+	return type_multirange_oid;
+}
+
+/*
+ *	AssignTypeMultirangeArrayOid
+ *
+ *	Pre-assign the range type's multirange array OID for use in pg_type.typarray
+ */
+Oid
+AssignTypeMultirangeArrayOid(void)
+{
+	Oid			type_multirange_array_oid;
+
+	/* Use binary-upgrade override for pg_type.oid? */
+	if (IsBinaryUpgrade)
+	{
+		if (!OidIsValid(binary_upgrade_next_mrng_array_pg_type_oid))
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+					 errmsg("pg_type multirange array OID value not set when in binary upgrade mode")));
+
+		type_multirange_array_oid = binary_upgrade_next_mrng_array_pg_type_oid;
+		binary_upgrade_next_mrng_array_pg_type_oid = InvalidOid;
+	}
+	else
+	{
+		Relation	pg_type = table_open(TypeRelationId, AccessShareLock);
+
+		type_multirange_array_oid = GetNewOidWithIndex(pg_type, TypeOidIndexId,
+													   Anum_pg_type_oid);
+		table_close(pg_type, AccessShareLock);
+	}
+
+	return type_multirange_array_oid;
+}
+
 
 /*-------------------------------------------------------------------
  * DefineCompositeType
diff --git a/src/backend/parser/parse_coerce.c b/src/backend/parser/parse_coerce.c
index 929f758ef4..27bfc4bf8c 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
@@ -1481,6 +1483,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;
@@ -1527,6 +1531,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 */
@@ -1577,6 +1590,41 @@ check_generic_type_consistency(const Oid *actual_arg_types,
 			/* otherwise, they better match */
 			return false;
 		}
+		else
+			range_typelem = InvalidOid;	/* keep compiler quiet */
+	}
+	else
+		range_typelem = InvalidOid; /* keep compiler quiet */
+
+	/* Get the element type based on the multirange type, if we have one */
+	if (OidIsValid(multirange_typeid))
+	{
+		multirange_typelem = get_range_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)
@@ -1680,13 +1728,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.
@@ -1744,7 +1796,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;
@@ -1762,6 +1814,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;
+		}
 	}
 
 	/*
@@ -1812,9 +1884,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
@@ -1846,6 +1921,71 @@ enforce_generic_type_consistency(const Oid *actual_arg_types,
 							   format_type_be(range_typeid),
 							   format_type_be(elem_typeid))));
 		}
+		else
+			range_typelem = InvalidOid;
+	}
+
+	/* 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_range_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))
@@ -1855,6 +1995,7 @@ enforce_generic_type_consistency(const Oid *actual_arg_types,
 			elem_typeid = ANYELEMENTOID;
 			array_typeid = ANYARRAYOID;
 			range_typeid = ANYRANGEOID;
+			multirange_typeid = ANYMULTIRANGEOID;
 		}
 		else
 		{
@@ -1927,6 +2068,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;
+			}
 		}
 	}
 
@@ -1958,6 +2110,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 ||
@@ -2006,8 +2174,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);
@@ -2019,11 +2186,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_range_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)
 		{
@@ -2051,6 +2244,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_range_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)
@@ -2059,6 +2265,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_range_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 */
@@ -2173,6 +2447,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..bd3cf3dc93
--- /dev/null
+++ b/src/backend/utils/adt/multirangetypes.c
@@ -0,0 +1,2186 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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/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 */
+	FmgrInfo	typioproc;		/* range type's I/O proc */
+	Oid			typioparam;		/* range type's I/O parameter */
+} 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 mltrngtypid,
+												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;
+	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 = NULL;
+	int32		range_str_len;
+	char	   *range_str_copy;
+
+	cache = get_multirange_io_data(fcinfo, mltrngtypoid, IOFunc_input);
+	rangetyp = cache->typcache->rngtype;
+
+	/* 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->typioproc,
+																 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->typioproc, 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->typioproc,
+														   &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;
+
+		range = RangeTypePGetDatum(ranges[i]);
+		range = PointerGetDatum(SendFunctionCall(&cache->typioproc, range));
+
+		pq_sendint32(buf, VARSIZE(range) - VARHDRSZ);
+		pq_sendbytes(buf, VARDATA(range), VARSIZE(range) - VARHDRSZ);
+	}
+
+	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)
+	{
+		Oid			typiofunc;
+		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,
+						 &typiofunc);
+
+		if (!OidIsValid(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(typiofunc, &cache->typioproc,
+					  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;
+	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++)
+		bytelen += MAXALIGN(VARSIZE(ranges[i]));
+
+	/* 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++)
+	{
+		memcpy(ptr, ranges[i], VARSIZE(ranges[i]));
+		ptr += MAXALIGN(VARSIZE(ranges[i]));
+	}
+
+	return multirange;
+}
+
+/*
+ * Converts a list of arbitrary ranges 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_constructor2(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,
+				(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+				 errmsg("multirange values cannot contain NULL members")));
+
+	rangeArray = PG_GETARG_ARRAYTYPE_P(0);
+
+	dims = ARR_NDIM(rangeArray);
+	if (dims > 1)
+		ereport(ERROR,
+				(errcode(ERRCODE_CARDINALITY_VIOLATION),
+				 errmsg("multiranges cannot be constructed from multi-dimensional arrays")));
+
+	rngtypid = ARR_ELEMTYPE(rangeArray);
+	if (rngtypid != rangetyp->type_id)
+		ereport(ERROR,
+				(errcode(ERRCODE_DATATYPE_MISMATCH),
+				 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,
+						(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+						 errmsg("multirange values cannot contain NULL members")));
+
+			/* make_multirange will do its own copy */
+			ranges[i] = DatumGetRangeTypeP(elements[i]);
+		}
+	}
+
+	PG_RETURN_MULTIRANGE_P(make_multirange(mltrngtypid, rangetyp, range_count, ranges));
+}
+
+/*
+ * Construct multirange value from a single range.
+ * It'd be nice if we could just use multirange_constructor2
+ * for this case, but we need a non-variadic single-arg function
+ * to let us define a CAST from a range to its multirange.
+ */
+Datum
+multirange_constructor1(PG_FUNCTION_ARGS)
+{
+	Oid			mltrngtypid = get_fn_expr_rettype(fcinfo->flinfo);
+	Oid			rngtypid;
+	TypeCacheEntry *typcache;
+	TypeCacheEntry *rangetyp;
+	RangeType  *range;
+
+	typcache = multirange_get_typcache(fcinfo, mltrngtypid);
+	rangetyp = typcache->rngtype;
+
+	/*
+	 * These checks should be guaranteed by our signature, but let's do them
+	 * just in case.
+	 */
+
+	if (PG_ARGISNULL(0))
+				ereport(ERROR,
+						(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+						 errmsg("multirange values cannot contain NULL members")));
+
+	range = PG_GETARG_RANGE_P(0);
+
+	/* Make sure the range type matches. */
+	rngtypid = RangeTypeGetOid(range);
+	if (rngtypid != rangetyp->type_id)
+		ereport(ERROR,
+				(errcode(ERRCODE_DATATYPE_MISMATCH),
+				 errmsg("type %u does not match constructor type", rngtypid)));
+
+	PG_RETURN_MULTIRANGE_P(make_multirange(mltrngtypid, rangetyp, 1, &range));
+}
+
+/*
+ * 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
+		elog(ERROR,		/* can't happen */
+			 "niladic multirange constructor must not receive arguments");
+}
+
+
+/* multirange, multirange -> multirange type functions */
+
+/* multirange union */
+Datum
+multirange_union(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
+	MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count1;
+	int32		range_count2;
+	int32		range_count3;
+	RangeType **ranges1;
+	RangeType **ranges2;
+	RangeType **ranges3;
+
+	if (MultirangeIsEmpty(mr1))
+		PG_RETURN_MULTIRANGE_P(mr2);
+	if (MultirangeIsEmpty(mr2))
+		PG_RETURN_MULTIRANGE_P(mr1);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr1));
+
+	multirange_deserialize(mr1, &range_count1, &ranges1);
+	multirange_deserialize(mr2, &range_count2, &ranges2);
+
+	range_count3 = range_count1 + range_count2;
+	ranges3 = palloc0(range_count3 * sizeof(RangeType *));
+	memcpy(ranges3, ranges1, range_count1 * sizeof(RangeType *));
+	memcpy(ranges3 + range_count1, ranges2, range_count2 * sizeof(RangeType *));
+	PG_RETURN_MULTIRANGE_P(make_multirange(typcache->type_id, typcache->rngtype,
+										   range_count3, ranges3));
+}
+
+/* multirange minus */
+Datum
+multirange_minus(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
+	MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
+	Oid			mltrngtypoid = MultirangeTypeGetOid(mr1);
+	TypeCacheEntry *typcache;
+	TypeCacheEntry *rangetyp;
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+
+	typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
+	rangetyp = typcache->rngtype;
+
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		PG_RETURN_MULTIRANGE_P(mr1);
+
+	multirange_deserialize(mr1, &range_count1, &ranges1);
+	multirange_deserialize(mr2, &range_count2, &ranges2);
+
+	PG_RETURN_MULTIRANGE_P(multirange_minus_internal(mltrngtypoid,
+													 rangetyp,
+													 range_count1,
+													 ranges1,
+													 range_count2,
+													 ranges2));
+}
+
+MultirangeType *
+multirange_minus_internal(Oid mltrngtypoid, TypeCacheEntry *rangetyp,
+						  int32 range_count1, RangeType **ranges1,
+						  int32 range_count2, RangeType **ranges2)
+{
+	RangeType  *r1;
+	RangeType  *r2;
+	RangeType **ranges3;
+	int32		range_count3;
+	int32		i1;
+	int32		i2;
+
+	/*
+	 * Worst case: every range in ranges1 makes a different cut to some range
+	 * in ranges2.
+	 */
+	ranges3 = palloc0((range_count1 + range_count2) * sizeof(RangeType *));
+	range_count3 = 0;
+
+	/*
+	 * For each range in mr1, keep subtracting until it's gone or the ranges
+	 * in mr2 have passed it. After a subtraction we assign what's left back
+	 * to r1. The parallel progress through mr1 and mr2 is similar to
+	 * multirange_overlaps_multirange_internal.
+	 */
+	r2 = ranges2[0];
+	for (i1 = 0, i2 = 0; i1 < range_count1; i1++)
+	{
+		r1 = ranges1[i1];
+
+		/* Discard r2s while r2 << r1 */
+		while (r2 != NULL && range_before_internal(rangetyp, r2, r1))
+		{
+			r2 = ++i2 >= range_count2 ? NULL : ranges2[i2];
+		}
+
+		while (r2 != NULL)
+		{
+			if (range_split_internal(rangetyp, r1, r2, &ranges3[range_count3], &r1))
+			{
+				/*
+				 * If r2 takes a bite out of the middle of r1, we need two
+				 * outputs
+				 */
+				range_count3++;
+				r2 = ++i2 >= range_count2 ? NULL : ranges2[i2];
+
+			}
+			else if (range_overlaps_internal(rangetyp, r1, r2))
+			{
+				/*
+				 * If r2 overlaps r1, replace r1 with r1 - r2.
+				 */
+				r1 = range_minus_internal(rangetyp, r1, r2);
+
+				/*
+				 * If r2 goes past r1, then we need to stay with it, in case
+				 * it hits future r1s. Otherwise we need to keep r1, in case
+				 * future r2s hit it. Since we already subtracted, there's no
+				 * point in using the overright/overleft calls.
+				 */
+				if (RangeIsEmpty(r1) || range_before_internal(rangetyp, r1, r2))
+					break;
+				else
+					r2 = ++i2 >= range_count2 ? NULL : ranges2[i2];
+
+			}
+			else
+			{
+				/*
+				 * This and all future r2s are past r1, so keep them. Also
+				 * assign whatever is left of r1 to the result.
+				 */
+				break;
+			}
+		}
+
+		/*
+		 * Nothing else can remove anything from r1, so keep it. Even if r1 is
+		 * empty here, make_multirange will remove it.
+		 */
+		ranges3[range_count3++] = r1;
+	}
+
+	return make_multirange(mltrngtypoid, rangetyp, range_count3, ranges3);
+}
+
+/* multirange intersection */
+Datum
+multirange_intersect(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
+	MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
+	Oid			mltrngtypoid = MultirangeTypeGetOid(mr1);
+	TypeCacheEntry *typcache;
+	TypeCacheEntry *rangetyp;
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+
+	typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
+	rangetyp = typcache->rngtype;
+
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		PG_RETURN_MULTIRANGE_P(make_empty_multirange(mltrngtypoid, rangetyp));
+
+	multirange_deserialize(mr1, &range_count1, &ranges1);
+	multirange_deserialize(mr2, &range_count2, &ranges2);
+
+	PG_RETURN_MULTIRANGE_P(multirange_intersect_internal(mltrngtypoid,
+														 rangetyp,
+														 range_count1,
+														 ranges1,
+														 range_count2,
+														 ranges2));
+}
+
+MultirangeType *
+multirange_intersect_internal(Oid mltrngtypoid, TypeCacheEntry *rangetyp,
+							  int32 range_count1, RangeType **ranges1,
+							  int32 range_count2, RangeType **ranges2)
+{
+	RangeType  *r1;
+	RangeType  *r2;
+	RangeType **ranges3;
+	int32		range_count3;
+	int32		i1;
+	int32		i2;
+
+	if (range_count1 == 0 || range_count2 == 0)
+		return make_multirange(mltrngtypoid, rangetyp, 0, NULL);
+
+	/*-----------------------------------------------
+	 * Worst case is a stitching pattern like this:
+	 *
+	 * mr1: --- --- --- ---
+	 * mr2:   --- --- ---
+	 * mr3:   - - - - - -
+	 *
+	 * That seems to be range_count1 + range_count2 - 1,
+	 * but one extra won't hurt.
+	 *-----------------------------------------------
+	 */
+	ranges3 = palloc0((range_count1 + range_count2) * sizeof(RangeType *));
+	range_count3 = 0;
+
+	/*
+	 * For each range in mr1, keep intersecting until the ranges in mr2 have
+	 * passed it. The parallel progress through mr1 and mr2 is similar to
+	 * multirange_minus_multirange_internal, but we don't have to assign back
+	 * to r1.
+	 */
+	r2 = ranges2[0];
+	for (i1 = 0, i2 = 0; i1 < range_count1; i1++)
+	{
+		r1 = ranges1[i1];
+
+		/* Discard r2s while r2 << r1 */
+		while (r2 != NULL && range_before_internal(rangetyp, r2, r1))
+		{
+			r2 = ++i2 >= range_count2 ? NULL : ranges2[i2];
+		}
+
+		while (r2 != NULL)
+		{
+			if (range_overlaps_internal(rangetyp, r1, r2))
+			{
+				/* Keep the overlapping part */
+				ranges3[range_count3++] = range_intersect_internal(rangetyp, r1, r2);
+
+				/* If we "used up" all of r2, go to the next one... */
+				if (range_overleft_internal(rangetyp, r2, r1))
+					r2 = ++i2 >= range_count2 ? NULL : ranges2[i2];
+
+				/* ...otherwise go to the next r1 */
+				else
+					break;
+			}
+			else
+				/* We're past r1, so move to the next one */
+				break;
+		}
+
+		/* If we're out of r2s, there can be no more intersections */
+		if (r2 == NULL)
+			break;
+	}
+
+	return make_multirange(mltrngtypoid, rangetyp, range_count3, ranges3);
+}
+
+/*
+ * range_agg_transfn: combine adjacent/overlapping ranges.
+ *
+ * All we do here is gather the input ranges into an array
+ * so that the finalfn can sort and combine them.
+ */
+Datum
+range_agg_transfn(PG_FUNCTION_ARGS)
+{
+	MemoryContext aggContext;
+	Oid			rngtypoid;
+	ArrayBuildState *state;
+
+	if (!AggCheckCallContext(fcinfo, &aggContext))
+		elog(ERROR, "range_agg_transfn called in non-aggregate context");
+
+	rngtypoid = get_fn_expr_argtype(fcinfo->flinfo, 1);
+	if (!type_is_range(rngtypoid))
+		ereport(ERROR,
+				(errcode(ERRCODE_DATATYPE_MISMATCH),
+				 errmsg("range_agg must be called with a range")));
+
+	if (PG_ARGISNULL(0))
+		state = initArrayResult(rngtypoid, aggContext, false);
+	else
+		state = (ArrayBuildState *) PG_GETARG_POINTER(0);
+
+	/* skip NULLs */
+	if (!PG_ARGISNULL(1))
+		accumArrayResult(state, PG_GETARG_DATUM(1), false, rngtypoid, aggContext);
+
+	PG_RETURN_POINTER(state);
+}
+
+/*
+ * range_agg_finalfn: use our internal array to merge touching ranges.
+ */
+Datum
+range_agg_finalfn(PG_FUNCTION_ARGS)
+{
+	MemoryContext aggContext;
+	Oid			mltrngtypoid;
+	TypeCacheEntry *typcache;
+	ArrayBuildState *state;
+	int32		range_count;
+	RangeType **ranges;
+	int			i;
+
+	if (!AggCheckCallContext(fcinfo, &aggContext))
+		elog(ERROR, "range_agg_finalfn called in non-aggregate context");
+
+	state = PG_ARGISNULL(0) ? NULL : (ArrayBuildState *) PG_GETARG_POINTER(0);
+	if (state == NULL)
+		/* This shouldn't be possible, but just in case.... */
+		PG_RETURN_NULL();
+
+	/* Also return NULL if we had zero inputs, like other aggregates */
+	range_count = state->nelems;
+	if (range_count == 0)
+		PG_RETURN_NULL();
+
+	mltrngtypoid = get_fn_expr_rettype(fcinfo->flinfo);
+	typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
+
+	ranges = palloc0(range_count * sizeof(RangeType *));
+	for (i = 0; i < range_count; i++)
+		ranges[i] = DatumGetRangeTypeP(state->dvalues[i]);
+
+	PG_RETURN_MULTIRANGE_P(make_multirange(mltrngtypoid, typcache->rngtype, range_count, ranges));
+}
+
+Datum
+multirange_intersect_agg_transfn(PG_FUNCTION_ARGS)
+{
+	MemoryContext aggContext;
+	Oid			mltrngtypoid;
+	TypeCacheEntry *typcache;
+	MultirangeType *result;
+	MultirangeType *current;
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+
+	if (!AggCheckCallContext(fcinfo, &aggContext))
+		elog(ERROR, "multirange_intersect_agg_transfn called in non-aggregate context");
+
+	mltrngtypoid = get_fn_expr_argtype(fcinfo->flinfo, 1);
+	if (!type_is_multirange(mltrngtypoid))
+		ereport(ERROR,
+				(errcode(ERRCODE_DATATYPE_MISMATCH),
+				 errmsg("range_intersect_agg must be called with a multirange")));
+
+	typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
+
+	/* strictness ensures these are non-null */
+	result = PG_GETARG_MULTIRANGE_P(0);
+	current = PG_GETARG_MULTIRANGE_P(1);
+
+	multirange_deserialize(result, &range_count1, &ranges1);
+	multirange_deserialize(current, &range_count2, &ranges2);
+
+	result = multirange_intersect_internal(mltrngtypoid,
+										   typcache->rngtype,
+										   range_count1,
+										   ranges1,
+										   range_count2,
+										   ranges2);
+	PG_RETURN_RANGE_P(result);
+}
+
+
+/* multirange -> element type functions */
+
+/* extract lower bound value */
+Datum
+multirange_lower(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+	bool		isnull;
+	Datum		result;
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_NULL();
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	result = range_lower_internal(typcache->rngtype, ranges[0], &isnull);
+
+	if (isnull)
+		PG_RETURN_NULL();
+	else
+		PG_RETURN_DATUM(result);
+}
+
+/* extract upper bound value */
+Datum
+multirange_upper(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+	bool		isnull;
+	Datum		result;
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_NULL();
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	result = range_upper_internal(typcache->rngtype, ranges[range_count - 1], &isnull);
+
+	if (isnull)
+		PG_RETURN_NULL();
+	else
+		PG_RETURN_DATUM(result);
+}
+
+
+/* multirange -> bool functions */
+
+/* is multirange empty? */
+Datum
+multirange_empty(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+
+	PG_RETURN_BOOL(mr->rangeCount == 0);
+}
+
+/* is lower bound inclusive? */
+Datum
+multirange_lower_inc(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	int32		range_count;
+	RangeType **ranges;
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_has_flag(ranges[0], RANGE_LB_INC));
+}
+
+/* is upper bound inclusive? */
+Datum
+multirange_upper_inc(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	int32		range_count;
+	RangeType **ranges;
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_has_flag(ranges[range_count - 1], RANGE_UB_INC));
+}
+
+/* is lower bound infinite? */
+Datum
+multirange_lower_inf(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	int32		range_count;
+	RangeType **ranges;
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_has_flag(ranges[0], RANGE_LB_INF));
+}
+
+/* is upper bound infinite? */
+Datum
+multirange_upper_inf(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	int32		range_count;
+	RangeType **ranges;
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_has_flag(ranges[range_count - 1], RANGE_UB_INF));
+}
+
+
+
+/* multirange, element -> bool functions */
+
+/* contains? */
+Datum
+multirange_contains_elem(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	Datum		val = PG_GETARG_DATUM(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(multirange_contains_elem_internal(typcache, mr, val));
+}
+
+/* contained by? */
+Datum
+elem_contained_by_multirange(PG_FUNCTION_ARGS)
+{
+	Datum		val = PG_GETARG_DATUM(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(multirange_contains_elem_internal(typcache, mr, val));
+}
+
+/*
+ * Test whether multirange mr contains a specific element value.
+ */
+bool
+multirange_contains_elem_internal(TypeCacheEntry *typcache, MultirangeType *mr, Datum val)
+{
+	TypeCacheEntry *rangetyp;
+	int32		range_count;
+	RangeType **ranges;
+	RangeType  *r;
+	int			i;
+
+	rangetyp = typcache->rngtype;
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	for (i = 0; i < range_count; i++)
+	{
+		r = ranges[i];
+		if (range_contains_elem_internal(rangetyp, r, val))
+			return true;
+	}
+
+	return false;
+}
+
+/* multirange, range -> bool functions */
+
+/* contains? */
+Datum
+multirange_contains_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(multirange_contains_range_internal(typcache, mr, r));
+}
+
+/* contained by? */
+Datum
+range_contained_by_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(multirange_contains_range_internal(typcache, mr, r));
+}
+
+/*
+ * Test whether multirange mr contains a specific range r.
+ */
+bool
+multirange_contains_range_internal(TypeCacheEntry *typcache, MultirangeType *mr, RangeType *r)
+{
+	TypeCacheEntry *rangetyp;
+	int32		range_count;
+	RangeType **ranges;
+	RangeType  *mrr;
+	int			i;
+
+	rangetyp = typcache->rngtype;
+
+	/*
+	 * Every multirange contains an infinite number of empty ranges, even an
+	 * empty one.
+	 */
+	if (RangeIsEmpty(r))
+		return true;
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	for (i = 0; i < range_count; i++)
+	{
+		mrr = ranges[i];
+		if (range_contains_internal(rangetyp, mrr, r))
+			return true;
+	}
+
+	return false;
+}
+
+
+/* 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));
+}
+
+/* overlaps? */
+Datum
+range_overlaps_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(range_overlaps_multirange_internal(typcache, r, mr));
+}
+
+Datum
+multirange_overlaps_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(range_overlaps_multirange_internal(typcache, r, mr));
+}
+
+Datum
+multirange_overlaps_multirange(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_overlaps_multirange_internal(typcache, mr1, mr2));
+}
+
+bool
+range_overlaps_multirange_internal(TypeCacheEntry *typcache, RangeType *r, MultirangeType *mr)
+{
+	TypeCacheEntry *rangetyp;
+	int32		range_count;
+	int32		i;
+	RangeType **ranges;
+	RangeType  *mrr;
+
+	/*
+	 * Empties never overlap, even with empties. (This seems strange since
+	 * they *do* contain each other, but we want to follow how ranges work.)
+	 */
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		return false;
+
+	rangetyp = typcache->rngtype;
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	/* Scan through mrr and once it gets to r they either overlap or not */
+	mrr = ranges[0];
+
+	/* Discard mrrs while mrr << r */
+	i = 0;
+	while (range_before_internal(rangetyp, mrr, r))
+	{
+		if (++i >= range_count)
+			return false;
+		mrr = ranges[i];
+	}
+
+	/* Now either we overlap or we passed r */
+	return range_overlaps_internal(rangetyp, mrr, r);
+}
+
+bool
+multirange_overlaps_multirange_internal(TypeCacheEntry *typcache, MultirangeType *mr1,
+										MultirangeType *mr2)
+{
+	TypeCacheEntry *rangetyp;
+	int32		range_count1;
+	int32		range_count2;
+	int32		i1;
+	int32		i2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+	RangeType  *r1;
+	RangeType  *r2;
+
+	/*
+	 * Empties never overlap, even with empties. (This seems strange since
+	 * they *do* contain each other, but we want to follow how ranges work.)
+	 */
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		return false;
+
+	rangetyp = typcache->rngtype;
+
+	multirange_deserialize(mr1, &range_count1, &ranges1);
+	multirange_deserialize(mr2, &range_count2, &ranges2);
+
+	/*
+	 * Every range in mr1 gets a chance to overlap with the ranges in mr2, but
+	 * we can use their ordering to avoid O(n^2). This is similar to
+	 * range_overlaps_multirange where r1 : r2 :: mrr : r, but there if we
+	 * don't find an overlap with r we're done, and here if we don't find an
+	 * overlap with r2 we try the next r2.
+	 */
+	r1 = ranges1[0];
+	for (i1 = 0, i2 = 0; i2 < range_count2; i2++)
+	{
+		r2 = ranges2[i2];
+
+		/* Discard r1s while r1 << r2 */
+		while (range_before_internal(rangetyp, r1, r2))
+		{
+			if (++i1 >= range_count1)
+				return false;
+			r1 = ranges1[i1];
+		}
+
+		/*
+		 * If r1 && r2, we're done, otherwise we failed to find an overlap for
+		 * r2, so go to the next one.
+		 */
+		if (range_overlaps_internal(rangetyp, r1, r2))
+			return true;
+	}
+
+	/* We looked through all of mr2 without finding an overlap */
+	return false;
+}
+
+/* does not extend to right of? */
+Datum
+range_overleft_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_overleft_internal(typcache->rngtype, r, ranges[range_count - 1]));
+}
+
+Datum
+multirange_overleft_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_overleft_internal(typcache->rngtype,
+										   ranges[range_count - 1], r));
+}
+
+Datum
+multirange_overleft_multirange(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
+	MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr1));
+
+	multirange_deserialize(mr1, &range_count1, &ranges1);
+	multirange_deserialize(mr2, &range_count2, &ranges2);
+
+	PG_RETURN_BOOL(range_overleft_internal(typcache->rngtype,
+										   ranges1[range_count1 - 1],
+										   ranges2[range_count2 - 1]));
+}
+
+/* does not extend to left of? */
+Datum
+range_overright_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_overright_internal(typcache->rngtype, r, ranges[0]));
+}
+
+Datum
+multirange_overright_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_overright_internal(typcache->rngtype, ranges[0], r));
+}
+
+Datum
+multirange_overright_multirange(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
+	MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr1));
+
+	multirange_deserialize(mr1, &range_count1, &ranges1);
+	multirange_deserialize(mr2, &range_count2, &ranges2);
+
+	PG_RETURN_BOOL(range_overright_internal(typcache->rngtype,
+											ranges1[0],
+											ranges2[0]));
+}
+
+/* contains? */
+Datum
+multirange_contains_multirange(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_contains_multirange_internal(typcache, mr1, mr2));
+}
+
+/* contained by? */
+Datum
+multirange_contained_by_multirange(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_contains_multirange_internal(typcache, mr2, mr1));
+}
+
+/*
+ * Test whether multirange mr1 contains every range from another multirange mr2.
+ */
+bool
+multirange_contains_multirange_internal(TypeCacheEntry *typcache,
+										MultirangeType *mr1, MultirangeType *mr2)
+{
+	TypeCacheEntry *rangetyp;
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+	RangeType  *r1;
+	RangeType  *r2;
+	int			i1,
+				i2;
+
+	rangetyp = typcache->rngtype;
+
+	multirange_deserialize(mr1, &range_count1, &ranges1);
+	multirange_deserialize(mr2, &range_count2, &ranges2);
+
+	/*
+	 * We follow the same logic for empties as ranges: - an empty multirange
+	 * contains an empty range/multirange. - an empty multirange can't contain
+	 * any other range/multirange. - an empty multirange is contained by any
+	 * other range/multirange.
+	 */
+
+	if (range_count2 == 0)
+		return true;
+	if (range_count1 == 0)
+		return false;
+
+	/*
+	 * Every range in mr2 must be contained by some range in mr1. To avoid
+	 * O(n^2) we walk through both ranges in tandem.
+	 */
+	r1 = ranges1[0];
+	for (i1 = 0, i2 = 0; i2 < range_count2; i2++)
+	{
+		r2 = ranges2[i2];
+
+		/* Discard r1s while r1 << r2 */
+		while (range_before_internal(rangetyp, r1, r2))
+		{
+			if (++i1 >= range_count1)
+				return false;
+			r1 = ranges1[i1];
+		}
+
+		/*
+		 * If r1 @> r2, go to the next r2, otherwise return false (since every
+		 * r1[n] and r1[n+1] must have a gap). Note this will give weird
+		 * answers if you don't canonicalize, e.g. with a custom
+		 * int2multirange {[1,1], [2,2]} there is a "gap". But that is
+		 * consistent with other range operators, e.g. '[1,1]'::int2range -|-
+		 * '[2,2]'::int2range is false.
+		 */
+		if (!range_contains_internal(rangetyp, r1, r2))
+			return false;
+	}
+
+	/* All ranges in mr2 are satisfied */
+	return true;
+}
+
+/* strictly left of? */
+Datum
+range_before_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(range_before_multirange_internal(typcache, r, mr));
+}
+
+Datum
+multirange_before_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(range_after_multirange_internal(typcache, r, mr));
+}
+
+Datum
+multirange_before_multirange(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_before_multirange_internal(typcache, mr1, mr2));
+}
+
+/* strictly right of? */
+Datum
+range_after_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(range_after_multirange_internal(typcache, r, mr));
+}
+
+Datum
+multirange_after_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(range_before_multirange_internal(typcache, r, mr));
+}
+
+Datum
+multirange_after_multirange(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_before_multirange_internal(typcache, mr2, mr1));
+}
+
+/* strictly left of? (internal version) */
+bool
+range_before_multirange_internal(TypeCacheEntry *typcache, RangeType *r,
+								 MultirangeType *mr)
+{
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		return false;
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	return range_before_internal(typcache->rngtype, r, ranges[0]);
+}
+
+bool
+multirange_before_multirange_internal(TypeCacheEntry *typcache, MultirangeType *mr1,
+									  MultirangeType *mr2)
+{
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		return false;
+
+	multirange_deserialize(mr1, &range_count1, &ranges1);
+	multirange_deserialize(mr2, &range_count2, &ranges2);
+
+	return range_before_internal(typcache->rngtype, ranges1[range_count1 - 1],
+								 ranges2[0]);
+}
+
+/* strictly right of? (internal version) */
+bool
+range_after_multirange_internal(TypeCacheEntry *typcache, RangeType *r,
+								MultirangeType *mr)
+{
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		return false;
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	return range_after_internal(typcache->rngtype, r, ranges[range_count - 1]);
+}
+
+/* adjacent to? */
+Datum
+range_adjacent_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		return false;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_adjacent_internal(typcache->rngtype, r, ranges[0]));
+}
+
+Datum
+multirange_adjacent_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		return false;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_adjacent_internal(typcache->rngtype, ranges[range_count - 1], r));
+}
+
+Datum
+multirange_adjacent_multirange(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
+	MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		return false;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr1));
+
+	multirange_deserialize(mr1, &range_count1, &ranges1);
+	multirange_deserialize(mr2, &range_count2, &ranges2);
+
+	PG_RETURN_BOOL(range_adjacent_internal(typcache->rngtype, ranges1[range_count1 - 1], ranges2[0]));
+}
+
+/* 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);
+}
+
+/* multirange -> range functions */
+
+/* Find the smallest range that includes everything in the multirange */
+Datum
+range_merge_from_multirange(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	Oid			mltrngtypoid = MultirangeTypeGetOid(mr);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_RANGE_P(make_empty_range(typcache->rngtype));
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_RANGE_P(range_union_internal(typcache->rngtype, ranges[0],
+										   ranges[range_count - 1], false));
+}
+
+/* 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/pg_upgrade_support.c b/src/backend/utils/adt/pg_upgrade_support.c
index 18f2ee8226..248ea7f44a 100644
--- a/src/backend/utils/adt/pg_upgrade_support.c
+++ b/src/backend/utils/adt/pg_upgrade_support.c
@@ -52,6 +52,28 @@ binary_upgrade_set_next_array_pg_type_oid(PG_FUNCTION_ARGS)
 }
 
 Datum
+binary_upgrade_set_next_multirange_pg_type_oid(PG_FUNCTION_ARGS)
+{
+	Oid			typoid = PG_GETARG_OID(0);
+
+	CHECK_IS_BINARY_UPGRADE;
+	binary_upgrade_next_mrng_pg_type_oid = typoid;
+
+	PG_RETURN_VOID();
+}
+
+Datum
+binary_upgrade_set_next_multirange_array_pg_type_oid(PG_FUNCTION_ARGS)
+{
+	Oid			typoid = PG_GETARG_OID(0);
+
+	CHECK_IS_BINARY_UPGRADE;
+	binary_upgrade_next_mrng_array_pg_type_oid = typoid;
+
+	PG_RETURN_VOID();
+}
+
+Datum
 binary_upgrade_set_next_toast_pg_type_oid(PG_FUNCTION_ARGS)
 {
 	Oid			typoid = PG_GETARG_OID(0);
diff --git a/src/backend/utils/adt/pseudotypes.c b/src/backend/utils/adt/pseudotypes.c
index 4653fc33e6..c909c9dc8c 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"
 
 
 /*
@@ -185,6 +186,30 @@ anyrange_out(PG_FUNCTION_ARGS)
 }
 
 /*
+ * 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.
  *
  * We allow this so that PL functions can return VOID without any special
diff --git a/src/backend/utils/adt/rangetypes.c b/src/backend/utils/adt/rangetypes.c
index 01ad8bc240..0af3c67296 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
 {
@@ -431,19 +429,37 @@ range_lower(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
 	TypeCacheEntry *typcache;
+	bool		isnull;
+	Datum		result;
+
+	typcache = range_get_typcache(fcinfo, RangeTypeGetOid(r1));
+
+	result = range_lower_internal(typcache, r1, &isnull);
+
+	if (isnull)
+		PG_RETURN_NULL();
+	else
+		PG_RETURN_DATUM(result);
+}
+
+Datum
+range_lower_internal(TypeCacheEntry *typcache, const RangeType *r1, bool *isnull)
+{
 	RangeBound	lower;
 	RangeBound	upper;
 	bool		empty;
 
-	typcache = range_get_typcache(fcinfo, RangeTypeGetOid(r1));
-
 	range_deserialize(typcache, r1, &lower, &upper, &empty);
 
 	/* Return NULL if there's no finite lower bound */
 	if (empty || lower.infinite)
-		PG_RETURN_NULL();
+	{
+		*isnull = true;
+		return 0;
+	}
 
-	PG_RETURN_DATUM(lower.val);
+	*isnull = false;
+	return lower.val;
 }
 
 /* extract upper bound value */
@@ -452,19 +468,37 @@ range_upper(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
 	TypeCacheEntry *typcache;
+	bool		isnull;
+	Datum		result;
+
+	typcache = range_get_typcache(fcinfo, RangeTypeGetOid(r1));
+
+	result = range_upper_internal(typcache, r1, &isnull);
+
+	if (isnull)
+		PG_RETURN_NULL();
+	else
+		PG_RETURN_DATUM(result);
+}
+
+Datum
+range_upper_internal(TypeCacheEntry *typcache, const RangeType *r1, bool *isnull)
+{
 	RangeBound	lower;
 	RangeBound	upper;
 	bool		empty;
 
-	typcache = range_get_typcache(fcinfo, RangeTypeGetOid(r1));
-
 	range_deserialize(typcache, r1, &lower, &upper, &empty);
 
 	/* Return NULL if there's no finite upper bound */
 	if (empty || upper.infinite)
-		PG_RETURN_NULL();
+	{
+		*isnull = true;
+		return 0;
+	}
 
-	PG_RETURN_DATUM(upper.val);
+	*isnull = false;
+	return upper.val;
 }
 
 
@@ -475,9 +509,8 @@ Datum
 range_empty(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
-	char		flags = range_get_flags(r1);
 
-	PG_RETURN_BOOL(flags & RANGE_EMPTY);
+	PG_RETURN_BOOL(range_has_flag(r1, RANGE_EMPTY));
 }
 
 /* is lower bound inclusive? */
@@ -485,9 +518,8 @@ Datum
 range_lower_inc(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
-	char		flags = range_get_flags(r1);
 
-	PG_RETURN_BOOL(flags & RANGE_LB_INC);
+	PG_RETURN_BOOL(range_has_flag(r1, RANGE_LB_INC));
 }
 
 /* is upper bound inclusive? */
@@ -495,9 +527,8 @@ Datum
 range_upper_inc(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
-	char		flags = range_get_flags(r1);
 
-	PG_RETURN_BOOL(flags & RANGE_UB_INC);
+	PG_RETURN_BOOL(range_has_flag(r1, RANGE_UB_INC));
 }
 
 /* is lower bound infinite? */
@@ -505,9 +536,8 @@ Datum
 range_lower_inf(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
-	char		flags = range_get_flags(r1);
 
-	PG_RETURN_BOOL(flags & RANGE_LB_INF);
+	PG_RETURN_BOOL(range_has_flag(r1, RANGE_LB_INF));
 }
 
 /* is upper bound infinite? */
@@ -515,9 +545,8 @@ Datum
 range_upper_inf(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
-	char		flags = range_get_flags(r1);
 
-	PG_RETURN_BOOL(flags & RANGE_UB_INF);
+	PG_RETURN_BOOL(range_has_flag(r1, RANGE_UB_INF));
 }
 
 
@@ -957,7 +986,25 @@ range_minus(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
 	RangeType  *r2 = PG_GETARG_RANGE_P(1);
+	RangeType  *ret;
 	TypeCacheEntry *typcache;
+
+	/* 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));
+
+	ret = range_minus_internal(typcache, r1, r2);
+	if (ret)
+		PG_RETURN_RANGE_P(ret);
+	else
+		PG_RETURN_NULL();
+}
+
+RangeType *
+range_minus_internal(TypeCacheEntry *typcache, RangeType *r1, RangeType *r2)
+{
 	RangeBound	lower1,
 				lower2;
 	RangeBound	upper1,
@@ -969,18 +1016,12 @@ range_minus(PG_FUNCTION_ARGS)
 				cmp_u1l2,
 				cmp_u1u2;
 
-	/* 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));
-
 	range_deserialize(typcache, r1, &lower1, &upper1, &empty1);
 	range_deserialize(typcache, r2, &lower2, &upper2, &empty2);
 
 	/* if either is empty, r1 is the correct answer */
 	if (empty1 || empty2)
-		PG_RETURN_RANGE_P(r1);
+		return r1;
 
 	cmp_l1l2 = range_cmp_bounds(typcache, &lower1, &lower2);
 	cmp_l1u2 = range_cmp_bounds(typcache, &lower1, &upper2);
@@ -993,34 +1034,34 @@ range_minus(PG_FUNCTION_ARGS)
 				 errmsg("result of range difference would not be contiguous")));
 
 	if (cmp_l1u2 > 0 || cmp_u1l2 < 0)
-		PG_RETURN_RANGE_P(r1);
+		return r1;
 
 	if (cmp_l1l2 >= 0 && cmp_u1u2 <= 0)
-		PG_RETURN_RANGE_P(make_empty_range(typcache));
+		return make_empty_range(typcache);
 
 	if (cmp_l1l2 <= 0 && cmp_u1l2 >= 0 && cmp_u1u2 <= 0)
 	{
 		lower2.inclusive = !lower2.inclusive;
 		lower2.lower = false;	/* it will become the upper bound */
-		PG_RETURN_RANGE_P(make_range(typcache, &lower1, &lower2, false));
+		return make_range(typcache, &lower1, &lower2, false);
 	}
 
 	if (cmp_l1l2 >= 0 && cmp_u1u2 >= 0 && cmp_l1u2 <= 0)
 	{
 		upper2.inclusive = !upper2.inclusive;
 		upper2.lower = true;	/* it will become the lower bound */
-		PG_RETURN_RANGE_P(make_range(typcache, &upper2, &upper1, false));
+		return make_range(typcache, &upper2, &upper1, false);
 	}
 
 	elog(ERROR, "unexpected case in range_minus");
-	PG_RETURN_NULL();
+	return NULL;
 }
 
 /*
  * Set union.  If strict is true, it is an error that the two input ranges
  * are not adjacent or overlapping.
  */
-static RangeType *
+RangeType *
 range_union_internal(TypeCacheEntry *typcache, RangeType *r1, RangeType *r2,
 					 bool strict)
 {
@@ -1101,6 +1142,19 @@ range_intersect(PG_FUNCTION_ARGS)
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
 	RangeType  *r2 = PG_GETARG_RANGE_P(1);
 	TypeCacheEntry *typcache;
+
+	/* 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));
+
+	PG_RETURN_RANGE_P(range_intersect_internal(typcache, r1, r2));
+}
+
+RangeType *
+range_intersect_internal(TypeCacheEntry *typcache, const RangeType *r1, const RangeType *r2)
+{
 	RangeBound	lower1,
 				lower2;
 	RangeBound	upper1,
@@ -1110,17 +1164,11 @@ range_intersect(PG_FUNCTION_ARGS)
 	RangeBound *result_lower;
 	RangeBound *result_upper;
 
-	/* 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));
-
 	range_deserialize(typcache, r1, &lower1, &upper1, &empty1);
 	range_deserialize(typcache, r2, &lower2, &upper2, &empty2);
 
-	if (empty1 || empty2 || !DatumGetBool(range_overlaps(fcinfo)))
-		PG_RETURN_RANGE_P(make_empty_range(typcache));
+	if (empty1 || empty2 || !range_overlaps_internal(typcache, r1, r2))
+		return make_empty_range(typcache);
 
 	if (range_cmp_bounds(typcache, &lower1, &lower2) >= 0)
 		result_lower = &lower1;
@@ -1132,9 +1180,81 @@ range_intersect(PG_FUNCTION_ARGS)
 	else
 		result_upper = &upper2;
 
-	PG_RETURN_RANGE_P(make_range(typcache, result_lower, result_upper, false));
+	return make_range(typcache, result_lower, result_upper, false);
+}
+
+/* range, range -> range, range functions */
+
+/*
+ * range_split_internal - if r2 intersects the middle of r1, leaving non-empty
+ * ranges on both sides, then return true and set output1 and output2 to the
+ * results of r1 - r2 (in order). Otherwise return false and don't set output1
+ * or output2. Neither input range should be empty.
+ */
+bool
+range_split_internal(TypeCacheEntry *typcache, const RangeType *r1, const RangeType *r2,
+					 RangeType **output1, RangeType **output2)
+{
+	RangeBound	lower1,
+				lower2;
+	RangeBound	upper1,
+				upper2;
+	bool		empty1,
+				empty2;
+
+	range_deserialize(typcache, r1, &lower1, &upper1, &empty1);
+	range_deserialize(typcache, r2, &lower2, &upper2, &empty2);
+
+	if (range_cmp_bounds(typcache, &lower1, &lower2) < 0 &&
+		range_cmp_bounds(typcache, &upper1, &upper2) > 0)
+	{
+		/*
+		 * Need to invert inclusive/exclusive for the lower2 and upper2
+		 * points. They can't be infinite though. We're allowed to overwrite
+		 * these RangeBounds since they only exist locally.
+		 */
+		lower2.inclusive = !lower2.inclusive;
+		lower2.lower = false;
+		upper2.inclusive = !upper2.inclusive;
+		upper2.lower = true;
+
+		*output1 = make_range(typcache, &lower1, &lower2, false);
+		*output2 = make_range(typcache, &upper2, &upper1, false);
+		return true;
+	}
+
+	return false;
 }
 
+/* range -> range aggregate functions */
+
+Datum
+range_intersect_agg_transfn(PG_FUNCTION_ARGS)
+{
+	MemoryContext aggContext;
+	Oid			rngtypoid;
+	TypeCacheEntry *typcache;
+	RangeType  *result;
+	RangeType  *current;
+
+	if (!AggCheckCallContext(fcinfo, &aggContext))
+		elog(ERROR, "range_intersect_agg_transfn called in non-aggregate context");
+
+	rngtypoid = get_fn_expr_argtype(fcinfo->flinfo, 1);
+	if (!type_is_range(rngtypoid))
+		ereport(ERROR, (errmsg("range_intersect_agg must be called with a range")));
+
+	typcache = range_get_typcache(fcinfo, rngtypoid);
+
+	/* strictness ensures these are non-null */
+	result = PG_GETARG_RANGE_P(0);
+	current = PG_GETARG_RANGE_P(1);
+
+	result = range_intersect_internal(typcache, result, current);
+	PG_RETURN_RANGE_P(result);
+}
+
+
 /* Btree support */
 
 /* btree comparator */
@@ -1144,12 +1264,6 @@ range_cmp(PG_FUNCTION_ARGS)
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
 	RangeType  *r2 = PG_GETARG_RANGE_P(1);
 	TypeCacheEntry *typcache;
-	RangeBound	lower1,
-				lower2;
-	RangeBound	upper1,
-				upper2;
-	bool		empty1,
-				empty2;
 	int			cmp;
 
 	check_stack_depth();		/* recurses when subtype is a range type */
@@ -1160,6 +1274,28 @@ range_cmp(PG_FUNCTION_ARGS)
 
 	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, const RangeType *r1, const 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);
 
@@ -1177,10 +1313,7 @@ range_cmp(PG_FUNCTION_ARGS)
 			cmp = range_cmp_bounds(typcache, &upper1, &upper2);
 	}
 
-	PG_FREE_IF_COPY(r1, 0);
-	PG_FREE_IF_COPY(r2, 1);
-
-	PG_RETURN_INT32(cmp);
+	return cmp;
 }
 
 /* inequality operators using the range_cmp function */
@@ -1218,13 +1351,10 @@ range_gt(PG_FUNCTION_ARGS)
 
 /* Hash support */
 
-/* hash a range value */
-Datum
-hash_range(PG_FUNCTION_ARGS)
+uint32
+hash_range_internal(TypeCacheEntry *typcache, const RangeType *r)
 {
-	RangeType  *r = PG_GETARG_RANGE_P(0);
 	uint32		result;
-	TypeCacheEntry *typcache;
 	TypeCacheEntry *scache;
 	RangeBound	lower;
 	RangeBound	upper;
@@ -1233,10 +1363,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 +1404,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, const RangeType *r, Datum seed)
+{
+	uint64		result;
 	TypeCacheEntry *scache;
 	RangeBound	lower;
 	RangeBound	upper;
@@ -1300,10 +1433,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 +1471,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));
 }
 
 /*
@@ -1770,6 +1917,21 @@ range_get_flags(const RangeType *range)
 }
 
 /*
+ * range_has_flag: set whether a range has a specific flag.
+ *
+ * This lets expose some of our functions that just check flags
+ * to the rest of the code base (like to multiranges)
+ * without writing full-fledged *_internal versions.
+ */
+bool
+range_has_flag(const RangeType *r1, char flag)
+{
+	char		flags = range_get_flags(r1);
+
+	return flags & flag;
+}
+
+/*
  * range_set_contain_empty: set the RANGE_CONTAIN_EMPTY bit in the value.
  *
  * This is only needed in GiST operations, so we don't include a provision
@@ -1938,6 +2100,46 @@ range_cmp_bound_values(TypeCacheEntry *typcache, const RangeBound *b1,
 }
 
 /*
+ * qsort callback for sorting ranges.
+ *
+ * Two empty ranges compare equal; an empty range sorts to the left of any
+ * non-empty range.  Two non-empty ranges are sorted by lower bound first
+ * and by upper bound next.
+ */
+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.
  */
 RangeType *
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index 27bbb58f56..c6c079c623 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -2495,6 +2495,16 @@ type_is_range(Oid typid)
 }
 
 /*
+ * 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
  *
  *		Given the type OID, fetch its category and preferred-type status.
@@ -3149,7 +3159,7 @@ get_namespace_name_or_temp(Oid nspid)
 		return get_namespace_name(nspid);
 }
 
-/*				---------- PG_RANGE CACHE ----------				 */
+/*				---------- PG_RANGE CACHES ----------				 */
 
 /*
  * get_range_subtype
@@ -3202,6 +3212,56 @@ get_range_collation(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->rngmultitypid;
+		ReleaseSysCache(tp);
+		return result;
+	}
+	else
+		return InvalidOid;
+}
+
+/*
+ * get_range_multirange_subtype
+ *		Returns the subtype of a given multirange type
+ *
+ * Returns InvalidOid if the type is not a multirange type.
+ */
+Oid
+get_range_multirange_subtype(Oid multirangeOid)
+{
+	HeapTuple	tp;
+
+	tp = SearchSysCache1(RANGEMULTIRANGE, 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 53d9ddf159..7654331a59 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -651,6 +651,18 @@ static const struct cachedesc cacheinfo[] = {
 		},
 		64
 	},
+	{RangeRelationId,			/* RANGEMULTIRANGE */
+		RangeMultirangeTypidIndexId,
+		1,
+		{
+			Anum_pg_range_rngmultitypid,
+			0,
+			0,
+			0
+		},
+		4
+	},
+
 	{RangeRelationId,			/* RANGETYPE */
 		RangeTypidIndexId,
 		1,
diff --git a/src/backend/utils/cache/typcache.c b/src/backend/utils/cache/typcache.c
index 854f133f9b..160846cf10 100644
--- a/src/backend/utils/cache/typcache.c
+++ b/src/backend/utils/cache/typcache.c
@@ -286,6 +286,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);
@@ -302,6 +303,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 TypeCacheTypCallback(Datum arg, int cacheid, uint32 hashvalue);
 static void TypeCacheOpcCallback(Datum arg, int cacheid, uint32 hashvalue);
@@ -553,8 +557,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))
@@ -591,7 +595,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))
@@ -616,7 +620,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))
@@ -641,7 +645,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))
@@ -693,6 +697,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;
@@ -737,6 +748,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;
@@ -817,6 +835,16 @@ lookup_type_cache(Oid type_id, int flags)
 	}
 
 	/*
+	 * 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
 	 */
 	if ((flags & TYPECACHE_DOMAIN_BASE_INFO) &&
@@ -927,6 +955,22 @@ load_rangetype_info(TypeCacheEntry *typentry)
 	typentry->rngelemtype = lookup_type_cache(subtypeOid, 0);
 }
 
+/*
+ * load_multirangetype_info --- helper routine to set up multirange type
+ * information
+ */
+static void
+load_multirangetype_info(TypeCacheEntry *typentry)
+{
+	Oid			rangetypeOid;
+
+	rangetypeOid = get_range_multirange_subtype(typentry->type_id);
+	if (!OidIsValid(rangetypeOid))
+		elog(ERROR, "cache lookup failed for multirange type %u",
+			 typentry->type_id);
+
+	typentry->rngtype = lookup_type_cache(rangetypeOid, TYPECACHE_RANGE_INFO);
+}
 
 /*
  * load_domaintype_info --- helper routine to set up domain constraint info
@@ -1527,11 +1571,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
@@ -1574,6 +1618,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 0201e4f8d3..b10cd99144 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,131 @@ resolve_polymorphic_tupdesc(TupleDesc tupdesc, oidvector *declared_args,
 				return false;
 			anyelement_type = subtype;
 		}
+
+		if (OidIsValid(anymultirange_type))
+		{
+			Oid			rngtype;
+			Oid			subtype;
+
+			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;
+			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;
+			Oid			subtype;
+
+			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;
+
+			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;
+			Oid			mltrngtype;
+			Oid			rngtype;
+
+			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;
+
+			mltrngtype = resolve_generic_type(ANYMULTIRANGEOID,
+											  anyrange_type,
+											  ANYRANGEOID);
+
+			/* check for inconsistent range and multirange results */
+			rngtype = get_range_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 +761,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 +793,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 +856,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 +880,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 +906,132 @@ resolve_polymorphic_argtypes(int numargs, Oid *argtypes, char *argmodes,
 				return false;
 			anyelement_type = subtype;
 		}
+
+		if (OidIsValid(anymultirange_type))
+		{
+			Oid			rngtype;
+			Oid			subtype;
+
+			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;
+
+			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;
+			Oid			subtype;
+
+			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;
+
+			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;
+			Oid			mltrngtype;
+			Oid			rngtype;
+
+			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;
+
+			mltrngtype = resolve_generic_type(ANYMULTIRANGEOID,
+											  anyrange_type,
+											  ANYRANGEOID);
+
+			/* check for inconsistent range and multirange results */
+			rngtype = get_range_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 +1051,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/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 28312e14ef..ba53ac13ba 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -271,7 +271,8 @@ static void dumpSearchPath(Archive *AH);
 static void binary_upgrade_set_type_oids_by_type_oid(Archive *fout,
 													 PQExpBuffer upgrade_buffer,
 													 Oid pg_type_oid,
-													 bool force_array_type);
+													 bool force_array_type,
+													 bool include_multirange_type);
 static bool binary_upgrade_set_type_oids_by_rel_oid(Archive *fout,
 													PQExpBuffer upgrade_buffer, Oid pg_rel_oid);
 static void binary_upgrade_set_pg_class_oids(Archive *fout,
@@ -1580,7 +1581,7 @@ selectDumpableType(TypeInfo *tyinfo, Archive *fout)
 	}
 
 	/* skip auto-generated array types */
-	if (tyinfo->isArray)
+	if (tyinfo->isArray || tyinfo->isMultirange)
 	{
 		tyinfo->dobj.objType = DO_DUMMY_TYPE;
 
@@ -4339,16 +4340,49 @@ append_depends_on_extension(Archive *fout,
 	}
 }
 
+static Oid
+get_next_possible_free_pg_type_oid(Archive *fout, PQExpBuffer upgrade_query)
+{
+	/*
+	 * If the old version didn't assign an array type, but the new version
+	 * does, we must select an unused type OID to assign.  This currently only
+	 * happens for domains, when upgrading pre-v11 to v11 and up.
+	 *
+	 * Note: local state here is kind of ugly, but we must have some, since we
+	 * mustn't choose the same unused OID more than once.
+	 */
+	static Oid	next_possible_free_oid = FirstNormalObjectId;
+	PGresult   *res;
+	bool		is_dup;
+
+	do
+	{
+		++next_possible_free_oid;
+		printfPQExpBuffer(upgrade_query,
+						  "SELECT EXISTS(SELECT 1 "
+						  "FROM pg_catalog.pg_type "
+						  "WHERE oid = '%u'::pg_catalog.oid);",
+						  next_possible_free_oid);
+		res = ExecuteSqlQueryForSingleRow(fout, upgrade_query->data);
+		is_dup = (PQgetvalue(res, 0, 0)[0] == 't');
+		PQclear(res);
+	} while (is_dup);
+
+	return next_possible_free_oid;
+}
 
 static void
 binary_upgrade_set_type_oids_by_type_oid(Archive *fout,
 										 PQExpBuffer upgrade_buffer,
 										 Oid pg_type_oid,
-										 bool force_array_type)
+										 bool force_array_type,
+										 bool include_multirange_type)
 {
 	PQExpBuffer upgrade_query = createPQExpBuffer();
 	PGresult   *res;
 	Oid			pg_type_array_oid;
+	Oid			pg_type_multirange_oid;
+	Oid			pg_type_multirange_array_oid;
 
 	appendPQExpBufferStr(upgrade_buffer, "\n-- For binary upgrade, must preserve pg_type oid\n");
 	appendPQExpBuffer(upgrade_buffer,
@@ -4369,33 +4403,7 @@ binary_upgrade_set_type_oids_by_type_oid(Archive *fout,
 	PQclear(res);
 
 	if (!OidIsValid(pg_type_array_oid) && force_array_type)
-	{
-		/*
-		 * If the old version didn't assign an array type, but the new version
-		 * does, we must select an unused type OID to assign.  This currently
-		 * only happens for domains, when upgrading pre-v11 to v11 and up.
-		 *
-		 * Note: local state here is kind of ugly, but we must have some,
-		 * since we mustn't choose the same unused OID more than once.
-		 */
-		static Oid	next_possible_free_oid = FirstNormalObjectId;
-		bool		is_dup;
-
-		do
-		{
-			++next_possible_free_oid;
-			printfPQExpBuffer(upgrade_query,
-							  "SELECT EXISTS(SELECT 1 "
-							  "FROM pg_catalog.pg_type "
-							  "WHERE oid = '%u'::pg_catalog.oid);",
-							  next_possible_free_oid);
-			res = ExecuteSqlQueryForSingleRow(fout, upgrade_query->data);
-			is_dup = (PQgetvalue(res, 0, 0)[0] == 't');
-			PQclear(res);
-		} while (is_dup);
-
-		pg_type_array_oid = next_possible_free_oid;
-	}
+		pg_type_array_oid = get_next_possible_free_pg_type_oid(fout, upgrade_query);
 
 	if (OidIsValid(pg_type_array_oid))
 	{
@@ -4406,6 +4414,46 @@ binary_upgrade_set_type_oids_by_type_oid(Archive *fout,
 						  pg_type_array_oid);
 	}
 
+	/*
+	 * Pre-set the multirange type oid and its own array type oid.
+	 */
+	if (include_multirange_type)
+	{
+		if (fout->remoteVersion >= 130000)
+		{
+			appendPQExpBuffer(upgrade_query,
+							  "SELECT t.oid, t.typarray "
+							  "FROM pg_catalog.pg_type t "
+							  "JOIN pg_catalog.pg_range r "
+							  "ON t.oid = r.rngmultitypid "
+							  "WHERE r.rngtypid = '%u'::pg_catalog.oid;",
+							  pg_type_oid);
+
+			res = ExecuteSqlQueryForSingleRow(fout, upgrade_query->data);
+
+			pg_type_multirange_oid = atooid(PQgetvalue(res, 0, PQfnumber(res, "oid")));
+			pg_type_multirange_array_oid = atooid(PQgetvalue(res, 0, PQfnumber(res, "typarray")));
+
+			PQclear(res);
+		}
+		else
+		{
+			pg_type_multirange_oid = get_next_possible_free_pg_type_oid(fout, upgrade_query);
+			pg_type_multirange_array_oid = get_next_possible_free_pg_type_oid(fout, upgrade_query);
+		}
+
+		appendPQExpBufferStr(upgrade_buffer,
+							 "\n-- For binary upgrade, must preserve multirange pg_type oid\n");
+		appendPQExpBuffer(upgrade_buffer,
+						  "SELECT pg_catalog.binary_upgrade_set_next_multirange_pg_type_oid('%u'::pg_catalog.oid);\n\n",
+						  pg_type_multirange_oid);
+		appendPQExpBufferStr(upgrade_buffer,
+							 "\n-- For binary upgrade, must preserve multirange pg_type array oid\n");
+		appendPQExpBuffer(upgrade_buffer,
+						  "SELECT pg_catalog.binary_upgrade_set_next_multirange_array_pg_type_oid('%u'::pg_catalog.oid);\n\n",
+						  pg_type_multirange_array_oid);
+	}
+
 	destroyPQExpBuffer(upgrade_query);
 }
 
@@ -4439,7 +4487,7 @@ binary_upgrade_set_type_oids_by_rel_oid(Archive *fout,
 	pg_type_oid = atooid(PQgetvalue(upgrade_res, 0, PQfnumber(upgrade_res, "crel")));
 
 	binary_upgrade_set_type_oids_by_type_oid(fout, upgrade_buffer,
-											 pg_type_oid, false);
+											 pg_type_oid, false, false);
 
 	if (!PQgetisnull(upgrade_res, 0, PQfnumber(upgrade_res, "trel")))
 	{
@@ -4988,6 +5036,11 @@ getTypes(Archive *fout, int *numTypes)
 		else
 			tyinfo[i].isArray = false;
 
+		if (tyinfo[i].typtype == 'm')
+			tyinfo[i].isMultirange = true;
+		else
+			tyinfo[i].isMultirange = false;
+
 		/* Decide whether we want to dump it */
 		selectDumpableType(&tyinfo[i], fout);
 
@@ -8102,9 +8155,12 @@ getProcLangs(Archive *fout, int *numProcLangs)
 
 /*
  * getCasts
- *	  get basic information about every cast in the system
+ *	  get basic information about most casts in the system
  *
  * numCasts is set to the number of casts read in
+ *
+ * Skip casts from a range to its multirange, since we'll create those
+ * automatically.
  */
 CastInfo *
 getCasts(Archive *fout, int *numCasts)
@@ -8122,7 +8178,20 @@ getCasts(Archive *fout, int *numCasts)
 	int			i_castcontext;
 	int			i_castmethod;
 
-	if (fout->remoteVersion >= 80400)
+	if (fout->remoteVersion >= 130000)
+	{
+		appendPQExpBufferStr(query, "SELECT tableoid, oid, "
+							 "castsource, casttarget, castfunc, castcontext, "
+							 "castmethod "
+							 "FROM pg_cast c "
+							 "WHERE NOT EXISTS ( "
+							 "SELECT 1 FROM pg_range r "
+							 "WHERE c.castsource = r.rngtypid "
+							 "AND c.casttarget = r.rngmultitypid "
+							 ") "
+							 "ORDER BY 3,4");
+	}
+	else if (fout->remoteVersion >= 80400)
 	{
 		appendPQExpBufferStr(query, "SELECT tableoid, oid, "
 							 "castsource, casttarget, castfunc, castcontext, "
@@ -10310,7 +10379,7 @@ dumpEnumType(Archive *fout, TypeInfo *tyinfo)
 	if (dopt->binary_upgrade)
 		binary_upgrade_set_type_oids_by_type_oid(fout, q,
 												 tyinfo->dobj.catId.oid,
-												 false);
+												 false, false);
 
 	appendPQExpBuffer(q, "CREATE TYPE %s AS ENUM (",
 					  qualtypname);
@@ -10436,7 +10505,7 @@ dumpRangeType(Archive *fout, TypeInfo *tyinfo)
 	if (dopt->binary_upgrade)
 		binary_upgrade_set_type_oids_by_type_oid(fout, q,
 												 tyinfo->dobj.catId.oid,
-												 false);
+												 false, true);
 
 	appendPQExpBuffer(q, "CREATE TYPE %s AS RANGE (",
 					  qualtypname);
@@ -10542,7 +10611,7 @@ dumpUndefinedType(Archive *fout, TypeInfo *tyinfo)
 	if (dopt->binary_upgrade)
 		binary_upgrade_set_type_oids_by_type_oid(fout, q,
 												 tyinfo->dobj.catId.oid,
-												 false);
+												 false, false);
 
 	appendPQExpBuffer(q, "CREATE TYPE %s;\n",
 					  qualtypname);
@@ -10747,7 +10816,7 @@ dumpBaseType(Archive *fout, TypeInfo *tyinfo)
 	if (dopt->binary_upgrade)
 		binary_upgrade_set_type_oids_by_type_oid(fout, q,
 												 tyinfo->dobj.catId.oid,
-												 false);
+												 false, false);
 
 	appendPQExpBuffer(q,
 					  "CREATE TYPE %s (\n"
@@ -10934,7 +11003,8 @@ dumpDomain(Archive *fout, TypeInfo *tyinfo)
 	if (dopt->binary_upgrade)
 		binary_upgrade_set_type_oids_by_type_oid(fout, q,
 												 tyinfo->dobj.catId.oid,
-												 true); /* force array type */
+												 true,		/* force array type */
+												 false);	/* force multirange type */
 
 	qtypname = pg_strdup(fmtId(tyinfo->dobj.name));
 	qualtypname = pg_strdup(fmtQualifiedDumpable(tyinfo));
@@ -11122,7 +11192,7 @@ dumpCompositeType(Archive *fout, TypeInfo *tyinfo)
 	{
 		binary_upgrade_set_type_oids_by_type_oid(fout, q,
 												 tyinfo->dobj.catId.oid,
-												 false);
+												 false, false);
 		binary_upgrade_set_pg_class_oids(fout, q, tyinfo->typrelid, false);
 	}
 
@@ -11396,7 +11466,7 @@ dumpShellType(Archive *fout, ShellTypeInfo *stinfo)
 	if (dopt->binary_upgrade)
 		binary_upgrade_set_type_oids_by_type_oid(fout, q,
 												 stinfo->baseType->dobj.catId.oid,
-												 false);
+												 false, false);
 
 	appendPQExpBuffer(q, "CREATE TYPE %s;\n",
 					  fmtQualifiedDumpable(stinfo));
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index e0c6444ef6..90a93b4cb2 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -176,6 +176,7 @@ typedef struct _typeInfo
 	char		typrelkind;		/* 'r', 'v', 'c', etc */
 	char		typtype;		/* 'b', 'c', etc */
 	bool		isArray;		/* true if auto-generated array type */
+	bool		isMultirange;	/* true if auto-generated multirange type */
 	bool		isDefined;		/* true if typisdefined */
 	/* If needed, we'll create a "shell type" entry for it; link that here: */
 	struct _shellTypeInfo *shellType;	/* shell-type entry, or NULL */
diff --git a/src/include/access/tupmacs.h b/src/include/access/tupmacs.h
index 70157cf90a..c262265b95 100644
--- a/src/include/access/tupmacs.h
+++ b/src/include/access/tupmacs.h
@@ -139,8 +139,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/binary_upgrade.h b/src/include/catalog/binary_upgrade.h
index 12d94fe1b3..01d95117cd 100644
--- a/src/include/catalog/binary_upgrade.h
+++ b/src/include/catalog/binary_upgrade.h
@@ -16,6 +16,8 @@
 
 extern PGDLLIMPORT Oid binary_upgrade_next_pg_type_oid;
 extern PGDLLIMPORT Oid binary_upgrade_next_array_pg_type_oid;
+extern PGDLLIMPORT Oid binary_upgrade_next_mrng_pg_type_oid;
+extern PGDLLIMPORT Oid binary_upgrade_next_mrng_array_pg_type_oid;
 extern PGDLLIMPORT Oid binary_upgrade_next_toast_pg_type_oid;
 
 extern PGDLLIMPORT Oid binary_upgrade_next_heap_pg_class_oid;
diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h
index 8be303870f..566f9364c5 100644
--- a/src/include/catalog/indexing.h
+++ b/src/include/catalog/indexing.h
@@ -327,6 +327,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_rngmultitypid_index, 8001, on pg_range using btree(rngmultitypid 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_aggregate.dat b/src/include/catalog/pg_aggregate.dat
index ffabe275c0..03bab74253 100644
--- a/src/include/catalog/pg_aggregate.dat
+++ b/src/include/catalog/pg_aggregate.dat
@@ -538,6 +538,17 @@
   aggtransfn => 'bytea_string_agg_transfn',
   aggfinalfn => 'bytea_string_agg_finalfn', aggtranstype => 'internal' },
 
+# range
+{ aggfnoid => 'range_intersect_agg(anyrange)',
+  aggtransfn => 'range_intersect_agg_transfn',
+  aggcombinefn => 'range_intersect_agg_transfn', aggtranstype => 'anyrange' },
+{ aggfnoid => 'range_intersect_agg(anymultirange)',
+  aggtransfn => 'multirange_intersect_agg_transfn',
+  aggcombinefn => 'multirange_intersect_agg_transfn', aggtranstype => 'anymultirange' },
+{ aggfnoid => 'range_agg(anyrange)', aggtransfn => 'range_agg_transfn',
+  aggfinalfn => 'range_agg_finalfn', aggfinalextra => 't',
+  aggtranstype => 'internal' },
+
 # json
 { aggfnoid => 'json_agg', aggtransfn => 'json_agg_transfn',
   aggfinalfn => 'json_agg_finalfn', aggtranstype => 'internal' },
diff --git a/src/include/catalog/pg_amop.dat b/src/include/catalog/pg_amop.dat
index 11aaa519c8..827ac4f03c 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 75c0152b66..cf0cac5641 100644
--- a/src/include/catalog/pg_amproc.dat
+++ b/src/include/catalog/pg_amproc.dat
@@ -285,6 +285,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' },
 
@@ -445,6 +447,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',
@@ -1263,12 +1270,13 @@
   amprocrighttype => 'anyrange', amprocnum => '4',
   amproc => 'brin_inclusion_union' },
 { amprocfamily => 'brin/range_inclusion_ops', amproclefttype => 'anyrange',
-  amprocrighttype => 'anyrange', amprocnum => '11', amproc => 'range_merge' },
+  amprocrighttype => 'anyrange', amprocnum => '11',
+  amproc => 'range_merge(anyrange,anyrange)' },
 { amprocfamily => 'brin/range_inclusion_ops', amproclefttype => 'anyrange',
   amprocrighttype => 'anyrange', amprocnum => '13',
   amproc => 'range_contains' },
 { amprocfamily => 'brin/range_inclusion_ops', amproclefttype => 'anyrange',
-  amprocrighttype => 'anyrange', amprocnum => '14', amproc => 'isempty' },
+  amprocrighttype => 'anyrange', amprocnum => '14', amproc => 'isempty(anyrange)' },
 
 # minmax pg_lsn
 { amprocfamily => 'brin/pg_lsn_minmax_ops', amproclefttype => 'pg_lsn',
diff --git a/src/include/catalog/pg_cast.dat b/src/include/catalog/pg_cast.dat
index 6ef8b8a4e7..14277c8fdf 100644
--- a/src/include/catalog/pg_cast.dat
+++ b/src/include/catalog/pg_cast.dat
@@ -512,4 +512,17 @@
 { castsource => 'jsonb', casttarget => 'float8', castfunc => 'float8(jsonb)',
   castcontext => 'e', castmethod => 'f' },
 
+# range to multirange
+{ castsource => 'int4range', casttarget => 'int4multirange', castfunc => 'int4multirange(int4range)',
+  castcontext => 'e', castmethod => 'f' },
+{ castsource => 'int8range', casttarget => 'int8multirange', castfunc => 'int8multirange(int8range)',
+  castcontext => 'e', castmethod => 'f' },
+{ castsource => 'numrange', casttarget => 'nummultirange', castfunc => 'nummultirange(numrange)',
+  castcontext => 'e', castmethod => 'f' },
+{ castsource => 'daterange', casttarget => 'datemultirange', castfunc => 'datemultirange(daterange)',
+  castcontext => 'e', castmethod => 'f' },
+{ castsource => 'tsrange', casttarget => 'tsmultirange', castfunc => 'tsmultirange(tsrange)',
+  castcontext => 'e', castmethod => 'f' },
+{ castsource => 'tstzrange', casttarget => 'tstzmultirange', castfunc => 'tstzmultirange(tstzrange)',
+  castcontext => 'e', castmethod => 'f' },
 ]
diff --git a/src/include/catalog/pg_opclass.dat b/src/include/catalog/pg_opclass.dat
index ab2f50c9eb..eebebbcdb4 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 7c135da3b1..24f4a32203 100644
--- a/src/include/catalog/pg_operator.dat
+++ b/src/include/catalog/pg_operator.dat
@@ -3297,5 +3297,174 @@
   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' },
+{ oid => '8073', oid_symbol => 'OID_RANGE_OVERLAPS_MULTIRANGE_OP',
+  descr => 'overlaps',
+  oprname => '&&', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '&&(anymultirange,anyrange)',
+  oprcode => 'range_overlaps_multirange', oprrest => 'areasel',
+  oprjoin => 'areajoinsel' },
+{ oid => '8074', oid_symbol => 'OID_MULTIRANGE_OVERLAPS_RANGE_OP', descr => 'contains',
+  oprname => '&&', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcom => '&&(anyrange,anymultirange)',
+  oprcode => 'multirange_overlaps_range', oprrest => 'areasel',
+  oprjoin => 'areajoinsel' },
+{ oid => '8075', oid_symbol => 'OID_MULTIRANGE_OVERLAPS_MULTIRANGE_OP', descr => 'contains',
+  oprname => '&&', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '&&(anymultirange,anymultirange)',
+  oprcode => 'multirange_overlaps_multirange', oprrest => 'areasel',
+  oprjoin => 'areajoinsel' },
+{ oid => '8064', oid_symbol => 'OID_MULTIRANGE_CONTAINS_ELEM_OP',
+  descr => 'contains',
+  oprname => '@>', oprleft => 'anymultirange', oprright => 'anyelement',
+  oprresult => 'bool', oprcom => '<@(anyelement,anymultirange)',
+  oprcode => 'multirange_contains_elem', oprrest => 'contsel',
+  oprjoin => 'contjoinsel' },
+{ oid => '8065', oid_symbol => 'OID_MULTIRANGE_CONTAINS_RANGE_OP', descr => 'contains',
+  oprname => '@>', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcom => '<@(anyrange,anymultirange)',
+  oprcode => 'multirange_contains_range', oprrest => 'contsel',
+  oprjoin => 'contjoinsel' },
+{ oid => '8066', oid_symbol => 'OID_MULTIRANGE_CONTAINS_MULTIRANGE_OP', descr => 'contains',
+  oprname => '@>', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '<@(anymultirange,anymultirange)',
+  oprcode => 'multirange_contains_multirange', oprrest => 'contsel',
+  oprjoin => 'contjoinsel' },
+{ oid => '8067', oid_symbol => 'OID_MULTIRANGE_ELEM_CONTAINED_OP',
+  descr => 'is contained by',
+  oprname => '<@', oprleft => 'anyelement', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '@>(anymultirange,anyelement)',
+  oprcode => 'elem_contained_by_multirange', oprrest => 'contsel',
+  oprjoin => 'contjoinsel' },
+{ oid => '8068', oid_symbol => 'OID_MULTIRANGE_RANGE_CONTAINED_OP',
+  descr => 'is contained by',
+  oprname => '<@', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '@>(anymultirange,anyrange)',
+  oprcode => 'range_contained_by_multirange', oprrest => 'contsel',
+  oprjoin => 'contjoinsel' },
+{ oid => '8069', oid_symbol => 'OID_MULTIRANGE_MULTIRANGE_CONTAINED_OP',
+  descr => 'is contained by',
+  oprname => '<@', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '@>(anymultirange,anymultirange)',
+  oprcode => 'multirange_contained_by_multirange', oprrest => 'contsel',
+  oprjoin => 'contjoinsel' },
+{ oid => '8106', oid_symbol => 'OID_RANGE_OVERLAPS_LEFT_MULTIRANGE_OP',
+  descr => 'overlaps or is left of',
+  oprname => '&<', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcode => 'range_overleft_multirange', oprrest => 'scalarltsel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8107', oid_symbol => 'OID_MULTIRANGE_OVERLAPS_LEFT_RANGE_OP',
+  descr => 'overlaps or is left of',
+  oprname => '&<', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcode => 'multirange_overleft_range', oprrest => 'scalarltsel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8108', oid_symbol => 'OID_MULTIRANGE_OVERLAPS_LEFT_MULTIRANGE_OP',
+  descr => 'overlaps or is left of',
+  oprname => '&<', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcode => 'multirange_overleft_multirange', oprrest => 'scalarltsel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8109', oid_symbol => 'OID_RANGE_OVERLAPS_RIGHT_MULTIRANGE_OP',
+  descr => 'overlaps or is right of',
+  oprname => '&>', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcode => 'range_overright_multirange', oprrest => 'scalargtsel',
+  oprjoin => 'scalargtjoinsel' },
+{ oid => '8110', oid_symbol => 'OID_MULTIRANGE_OVERLAPS_RIGHT_RANGE_OP',
+  descr => 'overlaps or is right of',
+  oprname => '&>', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcode => 'multirange_overright_range', oprrest => 'scalargtsel',
+  oprjoin => 'scalargtjoinsel' },
+{ oid => '8111', oid_symbol => 'OID_MULTIRANGE_OVERLAPS_RIGHT_MULTIRANGE_OP',
+  descr => 'overlaps or is right of',
+  oprname => '&>', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcode => 'multirange_overright_multirange', oprrest => 'scalargtsel',
+  oprjoin => 'scalargtjoinsel' },
+{ oid => '8091', oid_symbol => 'OID_RANGE_ADJACENT_MULTIRANGE_OP', descr => 'is adjacent to',
+  oprname => '-|-', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '-|-(anymultirange,anyrange)',
+  oprcode => 'range_adjacent_multirange', oprrest => 'scalarltsel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8092', oid_symbol => 'OID_MULTIRANGE_ADJACENT_RANGE_OP', descr => 'is adjacent to',
+  oprname => '-|-', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcom => '-|-(anyrange,anymultirange)',
+  oprcode => 'multirange_adjacent_range', oprrest => 'scalarltsel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8093', oid_symbol => 'OID_MULTIRANGE_ADJACENT_MULTIRANGE_OP', descr => 'is adjacent to',
+  oprname => '-|-', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '-|-(anymultirange,anymultirange)',
+  oprcode => 'multirange_adjacent_multirange', oprrest => 'scalarltsel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8117', descr => 'multirange union',
+  oprname => '+', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'anymultirange', oprcom => '+(anymultirange,anymultirange)',
+  oprcode => 'multirange_union' },
+{ oid => '8123', descr => 'multirange minus',
+  oprname => '-', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'anymultirange', oprcode => 'multirange_minus' },
+{ oid => '8129', descr => 'multirange intersect',
+  oprname => '*', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'anymultirange', oprcom => '*(anymultirange,anymultirange)',
+  oprcode => 'multirange_intersect' },
+{ oid => '8082', oid_symbol => 'OID_RANGE_BEFORE_MULTIRANGE_OP', descr => 'is left of',
+  oprname => '<<', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '>>(anymultirange,anyrange)',
+  oprcode => 'range_before_multirange', oprrest => 'scalarltsel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8083', oid_symbol => 'OID_MULTIRANGE_BEFORE_RANGE_OP', descr => 'is left of',
+  oprname => '<<', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcom => '>>(anyrange,anymultirange)',
+  oprcode => 'multirange_before_range', oprrest => 'scalarltsel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8084', oid_symbol => 'OID_MULTIRANGE_BEFORE_MULTIRANGE_OP', descr => 'is left of',
+  oprname => '<<', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '>>(anymultirange,anymultirange)',
+  oprcode => 'multirange_before_multirange', oprrest => 'scalarltsel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8085', oid_symbol => 'OID_RANGE_AFTER_MULTIRANGE_OP', descr => 'is right of',
+  oprname => '>>', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '<<(anymultirange,anyrange)',
+  oprcode => 'range_after_multirange', oprrest => 'scalargtsel',
+  oprjoin => 'scalargtjoinsel' },
+{ oid => '8086', oid_symbol => 'OID_MULTIRANGE_AFTER_RANGE_OP', descr => 'is right of',
+  oprname => '>>', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcom => '<<(anyrange,anymultirange)',
+  oprcode => 'multirange_after_range', oprrest => 'scalargtsel',
+  oprjoin => 'scalargtjoinsel' },
+{ oid => '8087', oid_symbol => 'OID_MULTIRANGE_AFTER_MULTIRANGE_OP', descr => 'is right of',
+  oprname => '>>', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '<<(anymultirange,anymultirange)',
+  oprcode => 'multirange_after_multirange', oprrest => 'scalargtsel',
+  oprjoin => 'scalargtjoinsel' },
 
 ]
diff --git a/src/include/catalog/pg_opfamily.dat b/src/include/catalog/pg_opfamily.dat
index 26227df216..901fd7b303 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 7fb574f9dc..27f25d989b 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -9643,6 +9643,10 @@
   descr => 'the smallest range which includes both of the given ranges',
   proname => 'range_merge', prorettype => 'anyrange',
   proargtypes => 'anyrange anyrange', prosrc => 'range_merge' },
+{ oid => '8143',
+  descr => 'the smallest range which includes the whole multirange',
+  proname => 'range_merge', prorettype => 'anyrange',
+  proargtypes => 'anymultirange', prosrc => 'range_merge_from_multirange' },
 { oid => '3868',
   proname => 'range_intersect', prorettype => 'anyrange',
   proargtypes => 'anyrange anyrange', prosrc => 'range_intersect' },
@@ -9692,6 +9696,13 @@
 { oid => '3169', descr => 'restriction selectivity for range operators',
   proname => 'rangesel', provolatile => 's', prorettype => 'float8',
   proargtypes => 'internal oid internal int4', prosrc => 'rangesel' },
+{ oid => '8133', descr => 'range aggregate by intersecting',
+  proname => 'range_intersect_agg_transfn', prorettype => 'anyrange',
+  proargtypes => 'anyrange anyrange', prosrc => 'range_intersect_agg_transfn'},
+{ oid => '8134', descr => 'range aggregate by intersecting',
+  proname => 'range_intersect_agg', prokind => 'a', proisstrict => 'f',
+  prorettype => 'anyrange', proargtypes => 'anyrange',
+  prosrc => 'aggregate_dummy'},
 
 { oid => '3914', descr => 'convert an int4 range to canonical form',
   proname => 'int4range_canonical', prorettype => 'int4range',
@@ -9760,6 +9771,258 @@
   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 => '8094', descr => 'lower bound of multirange',
+  proname => 'lower', prorettype => 'anyelement', proargtypes => 'anymultirange',
+  prosrc => 'multirange_lower' },
+{ oid => '8095', descr => 'upper bound of multirange',
+  proname => 'upper', prorettype => 'anyelement', proargtypes => 'anymultirange',
+  prosrc => 'multirange_upper' },
+{ oid => '8057', descr => 'is the multirange empty?',
+  proname => 'isempty', prorettype => 'bool',
+  proargtypes => 'anymultirange', prosrc => 'multirange_empty' },
+{ oid => '8096', descr => 'is the multirange\'s lower bound inclusive?',
+  proname => 'lower_inc', prorettype => 'bool', proargtypes => 'anymultirange',
+  prosrc => 'multirange_lower_inc' },
+{ oid => '8097', descr => 'is the multirange\'s upper bound inclusive?',
+  proname => 'upper_inc', prorettype => 'bool', proargtypes => 'anymultirange',
+  prosrc => 'multirange_upper_inc' },
+{ oid => '8098', descr => 'is the multirange\'s lower bound infinite?',
+  proname => 'lower_inf', prorettype => 'bool', proargtypes => 'anymultirange',
+  prosrc => 'multirange_lower_inf' },
+{ oid => '8099', descr => 'is the multirange\'s upper bound infinite?',
+  proname => 'upper_inf', prorettype => 'bool', proargtypes => 'anymultirange',
+  prosrc => 'multirange_upper_inf' },
+{ oid => '8008', descr => 'multirange typanalyze',
+  proname => 'multirange_typanalyze', provolatile => 's', prorettype => 'bool',
+  proargtypes => 'internal', prosrc => 'multirange_typanalyze' },
+{ oid => '8033',
+  proname => 'multirange_eq', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_eq' },
+{ oid => '8034',
+  proname => 'multirange_ne', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_ne' },
+{ oid => '8070',
+  proname => 'range_overlaps_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_overlaps_multirange' },
+{ oid => '8071',
+  proname => 'multirange_overlaps_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_overlaps_range' },
+{ oid => '8072',
+  proname => 'multirange_overlaps_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_overlaps_multirange' },
+{ oid => '8058',
+  proname => 'multirange_contains_elem', prorettype => 'bool',
+  proargtypes => 'anymultirange anyelement', prosrc => 'multirange_contains_elem' },
+{ oid => '8059',
+  proname => 'multirange_contains_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_contains_range' },
+{ oid => '8060',
+  proname => 'multirange_contains_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_contains_multirange' },
+{ oid => '8061',
+  proname => 'elem_contained_by_multirange', prorettype => 'bool',
+  proargtypes => 'anyelement anymultirange', prosrc => 'elem_contained_by_multirange' },
+{ oid => '8062',
+  proname => 'range_contained_by_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_contained_by_multirange' },
+{ oid => '8063',
+  proname => 'multirange_contained_by_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_contained_by_multirange' },
+{ oid => '8088',
+  proname => 'range_adjacent_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_adjacent_multirange' },
+{ oid => '8089',
+  proname => 'multirange_adjacent_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_adjacent_multirange' },
+{ oid => '8090',
+  proname => 'multirange_adjacent_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_adjacent_range' },
+{ oid => '8076',
+  proname => 'range_before_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_before_multirange' },
+{ oid => '8077',
+  proname => 'multirange_before_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_before_range' },
+{ oid => '8078',
+  proname => 'multirange_before_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_before_multirange' },
+{ oid => '8079',
+  proname => 'range_after_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_after_multirange' },
+{ oid => '8080',
+  proname => 'multirange_after_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_after_range' },
+{ oid => '8081',
+  proname => 'multirange_after_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_after_multirange' },
+{ oid => '8100',
+  proname => 'range_overleft_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_overleft_multirange' },
+{ oid => '8101',
+  proname => 'multirange_overleft_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_overleft_range' },
+{ oid => '8102',
+  proname => 'multirange_overleft_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_overleft_multirange' },
+{ oid => '8103',
+  proname => 'range_overright_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_overright_multirange' },
+{ oid => '8104',
+  proname => 'multirange_overright_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_overright_range' },
+{ oid => '8105',
+  proname => 'multirange_overright_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_overright_multirange' },
+{ oid => '8114',
+  proname => 'multirange_union', prorettype => 'anymultirange',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_union' },
+{ oid => '8120',
+  proname => 'multirange_minus', prorettype => 'anymultirange',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_minus' },
+{ oid => '8126',
+  proname => 'multirange_intersect', prorettype => 'anymultirange',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_intersect' },
+{ oid => '8022', descr => 'less-equal-greater',
+  proname => 'multirange_cmp', prorettype => 'int4',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_cmp' },
+{ oid => '8023',
+  proname => 'multirange_lt', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_lt' },
+{ oid => '8024',
+  proname => 'multirange_le', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_le' },
+{ oid => '8025',
+  proname => 'multirange_ge', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_ge' },
+{ oid => '8026',
+  proname => 'multirange_gt', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_gt' },
+{ oid => '8038', descr => 'hash a multirange',
+  proname => 'hash_multirange', prorettype => 'int4', proargtypes => 'anymultirange',
+  prosrc => 'hash_multirange' },
+{ oid => '8039', descr => 'hash a multirange',
+  proname => 'hash_multirange_extended', prorettype => 'int8',
+  proargtypes => 'anymultirange int8', prosrc => 'hash_multirange_extended' },
+
+{ oid => '8045', descr => 'int4multirange constructor',
+  proname => 'int4multirange', proisstrict => 'f',
+  prorettype => 'int4multirange', proargtypes => '',
+  prosrc => 'multirange_constructor0' },
+{ oid => '8146', descr => 'int4multirange constructor',
+  proname => 'int4multirange', proisstrict => 't',
+  prorettype => 'int4multirange', proargtypes => 'int4range',
+  prosrc => 'multirange_constructor1' },
+{ oid => '8046', descr => 'int4multirange constructor',
+  proname => 'int4multirange', provariadic => 'int4range', proisstrict => 'f',
+  prorettype => 'int4multirange', proargtypes => '_int4range',
+  proallargtypes => '{_int4range}', proargmodes => '{v}',
+  prosrc => 'multirange_constructor2' },
+{ oid => '8047', descr => 'nummultirange constructor',
+  proname => 'nummultirange', proisstrict => 'f',
+  prorettype => 'nummultirange', proargtypes => '',
+  prosrc => 'multirange_constructor0' },
+{ oid => '8147', descr => 'nummultirange constructor',
+  proname => 'nummultirange', proisstrict => 't',
+  prorettype => 'nummultirange', proargtypes => 'numrange',
+  prosrc => 'multirange_constructor1' },
+{ oid => '8048', descr => 'nummultirange constructor',
+  proname => 'nummultirange', provariadic => 'numrange', proisstrict => 'f',
+  prorettype => 'nummultirange', proargtypes => '_numrange',
+  proallargtypes => '{_numrange}', proargmodes => '{v}',
+  prosrc => 'multirange_constructor2' },
+{ oid => '8049', descr => 'tsmultirange constructor',
+  proname => 'tsmultirange', proisstrict => 'f',
+  prorettype => 'tsmultirange', proargtypes => '',
+  prosrc => 'multirange_constructor0' },
+{ oid => '8148', descr => 'tsmultirange constructor',
+  proname => 'tsmultirange', proisstrict => 't',
+  prorettype => 'tsmultirange', proargtypes => 'tsrange',
+  prosrc => 'multirange_constructor1' },
+{ oid => '8050', descr => 'tsmultirange constructor',
+  proname => 'tsmultirange', provariadic => 'tsrange', proisstrict => 'f',
+  prorettype => 'tsmultirange', proargtypes => '_tsrange',
+  proallargtypes => '{_tsrange}', proargmodes => '{v}',
+  prosrc => 'multirange_constructor2' },
+{ oid => '8051', descr => 'tstzmultirange constructor',
+  proname => 'tstzmultirange', proisstrict => 'f',
+  prorettype => 'tstzmultirange', proargtypes => '',
+  prosrc => 'multirange_constructor0' },
+{ oid => '8149', descr => 'tstzmultirange constructor',
+  proname => 'tstzmultirange', proisstrict => 't',
+  prorettype => 'tstzmultirange', proargtypes => 'tstzrange',
+  prosrc => 'multirange_constructor1' },
+{ oid => '8052', descr => 'tstzmultirange constructor',
+  proname => 'tstzmultirange', provariadic => 'tstzrange', proisstrict => 'f',
+  prorettype => 'tstzmultirange', proargtypes => '_tstzrange',
+  proallargtypes => '{_tstzrange}', proargmodes => '{v}',
+  prosrc => 'multirange_constructor2' },
+{ oid => '8053', descr => 'datemultirange constructor',
+  proname => 'datemultirange', proisstrict => 'f',
+  prorettype => 'datemultirange', proargtypes => '',
+  prosrc => 'multirange_constructor0' },
+{ oid => '8150', descr => 'datemultirange constructor',
+  proname => 'datemultirange', proisstrict => 't',
+  prorettype => 'datemultirange', proargtypes => 'daterange',
+  prosrc => 'multirange_constructor1' },
+{ oid => '8054', descr => 'datemultirange constructor',
+  proname => 'datemultirange', provariadic => 'daterange', proisstrict => 'f',
+  prorettype => 'datemultirange', proargtypes => '_daterange',
+  proallargtypes => '{_daterange}', proargmodes => '{v}',
+  prosrc => 'multirange_constructor2' },
+{ oid => '8055', descr => 'int8multirange constructor',
+  proname => 'int8multirange', proisstrict => 'f',
+  prorettype => 'int8multirange', proargtypes => '',
+  prosrc => 'multirange_constructor0' },
+{ oid => '8151', descr => 'int8multirange constructor',
+  proname => 'int8multirange', proisstrict => 't',
+  prorettype => 'int8multirange', proargtypes => 'int8range',
+  prosrc => 'multirange_constructor1' },
+{ oid => '8056', descr => 'int8multirange constructor',
+  proname => 'int8multirange', provariadic => 'int8range', proisstrict => 'f',
+  prorettype => 'int8multirange', proargtypes => '_int8range',
+  proallargtypes => '{_int8range}', proargmodes => '{v}',
+  prosrc => 'multirange_constructor2' },
+{ oid => '8152', descr => 'anymultirange cast',
+  proname => 'multirange', proisstrict => 't',
+  prorettype => 'anymultirange', proargtypes => 'anyrange',
+  prosrc => 'multirange_constructor1' },
+{ oid => '8130', descr => 'aggregate transition function',
+  proname => 'range_agg_transfn', proisstrict => 'f', prorettype => 'internal',
+  proargtypes => 'internal anyrange', prosrc => 'range_agg_transfn' },
+{ oid => '8131', descr => 'aggregate final function',
+  proname => 'range_agg_finalfn', proisstrict => 'f', prorettype => 'anymultirange',
+  proargtypes => 'internal anyrange', prosrc => 'range_agg_finalfn' },
+{ oid => '8132', descr => 'combine aggregate input into a multirange',
+  proname => 'range_agg', prokind => 'a', proisstrict => 'f',
+  prorettype => 'anymultirange', proargtypes => 'anyrange',
+  prosrc => 'aggregate_dummy' },
+{ oid => '8135', descr => 'range aggregate by intersecting',
+  proname => 'multirange_intersect_agg_transfn', prorettype => 'anymultirange',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_intersect_agg_transfn'},
+{ oid => '8136', descr => 'range aggregate by intersecting',
+  proname => 'range_intersect_agg', prokind => 'a', proisstrict => 'f',
+  prorettype => 'anymultirange', proargtypes => 'anymultirange',
+  prosrc => 'aggregate_dummy'},
+
 # date, time, timestamp constructors
 { oid => '3846', descr => 'construct date',
   proname => 'make_date', prorettype => 'date', proargtypes => 'int4 int4 int4',
@@ -10142,6 +10405,14 @@
   proname => 'binary_upgrade_set_next_array_pg_type_oid', provolatile => 'v',
   proparallel => 'r', prorettype => 'void', proargtypes => 'oid',
   prosrc => 'binary_upgrade_set_next_array_pg_type_oid' },
+{ oid => '8144', descr => 'for use by pg_upgrade',
+  proname => 'binary_upgrade_set_next_multirange_pg_type_oid', provolatile => 'v',
+  proparallel => 'r', prorettype => 'void', proargtypes => 'oid',
+  prosrc => 'binary_upgrade_set_next_multirange_pg_type_oid' },
+{ oid => '8145', descr => 'for use by pg_upgrade',
+  proname => 'binary_upgrade_set_next_multirange_array_pg_type_oid', provolatile => 'v',
+  proparallel => 'r', prorettype => 'void', proargtypes => 'oid',
+  prosrc => 'binary_upgrade_set_next_multirange_array_pg_type_oid' },
 { oid => '3585', descr => 'for use by pg_upgrade',
   proname => 'binary_upgrade_set_next_toast_pg_type_oid', provolatile => 'v',
   proparallel => 'r', prorettype => 'void', proargtypes => 'oid',
diff --git a/src/include/catalog/pg_range.dat b/src/include/catalog/pg_range.dat
index 479754c245..10060255c9 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',
+  rngmultitypid => 'int4multirange' },
 { rngtypid => 'numrange', rngsubtype => 'numeric',
   rngsubopc => 'btree/numeric_ops', rngcanonical => '-',
-  rngsubdiff => 'numrange_subdiff' },
+  rngsubdiff => 'numrange_subdiff', rngmultitypid => 'nummultirange' },
 { rngtypid => 'tsrange', rngsubtype => 'timestamp',
   rngsubopc => 'btree/timestamp_ops', rngcanonical => '-',
-  rngsubdiff => 'tsrange_subdiff' },
+  rngsubdiff => 'tsrange_subdiff', rngmultitypid => 'tsmultirange' },
 { rngtypid => 'tstzrange', rngsubtype => 'timestamptz',
   rngsubopc => 'btree/timestamptz_ops', rngcanonical => '-',
-  rngsubdiff => 'tstzrange_subdiff' },
+  rngsubdiff => 'tstzrange_subdiff', rngmultitypid => 'tstzmultirange' },
 { rngtypid => 'daterange', rngsubtype => 'date', rngsubopc => 'btree/date_ops',
-  rngcanonical => 'daterange_canonical', rngsubdiff => 'daterange_subdiff' },
+  rngcanonical => 'daterange_canonical', rngsubdiff => 'daterange_subdiff',
+  rngmultitypid => 'datemultirange' },
 { rngtypid => 'int8range', rngsubtype => 'int8', rngsubopc => 'btree/int8_ops',
-  rngcanonical => 'int8range_canonical', rngsubdiff => 'int8range_subdiff' },
+  rngcanonical => 'int8range_canonical', rngsubdiff => 'int8range_subdiff',
+  rngmultitypid => 'int8multirange' },
 
 ]
diff --git a/src/include/catalog/pg_range.h b/src/include/catalog/pg_range.h
index 98172bb1b6..60a8fde518 100644
--- a/src/include/catalog/pg_range.h
+++ b/src/include/catalog/pg_range.h
@@ -34,6 +34,9 @@ CATALOG(pg_range,3541,RangeRelationId)
 	/* OID of range's element type (subtype) */
 	Oid			rngsubtype BKI_LOOKUP(pg_type);
 
+	/* OID of the range's multirange type */
+	Oid			rngmultitypid BKI_LOOKUP(pg_type);
+
 	/* collation for this range type, or 0 */
 	Oid			rngcollation BKI_DEFAULT(0);
 
@@ -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 b00597d6ff..6e1cb796f1 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 => 'R', 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 => 'R', 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 => 'R', 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 => 'R', 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 => 'R', 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 => 'R', 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
@@ -590,5 +624,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 97890946c5..d18f19f55a 100644
--- a/src/include/catalog/pg_type.h
+++ b/src/include/catalog/pg_type.h
@@ -263,6 +263,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 */
 
@@ -299,7 +300,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 */
 
@@ -358,4 +360,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/commands/typecmds.h b/src/include/commands/typecmds.h
index 0162bc2ffe..80e3a5e399 100644
--- a/src/include/commands/typecmds.h
+++ b/src/include/commands/typecmds.h
@@ -29,6 +29,8 @@ extern ObjectAddress DefineRange(CreateRangeStmt *stmt);
 extern ObjectAddress AlterEnum(AlterEnumStmt *stmt);
 extern ObjectAddress DefineCompositeType(RangeVar *typevar, List *coldeflist);
 extern Oid	AssignTypeArrayOid(void);
+extern Oid	AssignTypeMultirangeOid(void);
+extern Oid	AssignTypeMultirangeArrayOid(void);
 
 extern ObjectAddress AlterDomainDefault(List *names, Node *defaultRaw);
 extern ObjectAddress AlterDomainNotNull(List *names, bool notNull);
diff --git a/src/include/utils/lsyscache.h b/src/include/utils/lsyscache.h
index 4e646c55e9..80410dcd04 100644
--- a/src/include/utils/lsyscache.h
+++ b/src/include/utils/lsyscache.h
@@ -155,6 +155,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);
@@ -181,6 +182,8 @@ 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_collation(Oid rangeOid);
+extern Oid	get_range_multirange(Oid rangeOid);
+extern Oid	get_range_multirange_subtype(Oid multirangeOid);
 extern Oid	get_index_column_opclass(Oid index_oid, int attno);
 extern bool	get_index_isreplident(Oid index_oid);
 extern bool get_index_isvalid(Oid index_oid);
diff --git a/src/include/utils/multirangetypes.h b/src/include/utils/multirangetypes.h
new file mode 100644
index 0000000000..469444776c
--- /dev/null
+++ b/src/include/utils/multirangetypes.h
@@ -0,0 +1,103 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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
+{
+	int32		vl_len_;		/* 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);
+extern bool multirange_contains_elem_internal(TypeCacheEntry *typcache, MultirangeType *mr,
+											  Datum elem);
+extern bool multirange_contains_range_internal(TypeCacheEntry *typcache, MultirangeType *mr,
+											   RangeType *r);
+extern bool multirange_contains_multirange_internal(TypeCacheEntry *typcache,
+													MultirangeType *mr1,
+													MultirangeType *mr2);
+extern bool range_overlaps_multirange_internal(TypeCacheEntry *typcache, RangeType *r,
+											   MultirangeType *mr);
+extern bool multirange_overlaps_multirange_internal(TypeCacheEntry *typcache,
+													MultirangeType *mr1,
+													MultirangeType *mr2);
+extern bool range_before_multirange_internal(TypeCacheEntry *typcache, RangeType *r,
+											 MultirangeType *mr);
+extern bool range_after_multirange_internal(TypeCacheEntry *typcache, RangeType *r,
+											MultirangeType *mr);
+extern bool multirange_before_multirange_internal(TypeCacheEntry *typcache,
+												  MultirangeType *mr1,
+												  MultirangeType *mr2);
+extern MultirangeType *multirange_minus_internal(Oid mltrngtypoid,
+												 TypeCacheEntry *rangetyp,
+												 int32 range_count1,
+												 RangeType **ranges1,
+												 int32 range_count2,
+												 RangeType **ranges2);
+extern MultirangeType *multirange_intersect_internal(Oid mltrngtypoid,
+													 TypeCacheEntry *rangetyp,
+													 int32 range_count1,
+													 RangeType **ranges1,
+													 int32 range_count2,
+													 RangeType **ranges2);
+
+/* 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 0cbbf09033..204aee4054 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)
 
@@ -92,13 +94,21 @@ typedef struct
  * prototypes for functions defined in rangetypes.c
  */
 
-extern bool range_contains_elem_internal(TypeCacheEntry *typcache, const RangeType *r, Datum val);
-
 /* internal versions of the above */
+extern int	range_cmp_internal(TypeCacheEntry *typcache, const RangeType *r1,
+							   const RangeType *r2);
+extern uint32 hash_range_internal(TypeCacheEntry *typcache, const RangeType *r);
+extern uint64 hash_range_extended_internal(TypeCacheEntry *typcache, const RangeType *r,
+										   Datum seed);
+extern Datum range_lower_internal(TypeCacheEntry *typcache, const RangeType *r1,
+								  bool *isnull);
+extern Datum range_upper_internal(TypeCacheEntry *typcache, const RangeType *r1,
+								  bool *isnull);
 extern bool range_eq_internal(TypeCacheEntry *typcache, const RangeType *r1,
 							  const RangeType *r2);
 extern bool range_ne_internal(TypeCacheEntry *typcache, const RangeType *r1,
 							  const RangeType *r2);
+extern bool range_contains_elem_internal(TypeCacheEntry *typcache, const RangeType *r, Datum val);
 extern bool range_contains_internal(TypeCacheEntry *typcache, const RangeType *r1,
 									const RangeType *r2);
 extern bool range_contained_by_internal(TypeCacheEntry *typcache, const RangeType *r1,
@@ -115,6 +125,12 @@ extern bool range_overleft_internal(TypeCacheEntry *typcache, const RangeType *r
 									const RangeType *r2);
 extern bool range_overright_internal(TypeCacheEntry *typcache, const RangeType *r1,
 									 const RangeType *r2);
+extern RangeType *range_union_internal(TypeCacheEntry *typcache, RangeType *r1,
+									   RangeType *r2, bool strict);
+extern RangeType *range_minus_internal(TypeCacheEntry *typcache, RangeType *r1,
+									   RangeType *r2);
+extern RangeType *range_intersect_internal(TypeCacheEntry *typcache, const RangeType *r1,
+										   const RangeType *r2);
 
 /* assorted support functions */
 extern TypeCacheEntry *range_get_typcache(FunctionCallInfo fcinfo,
@@ -125,6 +141,7 @@ extern void range_deserialize(TypeCacheEntry *typcache, const RangeType *range,
 							  RangeBound *lower, RangeBound *upper,
 							  bool *empty);
 extern char range_get_flags(const RangeType *range);
+extern bool range_has_flag(const RangeType *range, char flag);
 extern void range_set_contain_empty(RangeType *range);
 extern RangeType *make_range(TypeCacheEntry *typcache, RangeBound *lower,
 							 RangeBound *upper, bool empty);
@@ -132,8 +149,12 @@ extern int range_cmp_bounds(TypeCacheEntry *typcache, const RangeBound *b1,
 							const RangeBound *b2);
 extern int range_cmp_bound_values(TypeCacheEntry *typcache, const RangeBound *b1,
 								  const RangeBound *b2);
-extern bool bounds_adjacent(TypeCacheEntry *typcache, RangeBound bound1,
-							RangeBound bound2);
+extern int range_compare(const void *key1, const void *key2, void *arg);
+extern bool bounds_adjacent(TypeCacheEntry *typcache, const RangeBound bound1,
+							const RangeBound bound2);
 extern RangeType *make_empty_range(TypeCacheEntry *typcache);
+extern bool range_split_internal(TypeCacheEntry *typcache, const RangeType *r1,
+								 const RangeType *r2, RangeType **output1,
+								 RangeType **output2);
 
 #endif							/* RANGETYPES_H */
diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h
index f27b73d76d..e8f393520a 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -79,6 +79,7 @@ enum SysCacheIdentifier
 	PUBLICATIONOID,
 	PUBLICATIONREL,
 	PUBLICATIONRELMAP,
+	RANGEMULTIRANGE,
 	RANGETYPE,
 	RELNAMENSP,
 	RELOID,
diff --git a/src/include/utils/typcache.h b/src/include/utils/typcache.h
index cdd20e56d7..33f332a141 100644
--- a/src/include/utils/typcache.h
+++ b/src/include/utils/typcache.h
@@ -101,6 +101,11 @@ typedef struct TypeCacheEntry
 	FmgrInfo	rng_subdiff_finfo;	/* difference function, if any */
 
 	/*
+	 * Fields computed when TYPCACHE_MULTIRANGE_INFO is required.
+	 */
+	struct TypeCacheEntry *rngtype;	/* multirange's range underlying type */
+
+	/*
 	 * Domain's base type and typmod if it's a domain type.  Zeroes if not
 	 * domain, or if information hasn't been requested.
 	 */
@@ -143,6 +148,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 c8e43e684f..2d59021d68 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? */
@@ -2087,6 +2089,7 @@ build_datatype(HeapTuple typeTup, int32 typmod,
 		case TYPTYPE_BASE:
 		case TYPTYPE_ENUM:
 		case TYPTYPE_RANGE:
+		case TYPTYPE_MULTIRANGE:
 			typ->ttype = PLPGSQL_TTYPE_SCALAR;
 			break;
 		case TYPTYPE_COMPOSITE:
@@ -2501,6 +2504,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/dependency.out b/src/test/regress/expected/dependency.out
index b4a11b8aa9..8232795148 100644
--- a/src/test/regress/expected/dependency.out
+++ b/src/test/regress/expected/dependency.out
@@ -140,6 +140,7 @@ owner of sequence deptest_a_seq
 owner of table deptest
 owner of function deptest_func()
 owner of type deptest_enum
+owner of type deptest_multirange
 owner of type deptest_range
 owner of table deptest2
 owner of sequence ss1
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..723f2408b0
--- /dev/null
+++ b/src/test/regress/expected/multirangetypes.out
@@ -0,0 +1,2395 @@
+-- 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)
+
+--
+-- test casts, both a built-in range type and a user-defined one:
+--
+select 'empty'::int4range::int4multirange;
+ int4multirange 
+----------------
+ {}
+(1 row)
+
+select int4range(1, 3)::int4multirange;
+ int4range 
+-----------
+ {[1,3)}
+(1 row)
+
+select int4range(1, null)::int4multirange;
+ int4range 
+-----------
+ {[1,)}
+(1 row)
+
+select int4range(null, null)::int4multirange;
+ int4range 
+-----------
+ {(,)}
+(1 row)
+
+select 'empty'::textrange::textmultirange;
+ textmultirange 
+----------------
+ {}
+(1 row)
+
+select textrange('a', 'c')::textmultirange;
+ textrange 
+-----------
+ {[a,c)}
+(1 row)
+
+select textrange('a', null)::textmultirange;
+ textrange 
+-----------
+ {[a,)}
+(1 row)
+
+select textrange(null, null)::textmultirange;
+ textrange 
+-----------
+ {(,)}
+(1 row)
+
+--
+-- create some test data and test the operators
+--
+CREATE TABLE nummultirange_test (nmr NUMMULTIRANGE);
+CREATE INDEX nummultirange_test_btree ON nummultirange_test(nmr);
+INSERT INTO nummultirange_test VALUES('{}');
+INSERT INTO nummultirange_test VALUES('{[,)}');
+INSERT INTO nummultirange_test VALUES('{[3,]}');
+INSERT INTO nummultirange_test VALUES('{[,), [3,]}');
+INSERT INTO nummultirange_test VALUES('{[, 5)}');
+INSERT INTO nummultirange_test VALUES(nummultirange());
+INSERT INTO nummultirange_test VALUES(nummultirange(variadic '{}'::numrange[]));
+INSERT INTO nummultirange_test VALUES(nummultirange(numrange(1.1, 2.2)));
+INSERT INTO nummultirange_test VALUES('{empty}');
+INSERT INTO nummultirange_test VALUES(nummultirange(numrange(1.7, 1.7, '[]'), numrange(1.7, 1.9)));
+INSERT INTO nummultirange_test VALUES(nummultirange(numrange(1.7, 1.7, '[]'), numrange(1.9, 2.1)));
+SELECT nmr, isempty(nmr), lower(nmr), upper(nmr) FROM nummultirange_test ORDER BY nmr;
+          nmr          | isempty | lower | upper 
+-----------------------+---------+-------+-------
+ {}                    | t       |       |      
+ {}                    | t       |       |      
+ {}                    | t       |       |      
+ {}                    | t       |       |      
+ {(,5)}                | f       |       |     5
+ {(,)}                 | f       |       |      
+ {(,)}                 | f       |       |      
+ {[1.1,2.2)}           | f       |   1.1 |   2.2
+ {[1.7,1.7],[1.9,2.1)} | f       |   1.7 |   2.1
+ {[1.7,1.9)}           | f       |   1.7 |   1.9
+ {[3,)}                | f       |     3 |      
+(11 rows)
+
+SELECT nmr, lower_inc(nmr), lower_inf(nmr), upper_inc(nmr), upper_inf(nmr) FROM nummultirange_test ORDER BY nmr;
+          nmr          | lower_inc | lower_inf | upper_inc | upper_inf 
+-----------------------+-----------+-----------+-----------+-----------
+ {}                    | f         | f         | f         | f
+ {}                    | f         | f         | f         | f
+ {}                    | f         | f         | f         | f
+ {}                    | f         | f         | f         | f
+ {(,5)}                | f         | t         | f         | f
+ {(,)}                 | f         | t         | f         | t
+ {(,)}                 | f         | t         | f         | t
+ {[1.1,2.2)}           | t         | f         | f         | f
+ {[1.7,1.7],[1.9,2.1)} | t         | f         | f         | f
+ {[1.7,1.9)}           | t         | f         | f         | f
+ {[3,)}                | t         | f         | f         | t
+(11 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr = '{}';
+ nmr 
+-----
+ {}
+ {}
+ {}
+ {}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr = '{(,5)}';
+  nmr   
+--------
+ {(,5)}
+(1 row)
+
+SELECT * FROM nummultirange_test WHERE nmr = '{[3,)}';
+  nmr   
+--------
+ {[3,)}
+(1 row)
+
+SELECT * FROM nummultirange_test WHERE nmr = '{[1.7,1.7]}';
+ nmr 
+-----
+(0 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr = '{[1.7,1.7],[1.9,2.1)}';
+          nmr          
+-----------------------
+ {[1.7,1.7],[1.9,2.1)}
+(1 row)
+
+SELECT * FROM nummultirange_test WHERE nmr < '{}';
+ nmr 
+-----
+(0 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr < '{[-1000.0, -1000.0]}';
+  nmr   
+--------
+ {}
+ {(,)}
+ {(,)}
+ {(,5)}
+ {}
+ {}
+ {}
+(7 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr < '{[0.0, 1.0]}';
+  nmr   
+--------
+ {}
+ {(,)}
+ {(,)}
+ {(,5)}
+ {}
+ {}
+ {}
+(7 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr < '{[1000.0, 1001.0]}';
+          nmr          
+-----------------------
+ {}
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+ {}
+ {}
+ {[1.1,2.2)}
+ {}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(11 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr <= '{}';
+ nmr 
+-----
+ {}
+ {}
+ {}
+ {}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr <= '{[3,)}';
+          nmr          
+-----------------------
+ {}
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+ {}
+ {}
+ {[1.1,2.2)}
+ {}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(11 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr >= '{}';
+          nmr          
+-----------------------
+ {}
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+ {}
+ {}
+ {[1.1,2.2)}
+ {}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(11 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr >= '{[3,)}';
+  nmr   
+--------
+ {[3,)}
+(1 row)
+
+SELECT * FROM nummultirange_test WHERE nmr > '{}';
+          nmr          
+-----------------------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+ {[1.1,2.2)}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(7 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr > '{[-1000.0, -1000.0]}';
+          nmr          
+-----------------------
+ {[3,)}
+ {[1.1,2.2)}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr > '{[0.0, 1.0]}';
+          nmr          
+-----------------------
+ {[3,)}
+ {[1.1,2.2)}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr > '{[1000.0, 1001.0]}';
+ nmr 
+-----
+(0 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr <> '{}';
+          nmr          
+-----------------------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+ {[1.1,2.2)}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(7 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr <> '{(,5)}';
+          nmr          
+-----------------------
+ {}
+ {(,)}
+ {[3,)}
+ {(,)}
+ {}
+ {}
+ {[1.1,2.2)}
+ {}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(10 rows)
+
+select nummultirange(numrange(2.0, 1.0));
+ERROR:  range lower bound must be less than or equal to range upper bound
+select nummultirange(numrange(5.0, 6.0), numrange(1.0, 2.0));
+     nummultirange     
+-----------------------
+ {[1.0,2.0),[5.0,6.0)}
+(1 row)
+
+-- overlaps
+SELECT * FROM nummultirange_test WHERE range_overlaps_multirange(numrange(4.0, 4.2), nmr);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE numrange(4.0, 4.2) && nmr;
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE multirange_overlaps_range(nmr, numrange(4.0, 4.2));
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr && numrange(4.0, 4.2);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE multirange_overlaps_multirange(nmr, nummultirange(numrange(4.0, 4.2), numrange(6.0, 7.0)));
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr && nummultirange(numrange(4.0, 4.2), numrange(6.0, 7.0));
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr && nummultirange(numrange(6.0, 7.0));
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+(3 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr && nummultirange(numrange(6.0, 7.0), numrange(8.0, 9.0));
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+(3 rows)
+
+-- mr contains x
+SELECT * FROM nummultirange_test WHERE multirange_contains_elem(nmr, 4.0);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr @> 4.0;
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE multirange_contains_range(nmr, numrange(4.0, 4.2));
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr @> numrange(4.0, 4.2);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE multirange_contains_multirange(nmr, '{[4.0,4.2), [6.0, 8.0)}');
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+(3 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr @> '{[4.0,4.2), [6.0, 8.0)}'::nummultirange;
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+(3 rows)
+
+-- x is contained by mr
+SELECT * FROM nummultirange_test WHERE elem_contained_by_multirange(4.0, nmr);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE 4.0 <@ nmr;
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE range_contained_by_multirange(numrange(4.0, 4.2), nmr);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE numrange(4.0, 4.2) <@ nmr;
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE multirange_contained_by_multirange('{[4.0,4.2), [6.0, 8.0)}', nmr);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+(3 rows)
+
+SELECT * FROM nummultirange_test WHERE '{[4.0,4.2), [6.0, 8.0)}'::nummultirange <@ nmr;
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+(3 rows)
+
+-- overlaps
+SELECT 'empty'::numrange && nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT 'empty'::numrange && nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() && 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) && 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() && nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() && nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) && nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) && nummultirange(numrange(1,2), numrange(7,8));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(7,8)) && nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) && nummultirange(numrange(1,2), numrange(3.5,8));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(3.5,8)) && numrange(3,4);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(3.5,8)) && nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+-- contains
+SELECT nummultirange() @> nummultirange();
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange() @> 'empty'::numrange;
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(null,null)) @> numrange(1,2);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(null,null)) @> numrange(null,2);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(null,null)) @> numrange(2,null);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(null,5)) @> numrange(null,3);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(null,5)) @> numrange(null,8);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(5,null)) @> numrange(8,null);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(5,null)) @> numrange(3,null);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,5)) @> numrange(8,9);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,5)) @> numrange(3,9);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,5)) @> numrange(1,4);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,5)) @> numrange(1,5);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(-4,-2), numrange(1,5)) @> numrange(1,5);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,5), numrange(8,9)) @> numrange(1,5);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,5), numrange(8,9)) @> numrange(6,7);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,5), numrange(6,9)) @> numrange(6,7);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[1,5)}'::nummultirange @> '{[1,5)}';
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[-4,-2), [1,5)}'::nummultirange @> '{[1,5)}';
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[1,5), [8,9)}'::nummultirange @> '{[1,5)}';
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[1,5), [8,9)}'::nummultirange @> '{[6,7)}';
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT '{[1,5), [6,9)}'::nummultirange @> '{[6,7)}';
+ ?column? 
+----------
+ t
+(1 row)
+
+-- is contained by
+SELECT nummultirange() <@ nummultirange();
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT 'empty'::numrange <@ nummultirange();
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,2) <@ nummultirange(numrange(null,null));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(null,2) <@ nummultirange(numrange(null,null));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(2,null) <@ nummultirange(numrange(null,null));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(null,3) <@ nummultirange(numrange(null,5));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(null,8) <@ nummultirange(numrange(null,5));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(8,null) <@ nummultirange(numrange(5,null));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(3,null) <@ nummultirange(numrange(5,null));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(8,9) <@ nummultirange(numrange(1,5));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(3,9) <@ nummultirange(numrange(1,5));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(1,4) <@ nummultirange(numrange(1,5));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,5) <@ nummultirange(numrange(1,5));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,5) <@ nummultirange(numrange(-4,-2), numrange(1,5));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,5) <@ nummultirange(numrange(1,5), numrange(8,9));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(6,7) <@ nummultirange(numrange(1,5), numrange(8,9));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(6,7) <@ nummultirange(numrange(1,5), numrange(6,9));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[1,5)}' <@ '{[1,5)}'::nummultirange;
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[1,5)}' <@ '{[-4,-2), [1,5)}'::nummultirange;
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[1,5)}' <@ '{[1,5), [8,9)}'::nummultirange;
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[6,7)}' <@ '{[1,5), [8,9)}'::nummultirange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT '{[6,7)}' <@ '{[1,5), [6,9)}'::nummultirange;
+ ?column? 
+----------
+ t
+(1 row)
+
+-- overleft
+SELECT 'empty'::numrange &< nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT 'empty'::numrange &< nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() &< 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) &< 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() &< nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) &< nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() &< nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(6,7) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(1,2) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,4) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,6) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(3.5,6) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(6,7)) &< numrange(3,4);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) &< numrange(3,4);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,4)) &< numrange(3,4);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,6)) &< numrange(3,4);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3.5,6)) &< numrange(3,4);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(6,7)) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,4)) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,6)) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3.5,6)) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+-- overright
+SELECT nummultirange() &> 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) &> 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT 'empty'::numrange &> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT 'empty'::numrange &> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() &> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() &> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) &> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> numrange(6,7);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> numrange(1,2);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> numrange(1,4);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> numrange(1,6);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> numrange(3.5,6);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(3,4) &> nummultirange(numrange(6,7));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(3,4) &> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(3,4) &> nummultirange(numrange(1,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(3,4) &> nummultirange(numrange(1,6));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(3,4) &> nummultirange(numrange(3.5,6));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(6,7));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(1,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(1,6));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(3.5,6));
+ ?column? 
+----------
+ f
+(1 row)
+
+-- meets
+SELECT 'empty'::numrange -|- nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT 'empty'::numrange -|- nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() -|- 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() -|- nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() -|- nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(1,2) -|- nummultirange(numrange(2,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,2) -|- nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- numrange(2,4);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- numrange(3,4);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- nummultirange(numrange(2,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(5,6)) -|- nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(5,6)) -|- nummultirange(numrange(6,7));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(5,6)) -|- nummultirange(numrange(8,9));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- nummultirange(numrange(2,4), numrange(6,7));
+ ?column? 
+----------
+ t
+(1 row)
+
+-- strictly left
+select 'empty'::numrange << nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(1,2) << nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(1,2) << nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+select numrange(1,2) << nummultirange(numrange(0,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(1,2) << nummultirange(numrange(0,4), numrange(7,8));
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange() << 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange() << numrange(1,2);
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(3,4)) << numrange(3,6);
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(0,2)) << numrange(3,6);
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange(numrange(0,2), numrange(7,8)) << numrange(3,6);
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(-4,-2), numrange(0,2)) << numrange(3,6);
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange() << nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange() << nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(1,2)) << nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(1,2)) << nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(1,2)) << nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange(numrange(1,2)) << nummultirange(numrange(3,4), numrange(7,8));
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange(numrange(1,2), numrange(4,5)) << nummultirange(numrange(3,4), numrange(7,8));
+ ?column? 
+----------
+ f
+(1 row)
+
+-- strictly right
+select nummultirange() >> 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange() >> numrange(1,2);
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(3,4)) >> numrange(1,2);
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange(numrange(0,4)) >> numrange(1,2);
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(0,4), numrange(7,8)) >> numrange(1,2);
+ ?column? 
+----------
+ f
+(1 row)
+
+select 'empty'::numrange >> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(1,2) >> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(3,6) >> nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(3,6) >> nummultirange(numrange(0,2));
+ ?column? 
+----------
+ t
+(1 row)
+
+select numrange(3,6) >> nummultirange(numrange(0,2), numrange(7,8));
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(3,6) >> nummultirange(numrange(-4,-2), numrange(0,2));
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange() >> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(1,2)) >> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange() >> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(1,2)) >> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(3,4)) >> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange(numrange(3,4), numrange(7,8)) >> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange(numrange(3,4), numrange(7,8)) >> nummultirange(numrange(1,2), numrange(4,5));
+ ?column? 
+----------
+ f
+(1 row)
+
+-- union
+SELECT nummultirange() + nummultirange();
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange() + nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) + nummultirange();
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) + nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) + nummultirange(numrange(2,4));
+ ?column? 
+----------
+ {[1,4)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) + nummultirange(numrange(3,4));
+   ?column?    
+---------------
+ {[1,2),[3,4)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) + nummultirange(numrange(2,4));
+ ?column? 
+----------
+ {[1,5)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) + nummultirange(numrange(3,4));
+   ?column?    
+---------------
+ {[1,2),[3,5)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) + nummultirange(numrange(0,9));
+ ?column? 
+----------
+ {[0,9)}
+(1 row)
+
+-- merge
+SELECT range_merge(nummultirange());
+ range_merge 
+-------------
+ empty
+(1 row)
+
+SELECT range_merge(nummultirange(numrange(1,2)));
+ range_merge 
+-------------
+ [1,2)
+(1 row)
+
+SELECT range_merge(nummultirange(numrange(1,2), numrange(7,8)));
+ range_merge 
+-------------
+ [1,8)
+(1 row)
+
+-- minus
+SELECT nummultirange() - nummultirange();
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange() - nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) - nummultirange();
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(3,4)) - nummultirange();
+   ?column?    
+---------------
+ {[1,2),[3,4)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) - nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) - nummultirange(numrange(2,4));
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) - nummultirange(numrange(3,4));
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {[2,4)}
+(1 row)
+
+SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(2,3));
+   ?column?    
+---------------
+ {[1,2),[3,4)}
+(1 row)
+
+SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(0,8));
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(0,2));
+ ?column? 
+----------
+ {[2,4)}
+(1 row)
+
+SELECT nummultirange(numrange(1,8)) - nummultirange(numrange(0,2), numrange(3,4));
+   ?column?    
+---------------
+ {[2,3),[4,8)}
+(1 row)
+
+SELECT nummultirange(numrange(1,8)) - nummultirange(numrange(2,3), numrange(5,null));
+   ?column?    
+---------------
+ {[1,2),[3,5)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(-2,0));
+   ?column?    
+---------------
+ {[1,2),[4,5)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(2,4));
+   ?column?    
+---------------
+ {[1,2),[4,5)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(3,5));
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(0,9));
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange(numrange(1,3), numrange(4,5)) - nummultirange(numrange(2,9));
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(8,9));
+   ?column?    
+---------------
+ {[1,2),[4,5)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(-2,0), numrange(8,9));
+   ?column?    
+---------------
+ {[1,2),[4,5)}
+(1 row)
+
+-- intersection
+SELECT nummultirange() * nummultirange();
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange() * nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) * nummultirange();
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT '{[1,3)}'::nummultirange * '{[1,5)}'::nummultirange;
+ ?column? 
+----------
+ {[1,3)}
+(1 row)
+
+SELECT '{[1,3)}'::nummultirange * '{[0,5)}'::nummultirange;
+ ?column? 
+----------
+ {[1,3)}
+(1 row)
+
+SELECT '{[1,3)}'::nummultirange * '{[0,2)}'::nummultirange;
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT '{[1,3)}'::nummultirange * '{[2,5)}'::nummultirange;
+ ?column? 
+----------
+ {[2,3)}
+(1 row)
+
+SELECT '{[1,4)}'::nummultirange * '{[2,3)}'::nummultirange;
+ ?column? 
+----------
+ {[2,3)}
+(1 row)
+
+SELECT '{[1,4)}'::nummultirange * '{[0,2), [3,5)}'::nummultirange;
+   ?column?    
+---------------
+ {[1,2),[3,4)}
+(1 row)
+
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[0,8), [9,12)}'::nummultirange;
+       ?column?       
+----------------------
+ {[1,4),[7,8),[9,10)}
+(1 row)
+
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[9,12)}'::nummultirange;
+ ?column? 
+----------
+ {[9,10)}
+(1 row)
+
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[-5,-4), [5,6), [9,12)}'::nummultirange;
+ ?column? 
+----------
+ {[9,10)}
+(1 row)
+
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[0,2), [3,8), [9,12)}'::nummultirange;
+          ?column?          
+----------------------------
+ {[1,2),[3,4),[7,8),[9,10)}
+(1 row)
+
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[0,2), [3,8), [9,12)}'::nummultirange;
+          ?column?          
+----------------------------
+ {[1,2),[3,4),[7,8),[9,10)}
+(1 row)
+
+--
+-- range_agg function
+--
+create table reservations ( room_id integer not null, booked_during daterange );
+insert into reservations values
+-- 1: has a meets and a gap
+(1, daterange('2018-07-01', '2018-07-07')),
+(1, daterange('2018-07-07', '2018-07-14')),
+(1, daterange('2018-07-20', '2018-07-22')),
+-- 2: just a single row
+(2, daterange('2018-07-01', '2018-07-03')),
+-- 3: one null range
+(3, NULL),
+-- 4: two null ranges
+(4, NULL),
+(4, NULL),
+-- 5: a null range and a non-null range
+(5, NULL),
+(5, daterange('2018-07-01', '2018-07-03')),
+-- 6: has overlap
+(6, daterange('2018-07-01', '2018-07-07')),
+(6, daterange('2018-07-05', '2018-07-10')),
+-- 7: two ranges that meet: no gap or overlap
+(7, daterange('2018-07-01', '2018-07-07')),
+(7, daterange('2018-07-07', '2018-07-14')),
+-- 8: an empty range
+(8, 'empty'::daterange)
+;
+SELECT   room_id, range_agg(booked_during)
+FROM     reservations
+GROUP BY room_id
+ORDER BY room_id;
+ room_id |                     range_agg                     
+---------+---------------------------------------------------
+       1 | {[07-01-2018,07-14-2018),[07-20-2018,07-22-2018)}
+       2 | {[07-01-2018,07-03-2018)}
+       3 | 
+       4 | 
+       5 | {[07-01-2018,07-03-2018)}
+       6 | {[07-01-2018,07-10-2018)}
+       7 | {[07-01-2018,07-14-2018)}
+       8 | {}
+(8 rows)
+
+-- range_agg on a custom range type too
+SELECT  range_agg(r)
+FROM    (VALUES
+          ('[a,c]'::textrange),
+          ('[b,b]'::textrange),
+          ('[c,f]'::textrange),
+          ('[g,h)'::textrange),
+          ('[h,j)'::textrange)
+        ) t(r);
+   range_agg   
+---------------
+ {[a,f],[g,j)}
+(1 row)
+
+select range_intersect_agg(nmr) from nummultirange_test;
+ range_intersect_agg 
+---------------------
+ {}
+(1 row)
+
+select range_intersect_agg(nmr) from nummultirange_test where false;
+ range_intersect_agg 
+---------------------
+ 
+(1 row)
+
+-- test with just one input:
+select range_intersect_agg(nmr) from (values ('{[1,2]}'::nummultirange)) t(nmr);
+ range_intersect_agg 
+---------------------
+ {[1,2]}
+(1 row)
+
+select range_intersect_agg(nmr) from nummultirange_test where nmr @> 4.0;
+ range_intersect_agg 
+---------------------
+ {[3,5)}
+(1 row)
+
+create table nummultirange_test2(nmr nummultirange);
+create index nummultirange_test2_hash_idx on nummultirange_test2 using hash (nmr);
+INSERT INTO nummultirange_test2 VALUES('{[, 5)}');
+INSERT INTO nummultirange_test2 VALUES(nummultirange(numrange(1.1, 2.2)));
+INSERT INTO nummultirange_test2 VALUES(nummultirange(numrange(1.1, 2.2)));
+INSERT INTO nummultirange_test2 VALUES(nummultirange(numrange(1.1, 2.2,'()')));
+INSERT INTO nummultirange_test2 VALUES('{}');
+select * from nummultirange_test2 where nmr = '{}';
+ nmr 
+-----
+ {}
+(1 row)
+
+select * from nummultirange_test2 where nmr = nummultirange(numrange(1.1, 2.2));
+     nmr     
+-------------
+ {[1.1,2.2)}
+ {[1.1,2.2)}
+(2 rows)
+
+select * from nummultirange_test2 where nmr = nummultirange(numrange(1.1, 2.3));
+ nmr 
+-----
+(0 rows)
+
+set enable_nestloop=t;
+set enable_hashjoin=f;
+set enable_mergejoin=f;
+select * from nummultirange_test natural join nummultirange_test2 order by nmr;
+     nmr     
+-------------
+ {}
+ {}
+ {}
+ {}
+ {(,5)}
+ {[1.1,2.2)}
+ {[1.1,2.2)}
+(7 rows)
+
+set enable_nestloop=f;
+set enable_hashjoin=t;
+set enable_mergejoin=f;
+select * from nummultirange_test natural join nummultirange_test2 order by nmr;
+     nmr     
+-------------
+ {}
+ {}
+ {}
+ {}
+ {(,5)}
+ {[1.1,2.2)}
+ {[1.1,2.2)}
+(7 rows)
+
+set enable_nestloop=f;
+set enable_hashjoin=f;
+set enable_mergejoin=t;
+select * from nummultirange_test natural join nummultirange_test2 order by nmr;
+     nmr     
+-------------
+ {}
+ {}
+ {}
+ {}
+ {(,5)}
+ {[1.1,2.2)}
+ {[1.1,2.2)}
+(7 rows)
+
+set enable_nestloop to default;
+set enable_hashjoin to default;
+set enable_mergejoin to default;
+DROP TABLE nummultirange_test2;
+--
+-- Test user-defined multirange of floats
+--
+select '{[123.001, 5.e9)}'::float8multirange @> 888.882::float8;
+ ?column? 
+----------
+ t
+(1 row)
+
+create table float8multirange_test(f8mr float8multirange, i int);
+insert into float8multirange_test values(float8multirange(float8range(-100.00007, '1.111113e9')), 42);
+select * from float8multirange_test;
+           f8mr            | i  
+---------------------------+----
+ {[-100.00007,1111113000)} | 42
+(1 row)
+
+drop table float8multirange_test;
+--
+-- Test multirange types over domains
+--
+create domain mydomain as int4;
+create type mydomainrange as range(subtype=mydomain);
+select '{[4,50)}'::mydomainmultirange @> 7::mydomain;
+ ?column? 
+----------
+ t
+(1 row)
+
+drop domain mydomain cascade;
+NOTICE:  drop cascades to type mydomainrange
+--
+-- Test domains over multirange types
+--
+create domain restrictedmultirange as int4multirange check (upper(value) < 10);
+select '{[4,5)}'::restrictedmultirange @> 7;
+ ?column? 
+----------
+ f
+(1 row)
+
+select '{[4,50)}'::restrictedmultirange @> 7; -- should fail
+ERROR:  value for domain restrictedmultirange violates check constraint "restrictedmultirange_check"
+drop domain restrictedmultirange;
+--
+-- Test multiple multirange types over the same subtype
+--
+create type textrange1 as range(subtype=text, collation="C");
+create type textrange2 as range(subtype=text, collation="C");
+select textmultirange1(textrange2('a','Z'));  -- should fail
+ERROR:  function textmultirange1(textrange2) does not exist
+LINE 1: select textmultirange1(textrange2('a','Z'));
+               ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+select textmultirange1(textrange1('a','Z')) @> 'b'::text;
+ERROR:  range lower bound must be less than or equal to range upper bound
+select textmultirange2(textrange2('a','z')) @> 'b'::text;
+ ?column? 
+----------
+ t
+(1 row)
+
+drop type textrange1;
+drop type textrange2;
+--
+-- Test polymorphic type system
+--
+create function anyarray_anymultirange_func(a anyarray, r anymultirange)
+  returns anyelement as 'select $1[1] + lower($2);' language sql;
+select anyarray_anymultirange_func(ARRAY[1,2], int4multirange(int4range(10,20)));
+ anyarray_anymultirange_func 
+-----------------------------
+                          11
+(1 row)
+
+-- should fail
+select anyarray_anymultirange_func(ARRAY[1,2], nummultirange(numrange(10,20)));
+ERROR:  function anyarray_anymultirange_func(integer[], nummultirange) does not exist
+LINE 1: select anyarray_anymultirange_func(ARRAY[1,2], nummultirange...
+               ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+drop function anyarray_anymultirange_func(anyarray, anymultirange);
+-- should fail
+create function bogus_func(anyelement)
+  returns anymultirange as 'select int4multirange(int4range(1,10))' language sql;
+ERROR:  cannot determine result data type
+DETAIL:  A function returning "anyrange" or "anymultirange" must have at least one "anyrange" or "anymultirange" argument.
+-- should fail
+create function bogus_func(int)
+  returns anymultirange as 'select int4multirange(int4range(1,10))' language sql;
+ERROR:  cannot determine result data type
+DETAIL:  A function returning a polymorphic type must have at least one polymorphic argument.
+create function range_add_bounds(anymultirange)
+  returns anyelement as 'select lower($1) + upper($1)' language sql;
+select range_add_bounds(int4multirange(int4range(1, 17)));
+ range_add_bounds 
+------------------
+               18
+(1 row)
+
+select range_add_bounds(nummultirange(numrange(1.0001, 123.123)));
+ range_add_bounds 
+------------------
+         124.1231
+(1 row)
+
+create function multirangetypes_sql(q anymultirange, b anyarray, out c anyelement)
+  as $$ select upper($1) + $2[1] $$
+  language sql;
+select multirangetypes_sql(int4multirange(int4range(1,10)), ARRAY[2,20]);
+ multirangetypes_sql 
+---------------------
+                  12
+(1 row)
+
+select multirangetypes_sql(nummultirange(numrange(1,10)), ARRAY[2,20]);  -- match failure
+ERROR:  function multirangetypes_sql(nummultirange, integer[]) does not exist
+LINE 1: select multirangetypes_sql(nummultirange(numrange(1,10)), AR...
+               ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+--
+-- Arrays of multiranges
+--
+select ARRAY[nummultirange(numrange(1.1, 1.2)), nummultirange(numrange(12.3, 155.5))];
+              array               
+----------------------------------
+ {"{[1.1,1.2)}","{[12.3,155.5)}"}
+(1 row)
+
+create table i8mr_array (f1 int, f2 int8multirange[]);
+insert into i8mr_array values (42, array[int8multirange(int8range(1,10)), int8multirange(int8range(2,20))]);
+select * from i8mr_array;
+ f1 |           f2            
+----+-------------------------
+ 42 | {"{[1,10)}","{[2,20)}"}
+(1 row)
+
+drop table i8mr_array;
+--
+-- Multiranges of arrays
+--
+select arraymultirange(arrayrange(ARRAY[1,2], ARRAY[2,1]));
+   arraymultirange   
+---------------------
+ {["{1,2}","{2,1}")}
+(1 row)
+
+select arraymultirange(arrayrange(ARRAY[2,1], ARRAY[1,2]));  -- fail
+ERROR:  range lower bound must be less than or equal to range upper bound
+select array[1,1] <@ arraymultirange(arrayrange(array[1,2], array[2,1]));
+ ?column? 
+----------
+ f
+(1 row)
+
+select array[1,3] <@ arraymultirange(arrayrange(array[1,2], array[2,1]));
+ ?column? 
+----------
+ t
+(1 row)
+
+--
+-- Ranges of composites
+--
+create type two_ints as (a int, b int);
+create type two_ints_range as range (subtype = two_ints);
+-- with force_parallel_mode on, this exercises tqueue.c's range remapping
+select *, row_to_json(upper(t)) as u from
+  (values (two_ints_multirange(two_ints_range(row(1,2), row(3,4)))),
+          (two_ints_multirange(two_ints_range(row(5,6), row(7,8))))) v(t);
+          t          |       u       
+---------------------+---------------
+ {["(1,2)","(3,4)")} | {"a":3,"b":4}
+ {["(5,6)","(7,8)")} | {"a":7,"b":8}
+(2 rows)
+
+drop type two_ints cascade;
+NOTICE:  drop cascades to type two_ints_range
+--
+-- Check behavior when subtype lacks a hash function
+--
+set enable_sort = off;  -- try to make it pick a hash setop implementation
+select '{(2,5)}'::cashmultirange except select '{(5,6)}'::cashmultirange;
+ cashmultirange  
+-----------------
+ {($2.00,$5.00)}
+(1 row)
+
+reset enable_sort;
+--
+-- OUT/INOUT/TABLE functions
+--
+-- infer anymultirange from anymultirange
+create function mr_outparam_succeed(i anymultirange, out r anymultirange, out t text)
+  as $$ select $1, 'foo'::text $$ language sql;
+select * from mr_outparam_succeed(int4multirange(int4range(1,2)));
+    r    |  t  
+---------+-----
+ {[1,2)} | foo
+(1 row)
+
+-- infer anyarray from anymultirange
+create function mr_outparam_succeed2(i anymultirange, out r anyarray, out t text)
+  as $$ select ARRAY[upper($1)], 'foo'::text $$ language sql;
+select * from mr_outparam_succeed2(int4multirange(int4range(1,2)));
+  r  |  t  
+-----+-----
+ {2} | foo
+(1 row)
+
+-- infer anyrange from anymultirange
+create function mr_outparam_succeed3(i anymultirange, out r anyrange, out t text)
+  as $$ select range_merge($1), 'foo'::text $$ language sql;
+select * from mr_outparam_succeed3(int4multirange(int4range(1,2)));
+   r   |  t  
+-------+-----
+ [1,2) | foo
+(1 row)
+
+-- infer anymultirange from anyrange
+create function mr_outparam_succeed4(i anyrange, out r anymultirange, out t text)
+  as $$ select multirange($1), 'foo'::text $$ language sql;
+select * from mr_outparam_succeed4(int4range(1,2));
+    r    |  t  
+---------+-----
+ {[1,2)} | foo
+(1 row)
+
+-- infer anyelement from anymultirange
+create function mr_inoutparam_succeed(out i anyelement, inout r anymultirange)
+  as $$ select upper($1), $1 $$ language sql;
+select * from mr_inoutparam_succeed(int4multirange(int4range(1,2)));
+ i |    r    
+---+---------
+ 2 | {[1,2)}
+(1 row)
+
+-- infer anyelement+anymultirange from anyelement+anymultirange
+create function mr_table_succeed(i anyelement, r anymultirange) returns table(i anyelement, r anymultirange)
+  as $$ select $1, $2 $$ language sql;
+select * from mr_table_succeed(123, int4multirange(int4range(1,11)));
+  i  |    r     
+-----+----------
+ 123 | {[1,11)}
+(1 row)
+
+-- use anymultirange in plpgsql
+create function mr_polymorphic(i anyrange) returns anymultirange
+  as $$ begin return multirange($1); end; $$ language plpgsql;
+select mr_polymorphic(int4range(1, 4));
+ mr_polymorphic 
+----------------
+ {[1,4)}
+(1 row)
+
+-- should fail
+create function mr_outparam_fail(i anyelement, out r anymultirange, out t text)
+  as $$ select '[1,10]', 'foo' $$ language sql;
+ERROR:  cannot determine result data type
+DETAIL:  A function returning "anyrange" or "anymultirange" must have at least one "anyrange" or "anymultirange" argument.
+--should fail
+create function mr_inoutparam_fail(inout i anyelement, out r anymultirange)
+  as $$ select $1, '[1,10]' $$ language sql;
+ERROR:  cannot determine result data type
+DETAIL:  A function returning "anyrange" or "anymultirange" must have at least one "anyrange" or "anymultirange" argument.
+--should fail
+create function mr_table_fail(i anyelement) returns table(i anyelement, r anymultirange)
+  as $$ select $1, '[1,10]' $$ language sql;
+ERROR:  cannot determine result data type
+DETAIL:  A function returning "anyrange" or "anymultirange" must have at least one "anyrange" or "anymultirange" argument.
diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out
index 40468e8f49..1514f3af7c 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
@@ -2139,13 +2154,14 @@ ORDER BY 1, 2, 3;
                     | float_ops        | float4_ops       | real
                     | float_ops        | float8_ops       | double precision
                     | jsonb_ops        | jsonb_ops        | jsonb
+                    | multirange_ops   | multirange_ops   | anymultirange
                     | numeric_ops      | numeric_ops      | numeric
                     | range_ops        | range_ops        | anyrange
                     | record_image_ops | record_image_ops | record
                     | record_ops       | record_ops       | record
                     | tsquery_ops      | tsquery_ops      | tsquery
                     | tsvector_ops     | tsvector_ops     | tsvector
-(14 rows)
+(15 rows)
 
 -- **************** pg_index ****************
 -- Look for illegal values in pg_index fields.
diff --git a/src/test/regress/expected/rangetypes.out b/src/test/regress/expected/rangetypes.out
index 220f2d96cb..6d00a3efab 100644
--- a/src/test/regress/expected/rangetypes.out
+++ b/src/test/regress/expected/rangetypes.out
@@ -518,6 +518,24 @@ select numrange(1.0, 2.0) * numrange(2.5, 3.0);
  empty
 (1 row)
 
+select range_intersect_agg(nr) from numrange_test;
+ range_intersect_agg 
+---------------------
+ empty
+(1 row)
+
+select range_intersect_agg(nr) from numrange_test where false;
+ range_intersect_agg 
+---------------------
+ 
+(1 row)
+
+select range_intersect_agg(nr) from numrange_test where nr @> 4.0;
+ range_intersect_agg 
+---------------------
+ [3,5)
+(1 row)
+
 create table numrange_test2(nr numrange);
 create index numrange_test2_hash_idx on numrange_test2 using hash (nr);
 INSERT INTO numrange_test2 VALUES('[, 5)');
@@ -1371,7 +1389,7 @@ drop function anyarray_anyrange_func(anyarray, anyrange);
 create function bogus_func(anyelement)
   returns anyrange as 'select int4range(1,10)' language sql;
 ERROR:  cannot determine result data type
-DETAIL:  A function returning "anyrange" must have at least one "anyrange" argument.
+DETAIL:  A function returning "anyrange" or "anymultirange" must have at least one "anyrange" or "anymultirange" argument.
 -- should fail
 create function bogus_func(int)
   returns anyrange as 'select int4range(1,10)' language sql;
@@ -1482,6 +1500,7 @@ reset enable_sort;
 --
 -- OUT/INOUT/TABLE functions
 --
+-- infer anyrange from anyrange
 create function outparam_succeed(i anyrange, out r anyrange, out t text)
   as $$ select $1, 'foo'::text $$ language sql;
 select * from outparam_succeed(int4range(1,2));
@@ -1490,6 +1509,16 @@ select * from outparam_succeed(int4range(1,2));
  [1,2) | foo
 (1 row)
 
+-- infer anyarray from anyrange
+create function outparam_succeed2(i anyrange, out r anyarray, out t text)
+  as $$ select ARRAY[upper($1)], 'foo'::text $$ language sql;
+select * from outparam_succeed2(int4range(int4range(1,2)));
+  r  |  t  
+-----+-----
+ {2} | foo
+(1 row)
+
+-- infer anyelement from anyrange
 create function inoutparam_succeed(out i anyelement, inout r anyrange)
   as $$ select upper($1), $1 $$ language sql;
 select * from inoutparam_succeed(int4range(1,2));
@@ -1498,6 +1527,7 @@ select * from inoutparam_succeed(int4range(1,2));
  2 | [1,2)
 (1 row)
 
+-- infer anyelement+anyrange from anyelement+anyrange
 create function table_succeed(i anyelement, r anyrange) returns table(i anyelement, r anyrange)
   as $$ select $1, $2 $$ language sql;
 select * from table_succeed(123, int4range(1,11));
@@ -1510,14 +1540,14 @@ select * from table_succeed(123, int4range(1,11));
 create function outparam_fail(i anyelement, out r anyrange, out t text)
   as $$ select '[1,10]', 'foo' $$ language sql;
 ERROR:  cannot determine result data type
-DETAIL:  A function returning "anyrange" must have at least one "anyrange" argument.
+DETAIL:  A function returning "anyrange" or "anymultirange" must have at least one "anyrange" or "anymultirange" argument.
 --should fail
 create function inoutparam_fail(inout i anyelement, out r anyrange)
   as $$ select $1, '[1,10]' $$ language sql;
 ERROR:  cannot determine result data type
-DETAIL:  A function returning "anyrange" must have at least one "anyrange" argument.
+DETAIL:  A function returning "anyrange" or "anymultirange" must have at least one "anyrange" or "anymultirange" argument.
 --should fail
 create function table_fail(i anyelement) returns table(i anyelement, r anyrange)
   as $$ select $1, '[1,10]' $$ language sql;
 ERROR:  cannot determine result data type
-DETAIL:  A function returning "anyrange" must have at least one "anyrange" argument.
+DETAIL:  A function returning "anyrange" or "anymultirange" must have at least one "anyrange" or "anymultirange" argument.
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index 192445878d..d9ce961be2 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -92,6 +92,7 @@ num_exp_sqrt|t
 num_exp_sub|t
 num_input_test|f
 num_result|f
+nummultirange_test|t
 numrange_test|t
 onek|t
 onek2|t
@@ -172,6 +173,7 @@ quad_poly_tbl|t
 radix_text_tbl|t
 ramp|f
 real_city|f
+reservations|f
 road|t
 shighway|t
 slow_emp4000|f
diff --git a/src/test/regress/expected/type_sanity.out b/src/test/regress/expected/type_sanity.out
index cd7fc03b04..60a9b4d22f 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.rngmultitypid
+FROM pg_range p1
+WHERE p1.rngmultitypid IS NULL OR p1.rngmultitypid = 0;
+ rngtypid | rngsubtype | rngmultitypid 
+----------+------------+---------------
+(0 rows)
+
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index d2b17dd3ea..5355da7b01 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 acba391332..1f677f63af 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..0f70919e1a
--- /dev/null
+++ b/src/test/regress/sql/multirangetypes.sql
@@ -0,0 +1,630 @@
+-- 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'));
+
+--
+-- test casts, both a built-in range type and a user-defined one:
+--
+select 'empty'::int4range::int4multirange;
+select int4range(1, 3)::int4multirange;
+select int4range(1, null)::int4multirange;
+select int4range(null, null)::int4multirange;
+select 'empty'::textrange::textmultirange;
+select textrange('a', 'c')::textmultirange;
+select textrange('a', null)::textmultirange;
+select textrange(null, null)::textmultirange;
+
+--
+-- create some test data and test the operators
+--
+
+CREATE TABLE nummultirange_test (nmr NUMMULTIRANGE);
+CREATE INDEX nummultirange_test_btree ON nummultirange_test(nmr);
+
+INSERT INTO nummultirange_test VALUES('{}');
+INSERT INTO nummultirange_test VALUES('{[,)}');
+INSERT INTO nummultirange_test VALUES('{[3,]}');
+INSERT INTO nummultirange_test VALUES('{[,), [3,]}');
+INSERT INTO nummultirange_test VALUES('{[, 5)}');
+INSERT INTO nummultirange_test VALUES(nummultirange());
+INSERT INTO nummultirange_test VALUES(nummultirange(variadic '{}'::numrange[]));
+INSERT INTO nummultirange_test VALUES(nummultirange(numrange(1.1, 2.2)));
+INSERT INTO nummultirange_test VALUES('{empty}');
+INSERT INTO nummultirange_test VALUES(nummultirange(numrange(1.7, 1.7, '[]'), numrange(1.7, 1.9)));
+INSERT INTO nummultirange_test VALUES(nummultirange(numrange(1.7, 1.7, '[]'), numrange(1.9, 2.1)));
+
+SELECT nmr, isempty(nmr), lower(nmr), upper(nmr) FROM nummultirange_test ORDER BY nmr;
+SELECT nmr, lower_inc(nmr), lower_inf(nmr), upper_inc(nmr), upper_inf(nmr) FROM nummultirange_test ORDER BY nmr;
+
+SELECT * FROM nummultirange_test WHERE nmr = '{}';
+SELECT * FROM nummultirange_test WHERE nmr = '{(,5)}';
+SELECT * FROM nummultirange_test WHERE nmr = '{[3,)}';
+SELECT * FROM nummultirange_test WHERE nmr = '{[1.7,1.7]}';
+SELECT * FROM nummultirange_test WHERE nmr = '{[1.7,1.7],[1.9,2.1)}';
+SELECT * FROM nummultirange_test WHERE nmr < '{}';
+SELECT * FROM nummultirange_test WHERE nmr < '{[-1000.0, -1000.0]}';
+SELECT * FROM nummultirange_test WHERE nmr < '{[0.0, 1.0]}';
+SELECT * FROM nummultirange_test WHERE nmr < '{[1000.0, 1001.0]}';
+SELECT * FROM nummultirange_test WHERE nmr <= '{}';
+SELECT * FROM nummultirange_test WHERE nmr <= '{[3,)}';
+SELECT * FROM nummultirange_test WHERE nmr >= '{}';
+SELECT * FROM nummultirange_test WHERE nmr >= '{[3,)}';
+SELECT * FROM nummultirange_test WHERE nmr > '{}';
+SELECT * FROM nummultirange_test WHERE nmr > '{[-1000.0, -1000.0]}';
+SELECT * FROM nummultirange_test WHERE nmr > '{[0.0, 1.0]}';
+SELECT * FROM nummultirange_test WHERE nmr > '{[1000.0, 1001.0]}';
+SELECT * FROM nummultirange_test WHERE nmr <> '{}';
+SELECT * FROM nummultirange_test WHERE nmr <> '{(,5)}';
+
+select nummultirange(numrange(2.0, 1.0));
+select nummultirange(numrange(5.0, 6.0), numrange(1.0, 2.0));
+
+-- overlaps
+SELECT * FROM nummultirange_test WHERE range_overlaps_multirange(numrange(4.0, 4.2), nmr);
+SELECT * FROM nummultirange_test WHERE numrange(4.0, 4.2) && nmr;
+SELECT * FROM nummultirange_test WHERE multirange_overlaps_range(nmr, numrange(4.0, 4.2));
+SELECT * FROM nummultirange_test WHERE nmr && numrange(4.0, 4.2);
+SELECT * FROM nummultirange_test WHERE multirange_overlaps_multirange(nmr, nummultirange(numrange(4.0, 4.2), numrange(6.0, 7.0)));
+SELECT * FROM nummultirange_test WHERE nmr && nummultirange(numrange(4.0, 4.2), numrange(6.0, 7.0));
+SELECT * FROM nummultirange_test WHERE nmr && nummultirange(numrange(6.0, 7.0));
+SELECT * FROM nummultirange_test WHERE nmr && nummultirange(numrange(6.0, 7.0), numrange(8.0, 9.0));
+
+-- mr contains x
+SELECT * FROM nummultirange_test WHERE multirange_contains_elem(nmr, 4.0);
+SELECT * FROM nummultirange_test WHERE nmr @> 4.0;
+SELECT * FROM nummultirange_test WHERE multirange_contains_range(nmr, numrange(4.0, 4.2));
+SELECT * FROM nummultirange_test WHERE nmr @> numrange(4.0, 4.2);
+SELECT * FROM nummultirange_test WHERE multirange_contains_multirange(nmr, '{[4.0,4.2), [6.0, 8.0)}');
+SELECT * FROM nummultirange_test WHERE nmr @> '{[4.0,4.2), [6.0, 8.0)}'::nummultirange;
+
+-- x is contained by mr
+SELECT * FROM nummultirange_test WHERE elem_contained_by_multirange(4.0, nmr);
+SELECT * FROM nummultirange_test WHERE 4.0 <@ nmr;
+SELECT * FROM nummultirange_test WHERE range_contained_by_multirange(numrange(4.0, 4.2), nmr);
+SELECT * FROM nummultirange_test WHERE numrange(4.0, 4.2) <@ nmr;
+SELECT * FROM nummultirange_test WHERE multirange_contained_by_multirange('{[4.0,4.2), [6.0, 8.0)}', nmr);
+SELECT * FROM nummultirange_test WHERE '{[4.0,4.2), [6.0, 8.0)}'::nummultirange <@ nmr;
+
+-- overlaps
+SELECT 'empty'::numrange && nummultirange();
+SELECT 'empty'::numrange && nummultirange(numrange(1,2));
+SELECT nummultirange() && 'empty'::numrange;
+SELECT nummultirange(numrange(1,2)) && 'empty'::numrange;
+SELECT nummultirange() && nummultirange();
+SELECT nummultirange() && nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) && nummultirange();
+SELECT nummultirange(numrange(3,4)) && nummultirange(numrange(1,2), numrange(7,8));
+SELECT nummultirange(numrange(1,2), numrange(7,8)) && nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(3,4)) && nummultirange(numrange(1,2), numrange(3.5,8));
+SELECT nummultirange(numrange(1,2), numrange(3.5,8)) && numrange(3,4);
+SELECT nummultirange(numrange(1,2), numrange(3.5,8)) && nummultirange(numrange(3,4));
+
+-- contains
+SELECT nummultirange() @> nummultirange();
+SELECT nummultirange() @> 'empty'::numrange;
+SELECT nummultirange(numrange(null,null)) @> numrange(1,2);
+SELECT nummultirange(numrange(null,null)) @> numrange(null,2);
+SELECT nummultirange(numrange(null,null)) @> numrange(2,null);
+SELECT nummultirange(numrange(null,5)) @> numrange(null,3);
+SELECT nummultirange(numrange(null,5)) @> numrange(null,8);
+SELECT nummultirange(numrange(5,null)) @> numrange(8,null);
+SELECT nummultirange(numrange(5,null)) @> numrange(3,null);
+SELECT nummultirange(numrange(1,5)) @> numrange(8,9);
+SELECT nummultirange(numrange(1,5)) @> numrange(3,9);
+SELECT nummultirange(numrange(1,5)) @> numrange(1,4);
+SELECT nummultirange(numrange(1,5)) @> numrange(1,5);
+SELECT nummultirange(numrange(-4,-2), numrange(1,5)) @> numrange(1,5);
+SELECT nummultirange(numrange(1,5), numrange(8,9)) @> numrange(1,5);
+SELECT nummultirange(numrange(1,5), numrange(8,9)) @> numrange(6,7);
+SELECT nummultirange(numrange(1,5), numrange(6,9)) @> numrange(6,7);
+SELECT '{[1,5)}'::nummultirange @> '{[1,5)}';
+SELECT '{[-4,-2), [1,5)}'::nummultirange @> '{[1,5)}';
+SELECT '{[1,5), [8,9)}'::nummultirange @> '{[1,5)}';
+SELECT '{[1,5), [8,9)}'::nummultirange @> '{[6,7)}';
+SELECT '{[1,5), [6,9)}'::nummultirange @> '{[6,7)}';
+
+-- is contained by
+SELECT nummultirange() <@ nummultirange();
+SELECT 'empty'::numrange <@ nummultirange();
+SELECT numrange(1,2) <@ nummultirange(numrange(null,null));
+SELECT numrange(null,2) <@ nummultirange(numrange(null,null));
+SELECT numrange(2,null) <@ nummultirange(numrange(null,null));
+SELECT numrange(null,3) <@ nummultirange(numrange(null,5));
+SELECT numrange(null,8) <@ nummultirange(numrange(null,5));
+SELECT numrange(8,null) <@ nummultirange(numrange(5,null));
+SELECT numrange(3,null) <@ nummultirange(numrange(5,null));
+SELECT numrange(8,9) <@ nummultirange(numrange(1,5));
+SELECT numrange(3,9) <@ nummultirange(numrange(1,5));
+SELECT numrange(1,4) <@ nummultirange(numrange(1,5));
+SELECT numrange(1,5) <@ nummultirange(numrange(1,5));
+SELECT numrange(1,5) <@ nummultirange(numrange(-4,-2), numrange(1,5));
+SELECT numrange(1,5) <@ nummultirange(numrange(1,5), numrange(8,9));
+SELECT numrange(6,7) <@ nummultirange(numrange(1,5), numrange(8,9));
+SELECT numrange(6,7) <@ nummultirange(numrange(1,5), numrange(6,9));
+SELECT '{[1,5)}' <@ '{[1,5)}'::nummultirange;
+SELECT '{[1,5)}' <@ '{[-4,-2), [1,5)}'::nummultirange;
+SELECT '{[1,5)}' <@ '{[1,5), [8,9)}'::nummultirange;
+SELECT '{[6,7)}' <@ '{[1,5), [8,9)}'::nummultirange;
+SELECT '{[6,7)}' <@ '{[1,5), [6,9)}'::nummultirange;
+
+-- overleft
+SELECT 'empty'::numrange &< nummultirange();
+SELECT 'empty'::numrange &< nummultirange(numrange(1,2));
+SELECT nummultirange() &< 'empty'::numrange;
+SELECT nummultirange(numrange(1,2)) &< 'empty'::numrange;
+SELECT nummultirange() &< nummultirange();
+SELECT nummultirange(numrange(1,2)) &< nummultirange();
+SELECT nummultirange() &< nummultirange(numrange(1,2));
+SELECT numrange(6,7) &< nummultirange(numrange(3,4));
+SELECT numrange(1,2) &< nummultirange(numrange(3,4));
+SELECT numrange(1,4) &< nummultirange(numrange(3,4));
+SELECT numrange(1,6) &< nummultirange(numrange(3,4));
+SELECT numrange(3.5,6) &< nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(6,7)) &< numrange(3,4);
+SELECT nummultirange(numrange(1,2)) &< numrange(3,4);
+SELECT nummultirange(numrange(1,4)) &< numrange(3,4);
+SELECT nummultirange(numrange(1,6)) &< numrange(3,4);
+SELECT nummultirange(numrange(3.5,6)) &< numrange(3,4);
+SELECT nummultirange(numrange(6,7)) &< nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,2)) &< nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,4)) &< nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,6)) &< nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(3.5,6)) &< nummultirange(numrange(3,4));
+
+-- overright
+SELECT nummultirange() &> 'empty'::numrange;
+SELECT nummultirange(numrange(1,2)) &> 'empty'::numrange;
+SELECT 'empty'::numrange &> nummultirange();
+SELECT 'empty'::numrange &> nummultirange(numrange(1,2));
+SELECT nummultirange() &> nummultirange();
+SELECT nummultirange() &> nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) &> nummultirange();
+SELECT nummultirange(numrange(3,4)) &> numrange(6,7);
+SELECT nummultirange(numrange(3,4)) &> numrange(1,2);
+SELECT nummultirange(numrange(3,4)) &> numrange(1,4);
+SELECT nummultirange(numrange(3,4)) &> numrange(1,6);
+SELECT nummultirange(numrange(3,4)) &> numrange(3.5,6);
+SELECT numrange(3,4) &> nummultirange(numrange(6,7));
+SELECT numrange(3,4) &> nummultirange(numrange(1,2));
+SELECT numrange(3,4) &> nummultirange(numrange(1,4));
+SELECT numrange(3,4) &> nummultirange(numrange(1,6));
+SELECT numrange(3,4) &> nummultirange(numrange(3.5,6));
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(6,7));
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(1,4));
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(1,6));
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(3.5,6));
+
+-- meets
+SELECT 'empty'::numrange -|- nummultirange();
+SELECT 'empty'::numrange -|- nummultirange(numrange(1,2));
+SELECT nummultirange() -|- 'empty'::numrange;
+SELECT nummultirange(numrange(1,2)) -|- 'empty'::numrange;
+SELECT nummultirange() -|- nummultirange();
+SELECT nummultirange(numrange(1,2)) -|- nummultirange();
+SELECT nummultirange() -|- nummultirange(numrange(1,2));
+SELECT numrange(1,2) -|- nummultirange(numrange(2,4));
+SELECT numrange(1,2) -|- nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,2)) -|- numrange(2,4);
+SELECT nummultirange(numrange(1,2)) -|- numrange(3,4);
+SELECT nummultirange(numrange(1,2)) -|- nummultirange(numrange(2,4));
+SELECT nummultirange(numrange(1,2)) -|- nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,2), numrange(5,6)) -|- nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,2), numrange(5,6)) -|- nummultirange(numrange(6,7));
+SELECT nummultirange(numrange(1,2), numrange(5,6)) -|- nummultirange(numrange(8,9));
+SELECT nummultirange(numrange(1,2)) -|- nummultirange(numrange(2,4), numrange(6,7));
+
+-- strictly left
+select 'empty'::numrange << nummultirange();
+select numrange(1,2) << nummultirange();
+select numrange(1,2) << nummultirange(numrange(3,4));
+select numrange(1,2) << nummultirange(numrange(0,4));
+select numrange(1,2) << nummultirange(numrange(0,4), numrange(7,8));
+select nummultirange() << 'empty'::numrange;
+select nummultirange() << numrange(1,2);
+select nummultirange(numrange(3,4)) << numrange(3,6);
+select nummultirange(numrange(0,2)) << numrange(3,6);
+select nummultirange(numrange(0,2), numrange(7,8)) << numrange(3,6);
+select nummultirange(numrange(-4,-2), numrange(0,2)) << numrange(3,6);
+select nummultirange() << nummultirange();
+select nummultirange() << nummultirange(numrange(1,2));
+select nummultirange(numrange(1,2)) << nummultirange();
+select nummultirange(numrange(1,2)) << nummultirange(numrange(1,2));
+select nummultirange(numrange(1,2)) << nummultirange(numrange(3,4));
+select nummultirange(numrange(1,2)) << nummultirange(numrange(3,4), numrange(7,8));
+select nummultirange(numrange(1,2), numrange(4,5)) << nummultirange(numrange(3,4), numrange(7,8));
+
+-- strictly right
+select nummultirange() >> 'empty'::numrange;
+select nummultirange() >> numrange(1,2);
+select nummultirange(numrange(3,4)) >> numrange(1,2);
+select nummultirange(numrange(0,4)) >> numrange(1,2);
+select nummultirange(numrange(0,4), numrange(7,8)) >> numrange(1,2);
+select 'empty'::numrange >> nummultirange();
+select numrange(1,2) >> nummultirange();
+select numrange(3,6) >> nummultirange(numrange(3,4));
+select numrange(3,6) >> nummultirange(numrange(0,2));
+select numrange(3,6) >> nummultirange(numrange(0,2), numrange(7,8));
+select numrange(3,6) >> nummultirange(numrange(-4,-2), numrange(0,2));
+select nummultirange() >> nummultirange();
+select nummultirange(numrange(1,2)) >> nummultirange();
+select nummultirange() >> nummultirange(numrange(1,2));
+select nummultirange(numrange(1,2)) >> nummultirange(numrange(1,2));
+select nummultirange(numrange(3,4)) >> nummultirange(numrange(1,2));
+select nummultirange(numrange(3,4), numrange(7,8)) >> nummultirange(numrange(1,2));
+select nummultirange(numrange(3,4), numrange(7,8)) >> nummultirange(numrange(1,2), numrange(4,5));
+
+-- union
+SELECT nummultirange() + nummultirange();
+SELECT nummultirange() + nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) + nummultirange();
+SELECT nummultirange(numrange(1,2)) + nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) + nummultirange(numrange(2,4));
+SELECT nummultirange(numrange(1,2)) + nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) + nummultirange(numrange(2,4));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) + nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) + nummultirange(numrange(0,9));
+
+-- merge
+SELECT range_merge(nummultirange());
+SELECT range_merge(nummultirange(numrange(1,2)));
+SELECT range_merge(nummultirange(numrange(1,2), numrange(7,8)));
+
+-- minus
+SELECT nummultirange() - nummultirange();
+SELECT nummultirange() - nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) - nummultirange();
+SELECT nummultirange(numrange(1,2), numrange(3,4)) - nummultirange();
+SELECT nummultirange(numrange(1,2)) - nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) - nummultirange(numrange(2,4));
+SELECT nummultirange(numrange(1,2)) - nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(2,3));
+SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(0,8));
+SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(0,2));
+SELECT nummultirange(numrange(1,8)) - nummultirange(numrange(0,2), numrange(3,4));
+SELECT nummultirange(numrange(1,8)) - nummultirange(numrange(2,3), numrange(5,null));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(-2,0));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(2,4));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(3,5));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(0,9));
+SELECT nummultirange(numrange(1,3), numrange(4,5)) - nummultirange(numrange(2,9));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(8,9));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(-2,0), numrange(8,9));
+
+-- intersection
+SELECT nummultirange() * nummultirange();
+SELECT nummultirange() * nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) * nummultirange();
+SELECT '{[1,3)}'::nummultirange * '{[1,5)}'::nummultirange;
+SELECT '{[1,3)}'::nummultirange * '{[0,5)}'::nummultirange;
+SELECT '{[1,3)}'::nummultirange * '{[0,2)}'::nummultirange;
+SELECT '{[1,3)}'::nummultirange * '{[2,5)}'::nummultirange;
+SELECT '{[1,4)}'::nummultirange * '{[2,3)}'::nummultirange;
+SELECT '{[1,4)}'::nummultirange * '{[0,2), [3,5)}'::nummultirange;
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[0,8), [9,12)}'::nummultirange;
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[9,12)}'::nummultirange;
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[-5,-4), [5,6), [9,12)}'::nummultirange;
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[0,2), [3,8), [9,12)}'::nummultirange;
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[0,2), [3,8), [9,12)}'::nummultirange;
+
+--
+-- range_agg function
+--
+create table reservations ( room_id integer not null, booked_during daterange );
+insert into reservations values
+-- 1: has a meets and a gap
+(1, daterange('2018-07-01', '2018-07-07')),
+(1, daterange('2018-07-07', '2018-07-14')),
+(1, daterange('2018-07-20', '2018-07-22')),
+-- 2: just a single row
+(2, daterange('2018-07-01', '2018-07-03')),
+-- 3: one null range
+(3, NULL),
+-- 4: two null ranges
+(4, NULL),
+(4, NULL),
+-- 5: a null range and a non-null range
+(5, NULL),
+(5, daterange('2018-07-01', '2018-07-03')),
+-- 6: has overlap
+(6, daterange('2018-07-01', '2018-07-07')),
+(6, daterange('2018-07-05', '2018-07-10')),
+-- 7: two ranges that meet: no gap or overlap
+(7, daterange('2018-07-01', '2018-07-07')),
+(7, daterange('2018-07-07', '2018-07-14')),
+-- 8: an empty range
+(8, 'empty'::daterange)
+;
+SELECT   room_id, range_agg(booked_during)
+FROM     reservations
+GROUP BY room_id
+ORDER BY room_id;
+
+-- range_agg on a custom range type too
+SELECT  range_agg(r)
+FROM    (VALUES
+          ('[a,c]'::textrange),
+          ('[b,b]'::textrange),
+          ('[c,f]'::textrange),
+          ('[g,h)'::textrange),
+          ('[h,j)'::textrange)
+        ) t(r);
+
+select range_intersect_agg(nmr) from nummultirange_test;
+select range_intersect_agg(nmr) from nummultirange_test where false;
+-- test with just one input:
+select range_intersect_agg(nmr) from (values ('{[1,2]}'::nummultirange)) t(nmr);
+select range_intersect_agg(nmr) from nummultirange_test where nmr @> 4.0;
+
+create table nummultirange_test2(nmr nummultirange);
+create index nummultirange_test2_hash_idx on nummultirange_test2 using hash (nmr);
+
+INSERT INTO nummultirange_test2 VALUES('{[, 5)}');
+INSERT INTO nummultirange_test2 VALUES(nummultirange(numrange(1.1, 2.2)));
+INSERT INTO nummultirange_test2 VALUES(nummultirange(numrange(1.1, 2.2)));
+INSERT INTO nummultirange_test2 VALUES(nummultirange(numrange(1.1, 2.2,'()')));
+INSERT INTO nummultirange_test2 VALUES('{}');
+
+select * from nummultirange_test2 where nmr = '{}';
+select * from nummultirange_test2 where nmr = nummultirange(numrange(1.1, 2.2));
+select * from nummultirange_test2 where nmr = nummultirange(numrange(1.1, 2.3));
+
+set enable_nestloop=t;
+set enable_hashjoin=f;
+set enable_mergejoin=f;
+select * from nummultirange_test natural join nummultirange_test2 order by nmr;
+set enable_nestloop=f;
+set enable_hashjoin=t;
+set enable_mergejoin=f;
+select * from nummultirange_test natural join nummultirange_test2 order by nmr;
+set enable_nestloop=f;
+set enable_hashjoin=f;
+set enable_mergejoin=t;
+select * from nummultirange_test natural join nummultirange_test2 order by nmr;
+
+set enable_nestloop to default;
+set enable_hashjoin to default;
+set enable_mergejoin to default;
+
+DROP TABLE nummultirange_test2;
+
+--
+-- Test user-defined multirange of floats
+--
+
+select '{[123.001, 5.e9)}'::float8multirange @> 888.882::float8;
+create table float8multirange_test(f8mr float8multirange, i int);
+insert into float8multirange_test values(float8multirange(float8range(-100.00007, '1.111113e9')), 42);
+select * from float8multirange_test;
+drop table float8multirange_test;
+
+--
+-- Test multirange types over domains
+--
+
+create domain mydomain as int4;
+create type mydomainrange as range(subtype=mydomain);
+select '{[4,50)}'::mydomainmultirange @> 7::mydomain;
+drop domain mydomain cascade;
+
+--
+-- Test domains over multirange types
+--
+
+create domain restrictedmultirange as int4multirange check (upper(value) < 10);
+select '{[4,5)}'::restrictedmultirange @> 7;
+select '{[4,50)}'::restrictedmultirange @> 7; -- should fail
+drop domain restrictedmultirange;
+
+--
+-- Test multiple multirange types over the same subtype
+--
+
+create type textrange1 as range(subtype=text, collation="C");
+create type textrange2 as range(subtype=text, collation="C");
+
+select textmultirange1(textrange2('a','Z'));  -- should fail
+select textmultirange1(textrange1('a','Z')) @> 'b'::text;
+select textmultirange2(textrange2('a','z')) @> 'b'::text;
+
+drop type textrange1;
+drop type textrange2;
+
+--
+-- Test polymorphic type system
+--
+
+create function anyarray_anymultirange_func(a anyarray, r anymultirange)
+  returns anyelement as 'select $1[1] + lower($2);' language sql;
+
+select anyarray_anymultirange_func(ARRAY[1,2], int4multirange(int4range(10,20)));
+
+-- should fail
+select anyarray_anymultirange_func(ARRAY[1,2], nummultirange(numrange(10,20)));
+
+drop function anyarray_anymultirange_func(anyarray, anymultirange);
+
+-- should fail
+create function bogus_func(anyelement)
+  returns anymultirange as 'select int4multirange(int4range(1,10))' language sql;
+
+-- should fail
+create function bogus_func(int)
+  returns anymultirange as 'select int4multirange(int4range(1,10))' language sql;
+
+create function range_add_bounds(anymultirange)
+  returns anyelement as 'select lower($1) + upper($1)' language sql;
+
+select range_add_bounds(int4multirange(int4range(1, 17)));
+select range_add_bounds(nummultirange(numrange(1.0001, 123.123)));
+
+create function multirangetypes_sql(q anymultirange, b anyarray, out c anyelement)
+  as $$ select upper($1) + $2[1] $$
+  language sql;
+
+select multirangetypes_sql(int4multirange(int4range(1,10)), ARRAY[2,20]);
+select multirangetypes_sql(nummultirange(numrange(1,10)), ARRAY[2,20]);  -- match failure
+
+--
+-- Arrays of multiranges
+--
+
+select ARRAY[nummultirange(numrange(1.1, 1.2)), nummultirange(numrange(12.3, 155.5))];
+
+create table i8mr_array (f1 int, f2 int8multirange[]);
+insert into i8mr_array values (42, array[int8multirange(int8range(1,10)), int8multirange(int8range(2,20))]);
+select * from i8mr_array;
+drop table i8mr_array;
+
+--
+-- Multiranges of arrays
+--
+
+select arraymultirange(arrayrange(ARRAY[1,2], ARRAY[2,1]));
+select arraymultirange(arrayrange(ARRAY[2,1], ARRAY[1,2]));  -- fail
+
+select array[1,1] <@ arraymultirange(arrayrange(array[1,2], array[2,1]));
+select array[1,3] <@ arraymultirange(arrayrange(array[1,2], array[2,1]));
+
+--
+-- Ranges of composites
+--
+
+create type two_ints as (a int, b int);
+create type two_ints_range as range (subtype = two_ints);
+
+-- with force_parallel_mode on, this exercises tqueue.c's range remapping
+select *, row_to_json(upper(t)) as u from
+  (values (two_ints_multirange(two_ints_range(row(1,2), row(3,4)))),
+          (two_ints_multirange(two_ints_range(row(5,6), row(7,8))))) v(t);
+
+drop type two_ints cascade;
+
+--
+-- Check behavior when subtype lacks a hash function
+--
+
+set enable_sort = off;  -- try to make it pick a hash setop implementation
+
+select '{(2,5)}'::cashmultirange except select '{(5,6)}'::cashmultirange;
+
+reset enable_sort;
+
+--
+-- OUT/INOUT/TABLE functions
+--
+
+-- infer anymultirange from anymultirange
+create function mr_outparam_succeed(i anymultirange, out r anymultirange, out t text)
+  as $$ select $1, 'foo'::text $$ language sql;
+
+select * from mr_outparam_succeed(int4multirange(int4range(1,2)));
+
+-- infer anyarray from anymultirange
+create function mr_outparam_succeed2(i anymultirange, out r anyarray, out t text)
+  as $$ select ARRAY[upper($1)], 'foo'::text $$ language sql;
+
+select * from mr_outparam_succeed2(int4multirange(int4range(1,2)));
+
+-- infer anyrange from anymultirange
+create function mr_outparam_succeed3(i anymultirange, out r anyrange, out t text)
+  as $$ select range_merge($1), 'foo'::text $$ language sql;
+select * from mr_outparam_succeed3(int4multirange(int4range(1,2)));
+
+-- infer anymultirange from anyrange
+create function mr_outparam_succeed4(i anyrange, out r anymultirange, out t text)
+  as $$ select multirange($1), 'foo'::text $$ language sql;
+
+select * from mr_outparam_succeed4(int4range(1,2));
+
+-- infer anyelement from anymultirange
+create function mr_inoutparam_succeed(out i anyelement, inout r anymultirange)
+  as $$ select upper($1), $1 $$ language sql;
+
+select * from mr_inoutparam_succeed(int4multirange(int4range(1,2)));
+
+-- infer anyelement+anymultirange from anyelement+anymultirange
+create function mr_table_succeed(i anyelement, r anymultirange) returns table(i anyelement, r anymultirange)
+  as $$ select $1, $2 $$ language sql;
+
+select * from mr_table_succeed(123, int4multirange(int4range(1,11)));
+
+-- use anymultirange in plpgsql
+create function mr_polymorphic(i anyrange) returns anymultirange
+  as $$ begin return multirange($1); end; $$ language plpgsql;
+select mr_polymorphic(int4range(1, 4));
+
+-- should fail
+create function mr_outparam_fail(i anyelement, out r anymultirange, out t text)
+  as $$ select '[1,10]', 'foo' $$ language sql;
+
+--should fail
+create function mr_inoutparam_fail(inout i anyelement, out r anymultirange)
+  as $$ select $1, '[1,10]' $$ language sql;
+
+--should fail
+create function mr_table_fail(i anyelement) returns table(i anyelement, r anymultirange)
+  as $$ select $1, '[1,10]' $$ language sql;
diff --git a/src/test/regress/sql/opr_sanity.sql b/src/test/regress/sql/opr_sanity.sql
index f06f245db3..4110a56ad2 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/rangetypes.sql b/src/test/regress/sql/rangetypes.sql
index 72d80bc9d4..07c9bcf6d3 100644
--- a/src/test/regress/sql/rangetypes.sql
+++ b/src/test/regress/sql/rangetypes.sql
@@ -118,6 +118,10 @@ select numrange(1.0, 2.0) * numrange(2.0, 3.0);
 select numrange(1.0, 2.0) * numrange(1.5, 3.0);
 select numrange(1.0, 2.0) * numrange(2.5, 3.0);
 
+select range_intersect_agg(nr) from numrange_test;
+select range_intersect_agg(nr) from numrange_test where false;
+select range_intersect_agg(nr) from numrange_test where nr @> 4.0;
+
 create table numrange_test2(nr numrange);
 create index numrange_test2_hash_idx on numrange_test2 using hash (nr);
 
@@ -512,16 +516,25 @@ reset enable_sort;
 -- OUT/INOUT/TABLE functions
 --
 
+-- infer anyrange from anyrange
 create function outparam_succeed(i anyrange, out r anyrange, out t text)
   as $$ select $1, 'foo'::text $$ language sql;
 
 select * from outparam_succeed(int4range(1,2));
 
+-- infer anyarray from anyrange
+create function outparam_succeed2(i anyrange, out r anyarray, out t text)
+  as $$ select ARRAY[upper($1)], 'foo'::text $$ language sql;
+
+select * from outparam_succeed2(int4range(int4range(1,2)));
+
+-- infer anyelement from anyrange
 create function inoutparam_succeed(out i anyelement, inout r anyrange)
   as $$ select upper($1), $1 $$ language sql;
 
 select * from inoutparam_succeed(int4range(1,2));
 
+-- infer anyelement+anyrange from anyelement+anyrange
 create function table_succeed(i anyelement, r anyrange) returns table(i anyelement, r anyrange)
   as $$ select $1, $2 $$ language sql;
 
diff --git a/src/test/regress/sql/type_sanity.sql b/src/test/regress/sql/type_sanity.sql
index fed413bf98..2e34bc8b65 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.rngmultitypid
+FROM pg_range p1
+WHERE p1.rngmultitypid IS NULL OR p1.rngmultitypid = 0;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index e216de9570..c23ce28af1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1354,6 +1354,9 @@ MultiXactMember
 MultiXactOffset
 MultiXactStateData
 MultiXactStatus
+MultirangeIOData
+MultirangeParseState
+MultirangeType
 MyData
 NDBOX
 NODE
#111Paul A Jungwirth
pj@illuminatedcomputing.com
In reply to: Paul A Jungwirth (#110)
1 attachment(s)
Re: range_agg

On Sat, Mar 14, 2020 at 11:13 AM Paul A Jungwirth
<pj@illuminatedcomputing.com> wrote:

I think that should fix the cfbot failure.

I saw this patch was failing to apply again. There was some
refactoring to how polymorphic types are determined. I added my
changes for anymultirange to that new approach, and things should be
passing again.

Yours,
Paul

Attachments:

v14-multirange.patchapplication/octet-stream; name=v14-multirange.patchDownload
diff --git a/doc/src/sgml/extend.sgml b/doc/src/sgml/extend.sgml
index 9ec1af780b..92a1a254ac 100644
--- a/doc/src/sgml/extend.sgml
+++ b/doc/src/sgml/extend.sgml
@@ -229,9 +229,9 @@
    </indexterm>
 
     <para>
-     Five pseudo-types of special interest are <type>anyelement</type>,
+     Six pseudo-types of special interest are <type>anyelement</type>,
      <type>anyarray</type>, <type>anynonarray</type>, <type>anyenum</type>,
-     and <type>anyrange</type>,
+     <type>anyrange</type>, and <type>anymultirange</type>.
      which are collectively called <firstterm>polymorphic types</firstterm>.
      Any function declared using these types is said to be
      a <firstterm>polymorphic function</firstterm>.  A polymorphic function can
@@ -250,15 +250,15 @@
      position declared as <type>anyarray</type> can have any array data type,
      but similarly they must all be the same type.  And similarly,
      positions declared as <type>anyrange</type> must all be the same range
-     type.  Furthermore, if there are
+     type.  Likewise for <type>anymultirange</type>.
+    </para>
+
+    <para>
+     Furthermore, if there are
      positions declared <type>anyarray</type> and others declared
      <type>anyelement</type>, the actual array type in the
      <type>anyarray</type> positions must be an array whose elements are
      the same type appearing in the <type>anyelement</type> positions.
-     Similarly, if there are positions declared <type>anyrange</type>
-     and others declared <type>anyelement</type>, the actual range type in
-     the <type>anyrange</type> positions must be a range whose subtype is
-     the same type appearing in the <type>anyelement</type> positions.
      <type>anynonarray</type> is treated exactly the same as <type>anyelement</type>,
      but adds the additional constraint that the actual type must not be
      an array type.
@@ -268,6 +268,20 @@
     </para>
 
     <para>
+     Similarly, if there are positions declared <type>anyrange</type>
+     and others declared <type>anyelement</type>, the actual range type in
+     the <type>anyrange</type> positions must be a range whose subtype is
+     the same type appearing in the <type>anyelement</type> positions.
+     The type <type>anymultirange</type> accepts a multirange
+     whose contents match the other polymorphic types.
+     That is it must hold ranges matching <type>anyrange</type>
+     and base type elements matching <type>anyelement</type> (if either of
+     those are present). If <type>anyarray</type> is present its elements
+     must match the base type of the <type>anyrange</type> and/or the base
+     type of the ranges in an <type>anymultirange</type>.
+    </para>
+
+    <para>
      Thus, when more than one argument position is declared with a polymorphic
      type, the net effect is that only certain combinations of actual argument
      types are allowed.  For example, a function declared as
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index fc4d7f0f78..45cabfd7e2 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -13546,7 +13546,7 @@ SELECT NULLIF(value, '(none)') ...
        <row>
         <entry>Operator</entry>
         <entry>Description</entry>
-        <entry>Example</entry>
+        <entry>Examples</entry>
         <entry>Result</entry>
        </row>
       </thead>
@@ -14017,12 +14017,14 @@ NULL baz</literallayout>(3 rows)</entry>
   <title>Range Functions and Operators</title>
 
   <para>
-   See <xref linkend="rangetypes"/> for an overview of range types.
+   See <xref linkend="rangetypes"/> for an overview of range and multirange types.
   </para>
 
   <para>
    <xref linkend="range-operators-table"/> shows the operators
-   available for range types.
+   available for range and multirange types.
+   Many of these operators will accept either a range or multirange
+   on either side.
   </para>
 
     <table id="range-operators-table">
@@ -14040,134 +14042,215 @@ NULL baz</literallayout>(3 rows)</entry>
        <row>
         <entry> <literal>=</literal> </entry>
         <entry>equal</entry>
-        <entry><literal>int4range(1,5) = '[1,4]'::int4range</literal></entry>
-        <entry><literal>t</literal></entry>
+        <entry><literallayout class="monospaced">int4range(1,5) = '[1,4]'::int4range
+'{[1,5)}'::int4multirange = '{[1,4]}'::int4multirange</literallayout></entry>
+        <entry><literallayout class="monospaced">t</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>&lt;&gt;</literal> </entry>
         <entry>not equal</entry>
-        <entry><literal>numrange(1.1,2.2) &lt;&gt; numrange(1.1,2.3)</literal></entry>
-        <entry><literal>t</literal></entry>
+        <entry><literallayout class="monospaced">numrange(1.1,2.2) &lt;&gt; numrange(1.1,2.3)
+'{[1.1,2.2)}'::nummultirange &lt;&gt; '{[1.1,2.3)}'::nummultirange</literallayout></entry>
+        <entry><literallayout class="monospaced">t</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>&lt;</literal> </entry>
         <entry>less than</entry>
-        <entry><literal>int4range(1,10) &lt; int4range(2,3)</literal></entry>
-        <entry><literal>t</literal></entry>
+        <entry><literallayout class="monospaced">int4range(1,10) &lt; int4range(2,3)
+'{[1,10)}'::int4multirange &lt; '{[2,3)}'::int4multirange</literallayout>
+        </entry>
+        <entry><literallayout class="monospaced">t</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>&gt;</literal> </entry>
         <entry>greater than</entry>
-        <entry><literal>int4range(1,10) &gt; int4range(1,5)</literal></entry>
-        <entry><literal>t</literal></entry>
+        <entry>
+          <literallayout class="monospaced">int4range(1,10) &gt; int4range(1,5)
+'{[1,10)}'::int4multirange &gt; '{[1,5)}'::int4multirange</literallayout>
+        </entry>
+        <entry><literallayout class="monospaced">t</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>&lt;=</literal> </entry>
         <entry>less than or equal</entry>
-        <entry><literal>numrange(1.1,2.2) &lt;= numrange(1.1,2.2)</literal></entry>
-        <entry><literal>t</literal></entry>
+        <entry>
+          <literallayout class="monospaced">numrange(1.1,2.2) &lt;= numrange(1.1,2.2)
+'{[1.1,2.2)}'::nummultirange &lt;= '{[1.1,2.2)}'::nummultirange</literallayout>
+        </entry>
+        <entry><literallayout class="monospaced">t</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>&gt;=</literal> </entry>
         <entry>greater than or equal</entry>
-        <entry><literal>numrange(1.1,2.2) &gt;= numrange(1.1,2.0)</literal></entry>
-        <entry><literal>t</literal></entry>
+        <entry>
+          <literallayout class="monospaced">numrange(1.1,2.2) &gt;= numrange(1.1,2.0)
+'{[1.1,2.2)}'::nummultirange &gt;= '{[1.1,2.0)}'::nummultirange</literallayout>
+        </entry>
+        <entry><literallayout class="monospaced">t</literallayout></entry>
+       </row>
+
+       <row>
+        <entry> <literal>@&gt;</literal> </entry>
+        <entry>contains multirange</entry>
+        <entry>
+          <literallayout class="monospaced">int4range(2,4) @&gt; '{[2,3)}'::int4multirange
+'{[2,4)}'::int4multirange @&gt; '{[2,3)}'::int4multirange</literallayout>
+        </entry>
+        <entry><literallayout class="monospaced">t</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>@&gt;</literal> </entry>
         <entry>contains range</entry>
-        <entry><literal>int4range(2,4) @&gt; int4range(2,3)</literal></entry>
-        <entry><literal>t</literal></entry>
+        <entry>
+          <literallayout class="monospaced">int4range(2,4) @&gt; int4range(2,3)
+'{[2,4)}'::int4multirange @&gt; int4range(2,3)</literallayout>
+        </entry>
+        <entry><literallayout class="monospaced">t</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>@&gt;</literal> </entry>
         <entry>contains element</entry>
-        <entry><literal>'[2011-01-01,2011-03-01)'::tsrange @&gt; '2011-01-10'::timestamp</literal></entry>
-        <entry><literal>t</literal></entry>
+        <entry>
+          <literallayout class="monospaced">'[2011-01-01,2011-03-01)'::tsrange @&gt; '2011-01-10'::timestamp
+'{[2011-01-01,2011-03-01)}'::tsmultirange @&gt; '2011-01-10'::timestamp</literallayout>
+        </entry>
+        <entry><literallayout class="monospaced">t</literallayout></entry>
+       </row>
+
+       <row>
+        <entry> <literal>&lt;@</literal> </entry>
+        <entry>multirange is contained by</entry>
+        <entry>
+          <literallayout class="monospaced">'{[2,4)}'::int4multirange &lt;@ int4range(1,7)
+'{[2,4)}'::int4multirange &lt;@ '{[1,7)}'::int4multirange</literallayout>
+        </entry>
+        <entry><literallayout class="monospaced">t</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>&lt;@</literal> </entry>
         <entry>range is contained by</entry>
-        <entry><literal>int4range(2,4) &lt;@ int4range(1,7)</literal></entry>
-        <entry><literal>t</literal></entry>
+        <entry>
+          <literallayout class="monospaced">int4range(2,4) &lt;@ int4range(1,7)
+int4range(2,4) &lt;@ '{[1,7)}'::int4multirange</literallayout>
+        </entry>
+        <entry><literallayout class="monospaced">t</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>&lt;@</literal> </entry>
         <entry>element is contained by</entry>
-        <entry><literal>42 &lt;@ int4range(1,7)</literal></entry>
-        <entry><literal>f</literal></entry>
+        <entry>
+          <literallayout class="monospaced">42 &lt;@ int4range(1,7)
+42 &lt;@ '{[1,7)}'::int4multirange</literallayout>
+        </entry>
+        <entry><literallayout class="monospaced">f</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>&amp;&amp;</literal> </entry>
         <entry>overlap (have points in common)</entry>
-        <entry><literal>int8range(3,7) &amp;&amp; int8range(4,12)</literal></entry>
-        <entry><literal>t</literal></entry>
+        <entry>
+          <literallayout class="monospaced">int8range(3,7) &amp;&amp; int8range(4,12)
+int8range(3,7) &amp;&amp; '{[4,12)}'::int8multirange
+'{[3,7)}'::int8multirange &amp;&amp; int8range(4,12)
+'{[3,7)}'::int8multirange &amp;&amp; '{[4,12)}'::int8multirange</literallayout>
+        </entry>
+        <entry><literallayout class="monospaced">t</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>&lt;&lt;</literal> </entry>
         <entry>strictly left of</entry>
-        <entry><literal>int8range(1,10) &lt;&lt; int8range(100,110)</literal></entry>
-        <entry><literal>t</literal></entry>
+        <entry>
+          <literallayout class="monospaced">int8range(1,10) &lt;&lt; int8range(100,110)
+int8range(1,10) &lt;&lt; '{[100,110)}'::int8multirange
+'{[1,10)}'::int8multirange &lt;&lt; int8range(100,110)
+'{[1,10)}'::int8multirange &lt;&lt; '{[100,110)}'::int8multirange</literallayout>
+        </entry>
+        <entry><literallayout class="monospaced">t</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>&gt;&gt;</literal> </entry>
         <entry>strictly right of</entry>
-        <entry><literal>int8range(50,60) &gt;&gt; int8range(20,30)</literal></entry>
-        <entry><literal>t</literal></entry>
+        <entry>
+          <literallayout class="monospaced">int8range(50,60) &gt;&gt; int8range(20,30)
+int8range(50,60) &gt;&gt; '{[20,30)}'::int8multirange
+'{[50,60)}'::int8multirange &gt;&gt; int8range(20,30)
+'{[50,60)}'::int8multirange &gt;&gt; '{[20,30)}'::int8multirange</literallayout>
+        </entry>
+        <entry><literallayout class="monospaced">t</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>&amp;&lt;</literal> </entry>
         <entry>does not extend to the right of</entry>
-        <entry><literal>int8range(1,20) &amp;&lt; int8range(18,20)</literal></entry>
-        <entry><literal>t</literal></entry>
+        <entry>
+          <literallayout class="monospaced">int8range(1,20) &amp;&lt; int8range(18,20)
+int8range(1,20) &amp;&lt; '{[18,20)}'::int8multirange
+'{[1,20)}'::int8multirange &amp;&lt; int8range(18,20)
+'{[1,20)}'::int8multirange &amp;&lt; '{[18,20)}'::int8multirange</literallayout>
+        </entry>
+        <entry><literallayout class="monospaced">t</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>&amp;&gt;</literal> </entry>
         <entry>does not extend to the left of</entry>
-        <entry><literal>int8range(7,20) &amp;&gt; int8range(5,10)</literal></entry>
-        <entry><literal>t</literal></entry>
+        <entry>
+          <literallayout class="monospaced">int8range(7,20) &amp;&gt; int8range(5,10)
+int8range(7,20) &amp;&gt; '{[5,10)}'::int8multirange
+'{[7,20)}'::int8multirange &amp;&gt; int8range(5,10)
+'{[7,20)}'::int8multirange &amp;&gt; '{[5,10)}'::int8multirange</literallayout>
+        </entry>
+        <entry><literallayout class="monospaced">t</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>-|-</literal> </entry>
         <entry>is adjacent to</entry>
-        <entry><literal>numrange(1.1,2.2) -|- numrange(2.2,3.3)</literal></entry>
-        <entry><literal>t</literal></entry>
+        <entry>
+          <literallayout class="monospaced">numrange(1.1,2.2) -|- numrange(2.2,3.3)
+numrange(1.1,2.2) -|- '{[2.2,3.3)}'::nummultirange
+'{[1.1,2.2)}'::nummultirange -|- numrange(2.2,3.3)
+'{[1.1,2.2)}'::nummultirange -|- '{[2.2,3.3)}'::nummultirange</literallayout>
+        </entry>
+        <entry><literallayout class="monospaced">t</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>+</literal> </entry>
         <entry>union</entry>
-        <entry><literal>numrange(5,15) + numrange(10,20)</literal></entry>
-        <entry><literal>[5,20)</literal></entry>
+        <entry><literallayout class="monospaced">numrange(5,15) + numrange(10,20)
+'{[5,10)}'::nummultirange + '{[15,20)}'::nummultirange</literallayout></entry>
+        <entry><literallayout class="monospaced">[5,20)
+{[5,10), [15,20)}</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>*</literal> </entry>
         <entry>intersection</entry>
-        <entry><literal>int8range(5,15) * int8range(10,20)</literal></entry>
-        <entry><literal>[10,15)</literal></entry>
+        <entry><literallayout class="monospaced">int8range(5,15) * int8range(10,20)
+'{[5,15)}'::int8multirange * '{[10,20)}'::int8multirange</literallayout></entry>
+        <entry><literallayout class="monospaced">[10,15)
+{[10,15)}</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>-</literal> </entry>
         <entry>difference</entry>
-        <entry><literal>int8range(5,15) - int8range(10,20)</literal></entry>
-        <entry><literal>[5,10)</literal></entry>
+        <entry><literallayout class="monospaced">int8range(5,15) - int8range(10,20)
+'{[5,20)}'::int8multirange - '{[10,15)}'::int8multirange</literallayout></entry>
+        <entry><literallayout class="monospaced">[5,10)
+{[5,10), [15,20)}</literallayout></entry>
        </row>
 
       </tbody>
@@ -14185,19 +14268,31 @@ NULL baz</literallayout>(3 rows)</entry>
 
   <para>
    The left-of/right-of/adjacent operators always return false when an empty
-   range is involved; that is, an empty range is not considered to be either
-   before or after any other range.
+   range or multirange is involved; that is, an empty range is not considered to
+   be either before or after any other range.
+  </para>
+
+  <para>
+   Elsewhere empty ranges and multiranges are treated as the additive identity:
+   anything unioned with an empty value is itself. Anything minus an empty
+   value is itself. An empty multirange has exactly the same points as an empty
+   range. Every range contains the empty range. Every multirange contains as many
+   empty ranges as you like.
   </para>
 
   <para>
    The union and difference operators will fail if the resulting range would
    need to contain two disjoint sub-ranges, as such a range cannot be
-   represented.
+   represented. There are separate operators for union and difference that take
+   multirange parameters and return a multirange, and they do not fail even if
+   their arguments are disjoint. So if you need a union or difference operation
+   for ranges that may be disjoint, you can avoid errors by first casting your
+   ranges to multiranges.
   </para>
 
   <para>
    <xref linkend="range-functions-table"/> shows the functions
-   available for use with range types.
+   available for use with range and multirange types.
   </para>
 
   <indexterm>
@@ -14249,6 +14344,17 @@ NULL baz</literallayout>(3 rows)</entry>
        <row>
         <entry>
          <literal>
+          <function>lower</function>(<type>anymultirange</type>)
+         </literal>
+        </entry>
+        <entry>multirange's element type</entry>
+        <entry>lower bound of multirange</entry>
+        <entry><literal>lower('{[1.1,2.2)}'::nummultirange)</literal></entry>
+        <entry><literal>1.1</literal></entry>
+       </row>
+       <row>
+        <entry>
+         <literal>
           <function>upper</function>(<type>anyrange</type>)
          </literal>
         </entry>
@@ -14260,6 +14366,17 @@ NULL baz</literallayout>(3 rows)</entry>
        <row>
         <entry>
          <literal>
+          <function>upper</function>(<type>anymultirange</type>)
+         </literal>
+        </entry>
+        <entry>multirange's element type</entry>
+        <entry>upper bound of multirange</entry>
+        <entry><literal>upper('{[1.1,2.2)}'::nummultirange)</literal></entry>
+        <entry><literal>2.2</literal></entry>
+       </row>
+       <row>
+        <entry>
+         <literal>
           <function>isempty</function>(<type>anyrange</type>)
          </literal>
         </entry>
@@ -14271,6 +14388,17 @@ NULL baz</literallayout>(3 rows)</entry>
        <row>
         <entry>
          <literal>
+          <function>isempty</function>(<type>anymultirange</type>)
+         </literal>
+        </entry>
+        <entry><type>boolean</type></entry>
+        <entry>is the multirange empty?</entry>
+        <entry><literal>isempty('{[1.1,2.2)}'::nummultirange)</literal></entry>
+        <entry><literal>false</literal></entry>
+       </row>
+       <row>
+        <entry>
+         <literal>
           <function>lower_inc</function>(<type>anyrange</type>)
          </literal>
         </entry>
@@ -14282,6 +14410,17 @@ NULL baz</literallayout>(3 rows)</entry>
        <row>
         <entry>
          <literal>
+          <function>lower_inc</function>(<type>anymultirange</type>)
+         </literal>
+        </entry>
+        <entry><type>boolean</type></entry>
+        <entry>is the lower bound inclusive?</entry>
+        <entry><literal>lower_inc('{[1.1,2.2)}'::nummultirange)</literal></entry>
+        <entry><literal>true</literal></entry>
+       </row>
+       <row>
+        <entry>
+         <literal>
           <function>upper_inc</function>(<type>anyrange</type>)
          </literal>
         </entry>
@@ -14293,6 +14432,17 @@ NULL baz</literallayout>(3 rows)</entry>
        <row>
         <entry>
          <literal>
+          <function>upper_inc</function>(<type>anymultirange</type>)
+         </literal>
+        </entry>
+        <entry><type>boolean</type></entry>
+        <entry>is the upper bound inclusive?</entry>
+        <entry><literal>upper_inc('{[1.1,2.2)}'::nummultirange)</literal></entry>
+        <entry><literal>false</literal></entry>
+       </row>
+       <row>
+        <entry>
+         <literal>
           <function>lower_inf</function>(<type>anyrange</type>)
          </literal>
         </entry>
@@ -14304,6 +14454,17 @@ NULL baz</literallayout>(3 rows)</entry>
        <row>
         <entry>
          <literal>
+          <function>lower_inf</function>(<type>anymultirange</type>)
+         </literal>
+        </entry>
+        <entry><type>boolean</type></entry>
+        <entry>is the lower bound infinite?</entry>
+        <entry><literal>lower_inf('{(,)}'::datemultirange)</literal></entry>
+        <entry><literal>true</literal></entry>
+       </row>
+       <row>
+        <entry>
+         <literal>
           <function>upper_inf</function>(<type>anyrange</type>)
          </literal>
         </entry>
@@ -14315,6 +14476,17 @@ NULL baz</literallayout>(3 rows)</entry>
        <row>
         <entry>
          <literal>
+          <function>upper_inf</function>(<type>anymultirange</type>)
+         </literal>
+        </entry>
+        <entry><type>boolean</type></entry>
+        <entry>is the upper bound infinite?</entry>
+        <entry><literal>upper_inf('{(,)}'::datemultirange)</literal></entry>
+        <entry><literal>true</literal></entry>
+       </row>
+       <row>
+        <entry>
+         <literal>
           <function>range_merge</function>(<type>anyrange</type>, <type>anyrange</type>)
          </literal>
         </entry>
@@ -14323,16 +14495,38 @@ NULL baz</literallayout>(3 rows)</entry>
         <entry><literal>range_merge('[1,2)'::int4range, '[3,4)'::int4range)</literal></entry>
         <entry><literal>[1,4)</literal></entry>
        </row>
+       <row>
+        <entry>
+         <literal>
+          <function>range_merge</function>(<type>anymultirange</type>)
+         </literal>
+        </entry>
+        <entry><type>anyrange</type></entry>
+        <entry>the smallest range which includes the entire multirange</entry>
+        <entry><literal>range_merge('{[1,2), [3,4)}'::int4multirange)</literal></entry>
+        <entry><literal>[1,4)</literal></entry>
+       </row>
+       <row>
+        <entry>
+         <literal>
+          <function>multirange</function>(<type>anyrange</type>)
+         </literal>
+        </entry>
+        <entry><type>anymultirange</type></entry>
+        <entry>a multirange containing just the given range</entry>
+        <entry><literal>multirange('[1,2)'::int4range)</literal></entry>
+        <entry><literal>{[1,2)}</literal></entry>
+       </row>
       </tbody>
      </tgroup>
     </table>
 
   <para>
    The <function>lower</function> and  <function>upper</function> functions return null
-   if the range is empty or the requested bound is infinite.
+   if the input range/multirange is empty or the requested bound is infinite.
    The <function>lower_inc</function>, <function>upper_inc</function>,
    <function>lower_inf</function>, and <function>upper_inf</function>
-   functions all return false for an empty range.
+   functions all return false for an empty range or multirange.
   </para>
   </sect1>
 
@@ -14654,6 +14848,44 @@ NULL baz</literallayout>(3 rows)</entry>
      <row>
       <entry>
        <indexterm>
+        <primary>range_agg</primary>
+       </indexterm>
+       <function>
+         range_agg(<replaceable class="parameter">expression</replaceable>)
+       </function>
+      </entry>
+      <entry>
+       <type>anyrange</type>
+      </entry>
+      <entry>
+       <type>multirange</type> of the argument range type
+      </entry>
+      <entry>Yes</entry>
+      <entry>union of the non-null input values</entry>
+     </row>
+
+     <row>
+      <entry>
+       <indexterm>
+        <primary>range_intersect_agg</primary>
+       </indexterm>
+       <function>
+         range_intersect_agg(<replaceable class="parameter">expression</replaceable>)
+       </function>
+      </entry>
+      <entry>
+       <type>anyrange</type>
+      </entry>
+      <entry>
+       <type>multirange</type> of the argument range type
+      </entry>
+      <entry>No</entry>
+      <entry>intersection of the non-null input values</entry>
+     </row>
+
+     <row>
+      <entry>
+       <indexterm>
         <primary>string_agg</primary>
        </indexterm>
        <function>
diff --git a/doc/src/sgml/rangetypes.sgml b/doc/src/sgml/rangetypes.sgml
index b75fb3a392..c8784bdce3 100644
--- a/doc/src/sgml/rangetypes.sgml
+++ b/doc/src/sgml/rangetypes.sgml
@@ -27,6 +27,13 @@
   ranges from an instrument, and so forth can also be useful.
  </para>
 
+ <para>
+  Every range type has a corresponding multirange type. A multirange is
+  an ordered list of non-continguous, non-empty, non-null ranges. Most
+  range operators also work on multiranges, and they have a few functions
+  of their own.
+ </para>
+
  <sect2 id="rangetypes-builtin">
   <title>Built-in Range Types</title>
 
@@ -232,10 +239,30 @@ SELECT '[4,4]'::int4range;
 SELECT '[4,4)'::int4range;
 </programlisting>
   </para>
+
+  <para>
+   The input for a multirange is curly brackets (<literal>{</literal> and
+   <literal>}</literal>) containing zero or more valid ranges,
+   separated by commas. Whitespace is permitted around the brackets and
+   commas. This is intended to be reminiscent of array syntax, although
+   multiranges are much simpler: they have just one dimension and there is
+   no need to quote their contents. (The bounds of their ranges may be
+   quoted as above however.)
+  </para>
+
+  <para>
+  Examples:
+<programlisting>
+SELECT '{}'::int4multirange;
+SELECT '{[3,7)}'::int4multirange;
+SELECT '{[3,7), [8,9)}'::int4multirange;
+</programlisting>
+  </para>
+
  </sect2>
 
  <sect2 id="rangetypes-construct">
-  <title>Constructing Ranges</title>
+  <title>Constructing Ranges and Multiranges</title>
 
   <para>
    Each range type has a constructor function with the same name as the range
@@ -269,6 +296,19 @@ SELECT int8range(1, 14, '(]');
 SELECT numrange(NULL, 2.2);
 </programlisting>
   </para>
+
+  <para>
+   Each range type also has a multirange constructor with the same name as the
+   multirange type.  The constructor function takes zero or more arguments
+   which are all ranges of the appropriate type.
+   For example:
+
+<programlisting>
+SELECT nummultirange();
+SELECT nummultirange(numrange(1.0, 14.0));
+SELECT nummultirange(numrange(1.0, 14.0), numrange(20.0, 25.0));
+</programlisting>
+  </para>
  </sect2>
 
  <sect2 id="rangetypes-discrete">
@@ -342,6 +382,11 @@ SELECT '[1.234, 5.678]'::floatrange;
   </para>
 
   <para>
+   When you define your own range you automatically get a corresponding
+   multirange type.
+  </para>
+
+  <para>
    Defining your own range type also allows you to specify a different
    subtype B-tree operator class or collation to use, so as to change the sort
    ordering that determines which values fall into a given range.
diff --git a/src/backend/catalog/pg_proc.c b/src/backend/catalog/pg_proc.c
index 423fd79d94..fb375cd3bf 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,13 @@ 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 b5bc36c2bd..1217a6ddd0 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_rngmultitypid - 1] = ObjectIdGetDatum(multirangeTypeOid);
 
 	tup = heap_form_tuple(RelationGetDescr(pg_range), values, nulls);
 
@@ -100,6 +102,12 @@ 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 cd56714968..c359f27c74 100644
--- a/src/backend/catalog/pg_type.c
+++ b/src/backend/catalog/pg_type.c
@@ -27,6 +27,7 @@
 #include "catalog/pg_proc.h"
 #include "catalog/pg_type.h"
 #include "commands/typecmds.h"
+#include "mb/pg_wchar.h"
 #include "miscadmin.h"
 #include "parser/scansup.h"
 #include "utils/acl.h"
@@ -36,6 +37,9 @@
 #include "utils/rel.h"
 #include "utils/syscache.h"
 
+static char *makeUniqueTypeName(const char *typeName, Oid typeNamespace,
+								bool tryOriginal);
+
 /* Potentially set by pg_upgrade_support functions */
 Oid			binary_upgrade_next_pg_type_oid = InvalidOid;
 
@@ -809,31 +813,10 @@ RenameTypeInternal(Oid typeOid, const char *newTypeName, Oid typeNamespace)
 char *
 makeArrayTypeName(const char *typeName, Oid typeNamespace)
 {
-	char	   *arr = (char *) palloc(NAMEDATALEN);
-	int			namelen = strlen(typeName);
-	int			i;
-
-	/*
-	 * 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;
-	}
+	char	   *arr;
 
-	if (i >= NAMEDATALEN - 1)
+	arr = makeUniqueTypeName(typeName, typeNamespace, false);
+	if (arr == NULL)
 		ereport(ERROR,
 				(errcode(ERRCODE_DUPLICATE_OBJECT),
 				 errmsg("could not form array type name for type \"%s\"",
@@ -904,3 +887,91 @@ moveArrayTypeName(Oid typeOid, const char *typeName, Oid typeNamespace)
 
 	return true;
 }
+
+
+/*
+ * makeMultirangeTypeName
+ *	  - given a range type name, make a multirange type name for it
+ *
+ * caller is responsible for pfreeing the result
+ */
+char *
+makeMultirangeTypeName(const char *rangeTypeName, Oid typeNamespace)
+{
+	char	   *buf;
+	char	   *mrname;
+	char	   *rangestr;
+
+	/*
+	 * 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...
+	 */
+	rangestr = strstr(rangeTypeName, "range");
+	if (rangestr)
+	{
+		char	*prefix = pnstrdup(rangeTypeName, rangestr - rangeTypeName);
+
+		buf = psprintf("%s%s%s", prefix, "multi", rangestr);
+	}
+	else
+		buf = psprintf("%s_multirange", pnstrdup(rangeTypeName, NAMEDATALEN - 12));
+
+	/* clip it at NAMEDATALEN-1 bytes */
+	buf[pg_mbcliplen(buf, strlen(buf), NAMEDATALEN - 1)] = '\0';
+
+	mrname = makeUniqueTypeName(buf, typeNamespace, true);
+	if (mrname == NULL)
+		ereport(ERROR,
+				(errcode(ERRCODE_DUPLICATE_OBJECT),
+				 errmsg("could not form multirange type name for type \"%s\"",
+						rangeTypeName)));
+
+	return mrname;
+}
+
+/*
+ * makeUniqueTypeName
+ *		Generate a unique name for a prospective new type
+ *
+ * Given a typeName, return a new palloc'ed name by preprending underscores
+ * until a non-conflicting name results.
+ *
+ * If tryOriginal, first try with zero underscores.
+ */
+static char *
+makeUniqueTypeName(const char *typeName, Oid typeNamespace, bool tryOriginal)
+{
+	int			i;
+	int			namelen;
+	char		dest[NAMEDATALEN];
+
+	Assert(strlen(typeName) <= NAMEDATALEN - 1);
+
+	if (tryOriginal &&
+		!SearchSysCacheExists2(TYPENAMENSP,
+							   CStringGetDatum(typeName),
+							   ObjectIdGetDatum(typeNamespace)))
+		return pstrdup(typeName);
+
+	/*
+	 * The idea is to prepend underscores as needed until we make a name that
+	 * doesn't collide with anything ...
+	 */
+	namelen = strlen(typeName);
+	for (i = 1; i < NAMEDATALEN - 1; i++)
+	{
+		dest[i - 1] = '_';
+		strlcpy(dest + i, typeName, NAMEDATALEN - i);
+		if (namelen + i >= NAMEDATALEN)
+			truncate_identifier(dest, NAMEDATALEN, false);
+
+		if (!SearchSysCacheExists2(TYPENAMENSP,
+								   CStringGetDatum(dest),
+								   ObjectIdGetDatum(typeNamespace)))
+			return pstrdup(dest);
+	}
+
+	return NULL;
+}
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index 8891b1d564..dfb2edd3d0 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -105,9 +105,14 @@ typedef struct
 
 /* Potentially set by pg_upgrade_support functions */
 Oid			binary_upgrade_next_array_pg_type_oid = InvalidOid;
+Oid			binary_upgrade_next_mrng_pg_type_oid = InvalidOid;
+Oid			binary_upgrade_next_mrng_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 rangeOid,
+									   Oid rangeArrayOid, Oid *castFuncOid);
 static Oid	findTypeInputFunction(List *procname, Oid typeOid);
 static Oid	findTypeOutputFunction(List *procname, Oid typeOid);
 static Oid	findTypeReceiveFunction(List *procname, Oid typeOid);
@@ -738,7 +743,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",
@@ -1279,6 +1285,11 @@ checkEnumOwner(HeapTuple tup)
 /*
  * DefineRange
  *		Registers a new range type.
+ *
+ * Perhaps it might be worthwhile to set pg_type.typelem to the base type,
+ * and likewise on multiranges to set it to the range type. But having a
+ * non-zero typelem is treated elsewhere as a synonym for being an array,
+ * and users might have queries with that same assumption.
  */
 ObjectAddress
 DefineRange(CreateRangeStmt *stmt)
@@ -1287,7 +1298,11 @@ DefineRange(CreateRangeStmt *stmt)
 	Oid			typeNamespace;
 	Oid			typoid;
 	char	   *rangeArrayName;
+	char	   *multirangeTypeName;
+	char	   *multirangeArrayName;
 	Oid			rangeArrayOid;
+	Oid			multirangeOid;
+	Oid			multirangeArrayOid;
 	Oid			rangeSubtype = InvalidOid;
 	List	   *rangeSubOpclassName = NIL;
 	List	   *rangeCollationName = NIL;
@@ -1304,6 +1319,8 @@ DefineRange(CreateRangeStmt *stmt)
 	AclResult	aclresult;
 	ListCell   *lc;
 	ObjectAddress address;
+	ObjectAddress mltrngaddress;
+	Oid			castFuncOid;
 
 	/* Convert list of names to a name and namespace */
 	typeNamespace = QualifiedNameGetCreationNamespace(stmt->typeName,
@@ -1452,8 +1469,10 @@ DefineRange(CreateRangeStmt *stmt)
 	/* alignment must be TYPALIGN_INT or TYPALIGN_DOUBLE for ranges */
 	alignment = (subtypalign == TYPALIGN_DOUBLE) ? TYPALIGN_DOUBLE : TYPALIGN_INT;
 
-	/* Allocate OID for array type */
+	/* Allocate OID for array type, its multirange, and its multirange array */
 	rangeArrayOid = AssignTypeArrayOid();
+	multirangeOid = AssignTypeMultirangeOid();
+	multirangeArrayOid = AssignTypeMultirangeArrayOid();
 
 	/* Create the pg_type entry */
 	address =
@@ -1491,9 +1510,46 @@ DefineRange(CreateRangeStmt *stmt)
 	Assert(typoid == InvalidOid || typoid == address.objectId);
 	typoid = address.objectId;
 
+	/* Create the multirange that goes with it */
+	multirangeTypeName = makeMultirangeTypeName(typeName, typeNamespace);
+
+	mltrngaddress =
+		TypeCreate(multirangeOid,	/* force assignment of this 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_RANGE,	/* type-category (range 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) */
+	Assert(multirangeOid == mltrngaddress.objectId);
+
 	/* Create the entry in pg_range */
 	RangeCreate(typoid, rangeSubtype, rangeCollation, rangeSubOpclass,
-				rangeCanonical, rangeSubtypeDiff);
+				rangeCanonical, rangeSubtypeDiff, multirangeOid);
 
 	/*
 	 * Create the array type that goes with it.
@@ -1534,8 +1590,53 @@ 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 */
+			   multirangeOid,	/* 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,
+							   multirangeOid, typoid, rangeArrayOid,
+							   &castFuncOid);
+
+	/* Create cast from the range type to its multirange type */
+	CastCreate(typoid, multirangeOid, castFuncOid, 'e', 'f', DEPENDENCY_INTERNAL);
+
+	pfree(multirangeTypeName);
+	pfree(multirangeArrayName);
 
 	return address;
 }
@@ -1613,6 +1714,147 @@ 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.
+ *
+ * Sets castFuncOid to the oid of the new constructor that can be used
+ * to cast from a range to a multirange.
+ */
+static void
+makeMultirangeConstructors(const char *name, Oid namespace,
+						   Oid multirangeOid, Oid rangeOid, Oid rangeArrayOid,
+						   Oid *castFuncOid)
+{
+	ObjectAddress myself,
+				referenced;
+	oidvector  *argtypes;
+	Datum		allParamTypes;
+	ArrayType  *allParameterTypes;
+	Datum		paramModes;
+	ArrayType  *parameterModes;
+
+	referenced.classId = TypeRelationId;
+	referenced.objectId = multirangeOid;
+	referenced.objectSubId = 0;
+
+	/* 0-arg constructor - for empty multiranges */
+	argtypes = buildoidvector(NULL, 0);
+	myself = ProcedureCreate(name,	/* name: same as multirange type */
+							 namespace,
+							 false,	/* replace */
+							 false,	/* returns set */
+							 multirangeOid,	/* return type */
+							 BOOTSTRAP_SUPERUSERID,	/* proowner */
+							 INTERNALlanguageId,	/* language */
+							 F_FMGR_INTERNAL_VALIDATOR,
+							 "multirange_constructor0",	/* prosrc */
+							 NULL,	/* probin */
+							 PROKIND_FUNCTION,
+							 false,	/* security_definer */
+							 false,	/* leakproof */
+							 false, /* isStrict */
+							 PROVOLATILE_IMMUTABLE,	/* volatility */
+							 PROPARALLEL_SAFE,	/* parallel safety */
+							 argtypes,	/* parameterTypes */
+							 PointerGetDatum(NULL),	/* allParameterTypes */
+							 PointerGetDatum(NULL),	/* parameterModes */
+							 PointerGetDatum(NULL),	/* parameterNames */
+							 NIL,	/* parameterDefaults */
+							 PointerGetDatum(NULL),	/* trftypes */
+							 PointerGetDatum(NULL),	/* proconfig */
+							 InvalidOid,	/* prosupport */
+							 1.0,	/* procost */
+							 0.0);	/* prorows */
+	/*
+	 * Make the constructor 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);
+	pfree(argtypes);
+
+	/*
+	 * 1-arg constructor - for casts
+	 *
+	 * In theory we shouldn't need both this and the vararg (n-arg) constructor,
+	 * but having a separate 1-arg function lets us define casts against it.
+	 */
+	argtypes = buildoidvector(&rangeOid, 1);
+	myself = ProcedureCreate(name,	/* name: same as multirange type */
+							 namespace,
+							 false,	/* replace */
+							 false,	/* returns set */
+							 multirangeOid,	/* return type */
+							 BOOTSTRAP_SUPERUSERID,	/* proowner */
+							 INTERNALlanguageId,	/* language */
+							 F_FMGR_INTERNAL_VALIDATOR,
+							 "multirange_constructor1",	/* prosrc */
+							 NULL,	/* probin */
+							 PROKIND_FUNCTION,
+							 false,	/* security_definer */
+							 false,	/* leakproof */
+							 true, /* isStrict */
+							 PROVOLATILE_IMMUTABLE,	/* volatility */
+							 PROPARALLEL_SAFE,	/* parallel safety */
+							 argtypes,	/* parameterTypes */
+							 PointerGetDatum(NULL),	/* allParameterTypes */
+							 PointerGetDatum(NULL),	/* parameterModes */
+							 PointerGetDatum(NULL),	/* parameterNames */
+							 NIL,	/* parameterDefaults */
+							 PointerGetDatum(NULL),	/* trftypes */
+							 PointerGetDatum(NULL),	/* proconfig */
+							 InvalidOid,	/* prosupport */
+							 1.0,	/* procost */
+							 0.0);	/* prorows */
+	/* ditto */
+	recordDependencyOn(&myself, &referenced, DEPENDENCY_INTERNAL);
+	pfree(argtypes);
+	*castFuncOid = myself.objectId;
+
+	/* n-arg constructor - vararg */
+	argtypes = buildoidvector(&rangeArrayOid, 1);
+	allParamTypes = ObjectIdGetDatum(rangeArrayOid);
+	allParameterTypes = construct_array(&allParamTypes,
+										1, OIDOID,
+										sizeof(Oid), true, 'i');
+	paramModes = CharGetDatum(FUNC_PARAM_VARIADIC);
+	parameterModes = construct_array(&paramModes, 1, CHAROID,
+									 1, true, 'c');
+	myself = ProcedureCreate(name,	/* name: same as multirange type */
+							 namespace,
+							 false,	/* replace */
+							 false,	/* returns set */
+							 multirangeOid,	/* return type */
+							 BOOTSTRAP_SUPERUSERID,	/* proowner */
+							 INTERNALlanguageId,	/* language */
+							 F_FMGR_INTERNAL_VALIDATOR,
+							 "multirange_constructor2",	/* prosrc */
+							 NULL,	/* probin */
+							 PROKIND_FUNCTION,
+							 false,	/* security_definer */
+							 false,	/* leakproof */
+							 false, /* isStrict */
+							 PROVOLATILE_IMMUTABLE,	/* volatility */
+							 PROPARALLEL_SAFE,	/* parallel safety */
+							 argtypes,	/* parameterTypes */
+							 PointerGetDatum(allParameterTypes),	/* allParameterTypes */
+							 PointerGetDatum(parameterModes),	/* parameterModes */
+							 PointerGetDatum(NULL),	/* parameterNames */
+							 NIL,	/* parameterDefaults */
+							 PointerGetDatum(NULL),	/* trftypes */
+							 PointerGetDatum(NULL),	/* proconfig */
+							 InvalidOid,	/* prosupport */
+							 1.0,	/* procost */
+							 0.0);	/* prorows */
+	/* ditto */
+	recordDependencyOn(&myself, &referenced, DEPENDENCY_INTERNAL);
+	pfree(argtypes);
+	pfree(allParameterTypes);
+	pfree(parameterModes);
+}
 
 /*
  * Find suitable I/O functions for a type.
@@ -2045,6 +2287,72 @@ AssignTypeArrayOid(void)
 	return type_array_oid;
 }
 
+/*
+ *	AssignTypeMultirangeOid
+ *
+ *	Pre-assign the range type's multirange OID for use in pg_type.oid
+ */
+Oid
+AssignTypeMultirangeOid(void)
+{
+	Oid			type_multirange_oid;
+
+	/* Use binary-upgrade override for pg_type.oid? */
+	if (IsBinaryUpgrade)
+	{
+		if (!OidIsValid(binary_upgrade_next_mrng_pg_type_oid))
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+					 errmsg("pg_type multirange OID value not set when in binary upgrade mode")));
+
+		type_multirange_oid = binary_upgrade_next_mrng_pg_type_oid;
+		binary_upgrade_next_mrng_pg_type_oid = InvalidOid;
+	}
+	else
+	{
+		Relation	pg_type = table_open(TypeRelationId, AccessShareLock);
+
+		type_multirange_oid = GetNewOidWithIndex(pg_type, TypeOidIndexId,
+												 Anum_pg_type_oid);
+		table_close(pg_type, AccessShareLock);
+	}
+
+	return type_multirange_oid;
+}
+
+/*
+ *	AssignTypeMultirangeArrayOid
+ *
+ *	Pre-assign the range type's multirange array OID for use in pg_type.typarray
+ */
+Oid
+AssignTypeMultirangeArrayOid(void)
+{
+	Oid			type_multirange_array_oid;
+
+	/* Use binary-upgrade override for pg_type.oid? */
+	if (IsBinaryUpgrade)
+	{
+		if (!OidIsValid(binary_upgrade_next_mrng_array_pg_type_oid))
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+					 errmsg("pg_type multirange array OID value not set when in binary upgrade mode")));
+
+		type_multirange_array_oid = binary_upgrade_next_mrng_array_pg_type_oid;
+		binary_upgrade_next_mrng_array_pg_type_oid = InvalidOid;
+	}
+	else
+	{
+		Relation	pg_type = table_open(TypeRelationId, AccessShareLock);
+
+		type_multirange_array_oid = GetNewOidWithIndex(pg_type, TypeOidIndexId,
+													   Anum_pg_type_oid);
+		table_close(pg_type, AccessShareLock);
+	}
+
+	return type_multirange_array_oid;
+}
+
 
 /*-------------------------------------------------------------------
  * DefineCompositeType
diff --git a/src/backend/parser/parse_coerce.c b/src/backend/parser/parse_coerce.c
index 4a2b463d1b..96bb9f0232 100644
--- a/src/backend/parser/parse_coerce.c
+++ b/src/backend/parser/parse_coerce.c
@@ -185,18 +185,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
@@ -1479,6 +1481,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;
@@ -1525,6 +1529,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 */
@@ -1575,6 +1588,41 @@ check_generic_type_consistency(const Oid *actual_arg_types,
 			/* otherwise, they better match */
 			return false;
 		}
+		else
+			range_typelem = InvalidOid;	/* keep compiler quiet */
+	}
+	else
+		range_typelem = InvalidOid; /* keep compiler quiet */
+
+	/* Get the element type based on the multirange type, if we have one */
+	if (OidIsValid(multirange_typeid))
+	{
+		multirange_typelem = get_range_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)
@@ -1629,10 +1677,11 @@ check_generic_type_consistency(const Oid *actual_arg_types,
  * 5) If return type is ANYELEMENT, and any argument is ANYELEMENT, use the
  *	  argument's actual type as the function's return type.
  * 6) If return type is ANYELEMENT, no argument is ANYELEMENT, but any argument
- *	  is ANYARRAY or ANYRANGE, use the actual type of the argument to determine
- *	  the function's return type, i.e. the array type's corresponding element
- *	  type or the range type's corresponding subtype (or both, in which case
- *	  they must match).
+ *	  is ANYARRAY or ANYRANGE or ANYMULTIRANGE, use the actual type of the
+ *	  argument to determine the function's return type, i.e. the array type's
+ *	  corresponding element type or the range type's corresponding subtype
+ *	  or the multirange type's range type's corresponding subtype (or more than
+ *	  one, in which case they must match).
  * 7) If return type is ANYELEMENT, no argument is ANYELEMENT, ANYARRAY, or
  *	  ANYRANGE, generate an error.  (This condition is prevented by CREATE
  *	  FUNCTION and therefore is not expected here.)
@@ -1678,13 +1727,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.
@@ -1742,7 +1795,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;
@@ -1760,6 +1813,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;
+		}
 	}
 
 	/*
@@ -1810,9 +1883,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
@@ -1844,6 +1920,71 @@ enforce_generic_type_consistency(const Oid *actual_arg_types,
 							   format_type_be(range_typeid),
 							   format_type_be(elem_typeid))));
 		}
+		else
+			range_typelem = InvalidOid;
+	}
+
+	/* 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_range_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))
@@ -1853,6 +1994,7 @@ enforce_generic_type_consistency(const Oid *actual_arg_types,
 			elem_typeid = ANYELEMENTOID;
 			array_typeid = ANYARRAYOID;
 			range_typeid = ANYRANGEOID;
+			multirange_typeid = ANYMULTIRANGEOID;
 		}
 		else
 		{
@@ -1925,6 +2067,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;
+			}
 		}
 	}
 
@@ -1956,6 +2109,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 ||
@@ -1966,7 +2135,6 @@ enforce_generic_type_consistency(const Oid *actual_arg_types,
 	return rettype;
 }
 
-
 /* TypeCategory()
  *		Assign a category to the specified type OID.
  *
@@ -2069,6 +2237,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..bd3cf3dc93
--- /dev/null
+++ b/src/backend/utils/adt/multirangetypes.c
@@ -0,0 +1,2186 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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/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 */
+	FmgrInfo	typioproc;		/* range type's I/O proc */
+	Oid			typioparam;		/* range type's I/O parameter */
+} 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 mltrngtypid,
+												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;
+	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 = NULL;
+	int32		range_str_len;
+	char	   *range_str_copy;
+
+	cache = get_multirange_io_data(fcinfo, mltrngtypoid, IOFunc_input);
+	rangetyp = cache->typcache->rngtype;
+
+	/* 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->typioproc,
+																 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->typioproc, 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->typioproc,
+														   &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;
+
+		range = RangeTypePGetDatum(ranges[i]);
+		range = PointerGetDatum(SendFunctionCall(&cache->typioproc, range));
+
+		pq_sendint32(buf, VARSIZE(range) - VARHDRSZ);
+		pq_sendbytes(buf, VARDATA(range), VARSIZE(range) - VARHDRSZ);
+	}
+
+	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)
+	{
+		Oid			typiofunc;
+		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,
+						 &typiofunc);
+
+		if (!OidIsValid(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(typiofunc, &cache->typioproc,
+					  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;
+	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++)
+		bytelen += MAXALIGN(VARSIZE(ranges[i]));
+
+	/* 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++)
+	{
+		memcpy(ptr, ranges[i], VARSIZE(ranges[i]));
+		ptr += MAXALIGN(VARSIZE(ranges[i]));
+	}
+
+	return multirange;
+}
+
+/*
+ * Converts a list of arbitrary ranges 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_constructor2(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,
+				(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+				 errmsg("multirange values cannot contain NULL members")));
+
+	rangeArray = PG_GETARG_ARRAYTYPE_P(0);
+
+	dims = ARR_NDIM(rangeArray);
+	if (dims > 1)
+		ereport(ERROR,
+				(errcode(ERRCODE_CARDINALITY_VIOLATION),
+				 errmsg("multiranges cannot be constructed from multi-dimensional arrays")));
+
+	rngtypid = ARR_ELEMTYPE(rangeArray);
+	if (rngtypid != rangetyp->type_id)
+		ereport(ERROR,
+				(errcode(ERRCODE_DATATYPE_MISMATCH),
+				 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,
+						(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+						 errmsg("multirange values cannot contain NULL members")));
+
+			/* make_multirange will do its own copy */
+			ranges[i] = DatumGetRangeTypeP(elements[i]);
+		}
+	}
+
+	PG_RETURN_MULTIRANGE_P(make_multirange(mltrngtypid, rangetyp, range_count, ranges));
+}
+
+/*
+ * Construct multirange value from a single range.
+ * It'd be nice if we could just use multirange_constructor2
+ * for this case, but we need a non-variadic single-arg function
+ * to let us define a CAST from a range to its multirange.
+ */
+Datum
+multirange_constructor1(PG_FUNCTION_ARGS)
+{
+	Oid			mltrngtypid = get_fn_expr_rettype(fcinfo->flinfo);
+	Oid			rngtypid;
+	TypeCacheEntry *typcache;
+	TypeCacheEntry *rangetyp;
+	RangeType  *range;
+
+	typcache = multirange_get_typcache(fcinfo, mltrngtypid);
+	rangetyp = typcache->rngtype;
+
+	/*
+	 * These checks should be guaranteed by our signature, but let's do them
+	 * just in case.
+	 */
+
+	if (PG_ARGISNULL(0))
+				ereport(ERROR,
+						(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+						 errmsg("multirange values cannot contain NULL members")));
+
+	range = PG_GETARG_RANGE_P(0);
+
+	/* Make sure the range type matches. */
+	rngtypid = RangeTypeGetOid(range);
+	if (rngtypid != rangetyp->type_id)
+		ereport(ERROR,
+				(errcode(ERRCODE_DATATYPE_MISMATCH),
+				 errmsg("type %u does not match constructor type", rngtypid)));
+
+	PG_RETURN_MULTIRANGE_P(make_multirange(mltrngtypid, rangetyp, 1, &range));
+}
+
+/*
+ * 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
+		elog(ERROR,		/* can't happen */
+			 "niladic multirange constructor must not receive arguments");
+}
+
+
+/* multirange, multirange -> multirange type functions */
+
+/* multirange union */
+Datum
+multirange_union(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
+	MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count1;
+	int32		range_count2;
+	int32		range_count3;
+	RangeType **ranges1;
+	RangeType **ranges2;
+	RangeType **ranges3;
+
+	if (MultirangeIsEmpty(mr1))
+		PG_RETURN_MULTIRANGE_P(mr2);
+	if (MultirangeIsEmpty(mr2))
+		PG_RETURN_MULTIRANGE_P(mr1);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr1));
+
+	multirange_deserialize(mr1, &range_count1, &ranges1);
+	multirange_deserialize(mr2, &range_count2, &ranges2);
+
+	range_count3 = range_count1 + range_count2;
+	ranges3 = palloc0(range_count3 * sizeof(RangeType *));
+	memcpy(ranges3, ranges1, range_count1 * sizeof(RangeType *));
+	memcpy(ranges3 + range_count1, ranges2, range_count2 * sizeof(RangeType *));
+	PG_RETURN_MULTIRANGE_P(make_multirange(typcache->type_id, typcache->rngtype,
+										   range_count3, ranges3));
+}
+
+/* multirange minus */
+Datum
+multirange_minus(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
+	MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
+	Oid			mltrngtypoid = MultirangeTypeGetOid(mr1);
+	TypeCacheEntry *typcache;
+	TypeCacheEntry *rangetyp;
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+
+	typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
+	rangetyp = typcache->rngtype;
+
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		PG_RETURN_MULTIRANGE_P(mr1);
+
+	multirange_deserialize(mr1, &range_count1, &ranges1);
+	multirange_deserialize(mr2, &range_count2, &ranges2);
+
+	PG_RETURN_MULTIRANGE_P(multirange_minus_internal(mltrngtypoid,
+													 rangetyp,
+													 range_count1,
+													 ranges1,
+													 range_count2,
+													 ranges2));
+}
+
+MultirangeType *
+multirange_minus_internal(Oid mltrngtypoid, TypeCacheEntry *rangetyp,
+						  int32 range_count1, RangeType **ranges1,
+						  int32 range_count2, RangeType **ranges2)
+{
+	RangeType  *r1;
+	RangeType  *r2;
+	RangeType **ranges3;
+	int32		range_count3;
+	int32		i1;
+	int32		i2;
+
+	/*
+	 * Worst case: every range in ranges1 makes a different cut to some range
+	 * in ranges2.
+	 */
+	ranges3 = palloc0((range_count1 + range_count2) * sizeof(RangeType *));
+	range_count3 = 0;
+
+	/*
+	 * For each range in mr1, keep subtracting until it's gone or the ranges
+	 * in mr2 have passed it. After a subtraction we assign what's left back
+	 * to r1. The parallel progress through mr1 and mr2 is similar to
+	 * multirange_overlaps_multirange_internal.
+	 */
+	r2 = ranges2[0];
+	for (i1 = 0, i2 = 0; i1 < range_count1; i1++)
+	{
+		r1 = ranges1[i1];
+
+		/* Discard r2s while r2 << r1 */
+		while (r2 != NULL && range_before_internal(rangetyp, r2, r1))
+		{
+			r2 = ++i2 >= range_count2 ? NULL : ranges2[i2];
+		}
+
+		while (r2 != NULL)
+		{
+			if (range_split_internal(rangetyp, r1, r2, &ranges3[range_count3], &r1))
+			{
+				/*
+				 * If r2 takes a bite out of the middle of r1, we need two
+				 * outputs
+				 */
+				range_count3++;
+				r2 = ++i2 >= range_count2 ? NULL : ranges2[i2];
+
+			}
+			else if (range_overlaps_internal(rangetyp, r1, r2))
+			{
+				/*
+				 * If r2 overlaps r1, replace r1 with r1 - r2.
+				 */
+				r1 = range_minus_internal(rangetyp, r1, r2);
+
+				/*
+				 * If r2 goes past r1, then we need to stay with it, in case
+				 * it hits future r1s. Otherwise we need to keep r1, in case
+				 * future r2s hit it. Since we already subtracted, there's no
+				 * point in using the overright/overleft calls.
+				 */
+				if (RangeIsEmpty(r1) || range_before_internal(rangetyp, r1, r2))
+					break;
+				else
+					r2 = ++i2 >= range_count2 ? NULL : ranges2[i2];
+
+			}
+			else
+			{
+				/*
+				 * This and all future r2s are past r1, so keep them. Also
+				 * assign whatever is left of r1 to the result.
+				 */
+				break;
+			}
+		}
+
+		/*
+		 * Nothing else can remove anything from r1, so keep it. Even if r1 is
+		 * empty here, make_multirange will remove it.
+		 */
+		ranges3[range_count3++] = r1;
+	}
+
+	return make_multirange(mltrngtypoid, rangetyp, range_count3, ranges3);
+}
+
+/* multirange intersection */
+Datum
+multirange_intersect(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
+	MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
+	Oid			mltrngtypoid = MultirangeTypeGetOid(mr1);
+	TypeCacheEntry *typcache;
+	TypeCacheEntry *rangetyp;
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+
+	typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
+	rangetyp = typcache->rngtype;
+
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		PG_RETURN_MULTIRANGE_P(make_empty_multirange(mltrngtypoid, rangetyp));
+
+	multirange_deserialize(mr1, &range_count1, &ranges1);
+	multirange_deserialize(mr2, &range_count2, &ranges2);
+
+	PG_RETURN_MULTIRANGE_P(multirange_intersect_internal(mltrngtypoid,
+														 rangetyp,
+														 range_count1,
+														 ranges1,
+														 range_count2,
+														 ranges2));
+}
+
+MultirangeType *
+multirange_intersect_internal(Oid mltrngtypoid, TypeCacheEntry *rangetyp,
+							  int32 range_count1, RangeType **ranges1,
+							  int32 range_count2, RangeType **ranges2)
+{
+	RangeType  *r1;
+	RangeType  *r2;
+	RangeType **ranges3;
+	int32		range_count3;
+	int32		i1;
+	int32		i2;
+
+	if (range_count1 == 0 || range_count2 == 0)
+		return make_multirange(mltrngtypoid, rangetyp, 0, NULL);
+
+	/*-----------------------------------------------
+	 * Worst case is a stitching pattern like this:
+	 *
+	 * mr1: --- --- --- ---
+	 * mr2:   --- --- ---
+	 * mr3:   - - - - - -
+	 *
+	 * That seems to be range_count1 + range_count2 - 1,
+	 * but one extra won't hurt.
+	 *-----------------------------------------------
+	 */
+	ranges3 = palloc0((range_count1 + range_count2) * sizeof(RangeType *));
+	range_count3 = 0;
+
+	/*
+	 * For each range in mr1, keep intersecting until the ranges in mr2 have
+	 * passed it. The parallel progress through mr1 and mr2 is similar to
+	 * multirange_minus_multirange_internal, but we don't have to assign back
+	 * to r1.
+	 */
+	r2 = ranges2[0];
+	for (i1 = 0, i2 = 0; i1 < range_count1; i1++)
+	{
+		r1 = ranges1[i1];
+
+		/* Discard r2s while r2 << r1 */
+		while (r2 != NULL && range_before_internal(rangetyp, r2, r1))
+		{
+			r2 = ++i2 >= range_count2 ? NULL : ranges2[i2];
+		}
+
+		while (r2 != NULL)
+		{
+			if (range_overlaps_internal(rangetyp, r1, r2))
+			{
+				/* Keep the overlapping part */
+				ranges3[range_count3++] = range_intersect_internal(rangetyp, r1, r2);
+
+				/* If we "used up" all of r2, go to the next one... */
+				if (range_overleft_internal(rangetyp, r2, r1))
+					r2 = ++i2 >= range_count2 ? NULL : ranges2[i2];
+
+				/* ...otherwise go to the next r1 */
+				else
+					break;
+			}
+			else
+				/* We're past r1, so move to the next one */
+				break;
+		}
+
+		/* If we're out of r2s, there can be no more intersections */
+		if (r2 == NULL)
+			break;
+	}
+
+	return make_multirange(mltrngtypoid, rangetyp, range_count3, ranges3);
+}
+
+/*
+ * range_agg_transfn: combine adjacent/overlapping ranges.
+ *
+ * All we do here is gather the input ranges into an array
+ * so that the finalfn can sort and combine them.
+ */
+Datum
+range_agg_transfn(PG_FUNCTION_ARGS)
+{
+	MemoryContext aggContext;
+	Oid			rngtypoid;
+	ArrayBuildState *state;
+
+	if (!AggCheckCallContext(fcinfo, &aggContext))
+		elog(ERROR, "range_agg_transfn called in non-aggregate context");
+
+	rngtypoid = get_fn_expr_argtype(fcinfo->flinfo, 1);
+	if (!type_is_range(rngtypoid))
+		ereport(ERROR,
+				(errcode(ERRCODE_DATATYPE_MISMATCH),
+				 errmsg("range_agg must be called with a range")));
+
+	if (PG_ARGISNULL(0))
+		state = initArrayResult(rngtypoid, aggContext, false);
+	else
+		state = (ArrayBuildState *) PG_GETARG_POINTER(0);
+
+	/* skip NULLs */
+	if (!PG_ARGISNULL(1))
+		accumArrayResult(state, PG_GETARG_DATUM(1), false, rngtypoid, aggContext);
+
+	PG_RETURN_POINTER(state);
+}
+
+/*
+ * range_agg_finalfn: use our internal array to merge touching ranges.
+ */
+Datum
+range_agg_finalfn(PG_FUNCTION_ARGS)
+{
+	MemoryContext aggContext;
+	Oid			mltrngtypoid;
+	TypeCacheEntry *typcache;
+	ArrayBuildState *state;
+	int32		range_count;
+	RangeType **ranges;
+	int			i;
+
+	if (!AggCheckCallContext(fcinfo, &aggContext))
+		elog(ERROR, "range_agg_finalfn called in non-aggregate context");
+
+	state = PG_ARGISNULL(0) ? NULL : (ArrayBuildState *) PG_GETARG_POINTER(0);
+	if (state == NULL)
+		/* This shouldn't be possible, but just in case.... */
+		PG_RETURN_NULL();
+
+	/* Also return NULL if we had zero inputs, like other aggregates */
+	range_count = state->nelems;
+	if (range_count == 0)
+		PG_RETURN_NULL();
+
+	mltrngtypoid = get_fn_expr_rettype(fcinfo->flinfo);
+	typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
+
+	ranges = palloc0(range_count * sizeof(RangeType *));
+	for (i = 0; i < range_count; i++)
+		ranges[i] = DatumGetRangeTypeP(state->dvalues[i]);
+
+	PG_RETURN_MULTIRANGE_P(make_multirange(mltrngtypoid, typcache->rngtype, range_count, ranges));
+}
+
+Datum
+multirange_intersect_agg_transfn(PG_FUNCTION_ARGS)
+{
+	MemoryContext aggContext;
+	Oid			mltrngtypoid;
+	TypeCacheEntry *typcache;
+	MultirangeType *result;
+	MultirangeType *current;
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+
+	if (!AggCheckCallContext(fcinfo, &aggContext))
+		elog(ERROR, "multirange_intersect_agg_transfn called in non-aggregate context");
+
+	mltrngtypoid = get_fn_expr_argtype(fcinfo->flinfo, 1);
+	if (!type_is_multirange(mltrngtypoid))
+		ereport(ERROR,
+				(errcode(ERRCODE_DATATYPE_MISMATCH),
+				 errmsg("range_intersect_agg must be called with a multirange")));
+
+	typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
+
+	/* strictness ensures these are non-null */
+	result = PG_GETARG_MULTIRANGE_P(0);
+	current = PG_GETARG_MULTIRANGE_P(1);
+
+	multirange_deserialize(result, &range_count1, &ranges1);
+	multirange_deserialize(current, &range_count2, &ranges2);
+
+	result = multirange_intersect_internal(mltrngtypoid,
+										   typcache->rngtype,
+										   range_count1,
+										   ranges1,
+										   range_count2,
+										   ranges2);
+	PG_RETURN_RANGE_P(result);
+}
+
+
+/* multirange -> element type functions */
+
+/* extract lower bound value */
+Datum
+multirange_lower(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+	bool		isnull;
+	Datum		result;
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_NULL();
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	result = range_lower_internal(typcache->rngtype, ranges[0], &isnull);
+
+	if (isnull)
+		PG_RETURN_NULL();
+	else
+		PG_RETURN_DATUM(result);
+}
+
+/* extract upper bound value */
+Datum
+multirange_upper(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+	bool		isnull;
+	Datum		result;
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_NULL();
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	result = range_upper_internal(typcache->rngtype, ranges[range_count - 1], &isnull);
+
+	if (isnull)
+		PG_RETURN_NULL();
+	else
+		PG_RETURN_DATUM(result);
+}
+
+
+/* multirange -> bool functions */
+
+/* is multirange empty? */
+Datum
+multirange_empty(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+
+	PG_RETURN_BOOL(mr->rangeCount == 0);
+}
+
+/* is lower bound inclusive? */
+Datum
+multirange_lower_inc(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	int32		range_count;
+	RangeType **ranges;
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_has_flag(ranges[0], RANGE_LB_INC));
+}
+
+/* is upper bound inclusive? */
+Datum
+multirange_upper_inc(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	int32		range_count;
+	RangeType **ranges;
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_has_flag(ranges[range_count - 1], RANGE_UB_INC));
+}
+
+/* is lower bound infinite? */
+Datum
+multirange_lower_inf(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	int32		range_count;
+	RangeType **ranges;
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_has_flag(ranges[0], RANGE_LB_INF));
+}
+
+/* is upper bound infinite? */
+Datum
+multirange_upper_inf(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	int32		range_count;
+	RangeType **ranges;
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_has_flag(ranges[range_count - 1], RANGE_UB_INF));
+}
+
+
+
+/* multirange, element -> bool functions */
+
+/* contains? */
+Datum
+multirange_contains_elem(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	Datum		val = PG_GETARG_DATUM(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(multirange_contains_elem_internal(typcache, mr, val));
+}
+
+/* contained by? */
+Datum
+elem_contained_by_multirange(PG_FUNCTION_ARGS)
+{
+	Datum		val = PG_GETARG_DATUM(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(multirange_contains_elem_internal(typcache, mr, val));
+}
+
+/*
+ * Test whether multirange mr contains a specific element value.
+ */
+bool
+multirange_contains_elem_internal(TypeCacheEntry *typcache, MultirangeType *mr, Datum val)
+{
+	TypeCacheEntry *rangetyp;
+	int32		range_count;
+	RangeType **ranges;
+	RangeType  *r;
+	int			i;
+
+	rangetyp = typcache->rngtype;
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	for (i = 0; i < range_count; i++)
+	{
+		r = ranges[i];
+		if (range_contains_elem_internal(rangetyp, r, val))
+			return true;
+	}
+
+	return false;
+}
+
+/* multirange, range -> bool functions */
+
+/* contains? */
+Datum
+multirange_contains_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(multirange_contains_range_internal(typcache, mr, r));
+}
+
+/* contained by? */
+Datum
+range_contained_by_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(multirange_contains_range_internal(typcache, mr, r));
+}
+
+/*
+ * Test whether multirange mr contains a specific range r.
+ */
+bool
+multirange_contains_range_internal(TypeCacheEntry *typcache, MultirangeType *mr, RangeType *r)
+{
+	TypeCacheEntry *rangetyp;
+	int32		range_count;
+	RangeType **ranges;
+	RangeType  *mrr;
+	int			i;
+
+	rangetyp = typcache->rngtype;
+
+	/*
+	 * Every multirange contains an infinite number of empty ranges, even an
+	 * empty one.
+	 */
+	if (RangeIsEmpty(r))
+		return true;
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	for (i = 0; i < range_count; i++)
+	{
+		mrr = ranges[i];
+		if (range_contains_internal(rangetyp, mrr, r))
+			return true;
+	}
+
+	return false;
+}
+
+
+/* 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));
+}
+
+/* overlaps? */
+Datum
+range_overlaps_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(range_overlaps_multirange_internal(typcache, r, mr));
+}
+
+Datum
+multirange_overlaps_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(range_overlaps_multirange_internal(typcache, r, mr));
+}
+
+Datum
+multirange_overlaps_multirange(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_overlaps_multirange_internal(typcache, mr1, mr2));
+}
+
+bool
+range_overlaps_multirange_internal(TypeCacheEntry *typcache, RangeType *r, MultirangeType *mr)
+{
+	TypeCacheEntry *rangetyp;
+	int32		range_count;
+	int32		i;
+	RangeType **ranges;
+	RangeType  *mrr;
+
+	/*
+	 * Empties never overlap, even with empties. (This seems strange since
+	 * they *do* contain each other, but we want to follow how ranges work.)
+	 */
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		return false;
+
+	rangetyp = typcache->rngtype;
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	/* Scan through mrr and once it gets to r they either overlap or not */
+	mrr = ranges[0];
+
+	/* Discard mrrs while mrr << r */
+	i = 0;
+	while (range_before_internal(rangetyp, mrr, r))
+	{
+		if (++i >= range_count)
+			return false;
+		mrr = ranges[i];
+	}
+
+	/* Now either we overlap or we passed r */
+	return range_overlaps_internal(rangetyp, mrr, r);
+}
+
+bool
+multirange_overlaps_multirange_internal(TypeCacheEntry *typcache, MultirangeType *mr1,
+										MultirangeType *mr2)
+{
+	TypeCacheEntry *rangetyp;
+	int32		range_count1;
+	int32		range_count2;
+	int32		i1;
+	int32		i2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+	RangeType  *r1;
+	RangeType  *r2;
+
+	/*
+	 * Empties never overlap, even with empties. (This seems strange since
+	 * they *do* contain each other, but we want to follow how ranges work.)
+	 */
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		return false;
+
+	rangetyp = typcache->rngtype;
+
+	multirange_deserialize(mr1, &range_count1, &ranges1);
+	multirange_deserialize(mr2, &range_count2, &ranges2);
+
+	/*
+	 * Every range in mr1 gets a chance to overlap with the ranges in mr2, but
+	 * we can use their ordering to avoid O(n^2). This is similar to
+	 * range_overlaps_multirange where r1 : r2 :: mrr : r, but there if we
+	 * don't find an overlap with r we're done, and here if we don't find an
+	 * overlap with r2 we try the next r2.
+	 */
+	r1 = ranges1[0];
+	for (i1 = 0, i2 = 0; i2 < range_count2; i2++)
+	{
+		r2 = ranges2[i2];
+
+		/* Discard r1s while r1 << r2 */
+		while (range_before_internal(rangetyp, r1, r2))
+		{
+			if (++i1 >= range_count1)
+				return false;
+			r1 = ranges1[i1];
+		}
+
+		/*
+		 * If r1 && r2, we're done, otherwise we failed to find an overlap for
+		 * r2, so go to the next one.
+		 */
+		if (range_overlaps_internal(rangetyp, r1, r2))
+			return true;
+	}
+
+	/* We looked through all of mr2 without finding an overlap */
+	return false;
+}
+
+/* does not extend to right of? */
+Datum
+range_overleft_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_overleft_internal(typcache->rngtype, r, ranges[range_count - 1]));
+}
+
+Datum
+multirange_overleft_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_overleft_internal(typcache->rngtype,
+										   ranges[range_count - 1], r));
+}
+
+Datum
+multirange_overleft_multirange(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
+	MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr1));
+
+	multirange_deserialize(mr1, &range_count1, &ranges1);
+	multirange_deserialize(mr2, &range_count2, &ranges2);
+
+	PG_RETURN_BOOL(range_overleft_internal(typcache->rngtype,
+										   ranges1[range_count1 - 1],
+										   ranges2[range_count2 - 1]));
+}
+
+/* does not extend to left of? */
+Datum
+range_overright_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_overright_internal(typcache->rngtype, r, ranges[0]));
+}
+
+Datum
+multirange_overright_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_overright_internal(typcache->rngtype, ranges[0], r));
+}
+
+Datum
+multirange_overright_multirange(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
+	MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr1));
+
+	multirange_deserialize(mr1, &range_count1, &ranges1);
+	multirange_deserialize(mr2, &range_count2, &ranges2);
+
+	PG_RETURN_BOOL(range_overright_internal(typcache->rngtype,
+											ranges1[0],
+											ranges2[0]));
+}
+
+/* contains? */
+Datum
+multirange_contains_multirange(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_contains_multirange_internal(typcache, mr1, mr2));
+}
+
+/* contained by? */
+Datum
+multirange_contained_by_multirange(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_contains_multirange_internal(typcache, mr2, mr1));
+}
+
+/*
+ * Test whether multirange mr1 contains every range from another multirange mr2.
+ */
+bool
+multirange_contains_multirange_internal(TypeCacheEntry *typcache,
+										MultirangeType *mr1, MultirangeType *mr2)
+{
+	TypeCacheEntry *rangetyp;
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+	RangeType  *r1;
+	RangeType  *r2;
+	int			i1,
+				i2;
+
+	rangetyp = typcache->rngtype;
+
+	multirange_deserialize(mr1, &range_count1, &ranges1);
+	multirange_deserialize(mr2, &range_count2, &ranges2);
+
+	/*
+	 * We follow the same logic for empties as ranges: - an empty multirange
+	 * contains an empty range/multirange. - an empty multirange can't contain
+	 * any other range/multirange. - an empty multirange is contained by any
+	 * other range/multirange.
+	 */
+
+	if (range_count2 == 0)
+		return true;
+	if (range_count1 == 0)
+		return false;
+
+	/*
+	 * Every range in mr2 must be contained by some range in mr1. To avoid
+	 * O(n^2) we walk through both ranges in tandem.
+	 */
+	r1 = ranges1[0];
+	for (i1 = 0, i2 = 0; i2 < range_count2; i2++)
+	{
+		r2 = ranges2[i2];
+
+		/* Discard r1s while r1 << r2 */
+		while (range_before_internal(rangetyp, r1, r2))
+		{
+			if (++i1 >= range_count1)
+				return false;
+			r1 = ranges1[i1];
+		}
+
+		/*
+		 * If r1 @> r2, go to the next r2, otherwise return false (since every
+		 * r1[n] and r1[n+1] must have a gap). Note this will give weird
+		 * answers if you don't canonicalize, e.g. with a custom
+		 * int2multirange {[1,1], [2,2]} there is a "gap". But that is
+		 * consistent with other range operators, e.g. '[1,1]'::int2range -|-
+		 * '[2,2]'::int2range is false.
+		 */
+		if (!range_contains_internal(rangetyp, r1, r2))
+			return false;
+	}
+
+	/* All ranges in mr2 are satisfied */
+	return true;
+}
+
+/* strictly left of? */
+Datum
+range_before_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(range_before_multirange_internal(typcache, r, mr));
+}
+
+Datum
+multirange_before_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(range_after_multirange_internal(typcache, r, mr));
+}
+
+Datum
+multirange_before_multirange(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_before_multirange_internal(typcache, mr1, mr2));
+}
+
+/* strictly right of? */
+Datum
+range_after_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(range_after_multirange_internal(typcache, r, mr));
+}
+
+Datum
+multirange_after_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(range_before_multirange_internal(typcache, r, mr));
+}
+
+Datum
+multirange_after_multirange(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_before_multirange_internal(typcache, mr2, mr1));
+}
+
+/* strictly left of? (internal version) */
+bool
+range_before_multirange_internal(TypeCacheEntry *typcache, RangeType *r,
+								 MultirangeType *mr)
+{
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		return false;
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	return range_before_internal(typcache->rngtype, r, ranges[0]);
+}
+
+bool
+multirange_before_multirange_internal(TypeCacheEntry *typcache, MultirangeType *mr1,
+									  MultirangeType *mr2)
+{
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		return false;
+
+	multirange_deserialize(mr1, &range_count1, &ranges1);
+	multirange_deserialize(mr2, &range_count2, &ranges2);
+
+	return range_before_internal(typcache->rngtype, ranges1[range_count1 - 1],
+								 ranges2[0]);
+}
+
+/* strictly right of? (internal version) */
+bool
+range_after_multirange_internal(TypeCacheEntry *typcache, RangeType *r,
+								MultirangeType *mr)
+{
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		return false;
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	return range_after_internal(typcache->rngtype, r, ranges[range_count - 1]);
+}
+
+/* adjacent to? */
+Datum
+range_adjacent_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		return false;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_adjacent_internal(typcache->rngtype, r, ranges[0]));
+}
+
+Datum
+multirange_adjacent_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		return false;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_adjacent_internal(typcache->rngtype, ranges[range_count - 1], r));
+}
+
+Datum
+multirange_adjacent_multirange(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
+	MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		return false;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr1));
+
+	multirange_deserialize(mr1, &range_count1, &ranges1);
+	multirange_deserialize(mr2, &range_count2, &ranges2);
+
+	PG_RETURN_BOOL(range_adjacent_internal(typcache->rngtype, ranges1[range_count1 - 1], ranges2[0]));
+}
+
+/* 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);
+}
+
+/* multirange -> range functions */
+
+/* Find the smallest range that includes everything in the multirange */
+Datum
+range_merge_from_multirange(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	Oid			mltrngtypoid = MultirangeTypeGetOid(mr);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_RANGE_P(make_empty_range(typcache->rngtype));
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_RANGE_P(range_union_internal(typcache->rngtype, ranges[0],
+										   ranges[range_count - 1], false));
+}
+
+/* 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/pg_upgrade_support.c b/src/backend/utils/adt/pg_upgrade_support.c
index 18f2ee8226..248ea7f44a 100644
--- a/src/backend/utils/adt/pg_upgrade_support.c
+++ b/src/backend/utils/adt/pg_upgrade_support.c
@@ -52,6 +52,28 @@ binary_upgrade_set_next_array_pg_type_oid(PG_FUNCTION_ARGS)
 }
 
 Datum
+binary_upgrade_set_next_multirange_pg_type_oid(PG_FUNCTION_ARGS)
+{
+	Oid			typoid = PG_GETARG_OID(0);
+
+	CHECK_IS_BINARY_UPGRADE;
+	binary_upgrade_next_mrng_pg_type_oid = typoid;
+
+	PG_RETURN_VOID();
+}
+
+Datum
+binary_upgrade_set_next_multirange_array_pg_type_oid(PG_FUNCTION_ARGS)
+{
+	Oid			typoid = PG_GETARG_OID(0);
+
+	CHECK_IS_BINARY_UPGRADE;
+	binary_upgrade_next_mrng_array_pg_type_oid = typoid;
+
+	PG_RETURN_VOID();
+}
+
+Datum
 binary_upgrade_set_next_toast_pg_type_oid(PG_FUNCTION_ARGS)
 {
 	Oid			typoid = PG_GETARG_OID(0);
diff --git a/src/backend/utils/adt/pseudotypes.c b/src/backend/utils/adt/pseudotypes.c
index 9eee03c143..731efb8f98 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"
 
 
 /*
@@ -195,6 +196,54 @@ anyrange_out(PG_FUNCTION_ARGS)
 }
 
 /*
+ * 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);
+}
+
+/*
+ * anyrangearray_in		- input routine for pseudo-type ANYRANGEARRAY.
+ */
+Datum
+anyrangearray_in(PG_FUNCTION_ARGS)
+{
+	ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			 errmsg("cannot accept a value of type %s", "anyrangearray")));
+
+	PG_RETURN_VOID();			/* keep compiler quiet */
+}
+
+/*
+ * anyrangearray_out		- output routine for pseudo-type ANYRANGEARRAY.
+ *
+ * We may as well allow this, since array_out will in fact work.
+ */
+Datum
+anyrangearray_out(PG_FUNCTION_ARGS)
+{
+	return array_out(fcinfo);
+}
+
+/*
  * void
  *
  * We support void_in so that PL functions can return VOID without any
diff --git a/src/backend/utils/adt/rangetypes.c b/src/backend/utils/adt/rangetypes.c
index 01ad8bc240..0af3c67296 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
 {
@@ -431,19 +429,37 @@ range_lower(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
 	TypeCacheEntry *typcache;
+	bool		isnull;
+	Datum		result;
+
+	typcache = range_get_typcache(fcinfo, RangeTypeGetOid(r1));
+
+	result = range_lower_internal(typcache, r1, &isnull);
+
+	if (isnull)
+		PG_RETURN_NULL();
+	else
+		PG_RETURN_DATUM(result);
+}
+
+Datum
+range_lower_internal(TypeCacheEntry *typcache, const RangeType *r1, bool *isnull)
+{
 	RangeBound	lower;
 	RangeBound	upper;
 	bool		empty;
 
-	typcache = range_get_typcache(fcinfo, RangeTypeGetOid(r1));
-
 	range_deserialize(typcache, r1, &lower, &upper, &empty);
 
 	/* Return NULL if there's no finite lower bound */
 	if (empty || lower.infinite)
-		PG_RETURN_NULL();
+	{
+		*isnull = true;
+		return 0;
+	}
 
-	PG_RETURN_DATUM(lower.val);
+	*isnull = false;
+	return lower.val;
 }
 
 /* extract upper bound value */
@@ -452,19 +468,37 @@ range_upper(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
 	TypeCacheEntry *typcache;
+	bool		isnull;
+	Datum		result;
+
+	typcache = range_get_typcache(fcinfo, RangeTypeGetOid(r1));
+
+	result = range_upper_internal(typcache, r1, &isnull);
+
+	if (isnull)
+		PG_RETURN_NULL();
+	else
+		PG_RETURN_DATUM(result);
+}
+
+Datum
+range_upper_internal(TypeCacheEntry *typcache, const RangeType *r1, bool *isnull)
+{
 	RangeBound	lower;
 	RangeBound	upper;
 	bool		empty;
 
-	typcache = range_get_typcache(fcinfo, RangeTypeGetOid(r1));
-
 	range_deserialize(typcache, r1, &lower, &upper, &empty);
 
 	/* Return NULL if there's no finite upper bound */
 	if (empty || upper.infinite)
-		PG_RETURN_NULL();
+	{
+		*isnull = true;
+		return 0;
+	}
 
-	PG_RETURN_DATUM(upper.val);
+	*isnull = false;
+	return upper.val;
 }
 
 
@@ -475,9 +509,8 @@ Datum
 range_empty(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
-	char		flags = range_get_flags(r1);
 
-	PG_RETURN_BOOL(flags & RANGE_EMPTY);
+	PG_RETURN_BOOL(range_has_flag(r1, RANGE_EMPTY));
 }
 
 /* is lower bound inclusive? */
@@ -485,9 +518,8 @@ Datum
 range_lower_inc(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
-	char		flags = range_get_flags(r1);
 
-	PG_RETURN_BOOL(flags & RANGE_LB_INC);
+	PG_RETURN_BOOL(range_has_flag(r1, RANGE_LB_INC));
 }
 
 /* is upper bound inclusive? */
@@ -495,9 +527,8 @@ Datum
 range_upper_inc(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
-	char		flags = range_get_flags(r1);
 
-	PG_RETURN_BOOL(flags & RANGE_UB_INC);
+	PG_RETURN_BOOL(range_has_flag(r1, RANGE_UB_INC));
 }
 
 /* is lower bound infinite? */
@@ -505,9 +536,8 @@ Datum
 range_lower_inf(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
-	char		flags = range_get_flags(r1);
 
-	PG_RETURN_BOOL(flags & RANGE_LB_INF);
+	PG_RETURN_BOOL(range_has_flag(r1, RANGE_LB_INF));
 }
 
 /* is upper bound infinite? */
@@ -515,9 +545,8 @@ Datum
 range_upper_inf(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
-	char		flags = range_get_flags(r1);
 
-	PG_RETURN_BOOL(flags & RANGE_UB_INF);
+	PG_RETURN_BOOL(range_has_flag(r1, RANGE_UB_INF));
 }
 
 
@@ -957,7 +986,25 @@ range_minus(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
 	RangeType  *r2 = PG_GETARG_RANGE_P(1);
+	RangeType  *ret;
 	TypeCacheEntry *typcache;
+
+	/* 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));
+
+	ret = range_minus_internal(typcache, r1, r2);
+	if (ret)
+		PG_RETURN_RANGE_P(ret);
+	else
+		PG_RETURN_NULL();
+}
+
+RangeType *
+range_minus_internal(TypeCacheEntry *typcache, RangeType *r1, RangeType *r2)
+{
 	RangeBound	lower1,
 				lower2;
 	RangeBound	upper1,
@@ -969,18 +1016,12 @@ range_minus(PG_FUNCTION_ARGS)
 				cmp_u1l2,
 				cmp_u1u2;
 
-	/* 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));
-
 	range_deserialize(typcache, r1, &lower1, &upper1, &empty1);
 	range_deserialize(typcache, r2, &lower2, &upper2, &empty2);
 
 	/* if either is empty, r1 is the correct answer */
 	if (empty1 || empty2)
-		PG_RETURN_RANGE_P(r1);
+		return r1;
 
 	cmp_l1l2 = range_cmp_bounds(typcache, &lower1, &lower2);
 	cmp_l1u2 = range_cmp_bounds(typcache, &lower1, &upper2);
@@ -993,34 +1034,34 @@ range_minus(PG_FUNCTION_ARGS)
 				 errmsg("result of range difference would not be contiguous")));
 
 	if (cmp_l1u2 > 0 || cmp_u1l2 < 0)
-		PG_RETURN_RANGE_P(r1);
+		return r1;
 
 	if (cmp_l1l2 >= 0 && cmp_u1u2 <= 0)
-		PG_RETURN_RANGE_P(make_empty_range(typcache));
+		return make_empty_range(typcache);
 
 	if (cmp_l1l2 <= 0 && cmp_u1l2 >= 0 && cmp_u1u2 <= 0)
 	{
 		lower2.inclusive = !lower2.inclusive;
 		lower2.lower = false;	/* it will become the upper bound */
-		PG_RETURN_RANGE_P(make_range(typcache, &lower1, &lower2, false));
+		return make_range(typcache, &lower1, &lower2, false);
 	}
 
 	if (cmp_l1l2 >= 0 && cmp_u1u2 >= 0 && cmp_l1u2 <= 0)
 	{
 		upper2.inclusive = !upper2.inclusive;
 		upper2.lower = true;	/* it will become the lower bound */
-		PG_RETURN_RANGE_P(make_range(typcache, &upper2, &upper1, false));
+		return make_range(typcache, &upper2, &upper1, false);
 	}
 
 	elog(ERROR, "unexpected case in range_minus");
-	PG_RETURN_NULL();
+	return NULL;
 }
 
 /*
  * Set union.  If strict is true, it is an error that the two input ranges
  * are not adjacent or overlapping.
  */
-static RangeType *
+RangeType *
 range_union_internal(TypeCacheEntry *typcache, RangeType *r1, RangeType *r2,
 					 bool strict)
 {
@@ -1101,6 +1142,19 @@ range_intersect(PG_FUNCTION_ARGS)
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
 	RangeType  *r2 = PG_GETARG_RANGE_P(1);
 	TypeCacheEntry *typcache;
+
+	/* 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));
+
+	PG_RETURN_RANGE_P(range_intersect_internal(typcache, r1, r2));
+}
+
+RangeType *
+range_intersect_internal(TypeCacheEntry *typcache, const RangeType *r1, const RangeType *r2)
+{
 	RangeBound	lower1,
 				lower2;
 	RangeBound	upper1,
@@ -1110,17 +1164,11 @@ range_intersect(PG_FUNCTION_ARGS)
 	RangeBound *result_lower;
 	RangeBound *result_upper;
 
-	/* 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));
-
 	range_deserialize(typcache, r1, &lower1, &upper1, &empty1);
 	range_deserialize(typcache, r2, &lower2, &upper2, &empty2);
 
-	if (empty1 || empty2 || !DatumGetBool(range_overlaps(fcinfo)))
-		PG_RETURN_RANGE_P(make_empty_range(typcache));
+	if (empty1 || empty2 || !range_overlaps_internal(typcache, r1, r2))
+		return make_empty_range(typcache);
 
 	if (range_cmp_bounds(typcache, &lower1, &lower2) >= 0)
 		result_lower = &lower1;
@@ -1132,9 +1180,81 @@ range_intersect(PG_FUNCTION_ARGS)
 	else
 		result_upper = &upper2;
 
-	PG_RETURN_RANGE_P(make_range(typcache, result_lower, result_upper, false));
+	return make_range(typcache, result_lower, result_upper, false);
+}
+
+/* range, range -> range, range functions */
+
+/*
+ * range_split_internal - if r2 intersects the middle of r1, leaving non-empty
+ * ranges on both sides, then return true and set output1 and output2 to the
+ * results of r1 - r2 (in order). Otherwise return false and don't set output1
+ * or output2. Neither input range should be empty.
+ */
+bool
+range_split_internal(TypeCacheEntry *typcache, const RangeType *r1, const RangeType *r2,
+					 RangeType **output1, RangeType **output2)
+{
+	RangeBound	lower1,
+				lower2;
+	RangeBound	upper1,
+				upper2;
+	bool		empty1,
+				empty2;
+
+	range_deserialize(typcache, r1, &lower1, &upper1, &empty1);
+	range_deserialize(typcache, r2, &lower2, &upper2, &empty2);
+
+	if (range_cmp_bounds(typcache, &lower1, &lower2) < 0 &&
+		range_cmp_bounds(typcache, &upper1, &upper2) > 0)
+	{
+		/*
+		 * Need to invert inclusive/exclusive for the lower2 and upper2
+		 * points. They can't be infinite though. We're allowed to overwrite
+		 * these RangeBounds since they only exist locally.
+		 */
+		lower2.inclusive = !lower2.inclusive;
+		lower2.lower = false;
+		upper2.inclusive = !upper2.inclusive;
+		upper2.lower = true;
+
+		*output1 = make_range(typcache, &lower1, &lower2, false);
+		*output2 = make_range(typcache, &upper2, &upper1, false);
+		return true;
+	}
+
+	return false;
 }
 
+/* range -> range aggregate functions */
+
+Datum
+range_intersect_agg_transfn(PG_FUNCTION_ARGS)
+{
+	MemoryContext aggContext;
+	Oid			rngtypoid;
+	TypeCacheEntry *typcache;
+	RangeType  *result;
+	RangeType  *current;
+
+	if (!AggCheckCallContext(fcinfo, &aggContext))
+		elog(ERROR, "range_intersect_agg_transfn called in non-aggregate context");
+
+	rngtypoid = get_fn_expr_argtype(fcinfo->flinfo, 1);
+	if (!type_is_range(rngtypoid))
+		ereport(ERROR, (errmsg("range_intersect_agg must be called with a range")));
+
+	typcache = range_get_typcache(fcinfo, rngtypoid);
+
+	/* strictness ensures these are non-null */
+	result = PG_GETARG_RANGE_P(0);
+	current = PG_GETARG_RANGE_P(1);
+
+	result = range_intersect_internal(typcache, result, current);
+	PG_RETURN_RANGE_P(result);
+}
+
+
 /* Btree support */
 
 /* btree comparator */
@@ -1144,12 +1264,6 @@ range_cmp(PG_FUNCTION_ARGS)
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
 	RangeType  *r2 = PG_GETARG_RANGE_P(1);
 	TypeCacheEntry *typcache;
-	RangeBound	lower1,
-				lower2;
-	RangeBound	upper1,
-				upper2;
-	bool		empty1,
-				empty2;
 	int			cmp;
 
 	check_stack_depth();		/* recurses when subtype is a range type */
@@ -1160,6 +1274,28 @@ range_cmp(PG_FUNCTION_ARGS)
 
 	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, const RangeType *r1, const 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);
 
@@ -1177,10 +1313,7 @@ range_cmp(PG_FUNCTION_ARGS)
 			cmp = range_cmp_bounds(typcache, &upper1, &upper2);
 	}
 
-	PG_FREE_IF_COPY(r1, 0);
-	PG_FREE_IF_COPY(r2, 1);
-
-	PG_RETURN_INT32(cmp);
+	return cmp;
 }
 
 /* inequality operators using the range_cmp function */
@@ -1218,13 +1351,10 @@ range_gt(PG_FUNCTION_ARGS)
 
 /* Hash support */
 
-/* hash a range value */
-Datum
-hash_range(PG_FUNCTION_ARGS)
+uint32
+hash_range_internal(TypeCacheEntry *typcache, const RangeType *r)
 {
-	RangeType  *r = PG_GETARG_RANGE_P(0);
 	uint32		result;
-	TypeCacheEntry *typcache;
 	TypeCacheEntry *scache;
 	RangeBound	lower;
 	RangeBound	upper;
@@ -1233,10 +1363,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 +1404,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, const RangeType *r, Datum seed)
+{
+	uint64		result;
 	TypeCacheEntry *scache;
 	RangeBound	lower;
 	RangeBound	upper;
@@ -1300,10 +1433,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 +1471,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));
 }
 
 /*
@@ -1770,6 +1917,21 @@ range_get_flags(const RangeType *range)
 }
 
 /*
+ * range_has_flag: set whether a range has a specific flag.
+ *
+ * This lets expose some of our functions that just check flags
+ * to the rest of the code base (like to multiranges)
+ * without writing full-fledged *_internal versions.
+ */
+bool
+range_has_flag(const RangeType *r1, char flag)
+{
+	char		flags = range_get_flags(r1);
+
+	return flags & flag;
+}
+
+/*
  * range_set_contain_empty: set the RANGE_CONTAIN_EMPTY bit in the value.
  *
  * This is only needed in GiST operations, so we don't include a provision
@@ -1938,6 +2100,46 @@ range_cmp_bound_values(TypeCacheEntry *typcache, const RangeBound *b1,
 }
 
 /*
+ * qsort callback for sorting ranges.
+ *
+ * Two empty ranges compare equal; an empty range sorts to the left of any
+ * non-empty range.  Two non-empty ranges are sorted by lower bound first
+ * and by upper bound next.
+ */
+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.
  */
 RangeType *
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index 27bbb58f56..c6c079c623 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -2495,6 +2495,16 @@ type_is_range(Oid typid)
 }
 
 /*
+ * 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
  *
  *		Given the type OID, fetch its category and preferred-type status.
@@ -3149,7 +3159,7 @@ get_namespace_name_or_temp(Oid nspid)
 		return get_namespace_name(nspid);
 }
 
-/*				---------- PG_RANGE CACHE ----------				 */
+/*				---------- PG_RANGE CACHES ----------				 */
 
 /*
  * get_range_subtype
@@ -3202,6 +3212,56 @@ get_range_collation(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->rngmultitypid;
+		ReleaseSysCache(tp);
+		return result;
+	}
+	else
+		return InvalidOid;
+}
+
+/*
+ * get_range_multirange_subtype
+ *		Returns the subtype of a given multirange type
+ *
+ * Returns InvalidOid if the type is not a multirange type.
+ */
+Oid
+get_range_multirange_subtype(Oid multirangeOid)
+{
+	HeapTuple	tp;
+
+	tp = SearchSysCache1(RANGEMULTIRANGE, 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 53d9ddf159..7654331a59 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -651,6 +651,18 @@ static const struct cachedesc cacheinfo[] = {
 		},
 		64
 	},
+	{RangeRelationId,			/* RANGEMULTIRANGE */
+		RangeMultirangeTypidIndexId,
+		1,
+		{
+			Anum_pg_range_rngmultitypid,
+			0,
+			0,
+			0
+		},
+		4
+	},
+
 	{RangeRelationId,			/* RANGETYPE */
 		RangeTypidIndexId,
 		1,
diff --git a/src/backend/utils/cache/typcache.c b/src/backend/utils/cache/typcache.c
index 854f133f9b..160846cf10 100644
--- a/src/backend/utils/cache/typcache.c
+++ b/src/backend/utils/cache/typcache.c
@@ -286,6 +286,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);
@@ -302,6 +303,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 TypeCacheTypCallback(Datum arg, int cacheid, uint32 hashvalue);
 static void TypeCacheOpcCallback(Datum arg, int cacheid, uint32 hashvalue);
@@ -553,8 +557,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))
@@ -591,7 +595,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))
@@ -616,7 +620,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))
@@ -641,7 +645,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))
@@ -693,6 +697,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;
@@ -737,6 +748,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;
@@ -817,6 +835,16 @@ lookup_type_cache(Oid type_id, int flags)
 	}
 
 	/*
+	 * 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
 	 */
 	if ((flags & TYPECACHE_DOMAIN_BASE_INFO) &&
@@ -927,6 +955,22 @@ load_rangetype_info(TypeCacheEntry *typentry)
 	typentry->rngelemtype = lookup_type_cache(subtypeOid, 0);
 }
 
+/*
+ * load_multirangetype_info --- helper routine to set up multirange type
+ * information
+ */
+static void
+load_multirangetype_info(TypeCacheEntry *typentry)
+{
+	Oid			rangetypeOid;
+
+	rangetypeOid = get_range_multirange_subtype(typentry->type_id);
+	if (!OidIsValid(rangetypeOid))
+		elog(ERROR, "cache lookup failed for multirange type %u",
+			 typentry->type_id);
+
+	typentry->rngtype = lookup_type_cache(rangetypeOid, TYPECACHE_RANGE_INFO);
+}
 
 /*
  * load_domaintype_info --- helper routine to set up domain constraint info
@@ -1527,11 +1571,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
@@ -1574,6 +1618,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 4e9d21bd54..efefc06d15 100644
--- a/src/backend/utils/fmgr/funcapi.c
+++ b/src/backend/utils/fmgr/funcapi.c
@@ -35,6 +35,7 @@ typedef struct polymorphic_actuals
 	Oid			anyelement_type;	/* anyelement mapping, if known */
 	Oid			anyarray_type;	/* anyarray mapping, if known */
 	Oid			anyrange_type;	/* anyrange mapping, if known */
+	Oid			anymultirange_type;	/* anymultirange mapping, if known */
 } polymorphic_actuals;
 
 static void shutdown_MultiFuncCall(Datum arg);
@@ -46,6 +47,7 @@ static TypeFuncClass internal_get_result_type(Oid funcid,
 static void resolve_anyelement_from_others(polymorphic_actuals *actuals);
 static void resolve_anyarray_from_others(polymorphic_actuals *actuals);
 static void resolve_anyrange_from_others(polymorphic_actuals *actuals);
+static void resolve_anymultirange_from_others(polymorphic_actuals *actuals);
 static bool resolve_polymorphic_tupdesc(TupleDesc tupdesc,
 										oidvector *declared_args,
 										Node *call_expr);
@@ -503,6 +505,31 @@ resolve_anyelement_from_others(polymorphic_actuals *actuals)
 							format_type_be(range_base_type))));
 		actuals->anyelement_type = range_typelem;
 	}
+	else if (OidIsValid(actuals->anymultirange_type))
+	{
+		/* Use the element type based on the multirange type */
+		Oid			multirange_base_type = getBaseType(actuals->anymultirange_type);
+		Oid			multirange_typelem =
+			get_range_multirange_subtype(multirange_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(multirange_base_type))));
+
+		Oid			range_base_type = getBaseType(multirange_typelem);
+		Oid			range_typelem = get_range_subtype(range_base_type);
+
+		if (!OidIsValid(range_typelem))
+			ereport(ERROR,
+					(errcode(ERRCODE_DATATYPE_MISMATCH),
+					 errmsg("argument declared %s does not contain a range type but type %s",
+							"anymultirange",
+							format_type_be(range_base_type))));
+		actuals->anyelement_type = range_typelem;
+	}
 	else
 		elog(ERROR, "could not determine polymorphic type");
 }
@@ -540,10 +567,54 @@ static void
 resolve_anyrange_from_others(polymorphic_actuals *actuals)
 {
 	/*
-	 * We can't deduce a range type from other polymorphic inputs, because
-	 * there may be multiple range types with the same subtype.
+	 * We can't deduce a range type from other polymorphic array or base types,
+	 * because there may be multiple range types with the same subtype, but we
+	 * can deduce it from a polymorphic multirange type.
+	 */
+	if (OidIsValid(actuals->anymultirange_type))
+	{
+		/* Use the element type based on the multirange type */
+		Oid			multirange_base_type = getBaseType(actuals->anymultirange_type);
+		Oid			multirange_typelem =
+			get_range_multirange_subtype(multirange_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(multirange_base_type))));
+		actuals->anyrange_type = multirange_typelem;
+	}
+	else
+		elog(ERROR, "could not determine polymorphic type");
+}
+
+/*
+ * Resolve actual type of ANYMULTIRANGE from other polymorphic inputs
+ */
+static void
+resolve_anymultirange_from_others(polymorphic_actuals *actuals)
+{
+	/*
+	 * We can't deduce a multirange type from polymorphic array or base types,
+	 * because there may be multiple range types with the same subtype, but we
+	 * can deduce it from a polymorphic range type.
 	 */
-	elog(ERROR, "could not determine polymorphic type");
+	if (OidIsValid(actuals->anyrange_type))
+	{
+		Oid			range_base_type = getBaseType(actuals->anyrange_type);
+		Oid			multirange_typeid = get_range_multirange(range_base_type);
+
+		if (!OidIsValid(multirange_typeid))
+			ereport(ERROR,
+					(errcode(ERRCODE_UNDEFINED_OBJECT),
+					 errmsg("could not find multirange type for data type %s",
+							format_type_be(actuals->anyrange_type))));
+		actuals->anymultirange_type = multirange_typeid;
+	}
+	else
+		elog(ERROR, "could not determine polymorphic type");
 }
 
 /*
@@ -564,6 +635,7 @@ 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;
 	polymorphic_actuals poly_actuals;
 	Oid			anycollation = InvalidOid;
 	int			i;
@@ -587,6 +659,10 @@ resolve_polymorphic_tupdesc(TupleDesc tupdesc, oidvector *declared_args,
 				have_polymorphic_result = true;
 				have_anyrange_result = true;
 				break;
+			case ANYMULTIRANGEOID:
+				have_polymorphic_result = true;
+				have_anymultirange_result = true;
+				break;
 			default:
 				break;
 		}
@@ -636,6 +712,15 @@ resolve_polymorphic_tupdesc(TupleDesc tupdesc, oidvector *declared_args,
 						return false;
 				}
 				break;
+			case ANYMULTIRANGEOID:
+				if (!OidIsValid(poly_actuals.anymultirange_type))
+				{
+					poly_actuals.anymultirange_type =
+						get_call_expr_argtype(call_expr, i);
+					if (!OidIsValid(poly_actuals.anymultirange_type))
+						return false;
+				}
+				break;
 			default:
 				break;
 		}
@@ -651,6 +736,9 @@ resolve_polymorphic_tupdesc(TupleDesc tupdesc, oidvector *declared_args,
 	if (have_anyrange_result && !OidIsValid(poly_actuals.anyrange_type))
 		resolve_anyrange_from_others(&poly_actuals);
 
+	if (have_anymultirange_result && !OidIsValid(poly_actuals.anymultirange_type))
+		resolve_anymultirange_from_others(&poly_actuals);
+
 	/*
 	 * Identify the collation to use for polymorphic OUT parameters. (It'll
 	 * necessarily be the same for both anyelement and anyarray.)  Note that
@@ -708,6 +796,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),
+								   poly_actuals.anymultirange_type,
+								   -1,
+								   0);
+				/* no collation should be attached to a multirange type */
+				break;
 			default:
 				break;
 		}
@@ -735,6 +831,7 @@ 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;
 	polymorphic_actuals poly_actuals;
 	int			inargno;
 	int			i;
@@ -808,6 +905,21 @@ resolve_polymorphic_argtypes(int numargs, Oid *argtypes, char *argmodes,
 					argtypes[i] = poly_actuals.anyrange_type;
 				}
 				break;
+			case ANYMULTIRANGEOID:
+				if (argmode == PROARGMODE_OUT || argmode == PROARGMODE_TABLE)
+					have_anymultirange_result = true;
+				else
+				{
+					if (!OidIsValid(poly_actuals.anymultirange_type))
+					{
+						poly_actuals.anymultirange_type =
+							get_call_expr_argtype(call_expr, inargno);
+						if (!OidIsValid(poly_actuals.anymultirange_type))
+							return false;
+					}
+					argtypes[i] = poly_actuals.anymultirange_type;
+				}
+				break;
 			default:
 				break;
 		}
@@ -829,6 +941,9 @@ resolve_polymorphic_argtypes(int numargs, Oid *argtypes, char *argmodes,
 	if (have_anyrange_result && !OidIsValid(poly_actuals.anyrange_type))
 		resolve_anyrange_from_others(&poly_actuals);
 
+	if (have_anymultirange_result && !OidIsValid(poly_actuals.anymultirange_type))
+		resolve_anymultirange_from_others(&poly_actuals);
+
 	/* And finally replace the output column types as needed */
 	for (i = 0; i < numargs; i++)
 	{
@@ -845,6 +960,9 @@ resolve_polymorphic_argtypes(int numargs, Oid *argtypes, char *argmodes,
 			case ANYRANGEOID:
 				argtypes[i] = poly_actuals.anyrange_type;
 				break;
+			case ANYMULTIRANGEOID:
+				argtypes[i] = poly_actuals.anymultirange_type;
+				break;
 			default:
 				break;
 		}
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index ced0681ec3..f8567ad9a2 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -271,7 +271,8 @@ static void dumpSearchPath(Archive *AH);
 static void binary_upgrade_set_type_oids_by_type_oid(Archive *fout,
 													 PQExpBuffer upgrade_buffer,
 													 Oid pg_type_oid,
-													 bool force_array_type);
+													 bool force_array_type,
+													 bool include_multirange_type);
 static bool binary_upgrade_set_type_oids_by_rel_oid(Archive *fout,
 													PQExpBuffer upgrade_buffer, Oid pg_rel_oid);
 static void binary_upgrade_set_pg_class_oids(Archive *fout,
@@ -1580,7 +1581,7 @@ selectDumpableType(TypeInfo *tyinfo, Archive *fout)
 	}
 
 	/* skip auto-generated array types */
-	if (tyinfo->isArray)
+	if (tyinfo->isArray || tyinfo->isMultirange)
 	{
 		tyinfo->dobj.objType = DO_DUMMY_TYPE;
 
@@ -4340,16 +4341,49 @@ append_depends_on_extension(Archive *fout,
 	}
 }
 
+static Oid
+get_next_possible_free_pg_type_oid(Archive *fout, PQExpBuffer upgrade_query)
+{
+	/*
+	 * If the old version didn't assign an array type, but the new version
+	 * does, we must select an unused type OID to assign.  This currently only
+	 * happens for domains, when upgrading pre-v11 to v11 and up.
+	 *
+	 * Note: local state here is kind of ugly, but we must have some, since we
+	 * mustn't choose the same unused OID more than once.
+	 */
+	static Oid	next_possible_free_oid = FirstNormalObjectId;
+	PGresult   *res;
+	bool		is_dup;
+
+	do
+	{
+		++next_possible_free_oid;
+		printfPQExpBuffer(upgrade_query,
+						  "SELECT EXISTS(SELECT 1 "
+						  "FROM pg_catalog.pg_type "
+						  "WHERE oid = '%u'::pg_catalog.oid);",
+						  next_possible_free_oid);
+		res = ExecuteSqlQueryForSingleRow(fout, upgrade_query->data);
+		is_dup = (PQgetvalue(res, 0, 0)[0] == 't');
+		PQclear(res);
+	} while (is_dup);
+
+	return next_possible_free_oid;
+}
 
 static void
 binary_upgrade_set_type_oids_by_type_oid(Archive *fout,
 										 PQExpBuffer upgrade_buffer,
 										 Oid pg_type_oid,
-										 bool force_array_type)
+										 bool force_array_type,
+										 bool include_multirange_type)
 {
 	PQExpBuffer upgrade_query = createPQExpBuffer();
 	PGresult   *res;
 	Oid			pg_type_array_oid;
+	Oid			pg_type_multirange_oid;
+	Oid			pg_type_multirange_array_oid;
 
 	appendPQExpBufferStr(upgrade_buffer, "\n-- For binary upgrade, must preserve pg_type oid\n");
 	appendPQExpBuffer(upgrade_buffer,
@@ -4370,33 +4404,7 @@ binary_upgrade_set_type_oids_by_type_oid(Archive *fout,
 	PQclear(res);
 
 	if (!OidIsValid(pg_type_array_oid) && force_array_type)
-	{
-		/*
-		 * If the old version didn't assign an array type, but the new version
-		 * does, we must select an unused type OID to assign.  This currently
-		 * only happens for domains, when upgrading pre-v11 to v11 and up.
-		 *
-		 * Note: local state here is kind of ugly, but we must have some,
-		 * since we mustn't choose the same unused OID more than once.
-		 */
-		static Oid	next_possible_free_oid = FirstNormalObjectId;
-		bool		is_dup;
-
-		do
-		{
-			++next_possible_free_oid;
-			printfPQExpBuffer(upgrade_query,
-							  "SELECT EXISTS(SELECT 1 "
-							  "FROM pg_catalog.pg_type "
-							  "WHERE oid = '%u'::pg_catalog.oid);",
-							  next_possible_free_oid);
-			res = ExecuteSqlQueryForSingleRow(fout, upgrade_query->data);
-			is_dup = (PQgetvalue(res, 0, 0)[0] == 't');
-			PQclear(res);
-		} while (is_dup);
-
-		pg_type_array_oid = next_possible_free_oid;
-	}
+		pg_type_array_oid = get_next_possible_free_pg_type_oid(fout, upgrade_query);
 
 	if (OidIsValid(pg_type_array_oid))
 	{
@@ -4407,6 +4415,46 @@ binary_upgrade_set_type_oids_by_type_oid(Archive *fout,
 						  pg_type_array_oid);
 	}
 
+	/*
+	 * Pre-set the multirange type oid and its own array type oid.
+	 */
+	if (include_multirange_type)
+	{
+		if (fout->remoteVersion >= 130000)
+		{
+			appendPQExpBuffer(upgrade_query,
+							  "SELECT t.oid, t.typarray "
+							  "FROM pg_catalog.pg_type t "
+							  "JOIN pg_catalog.pg_range r "
+							  "ON t.oid = r.rngmultitypid "
+							  "WHERE r.rngtypid = '%u'::pg_catalog.oid;",
+							  pg_type_oid);
+
+			res = ExecuteSqlQueryForSingleRow(fout, upgrade_query->data);
+
+			pg_type_multirange_oid = atooid(PQgetvalue(res, 0, PQfnumber(res, "oid")));
+			pg_type_multirange_array_oid = atooid(PQgetvalue(res, 0, PQfnumber(res, "typarray")));
+
+			PQclear(res);
+		}
+		else
+		{
+			pg_type_multirange_oid = get_next_possible_free_pg_type_oid(fout, upgrade_query);
+			pg_type_multirange_array_oid = get_next_possible_free_pg_type_oid(fout, upgrade_query);
+		}
+
+		appendPQExpBufferStr(upgrade_buffer,
+							 "\n-- For binary upgrade, must preserve multirange pg_type oid\n");
+		appendPQExpBuffer(upgrade_buffer,
+						  "SELECT pg_catalog.binary_upgrade_set_next_multirange_pg_type_oid('%u'::pg_catalog.oid);\n\n",
+						  pg_type_multirange_oid);
+		appendPQExpBufferStr(upgrade_buffer,
+							 "\n-- For binary upgrade, must preserve multirange pg_type array oid\n");
+		appendPQExpBuffer(upgrade_buffer,
+						  "SELECT pg_catalog.binary_upgrade_set_next_multirange_array_pg_type_oid('%u'::pg_catalog.oid);\n\n",
+						  pg_type_multirange_array_oid);
+	}
+
 	destroyPQExpBuffer(upgrade_query);
 }
 
@@ -4440,7 +4488,7 @@ binary_upgrade_set_type_oids_by_rel_oid(Archive *fout,
 	pg_type_oid = atooid(PQgetvalue(upgrade_res, 0, PQfnumber(upgrade_res, "crel")));
 
 	binary_upgrade_set_type_oids_by_type_oid(fout, upgrade_buffer,
-											 pg_type_oid, false);
+											 pg_type_oid, false, false);
 
 	if (!PQgetisnull(upgrade_res, 0, PQfnumber(upgrade_res, "trel")))
 	{
@@ -4989,6 +5037,11 @@ getTypes(Archive *fout, int *numTypes)
 		else
 			tyinfo[i].isArray = false;
 
+		if (tyinfo[i].typtype == 'm')
+			tyinfo[i].isMultirange = true;
+		else
+			tyinfo[i].isMultirange = false;
+
 		/* Decide whether we want to dump it */
 		selectDumpableType(&tyinfo[i], fout);
 
@@ -8103,9 +8156,12 @@ getProcLangs(Archive *fout, int *numProcLangs)
 
 /*
  * getCasts
- *	  get basic information about every cast in the system
+ *	  get basic information about most casts in the system
  *
  * numCasts is set to the number of casts read in
+ *
+ * Skip casts from a range to its multirange, since we'll create those
+ * automatically.
  */
 CastInfo *
 getCasts(Archive *fout, int *numCasts)
@@ -8123,7 +8179,20 @@ getCasts(Archive *fout, int *numCasts)
 	int			i_castcontext;
 	int			i_castmethod;
 
-	if (fout->remoteVersion >= 80400)
+	if (fout->remoteVersion >= 130000)
+	{
+		appendPQExpBufferStr(query, "SELECT tableoid, oid, "
+							 "castsource, casttarget, castfunc, castcontext, "
+							 "castmethod "
+							 "FROM pg_cast c "
+							 "WHERE NOT EXISTS ( "
+							 "SELECT 1 FROM pg_range r "
+							 "WHERE c.castsource = r.rngtypid "
+							 "AND c.casttarget = r.rngmultitypid "
+							 ") "
+							 "ORDER BY 3,4");
+	}
+	else if (fout->remoteVersion >= 80400)
 	{
 		appendPQExpBufferStr(query, "SELECT tableoid, oid, "
 							 "castsource, casttarget, castfunc, castcontext, "
@@ -10311,7 +10380,7 @@ dumpEnumType(Archive *fout, TypeInfo *tyinfo)
 	if (dopt->binary_upgrade)
 		binary_upgrade_set_type_oids_by_type_oid(fout, q,
 												 tyinfo->dobj.catId.oid,
-												 false);
+												 false, false);
 
 	appendPQExpBuffer(q, "CREATE TYPE %s AS ENUM (",
 					  qualtypname);
@@ -10437,7 +10506,7 @@ dumpRangeType(Archive *fout, TypeInfo *tyinfo)
 	if (dopt->binary_upgrade)
 		binary_upgrade_set_type_oids_by_type_oid(fout, q,
 												 tyinfo->dobj.catId.oid,
-												 false);
+												 false, true);
 
 	appendPQExpBuffer(q, "CREATE TYPE %s AS RANGE (",
 					  qualtypname);
@@ -10543,7 +10612,7 @@ dumpUndefinedType(Archive *fout, TypeInfo *tyinfo)
 	if (dopt->binary_upgrade)
 		binary_upgrade_set_type_oids_by_type_oid(fout, q,
 												 tyinfo->dobj.catId.oid,
-												 false);
+												 false, false);
 
 	appendPQExpBuffer(q, "CREATE TYPE %s;\n",
 					  qualtypname);
@@ -10748,7 +10817,7 @@ dumpBaseType(Archive *fout, TypeInfo *tyinfo)
 	if (dopt->binary_upgrade)
 		binary_upgrade_set_type_oids_by_type_oid(fout, q,
 												 tyinfo->dobj.catId.oid,
-												 false);
+												 false, false);
 
 	appendPQExpBuffer(q,
 					  "CREATE TYPE %s (\n"
@@ -10935,7 +11004,8 @@ dumpDomain(Archive *fout, TypeInfo *tyinfo)
 	if (dopt->binary_upgrade)
 		binary_upgrade_set_type_oids_by_type_oid(fout, q,
 												 tyinfo->dobj.catId.oid,
-												 true); /* force array type */
+												 true,		/* force array type */
+												 false);	/* force multirange type */
 
 	qtypname = pg_strdup(fmtId(tyinfo->dobj.name));
 	qualtypname = pg_strdup(fmtQualifiedDumpable(tyinfo));
@@ -11123,7 +11193,7 @@ dumpCompositeType(Archive *fout, TypeInfo *tyinfo)
 	{
 		binary_upgrade_set_type_oids_by_type_oid(fout, q,
 												 tyinfo->dobj.catId.oid,
-												 false);
+												 false, false);
 		binary_upgrade_set_pg_class_oids(fout, q, tyinfo->typrelid, false);
 	}
 
@@ -11397,7 +11467,7 @@ dumpShellType(Archive *fout, ShellTypeInfo *stinfo)
 	if (dopt->binary_upgrade)
 		binary_upgrade_set_type_oids_by_type_oid(fout, q,
 												 stinfo->baseType->dobj.catId.oid,
-												 false);
+												 false, false);
 
 	appendPQExpBuffer(q, "CREATE TYPE %s;\n",
 					  fmtQualifiedDumpable(stinfo));
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index e0c6444ef6..90a93b4cb2 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -176,6 +176,7 @@ typedef struct _typeInfo
 	char		typrelkind;		/* 'r', 'v', 'c', etc */
 	char		typtype;		/* 'b', 'c', etc */
 	bool		isArray;		/* true if auto-generated array type */
+	bool		isMultirange;	/* true if auto-generated multirange type */
 	bool		isDefined;		/* true if typisdefined */
 	/* If needed, we'll create a "shell type" entry for it; link that here: */
 	struct _shellTypeInfo *shellType;	/* shell-type entry, or NULL */
diff --git a/src/include/access/tupmacs.h b/src/include/access/tupmacs.h
index 70157cf90a..c262265b95 100644
--- a/src/include/access/tupmacs.h
+++ b/src/include/access/tupmacs.h
@@ -139,8 +139,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/binary_upgrade.h b/src/include/catalog/binary_upgrade.h
index 12d94fe1b3..01d95117cd 100644
--- a/src/include/catalog/binary_upgrade.h
+++ b/src/include/catalog/binary_upgrade.h
@@ -16,6 +16,8 @@
 
 extern PGDLLIMPORT Oid binary_upgrade_next_pg_type_oid;
 extern PGDLLIMPORT Oid binary_upgrade_next_array_pg_type_oid;
+extern PGDLLIMPORT Oid binary_upgrade_next_mrng_pg_type_oid;
+extern PGDLLIMPORT Oid binary_upgrade_next_mrng_array_pg_type_oid;
 extern PGDLLIMPORT Oid binary_upgrade_next_toast_pg_type_oid;
 
 extern PGDLLIMPORT Oid binary_upgrade_next_heap_pg_class_oid;
diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h
index 8be303870f..566f9364c5 100644
--- a/src/include/catalog/indexing.h
+++ b/src/include/catalog/indexing.h
@@ -327,6 +327,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_rngmultitypid_index, 8001, on pg_range using btree(rngmultitypid 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_aggregate.dat b/src/include/catalog/pg_aggregate.dat
index ffabe275c0..03bab74253 100644
--- a/src/include/catalog/pg_aggregate.dat
+++ b/src/include/catalog/pg_aggregate.dat
@@ -538,6 +538,17 @@
   aggtransfn => 'bytea_string_agg_transfn',
   aggfinalfn => 'bytea_string_agg_finalfn', aggtranstype => 'internal' },
 
+# range
+{ aggfnoid => 'range_intersect_agg(anyrange)',
+  aggtransfn => 'range_intersect_agg_transfn',
+  aggcombinefn => 'range_intersect_agg_transfn', aggtranstype => 'anyrange' },
+{ aggfnoid => 'range_intersect_agg(anymultirange)',
+  aggtransfn => 'multirange_intersect_agg_transfn',
+  aggcombinefn => 'multirange_intersect_agg_transfn', aggtranstype => 'anymultirange' },
+{ aggfnoid => 'range_agg(anyrange)', aggtransfn => 'range_agg_transfn',
+  aggfinalfn => 'range_agg_finalfn', aggfinalextra => 't',
+  aggtranstype => 'internal' },
+
 # json
 { aggfnoid => 'json_agg', aggtransfn => 'json_agg_transfn',
   aggfinalfn => 'json_agg_finalfn', aggtranstype => 'internal' },
diff --git a/src/include/catalog/pg_amop.dat b/src/include/catalog/pg_amop.dat
index 11aaa519c8..827ac4f03c 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 75c0152b66..cf0cac5641 100644
--- a/src/include/catalog/pg_amproc.dat
+++ b/src/include/catalog/pg_amproc.dat
@@ -285,6 +285,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' },
 
@@ -445,6 +447,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',
@@ -1263,12 +1270,13 @@
   amprocrighttype => 'anyrange', amprocnum => '4',
   amproc => 'brin_inclusion_union' },
 { amprocfamily => 'brin/range_inclusion_ops', amproclefttype => 'anyrange',
-  amprocrighttype => 'anyrange', amprocnum => '11', amproc => 'range_merge' },
+  amprocrighttype => 'anyrange', amprocnum => '11',
+  amproc => 'range_merge(anyrange,anyrange)' },
 { amprocfamily => 'brin/range_inclusion_ops', amproclefttype => 'anyrange',
   amprocrighttype => 'anyrange', amprocnum => '13',
   amproc => 'range_contains' },
 { amprocfamily => 'brin/range_inclusion_ops', amproclefttype => 'anyrange',
-  amprocrighttype => 'anyrange', amprocnum => '14', amproc => 'isempty' },
+  amprocrighttype => 'anyrange', amprocnum => '14', amproc => 'isempty(anyrange)' },
 
 # minmax pg_lsn
 { amprocfamily => 'brin/pg_lsn_minmax_ops', amproclefttype => 'pg_lsn',
diff --git a/src/include/catalog/pg_cast.dat b/src/include/catalog/pg_cast.dat
index 6ef8b8a4e7..14277c8fdf 100644
--- a/src/include/catalog/pg_cast.dat
+++ b/src/include/catalog/pg_cast.dat
@@ -512,4 +512,17 @@
 { castsource => 'jsonb', casttarget => 'float8', castfunc => 'float8(jsonb)',
   castcontext => 'e', castmethod => 'f' },
 
+# range to multirange
+{ castsource => 'int4range', casttarget => 'int4multirange', castfunc => 'int4multirange(int4range)',
+  castcontext => 'e', castmethod => 'f' },
+{ castsource => 'int8range', casttarget => 'int8multirange', castfunc => 'int8multirange(int8range)',
+  castcontext => 'e', castmethod => 'f' },
+{ castsource => 'numrange', casttarget => 'nummultirange', castfunc => 'nummultirange(numrange)',
+  castcontext => 'e', castmethod => 'f' },
+{ castsource => 'daterange', casttarget => 'datemultirange', castfunc => 'datemultirange(daterange)',
+  castcontext => 'e', castmethod => 'f' },
+{ castsource => 'tsrange', casttarget => 'tsmultirange', castfunc => 'tsmultirange(tsrange)',
+  castcontext => 'e', castmethod => 'f' },
+{ castsource => 'tstzrange', casttarget => 'tstzmultirange', castfunc => 'tstzmultirange(tstzrange)',
+  castcontext => 'e', castmethod => 'f' },
 ]
diff --git a/src/include/catalog/pg_opclass.dat b/src/include/catalog/pg_opclass.dat
index ab2f50c9eb..eebebbcdb4 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 7c135da3b1..24f4a32203 100644
--- a/src/include/catalog/pg_operator.dat
+++ b/src/include/catalog/pg_operator.dat
@@ -3297,5 +3297,174 @@
   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' },
+{ oid => '8073', oid_symbol => 'OID_RANGE_OVERLAPS_MULTIRANGE_OP',
+  descr => 'overlaps',
+  oprname => '&&', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '&&(anymultirange,anyrange)',
+  oprcode => 'range_overlaps_multirange', oprrest => 'areasel',
+  oprjoin => 'areajoinsel' },
+{ oid => '8074', oid_symbol => 'OID_MULTIRANGE_OVERLAPS_RANGE_OP', descr => 'contains',
+  oprname => '&&', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcom => '&&(anyrange,anymultirange)',
+  oprcode => 'multirange_overlaps_range', oprrest => 'areasel',
+  oprjoin => 'areajoinsel' },
+{ oid => '8075', oid_symbol => 'OID_MULTIRANGE_OVERLAPS_MULTIRANGE_OP', descr => 'contains',
+  oprname => '&&', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '&&(anymultirange,anymultirange)',
+  oprcode => 'multirange_overlaps_multirange', oprrest => 'areasel',
+  oprjoin => 'areajoinsel' },
+{ oid => '8064', oid_symbol => 'OID_MULTIRANGE_CONTAINS_ELEM_OP',
+  descr => 'contains',
+  oprname => '@>', oprleft => 'anymultirange', oprright => 'anyelement',
+  oprresult => 'bool', oprcom => '<@(anyelement,anymultirange)',
+  oprcode => 'multirange_contains_elem', oprrest => 'contsel',
+  oprjoin => 'contjoinsel' },
+{ oid => '8065', oid_symbol => 'OID_MULTIRANGE_CONTAINS_RANGE_OP', descr => 'contains',
+  oprname => '@>', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcom => '<@(anyrange,anymultirange)',
+  oprcode => 'multirange_contains_range', oprrest => 'contsel',
+  oprjoin => 'contjoinsel' },
+{ oid => '8066', oid_symbol => 'OID_MULTIRANGE_CONTAINS_MULTIRANGE_OP', descr => 'contains',
+  oprname => '@>', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '<@(anymultirange,anymultirange)',
+  oprcode => 'multirange_contains_multirange', oprrest => 'contsel',
+  oprjoin => 'contjoinsel' },
+{ oid => '8067', oid_symbol => 'OID_MULTIRANGE_ELEM_CONTAINED_OP',
+  descr => 'is contained by',
+  oprname => '<@', oprleft => 'anyelement', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '@>(anymultirange,anyelement)',
+  oprcode => 'elem_contained_by_multirange', oprrest => 'contsel',
+  oprjoin => 'contjoinsel' },
+{ oid => '8068', oid_symbol => 'OID_MULTIRANGE_RANGE_CONTAINED_OP',
+  descr => 'is contained by',
+  oprname => '<@', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '@>(anymultirange,anyrange)',
+  oprcode => 'range_contained_by_multirange', oprrest => 'contsel',
+  oprjoin => 'contjoinsel' },
+{ oid => '8069', oid_symbol => 'OID_MULTIRANGE_MULTIRANGE_CONTAINED_OP',
+  descr => 'is contained by',
+  oprname => '<@', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '@>(anymultirange,anymultirange)',
+  oprcode => 'multirange_contained_by_multirange', oprrest => 'contsel',
+  oprjoin => 'contjoinsel' },
+{ oid => '8106', oid_symbol => 'OID_RANGE_OVERLAPS_LEFT_MULTIRANGE_OP',
+  descr => 'overlaps or is left of',
+  oprname => '&<', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcode => 'range_overleft_multirange', oprrest => 'scalarltsel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8107', oid_symbol => 'OID_MULTIRANGE_OVERLAPS_LEFT_RANGE_OP',
+  descr => 'overlaps or is left of',
+  oprname => '&<', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcode => 'multirange_overleft_range', oprrest => 'scalarltsel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8108', oid_symbol => 'OID_MULTIRANGE_OVERLAPS_LEFT_MULTIRANGE_OP',
+  descr => 'overlaps or is left of',
+  oprname => '&<', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcode => 'multirange_overleft_multirange', oprrest => 'scalarltsel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8109', oid_symbol => 'OID_RANGE_OVERLAPS_RIGHT_MULTIRANGE_OP',
+  descr => 'overlaps or is right of',
+  oprname => '&>', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcode => 'range_overright_multirange', oprrest => 'scalargtsel',
+  oprjoin => 'scalargtjoinsel' },
+{ oid => '8110', oid_symbol => 'OID_MULTIRANGE_OVERLAPS_RIGHT_RANGE_OP',
+  descr => 'overlaps or is right of',
+  oprname => '&>', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcode => 'multirange_overright_range', oprrest => 'scalargtsel',
+  oprjoin => 'scalargtjoinsel' },
+{ oid => '8111', oid_symbol => 'OID_MULTIRANGE_OVERLAPS_RIGHT_MULTIRANGE_OP',
+  descr => 'overlaps or is right of',
+  oprname => '&>', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcode => 'multirange_overright_multirange', oprrest => 'scalargtsel',
+  oprjoin => 'scalargtjoinsel' },
+{ oid => '8091', oid_symbol => 'OID_RANGE_ADJACENT_MULTIRANGE_OP', descr => 'is adjacent to',
+  oprname => '-|-', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '-|-(anymultirange,anyrange)',
+  oprcode => 'range_adjacent_multirange', oprrest => 'scalarltsel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8092', oid_symbol => 'OID_MULTIRANGE_ADJACENT_RANGE_OP', descr => 'is adjacent to',
+  oprname => '-|-', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcom => '-|-(anyrange,anymultirange)',
+  oprcode => 'multirange_adjacent_range', oprrest => 'scalarltsel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8093', oid_symbol => 'OID_MULTIRANGE_ADJACENT_MULTIRANGE_OP', descr => 'is adjacent to',
+  oprname => '-|-', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '-|-(anymultirange,anymultirange)',
+  oprcode => 'multirange_adjacent_multirange', oprrest => 'scalarltsel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8117', descr => 'multirange union',
+  oprname => '+', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'anymultirange', oprcom => '+(anymultirange,anymultirange)',
+  oprcode => 'multirange_union' },
+{ oid => '8123', descr => 'multirange minus',
+  oprname => '-', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'anymultirange', oprcode => 'multirange_minus' },
+{ oid => '8129', descr => 'multirange intersect',
+  oprname => '*', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'anymultirange', oprcom => '*(anymultirange,anymultirange)',
+  oprcode => 'multirange_intersect' },
+{ oid => '8082', oid_symbol => 'OID_RANGE_BEFORE_MULTIRANGE_OP', descr => 'is left of',
+  oprname => '<<', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '>>(anymultirange,anyrange)',
+  oprcode => 'range_before_multirange', oprrest => 'scalarltsel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8083', oid_symbol => 'OID_MULTIRANGE_BEFORE_RANGE_OP', descr => 'is left of',
+  oprname => '<<', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcom => '>>(anyrange,anymultirange)',
+  oprcode => 'multirange_before_range', oprrest => 'scalarltsel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8084', oid_symbol => 'OID_MULTIRANGE_BEFORE_MULTIRANGE_OP', descr => 'is left of',
+  oprname => '<<', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '>>(anymultirange,anymultirange)',
+  oprcode => 'multirange_before_multirange', oprrest => 'scalarltsel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8085', oid_symbol => 'OID_RANGE_AFTER_MULTIRANGE_OP', descr => 'is right of',
+  oprname => '>>', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '<<(anymultirange,anyrange)',
+  oprcode => 'range_after_multirange', oprrest => 'scalargtsel',
+  oprjoin => 'scalargtjoinsel' },
+{ oid => '8086', oid_symbol => 'OID_MULTIRANGE_AFTER_RANGE_OP', descr => 'is right of',
+  oprname => '>>', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcom => '<<(anyrange,anymultirange)',
+  oprcode => 'multirange_after_range', oprrest => 'scalargtsel',
+  oprjoin => 'scalargtjoinsel' },
+{ oid => '8087', oid_symbol => 'OID_MULTIRANGE_AFTER_MULTIRANGE_OP', descr => 'is right of',
+  oprname => '>>', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '<<(anymultirange,anymultirange)',
+  oprcode => 'multirange_after_multirange', oprrest => 'scalargtsel',
+  oprjoin => 'scalargtjoinsel' },
 
 ]
diff --git a/src/include/catalog/pg_opfamily.dat b/src/include/catalog/pg_opfamily.dat
index 26227df216..901fd7b303 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 7fb574f9dc..27f25d989b 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -9643,6 +9643,10 @@
   descr => 'the smallest range which includes both of the given ranges',
   proname => 'range_merge', prorettype => 'anyrange',
   proargtypes => 'anyrange anyrange', prosrc => 'range_merge' },
+{ oid => '8143',
+  descr => 'the smallest range which includes the whole multirange',
+  proname => 'range_merge', prorettype => 'anyrange',
+  proargtypes => 'anymultirange', prosrc => 'range_merge_from_multirange' },
 { oid => '3868',
   proname => 'range_intersect', prorettype => 'anyrange',
   proargtypes => 'anyrange anyrange', prosrc => 'range_intersect' },
@@ -9692,6 +9696,13 @@
 { oid => '3169', descr => 'restriction selectivity for range operators',
   proname => 'rangesel', provolatile => 's', prorettype => 'float8',
   proargtypes => 'internal oid internal int4', prosrc => 'rangesel' },
+{ oid => '8133', descr => 'range aggregate by intersecting',
+  proname => 'range_intersect_agg_transfn', prorettype => 'anyrange',
+  proargtypes => 'anyrange anyrange', prosrc => 'range_intersect_agg_transfn'},
+{ oid => '8134', descr => 'range aggregate by intersecting',
+  proname => 'range_intersect_agg', prokind => 'a', proisstrict => 'f',
+  prorettype => 'anyrange', proargtypes => 'anyrange',
+  prosrc => 'aggregate_dummy'},
 
 { oid => '3914', descr => 'convert an int4 range to canonical form',
   proname => 'int4range_canonical', prorettype => 'int4range',
@@ -9760,6 +9771,258 @@
   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 => '8094', descr => 'lower bound of multirange',
+  proname => 'lower', prorettype => 'anyelement', proargtypes => 'anymultirange',
+  prosrc => 'multirange_lower' },
+{ oid => '8095', descr => 'upper bound of multirange',
+  proname => 'upper', prorettype => 'anyelement', proargtypes => 'anymultirange',
+  prosrc => 'multirange_upper' },
+{ oid => '8057', descr => 'is the multirange empty?',
+  proname => 'isempty', prorettype => 'bool',
+  proargtypes => 'anymultirange', prosrc => 'multirange_empty' },
+{ oid => '8096', descr => 'is the multirange\'s lower bound inclusive?',
+  proname => 'lower_inc', prorettype => 'bool', proargtypes => 'anymultirange',
+  prosrc => 'multirange_lower_inc' },
+{ oid => '8097', descr => 'is the multirange\'s upper bound inclusive?',
+  proname => 'upper_inc', prorettype => 'bool', proargtypes => 'anymultirange',
+  prosrc => 'multirange_upper_inc' },
+{ oid => '8098', descr => 'is the multirange\'s lower bound infinite?',
+  proname => 'lower_inf', prorettype => 'bool', proargtypes => 'anymultirange',
+  prosrc => 'multirange_lower_inf' },
+{ oid => '8099', descr => 'is the multirange\'s upper bound infinite?',
+  proname => 'upper_inf', prorettype => 'bool', proargtypes => 'anymultirange',
+  prosrc => 'multirange_upper_inf' },
+{ oid => '8008', descr => 'multirange typanalyze',
+  proname => 'multirange_typanalyze', provolatile => 's', prorettype => 'bool',
+  proargtypes => 'internal', prosrc => 'multirange_typanalyze' },
+{ oid => '8033',
+  proname => 'multirange_eq', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_eq' },
+{ oid => '8034',
+  proname => 'multirange_ne', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_ne' },
+{ oid => '8070',
+  proname => 'range_overlaps_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_overlaps_multirange' },
+{ oid => '8071',
+  proname => 'multirange_overlaps_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_overlaps_range' },
+{ oid => '8072',
+  proname => 'multirange_overlaps_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_overlaps_multirange' },
+{ oid => '8058',
+  proname => 'multirange_contains_elem', prorettype => 'bool',
+  proargtypes => 'anymultirange anyelement', prosrc => 'multirange_contains_elem' },
+{ oid => '8059',
+  proname => 'multirange_contains_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_contains_range' },
+{ oid => '8060',
+  proname => 'multirange_contains_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_contains_multirange' },
+{ oid => '8061',
+  proname => 'elem_contained_by_multirange', prorettype => 'bool',
+  proargtypes => 'anyelement anymultirange', prosrc => 'elem_contained_by_multirange' },
+{ oid => '8062',
+  proname => 'range_contained_by_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_contained_by_multirange' },
+{ oid => '8063',
+  proname => 'multirange_contained_by_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_contained_by_multirange' },
+{ oid => '8088',
+  proname => 'range_adjacent_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_adjacent_multirange' },
+{ oid => '8089',
+  proname => 'multirange_adjacent_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_adjacent_multirange' },
+{ oid => '8090',
+  proname => 'multirange_adjacent_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_adjacent_range' },
+{ oid => '8076',
+  proname => 'range_before_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_before_multirange' },
+{ oid => '8077',
+  proname => 'multirange_before_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_before_range' },
+{ oid => '8078',
+  proname => 'multirange_before_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_before_multirange' },
+{ oid => '8079',
+  proname => 'range_after_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_after_multirange' },
+{ oid => '8080',
+  proname => 'multirange_after_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_after_range' },
+{ oid => '8081',
+  proname => 'multirange_after_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_after_multirange' },
+{ oid => '8100',
+  proname => 'range_overleft_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_overleft_multirange' },
+{ oid => '8101',
+  proname => 'multirange_overleft_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_overleft_range' },
+{ oid => '8102',
+  proname => 'multirange_overleft_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_overleft_multirange' },
+{ oid => '8103',
+  proname => 'range_overright_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_overright_multirange' },
+{ oid => '8104',
+  proname => 'multirange_overright_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_overright_range' },
+{ oid => '8105',
+  proname => 'multirange_overright_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_overright_multirange' },
+{ oid => '8114',
+  proname => 'multirange_union', prorettype => 'anymultirange',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_union' },
+{ oid => '8120',
+  proname => 'multirange_minus', prorettype => 'anymultirange',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_minus' },
+{ oid => '8126',
+  proname => 'multirange_intersect', prorettype => 'anymultirange',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_intersect' },
+{ oid => '8022', descr => 'less-equal-greater',
+  proname => 'multirange_cmp', prorettype => 'int4',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_cmp' },
+{ oid => '8023',
+  proname => 'multirange_lt', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_lt' },
+{ oid => '8024',
+  proname => 'multirange_le', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_le' },
+{ oid => '8025',
+  proname => 'multirange_ge', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_ge' },
+{ oid => '8026',
+  proname => 'multirange_gt', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_gt' },
+{ oid => '8038', descr => 'hash a multirange',
+  proname => 'hash_multirange', prorettype => 'int4', proargtypes => 'anymultirange',
+  prosrc => 'hash_multirange' },
+{ oid => '8039', descr => 'hash a multirange',
+  proname => 'hash_multirange_extended', prorettype => 'int8',
+  proargtypes => 'anymultirange int8', prosrc => 'hash_multirange_extended' },
+
+{ oid => '8045', descr => 'int4multirange constructor',
+  proname => 'int4multirange', proisstrict => 'f',
+  prorettype => 'int4multirange', proargtypes => '',
+  prosrc => 'multirange_constructor0' },
+{ oid => '8146', descr => 'int4multirange constructor',
+  proname => 'int4multirange', proisstrict => 't',
+  prorettype => 'int4multirange', proargtypes => 'int4range',
+  prosrc => 'multirange_constructor1' },
+{ oid => '8046', descr => 'int4multirange constructor',
+  proname => 'int4multirange', provariadic => 'int4range', proisstrict => 'f',
+  prorettype => 'int4multirange', proargtypes => '_int4range',
+  proallargtypes => '{_int4range}', proargmodes => '{v}',
+  prosrc => 'multirange_constructor2' },
+{ oid => '8047', descr => 'nummultirange constructor',
+  proname => 'nummultirange', proisstrict => 'f',
+  prorettype => 'nummultirange', proargtypes => '',
+  prosrc => 'multirange_constructor0' },
+{ oid => '8147', descr => 'nummultirange constructor',
+  proname => 'nummultirange', proisstrict => 't',
+  prorettype => 'nummultirange', proargtypes => 'numrange',
+  prosrc => 'multirange_constructor1' },
+{ oid => '8048', descr => 'nummultirange constructor',
+  proname => 'nummultirange', provariadic => 'numrange', proisstrict => 'f',
+  prorettype => 'nummultirange', proargtypes => '_numrange',
+  proallargtypes => '{_numrange}', proargmodes => '{v}',
+  prosrc => 'multirange_constructor2' },
+{ oid => '8049', descr => 'tsmultirange constructor',
+  proname => 'tsmultirange', proisstrict => 'f',
+  prorettype => 'tsmultirange', proargtypes => '',
+  prosrc => 'multirange_constructor0' },
+{ oid => '8148', descr => 'tsmultirange constructor',
+  proname => 'tsmultirange', proisstrict => 't',
+  prorettype => 'tsmultirange', proargtypes => 'tsrange',
+  prosrc => 'multirange_constructor1' },
+{ oid => '8050', descr => 'tsmultirange constructor',
+  proname => 'tsmultirange', provariadic => 'tsrange', proisstrict => 'f',
+  prorettype => 'tsmultirange', proargtypes => '_tsrange',
+  proallargtypes => '{_tsrange}', proargmodes => '{v}',
+  prosrc => 'multirange_constructor2' },
+{ oid => '8051', descr => 'tstzmultirange constructor',
+  proname => 'tstzmultirange', proisstrict => 'f',
+  prorettype => 'tstzmultirange', proargtypes => '',
+  prosrc => 'multirange_constructor0' },
+{ oid => '8149', descr => 'tstzmultirange constructor',
+  proname => 'tstzmultirange', proisstrict => 't',
+  prorettype => 'tstzmultirange', proargtypes => 'tstzrange',
+  prosrc => 'multirange_constructor1' },
+{ oid => '8052', descr => 'tstzmultirange constructor',
+  proname => 'tstzmultirange', provariadic => 'tstzrange', proisstrict => 'f',
+  prorettype => 'tstzmultirange', proargtypes => '_tstzrange',
+  proallargtypes => '{_tstzrange}', proargmodes => '{v}',
+  prosrc => 'multirange_constructor2' },
+{ oid => '8053', descr => 'datemultirange constructor',
+  proname => 'datemultirange', proisstrict => 'f',
+  prorettype => 'datemultirange', proargtypes => '',
+  prosrc => 'multirange_constructor0' },
+{ oid => '8150', descr => 'datemultirange constructor',
+  proname => 'datemultirange', proisstrict => 't',
+  prorettype => 'datemultirange', proargtypes => 'daterange',
+  prosrc => 'multirange_constructor1' },
+{ oid => '8054', descr => 'datemultirange constructor',
+  proname => 'datemultirange', provariadic => 'daterange', proisstrict => 'f',
+  prorettype => 'datemultirange', proargtypes => '_daterange',
+  proallargtypes => '{_daterange}', proargmodes => '{v}',
+  prosrc => 'multirange_constructor2' },
+{ oid => '8055', descr => 'int8multirange constructor',
+  proname => 'int8multirange', proisstrict => 'f',
+  prorettype => 'int8multirange', proargtypes => '',
+  prosrc => 'multirange_constructor0' },
+{ oid => '8151', descr => 'int8multirange constructor',
+  proname => 'int8multirange', proisstrict => 't',
+  prorettype => 'int8multirange', proargtypes => 'int8range',
+  prosrc => 'multirange_constructor1' },
+{ oid => '8056', descr => 'int8multirange constructor',
+  proname => 'int8multirange', provariadic => 'int8range', proisstrict => 'f',
+  prorettype => 'int8multirange', proargtypes => '_int8range',
+  proallargtypes => '{_int8range}', proargmodes => '{v}',
+  prosrc => 'multirange_constructor2' },
+{ oid => '8152', descr => 'anymultirange cast',
+  proname => 'multirange', proisstrict => 't',
+  prorettype => 'anymultirange', proargtypes => 'anyrange',
+  prosrc => 'multirange_constructor1' },
+{ oid => '8130', descr => 'aggregate transition function',
+  proname => 'range_agg_transfn', proisstrict => 'f', prorettype => 'internal',
+  proargtypes => 'internal anyrange', prosrc => 'range_agg_transfn' },
+{ oid => '8131', descr => 'aggregate final function',
+  proname => 'range_agg_finalfn', proisstrict => 'f', prorettype => 'anymultirange',
+  proargtypes => 'internal anyrange', prosrc => 'range_agg_finalfn' },
+{ oid => '8132', descr => 'combine aggregate input into a multirange',
+  proname => 'range_agg', prokind => 'a', proisstrict => 'f',
+  prorettype => 'anymultirange', proargtypes => 'anyrange',
+  prosrc => 'aggregate_dummy' },
+{ oid => '8135', descr => 'range aggregate by intersecting',
+  proname => 'multirange_intersect_agg_transfn', prorettype => 'anymultirange',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_intersect_agg_transfn'},
+{ oid => '8136', descr => 'range aggregate by intersecting',
+  proname => 'range_intersect_agg', prokind => 'a', proisstrict => 'f',
+  prorettype => 'anymultirange', proargtypes => 'anymultirange',
+  prosrc => 'aggregate_dummy'},
+
 # date, time, timestamp constructors
 { oid => '3846', descr => 'construct date',
   proname => 'make_date', prorettype => 'date', proargtypes => 'int4 int4 int4',
@@ -10142,6 +10405,14 @@
   proname => 'binary_upgrade_set_next_array_pg_type_oid', provolatile => 'v',
   proparallel => 'r', prorettype => 'void', proargtypes => 'oid',
   prosrc => 'binary_upgrade_set_next_array_pg_type_oid' },
+{ oid => '8144', descr => 'for use by pg_upgrade',
+  proname => 'binary_upgrade_set_next_multirange_pg_type_oid', provolatile => 'v',
+  proparallel => 'r', prorettype => 'void', proargtypes => 'oid',
+  prosrc => 'binary_upgrade_set_next_multirange_pg_type_oid' },
+{ oid => '8145', descr => 'for use by pg_upgrade',
+  proname => 'binary_upgrade_set_next_multirange_array_pg_type_oid', provolatile => 'v',
+  proparallel => 'r', prorettype => 'void', proargtypes => 'oid',
+  prosrc => 'binary_upgrade_set_next_multirange_array_pg_type_oid' },
 { oid => '3585', descr => 'for use by pg_upgrade',
   proname => 'binary_upgrade_set_next_toast_pg_type_oid', provolatile => 'v',
   proparallel => 'r', prorettype => 'void', proargtypes => 'oid',
diff --git a/src/include/catalog/pg_range.dat b/src/include/catalog/pg_range.dat
index 479754c245..10060255c9 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',
+  rngmultitypid => 'int4multirange' },
 { rngtypid => 'numrange', rngsubtype => 'numeric',
   rngsubopc => 'btree/numeric_ops', rngcanonical => '-',
-  rngsubdiff => 'numrange_subdiff' },
+  rngsubdiff => 'numrange_subdiff', rngmultitypid => 'nummultirange' },
 { rngtypid => 'tsrange', rngsubtype => 'timestamp',
   rngsubopc => 'btree/timestamp_ops', rngcanonical => '-',
-  rngsubdiff => 'tsrange_subdiff' },
+  rngsubdiff => 'tsrange_subdiff', rngmultitypid => 'tsmultirange' },
 { rngtypid => 'tstzrange', rngsubtype => 'timestamptz',
   rngsubopc => 'btree/timestamptz_ops', rngcanonical => '-',
-  rngsubdiff => 'tstzrange_subdiff' },
+  rngsubdiff => 'tstzrange_subdiff', rngmultitypid => 'tstzmultirange' },
 { rngtypid => 'daterange', rngsubtype => 'date', rngsubopc => 'btree/date_ops',
-  rngcanonical => 'daterange_canonical', rngsubdiff => 'daterange_subdiff' },
+  rngcanonical => 'daterange_canonical', rngsubdiff => 'daterange_subdiff',
+  rngmultitypid => 'datemultirange' },
 { rngtypid => 'int8range', rngsubtype => 'int8', rngsubopc => 'btree/int8_ops',
-  rngcanonical => 'int8range_canonical', rngsubdiff => 'int8range_subdiff' },
+  rngcanonical => 'int8range_canonical', rngsubdiff => 'int8range_subdiff',
+  rngmultitypid => 'int8multirange' },
 
 ]
diff --git a/src/include/catalog/pg_range.h b/src/include/catalog/pg_range.h
index 98172bb1b6..60a8fde518 100644
--- a/src/include/catalog/pg_range.h
+++ b/src/include/catalog/pg_range.h
@@ -34,6 +34,9 @@ CATALOG(pg_range,3541,RangeRelationId)
 	/* OID of range's element type (subtype) */
 	Oid			rngsubtype BKI_LOOKUP(pg_type);
 
+	/* OID of the range's multirange type */
+	Oid			rngmultitypid BKI_LOOKUP(pg_type);
+
 	/* collation for this range type, or 0 */
 	Oid			rngcollation BKI_DEFAULT(0);
 
@@ -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 b00597d6ff..6e1cb796f1 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 => 'R', 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 => 'R', 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 => 'R', 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 => 'R', 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 => 'R', 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 => 'R', 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
@@ -590,5 +624,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 97890946c5..d18f19f55a 100644
--- a/src/include/catalog/pg_type.h
+++ b/src/include/catalog/pg_type.h
@@ -263,6 +263,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 */
 
@@ -299,7 +300,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 */
 
@@ -358,4 +360,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/commands/typecmds.h b/src/include/commands/typecmds.h
index 0162bc2ffe..80e3a5e399 100644
--- a/src/include/commands/typecmds.h
+++ b/src/include/commands/typecmds.h
@@ -29,6 +29,8 @@ extern ObjectAddress DefineRange(CreateRangeStmt *stmt);
 extern ObjectAddress AlterEnum(AlterEnumStmt *stmt);
 extern ObjectAddress DefineCompositeType(RangeVar *typevar, List *coldeflist);
 extern Oid	AssignTypeArrayOid(void);
+extern Oid	AssignTypeMultirangeOid(void);
+extern Oid	AssignTypeMultirangeArrayOid(void);
 
 extern ObjectAddress AlterDomainDefault(List *names, Node *defaultRaw);
 extern ObjectAddress AlterDomainNotNull(List *names, bool notNull);
diff --git a/src/include/utils/lsyscache.h b/src/include/utils/lsyscache.h
index 4e646c55e9..80410dcd04 100644
--- a/src/include/utils/lsyscache.h
+++ b/src/include/utils/lsyscache.h
@@ -155,6 +155,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);
@@ -181,6 +182,8 @@ 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_collation(Oid rangeOid);
+extern Oid	get_range_multirange(Oid rangeOid);
+extern Oid	get_range_multirange_subtype(Oid multirangeOid);
 extern Oid	get_index_column_opclass(Oid index_oid, int attno);
 extern bool	get_index_isreplident(Oid index_oid);
 extern bool get_index_isvalid(Oid index_oid);
diff --git a/src/include/utils/multirangetypes.h b/src/include/utils/multirangetypes.h
new file mode 100644
index 0000000000..469444776c
--- /dev/null
+++ b/src/include/utils/multirangetypes.h
@@ -0,0 +1,103 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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
+{
+	int32		vl_len_;		/* 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);
+extern bool multirange_contains_elem_internal(TypeCacheEntry *typcache, MultirangeType *mr,
+											  Datum elem);
+extern bool multirange_contains_range_internal(TypeCacheEntry *typcache, MultirangeType *mr,
+											   RangeType *r);
+extern bool multirange_contains_multirange_internal(TypeCacheEntry *typcache,
+													MultirangeType *mr1,
+													MultirangeType *mr2);
+extern bool range_overlaps_multirange_internal(TypeCacheEntry *typcache, RangeType *r,
+											   MultirangeType *mr);
+extern bool multirange_overlaps_multirange_internal(TypeCacheEntry *typcache,
+													MultirangeType *mr1,
+													MultirangeType *mr2);
+extern bool range_before_multirange_internal(TypeCacheEntry *typcache, RangeType *r,
+											 MultirangeType *mr);
+extern bool range_after_multirange_internal(TypeCacheEntry *typcache, RangeType *r,
+											MultirangeType *mr);
+extern bool multirange_before_multirange_internal(TypeCacheEntry *typcache,
+												  MultirangeType *mr1,
+												  MultirangeType *mr2);
+extern MultirangeType *multirange_minus_internal(Oid mltrngtypoid,
+												 TypeCacheEntry *rangetyp,
+												 int32 range_count1,
+												 RangeType **ranges1,
+												 int32 range_count2,
+												 RangeType **ranges2);
+extern MultirangeType *multirange_intersect_internal(Oid mltrngtypoid,
+													 TypeCacheEntry *rangetyp,
+													 int32 range_count1,
+													 RangeType **ranges1,
+													 int32 range_count2,
+													 RangeType **ranges2);
+
+/* 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 0cbbf09033..204aee4054 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)
 
@@ -92,13 +94,21 @@ typedef struct
  * prototypes for functions defined in rangetypes.c
  */
 
-extern bool range_contains_elem_internal(TypeCacheEntry *typcache, const RangeType *r, Datum val);
-
 /* internal versions of the above */
+extern int	range_cmp_internal(TypeCacheEntry *typcache, const RangeType *r1,
+							   const RangeType *r2);
+extern uint32 hash_range_internal(TypeCacheEntry *typcache, const RangeType *r);
+extern uint64 hash_range_extended_internal(TypeCacheEntry *typcache, const RangeType *r,
+										   Datum seed);
+extern Datum range_lower_internal(TypeCacheEntry *typcache, const RangeType *r1,
+								  bool *isnull);
+extern Datum range_upper_internal(TypeCacheEntry *typcache, const RangeType *r1,
+								  bool *isnull);
 extern bool range_eq_internal(TypeCacheEntry *typcache, const RangeType *r1,
 							  const RangeType *r2);
 extern bool range_ne_internal(TypeCacheEntry *typcache, const RangeType *r1,
 							  const RangeType *r2);
+extern bool range_contains_elem_internal(TypeCacheEntry *typcache, const RangeType *r, Datum val);
 extern bool range_contains_internal(TypeCacheEntry *typcache, const RangeType *r1,
 									const RangeType *r2);
 extern bool range_contained_by_internal(TypeCacheEntry *typcache, const RangeType *r1,
@@ -115,6 +125,12 @@ extern bool range_overleft_internal(TypeCacheEntry *typcache, const RangeType *r
 									const RangeType *r2);
 extern bool range_overright_internal(TypeCacheEntry *typcache, const RangeType *r1,
 									 const RangeType *r2);
+extern RangeType *range_union_internal(TypeCacheEntry *typcache, RangeType *r1,
+									   RangeType *r2, bool strict);
+extern RangeType *range_minus_internal(TypeCacheEntry *typcache, RangeType *r1,
+									   RangeType *r2);
+extern RangeType *range_intersect_internal(TypeCacheEntry *typcache, const RangeType *r1,
+										   const RangeType *r2);
 
 /* assorted support functions */
 extern TypeCacheEntry *range_get_typcache(FunctionCallInfo fcinfo,
@@ -125,6 +141,7 @@ extern void range_deserialize(TypeCacheEntry *typcache, const RangeType *range,
 							  RangeBound *lower, RangeBound *upper,
 							  bool *empty);
 extern char range_get_flags(const RangeType *range);
+extern bool range_has_flag(const RangeType *range, char flag);
 extern void range_set_contain_empty(RangeType *range);
 extern RangeType *make_range(TypeCacheEntry *typcache, RangeBound *lower,
 							 RangeBound *upper, bool empty);
@@ -132,8 +149,12 @@ extern int range_cmp_bounds(TypeCacheEntry *typcache, const RangeBound *b1,
 							const RangeBound *b2);
 extern int range_cmp_bound_values(TypeCacheEntry *typcache, const RangeBound *b1,
 								  const RangeBound *b2);
-extern bool bounds_adjacent(TypeCacheEntry *typcache, RangeBound bound1,
-							RangeBound bound2);
+extern int range_compare(const void *key1, const void *key2, void *arg);
+extern bool bounds_adjacent(TypeCacheEntry *typcache, const RangeBound bound1,
+							const RangeBound bound2);
 extern RangeType *make_empty_range(TypeCacheEntry *typcache);
+extern bool range_split_internal(TypeCacheEntry *typcache, const RangeType *r1,
+								 const RangeType *r2, RangeType **output1,
+								 RangeType **output2);
 
 #endif							/* RANGETYPES_H */
diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h
index f27b73d76d..e8f393520a 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -79,6 +79,7 @@ enum SysCacheIdentifier
 	PUBLICATIONOID,
 	PUBLICATIONREL,
 	PUBLICATIONRELMAP,
+	RANGEMULTIRANGE,
 	RANGETYPE,
 	RELNAMENSP,
 	RELOID,
diff --git a/src/include/utils/typcache.h b/src/include/utils/typcache.h
index cdd20e56d7..33f332a141 100644
--- a/src/include/utils/typcache.h
+++ b/src/include/utils/typcache.h
@@ -101,6 +101,11 @@ typedef struct TypeCacheEntry
 	FmgrInfo	rng_subdiff_finfo;	/* difference function, if any */
 
 	/*
+	 * Fields computed when TYPCACHE_MULTIRANGE_INFO is required.
+	 */
+	struct TypeCacheEntry *rngtype;	/* multirange's range underlying type */
+
+	/*
 	 * Domain's base type and typmod if it's a domain type.  Zeroes if not
 	 * domain, or if information hasn't been requested.
 	 */
@@ -143,6 +148,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 c8e43e684f..2d59021d68 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? */
@@ -2087,6 +2089,7 @@ build_datatype(HeapTuple typeTup, int32 typmod,
 		case TYPTYPE_BASE:
 		case TYPTYPE_ENUM:
 		case TYPTYPE_RANGE:
+		case TYPTYPE_MULTIRANGE:
 			typ->ttype = PLPGSQL_TTYPE_SCALAR;
 			break;
 		case TYPTYPE_COMPOSITE:
@@ -2501,6 +2504,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/dependency.out b/src/test/regress/expected/dependency.out
index b4a11b8aa9..8232795148 100644
--- a/src/test/regress/expected/dependency.out
+++ b/src/test/regress/expected/dependency.out
@@ -140,6 +140,7 @@ owner of sequence deptest_a_seq
 owner of table deptest
 owner of function deptest_func()
 owner of type deptest_enum
+owner of type deptest_multirange
 owner of type deptest_range
 owner of table deptest2
 owner of sequence ss1
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..723f2408b0
--- /dev/null
+++ b/src/test/regress/expected/multirangetypes.out
@@ -0,0 +1,2395 @@
+-- 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)
+
+--
+-- test casts, both a built-in range type and a user-defined one:
+--
+select 'empty'::int4range::int4multirange;
+ int4multirange 
+----------------
+ {}
+(1 row)
+
+select int4range(1, 3)::int4multirange;
+ int4range 
+-----------
+ {[1,3)}
+(1 row)
+
+select int4range(1, null)::int4multirange;
+ int4range 
+-----------
+ {[1,)}
+(1 row)
+
+select int4range(null, null)::int4multirange;
+ int4range 
+-----------
+ {(,)}
+(1 row)
+
+select 'empty'::textrange::textmultirange;
+ textmultirange 
+----------------
+ {}
+(1 row)
+
+select textrange('a', 'c')::textmultirange;
+ textrange 
+-----------
+ {[a,c)}
+(1 row)
+
+select textrange('a', null)::textmultirange;
+ textrange 
+-----------
+ {[a,)}
+(1 row)
+
+select textrange(null, null)::textmultirange;
+ textrange 
+-----------
+ {(,)}
+(1 row)
+
+--
+-- create some test data and test the operators
+--
+CREATE TABLE nummultirange_test (nmr NUMMULTIRANGE);
+CREATE INDEX nummultirange_test_btree ON nummultirange_test(nmr);
+INSERT INTO nummultirange_test VALUES('{}');
+INSERT INTO nummultirange_test VALUES('{[,)}');
+INSERT INTO nummultirange_test VALUES('{[3,]}');
+INSERT INTO nummultirange_test VALUES('{[,), [3,]}');
+INSERT INTO nummultirange_test VALUES('{[, 5)}');
+INSERT INTO nummultirange_test VALUES(nummultirange());
+INSERT INTO nummultirange_test VALUES(nummultirange(variadic '{}'::numrange[]));
+INSERT INTO nummultirange_test VALUES(nummultirange(numrange(1.1, 2.2)));
+INSERT INTO nummultirange_test VALUES('{empty}');
+INSERT INTO nummultirange_test VALUES(nummultirange(numrange(1.7, 1.7, '[]'), numrange(1.7, 1.9)));
+INSERT INTO nummultirange_test VALUES(nummultirange(numrange(1.7, 1.7, '[]'), numrange(1.9, 2.1)));
+SELECT nmr, isempty(nmr), lower(nmr), upper(nmr) FROM nummultirange_test ORDER BY nmr;
+          nmr          | isempty | lower | upper 
+-----------------------+---------+-------+-------
+ {}                    | t       |       |      
+ {}                    | t       |       |      
+ {}                    | t       |       |      
+ {}                    | t       |       |      
+ {(,5)}                | f       |       |     5
+ {(,)}                 | f       |       |      
+ {(,)}                 | f       |       |      
+ {[1.1,2.2)}           | f       |   1.1 |   2.2
+ {[1.7,1.7],[1.9,2.1)} | f       |   1.7 |   2.1
+ {[1.7,1.9)}           | f       |   1.7 |   1.9
+ {[3,)}                | f       |     3 |      
+(11 rows)
+
+SELECT nmr, lower_inc(nmr), lower_inf(nmr), upper_inc(nmr), upper_inf(nmr) FROM nummultirange_test ORDER BY nmr;
+          nmr          | lower_inc | lower_inf | upper_inc | upper_inf 
+-----------------------+-----------+-----------+-----------+-----------
+ {}                    | f         | f         | f         | f
+ {}                    | f         | f         | f         | f
+ {}                    | f         | f         | f         | f
+ {}                    | f         | f         | f         | f
+ {(,5)}                | f         | t         | f         | f
+ {(,)}                 | f         | t         | f         | t
+ {(,)}                 | f         | t         | f         | t
+ {[1.1,2.2)}           | t         | f         | f         | f
+ {[1.7,1.7],[1.9,2.1)} | t         | f         | f         | f
+ {[1.7,1.9)}           | t         | f         | f         | f
+ {[3,)}                | t         | f         | f         | t
+(11 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr = '{}';
+ nmr 
+-----
+ {}
+ {}
+ {}
+ {}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr = '{(,5)}';
+  nmr   
+--------
+ {(,5)}
+(1 row)
+
+SELECT * FROM nummultirange_test WHERE nmr = '{[3,)}';
+  nmr   
+--------
+ {[3,)}
+(1 row)
+
+SELECT * FROM nummultirange_test WHERE nmr = '{[1.7,1.7]}';
+ nmr 
+-----
+(0 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr = '{[1.7,1.7],[1.9,2.1)}';
+          nmr          
+-----------------------
+ {[1.7,1.7],[1.9,2.1)}
+(1 row)
+
+SELECT * FROM nummultirange_test WHERE nmr < '{}';
+ nmr 
+-----
+(0 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr < '{[-1000.0, -1000.0]}';
+  nmr   
+--------
+ {}
+ {(,)}
+ {(,)}
+ {(,5)}
+ {}
+ {}
+ {}
+(7 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr < '{[0.0, 1.0]}';
+  nmr   
+--------
+ {}
+ {(,)}
+ {(,)}
+ {(,5)}
+ {}
+ {}
+ {}
+(7 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr < '{[1000.0, 1001.0]}';
+          nmr          
+-----------------------
+ {}
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+ {}
+ {}
+ {[1.1,2.2)}
+ {}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(11 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr <= '{}';
+ nmr 
+-----
+ {}
+ {}
+ {}
+ {}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr <= '{[3,)}';
+          nmr          
+-----------------------
+ {}
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+ {}
+ {}
+ {[1.1,2.2)}
+ {}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(11 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr >= '{}';
+          nmr          
+-----------------------
+ {}
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+ {}
+ {}
+ {[1.1,2.2)}
+ {}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(11 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr >= '{[3,)}';
+  nmr   
+--------
+ {[3,)}
+(1 row)
+
+SELECT * FROM nummultirange_test WHERE nmr > '{}';
+          nmr          
+-----------------------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+ {[1.1,2.2)}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(7 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr > '{[-1000.0, -1000.0]}';
+          nmr          
+-----------------------
+ {[3,)}
+ {[1.1,2.2)}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr > '{[0.0, 1.0]}';
+          nmr          
+-----------------------
+ {[3,)}
+ {[1.1,2.2)}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr > '{[1000.0, 1001.0]}';
+ nmr 
+-----
+(0 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr <> '{}';
+          nmr          
+-----------------------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+ {[1.1,2.2)}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(7 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr <> '{(,5)}';
+          nmr          
+-----------------------
+ {}
+ {(,)}
+ {[3,)}
+ {(,)}
+ {}
+ {}
+ {[1.1,2.2)}
+ {}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(10 rows)
+
+select nummultirange(numrange(2.0, 1.0));
+ERROR:  range lower bound must be less than or equal to range upper bound
+select nummultirange(numrange(5.0, 6.0), numrange(1.0, 2.0));
+     nummultirange     
+-----------------------
+ {[1.0,2.0),[5.0,6.0)}
+(1 row)
+
+-- overlaps
+SELECT * FROM nummultirange_test WHERE range_overlaps_multirange(numrange(4.0, 4.2), nmr);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE numrange(4.0, 4.2) && nmr;
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE multirange_overlaps_range(nmr, numrange(4.0, 4.2));
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr && numrange(4.0, 4.2);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE multirange_overlaps_multirange(nmr, nummultirange(numrange(4.0, 4.2), numrange(6.0, 7.0)));
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr && nummultirange(numrange(4.0, 4.2), numrange(6.0, 7.0));
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr && nummultirange(numrange(6.0, 7.0));
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+(3 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr && nummultirange(numrange(6.0, 7.0), numrange(8.0, 9.0));
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+(3 rows)
+
+-- mr contains x
+SELECT * FROM nummultirange_test WHERE multirange_contains_elem(nmr, 4.0);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr @> 4.0;
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE multirange_contains_range(nmr, numrange(4.0, 4.2));
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr @> numrange(4.0, 4.2);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE multirange_contains_multirange(nmr, '{[4.0,4.2), [6.0, 8.0)}');
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+(3 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr @> '{[4.0,4.2), [6.0, 8.0)}'::nummultirange;
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+(3 rows)
+
+-- x is contained by mr
+SELECT * FROM nummultirange_test WHERE elem_contained_by_multirange(4.0, nmr);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE 4.0 <@ nmr;
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE range_contained_by_multirange(numrange(4.0, 4.2), nmr);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE numrange(4.0, 4.2) <@ nmr;
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE multirange_contained_by_multirange('{[4.0,4.2), [6.0, 8.0)}', nmr);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+(3 rows)
+
+SELECT * FROM nummultirange_test WHERE '{[4.0,4.2), [6.0, 8.0)}'::nummultirange <@ nmr;
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+(3 rows)
+
+-- overlaps
+SELECT 'empty'::numrange && nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT 'empty'::numrange && nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() && 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) && 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() && nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() && nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) && nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) && nummultirange(numrange(1,2), numrange(7,8));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(7,8)) && nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) && nummultirange(numrange(1,2), numrange(3.5,8));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(3.5,8)) && numrange(3,4);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(3.5,8)) && nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+-- contains
+SELECT nummultirange() @> nummultirange();
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange() @> 'empty'::numrange;
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(null,null)) @> numrange(1,2);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(null,null)) @> numrange(null,2);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(null,null)) @> numrange(2,null);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(null,5)) @> numrange(null,3);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(null,5)) @> numrange(null,8);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(5,null)) @> numrange(8,null);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(5,null)) @> numrange(3,null);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,5)) @> numrange(8,9);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,5)) @> numrange(3,9);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,5)) @> numrange(1,4);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,5)) @> numrange(1,5);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(-4,-2), numrange(1,5)) @> numrange(1,5);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,5), numrange(8,9)) @> numrange(1,5);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,5), numrange(8,9)) @> numrange(6,7);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,5), numrange(6,9)) @> numrange(6,7);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[1,5)}'::nummultirange @> '{[1,5)}';
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[-4,-2), [1,5)}'::nummultirange @> '{[1,5)}';
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[1,5), [8,9)}'::nummultirange @> '{[1,5)}';
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[1,5), [8,9)}'::nummultirange @> '{[6,7)}';
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT '{[1,5), [6,9)}'::nummultirange @> '{[6,7)}';
+ ?column? 
+----------
+ t
+(1 row)
+
+-- is contained by
+SELECT nummultirange() <@ nummultirange();
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT 'empty'::numrange <@ nummultirange();
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,2) <@ nummultirange(numrange(null,null));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(null,2) <@ nummultirange(numrange(null,null));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(2,null) <@ nummultirange(numrange(null,null));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(null,3) <@ nummultirange(numrange(null,5));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(null,8) <@ nummultirange(numrange(null,5));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(8,null) <@ nummultirange(numrange(5,null));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(3,null) <@ nummultirange(numrange(5,null));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(8,9) <@ nummultirange(numrange(1,5));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(3,9) <@ nummultirange(numrange(1,5));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(1,4) <@ nummultirange(numrange(1,5));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,5) <@ nummultirange(numrange(1,5));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,5) <@ nummultirange(numrange(-4,-2), numrange(1,5));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,5) <@ nummultirange(numrange(1,5), numrange(8,9));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(6,7) <@ nummultirange(numrange(1,5), numrange(8,9));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(6,7) <@ nummultirange(numrange(1,5), numrange(6,9));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[1,5)}' <@ '{[1,5)}'::nummultirange;
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[1,5)}' <@ '{[-4,-2), [1,5)}'::nummultirange;
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[1,5)}' <@ '{[1,5), [8,9)}'::nummultirange;
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[6,7)}' <@ '{[1,5), [8,9)}'::nummultirange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT '{[6,7)}' <@ '{[1,5), [6,9)}'::nummultirange;
+ ?column? 
+----------
+ t
+(1 row)
+
+-- overleft
+SELECT 'empty'::numrange &< nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT 'empty'::numrange &< nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() &< 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) &< 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() &< nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) &< nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() &< nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(6,7) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(1,2) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,4) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,6) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(3.5,6) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(6,7)) &< numrange(3,4);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) &< numrange(3,4);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,4)) &< numrange(3,4);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,6)) &< numrange(3,4);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3.5,6)) &< numrange(3,4);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(6,7)) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,4)) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,6)) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3.5,6)) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+-- overright
+SELECT nummultirange() &> 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) &> 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT 'empty'::numrange &> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT 'empty'::numrange &> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() &> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() &> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) &> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> numrange(6,7);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> numrange(1,2);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> numrange(1,4);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> numrange(1,6);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> numrange(3.5,6);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(3,4) &> nummultirange(numrange(6,7));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(3,4) &> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(3,4) &> nummultirange(numrange(1,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(3,4) &> nummultirange(numrange(1,6));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(3,4) &> nummultirange(numrange(3.5,6));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(6,7));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(1,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(1,6));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(3.5,6));
+ ?column? 
+----------
+ f
+(1 row)
+
+-- meets
+SELECT 'empty'::numrange -|- nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT 'empty'::numrange -|- nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() -|- 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() -|- nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() -|- nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(1,2) -|- nummultirange(numrange(2,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,2) -|- nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- numrange(2,4);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- numrange(3,4);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- nummultirange(numrange(2,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(5,6)) -|- nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(5,6)) -|- nummultirange(numrange(6,7));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(5,6)) -|- nummultirange(numrange(8,9));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- nummultirange(numrange(2,4), numrange(6,7));
+ ?column? 
+----------
+ t
+(1 row)
+
+-- strictly left
+select 'empty'::numrange << nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(1,2) << nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(1,2) << nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+select numrange(1,2) << nummultirange(numrange(0,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(1,2) << nummultirange(numrange(0,4), numrange(7,8));
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange() << 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange() << numrange(1,2);
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(3,4)) << numrange(3,6);
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(0,2)) << numrange(3,6);
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange(numrange(0,2), numrange(7,8)) << numrange(3,6);
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(-4,-2), numrange(0,2)) << numrange(3,6);
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange() << nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange() << nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(1,2)) << nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(1,2)) << nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(1,2)) << nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange(numrange(1,2)) << nummultirange(numrange(3,4), numrange(7,8));
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange(numrange(1,2), numrange(4,5)) << nummultirange(numrange(3,4), numrange(7,8));
+ ?column? 
+----------
+ f
+(1 row)
+
+-- strictly right
+select nummultirange() >> 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange() >> numrange(1,2);
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(3,4)) >> numrange(1,2);
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange(numrange(0,4)) >> numrange(1,2);
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(0,4), numrange(7,8)) >> numrange(1,2);
+ ?column? 
+----------
+ f
+(1 row)
+
+select 'empty'::numrange >> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(1,2) >> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(3,6) >> nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(3,6) >> nummultirange(numrange(0,2));
+ ?column? 
+----------
+ t
+(1 row)
+
+select numrange(3,6) >> nummultirange(numrange(0,2), numrange(7,8));
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(3,6) >> nummultirange(numrange(-4,-2), numrange(0,2));
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange() >> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(1,2)) >> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange() >> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(1,2)) >> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(3,4)) >> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange(numrange(3,4), numrange(7,8)) >> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange(numrange(3,4), numrange(7,8)) >> nummultirange(numrange(1,2), numrange(4,5));
+ ?column? 
+----------
+ f
+(1 row)
+
+-- union
+SELECT nummultirange() + nummultirange();
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange() + nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) + nummultirange();
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) + nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) + nummultirange(numrange(2,4));
+ ?column? 
+----------
+ {[1,4)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) + nummultirange(numrange(3,4));
+   ?column?    
+---------------
+ {[1,2),[3,4)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) + nummultirange(numrange(2,4));
+ ?column? 
+----------
+ {[1,5)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) + nummultirange(numrange(3,4));
+   ?column?    
+---------------
+ {[1,2),[3,5)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) + nummultirange(numrange(0,9));
+ ?column? 
+----------
+ {[0,9)}
+(1 row)
+
+-- merge
+SELECT range_merge(nummultirange());
+ range_merge 
+-------------
+ empty
+(1 row)
+
+SELECT range_merge(nummultirange(numrange(1,2)));
+ range_merge 
+-------------
+ [1,2)
+(1 row)
+
+SELECT range_merge(nummultirange(numrange(1,2), numrange(7,8)));
+ range_merge 
+-------------
+ [1,8)
+(1 row)
+
+-- minus
+SELECT nummultirange() - nummultirange();
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange() - nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) - nummultirange();
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(3,4)) - nummultirange();
+   ?column?    
+---------------
+ {[1,2),[3,4)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) - nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) - nummultirange(numrange(2,4));
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) - nummultirange(numrange(3,4));
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {[2,4)}
+(1 row)
+
+SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(2,3));
+   ?column?    
+---------------
+ {[1,2),[3,4)}
+(1 row)
+
+SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(0,8));
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(0,2));
+ ?column? 
+----------
+ {[2,4)}
+(1 row)
+
+SELECT nummultirange(numrange(1,8)) - nummultirange(numrange(0,2), numrange(3,4));
+   ?column?    
+---------------
+ {[2,3),[4,8)}
+(1 row)
+
+SELECT nummultirange(numrange(1,8)) - nummultirange(numrange(2,3), numrange(5,null));
+   ?column?    
+---------------
+ {[1,2),[3,5)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(-2,0));
+   ?column?    
+---------------
+ {[1,2),[4,5)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(2,4));
+   ?column?    
+---------------
+ {[1,2),[4,5)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(3,5));
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(0,9));
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange(numrange(1,3), numrange(4,5)) - nummultirange(numrange(2,9));
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(8,9));
+   ?column?    
+---------------
+ {[1,2),[4,5)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(-2,0), numrange(8,9));
+   ?column?    
+---------------
+ {[1,2),[4,5)}
+(1 row)
+
+-- intersection
+SELECT nummultirange() * nummultirange();
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange() * nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) * nummultirange();
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT '{[1,3)}'::nummultirange * '{[1,5)}'::nummultirange;
+ ?column? 
+----------
+ {[1,3)}
+(1 row)
+
+SELECT '{[1,3)}'::nummultirange * '{[0,5)}'::nummultirange;
+ ?column? 
+----------
+ {[1,3)}
+(1 row)
+
+SELECT '{[1,3)}'::nummultirange * '{[0,2)}'::nummultirange;
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT '{[1,3)}'::nummultirange * '{[2,5)}'::nummultirange;
+ ?column? 
+----------
+ {[2,3)}
+(1 row)
+
+SELECT '{[1,4)}'::nummultirange * '{[2,3)}'::nummultirange;
+ ?column? 
+----------
+ {[2,3)}
+(1 row)
+
+SELECT '{[1,4)}'::nummultirange * '{[0,2), [3,5)}'::nummultirange;
+   ?column?    
+---------------
+ {[1,2),[3,4)}
+(1 row)
+
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[0,8), [9,12)}'::nummultirange;
+       ?column?       
+----------------------
+ {[1,4),[7,8),[9,10)}
+(1 row)
+
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[9,12)}'::nummultirange;
+ ?column? 
+----------
+ {[9,10)}
+(1 row)
+
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[-5,-4), [5,6), [9,12)}'::nummultirange;
+ ?column? 
+----------
+ {[9,10)}
+(1 row)
+
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[0,2), [3,8), [9,12)}'::nummultirange;
+          ?column?          
+----------------------------
+ {[1,2),[3,4),[7,8),[9,10)}
+(1 row)
+
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[0,2), [3,8), [9,12)}'::nummultirange;
+          ?column?          
+----------------------------
+ {[1,2),[3,4),[7,8),[9,10)}
+(1 row)
+
+--
+-- range_agg function
+--
+create table reservations ( room_id integer not null, booked_during daterange );
+insert into reservations values
+-- 1: has a meets and a gap
+(1, daterange('2018-07-01', '2018-07-07')),
+(1, daterange('2018-07-07', '2018-07-14')),
+(1, daterange('2018-07-20', '2018-07-22')),
+-- 2: just a single row
+(2, daterange('2018-07-01', '2018-07-03')),
+-- 3: one null range
+(3, NULL),
+-- 4: two null ranges
+(4, NULL),
+(4, NULL),
+-- 5: a null range and a non-null range
+(5, NULL),
+(5, daterange('2018-07-01', '2018-07-03')),
+-- 6: has overlap
+(6, daterange('2018-07-01', '2018-07-07')),
+(6, daterange('2018-07-05', '2018-07-10')),
+-- 7: two ranges that meet: no gap or overlap
+(7, daterange('2018-07-01', '2018-07-07')),
+(7, daterange('2018-07-07', '2018-07-14')),
+-- 8: an empty range
+(8, 'empty'::daterange)
+;
+SELECT   room_id, range_agg(booked_during)
+FROM     reservations
+GROUP BY room_id
+ORDER BY room_id;
+ room_id |                     range_agg                     
+---------+---------------------------------------------------
+       1 | {[07-01-2018,07-14-2018),[07-20-2018,07-22-2018)}
+       2 | {[07-01-2018,07-03-2018)}
+       3 | 
+       4 | 
+       5 | {[07-01-2018,07-03-2018)}
+       6 | {[07-01-2018,07-10-2018)}
+       7 | {[07-01-2018,07-14-2018)}
+       8 | {}
+(8 rows)
+
+-- range_agg on a custom range type too
+SELECT  range_agg(r)
+FROM    (VALUES
+          ('[a,c]'::textrange),
+          ('[b,b]'::textrange),
+          ('[c,f]'::textrange),
+          ('[g,h)'::textrange),
+          ('[h,j)'::textrange)
+        ) t(r);
+   range_agg   
+---------------
+ {[a,f],[g,j)}
+(1 row)
+
+select range_intersect_agg(nmr) from nummultirange_test;
+ range_intersect_agg 
+---------------------
+ {}
+(1 row)
+
+select range_intersect_agg(nmr) from nummultirange_test where false;
+ range_intersect_agg 
+---------------------
+ 
+(1 row)
+
+-- test with just one input:
+select range_intersect_agg(nmr) from (values ('{[1,2]}'::nummultirange)) t(nmr);
+ range_intersect_agg 
+---------------------
+ {[1,2]}
+(1 row)
+
+select range_intersect_agg(nmr) from nummultirange_test where nmr @> 4.0;
+ range_intersect_agg 
+---------------------
+ {[3,5)}
+(1 row)
+
+create table nummultirange_test2(nmr nummultirange);
+create index nummultirange_test2_hash_idx on nummultirange_test2 using hash (nmr);
+INSERT INTO nummultirange_test2 VALUES('{[, 5)}');
+INSERT INTO nummultirange_test2 VALUES(nummultirange(numrange(1.1, 2.2)));
+INSERT INTO nummultirange_test2 VALUES(nummultirange(numrange(1.1, 2.2)));
+INSERT INTO nummultirange_test2 VALUES(nummultirange(numrange(1.1, 2.2,'()')));
+INSERT INTO nummultirange_test2 VALUES('{}');
+select * from nummultirange_test2 where nmr = '{}';
+ nmr 
+-----
+ {}
+(1 row)
+
+select * from nummultirange_test2 where nmr = nummultirange(numrange(1.1, 2.2));
+     nmr     
+-------------
+ {[1.1,2.2)}
+ {[1.1,2.2)}
+(2 rows)
+
+select * from nummultirange_test2 where nmr = nummultirange(numrange(1.1, 2.3));
+ nmr 
+-----
+(0 rows)
+
+set enable_nestloop=t;
+set enable_hashjoin=f;
+set enable_mergejoin=f;
+select * from nummultirange_test natural join nummultirange_test2 order by nmr;
+     nmr     
+-------------
+ {}
+ {}
+ {}
+ {}
+ {(,5)}
+ {[1.1,2.2)}
+ {[1.1,2.2)}
+(7 rows)
+
+set enable_nestloop=f;
+set enable_hashjoin=t;
+set enable_mergejoin=f;
+select * from nummultirange_test natural join nummultirange_test2 order by nmr;
+     nmr     
+-------------
+ {}
+ {}
+ {}
+ {}
+ {(,5)}
+ {[1.1,2.2)}
+ {[1.1,2.2)}
+(7 rows)
+
+set enable_nestloop=f;
+set enable_hashjoin=f;
+set enable_mergejoin=t;
+select * from nummultirange_test natural join nummultirange_test2 order by nmr;
+     nmr     
+-------------
+ {}
+ {}
+ {}
+ {}
+ {(,5)}
+ {[1.1,2.2)}
+ {[1.1,2.2)}
+(7 rows)
+
+set enable_nestloop to default;
+set enable_hashjoin to default;
+set enable_mergejoin to default;
+DROP TABLE nummultirange_test2;
+--
+-- Test user-defined multirange of floats
+--
+select '{[123.001, 5.e9)}'::float8multirange @> 888.882::float8;
+ ?column? 
+----------
+ t
+(1 row)
+
+create table float8multirange_test(f8mr float8multirange, i int);
+insert into float8multirange_test values(float8multirange(float8range(-100.00007, '1.111113e9')), 42);
+select * from float8multirange_test;
+           f8mr            | i  
+---------------------------+----
+ {[-100.00007,1111113000)} | 42
+(1 row)
+
+drop table float8multirange_test;
+--
+-- Test multirange types over domains
+--
+create domain mydomain as int4;
+create type mydomainrange as range(subtype=mydomain);
+select '{[4,50)}'::mydomainmultirange @> 7::mydomain;
+ ?column? 
+----------
+ t
+(1 row)
+
+drop domain mydomain cascade;
+NOTICE:  drop cascades to type mydomainrange
+--
+-- Test domains over multirange types
+--
+create domain restrictedmultirange as int4multirange check (upper(value) < 10);
+select '{[4,5)}'::restrictedmultirange @> 7;
+ ?column? 
+----------
+ f
+(1 row)
+
+select '{[4,50)}'::restrictedmultirange @> 7; -- should fail
+ERROR:  value for domain restrictedmultirange violates check constraint "restrictedmultirange_check"
+drop domain restrictedmultirange;
+--
+-- Test multiple multirange types over the same subtype
+--
+create type textrange1 as range(subtype=text, collation="C");
+create type textrange2 as range(subtype=text, collation="C");
+select textmultirange1(textrange2('a','Z'));  -- should fail
+ERROR:  function textmultirange1(textrange2) does not exist
+LINE 1: select textmultirange1(textrange2('a','Z'));
+               ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+select textmultirange1(textrange1('a','Z')) @> 'b'::text;
+ERROR:  range lower bound must be less than or equal to range upper bound
+select textmultirange2(textrange2('a','z')) @> 'b'::text;
+ ?column? 
+----------
+ t
+(1 row)
+
+drop type textrange1;
+drop type textrange2;
+--
+-- Test polymorphic type system
+--
+create function anyarray_anymultirange_func(a anyarray, r anymultirange)
+  returns anyelement as 'select $1[1] + lower($2);' language sql;
+select anyarray_anymultirange_func(ARRAY[1,2], int4multirange(int4range(10,20)));
+ anyarray_anymultirange_func 
+-----------------------------
+                          11
+(1 row)
+
+-- should fail
+select anyarray_anymultirange_func(ARRAY[1,2], nummultirange(numrange(10,20)));
+ERROR:  function anyarray_anymultirange_func(integer[], nummultirange) does not exist
+LINE 1: select anyarray_anymultirange_func(ARRAY[1,2], nummultirange...
+               ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+drop function anyarray_anymultirange_func(anyarray, anymultirange);
+-- should fail
+create function bogus_func(anyelement)
+  returns anymultirange as 'select int4multirange(int4range(1,10))' language sql;
+ERROR:  cannot determine result data type
+DETAIL:  A function returning "anyrange" or "anymultirange" must have at least one "anyrange" or "anymultirange" argument.
+-- should fail
+create function bogus_func(int)
+  returns anymultirange as 'select int4multirange(int4range(1,10))' language sql;
+ERROR:  cannot determine result data type
+DETAIL:  A function returning a polymorphic type must have at least one polymorphic argument.
+create function range_add_bounds(anymultirange)
+  returns anyelement as 'select lower($1) + upper($1)' language sql;
+select range_add_bounds(int4multirange(int4range(1, 17)));
+ range_add_bounds 
+------------------
+               18
+(1 row)
+
+select range_add_bounds(nummultirange(numrange(1.0001, 123.123)));
+ range_add_bounds 
+------------------
+         124.1231
+(1 row)
+
+create function multirangetypes_sql(q anymultirange, b anyarray, out c anyelement)
+  as $$ select upper($1) + $2[1] $$
+  language sql;
+select multirangetypes_sql(int4multirange(int4range(1,10)), ARRAY[2,20]);
+ multirangetypes_sql 
+---------------------
+                  12
+(1 row)
+
+select multirangetypes_sql(nummultirange(numrange(1,10)), ARRAY[2,20]);  -- match failure
+ERROR:  function multirangetypes_sql(nummultirange, integer[]) does not exist
+LINE 1: select multirangetypes_sql(nummultirange(numrange(1,10)), AR...
+               ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+--
+-- Arrays of multiranges
+--
+select ARRAY[nummultirange(numrange(1.1, 1.2)), nummultirange(numrange(12.3, 155.5))];
+              array               
+----------------------------------
+ {"{[1.1,1.2)}","{[12.3,155.5)}"}
+(1 row)
+
+create table i8mr_array (f1 int, f2 int8multirange[]);
+insert into i8mr_array values (42, array[int8multirange(int8range(1,10)), int8multirange(int8range(2,20))]);
+select * from i8mr_array;
+ f1 |           f2            
+----+-------------------------
+ 42 | {"{[1,10)}","{[2,20)}"}
+(1 row)
+
+drop table i8mr_array;
+--
+-- Multiranges of arrays
+--
+select arraymultirange(arrayrange(ARRAY[1,2], ARRAY[2,1]));
+   arraymultirange   
+---------------------
+ {["{1,2}","{2,1}")}
+(1 row)
+
+select arraymultirange(arrayrange(ARRAY[2,1], ARRAY[1,2]));  -- fail
+ERROR:  range lower bound must be less than or equal to range upper bound
+select array[1,1] <@ arraymultirange(arrayrange(array[1,2], array[2,1]));
+ ?column? 
+----------
+ f
+(1 row)
+
+select array[1,3] <@ arraymultirange(arrayrange(array[1,2], array[2,1]));
+ ?column? 
+----------
+ t
+(1 row)
+
+--
+-- Ranges of composites
+--
+create type two_ints as (a int, b int);
+create type two_ints_range as range (subtype = two_ints);
+-- with force_parallel_mode on, this exercises tqueue.c's range remapping
+select *, row_to_json(upper(t)) as u from
+  (values (two_ints_multirange(two_ints_range(row(1,2), row(3,4)))),
+          (two_ints_multirange(two_ints_range(row(5,6), row(7,8))))) v(t);
+          t          |       u       
+---------------------+---------------
+ {["(1,2)","(3,4)")} | {"a":3,"b":4}
+ {["(5,6)","(7,8)")} | {"a":7,"b":8}
+(2 rows)
+
+drop type two_ints cascade;
+NOTICE:  drop cascades to type two_ints_range
+--
+-- Check behavior when subtype lacks a hash function
+--
+set enable_sort = off;  -- try to make it pick a hash setop implementation
+select '{(2,5)}'::cashmultirange except select '{(5,6)}'::cashmultirange;
+ cashmultirange  
+-----------------
+ {($2.00,$5.00)}
+(1 row)
+
+reset enable_sort;
+--
+-- OUT/INOUT/TABLE functions
+--
+-- infer anymultirange from anymultirange
+create function mr_outparam_succeed(i anymultirange, out r anymultirange, out t text)
+  as $$ select $1, 'foo'::text $$ language sql;
+select * from mr_outparam_succeed(int4multirange(int4range(1,2)));
+    r    |  t  
+---------+-----
+ {[1,2)} | foo
+(1 row)
+
+-- infer anyarray from anymultirange
+create function mr_outparam_succeed2(i anymultirange, out r anyarray, out t text)
+  as $$ select ARRAY[upper($1)], 'foo'::text $$ language sql;
+select * from mr_outparam_succeed2(int4multirange(int4range(1,2)));
+  r  |  t  
+-----+-----
+ {2} | foo
+(1 row)
+
+-- infer anyrange from anymultirange
+create function mr_outparam_succeed3(i anymultirange, out r anyrange, out t text)
+  as $$ select range_merge($1), 'foo'::text $$ language sql;
+select * from mr_outparam_succeed3(int4multirange(int4range(1,2)));
+   r   |  t  
+-------+-----
+ [1,2) | foo
+(1 row)
+
+-- infer anymultirange from anyrange
+create function mr_outparam_succeed4(i anyrange, out r anymultirange, out t text)
+  as $$ select multirange($1), 'foo'::text $$ language sql;
+select * from mr_outparam_succeed4(int4range(1,2));
+    r    |  t  
+---------+-----
+ {[1,2)} | foo
+(1 row)
+
+-- infer anyelement from anymultirange
+create function mr_inoutparam_succeed(out i anyelement, inout r anymultirange)
+  as $$ select upper($1), $1 $$ language sql;
+select * from mr_inoutparam_succeed(int4multirange(int4range(1,2)));
+ i |    r    
+---+---------
+ 2 | {[1,2)}
+(1 row)
+
+-- infer anyelement+anymultirange from anyelement+anymultirange
+create function mr_table_succeed(i anyelement, r anymultirange) returns table(i anyelement, r anymultirange)
+  as $$ select $1, $2 $$ language sql;
+select * from mr_table_succeed(123, int4multirange(int4range(1,11)));
+  i  |    r     
+-----+----------
+ 123 | {[1,11)}
+(1 row)
+
+-- use anymultirange in plpgsql
+create function mr_polymorphic(i anyrange) returns anymultirange
+  as $$ begin return multirange($1); end; $$ language plpgsql;
+select mr_polymorphic(int4range(1, 4));
+ mr_polymorphic 
+----------------
+ {[1,4)}
+(1 row)
+
+-- should fail
+create function mr_outparam_fail(i anyelement, out r anymultirange, out t text)
+  as $$ select '[1,10]', 'foo' $$ language sql;
+ERROR:  cannot determine result data type
+DETAIL:  A function returning "anyrange" or "anymultirange" must have at least one "anyrange" or "anymultirange" argument.
+--should fail
+create function mr_inoutparam_fail(inout i anyelement, out r anymultirange)
+  as $$ select $1, '[1,10]' $$ language sql;
+ERROR:  cannot determine result data type
+DETAIL:  A function returning "anyrange" or "anymultirange" must have at least one "anyrange" or "anymultirange" argument.
+--should fail
+create function mr_table_fail(i anyelement) returns table(i anyelement, r anymultirange)
+  as $$ select $1, '[1,10]' $$ language sql;
+ERROR:  cannot determine result data type
+DETAIL:  A function returning "anyrange" or "anymultirange" must have at least one "anyrange" or "anymultirange" argument.
diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out
index 40468e8f49..1514f3af7c 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
@@ -2139,13 +2154,14 @@ ORDER BY 1, 2, 3;
                     | float_ops        | float4_ops       | real
                     | float_ops        | float8_ops       | double precision
                     | jsonb_ops        | jsonb_ops        | jsonb
+                    | multirange_ops   | multirange_ops   | anymultirange
                     | numeric_ops      | numeric_ops      | numeric
                     | range_ops        | range_ops        | anyrange
                     | record_image_ops | record_image_ops | record
                     | record_ops       | record_ops       | record
                     | tsquery_ops      | tsquery_ops      | tsquery
                     | tsvector_ops     | tsvector_ops     | tsvector
-(14 rows)
+(15 rows)
 
 -- **************** pg_index ****************
 -- Look for illegal values in pg_index fields.
diff --git a/src/test/regress/expected/rangetypes.out b/src/test/regress/expected/rangetypes.out
index 348235a15e..3548026223 100644
--- a/src/test/regress/expected/rangetypes.out
+++ b/src/test/regress/expected/rangetypes.out
@@ -518,6 +518,24 @@ select numrange(1.0, 2.0) * numrange(2.5, 3.0);
  empty
 (1 row)
 
+select range_intersect_agg(nr) from numrange_test;
+ range_intersect_agg 
+---------------------
+ empty
+(1 row)
+
+select range_intersect_agg(nr) from numrange_test where false;
+ range_intersect_agg 
+---------------------
+ 
+(1 row)
+
+select range_intersect_agg(nr) from numrange_test where nr @> 4.0;
+ range_intersect_agg 
+---------------------
+ [3,5)
+(1 row)
+
 create table numrange_test2(nr numrange);
 create index numrange_test2_hash_idx on numrange_test2 using hash (nr);
 INSERT INTO numrange_test2 VALUES('[, 5)');
@@ -1371,7 +1389,7 @@ drop function anyarray_anyrange_func(anyarray, anyrange);
 create function bogus_func(anyelement)
   returns anyrange as 'select int4range(1,10)' language sql;
 ERROR:  cannot determine result data type
-DETAIL:  A function returning "anyrange" must have at least one "anyrange" argument.
+DETAIL:  A function returning "anyrange" or "anymultirange" must have at least one "anyrange" or "anymultirange" argument.
 -- should fail
 create function bogus_func(int)
   returns anyrange as 'select int4range(1,10)' language sql;
@@ -1482,6 +1500,7 @@ reset enable_sort;
 --
 -- OUT/INOUT/TABLE functions
 --
+-- infer anyrange from anyrange
 create function outparam_succeed(i anyrange, out r anyrange, out t text)
   as $$ select $1, 'foo'::text $$ language sql;
 select * from outparam_succeed(int4range(1,2));
@@ -1499,6 +1518,16 @@ select * from outparam2_succeed(int4range(1,11));
  {1,11} | {11,1}
 (1 row)
 
+-- infer anyarray from anyrange
+create function outparam_succeed2(i anyrange, out r anyarray, out t text)
+  as $$ select ARRAY[upper($1)], 'foo'::text $$ language sql;
+select * from outparam_succeed2(int4range(int4range(1,2)));
+  r  |  t  
+-----+-----
+ {2} | foo
+(1 row)
+
+-- infer anyelement from anyrange
 create function inoutparam_succeed(out i anyelement, inout r anyrange)
   as $$ select upper($1), $1 $$ language sql;
 select * from inoutparam_succeed(int4range(1,2));
@@ -1521,14 +1550,14 @@ select * from table_succeed(int4range(1,11));
 create function outparam_fail(i anyelement, out r anyrange, out t text)
   as $$ select '[1,10]', 'foo' $$ language sql;
 ERROR:  cannot determine result data type
-DETAIL:  A function returning "anyrange" must have at least one "anyrange" argument.
+DETAIL:  A function returning "anyrange" or "anymultirange" must have at least one "anyrange" or "anymultirange" argument.
 --should fail
 create function inoutparam_fail(inout i anyelement, out r anyrange)
   as $$ select $1, '[1,10]' $$ language sql;
 ERROR:  cannot determine result data type
-DETAIL:  A function returning "anyrange" must have at least one "anyrange" argument.
+DETAIL:  A function returning "anyrange" or "anymultirange" must have at least one "anyrange" or "anymultirange" argument.
 --should fail
 create function table_fail(i anyelement) returns table(i anyelement, r anyrange)
   as $$ select $1, '[1,10]' $$ language sql;
 ERROR:  cannot determine result data type
-DETAIL:  A function returning "anyrange" must have at least one "anyrange" argument.
+DETAIL:  A function returning "anyrange" or "anymultirange" must have at least one "anyrange" or "anymultirange" argument.
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index 192445878d..d9ce961be2 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -92,6 +92,7 @@ num_exp_sqrt|t
 num_exp_sub|t
 num_input_test|f
 num_result|f
+nummultirange_test|t
 numrange_test|t
 onek|t
 onek2|t
@@ -172,6 +173,7 @@ quad_poly_tbl|t
 radix_text_tbl|t
 ramp|f
 real_city|f
+reservations|f
 road|t
 shighway|t
 slow_emp4000|f
diff --git a/src/test/regress/expected/type_sanity.out b/src/test/regress/expected/type_sanity.out
index cd7fc03b04..60a9b4d22f 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.rngmultitypid
+FROM pg_range p1
+WHERE p1.rngmultitypid IS NULL OR p1.rngmultitypid = 0;
+ rngtypid | rngsubtype | rngmultitypid 
+----------+------------+---------------
+(0 rows)
+
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index d2b17dd3ea..5355da7b01 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 acba391332..1f677f63af 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..0f70919e1a
--- /dev/null
+++ b/src/test/regress/sql/multirangetypes.sql
@@ -0,0 +1,630 @@
+-- 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'));
+
+--
+-- test casts, both a built-in range type and a user-defined one:
+--
+select 'empty'::int4range::int4multirange;
+select int4range(1, 3)::int4multirange;
+select int4range(1, null)::int4multirange;
+select int4range(null, null)::int4multirange;
+select 'empty'::textrange::textmultirange;
+select textrange('a', 'c')::textmultirange;
+select textrange('a', null)::textmultirange;
+select textrange(null, null)::textmultirange;
+
+--
+-- create some test data and test the operators
+--
+
+CREATE TABLE nummultirange_test (nmr NUMMULTIRANGE);
+CREATE INDEX nummultirange_test_btree ON nummultirange_test(nmr);
+
+INSERT INTO nummultirange_test VALUES('{}');
+INSERT INTO nummultirange_test VALUES('{[,)}');
+INSERT INTO nummultirange_test VALUES('{[3,]}');
+INSERT INTO nummultirange_test VALUES('{[,), [3,]}');
+INSERT INTO nummultirange_test VALUES('{[, 5)}');
+INSERT INTO nummultirange_test VALUES(nummultirange());
+INSERT INTO nummultirange_test VALUES(nummultirange(variadic '{}'::numrange[]));
+INSERT INTO nummultirange_test VALUES(nummultirange(numrange(1.1, 2.2)));
+INSERT INTO nummultirange_test VALUES('{empty}');
+INSERT INTO nummultirange_test VALUES(nummultirange(numrange(1.7, 1.7, '[]'), numrange(1.7, 1.9)));
+INSERT INTO nummultirange_test VALUES(nummultirange(numrange(1.7, 1.7, '[]'), numrange(1.9, 2.1)));
+
+SELECT nmr, isempty(nmr), lower(nmr), upper(nmr) FROM nummultirange_test ORDER BY nmr;
+SELECT nmr, lower_inc(nmr), lower_inf(nmr), upper_inc(nmr), upper_inf(nmr) FROM nummultirange_test ORDER BY nmr;
+
+SELECT * FROM nummultirange_test WHERE nmr = '{}';
+SELECT * FROM nummultirange_test WHERE nmr = '{(,5)}';
+SELECT * FROM nummultirange_test WHERE nmr = '{[3,)}';
+SELECT * FROM nummultirange_test WHERE nmr = '{[1.7,1.7]}';
+SELECT * FROM nummultirange_test WHERE nmr = '{[1.7,1.7],[1.9,2.1)}';
+SELECT * FROM nummultirange_test WHERE nmr < '{}';
+SELECT * FROM nummultirange_test WHERE nmr < '{[-1000.0, -1000.0]}';
+SELECT * FROM nummultirange_test WHERE nmr < '{[0.0, 1.0]}';
+SELECT * FROM nummultirange_test WHERE nmr < '{[1000.0, 1001.0]}';
+SELECT * FROM nummultirange_test WHERE nmr <= '{}';
+SELECT * FROM nummultirange_test WHERE nmr <= '{[3,)}';
+SELECT * FROM nummultirange_test WHERE nmr >= '{}';
+SELECT * FROM nummultirange_test WHERE nmr >= '{[3,)}';
+SELECT * FROM nummultirange_test WHERE nmr > '{}';
+SELECT * FROM nummultirange_test WHERE nmr > '{[-1000.0, -1000.0]}';
+SELECT * FROM nummultirange_test WHERE nmr > '{[0.0, 1.0]}';
+SELECT * FROM nummultirange_test WHERE nmr > '{[1000.0, 1001.0]}';
+SELECT * FROM nummultirange_test WHERE nmr <> '{}';
+SELECT * FROM nummultirange_test WHERE nmr <> '{(,5)}';
+
+select nummultirange(numrange(2.0, 1.0));
+select nummultirange(numrange(5.0, 6.0), numrange(1.0, 2.0));
+
+-- overlaps
+SELECT * FROM nummultirange_test WHERE range_overlaps_multirange(numrange(4.0, 4.2), nmr);
+SELECT * FROM nummultirange_test WHERE numrange(4.0, 4.2) && nmr;
+SELECT * FROM nummultirange_test WHERE multirange_overlaps_range(nmr, numrange(4.0, 4.2));
+SELECT * FROM nummultirange_test WHERE nmr && numrange(4.0, 4.2);
+SELECT * FROM nummultirange_test WHERE multirange_overlaps_multirange(nmr, nummultirange(numrange(4.0, 4.2), numrange(6.0, 7.0)));
+SELECT * FROM nummultirange_test WHERE nmr && nummultirange(numrange(4.0, 4.2), numrange(6.0, 7.0));
+SELECT * FROM nummultirange_test WHERE nmr && nummultirange(numrange(6.0, 7.0));
+SELECT * FROM nummultirange_test WHERE nmr && nummultirange(numrange(6.0, 7.0), numrange(8.0, 9.0));
+
+-- mr contains x
+SELECT * FROM nummultirange_test WHERE multirange_contains_elem(nmr, 4.0);
+SELECT * FROM nummultirange_test WHERE nmr @> 4.0;
+SELECT * FROM nummultirange_test WHERE multirange_contains_range(nmr, numrange(4.0, 4.2));
+SELECT * FROM nummultirange_test WHERE nmr @> numrange(4.0, 4.2);
+SELECT * FROM nummultirange_test WHERE multirange_contains_multirange(nmr, '{[4.0,4.2), [6.0, 8.0)}');
+SELECT * FROM nummultirange_test WHERE nmr @> '{[4.0,4.2), [6.0, 8.0)}'::nummultirange;
+
+-- x is contained by mr
+SELECT * FROM nummultirange_test WHERE elem_contained_by_multirange(4.0, nmr);
+SELECT * FROM nummultirange_test WHERE 4.0 <@ nmr;
+SELECT * FROM nummultirange_test WHERE range_contained_by_multirange(numrange(4.0, 4.2), nmr);
+SELECT * FROM nummultirange_test WHERE numrange(4.0, 4.2) <@ nmr;
+SELECT * FROM nummultirange_test WHERE multirange_contained_by_multirange('{[4.0,4.2), [6.0, 8.0)}', nmr);
+SELECT * FROM nummultirange_test WHERE '{[4.0,4.2), [6.0, 8.0)}'::nummultirange <@ nmr;
+
+-- overlaps
+SELECT 'empty'::numrange && nummultirange();
+SELECT 'empty'::numrange && nummultirange(numrange(1,2));
+SELECT nummultirange() && 'empty'::numrange;
+SELECT nummultirange(numrange(1,2)) && 'empty'::numrange;
+SELECT nummultirange() && nummultirange();
+SELECT nummultirange() && nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) && nummultirange();
+SELECT nummultirange(numrange(3,4)) && nummultirange(numrange(1,2), numrange(7,8));
+SELECT nummultirange(numrange(1,2), numrange(7,8)) && nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(3,4)) && nummultirange(numrange(1,2), numrange(3.5,8));
+SELECT nummultirange(numrange(1,2), numrange(3.5,8)) && numrange(3,4);
+SELECT nummultirange(numrange(1,2), numrange(3.5,8)) && nummultirange(numrange(3,4));
+
+-- contains
+SELECT nummultirange() @> nummultirange();
+SELECT nummultirange() @> 'empty'::numrange;
+SELECT nummultirange(numrange(null,null)) @> numrange(1,2);
+SELECT nummultirange(numrange(null,null)) @> numrange(null,2);
+SELECT nummultirange(numrange(null,null)) @> numrange(2,null);
+SELECT nummultirange(numrange(null,5)) @> numrange(null,3);
+SELECT nummultirange(numrange(null,5)) @> numrange(null,8);
+SELECT nummultirange(numrange(5,null)) @> numrange(8,null);
+SELECT nummultirange(numrange(5,null)) @> numrange(3,null);
+SELECT nummultirange(numrange(1,5)) @> numrange(8,9);
+SELECT nummultirange(numrange(1,5)) @> numrange(3,9);
+SELECT nummultirange(numrange(1,5)) @> numrange(1,4);
+SELECT nummultirange(numrange(1,5)) @> numrange(1,5);
+SELECT nummultirange(numrange(-4,-2), numrange(1,5)) @> numrange(1,5);
+SELECT nummultirange(numrange(1,5), numrange(8,9)) @> numrange(1,5);
+SELECT nummultirange(numrange(1,5), numrange(8,9)) @> numrange(6,7);
+SELECT nummultirange(numrange(1,5), numrange(6,9)) @> numrange(6,7);
+SELECT '{[1,5)}'::nummultirange @> '{[1,5)}';
+SELECT '{[-4,-2), [1,5)}'::nummultirange @> '{[1,5)}';
+SELECT '{[1,5), [8,9)}'::nummultirange @> '{[1,5)}';
+SELECT '{[1,5), [8,9)}'::nummultirange @> '{[6,7)}';
+SELECT '{[1,5), [6,9)}'::nummultirange @> '{[6,7)}';
+
+-- is contained by
+SELECT nummultirange() <@ nummultirange();
+SELECT 'empty'::numrange <@ nummultirange();
+SELECT numrange(1,2) <@ nummultirange(numrange(null,null));
+SELECT numrange(null,2) <@ nummultirange(numrange(null,null));
+SELECT numrange(2,null) <@ nummultirange(numrange(null,null));
+SELECT numrange(null,3) <@ nummultirange(numrange(null,5));
+SELECT numrange(null,8) <@ nummultirange(numrange(null,5));
+SELECT numrange(8,null) <@ nummultirange(numrange(5,null));
+SELECT numrange(3,null) <@ nummultirange(numrange(5,null));
+SELECT numrange(8,9) <@ nummultirange(numrange(1,5));
+SELECT numrange(3,9) <@ nummultirange(numrange(1,5));
+SELECT numrange(1,4) <@ nummultirange(numrange(1,5));
+SELECT numrange(1,5) <@ nummultirange(numrange(1,5));
+SELECT numrange(1,5) <@ nummultirange(numrange(-4,-2), numrange(1,5));
+SELECT numrange(1,5) <@ nummultirange(numrange(1,5), numrange(8,9));
+SELECT numrange(6,7) <@ nummultirange(numrange(1,5), numrange(8,9));
+SELECT numrange(6,7) <@ nummultirange(numrange(1,5), numrange(6,9));
+SELECT '{[1,5)}' <@ '{[1,5)}'::nummultirange;
+SELECT '{[1,5)}' <@ '{[-4,-2), [1,5)}'::nummultirange;
+SELECT '{[1,5)}' <@ '{[1,5), [8,9)}'::nummultirange;
+SELECT '{[6,7)}' <@ '{[1,5), [8,9)}'::nummultirange;
+SELECT '{[6,7)}' <@ '{[1,5), [6,9)}'::nummultirange;
+
+-- overleft
+SELECT 'empty'::numrange &< nummultirange();
+SELECT 'empty'::numrange &< nummultirange(numrange(1,2));
+SELECT nummultirange() &< 'empty'::numrange;
+SELECT nummultirange(numrange(1,2)) &< 'empty'::numrange;
+SELECT nummultirange() &< nummultirange();
+SELECT nummultirange(numrange(1,2)) &< nummultirange();
+SELECT nummultirange() &< nummultirange(numrange(1,2));
+SELECT numrange(6,7) &< nummultirange(numrange(3,4));
+SELECT numrange(1,2) &< nummultirange(numrange(3,4));
+SELECT numrange(1,4) &< nummultirange(numrange(3,4));
+SELECT numrange(1,6) &< nummultirange(numrange(3,4));
+SELECT numrange(3.5,6) &< nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(6,7)) &< numrange(3,4);
+SELECT nummultirange(numrange(1,2)) &< numrange(3,4);
+SELECT nummultirange(numrange(1,4)) &< numrange(3,4);
+SELECT nummultirange(numrange(1,6)) &< numrange(3,4);
+SELECT nummultirange(numrange(3.5,6)) &< numrange(3,4);
+SELECT nummultirange(numrange(6,7)) &< nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,2)) &< nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,4)) &< nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,6)) &< nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(3.5,6)) &< nummultirange(numrange(3,4));
+
+-- overright
+SELECT nummultirange() &> 'empty'::numrange;
+SELECT nummultirange(numrange(1,2)) &> 'empty'::numrange;
+SELECT 'empty'::numrange &> nummultirange();
+SELECT 'empty'::numrange &> nummultirange(numrange(1,2));
+SELECT nummultirange() &> nummultirange();
+SELECT nummultirange() &> nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) &> nummultirange();
+SELECT nummultirange(numrange(3,4)) &> numrange(6,7);
+SELECT nummultirange(numrange(3,4)) &> numrange(1,2);
+SELECT nummultirange(numrange(3,4)) &> numrange(1,4);
+SELECT nummultirange(numrange(3,4)) &> numrange(1,6);
+SELECT nummultirange(numrange(3,4)) &> numrange(3.5,6);
+SELECT numrange(3,4) &> nummultirange(numrange(6,7));
+SELECT numrange(3,4) &> nummultirange(numrange(1,2));
+SELECT numrange(3,4) &> nummultirange(numrange(1,4));
+SELECT numrange(3,4) &> nummultirange(numrange(1,6));
+SELECT numrange(3,4) &> nummultirange(numrange(3.5,6));
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(6,7));
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(1,4));
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(1,6));
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(3.5,6));
+
+-- meets
+SELECT 'empty'::numrange -|- nummultirange();
+SELECT 'empty'::numrange -|- nummultirange(numrange(1,2));
+SELECT nummultirange() -|- 'empty'::numrange;
+SELECT nummultirange(numrange(1,2)) -|- 'empty'::numrange;
+SELECT nummultirange() -|- nummultirange();
+SELECT nummultirange(numrange(1,2)) -|- nummultirange();
+SELECT nummultirange() -|- nummultirange(numrange(1,2));
+SELECT numrange(1,2) -|- nummultirange(numrange(2,4));
+SELECT numrange(1,2) -|- nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,2)) -|- numrange(2,4);
+SELECT nummultirange(numrange(1,2)) -|- numrange(3,4);
+SELECT nummultirange(numrange(1,2)) -|- nummultirange(numrange(2,4));
+SELECT nummultirange(numrange(1,2)) -|- nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,2), numrange(5,6)) -|- nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,2), numrange(5,6)) -|- nummultirange(numrange(6,7));
+SELECT nummultirange(numrange(1,2), numrange(5,6)) -|- nummultirange(numrange(8,9));
+SELECT nummultirange(numrange(1,2)) -|- nummultirange(numrange(2,4), numrange(6,7));
+
+-- strictly left
+select 'empty'::numrange << nummultirange();
+select numrange(1,2) << nummultirange();
+select numrange(1,2) << nummultirange(numrange(3,4));
+select numrange(1,2) << nummultirange(numrange(0,4));
+select numrange(1,2) << nummultirange(numrange(0,4), numrange(7,8));
+select nummultirange() << 'empty'::numrange;
+select nummultirange() << numrange(1,2);
+select nummultirange(numrange(3,4)) << numrange(3,6);
+select nummultirange(numrange(0,2)) << numrange(3,6);
+select nummultirange(numrange(0,2), numrange(7,8)) << numrange(3,6);
+select nummultirange(numrange(-4,-2), numrange(0,2)) << numrange(3,6);
+select nummultirange() << nummultirange();
+select nummultirange() << nummultirange(numrange(1,2));
+select nummultirange(numrange(1,2)) << nummultirange();
+select nummultirange(numrange(1,2)) << nummultirange(numrange(1,2));
+select nummultirange(numrange(1,2)) << nummultirange(numrange(3,4));
+select nummultirange(numrange(1,2)) << nummultirange(numrange(3,4), numrange(7,8));
+select nummultirange(numrange(1,2), numrange(4,5)) << nummultirange(numrange(3,4), numrange(7,8));
+
+-- strictly right
+select nummultirange() >> 'empty'::numrange;
+select nummultirange() >> numrange(1,2);
+select nummultirange(numrange(3,4)) >> numrange(1,2);
+select nummultirange(numrange(0,4)) >> numrange(1,2);
+select nummultirange(numrange(0,4), numrange(7,8)) >> numrange(1,2);
+select 'empty'::numrange >> nummultirange();
+select numrange(1,2) >> nummultirange();
+select numrange(3,6) >> nummultirange(numrange(3,4));
+select numrange(3,6) >> nummultirange(numrange(0,2));
+select numrange(3,6) >> nummultirange(numrange(0,2), numrange(7,8));
+select numrange(3,6) >> nummultirange(numrange(-4,-2), numrange(0,2));
+select nummultirange() >> nummultirange();
+select nummultirange(numrange(1,2)) >> nummultirange();
+select nummultirange() >> nummultirange(numrange(1,2));
+select nummultirange(numrange(1,2)) >> nummultirange(numrange(1,2));
+select nummultirange(numrange(3,4)) >> nummultirange(numrange(1,2));
+select nummultirange(numrange(3,4), numrange(7,8)) >> nummultirange(numrange(1,2));
+select nummultirange(numrange(3,4), numrange(7,8)) >> nummultirange(numrange(1,2), numrange(4,5));
+
+-- union
+SELECT nummultirange() + nummultirange();
+SELECT nummultirange() + nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) + nummultirange();
+SELECT nummultirange(numrange(1,2)) + nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) + nummultirange(numrange(2,4));
+SELECT nummultirange(numrange(1,2)) + nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) + nummultirange(numrange(2,4));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) + nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) + nummultirange(numrange(0,9));
+
+-- merge
+SELECT range_merge(nummultirange());
+SELECT range_merge(nummultirange(numrange(1,2)));
+SELECT range_merge(nummultirange(numrange(1,2), numrange(7,8)));
+
+-- minus
+SELECT nummultirange() - nummultirange();
+SELECT nummultirange() - nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) - nummultirange();
+SELECT nummultirange(numrange(1,2), numrange(3,4)) - nummultirange();
+SELECT nummultirange(numrange(1,2)) - nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) - nummultirange(numrange(2,4));
+SELECT nummultirange(numrange(1,2)) - nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(2,3));
+SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(0,8));
+SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(0,2));
+SELECT nummultirange(numrange(1,8)) - nummultirange(numrange(0,2), numrange(3,4));
+SELECT nummultirange(numrange(1,8)) - nummultirange(numrange(2,3), numrange(5,null));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(-2,0));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(2,4));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(3,5));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(0,9));
+SELECT nummultirange(numrange(1,3), numrange(4,5)) - nummultirange(numrange(2,9));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(8,9));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(-2,0), numrange(8,9));
+
+-- intersection
+SELECT nummultirange() * nummultirange();
+SELECT nummultirange() * nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) * nummultirange();
+SELECT '{[1,3)}'::nummultirange * '{[1,5)}'::nummultirange;
+SELECT '{[1,3)}'::nummultirange * '{[0,5)}'::nummultirange;
+SELECT '{[1,3)}'::nummultirange * '{[0,2)}'::nummultirange;
+SELECT '{[1,3)}'::nummultirange * '{[2,5)}'::nummultirange;
+SELECT '{[1,4)}'::nummultirange * '{[2,3)}'::nummultirange;
+SELECT '{[1,4)}'::nummultirange * '{[0,2), [3,5)}'::nummultirange;
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[0,8), [9,12)}'::nummultirange;
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[9,12)}'::nummultirange;
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[-5,-4), [5,6), [9,12)}'::nummultirange;
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[0,2), [3,8), [9,12)}'::nummultirange;
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[0,2), [3,8), [9,12)}'::nummultirange;
+
+--
+-- range_agg function
+--
+create table reservations ( room_id integer not null, booked_during daterange );
+insert into reservations values
+-- 1: has a meets and a gap
+(1, daterange('2018-07-01', '2018-07-07')),
+(1, daterange('2018-07-07', '2018-07-14')),
+(1, daterange('2018-07-20', '2018-07-22')),
+-- 2: just a single row
+(2, daterange('2018-07-01', '2018-07-03')),
+-- 3: one null range
+(3, NULL),
+-- 4: two null ranges
+(4, NULL),
+(4, NULL),
+-- 5: a null range and a non-null range
+(5, NULL),
+(5, daterange('2018-07-01', '2018-07-03')),
+-- 6: has overlap
+(6, daterange('2018-07-01', '2018-07-07')),
+(6, daterange('2018-07-05', '2018-07-10')),
+-- 7: two ranges that meet: no gap or overlap
+(7, daterange('2018-07-01', '2018-07-07')),
+(7, daterange('2018-07-07', '2018-07-14')),
+-- 8: an empty range
+(8, 'empty'::daterange)
+;
+SELECT   room_id, range_agg(booked_during)
+FROM     reservations
+GROUP BY room_id
+ORDER BY room_id;
+
+-- range_agg on a custom range type too
+SELECT  range_agg(r)
+FROM    (VALUES
+          ('[a,c]'::textrange),
+          ('[b,b]'::textrange),
+          ('[c,f]'::textrange),
+          ('[g,h)'::textrange),
+          ('[h,j)'::textrange)
+        ) t(r);
+
+select range_intersect_agg(nmr) from nummultirange_test;
+select range_intersect_agg(nmr) from nummultirange_test where false;
+-- test with just one input:
+select range_intersect_agg(nmr) from (values ('{[1,2]}'::nummultirange)) t(nmr);
+select range_intersect_agg(nmr) from nummultirange_test where nmr @> 4.0;
+
+create table nummultirange_test2(nmr nummultirange);
+create index nummultirange_test2_hash_idx on nummultirange_test2 using hash (nmr);
+
+INSERT INTO nummultirange_test2 VALUES('{[, 5)}');
+INSERT INTO nummultirange_test2 VALUES(nummultirange(numrange(1.1, 2.2)));
+INSERT INTO nummultirange_test2 VALUES(nummultirange(numrange(1.1, 2.2)));
+INSERT INTO nummultirange_test2 VALUES(nummultirange(numrange(1.1, 2.2,'()')));
+INSERT INTO nummultirange_test2 VALUES('{}');
+
+select * from nummultirange_test2 where nmr = '{}';
+select * from nummultirange_test2 where nmr = nummultirange(numrange(1.1, 2.2));
+select * from nummultirange_test2 where nmr = nummultirange(numrange(1.1, 2.3));
+
+set enable_nestloop=t;
+set enable_hashjoin=f;
+set enable_mergejoin=f;
+select * from nummultirange_test natural join nummultirange_test2 order by nmr;
+set enable_nestloop=f;
+set enable_hashjoin=t;
+set enable_mergejoin=f;
+select * from nummultirange_test natural join nummultirange_test2 order by nmr;
+set enable_nestloop=f;
+set enable_hashjoin=f;
+set enable_mergejoin=t;
+select * from nummultirange_test natural join nummultirange_test2 order by nmr;
+
+set enable_nestloop to default;
+set enable_hashjoin to default;
+set enable_mergejoin to default;
+
+DROP TABLE nummultirange_test2;
+
+--
+-- Test user-defined multirange of floats
+--
+
+select '{[123.001, 5.e9)}'::float8multirange @> 888.882::float8;
+create table float8multirange_test(f8mr float8multirange, i int);
+insert into float8multirange_test values(float8multirange(float8range(-100.00007, '1.111113e9')), 42);
+select * from float8multirange_test;
+drop table float8multirange_test;
+
+--
+-- Test multirange types over domains
+--
+
+create domain mydomain as int4;
+create type mydomainrange as range(subtype=mydomain);
+select '{[4,50)}'::mydomainmultirange @> 7::mydomain;
+drop domain mydomain cascade;
+
+--
+-- Test domains over multirange types
+--
+
+create domain restrictedmultirange as int4multirange check (upper(value) < 10);
+select '{[4,5)}'::restrictedmultirange @> 7;
+select '{[4,50)}'::restrictedmultirange @> 7; -- should fail
+drop domain restrictedmultirange;
+
+--
+-- Test multiple multirange types over the same subtype
+--
+
+create type textrange1 as range(subtype=text, collation="C");
+create type textrange2 as range(subtype=text, collation="C");
+
+select textmultirange1(textrange2('a','Z'));  -- should fail
+select textmultirange1(textrange1('a','Z')) @> 'b'::text;
+select textmultirange2(textrange2('a','z')) @> 'b'::text;
+
+drop type textrange1;
+drop type textrange2;
+
+--
+-- Test polymorphic type system
+--
+
+create function anyarray_anymultirange_func(a anyarray, r anymultirange)
+  returns anyelement as 'select $1[1] + lower($2);' language sql;
+
+select anyarray_anymultirange_func(ARRAY[1,2], int4multirange(int4range(10,20)));
+
+-- should fail
+select anyarray_anymultirange_func(ARRAY[1,2], nummultirange(numrange(10,20)));
+
+drop function anyarray_anymultirange_func(anyarray, anymultirange);
+
+-- should fail
+create function bogus_func(anyelement)
+  returns anymultirange as 'select int4multirange(int4range(1,10))' language sql;
+
+-- should fail
+create function bogus_func(int)
+  returns anymultirange as 'select int4multirange(int4range(1,10))' language sql;
+
+create function range_add_bounds(anymultirange)
+  returns anyelement as 'select lower($1) + upper($1)' language sql;
+
+select range_add_bounds(int4multirange(int4range(1, 17)));
+select range_add_bounds(nummultirange(numrange(1.0001, 123.123)));
+
+create function multirangetypes_sql(q anymultirange, b anyarray, out c anyelement)
+  as $$ select upper($1) + $2[1] $$
+  language sql;
+
+select multirangetypes_sql(int4multirange(int4range(1,10)), ARRAY[2,20]);
+select multirangetypes_sql(nummultirange(numrange(1,10)), ARRAY[2,20]);  -- match failure
+
+--
+-- Arrays of multiranges
+--
+
+select ARRAY[nummultirange(numrange(1.1, 1.2)), nummultirange(numrange(12.3, 155.5))];
+
+create table i8mr_array (f1 int, f2 int8multirange[]);
+insert into i8mr_array values (42, array[int8multirange(int8range(1,10)), int8multirange(int8range(2,20))]);
+select * from i8mr_array;
+drop table i8mr_array;
+
+--
+-- Multiranges of arrays
+--
+
+select arraymultirange(arrayrange(ARRAY[1,2], ARRAY[2,1]));
+select arraymultirange(arrayrange(ARRAY[2,1], ARRAY[1,2]));  -- fail
+
+select array[1,1] <@ arraymultirange(arrayrange(array[1,2], array[2,1]));
+select array[1,3] <@ arraymultirange(arrayrange(array[1,2], array[2,1]));
+
+--
+-- Ranges of composites
+--
+
+create type two_ints as (a int, b int);
+create type two_ints_range as range (subtype = two_ints);
+
+-- with force_parallel_mode on, this exercises tqueue.c's range remapping
+select *, row_to_json(upper(t)) as u from
+  (values (two_ints_multirange(two_ints_range(row(1,2), row(3,4)))),
+          (two_ints_multirange(two_ints_range(row(5,6), row(7,8))))) v(t);
+
+drop type two_ints cascade;
+
+--
+-- Check behavior when subtype lacks a hash function
+--
+
+set enable_sort = off;  -- try to make it pick a hash setop implementation
+
+select '{(2,5)}'::cashmultirange except select '{(5,6)}'::cashmultirange;
+
+reset enable_sort;
+
+--
+-- OUT/INOUT/TABLE functions
+--
+
+-- infer anymultirange from anymultirange
+create function mr_outparam_succeed(i anymultirange, out r anymultirange, out t text)
+  as $$ select $1, 'foo'::text $$ language sql;
+
+select * from mr_outparam_succeed(int4multirange(int4range(1,2)));
+
+-- infer anyarray from anymultirange
+create function mr_outparam_succeed2(i anymultirange, out r anyarray, out t text)
+  as $$ select ARRAY[upper($1)], 'foo'::text $$ language sql;
+
+select * from mr_outparam_succeed2(int4multirange(int4range(1,2)));
+
+-- infer anyrange from anymultirange
+create function mr_outparam_succeed3(i anymultirange, out r anyrange, out t text)
+  as $$ select range_merge($1), 'foo'::text $$ language sql;
+select * from mr_outparam_succeed3(int4multirange(int4range(1,2)));
+
+-- infer anymultirange from anyrange
+create function mr_outparam_succeed4(i anyrange, out r anymultirange, out t text)
+  as $$ select multirange($1), 'foo'::text $$ language sql;
+
+select * from mr_outparam_succeed4(int4range(1,2));
+
+-- infer anyelement from anymultirange
+create function mr_inoutparam_succeed(out i anyelement, inout r anymultirange)
+  as $$ select upper($1), $1 $$ language sql;
+
+select * from mr_inoutparam_succeed(int4multirange(int4range(1,2)));
+
+-- infer anyelement+anymultirange from anyelement+anymultirange
+create function mr_table_succeed(i anyelement, r anymultirange) returns table(i anyelement, r anymultirange)
+  as $$ select $1, $2 $$ language sql;
+
+select * from mr_table_succeed(123, int4multirange(int4range(1,11)));
+
+-- use anymultirange in plpgsql
+create function mr_polymorphic(i anyrange) returns anymultirange
+  as $$ begin return multirange($1); end; $$ language plpgsql;
+select mr_polymorphic(int4range(1, 4));
+
+-- should fail
+create function mr_outparam_fail(i anyelement, out r anymultirange, out t text)
+  as $$ select '[1,10]', 'foo' $$ language sql;
+
+--should fail
+create function mr_inoutparam_fail(inout i anyelement, out r anymultirange)
+  as $$ select $1, '[1,10]' $$ language sql;
+
+--should fail
+create function mr_table_fail(i anyelement) returns table(i anyelement, r anymultirange)
+  as $$ select $1, '[1,10]' $$ language sql;
diff --git a/src/test/regress/sql/opr_sanity.sql b/src/test/regress/sql/opr_sanity.sql
index f06f245db3..4110a56ad2 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/rangetypes.sql b/src/test/regress/sql/rangetypes.sql
index 85eaa9b34c..825f56fd88 100644
--- a/src/test/regress/sql/rangetypes.sql
+++ b/src/test/regress/sql/rangetypes.sql
@@ -118,6 +118,10 @@ select numrange(1.0, 2.0) * numrange(2.0, 3.0);
 select numrange(1.0, 2.0) * numrange(1.5, 3.0);
 select numrange(1.0, 2.0) * numrange(2.5, 3.0);
 
+select range_intersect_agg(nr) from numrange_test;
+select range_intersect_agg(nr) from numrange_test where false;
+select range_intersect_agg(nr) from numrange_test where nr @> 4.0;
+
 create table numrange_test2(nr numrange);
 create index numrange_test2_hash_idx on numrange_test2 using hash (nr);
 
@@ -512,6 +516,7 @@ reset enable_sort;
 -- OUT/INOUT/TABLE functions
 --
 
+-- infer anyrange from anyrange
 create function outparam_succeed(i anyrange, out r anyrange, out t text)
   as $$ select $1, 'foo'::text $$ language sql;
 
@@ -523,6 +528,13 @@ create function outparam2_succeed(r anyrange, out lu anyarray, out ul anyarray)
 
 select * from outparam2_succeed(int4range(1,11));
 
+-- infer anyarray from anyrange
+create function outparam_succeed2(i anyrange, out r anyarray, out t text)
+  as $$ select ARRAY[upper($1)], 'foo'::text $$ language sql;
+
+select * from outparam_succeed2(int4range(int4range(1,2)));
+
+-- infer anyelement from anyrange
 create function inoutparam_succeed(out i anyelement, inout r anyrange)
   as $$ select upper($1), $1 $$ language sql;
 
diff --git a/src/test/regress/sql/type_sanity.sql b/src/test/regress/sql/type_sanity.sql
index fed413bf98..2e34bc8b65 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.rngmultitypid
+FROM pg_range p1
+WHERE p1.rngmultitypid IS NULL OR p1.rngmultitypid = 0;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index e216de9570..c23ce28af1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1354,6 +1354,9 @@ MultiXactMember
 MultiXactOffset
 MultiXactStateData
 MultiXactStatus
+MultirangeIOData
+MultirangeParseState
+MultirangeType
 MyData
 NDBOX
 NODE
#112Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Paul A Jungwirth (#111)
Re: range_agg

On 2020-Mar-16, Paul A Jungwirth wrote:

On Sat, Mar 14, 2020 at 11:13 AM Paul A Jungwirth
<pj@illuminatedcomputing.com> wrote:

I think that should fix the cfbot failure.

I saw this patch was failing to apply again. There was some
refactoring to how polymorphic types are determined. I added my
changes for anymultirange to that new approach, and things should be
passing again.

There's been another flurry of commits in the polymorphic types area.
Can you please rebase again?

--
�lvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

#113Paul A Jungwirth
pj@illuminatedcomputing.com
In reply to: Alvaro Herrera (#112)
Re: range_agg

On Thu, Mar 19, 2020 at 1:42 PM Alvaro Herrera <alvherre@2ndquadrant.com> wrote:

On 2020-Mar-16, Paul A Jungwirth wrote:

On Sat, Mar 14, 2020 at 11:13 AM Paul A Jungwirth
<pj@illuminatedcomputing.com> wrote:

I think that should fix the cfbot failure.

I saw this patch was failing to apply again. There was some
refactoring to how polymorphic types are determined. I added my
changes for anymultirange to that new approach, and things should be
passing again.

There's been another flurry of commits in the polymorphic types area.
Can you please rebase again?

I noticed that too. :-) I'm about halfway through a rebase right now.
I can probably finish it up tonight.

Paul

#114Paul A Jungwirth
pj@illuminatedcomputing.com
In reply to: Paul A Jungwirth (#113)
1 attachment(s)
Re: range_agg

On Thu, Mar 19, 2020 at 1:43 PM Paul A Jungwirth
<pj@illuminatedcomputing.com> wrote:

On Thu, Mar 19, 2020 at 1:42 PM Alvaro Herrera <alvherre@2ndquadrant.com> wrote:

There's been another flurry of commits in the polymorphic types area.
Can you please rebase again?

I noticed that too. :-) I'm about halfway through a rebase right now.
I can probably finish it up tonight.

Here is that patch. I should probably add an anycompatiblemultirange
type now too? I'll get started on that tomorrow.

Regards,
Paul

Attachments:

v15-multirange.patchapplication/octet-stream; name=v15-multirange.patchDownload
diff --git a/doc/src/sgml/extend.sgml b/doc/src/sgml/extend.sgml
index 930aeb767c..febb02e80a 100644
--- a/doc/src/sgml/extend.sgml
+++ b/doc/src/sgml/extend.sgml
@@ -286,6 +286,14 @@
        </row>
 
        <row>
+        <entry><type>anymultirange</type></entry>
+        <entry>Simple</entry>
+        <entry>Indicates that a function accepts any multirange data type
+        (see <xref linkend="rangetypes"/>)
+        </entry>
+       </row>
+
+       <row>
         <entry><type>anycompatible</type></entry>
         <entry>Common</entry>
         <entry>Indicates that a function accepts any data type,
@@ -343,17 +351,15 @@
      position declared as <type>anyarray</type> can have any array data type,
      but similarly they must all be the same type.  And similarly,
      positions declared as <type>anyrange</type> must all be the same range
-     type.  Furthermore, if there are
+     type.  Likewise for <type>anymultirange</type>.
+    </para>
+
+    <para>
+     Furthermore, if there are
      positions declared <type>anyarray</type> and others declared
      <type>anyelement</type>, the actual array type in the
      <type>anyarray</type> positions must be an array whose elements are
      the same type appearing in the <type>anyelement</type> positions.
-     Similarly, if there are positions declared <type>anyrange</type>
-     and others declared <type>anyelement</type> or <type>anyarray</type>,
-     the actual range type in the <type>anyrange</type> positions must be a
-     range whose subtype is the same type appearing in
-     the <type>anyelement</type> positions and the same as the element type
-     of the <type>anyarray</type> positions.
      <type>anynonarray</type> is treated exactly the same as <type>anyelement</type>,
      but adds the additional constraint that the actual type must not be
      an array type.
@@ -363,6 +369,19 @@
     </para>
 
     <para>
+     Similarly, if there are positions declared <type>anyrange</type>
+     and others declared <type>anyelement</type> or <type>anyarray</type>,
+     the actual range type in the <type>anyrange</type> positions must be a
+     range whose subtype is the same type appearing in
+     the <type>anyelement</type> positions and the same as the element type
+     of the <type>anyarray</type> positions.
+     If there are positions declared <type>anymultirange</type>,
+     their actual multirange type must contain ranges matching parameters declared
+     <type>anyrange</type> and base elements matching parameters declared
+     <type>anyelement</type> and <type>anyarray</type>.
+    </para>
+
+    <para>
      Thus, when more than one argument position is declared with a polymorphic
      type, the net effect is that only certain combinations of actual argument
      types are allowed.  For example, a function declared as
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 464a48ed6a..cfcafa5b9e 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -13546,7 +13546,7 @@ SELECT NULLIF(value, '(none)') ...
        <row>
         <entry>Operator</entry>
         <entry>Description</entry>
-        <entry>Example</entry>
+        <entry>Examples</entry>
         <entry>Result</entry>
        </row>
       </thead>
@@ -14017,12 +14017,14 @@ NULL baz</literallayout>(3 rows)</entry>
   <title>Range Functions and Operators</title>
 
   <para>
-   See <xref linkend="rangetypes"/> for an overview of range types.
+   See <xref linkend="rangetypes"/> for an overview of range and multirange types.
   </para>
 
   <para>
    <xref linkend="range-operators-table"/> shows the operators
-   available for range types.
+   available for range and multirange types.
+   Many of these operators will accept either a range or multirange
+   on either side.
   </para>
 
     <table id="range-operators-table">
@@ -14040,134 +14042,215 @@ NULL baz</literallayout>(3 rows)</entry>
        <row>
         <entry> <literal>=</literal> </entry>
         <entry>equal</entry>
-        <entry><literal>int4range(1,5) = '[1,4]'::int4range</literal></entry>
-        <entry><literal>t</literal></entry>
+        <entry><literallayout class="monospaced">int4range(1,5) = '[1,4]'::int4range
+'{[1,5)}'::int4multirange = '{[1,4]}'::int4multirange</literallayout></entry>
+        <entry><literallayout class="monospaced">t</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>&lt;&gt;</literal> </entry>
         <entry>not equal</entry>
-        <entry><literal>numrange(1.1,2.2) &lt;&gt; numrange(1.1,2.3)</literal></entry>
-        <entry><literal>t</literal></entry>
+        <entry><literallayout class="monospaced">numrange(1.1,2.2) &lt;&gt; numrange(1.1,2.3)
+'{[1.1,2.2)}'::nummultirange &lt;&gt; '{[1.1,2.3)}'::nummultirange</literallayout></entry>
+        <entry><literallayout class="monospaced">t</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>&lt;</literal> </entry>
         <entry>less than</entry>
-        <entry><literal>int4range(1,10) &lt; int4range(2,3)</literal></entry>
-        <entry><literal>t</literal></entry>
+        <entry><literallayout class="monospaced">int4range(1,10) &lt; int4range(2,3)
+'{[1,10)}'::int4multirange &lt; '{[2,3)}'::int4multirange</literallayout>
+        </entry>
+        <entry><literallayout class="monospaced">t</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>&gt;</literal> </entry>
         <entry>greater than</entry>
-        <entry><literal>int4range(1,10) &gt; int4range(1,5)</literal></entry>
-        <entry><literal>t</literal></entry>
+        <entry>
+          <literallayout class="monospaced">int4range(1,10) &gt; int4range(1,5)
+'{[1,10)}'::int4multirange &gt; '{[1,5)}'::int4multirange</literallayout>
+        </entry>
+        <entry><literallayout class="monospaced">t</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>&lt;=</literal> </entry>
         <entry>less than or equal</entry>
-        <entry><literal>numrange(1.1,2.2) &lt;= numrange(1.1,2.2)</literal></entry>
-        <entry><literal>t</literal></entry>
+        <entry>
+          <literallayout class="monospaced">numrange(1.1,2.2) &lt;= numrange(1.1,2.2)
+'{[1.1,2.2)}'::nummultirange &lt;= '{[1.1,2.2)}'::nummultirange</literallayout>
+        </entry>
+        <entry><literallayout class="monospaced">t</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>&gt;=</literal> </entry>
         <entry>greater than or equal</entry>
-        <entry><literal>numrange(1.1,2.2) &gt;= numrange(1.1,2.0)</literal></entry>
-        <entry><literal>t</literal></entry>
+        <entry>
+          <literallayout class="monospaced">numrange(1.1,2.2) &gt;= numrange(1.1,2.0)
+'{[1.1,2.2)}'::nummultirange &gt;= '{[1.1,2.0)}'::nummultirange</literallayout>
+        </entry>
+        <entry><literallayout class="monospaced">t</literallayout></entry>
+       </row>
+
+       <row>
+        <entry> <literal>@&gt;</literal> </entry>
+        <entry>contains multirange</entry>
+        <entry>
+          <literallayout class="monospaced">int4range(2,4) @&gt; '{[2,3)}'::int4multirange
+'{[2,4)}'::int4multirange @&gt; '{[2,3)}'::int4multirange</literallayout>
+        </entry>
+        <entry><literallayout class="monospaced">t</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>@&gt;</literal> </entry>
         <entry>contains range</entry>
-        <entry><literal>int4range(2,4) @&gt; int4range(2,3)</literal></entry>
-        <entry><literal>t</literal></entry>
+        <entry>
+          <literallayout class="monospaced">int4range(2,4) @&gt; int4range(2,3)
+'{[2,4)}'::int4multirange @&gt; int4range(2,3)</literallayout>
+        </entry>
+        <entry><literallayout class="monospaced">t</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>@&gt;</literal> </entry>
         <entry>contains element</entry>
-        <entry><literal>'[2011-01-01,2011-03-01)'::tsrange @&gt; '2011-01-10'::timestamp</literal></entry>
-        <entry><literal>t</literal></entry>
+        <entry>
+          <literallayout class="monospaced">'[2011-01-01,2011-03-01)'::tsrange @&gt; '2011-01-10'::timestamp
+'{[2011-01-01,2011-03-01)}'::tsmultirange @&gt; '2011-01-10'::timestamp</literallayout>
+        </entry>
+        <entry><literallayout class="monospaced">t</literallayout></entry>
+       </row>
+
+       <row>
+        <entry> <literal>&lt;@</literal> </entry>
+        <entry>multirange is contained by</entry>
+        <entry>
+          <literallayout class="monospaced">'{[2,4)}'::int4multirange &lt;@ int4range(1,7)
+'{[2,4)}'::int4multirange &lt;@ '{[1,7)}'::int4multirange</literallayout>
+        </entry>
+        <entry><literallayout class="monospaced">t</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>&lt;@</literal> </entry>
         <entry>range is contained by</entry>
-        <entry><literal>int4range(2,4) &lt;@ int4range(1,7)</literal></entry>
-        <entry><literal>t</literal></entry>
+        <entry>
+          <literallayout class="monospaced">int4range(2,4) &lt;@ int4range(1,7)
+int4range(2,4) &lt;@ '{[1,7)}'::int4multirange</literallayout>
+        </entry>
+        <entry><literallayout class="monospaced">t</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>&lt;@</literal> </entry>
         <entry>element is contained by</entry>
-        <entry><literal>42 &lt;@ int4range(1,7)</literal></entry>
-        <entry><literal>f</literal></entry>
+        <entry>
+          <literallayout class="monospaced">42 &lt;@ int4range(1,7)
+42 &lt;@ '{[1,7)}'::int4multirange</literallayout>
+        </entry>
+        <entry><literallayout class="monospaced">f</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>&amp;&amp;</literal> </entry>
         <entry>overlap (have points in common)</entry>
-        <entry><literal>int8range(3,7) &amp;&amp; int8range(4,12)</literal></entry>
-        <entry><literal>t</literal></entry>
+        <entry>
+          <literallayout class="monospaced">int8range(3,7) &amp;&amp; int8range(4,12)
+int8range(3,7) &amp;&amp; '{[4,12)}'::int8multirange
+'{[3,7)}'::int8multirange &amp;&amp; int8range(4,12)
+'{[3,7)}'::int8multirange &amp;&amp; '{[4,12)}'::int8multirange</literallayout>
+        </entry>
+        <entry><literallayout class="monospaced">t</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>&lt;&lt;</literal> </entry>
         <entry>strictly left of</entry>
-        <entry><literal>int8range(1,10) &lt;&lt; int8range(100,110)</literal></entry>
-        <entry><literal>t</literal></entry>
+        <entry>
+          <literallayout class="monospaced">int8range(1,10) &lt;&lt; int8range(100,110)
+int8range(1,10) &lt;&lt; '{[100,110)}'::int8multirange
+'{[1,10)}'::int8multirange &lt;&lt; int8range(100,110)
+'{[1,10)}'::int8multirange &lt;&lt; '{[100,110)}'::int8multirange</literallayout>
+        </entry>
+        <entry><literallayout class="monospaced">t</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>&gt;&gt;</literal> </entry>
         <entry>strictly right of</entry>
-        <entry><literal>int8range(50,60) &gt;&gt; int8range(20,30)</literal></entry>
-        <entry><literal>t</literal></entry>
+        <entry>
+          <literallayout class="monospaced">int8range(50,60) &gt;&gt; int8range(20,30)
+int8range(50,60) &gt;&gt; '{[20,30)}'::int8multirange
+'{[50,60)}'::int8multirange &gt;&gt; int8range(20,30)
+'{[50,60)}'::int8multirange &gt;&gt; '{[20,30)}'::int8multirange</literallayout>
+        </entry>
+        <entry><literallayout class="monospaced">t</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>&amp;&lt;</literal> </entry>
         <entry>does not extend to the right of</entry>
-        <entry><literal>int8range(1,20) &amp;&lt; int8range(18,20)</literal></entry>
-        <entry><literal>t</literal></entry>
+        <entry>
+          <literallayout class="monospaced">int8range(1,20) &amp;&lt; int8range(18,20)
+int8range(1,20) &amp;&lt; '{[18,20)}'::int8multirange
+'{[1,20)}'::int8multirange &amp;&lt; int8range(18,20)
+'{[1,20)}'::int8multirange &amp;&lt; '{[18,20)}'::int8multirange</literallayout>
+        </entry>
+        <entry><literallayout class="monospaced">t</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>&amp;&gt;</literal> </entry>
         <entry>does not extend to the left of</entry>
-        <entry><literal>int8range(7,20) &amp;&gt; int8range(5,10)</literal></entry>
-        <entry><literal>t</literal></entry>
+        <entry>
+          <literallayout class="monospaced">int8range(7,20) &amp;&gt; int8range(5,10)
+int8range(7,20) &amp;&gt; '{[5,10)}'::int8multirange
+'{[7,20)}'::int8multirange &amp;&gt; int8range(5,10)
+'{[7,20)}'::int8multirange &amp;&gt; '{[5,10)}'::int8multirange</literallayout>
+        </entry>
+        <entry><literallayout class="monospaced">t</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>-|-</literal> </entry>
         <entry>is adjacent to</entry>
-        <entry><literal>numrange(1.1,2.2) -|- numrange(2.2,3.3)</literal></entry>
-        <entry><literal>t</literal></entry>
+        <entry>
+          <literallayout class="monospaced">numrange(1.1,2.2) -|- numrange(2.2,3.3)
+numrange(1.1,2.2) -|- '{[2.2,3.3)}'::nummultirange
+'{[1.1,2.2)}'::nummultirange -|- numrange(2.2,3.3)
+'{[1.1,2.2)}'::nummultirange -|- '{[2.2,3.3)}'::nummultirange</literallayout>
+        </entry>
+        <entry><literallayout class="monospaced">t</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>+</literal> </entry>
         <entry>union</entry>
-        <entry><literal>numrange(5,15) + numrange(10,20)</literal></entry>
-        <entry><literal>[5,20)</literal></entry>
+        <entry><literallayout class="monospaced">numrange(5,15) + numrange(10,20)
+'{[5,10)}'::nummultirange + '{[15,20)}'::nummultirange</literallayout></entry>
+        <entry><literallayout class="monospaced">[5,20)
+{[5,10), [15,20)}</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>*</literal> </entry>
         <entry>intersection</entry>
-        <entry><literal>int8range(5,15) * int8range(10,20)</literal></entry>
-        <entry><literal>[10,15)</literal></entry>
+        <entry><literallayout class="monospaced">int8range(5,15) * int8range(10,20)
+'{[5,15)}'::int8multirange * '{[10,20)}'::int8multirange</literallayout></entry>
+        <entry><literallayout class="monospaced">[10,15)
+{[10,15)}</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>-</literal> </entry>
         <entry>difference</entry>
-        <entry><literal>int8range(5,15) - int8range(10,20)</literal></entry>
-        <entry><literal>[5,10)</literal></entry>
+        <entry><literallayout class="monospaced">int8range(5,15) - int8range(10,20)
+'{[5,20)}'::int8multirange - '{[10,15)}'::int8multirange</literallayout></entry>
+        <entry><literallayout class="monospaced">[5,10)
+{[5,10), [15,20)}</literallayout></entry>
        </row>
 
       </tbody>
@@ -14185,19 +14268,31 @@ NULL baz</literallayout>(3 rows)</entry>
 
   <para>
    The left-of/right-of/adjacent operators always return false when an empty
-   range is involved; that is, an empty range is not considered to be either
-   before or after any other range.
+   range or multirange is involved; that is, an empty range is not considered to
+   be either before or after any other range.
+  </para>
+
+  <para>
+   Elsewhere empty ranges and multiranges are treated as the additive identity:
+   anything unioned with an empty value is itself. Anything minus an empty
+   value is itself. An empty multirange has exactly the same points as an empty
+   range. Every range contains the empty range. Every multirange contains as many
+   empty ranges as you like.
   </para>
 
   <para>
    The union and difference operators will fail if the resulting range would
    need to contain two disjoint sub-ranges, as such a range cannot be
-   represented.
+   represented. There are separate operators for union and difference that take
+   multirange parameters and return a multirange, and they do not fail even if
+   their arguments are disjoint. So if you need a union or difference operation
+   for ranges that may be disjoint, you can avoid errors by first casting your
+   ranges to multiranges.
   </para>
 
   <para>
    <xref linkend="range-functions-table"/> shows the functions
-   available for use with range types.
+   available for use with range and multirange types.
   </para>
 
   <indexterm>
@@ -14249,6 +14344,17 @@ NULL baz</literallayout>(3 rows)</entry>
        <row>
         <entry>
          <literal>
+          <function>lower</function>(<type>anymultirange</type>)
+         </literal>
+        </entry>
+        <entry>multirange's element type</entry>
+        <entry>lower bound of multirange</entry>
+        <entry><literal>lower('{[1.1,2.2)}'::nummultirange)</literal></entry>
+        <entry><literal>1.1</literal></entry>
+       </row>
+       <row>
+        <entry>
+         <literal>
           <function>upper</function>(<type>anyrange</type>)
          </literal>
         </entry>
@@ -14260,6 +14366,17 @@ NULL baz</literallayout>(3 rows)</entry>
        <row>
         <entry>
          <literal>
+          <function>upper</function>(<type>anymultirange</type>)
+         </literal>
+        </entry>
+        <entry>multirange's element type</entry>
+        <entry>upper bound of multirange</entry>
+        <entry><literal>upper('{[1.1,2.2)}'::nummultirange)</literal></entry>
+        <entry><literal>2.2</literal></entry>
+       </row>
+       <row>
+        <entry>
+         <literal>
           <function>isempty</function>(<type>anyrange</type>)
          </literal>
         </entry>
@@ -14271,6 +14388,17 @@ NULL baz</literallayout>(3 rows)</entry>
        <row>
         <entry>
          <literal>
+          <function>isempty</function>(<type>anymultirange</type>)
+         </literal>
+        </entry>
+        <entry><type>boolean</type></entry>
+        <entry>is the multirange empty?</entry>
+        <entry><literal>isempty('{[1.1,2.2)}'::nummultirange)</literal></entry>
+        <entry><literal>false</literal></entry>
+       </row>
+       <row>
+        <entry>
+         <literal>
           <function>lower_inc</function>(<type>anyrange</type>)
          </literal>
         </entry>
@@ -14282,6 +14410,17 @@ NULL baz</literallayout>(3 rows)</entry>
        <row>
         <entry>
          <literal>
+          <function>lower_inc</function>(<type>anymultirange</type>)
+         </literal>
+        </entry>
+        <entry><type>boolean</type></entry>
+        <entry>is the lower bound inclusive?</entry>
+        <entry><literal>lower_inc('{[1.1,2.2)}'::nummultirange)</literal></entry>
+        <entry><literal>true</literal></entry>
+       </row>
+       <row>
+        <entry>
+         <literal>
           <function>upper_inc</function>(<type>anyrange</type>)
          </literal>
         </entry>
@@ -14293,6 +14432,17 @@ NULL baz</literallayout>(3 rows)</entry>
        <row>
         <entry>
          <literal>
+          <function>upper_inc</function>(<type>anymultirange</type>)
+         </literal>
+        </entry>
+        <entry><type>boolean</type></entry>
+        <entry>is the upper bound inclusive?</entry>
+        <entry><literal>upper_inc('{[1.1,2.2)}'::nummultirange)</literal></entry>
+        <entry><literal>false</literal></entry>
+       </row>
+       <row>
+        <entry>
+         <literal>
           <function>lower_inf</function>(<type>anyrange</type>)
          </literal>
         </entry>
@@ -14304,6 +14454,17 @@ NULL baz</literallayout>(3 rows)</entry>
        <row>
         <entry>
          <literal>
+          <function>lower_inf</function>(<type>anymultirange</type>)
+         </literal>
+        </entry>
+        <entry><type>boolean</type></entry>
+        <entry>is the lower bound infinite?</entry>
+        <entry><literal>lower_inf('{(,)}'::datemultirange)</literal></entry>
+        <entry><literal>true</literal></entry>
+       </row>
+       <row>
+        <entry>
+         <literal>
           <function>upper_inf</function>(<type>anyrange</type>)
          </literal>
         </entry>
@@ -14315,6 +14476,17 @@ NULL baz</literallayout>(3 rows)</entry>
        <row>
         <entry>
          <literal>
+          <function>upper_inf</function>(<type>anymultirange</type>)
+         </literal>
+        </entry>
+        <entry><type>boolean</type></entry>
+        <entry>is the upper bound infinite?</entry>
+        <entry><literal>upper_inf('{(,)}'::datemultirange)</literal></entry>
+        <entry><literal>true</literal></entry>
+       </row>
+       <row>
+        <entry>
+         <literal>
           <function>range_merge</function>(<type>anyrange</type>, <type>anyrange</type>)
          </literal>
         </entry>
@@ -14323,16 +14495,38 @@ NULL baz</literallayout>(3 rows)</entry>
         <entry><literal>range_merge('[1,2)'::int4range, '[3,4)'::int4range)</literal></entry>
         <entry><literal>[1,4)</literal></entry>
        </row>
+       <row>
+        <entry>
+         <literal>
+          <function>range_merge</function>(<type>anymultirange</type>)
+         </literal>
+        </entry>
+        <entry><type>anyrange</type></entry>
+        <entry>the smallest range which includes the entire multirange</entry>
+        <entry><literal>range_merge('{[1,2), [3,4)}'::int4multirange)</literal></entry>
+        <entry><literal>[1,4)</literal></entry>
+       </row>
+       <row>
+        <entry>
+         <literal>
+          <function>multirange</function>(<type>anyrange</type>)
+         </literal>
+        </entry>
+        <entry><type>anymultirange</type></entry>
+        <entry>a multirange containing just the given range</entry>
+        <entry><literal>multirange('[1,2)'::int4range)</literal></entry>
+        <entry><literal>{[1,2)}</literal></entry>
+       </row>
       </tbody>
      </tgroup>
     </table>
 
   <para>
    The <function>lower</function> and  <function>upper</function> functions return null
-   if the range is empty or the requested bound is infinite.
+   if the input range/multirange is empty or the requested bound is infinite.
    The <function>lower_inc</function>, <function>upper_inc</function>,
    <function>lower_inf</function>, and <function>upper_inf</function>
-   functions all return false for an empty range.
+   functions all return false for an empty range or multirange.
   </para>
   </sect1>
 
@@ -14654,6 +14848,44 @@ NULL baz</literallayout>(3 rows)</entry>
      <row>
       <entry>
        <indexterm>
+        <primary>range_agg</primary>
+       </indexterm>
+       <function>
+         range_agg(<replaceable class="parameter">expression</replaceable>)
+       </function>
+      </entry>
+      <entry>
+       <type>anyrange</type>
+      </entry>
+      <entry>
+       <type>multirange</type> of the argument range type
+      </entry>
+      <entry>Yes</entry>
+      <entry>union of the non-null input values</entry>
+     </row>
+
+     <row>
+      <entry>
+       <indexterm>
+        <primary>range_intersect_agg</primary>
+       </indexterm>
+       <function>
+         range_intersect_agg(<replaceable class="parameter">expression</replaceable>)
+       </function>
+      </entry>
+      <entry>
+       <type>anyrange</type>
+      </entry>
+      <entry>
+       <type>multirange</type> of the argument range type
+      </entry>
+      <entry>No</entry>
+      <entry>intersection of the non-null input values</entry>
+     </row>
+
+     <row>
+      <entry>
+       <indexterm>
         <primary>string_agg</primary>
        </indexterm>
        <function>
diff --git a/doc/src/sgml/rangetypes.sgml b/doc/src/sgml/rangetypes.sgml
index b75fb3a392..c8784bdce3 100644
--- a/doc/src/sgml/rangetypes.sgml
+++ b/doc/src/sgml/rangetypes.sgml
@@ -27,6 +27,13 @@
   ranges from an instrument, and so forth can also be useful.
  </para>
 
+ <para>
+  Every range type has a corresponding multirange type. A multirange is
+  an ordered list of non-continguous, non-empty, non-null ranges. Most
+  range operators also work on multiranges, and they have a few functions
+  of their own.
+ </para>
+
  <sect2 id="rangetypes-builtin">
   <title>Built-in Range Types</title>
 
@@ -232,10 +239,30 @@ SELECT '[4,4]'::int4range;
 SELECT '[4,4)'::int4range;
 </programlisting>
   </para>
+
+  <para>
+   The input for a multirange is curly brackets (<literal>{</literal> and
+   <literal>}</literal>) containing zero or more valid ranges,
+   separated by commas. Whitespace is permitted around the brackets and
+   commas. This is intended to be reminiscent of array syntax, although
+   multiranges are much simpler: they have just one dimension and there is
+   no need to quote their contents. (The bounds of their ranges may be
+   quoted as above however.)
+  </para>
+
+  <para>
+  Examples:
+<programlisting>
+SELECT '{}'::int4multirange;
+SELECT '{[3,7)}'::int4multirange;
+SELECT '{[3,7), [8,9)}'::int4multirange;
+</programlisting>
+  </para>
+
  </sect2>
 
  <sect2 id="rangetypes-construct">
-  <title>Constructing Ranges</title>
+  <title>Constructing Ranges and Multiranges</title>
 
   <para>
    Each range type has a constructor function with the same name as the range
@@ -269,6 +296,19 @@ SELECT int8range(1, 14, '(]');
 SELECT numrange(NULL, 2.2);
 </programlisting>
   </para>
+
+  <para>
+   Each range type also has a multirange constructor with the same name as the
+   multirange type.  The constructor function takes zero or more arguments
+   which are all ranges of the appropriate type.
+   For example:
+
+<programlisting>
+SELECT nummultirange();
+SELECT nummultirange(numrange(1.0, 14.0));
+SELECT nummultirange(numrange(1.0, 14.0), numrange(20.0, 25.0));
+</programlisting>
+  </para>
  </sect2>
 
  <sect2 id="rangetypes-discrete">
@@ -342,6 +382,11 @@ SELECT '[1.234, 5.678]'::floatrange;
   </para>
 
   <para>
+   When you define your own range you automatically get a corresponding
+   multirange type.
+  </para>
+
+  <para>
    Defining your own range type also allows you to specify a different
    subtype B-tree operator class or collation to use, so as to change the sort
    ordering that determines which values fall into a given range.
diff --git a/src/backend/catalog/pg_range.c b/src/backend/catalog/pg_range.c
index b5bc36c2bd..1217a6ddd0 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_rngmultitypid - 1] = ObjectIdGetDatum(multirangeTypeOid);
 
 	tup = heap_form_tuple(RelationGetDescr(pg_range), values, nulls);
 
@@ -100,6 +102,12 @@ 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 cd56714968..c359f27c74 100644
--- a/src/backend/catalog/pg_type.c
+++ b/src/backend/catalog/pg_type.c
@@ -27,6 +27,7 @@
 #include "catalog/pg_proc.h"
 #include "catalog/pg_type.h"
 #include "commands/typecmds.h"
+#include "mb/pg_wchar.h"
 #include "miscadmin.h"
 #include "parser/scansup.h"
 #include "utils/acl.h"
@@ -36,6 +37,9 @@
 #include "utils/rel.h"
 #include "utils/syscache.h"
 
+static char *makeUniqueTypeName(const char *typeName, Oid typeNamespace,
+								bool tryOriginal);
+
 /* Potentially set by pg_upgrade_support functions */
 Oid			binary_upgrade_next_pg_type_oid = InvalidOid;
 
@@ -809,31 +813,10 @@ RenameTypeInternal(Oid typeOid, const char *newTypeName, Oid typeNamespace)
 char *
 makeArrayTypeName(const char *typeName, Oid typeNamespace)
 {
-	char	   *arr = (char *) palloc(NAMEDATALEN);
-	int			namelen = strlen(typeName);
-	int			i;
-
-	/*
-	 * 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;
-	}
+	char	   *arr;
 
-	if (i >= NAMEDATALEN - 1)
+	arr = makeUniqueTypeName(typeName, typeNamespace, false);
+	if (arr == NULL)
 		ereport(ERROR,
 				(errcode(ERRCODE_DUPLICATE_OBJECT),
 				 errmsg("could not form array type name for type \"%s\"",
@@ -904,3 +887,91 @@ moveArrayTypeName(Oid typeOid, const char *typeName, Oid typeNamespace)
 
 	return true;
 }
+
+
+/*
+ * makeMultirangeTypeName
+ *	  - given a range type name, make a multirange type name for it
+ *
+ * caller is responsible for pfreeing the result
+ */
+char *
+makeMultirangeTypeName(const char *rangeTypeName, Oid typeNamespace)
+{
+	char	   *buf;
+	char	   *mrname;
+	char	   *rangestr;
+
+	/*
+	 * 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...
+	 */
+	rangestr = strstr(rangeTypeName, "range");
+	if (rangestr)
+	{
+		char	*prefix = pnstrdup(rangeTypeName, rangestr - rangeTypeName);
+
+		buf = psprintf("%s%s%s", prefix, "multi", rangestr);
+	}
+	else
+		buf = psprintf("%s_multirange", pnstrdup(rangeTypeName, NAMEDATALEN - 12));
+
+	/* clip it at NAMEDATALEN-1 bytes */
+	buf[pg_mbcliplen(buf, strlen(buf), NAMEDATALEN - 1)] = '\0';
+
+	mrname = makeUniqueTypeName(buf, typeNamespace, true);
+	if (mrname == NULL)
+		ereport(ERROR,
+				(errcode(ERRCODE_DUPLICATE_OBJECT),
+				 errmsg("could not form multirange type name for type \"%s\"",
+						rangeTypeName)));
+
+	return mrname;
+}
+
+/*
+ * makeUniqueTypeName
+ *		Generate a unique name for a prospective new type
+ *
+ * Given a typeName, return a new palloc'ed name by preprending underscores
+ * until a non-conflicting name results.
+ *
+ * If tryOriginal, first try with zero underscores.
+ */
+static char *
+makeUniqueTypeName(const char *typeName, Oid typeNamespace, bool tryOriginal)
+{
+	int			i;
+	int			namelen;
+	char		dest[NAMEDATALEN];
+
+	Assert(strlen(typeName) <= NAMEDATALEN - 1);
+
+	if (tryOriginal &&
+		!SearchSysCacheExists2(TYPENAMENSP,
+							   CStringGetDatum(typeName),
+							   ObjectIdGetDatum(typeNamespace)))
+		return pstrdup(typeName);
+
+	/*
+	 * The idea is to prepend underscores as needed until we make a name that
+	 * doesn't collide with anything ...
+	 */
+	namelen = strlen(typeName);
+	for (i = 1; i < NAMEDATALEN - 1; i++)
+	{
+		dest[i - 1] = '_';
+		strlcpy(dest + i, typeName, NAMEDATALEN - i);
+		if (namelen + i >= NAMEDATALEN)
+			truncate_identifier(dest, NAMEDATALEN, false);
+
+		if (!SearchSysCacheExists2(TYPENAMENSP,
+								   CStringGetDatum(dest),
+								   ObjectIdGetDatum(typeNamespace)))
+			return pstrdup(dest);
+	}
+
+	return NULL;
+}
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index 8891b1d564..dfb2edd3d0 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -105,9 +105,14 @@ typedef struct
 
 /* Potentially set by pg_upgrade_support functions */
 Oid			binary_upgrade_next_array_pg_type_oid = InvalidOid;
+Oid			binary_upgrade_next_mrng_pg_type_oid = InvalidOid;
+Oid			binary_upgrade_next_mrng_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 rangeOid,
+									   Oid rangeArrayOid, Oid *castFuncOid);
 static Oid	findTypeInputFunction(List *procname, Oid typeOid);
 static Oid	findTypeOutputFunction(List *procname, Oid typeOid);
 static Oid	findTypeReceiveFunction(List *procname, Oid typeOid);
@@ -738,7 +743,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",
@@ -1279,6 +1285,11 @@ checkEnumOwner(HeapTuple tup)
 /*
  * DefineRange
  *		Registers a new range type.
+ *
+ * Perhaps it might be worthwhile to set pg_type.typelem to the base type,
+ * and likewise on multiranges to set it to the range type. But having a
+ * non-zero typelem is treated elsewhere as a synonym for being an array,
+ * and users might have queries with that same assumption.
  */
 ObjectAddress
 DefineRange(CreateRangeStmt *stmt)
@@ -1287,7 +1298,11 @@ DefineRange(CreateRangeStmt *stmt)
 	Oid			typeNamespace;
 	Oid			typoid;
 	char	   *rangeArrayName;
+	char	   *multirangeTypeName;
+	char	   *multirangeArrayName;
 	Oid			rangeArrayOid;
+	Oid			multirangeOid;
+	Oid			multirangeArrayOid;
 	Oid			rangeSubtype = InvalidOid;
 	List	   *rangeSubOpclassName = NIL;
 	List	   *rangeCollationName = NIL;
@@ -1304,6 +1319,8 @@ DefineRange(CreateRangeStmt *stmt)
 	AclResult	aclresult;
 	ListCell   *lc;
 	ObjectAddress address;
+	ObjectAddress mltrngaddress;
+	Oid			castFuncOid;
 
 	/* Convert list of names to a name and namespace */
 	typeNamespace = QualifiedNameGetCreationNamespace(stmt->typeName,
@@ -1452,8 +1469,10 @@ DefineRange(CreateRangeStmt *stmt)
 	/* alignment must be TYPALIGN_INT or TYPALIGN_DOUBLE for ranges */
 	alignment = (subtypalign == TYPALIGN_DOUBLE) ? TYPALIGN_DOUBLE : TYPALIGN_INT;
 
-	/* Allocate OID for array type */
+	/* Allocate OID for array type, its multirange, and its multirange array */
 	rangeArrayOid = AssignTypeArrayOid();
+	multirangeOid = AssignTypeMultirangeOid();
+	multirangeArrayOid = AssignTypeMultirangeArrayOid();
 
 	/* Create the pg_type entry */
 	address =
@@ -1491,9 +1510,46 @@ DefineRange(CreateRangeStmt *stmt)
 	Assert(typoid == InvalidOid || typoid == address.objectId);
 	typoid = address.objectId;
 
+	/* Create the multirange that goes with it */
+	multirangeTypeName = makeMultirangeTypeName(typeName, typeNamespace);
+
+	mltrngaddress =
+		TypeCreate(multirangeOid,	/* force assignment of this 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_RANGE,	/* type-category (range 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) */
+	Assert(multirangeOid == mltrngaddress.objectId);
+
 	/* Create the entry in pg_range */
 	RangeCreate(typoid, rangeSubtype, rangeCollation, rangeSubOpclass,
-				rangeCanonical, rangeSubtypeDiff);
+				rangeCanonical, rangeSubtypeDiff, multirangeOid);
 
 	/*
 	 * Create the array type that goes with it.
@@ -1534,8 +1590,53 @@ 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 */
+			   multirangeOid,	/* 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,
+							   multirangeOid, typoid, rangeArrayOid,
+							   &castFuncOid);
+
+	/* Create cast from the range type to its multirange type */
+	CastCreate(typoid, multirangeOid, castFuncOid, 'e', 'f', DEPENDENCY_INTERNAL);
+
+	pfree(multirangeTypeName);
+	pfree(multirangeArrayName);
 
 	return address;
 }
@@ -1613,6 +1714,147 @@ 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.
+ *
+ * Sets castFuncOid to the oid of the new constructor that can be used
+ * to cast from a range to a multirange.
+ */
+static void
+makeMultirangeConstructors(const char *name, Oid namespace,
+						   Oid multirangeOid, Oid rangeOid, Oid rangeArrayOid,
+						   Oid *castFuncOid)
+{
+	ObjectAddress myself,
+				referenced;
+	oidvector  *argtypes;
+	Datum		allParamTypes;
+	ArrayType  *allParameterTypes;
+	Datum		paramModes;
+	ArrayType  *parameterModes;
+
+	referenced.classId = TypeRelationId;
+	referenced.objectId = multirangeOid;
+	referenced.objectSubId = 0;
+
+	/* 0-arg constructor - for empty multiranges */
+	argtypes = buildoidvector(NULL, 0);
+	myself = ProcedureCreate(name,	/* name: same as multirange type */
+							 namespace,
+							 false,	/* replace */
+							 false,	/* returns set */
+							 multirangeOid,	/* return type */
+							 BOOTSTRAP_SUPERUSERID,	/* proowner */
+							 INTERNALlanguageId,	/* language */
+							 F_FMGR_INTERNAL_VALIDATOR,
+							 "multirange_constructor0",	/* prosrc */
+							 NULL,	/* probin */
+							 PROKIND_FUNCTION,
+							 false,	/* security_definer */
+							 false,	/* leakproof */
+							 false, /* isStrict */
+							 PROVOLATILE_IMMUTABLE,	/* volatility */
+							 PROPARALLEL_SAFE,	/* parallel safety */
+							 argtypes,	/* parameterTypes */
+							 PointerGetDatum(NULL),	/* allParameterTypes */
+							 PointerGetDatum(NULL),	/* parameterModes */
+							 PointerGetDatum(NULL),	/* parameterNames */
+							 NIL,	/* parameterDefaults */
+							 PointerGetDatum(NULL),	/* trftypes */
+							 PointerGetDatum(NULL),	/* proconfig */
+							 InvalidOid,	/* prosupport */
+							 1.0,	/* procost */
+							 0.0);	/* prorows */
+	/*
+	 * Make the constructor 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);
+	pfree(argtypes);
+
+	/*
+	 * 1-arg constructor - for casts
+	 *
+	 * In theory we shouldn't need both this and the vararg (n-arg) constructor,
+	 * but having a separate 1-arg function lets us define casts against it.
+	 */
+	argtypes = buildoidvector(&rangeOid, 1);
+	myself = ProcedureCreate(name,	/* name: same as multirange type */
+							 namespace,
+							 false,	/* replace */
+							 false,	/* returns set */
+							 multirangeOid,	/* return type */
+							 BOOTSTRAP_SUPERUSERID,	/* proowner */
+							 INTERNALlanguageId,	/* language */
+							 F_FMGR_INTERNAL_VALIDATOR,
+							 "multirange_constructor1",	/* prosrc */
+							 NULL,	/* probin */
+							 PROKIND_FUNCTION,
+							 false,	/* security_definer */
+							 false,	/* leakproof */
+							 true, /* isStrict */
+							 PROVOLATILE_IMMUTABLE,	/* volatility */
+							 PROPARALLEL_SAFE,	/* parallel safety */
+							 argtypes,	/* parameterTypes */
+							 PointerGetDatum(NULL),	/* allParameterTypes */
+							 PointerGetDatum(NULL),	/* parameterModes */
+							 PointerGetDatum(NULL),	/* parameterNames */
+							 NIL,	/* parameterDefaults */
+							 PointerGetDatum(NULL),	/* trftypes */
+							 PointerGetDatum(NULL),	/* proconfig */
+							 InvalidOid,	/* prosupport */
+							 1.0,	/* procost */
+							 0.0);	/* prorows */
+	/* ditto */
+	recordDependencyOn(&myself, &referenced, DEPENDENCY_INTERNAL);
+	pfree(argtypes);
+	*castFuncOid = myself.objectId;
+
+	/* n-arg constructor - vararg */
+	argtypes = buildoidvector(&rangeArrayOid, 1);
+	allParamTypes = ObjectIdGetDatum(rangeArrayOid);
+	allParameterTypes = construct_array(&allParamTypes,
+										1, OIDOID,
+										sizeof(Oid), true, 'i');
+	paramModes = CharGetDatum(FUNC_PARAM_VARIADIC);
+	parameterModes = construct_array(&paramModes, 1, CHAROID,
+									 1, true, 'c');
+	myself = ProcedureCreate(name,	/* name: same as multirange type */
+							 namespace,
+							 false,	/* replace */
+							 false,	/* returns set */
+							 multirangeOid,	/* return type */
+							 BOOTSTRAP_SUPERUSERID,	/* proowner */
+							 INTERNALlanguageId,	/* language */
+							 F_FMGR_INTERNAL_VALIDATOR,
+							 "multirange_constructor2",	/* prosrc */
+							 NULL,	/* probin */
+							 PROKIND_FUNCTION,
+							 false,	/* security_definer */
+							 false,	/* leakproof */
+							 false, /* isStrict */
+							 PROVOLATILE_IMMUTABLE,	/* volatility */
+							 PROPARALLEL_SAFE,	/* parallel safety */
+							 argtypes,	/* parameterTypes */
+							 PointerGetDatum(allParameterTypes),	/* allParameterTypes */
+							 PointerGetDatum(parameterModes),	/* parameterModes */
+							 PointerGetDatum(NULL),	/* parameterNames */
+							 NIL,	/* parameterDefaults */
+							 PointerGetDatum(NULL),	/* trftypes */
+							 PointerGetDatum(NULL),	/* proconfig */
+							 InvalidOid,	/* prosupport */
+							 1.0,	/* procost */
+							 0.0);	/* prorows */
+	/* ditto */
+	recordDependencyOn(&myself, &referenced, DEPENDENCY_INTERNAL);
+	pfree(argtypes);
+	pfree(allParameterTypes);
+	pfree(parameterModes);
+}
 
 /*
  * Find suitable I/O functions for a type.
@@ -2045,6 +2287,72 @@ AssignTypeArrayOid(void)
 	return type_array_oid;
 }
 
+/*
+ *	AssignTypeMultirangeOid
+ *
+ *	Pre-assign the range type's multirange OID for use in pg_type.oid
+ */
+Oid
+AssignTypeMultirangeOid(void)
+{
+	Oid			type_multirange_oid;
+
+	/* Use binary-upgrade override for pg_type.oid? */
+	if (IsBinaryUpgrade)
+	{
+		if (!OidIsValid(binary_upgrade_next_mrng_pg_type_oid))
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+					 errmsg("pg_type multirange OID value not set when in binary upgrade mode")));
+
+		type_multirange_oid = binary_upgrade_next_mrng_pg_type_oid;
+		binary_upgrade_next_mrng_pg_type_oid = InvalidOid;
+	}
+	else
+	{
+		Relation	pg_type = table_open(TypeRelationId, AccessShareLock);
+
+		type_multirange_oid = GetNewOidWithIndex(pg_type, TypeOidIndexId,
+												 Anum_pg_type_oid);
+		table_close(pg_type, AccessShareLock);
+	}
+
+	return type_multirange_oid;
+}
+
+/*
+ *	AssignTypeMultirangeArrayOid
+ *
+ *	Pre-assign the range type's multirange array OID for use in pg_type.typarray
+ */
+Oid
+AssignTypeMultirangeArrayOid(void)
+{
+	Oid			type_multirange_array_oid;
+
+	/* Use binary-upgrade override for pg_type.oid? */
+	if (IsBinaryUpgrade)
+	{
+		if (!OidIsValid(binary_upgrade_next_mrng_array_pg_type_oid))
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+					 errmsg("pg_type multirange array OID value not set when in binary upgrade mode")));
+
+		type_multirange_array_oid = binary_upgrade_next_mrng_array_pg_type_oid;
+		binary_upgrade_next_mrng_array_pg_type_oid = InvalidOid;
+	}
+	else
+	{
+		Relation	pg_type = table_open(TypeRelationId, AccessShareLock);
+
+		type_multirange_array_oid = GetNewOidWithIndex(pg_type, TypeOidIndexId,
+													   Anum_pg_type_oid);
+		table_close(pg_type, AccessShareLock);
+	}
+
+	return type_multirange_array_oid;
+}
+
 
 /*-------------------------------------------------------------------
  * DefineCompositeType
diff --git a/src/backend/parser/parse_coerce.c b/src/backend/parser/parse_coerce.c
index 645e4aa4ce..fb8c3e5cb0 100644
--- a/src/backend/parser/parse_coerce.c
+++ b/src/backend/parser/parse_coerce.c
@@ -188,6 +188,7 @@ coerce_type(ParseState *pstate, Node *node,
 	if (targetTypeId == ANYARRAYOID ||
 		targetTypeId == ANYENUMOID ||
 		targetTypeId == ANYRANGEOID ||
+		targetTypeId == ANYMULTIRANGEOID ||
 		targetTypeId == ANYCOMPATIBLEARRAYOID ||
 		targetTypeId == ANYCOMPATIBLERANGEOID)
 	{
@@ -195,12 +196,12 @@ coerce_type(ParseState *pstate, Node *node,
 		 * 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 the pseudotype's input
-		 * function, 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
+		 * the pseudotype's input function, 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
@@ -1531,8 +1532,8 @@ coerce_to_common_type(ParseState *pstate, Node *node,
  * 1) All arguments declared ANYELEMENT must have the same datatype.
  * 2) All arguments declared ANYARRAY must have the same datatype,
  *	  which must be a varlena array type.
- * 3) All arguments declared ANYRANGE must have the same datatype,
- *	  which must be a range type.
+ * 3) All arguments declared ANYRANGE or ANYMULTIRANGE must be a range or
+ *	  multirange type, all derived from the same base datatype.
  * 4) If there are arguments of more than one of these polymorphic types,
  *	  the array element type and/or range subtype must be the same as each
  *	  other and the same as the ANYELEMENT type.
@@ -1582,8 +1583,10 @@ check_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			anycompatible_range_typeid = InvalidOid;
 	Oid			anycompatible_range_typelem = InvalidOid;
+	Oid			range_typelem = InvalidOid;
 	bool		have_anynonarray = false;
 	bool		have_anyenum = false;
 	bool		have_anycompatible_nonarray = false;
@@ -1632,6 +1635,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;
+		}
 		else if (decl_type == ANYCOMPATIBLEOID ||
 				 decl_type == ANYCOMPATIBLENONARRAYOID)
 		{
@@ -1722,8 +1734,6 @@ check_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))
 	{
-		Oid			range_typelem;
-
 		range_typelem = get_range_subtype(range_typeid);
 		if (!OidIsValid(range_typelem))
 			return false;		/* should be a range, but isn't */
@@ -1742,6 +1752,45 @@ 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))
+	{
+		Oid			multirange_typelem;
+
+		multirange_typelem = get_range_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 */
+		}
+		else if (multirange_typelem != range_typeid)
+		{
+			/* otherwise, they better match */
+			return false;
+		}
+
+		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 */
@@ -1820,16 +1869,23 @@ check_generic_type_consistency(const Oid *actual_arg_types,
  * 2) If return type is ANYARRAY, and any argument is ANYARRAY, use the
  *	  argument's actual type as the function's return type.
  * 3) Similarly, if return type is ANYRANGE, and any argument is ANYRANGE,
- *	  use the argument's actual type as the function's return type.
- * 4) Otherwise, if return type is ANYELEMENT or ANYARRAY, there should be
- *	  at least one ANYELEMENT, ANYARRAY, or ANYRANGE input; deduce the
- *	  return type from those inputs, or throw error if we can't.
- * 5) Otherwise, if return type is ANYRANGE, throw error.  (There should
- *	  be at least one ANYRANGE input, since CREATE FUNCTION enforces that.)
- * 6) ANYENUM is treated the same as ANYELEMENT except that if it is used
+ *	  use the argument's actual type as the function's return type. Or
+ *	  if any argument is ANYMULTIRANGE, use its range type as the function's
+ *	  return type.
+ * 4) Otherwise, if return type is ANYMULTIRANGE, and any argument is
+ *	  ANYMULTIRANGE, use the argument's actual type as the function's return
+ *	  type. Or if any argument is ANYRANGE, use its multirange type as the
+ *	  function's return type.
+ * 5) Otherwise, if return type is ANYELEMENT or ANYARRAY, there should be
+ *	  at least one ANYELEMENT, ANYARRAY, ANYRANGE, or ANYMULTIRANGE input;
+ *	  deduce the return type from those inputs, or throw error if we can't.
+ * 6) Otherwise, if return type is ANYRANGE, throw error.  (There should
+ *	  be at least one ANYRANGE or ANYMULTIRANGE input, since CREATE FUNCTION
+ *	  enforces that.)
+ * 7) ANYENUM is treated the same as ANYELEMENT except that if it is used
  *	  (alone or in combination with plain ANYELEMENT), we add the extra
  *	  condition that the ANYELEMENT type must be an enum.
- * 7) ANYNONARRAY is treated the same as ANYELEMENT except that if it is used,
+ * 8) ANYNONARRAY is treated the same as ANYELEMENT except that if it is used,
  *	  we add the extra condition that the ANYELEMENT type must not be an array.
  *	  (This is a no-op if used in combination with ANYARRAY or ANYENUM, but
  *	  is an extra restriction if not.)
@@ -1882,10 +1938,13 @@ 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			anycompatible_typeid = InvalidOid;
 	Oid			anycompatible_array_typeid = InvalidOid;
 	Oid			anycompatible_range_typeid = InvalidOid;
 	Oid			anycompatible_range_typelem = InvalidOid;
+	Oid			range_typelem;
+	Oid			multirange_typelem;
 	bool		have_anynonarray = (rettype == ANYNONARRAYOID);
 	bool		have_anyenum = (rettype == ANYENUMOID);
 	bool		have_anycompatible_nonarray = (rettype == ANYCOMPATIBLENONARRAYOID);
@@ -1970,6 +2029,26 @@ enforce_generic_type_consistency(const Oid *actual_arg_types,
 								   format_type_be(actual_type))));
 			range_typeid = actual_type;
 		}
+		else if (decl_type == ANYMULTIRANGEOID)
+		{
+			n_poly_args++;
+			if (actual_type == UNKNOWNOID)
+			{
+				have_poly_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;
+		}
 		else if (decl_type == ANYCOMPATIBLEOID ||
 				 decl_type == ANYCOMPATIBLENONARRAYOID)
 		{
@@ -2109,8 +2188,6 @@ 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))
 		{
-			Oid			range_typelem;
-
 			range_typelem = get_range_subtype(range_typeid);
 			if (!OidIsValid(range_typelem))
 				ereport(ERROR,
@@ -2139,6 +2216,61 @@ enforce_generic_type_consistency(const Oid *actual_arg_types,
 								   format_type_be(elem_typeid))));
 			}
 		}
+		else
+			range_typelem = InvalidOid;
+
+		/* Get the element type based on the multirange type, if we have one */
+		if (OidIsValid(multirange_typeid))
+		{
+			multirange_typelem = get_range_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))));
+			}
+
+			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 */
+				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))));
+			}
+		}
+		else
+			multirange_typelem = InvalidOid;
 
 		if (!OidIsValid(elem_typeid))
 		{
@@ -2147,6 +2279,7 @@ enforce_generic_type_consistency(const Oid *actual_arg_types,
 				elem_typeid = ANYELEMENTOID;
 				array_typeid = ANYARRAYOID;
 				range_typeid = ANYRANGEOID;
+				multirange_typeid = ANYMULTIRANGEOID;
 			}
 			else
 			{
@@ -2327,6 +2460,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;
+			}
 		}
 	}
 
@@ -2363,6 +2507,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 ANYCOMPATIBLE use the appropriate type */
 	if (rettype == ANYCOMPATIBLEOID ||
 		rettype == ANYCOMPATIBLENONARRAYOID)
@@ -2414,20 +2574,36 @@ check_valid_polymorphic_signature(Oid ret_type,
 								  const Oid *declared_arg_types,
 								  int nargs)
 {
-	if (ret_type == ANYRANGEOID || ret_type == ANYCOMPATIBLERANGEOID)
+	if (ret_type == ANYRANGEOID || ret_type == ANYMULTIRANGEOID)
+	{
+		/*
+		 * ANYRANGE and ANYMULTIRANGE require an ANYRANGE or ANYMULTIRANGE input,
+		 * else we can't tell which of several range types with the same element
+		 * type to use.
+		 */
+		for (int i = 0; i < nargs; i++)
+		{
+			if (declared_arg_types[i] == ANYRANGEOID ||
+				declared_arg_types[i] == ANYMULTIRANGEOID)
+				return NULL;	/* OK */
+		}
+		return psprintf(_("A result of type %s requires at least one input of type anyrange or anymultirange."),
+						format_type_be(ret_type));
+	}
+	else if (ret_type == ANYCOMPATIBLERANGEOID)
 	{
 		/*
-		 * ANYRANGE requires an ANYRANGE input, else we can't tell which of
-		 * several range types with the same element type to use.  Likewise
-		 * for ANYCOMPATIBLERANGE.
+		 * ANYCOMPATIBLERANGE requires an ANYCOMPATIBLERANGE input,
+		 * else we can't tell which of several range types with the same element
+		 * type to use.
 		 */
 		for (int i = 0; i < nargs; i++)
 		{
 			if (declared_arg_types[i] == ret_type)
 				return NULL;	/* OK */
 		}
-		return psprintf(_("A result of type %s requires at least one input of type %s."),
-						format_type_be(ret_type), format_type_be(ret_type));
+		return psprintf(_("A result of type %s requires at least one input of type anycompatiblerange."),
+						format_type_be(ret_type));
 	}
 	else if (IsPolymorphicTypeFamily1(ret_type))
 	{
@@ -2438,7 +2614,7 @@ check_valid_polymorphic_signature(Oid ret_type,
 				return NULL;	/* OK */
 		}
 		/* Keep this list in sync with IsPolymorphicTypeFamily1! */
-		return psprintf(_("A result of type %s requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange."),
+		return psprintf(_("A result of type %s requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange."),
 						format_type_be(ret_type));
 	}
 	else if (IsPolymorphicTypeFamily2(ret_type))
@@ -2590,6 +2766,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..bd3cf3dc93
--- /dev/null
+++ b/src/backend/utils/adt/multirangetypes.c
@@ -0,0 +1,2186 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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/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 */
+	FmgrInfo	typioproc;		/* range type's I/O proc */
+	Oid			typioparam;		/* range type's I/O parameter */
+} 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 mltrngtypid,
+												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;
+	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 = NULL;
+	int32		range_str_len;
+	char	   *range_str_copy;
+
+	cache = get_multirange_io_data(fcinfo, mltrngtypoid, IOFunc_input);
+	rangetyp = cache->typcache->rngtype;
+
+	/* 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->typioproc,
+																 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->typioproc, 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->typioproc,
+														   &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;
+
+		range = RangeTypePGetDatum(ranges[i]);
+		range = PointerGetDatum(SendFunctionCall(&cache->typioproc, range));
+
+		pq_sendint32(buf, VARSIZE(range) - VARHDRSZ);
+		pq_sendbytes(buf, VARDATA(range), VARSIZE(range) - VARHDRSZ);
+	}
+
+	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)
+	{
+		Oid			typiofunc;
+		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,
+						 &typiofunc);
+
+		if (!OidIsValid(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(typiofunc, &cache->typioproc,
+					  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;
+	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++)
+		bytelen += MAXALIGN(VARSIZE(ranges[i]));
+
+	/* 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++)
+	{
+		memcpy(ptr, ranges[i], VARSIZE(ranges[i]));
+		ptr += MAXALIGN(VARSIZE(ranges[i]));
+	}
+
+	return multirange;
+}
+
+/*
+ * Converts a list of arbitrary ranges 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_constructor2(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,
+				(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+				 errmsg("multirange values cannot contain NULL members")));
+
+	rangeArray = PG_GETARG_ARRAYTYPE_P(0);
+
+	dims = ARR_NDIM(rangeArray);
+	if (dims > 1)
+		ereport(ERROR,
+				(errcode(ERRCODE_CARDINALITY_VIOLATION),
+				 errmsg("multiranges cannot be constructed from multi-dimensional arrays")));
+
+	rngtypid = ARR_ELEMTYPE(rangeArray);
+	if (rngtypid != rangetyp->type_id)
+		ereport(ERROR,
+				(errcode(ERRCODE_DATATYPE_MISMATCH),
+				 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,
+						(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+						 errmsg("multirange values cannot contain NULL members")));
+
+			/* make_multirange will do its own copy */
+			ranges[i] = DatumGetRangeTypeP(elements[i]);
+		}
+	}
+
+	PG_RETURN_MULTIRANGE_P(make_multirange(mltrngtypid, rangetyp, range_count, ranges));
+}
+
+/*
+ * Construct multirange value from a single range.
+ * It'd be nice if we could just use multirange_constructor2
+ * for this case, but we need a non-variadic single-arg function
+ * to let us define a CAST from a range to its multirange.
+ */
+Datum
+multirange_constructor1(PG_FUNCTION_ARGS)
+{
+	Oid			mltrngtypid = get_fn_expr_rettype(fcinfo->flinfo);
+	Oid			rngtypid;
+	TypeCacheEntry *typcache;
+	TypeCacheEntry *rangetyp;
+	RangeType  *range;
+
+	typcache = multirange_get_typcache(fcinfo, mltrngtypid);
+	rangetyp = typcache->rngtype;
+
+	/*
+	 * These checks should be guaranteed by our signature, but let's do them
+	 * just in case.
+	 */
+
+	if (PG_ARGISNULL(0))
+				ereport(ERROR,
+						(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+						 errmsg("multirange values cannot contain NULL members")));
+
+	range = PG_GETARG_RANGE_P(0);
+
+	/* Make sure the range type matches. */
+	rngtypid = RangeTypeGetOid(range);
+	if (rngtypid != rangetyp->type_id)
+		ereport(ERROR,
+				(errcode(ERRCODE_DATATYPE_MISMATCH),
+				 errmsg("type %u does not match constructor type", rngtypid)));
+
+	PG_RETURN_MULTIRANGE_P(make_multirange(mltrngtypid, rangetyp, 1, &range));
+}
+
+/*
+ * 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
+		elog(ERROR,		/* can't happen */
+			 "niladic multirange constructor must not receive arguments");
+}
+
+
+/* multirange, multirange -> multirange type functions */
+
+/* multirange union */
+Datum
+multirange_union(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
+	MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count1;
+	int32		range_count2;
+	int32		range_count3;
+	RangeType **ranges1;
+	RangeType **ranges2;
+	RangeType **ranges3;
+
+	if (MultirangeIsEmpty(mr1))
+		PG_RETURN_MULTIRANGE_P(mr2);
+	if (MultirangeIsEmpty(mr2))
+		PG_RETURN_MULTIRANGE_P(mr1);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr1));
+
+	multirange_deserialize(mr1, &range_count1, &ranges1);
+	multirange_deserialize(mr2, &range_count2, &ranges2);
+
+	range_count3 = range_count1 + range_count2;
+	ranges3 = palloc0(range_count3 * sizeof(RangeType *));
+	memcpy(ranges3, ranges1, range_count1 * sizeof(RangeType *));
+	memcpy(ranges3 + range_count1, ranges2, range_count2 * sizeof(RangeType *));
+	PG_RETURN_MULTIRANGE_P(make_multirange(typcache->type_id, typcache->rngtype,
+										   range_count3, ranges3));
+}
+
+/* multirange minus */
+Datum
+multirange_minus(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
+	MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
+	Oid			mltrngtypoid = MultirangeTypeGetOid(mr1);
+	TypeCacheEntry *typcache;
+	TypeCacheEntry *rangetyp;
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+
+	typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
+	rangetyp = typcache->rngtype;
+
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		PG_RETURN_MULTIRANGE_P(mr1);
+
+	multirange_deserialize(mr1, &range_count1, &ranges1);
+	multirange_deserialize(mr2, &range_count2, &ranges2);
+
+	PG_RETURN_MULTIRANGE_P(multirange_minus_internal(mltrngtypoid,
+													 rangetyp,
+													 range_count1,
+													 ranges1,
+													 range_count2,
+													 ranges2));
+}
+
+MultirangeType *
+multirange_minus_internal(Oid mltrngtypoid, TypeCacheEntry *rangetyp,
+						  int32 range_count1, RangeType **ranges1,
+						  int32 range_count2, RangeType **ranges2)
+{
+	RangeType  *r1;
+	RangeType  *r2;
+	RangeType **ranges3;
+	int32		range_count3;
+	int32		i1;
+	int32		i2;
+
+	/*
+	 * Worst case: every range in ranges1 makes a different cut to some range
+	 * in ranges2.
+	 */
+	ranges3 = palloc0((range_count1 + range_count2) * sizeof(RangeType *));
+	range_count3 = 0;
+
+	/*
+	 * For each range in mr1, keep subtracting until it's gone or the ranges
+	 * in mr2 have passed it. After a subtraction we assign what's left back
+	 * to r1. The parallel progress through mr1 and mr2 is similar to
+	 * multirange_overlaps_multirange_internal.
+	 */
+	r2 = ranges2[0];
+	for (i1 = 0, i2 = 0; i1 < range_count1; i1++)
+	{
+		r1 = ranges1[i1];
+
+		/* Discard r2s while r2 << r1 */
+		while (r2 != NULL && range_before_internal(rangetyp, r2, r1))
+		{
+			r2 = ++i2 >= range_count2 ? NULL : ranges2[i2];
+		}
+
+		while (r2 != NULL)
+		{
+			if (range_split_internal(rangetyp, r1, r2, &ranges3[range_count3], &r1))
+			{
+				/*
+				 * If r2 takes a bite out of the middle of r1, we need two
+				 * outputs
+				 */
+				range_count3++;
+				r2 = ++i2 >= range_count2 ? NULL : ranges2[i2];
+
+			}
+			else if (range_overlaps_internal(rangetyp, r1, r2))
+			{
+				/*
+				 * If r2 overlaps r1, replace r1 with r1 - r2.
+				 */
+				r1 = range_minus_internal(rangetyp, r1, r2);
+
+				/*
+				 * If r2 goes past r1, then we need to stay with it, in case
+				 * it hits future r1s. Otherwise we need to keep r1, in case
+				 * future r2s hit it. Since we already subtracted, there's no
+				 * point in using the overright/overleft calls.
+				 */
+				if (RangeIsEmpty(r1) || range_before_internal(rangetyp, r1, r2))
+					break;
+				else
+					r2 = ++i2 >= range_count2 ? NULL : ranges2[i2];
+
+			}
+			else
+			{
+				/*
+				 * This and all future r2s are past r1, so keep them. Also
+				 * assign whatever is left of r1 to the result.
+				 */
+				break;
+			}
+		}
+
+		/*
+		 * Nothing else can remove anything from r1, so keep it. Even if r1 is
+		 * empty here, make_multirange will remove it.
+		 */
+		ranges3[range_count3++] = r1;
+	}
+
+	return make_multirange(mltrngtypoid, rangetyp, range_count3, ranges3);
+}
+
+/* multirange intersection */
+Datum
+multirange_intersect(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
+	MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
+	Oid			mltrngtypoid = MultirangeTypeGetOid(mr1);
+	TypeCacheEntry *typcache;
+	TypeCacheEntry *rangetyp;
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+
+	typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
+	rangetyp = typcache->rngtype;
+
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		PG_RETURN_MULTIRANGE_P(make_empty_multirange(mltrngtypoid, rangetyp));
+
+	multirange_deserialize(mr1, &range_count1, &ranges1);
+	multirange_deserialize(mr2, &range_count2, &ranges2);
+
+	PG_RETURN_MULTIRANGE_P(multirange_intersect_internal(mltrngtypoid,
+														 rangetyp,
+														 range_count1,
+														 ranges1,
+														 range_count2,
+														 ranges2));
+}
+
+MultirangeType *
+multirange_intersect_internal(Oid mltrngtypoid, TypeCacheEntry *rangetyp,
+							  int32 range_count1, RangeType **ranges1,
+							  int32 range_count2, RangeType **ranges2)
+{
+	RangeType  *r1;
+	RangeType  *r2;
+	RangeType **ranges3;
+	int32		range_count3;
+	int32		i1;
+	int32		i2;
+
+	if (range_count1 == 0 || range_count2 == 0)
+		return make_multirange(mltrngtypoid, rangetyp, 0, NULL);
+
+	/*-----------------------------------------------
+	 * Worst case is a stitching pattern like this:
+	 *
+	 * mr1: --- --- --- ---
+	 * mr2:   --- --- ---
+	 * mr3:   - - - - - -
+	 *
+	 * That seems to be range_count1 + range_count2 - 1,
+	 * but one extra won't hurt.
+	 *-----------------------------------------------
+	 */
+	ranges3 = palloc0((range_count1 + range_count2) * sizeof(RangeType *));
+	range_count3 = 0;
+
+	/*
+	 * For each range in mr1, keep intersecting until the ranges in mr2 have
+	 * passed it. The parallel progress through mr1 and mr2 is similar to
+	 * multirange_minus_multirange_internal, but we don't have to assign back
+	 * to r1.
+	 */
+	r2 = ranges2[0];
+	for (i1 = 0, i2 = 0; i1 < range_count1; i1++)
+	{
+		r1 = ranges1[i1];
+
+		/* Discard r2s while r2 << r1 */
+		while (r2 != NULL && range_before_internal(rangetyp, r2, r1))
+		{
+			r2 = ++i2 >= range_count2 ? NULL : ranges2[i2];
+		}
+
+		while (r2 != NULL)
+		{
+			if (range_overlaps_internal(rangetyp, r1, r2))
+			{
+				/* Keep the overlapping part */
+				ranges3[range_count3++] = range_intersect_internal(rangetyp, r1, r2);
+
+				/* If we "used up" all of r2, go to the next one... */
+				if (range_overleft_internal(rangetyp, r2, r1))
+					r2 = ++i2 >= range_count2 ? NULL : ranges2[i2];
+
+				/* ...otherwise go to the next r1 */
+				else
+					break;
+			}
+			else
+				/* We're past r1, so move to the next one */
+				break;
+		}
+
+		/* If we're out of r2s, there can be no more intersections */
+		if (r2 == NULL)
+			break;
+	}
+
+	return make_multirange(mltrngtypoid, rangetyp, range_count3, ranges3);
+}
+
+/*
+ * range_agg_transfn: combine adjacent/overlapping ranges.
+ *
+ * All we do here is gather the input ranges into an array
+ * so that the finalfn can sort and combine them.
+ */
+Datum
+range_agg_transfn(PG_FUNCTION_ARGS)
+{
+	MemoryContext aggContext;
+	Oid			rngtypoid;
+	ArrayBuildState *state;
+
+	if (!AggCheckCallContext(fcinfo, &aggContext))
+		elog(ERROR, "range_agg_transfn called in non-aggregate context");
+
+	rngtypoid = get_fn_expr_argtype(fcinfo->flinfo, 1);
+	if (!type_is_range(rngtypoid))
+		ereport(ERROR,
+				(errcode(ERRCODE_DATATYPE_MISMATCH),
+				 errmsg("range_agg must be called with a range")));
+
+	if (PG_ARGISNULL(0))
+		state = initArrayResult(rngtypoid, aggContext, false);
+	else
+		state = (ArrayBuildState *) PG_GETARG_POINTER(0);
+
+	/* skip NULLs */
+	if (!PG_ARGISNULL(1))
+		accumArrayResult(state, PG_GETARG_DATUM(1), false, rngtypoid, aggContext);
+
+	PG_RETURN_POINTER(state);
+}
+
+/*
+ * range_agg_finalfn: use our internal array to merge touching ranges.
+ */
+Datum
+range_agg_finalfn(PG_FUNCTION_ARGS)
+{
+	MemoryContext aggContext;
+	Oid			mltrngtypoid;
+	TypeCacheEntry *typcache;
+	ArrayBuildState *state;
+	int32		range_count;
+	RangeType **ranges;
+	int			i;
+
+	if (!AggCheckCallContext(fcinfo, &aggContext))
+		elog(ERROR, "range_agg_finalfn called in non-aggregate context");
+
+	state = PG_ARGISNULL(0) ? NULL : (ArrayBuildState *) PG_GETARG_POINTER(0);
+	if (state == NULL)
+		/* This shouldn't be possible, but just in case.... */
+		PG_RETURN_NULL();
+
+	/* Also return NULL if we had zero inputs, like other aggregates */
+	range_count = state->nelems;
+	if (range_count == 0)
+		PG_RETURN_NULL();
+
+	mltrngtypoid = get_fn_expr_rettype(fcinfo->flinfo);
+	typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
+
+	ranges = palloc0(range_count * sizeof(RangeType *));
+	for (i = 0; i < range_count; i++)
+		ranges[i] = DatumGetRangeTypeP(state->dvalues[i]);
+
+	PG_RETURN_MULTIRANGE_P(make_multirange(mltrngtypoid, typcache->rngtype, range_count, ranges));
+}
+
+Datum
+multirange_intersect_agg_transfn(PG_FUNCTION_ARGS)
+{
+	MemoryContext aggContext;
+	Oid			mltrngtypoid;
+	TypeCacheEntry *typcache;
+	MultirangeType *result;
+	MultirangeType *current;
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+
+	if (!AggCheckCallContext(fcinfo, &aggContext))
+		elog(ERROR, "multirange_intersect_agg_transfn called in non-aggregate context");
+
+	mltrngtypoid = get_fn_expr_argtype(fcinfo->flinfo, 1);
+	if (!type_is_multirange(mltrngtypoid))
+		ereport(ERROR,
+				(errcode(ERRCODE_DATATYPE_MISMATCH),
+				 errmsg("range_intersect_agg must be called with a multirange")));
+
+	typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
+
+	/* strictness ensures these are non-null */
+	result = PG_GETARG_MULTIRANGE_P(0);
+	current = PG_GETARG_MULTIRANGE_P(1);
+
+	multirange_deserialize(result, &range_count1, &ranges1);
+	multirange_deserialize(current, &range_count2, &ranges2);
+
+	result = multirange_intersect_internal(mltrngtypoid,
+										   typcache->rngtype,
+										   range_count1,
+										   ranges1,
+										   range_count2,
+										   ranges2);
+	PG_RETURN_RANGE_P(result);
+}
+
+
+/* multirange -> element type functions */
+
+/* extract lower bound value */
+Datum
+multirange_lower(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+	bool		isnull;
+	Datum		result;
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_NULL();
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	result = range_lower_internal(typcache->rngtype, ranges[0], &isnull);
+
+	if (isnull)
+		PG_RETURN_NULL();
+	else
+		PG_RETURN_DATUM(result);
+}
+
+/* extract upper bound value */
+Datum
+multirange_upper(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+	bool		isnull;
+	Datum		result;
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_NULL();
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	result = range_upper_internal(typcache->rngtype, ranges[range_count - 1], &isnull);
+
+	if (isnull)
+		PG_RETURN_NULL();
+	else
+		PG_RETURN_DATUM(result);
+}
+
+
+/* multirange -> bool functions */
+
+/* is multirange empty? */
+Datum
+multirange_empty(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+
+	PG_RETURN_BOOL(mr->rangeCount == 0);
+}
+
+/* is lower bound inclusive? */
+Datum
+multirange_lower_inc(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	int32		range_count;
+	RangeType **ranges;
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_has_flag(ranges[0], RANGE_LB_INC));
+}
+
+/* is upper bound inclusive? */
+Datum
+multirange_upper_inc(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	int32		range_count;
+	RangeType **ranges;
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_has_flag(ranges[range_count - 1], RANGE_UB_INC));
+}
+
+/* is lower bound infinite? */
+Datum
+multirange_lower_inf(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	int32		range_count;
+	RangeType **ranges;
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_has_flag(ranges[0], RANGE_LB_INF));
+}
+
+/* is upper bound infinite? */
+Datum
+multirange_upper_inf(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	int32		range_count;
+	RangeType **ranges;
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_has_flag(ranges[range_count - 1], RANGE_UB_INF));
+}
+
+
+
+/* multirange, element -> bool functions */
+
+/* contains? */
+Datum
+multirange_contains_elem(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	Datum		val = PG_GETARG_DATUM(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(multirange_contains_elem_internal(typcache, mr, val));
+}
+
+/* contained by? */
+Datum
+elem_contained_by_multirange(PG_FUNCTION_ARGS)
+{
+	Datum		val = PG_GETARG_DATUM(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(multirange_contains_elem_internal(typcache, mr, val));
+}
+
+/*
+ * Test whether multirange mr contains a specific element value.
+ */
+bool
+multirange_contains_elem_internal(TypeCacheEntry *typcache, MultirangeType *mr, Datum val)
+{
+	TypeCacheEntry *rangetyp;
+	int32		range_count;
+	RangeType **ranges;
+	RangeType  *r;
+	int			i;
+
+	rangetyp = typcache->rngtype;
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	for (i = 0; i < range_count; i++)
+	{
+		r = ranges[i];
+		if (range_contains_elem_internal(rangetyp, r, val))
+			return true;
+	}
+
+	return false;
+}
+
+/* multirange, range -> bool functions */
+
+/* contains? */
+Datum
+multirange_contains_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(multirange_contains_range_internal(typcache, mr, r));
+}
+
+/* contained by? */
+Datum
+range_contained_by_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(multirange_contains_range_internal(typcache, mr, r));
+}
+
+/*
+ * Test whether multirange mr contains a specific range r.
+ */
+bool
+multirange_contains_range_internal(TypeCacheEntry *typcache, MultirangeType *mr, RangeType *r)
+{
+	TypeCacheEntry *rangetyp;
+	int32		range_count;
+	RangeType **ranges;
+	RangeType  *mrr;
+	int			i;
+
+	rangetyp = typcache->rngtype;
+
+	/*
+	 * Every multirange contains an infinite number of empty ranges, even an
+	 * empty one.
+	 */
+	if (RangeIsEmpty(r))
+		return true;
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	for (i = 0; i < range_count; i++)
+	{
+		mrr = ranges[i];
+		if (range_contains_internal(rangetyp, mrr, r))
+			return true;
+	}
+
+	return false;
+}
+
+
+/* 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));
+}
+
+/* overlaps? */
+Datum
+range_overlaps_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(range_overlaps_multirange_internal(typcache, r, mr));
+}
+
+Datum
+multirange_overlaps_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(range_overlaps_multirange_internal(typcache, r, mr));
+}
+
+Datum
+multirange_overlaps_multirange(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_overlaps_multirange_internal(typcache, mr1, mr2));
+}
+
+bool
+range_overlaps_multirange_internal(TypeCacheEntry *typcache, RangeType *r, MultirangeType *mr)
+{
+	TypeCacheEntry *rangetyp;
+	int32		range_count;
+	int32		i;
+	RangeType **ranges;
+	RangeType  *mrr;
+
+	/*
+	 * Empties never overlap, even with empties. (This seems strange since
+	 * they *do* contain each other, but we want to follow how ranges work.)
+	 */
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		return false;
+
+	rangetyp = typcache->rngtype;
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	/* Scan through mrr and once it gets to r they either overlap or not */
+	mrr = ranges[0];
+
+	/* Discard mrrs while mrr << r */
+	i = 0;
+	while (range_before_internal(rangetyp, mrr, r))
+	{
+		if (++i >= range_count)
+			return false;
+		mrr = ranges[i];
+	}
+
+	/* Now either we overlap or we passed r */
+	return range_overlaps_internal(rangetyp, mrr, r);
+}
+
+bool
+multirange_overlaps_multirange_internal(TypeCacheEntry *typcache, MultirangeType *mr1,
+										MultirangeType *mr2)
+{
+	TypeCacheEntry *rangetyp;
+	int32		range_count1;
+	int32		range_count2;
+	int32		i1;
+	int32		i2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+	RangeType  *r1;
+	RangeType  *r2;
+
+	/*
+	 * Empties never overlap, even with empties. (This seems strange since
+	 * they *do* contain each other, but we want to follow how ranges work.)
+	 */
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		return false;
+
+	rangetyp = typcache->rngtype;
+
+	multirange_deserialize(mr1, &range_count1, &ranges1);
+	multirange_deserialize(mr2, &range_count2, &ranges2);
+
+	/*
+	 * Every range in mr1 gets a chance to overlap with the ranges in mr2, but
+	 * we can use their ordering to avoid O(n^2). This is similar to
+	 * range_overlaps_multirange where r1 : r2 :: mrr : r, but there if we
+	 * don't find an overlap with r we're done, and here if we don't find an
+	 * overlap with r2 we try the next r2.
+	 */
+	r1 = ranges1[0];
+	for (i1 = 0, i2 = 0; i2 < range_count2; i2++)
+	{
+		r2 = ranges2[i2];
+
+		/* Discard r1s while r1 << r2 */
+		while (range_before_internal(rangetyp, r1, r2))
+		{
+			if (++i1 >= range_count1)
+				return false;
+			r1 = ranges1[i1];
+		}
+
+		/*
+		 * If r1 && r2, we're done, otherwise we failed to find an overlap for
+		 * r2, so go to the next one.
+		 */
+		if (range_overlaps_internal(rangetyp, r1, r2))
+			return true;
+	}
+
+	/* We looked through all of mr2 without finding an overlap */
+	return false;
+}
+
+/* does not extend to right of? */
+Datum
+range_overleft_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_overleft_internal(typcache->rngtype, r, ranges[range_count - 1]));
+}
+
+Datum
+multirange_overleft_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_overleft_internal(typcache->rngtype,
+										   ranges[range_count - 1], r));
+}
+
+Datum
+multirange_overleft_multirange(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
+	MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr1));
+
+	multirange_deserialize(mr1, &range_count1, &ranges1);
+	multirange_deserialize(mr2, &range_count2, &ranges2);
+
+	PG_RETURN_BOOL(range_overleft_internal(typcache->rngtype,
+										   ranges1[range_count1 - 1],
+										   ranges2[range_count2 - 1]));
+}
+
+/* does not extend to left of? */
+Datum
+range_overright_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_overright_internal(typcache->rngtype, r, ranges[0]));
+}
+
+Datum
+multirange_overright_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_overright_internal(typcache->rngtype, ranges[0], r));
+}
+
+Datum
+multirange_overright_multirange(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
+	MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr1));
+
+	multirange_deserialize(mr1, &range_count1, &ranges1);
+	multirange_deserialize(mr2, &range_count2, &ranges2);
+
+	PG_RETURN_BOOL(range_overright_internal(typcache->rngtype,
+											ranges1[0],
+											ranges2[0]));
+}
+
+/* contains? */
+Datum
+multirange_contains_multirange(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_contains_multirange_internal(typcache, mr1, mr2));
+}
+
+/* contained by? */
+Datum
+multirange_contained_by_multirange(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_contains_multirange_internal(typcache, mr2, mr1));
+}
+
+/*
+ * Test whether multirange mr1 contains every range from another multirange mr2.
+ */
+bool
+multirange_contains_multirange_internal(TypeCacheEntry *typcache,
+										MultirangeType *mr1, MultirangeType *mr2)
+{
+	TypeCacheEntry *rangetyp;
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+	RangeType  *r1;
+	RangeType  *r2;
+	int			i1,
+				i2;
+
+	rangetyp = typcache->rngtype;
+
+	multirange_deserialize(mr1, &range_count1, &ranges1);
+	multirange_deserialize(mr2, &range_count2, &ranges2);
+
+	/*
+	 * We follow the same logic for empties as ranges: - an empty multirange
+	 * contains an empty range/multirange. - an empty multirange can't contain
+	 * any other range/multirange. - an empty multirange is contained by any
+	 * other range/multirange.
+	 */
+
+	if (range_count2 == 0)
+		return true;
+	if (range_count1 == 0)
+		return false;
+
+	/*
+	 * Every range in mr2 must be contained by some range in mr1. To avoid
+	 * O(n^2) we walk through both ranges in tandem.
+	 */
+	r1 = ranges1[0];
+	for (i1 = 0, i2 = 0; i2 < range_count2; i2++)
+	{
+		r2 = ranges2[i2];
+
+		/* Discard r1s while r1 << r2 */
+		while (range_before_internal(rangetyp, r1, r2))
+		{
+			if (++i1 >= range_count1)
+				return false;
+			r1 = ranges1[i1];
+		}
+
+		/*
+		 * If r1 @> r2, go to the next r2, otherwise return false (since every
+		 * r1[n] and r1[n+1] must have a gap). Note this will give weird
+		 * answers if you don't canonicalize, e.g. with a custom
+		 * int2multirange {[1,1], [2,2]} there is a "gap". But that is
+		 * consistent with other range operators, e.g. '[1,1]'::int2range -|-
+		 * '[2,2]'::int2range is false.
+		 */
+		if (!range_contains_internal(rangetyp, r1, r2))
+			return false;
+	}
+
+	/* All ranges in mr2 are satisfied */
+	return true;
+}
+
+/* strictly left of? */
+Datum
+range_before_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(range_before_multirange_internal(typcache, r, mr));
+}
+
+Datum
+multirange_before_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(range_after_multirange_internal(typcache, r, mr));
+}
+
+Datum
+multirange_before_multirange(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_before_multirange_internal(typcache, mr1, mr2));
+}
+
+/* strictly right of? */
+Datum
+range_after_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(range_after_multirange_internal(typcache, r, mr));
+}
+
+Datum
+multirange_after_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(range_before_multirange_internal(typcache, r, mr));
+}
+
+Datum
+multirange_after_multirange(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_before_multirange_internal(typcache, mr2, mr1));
+}
+
+/* strictly left of? (internal version) */
+bool
+range_before_multirange_internal(TypeCacheEntry *typcache, RangeType *r,
+								 MultirangeType *mr)
+{
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		return false;
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	return range_before_internal(typcache->rngtype, r, ranges[0]);
+}
+
+bool
+multirange_before_multirange_internal(TypeCacheEntry *typcache, MultirangeType *mr1,
+									  MultirangeType *mr2)
+{
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		return false;
+
+	multirange_deserialize(mr1, &range_count1, &ranges1);
+	multirange_deserialize(mr2, &range_count2, &ranges2);
+
+	return range_before_internal(typcache->rngtype, ranges1[range_count1 - 1],
+								 ranges2[0]);
+}
+
+/* strictly right of? (internal version) */
+bool
+range_after_multirange_internal(TypeCacheEntry *typcache, RangeType *r,
+								MultirangeType *mr)
+{
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		return false;
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	return range_after_internal(typcache->rngtype, r, ranges[range_count - 1]);
+}
+
+/* adjacent to? */
+Datum
+range_adjacent_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		return false;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_adjacent_internal(typcache->rngtype, r, ranges[0]));
+}
+
+Datum
+multirange_adjacent_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		return false;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_adjacent_internal(typcache->rngtype, ranges[range_count - 1], r));
+}
+
+Datum
+multirange_adjacent_multirange(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
+	MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		return false;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr1));
+
+	multirange_deserialize(mr1, &range_count1, &ranges1);
+	multirange_deserialize(mr2, &range_count2, &ranges2);
+
+	PG_RETURN_BOOL(range_adjacent_internal(typcache->rngtype, ranges1[range_count1 - 1], ranges2[0]));
+}
+
+/* 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);
+}
+
+/* multirange -> range functions */
+
+/* Find the smallest range that includes everything in the multirange */
+Datum
+range_merge_from_multirange(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	Oid			mltrngtypoid = MultirangeTypeGetOid(mr);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_RANGE_P(make_empty_range(typcache->rngtype));
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_RANGE_P(range_union_internal(typcache->rngtype, ranges[0],
+										   ranges[range_count - 1], false));
+}
+
+/* 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/pg_upgrade_support.c b/src/backend/utils/adt/pg_upgrade_support.c
index 18f2ee8226..248ea7f44a 100644
--- a/src/backend/utils/adt/pg_upgrade_support.c
+++ b/src/backend/utils/adt/pg_upgrade_support.c
@@ -52,6 +52,28 @@ binary_upgrade_set_next_array_pg_type_oid(PG_FUNCTION_ARGS)
 }
 
 Datum
+binary_upgrade_set_next_multirange_pg_type_oid(PG_FUNCTION_ARGS)
+{
+	Oid			typoid = PG_GETARG_OID(0);
+
+	CHECK_IS_BINARY_UPGRADE;
+	binary_upgrade_next_mrng_pg_type_oid = typoid;
+
+	PG_RETURN_VOID();
+}
+
+Datum
+binary_upgrade_set_next_multirange_array_pg_type_oid(PG_FUNCTION_ARGS)
+{
+	Oid			typoid = PG_GETARG_OID(0);
+
+	CHECK_IS_BINARY_UPGRADE;
+	binary_upgrade_next_mrng_array_pg_type_oid = typoid;
+
+	PG_RETURN_VOID();
+}
+
+Datum
 binary_upgrade_set_next_toast_pg_type_oid(PG_FUNCTION_ARGS)
 {
 	Oid			typoid = PG_GETARG_OID(0);
diff --git a/src/backend/utils/adt/pseudotypes.c b/src/backend/utils/adt/pseudotypes.c
index 3d6b2f9093..4fcd8ce6de 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"
 
 
 /*
@@ -228,6 +229,30 @@ anycompatiblerange_out(PG_FUNCTION_ARGS)
 }
 
 /*
+ * 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
  *
  * We support void_in so that PL functions can return VOID without any
diff --git a/src/backend/utils/adt/rangetypes.c b/src/backend/utils/adt/rangetypes.c
index 01ad8bc240..0af3c67296 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
 {
@@ -431,19 +429,37 @@ range_lower(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
 	TypeCacheEntry *typcache;
+	bool		isnull;
+	Datum		result;
+
+	typcache = range_get_typcache(fcinfo, RangeTypeGetOid(r1));
+
+	result = range_lower_internal(typcache, r1, &isnull);
+
+	if (isnull)
+		PG_RETURN_NULL();
+	else
+		PG_RETURN_DATUM(result);
+}
+
+Datum
+range_lower_internal(TypeCacheEntry *typcache, const RangeType *r1, bool *isnull)
+{
 	RangeBound	lower;
 	RangeBound	upper;
 	bool		empty;
 
-	typcache = range_get_typcache(fcinfo, RangeTypeGetOid(r1));
-
 	range_deserialize(typcache, r1, &lower, &upper, &empty);
 
 	/* Return NULL if there's no finite lower bound */
 	if (empty || lower.infinite)
-		PG_RETURN_NULL();
+	{
+		*isnull = true;
+		return 0;
+	}
 
-	PG_RETURN_DATUM(lower.val);
+	*isnull = false;
+	return lower.val;
 }
 
 /* extract upper bound value */
@@ -452,19 +468,37 @@ range_upper(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
 	TypeCacheEntry *typcache;
+	bool		isnull;
+	Datum		result;
+
+	typcache = range_get_typcache(fcinfo, RangeTypeGetOid(r1));
+
+	result = range_upper_internal(typcache, r1, &isnull);
+
+	if (isnull)
+		PG_RETURN_NULL();
+	else
+		PG_RETURN_DATUM(result);
+}
+
+Datum
+range_upper_internal(TypeCacheEntry *typcache, const RangeType *r1, bool *isnull)
+{
 	RangeBound	lower;
 	RangeBound	upper;
 	bool		empty;
 
-	typcache = range_get_typcache(fcinfo, RangeTypeGetOid(r1));
-
 	range_deserialize(typcache, r1, &lower, &upper, &empty);
 
 	/* Return NULL if there's no finite upper bound */
 	if (empty || upper.infinite)
-		PG_RETURN_NULL();
+	{
+		*isnull = true;
+		return 0;
+	}
 
-	PG_RETURN_DATUM(upper.val);
+	*isnull = false;
+	return upper.val;
 }
 
 
@@ -475,9 +509,8 @@ Datum
 range_empty(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
-	char		flags = range_get_flags(r1);
 
-	PG_RETURN_BOOL(flags & RANGE_EMPTY);
+	PG_RETURN_BOOL(range_has_flag(r1, RANGE_EMPTY));
 }
 
 /* is lower bound inclusive? */
@@ -485,9 +518,8 @@ Datum
 range_lower_inc(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
-	char		flags = range_get_flags(r1);
 
-	PG_RETURN_BOOL(flags & RANGE_LB_INC);
+	PG_RETURN_BOOL(range_has_flag(r1, RANGE_LB_INC));
 }
 
 /* is upper bound inclusive? */
@@ -495,9 +527,8 @@ Datum
 range_upper_inc(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
-	char		flags = range_get_flags(r1);
 
-	PG_RETURN_BOOL(flags & RANGE_UB_INC);
+	PG_RETURN_BOOL(range_has_flag(r1, RANGE_UB_INC));
 }
 
 /* is lower bound infinite? */
@@ -505,9 +536,8 @@ Datum
 range_lower_inf(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
-	char		flags = range_get_flags(r1);
 
-	PG_RETURN_BOOL(flags & RANGE_LB_INF);
+	PG_RETURN_BOOL(range_has_flag(r1, RANGE_LB_INF));
 }
 
 /* is upper bound infinite? */
@@ -515,9 +545,8 @@ Datum
 range_upper_inf(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
-	char		flags = range_get_flags(r1);
 
-	PG_RETURN_BOOL(flags & RANGE_UB_INF);
+	PG_RETURN_BOOL(range_has_flag(r1, RANGE_UB_INF));
 }
 
 
@@ -957,7 +986,25 @@ range_minus(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
 	RangeType  *r2 = PG_GETARG_RANGE_P(1);
+	RangeType  *ret;
 	TypeCacheEntry *typcache;
+
+	/* 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));
+
+	ret = range_minus_internal(typcache, r1, r2);
+	if (ret)
+		PG_RETURN_RANGE_P(ret);
+	else
+		PG_RETURN_NULL();
+}
+
+RangeType *
+range_minus_internal(TypeCacheEntry *typcache, RangeType *r1, RangeType *r2)
+{
 	RangeBound	lower1,
 				lower2;
 	RangeBound	upper1,
@@ -969,18 +1016,12 @@ range_minus(PG_FUNCTION_ARGS)
 				cmp_u1l2,
 				cmp_u1u2;
 
-	/* 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));
-
 	range_deserialize(typcache, r1, &lower1, &upper1, &empty1);
 	range_deserialize(typcache, r2, &lower2, &upper2, &empty2);
 
 	/* if either is empty, r1 is the correct answer */
 	if (empty1 || empty2)
-		PG_RETURN_RANGE_P(r1);
+		return r1;
 
 	cmp_l1l2 = range_cmp_bounds(typcache, &lower1, &lower2);
 	cmp_l1u2 = range_cmp_bounds(typcache, &lower1, &upper2);
@@ -993,34 +1034,34 @@ range_minus(PG_FUNCTION_ARGS)
 				 errmsg("result of range difference would not be contiguous")));
 
 	if (cmp_l1u2 > 0 || cmp_u1l2 < 0)
-		PG_RETURN_RANGE_P(r1);
+		return r1;
 
 	if (cmp_l1l2 >= 0 && cmp_u1u2 <= 0)
-		PG_RETURN_RANGE_P(make_empty_range(typcache));
+		return make_empty_range(typcache);
 
 	if (cmp_l1l2 <= 0 && cmp_u1l2 >= 0 && cmp_u1u2 <= 0)
 	{
 		lower2.inclusive = !lower2.inclusive;
 		lower2.lower = false;	/* it will become the upper bound */
-		PG_RETURN_RANGE_P(make_range(typcache, &lower1, &lower2, false));
+		return make_range(typcache, &lower1, &lower2, false);
 	}
 
 	if (cmp_l1l2 >= 0 && cmp_u1u2 >= 0 && cmp_l1u2 <= 0)
 	{
 		upper2.inclusive = !upper2.inclusive;
 		upper2.lower = true;	/* it will become the lower bound */
-		PG_RETURN_RANGE_P(make_range(typcache, &upper2, &upper1, false));
+		return make_range(typcache, &upper2, &upper1, false);
 	}
 
 	elog(ERROR, "unexpected case in range_minus");
-	PG_RETURN_NULL();
+	return NULL;
 }
 
 /*
  * Set union.  If strict is true, it is an error that the two input ranges
  * are not adjacent or overlapping.
  */
-static RangeType *
+RangeType *
 range_union_internal(TypeCacheEntry *typcache, RangeType *r1, RangeType *r2,
 					 bool strict)
 {
@@ -1101,6 +1142,19 @@ range_intersect(PG_FUNCTION_ARGS)
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
 	RangeType  *r2 = PG_GETARG_RANGE_P(1);
 	TypeCacheEntry *typcache;
+
+	/* 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));
+
+	PG_RETURN_RANGE_P(range_intersect_internal(typcache, r1, r2));
+}
+
+RangeType *
+range_intersect_internal(TypeCacheEntry *typcache, const RangeType *r1, const RangeType *r2)
+{
 	RangeBound	lower1,
 				lower2;
 	RangeBound	upper1,
@@ -1110,17 +1164,11 @@ range_intersect(PG_FUNCTION_ARGS)
 	RangeBound *result_lower;
 	RangeBound *result_upper;
 
-	/* 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));
-
 	range_deserialize(typcache, r1, &lower1, &upper1, &empty1);
 	range_deserialize(typcache, r2, &lower2, &upper2, &empty2);
 
-	if (empty1 || empty2 || !DatumGetBool(range_overlaps(fcinfo)))
-		PG_RETURN_RANGE_P(make_empty_range(typcache));
+	if (empty1 || empty2 || !range_overlaps_internal(typcache, r1, r2))
+		return make_empty_range(typcache);
 
 	if (range_cmp_bounds(typcache, &lower1, &lower2) >= 0)
 		result_lower = &lower1;
@@ -1132,9 +1180,81 @@ range_intersect(PG_FUNCTION_ARGS)
 	else
 		result_upper = &upper2;
 
-	PG_RETURN_RANGE_P(make_range(typcache, result_lower, result_upper, false));
+	return make_range(typcache, result_lower, result_upper, false);
+}
+
+/* range, range -> range, range functions */
+
+/*
+ * range_split_internal - if r2 intersects the middle of r1, leaving non-empty
+ * ranges on both sides, then return true and set output1 and output2 to the
+ * results of r1 - r2 (in order). Otherwise return false and don't set output1
+ * or output2. Neither input range should be empty.
+ */
+bool
+range_split_internal(TypeCacheEntry *typcache, const RangeType *r1, const RangeType *r2,
+					 RangeType **output1, RangeType **output2)
+{
+	RangeBound	lower1,
+				lower2;
+	RangeBound	upper1,
+				upper2;
+	bool		empty1,
+				empty2;
+
+	range_deserialize(typcache, r1, &lower1, &upper1, &empty1);
+	range_deserialize(typcache, r2, &lower2, &upper2, &empty2);
+
+	if (range_cmp_bounds(typcache, &lower1, &lower2) < 0 &&
+		range_cmp_bounds(typcache, &upper1, &upper2) > 0)
+	{
+		/*
+		 * Need to invert inclusive/exclusive for the lower2 and upper2
+		 * points. They can't be infinite though. We're allowed to overwrite
+		 * these RangeBounds since they only exist locally.
+		 */
+		lower2.inclusive = !lower2.inclusive;
+		lower2.lower = false;
+		upper2.inclusive = !upper2.inclusive;
+		upper2.lower = true;
+
+		*output1 = make_range(typcache, &lower1, &lower2, false);
+		*output2 = make_range(typcache, &upper2, &upper1, false);
+		return true;
+	}
+
+	return false;
 }
 
+/* range -> range aggregate functions */
+
+Datum
+range_intersect_agg_transfn(PG_FUNCTION_ARGS)
+{
+	MemoryContext aggContext;
+	Oid			rngtypoid;
+	TypeCacheEntry *typcache;
+	RangeType  *result;
+	RangeType  *current;
+
+	if (!AggCheckCallContext(fcinfo, &aggContext))
+		elog(ERROR, "range_intersect_agg_transfn called in non-aggregate context");
+
+	rngtypoid = get_fn_expr_argtype(fcinfo->flinfo, 1);
+	if (!type_is_range(rngtypoid))
+		ereport(ERROR, (errmsg("range_intersect_agg must be called with a range")));
+
+	typcache = range_get_typcache(fcinfo, rngtypoid);
+
+	/* strictness ensures these are non-null */
+	result = PG_GETARG_RANGE_P(0);
+	current = PG_GETARG_RANGE_P(1);
+
+	result = range_intersect_internal(typcache, result, current);
+	PG_RETURN_RANGE_P(result);
+}
+
+
 /* Btree support */
 
 /* btree comparator */
@@ -1144,12 +1264,6 @@ range_cmp(PG_FUNCTION_ARGS)
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
 	RangeType  *r2 = PG_GETARG_RANGE_P(1);
 	TypeCacheEntry *typcache;
-	RangeBound	lower1,
-				lower2;
-	RangeBound	upper1,
-				upper2;
-	bool		empty1,
-				empty2;
 	int			cmp;
 
 	check_stack_depth();		/* recurses when subtype is a range type */
@@ -1160,6 +1274,28 @@ range_cmp(PG_FUNCTION_ARGS)
 
 	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, const RangeType *r1, const 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);
 
@@ -1177,10 +1313,7 @@ range_cmp(PG_FUNCTION_ARGS)
 			cmp = range_cmp_bounds(typcache, &upper1, &upper2);
 	}
 
-	PG_FREE_IF_COPY(r1, 0);
-	PG_FREE_IF_COPY(r2, 1);
-
-	PG_RETURN_INT32(cmp);
+	return cmp;
 }
 
 /* inequality operators using the range_cmp function */
@@ -1218,13 +1351,10 @@ range_gt(PG_FUNCTION_ARGS)
 
 /* Hash support */
 
-/* hash a range value */
-Datum
-hash_range(PG_FUNCTION_ARGS)
+uint32
+hash_range_internal(TypeCacheEntry *typcache, const RangeType *r)
 {
-	RangeType  *r = PG_GETARG_RANGE_P(0);
 	uint32		result;
-	TypeCacheEntry *typcache;
 	TypeCacheEntry *scache;
 	RangeBound	lower;
 	RangeBound	upper;
@@ -1233,10 +1363,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 +1404,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, const RangeType *r, Datum seed)
+{
+	uint64		result;
 	TypeCacheEntry *scache;
 	RangeBound	lower;
 	RangeBound	upper;
@@ -1300,10 +1433,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 +1471,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));
 }
 
 /*
@@ -1770,6 +1917,21 @@ range_get_flags(const RangeType *range)
 }
 
 /*
+ * range_has_flag: set whether a range has a specific flag.
+ *
+ * This lets expose some of our functions that just check flags
+ * to the rest of the code base (like to multiranges)
+ * without writing full-fledged *_internal versions.
+ */
+bool
+range_has_flag(const RangeType *r1, char flag)
+{
+	char		flags = range_get_flags(r1);
+
+	return flags & flag;
+}
+
+/*
  * range_set_contain_empty: set the RANGE_CONTAIN_EMPTY bit in the value.
  *
  * This is only needed in GiST operations, so we don't include a provision
@@ -1938,6 +2100,46 @@ range_cmp_bound_values(TypeCacheEntry *typcache, const RangeBound *b1,
 }
 
 /*
+ * qsort callback for sorting ranges.
+ *
+ * Two empty ranges compare equal; an empty range sorts to the left of any
+ * non-empty range.  Two non-empty ranges are sorted by lower bound first
+ * and by upper bound next.
+ */
+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.
  */
 RangeType *
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index 27bbb58f56..c6c079c623 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -2495,6 +2495,16 @@ type_is_range(Oid typid)
 }
 
 /*
+ * 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
  *
  *		Given the type OID, fetch its category and preferred-type status.
@@ -3149,7 +3159,7 @@ get_namespace_name_or_temp(Oid nspid)
 		return get_namespace_name(nspid);
 }
 
-/*				---------- PG_RANGE CACHE ----------				 */
+/*				---------- PG_RANGE CACHES ----------				 */
 
 /*
  * get_range_subtype
@@ -3202,6 +3212,56 @@ get_range_collation(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->rngmultitypid;
+		ReleaseSysCache(tp);
+		return result;
+	}
+	else
+		return InvalidOid;
+}
+
+/*
+ * get_range_multirange_subtype
+ *		Returns the subtype of a given multirange type
+ *
+ * Returns InvalidOid if the type is not a multirange type.
+ */
+Oid
+get_range_multirange_subtype(Oid multirangeOid)
+{
+	HeapTuple	tp;
+
+	tp = SearchSysCache1(RANGEMULTIRANGE, 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 53d9ddf159..7654331a59 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -651,6 +651,18 @@ static const struct cachedesc cacheinfo[] = {
 		},
 		64
 	},
+	{RangeRelationId,			/* RANGEMULTIRANGE */
+		RangeMultirangeTypidIndexId,
+		1,
+		{
+			Anum_pg_range_rngmultitypid,
+			0,
+			0,
+			0
+		},
+		4
+	},
+
 	{RangeRelationId,			/* RANGETYPE */
 		RangeTypidIndexId,
 		1,
diff --git a/src/backend/utils/cache/typcache.c b/src/backend/utils/cache/typcache.c
index 854f133f9b..160846cf10 100644
--- a/src/backend/utils/cache/typcache.c
+++ b/src/backend/utils/cache/typcache.c
@@ -286,6 +286,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);
@@ -302,6 +303,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 TypeCacheTypCallback(Datum arg, int cacheid, uint32 hashvalue);
 static void TypeCacheOpcCallback(Datum arg, int cacheid, uint32 hashvalue);
@@ -553,8 +557,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))
@@ -591,7 +595,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))
@@ -616,7 +620,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))
@@ -641,7 +645,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))
@@ -693,6 +697,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;
@@ -737,6 +748,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;
@@ -817,6 +835,16 @@ lookup_type_cache(Oid type_id, int flags)
 	}
 
 	/*
+	 * 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
 	 */
 	if ((flags & TYPECACHE_DOMAIN_BASE_INFO) &&
@@ -927,6 +955,22 @@ load_rangetype_info(TypeCacheEntry *typentry)
 	typentry->rngelemtype = lookup_type_cache(subtypeOid, 0);
 }
 
+/*
+ * load_multirangetype_info --- helper routine to set up multirange type
+ * information
+ */
+static void
+load_multirangetype_info(TypeCacheEntry *typentry)
+{
+	Oid			rangetypeOid;
+
+	rangetypeOid = get_range_multirange_subtype(typentry->type_id);
+	if (!OidIsValid(rangetypeOid))
+		elog(ERROR, "cache lookup failed for multirange type %u",
+			 typentry->type_id);
+
+	typentry->rngtype = lookup_type_cache(rangetypeOid, TYPECACHE_RANGE_INFO);
+}
 
 /*
  * load_domaintype_info --- helper routine to set up domain constraint info
@@ -1527,11 +1571,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
@@ -1574,6 +1618,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 78ed857203..ab24359981 100644
--- a/src/backend/utils/fmgr/funcapi.c
+++ b/src/backend/utils/fmgr/funcapi.c
@@ -35,6 +35,7 @@ typedef struct polymorphic_actuals
 	Oid			anyelement_type;	/* anyelement mapping, if known */
 	Oid			anyarray_type;	/* anyarray mapping, if known */
 	Oid			anyrange_type;	/* anyrange mapping, if known */
+	Oid			anymultirange_type;	/* anymultirange mapping, if known */
 } polymorphic_actuals;
 
 static void shutdown_MultiFuncCall(Datum arg);
@@ -46,6 +47,7 @@ static TypeFuncClass internal_get_result_type(Oid funcid,
 static void resolve_anyelement_from_others(polymorphic_actuals *actuals);
 static void resolve_anyarray_from_others(polymorphic_actuals *actuals);
 static void resolve_anyrange_from_others(polymorphic_actuals *actuals);
+static void resolve_anymultirange_from_others(polymorphic_actuals *actuals);
 static bool resolve_polymorphic_tupdesc(TupleDesc tupdesc,
 										oidvector *declared_args,
 										Node *call_expr);
@@ -503,6 +505,31 @@ resolve_anyelement_from_others(polymorphic_actuals *actuals)
 							format_type_be(range_base_type))));
 		actuals->anyelement_type = range_typelem;
 	}
+	else if (OidIsValid(actuals->anymultirange_type))
+	{
+		/* Use the element type based on the multirange type */
+		Oid			multirange_base_type = getBaseType(actuals->anymultirange_type);
+		Oid			multirange_typelem =
+			get_range_multirange_subtype(multirange_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(multirange_base_type))));
+
+		Oid			range_base_type = getBaseType(multirange_typelem);
+		Oid			range_typelem = get_range_subtype(range_base_type);
+
+		if (!OidIsValid(range_typelem))
+			ereport(ERROR,
+					(errcode(ERRCODE_DATATYPE_MISMATCH),
+					 errmsg("argument declared %s does not contain a range type but type %s",
+							"anymultirange",
+							format_type_be(range_base_type))));
+		actuals->anyelement_type = range_typelem;
+	}
 	else
 		elog(ERROR, "could not determine polymorphic type");
 }
@@ -540,10 +567,54 @@ static void
 resolve_anyrange_from_others(polymorphic_actuals *actuals)
 {
 	/*
-	 * We can't deduce a range type from other polymorphic inputs, because
-	 * there may be multiple range types with the same subtype.
+	 * We can't deduce a range type from other polymorphic array or base types,
+	 * because there may be multiple range types with the same subtype, but we
+	 * can deduce it from a polymorphic multirange type.
 	 */
-	elog(ERROR, "could not determine polymorphic type");
+	if (OidIsValid(actuals->anymultirange_type))
+	{
+		/* Use the element type based on the multirange type */
+		Oid			multirange_base_type = getBaseType(actuals->anymultirange_type);
+		Oid			multirange_typelem =
+			get_range_multirange_subtype(multirange_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(multirange_base_type))));
+		actuals->anyrange_type = multirange_typelem;
+	}
+	else
+		elog(ERROR, "could not determine polymorphic type");
+}
+
+/*
+ * Resolve actual type of ANYMULTIRANGE from other polymorphic inputs
+ */
+static void
+resolve_anymultirange_from_others(polymorphic_actuals *actuals)
+{
+	/*
+	 * We can't deduce a multirange type from polymorphic array or base types,
+	 * because there may be multiple range types with the same subtype, but we
+	 * can deduce it from a polymorphic range type.
+	 */
+	if (OidIsValid(actuals->anyrange_type))
+	{
+		Oid			range_base_type = getBaseType(actuals->anyrange_type);
+		Oid			multirange_typeid = get_range_multirange(range_base_type);
+
+		if (!OidIsValid(multirange_typeid))
+			ereport(ERROR,
+					(errcode(ERRCODE_UNDEFINED_OBJECT),
+					 errmsg("could not find multirange type for data type %s",
+							format_type_be(actuals->anyrange_type))));
+		actuals->anymultirange_type = multirange_typeid;
+	}
+	else
+		elog(ERROR, "could not determine polymorphic type");
 }
 
 /*
@@ -566,6 +637,7 @@ 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_anycompatible_result = false;
 	bool		have_anycompatible_array_result = false;
 	bool		have_anycompatible_range_result = false;
@@ -594,6 +666,10 @@ resolve_polymorphic_tupdesc(TupleDesc tupdesc, oidvector *declared_args,
 				have_polymorphic_result = true;
 				have_anyrange_result = true;
 				break;
+			case ANYMULTIRANGEOID:
+				have_polymorphic_result = true;
+				have_anymultirange_result = true;
+				break;
 			case ANYCOMPATIBLEOID:
 			case ANYCOMPATIBLENONARRAYOID:
 				have_polymorphic_result = true;
@@ -660,6 +736,15 @@ resolve_polymorphic_tupdesc(TupleDesc tupdesc, oidvector *declared_args,
 						return false;
 				}
 				break;
+			case ANYMULTIRANGEOID:
+				if (!OidIsValid(poly_actuals.anymultirange_type))
+				{
+					poly_actuals.anymultirange_type =
+						get_call_expr_argtype(call_expr, i);
+					if (!OidIsValid(poly_actuals.anymultirange_type))
+						return false;
+				}
+				break;
 			case ANYCOMPATIBLEOID:
 			case ANYCOMPATIBLENONARRAYOID:
 				if (!OidIsValid(anyc_actuals.anyelement_type))
@@ -703,6 +788,9 @@ resolve_polymorphic_tupdesc(TupleDesc tupdesc, oidvector *declared_args,
 	if (have_anyrange_result && !OidIsValid(poly_actuals.anyrange_type))
 		resolve_anyrange_from_others(&poly_actuals);
 
+	if (have_anymultirange_result && !OidIsValid(poly_actuals.anymultirange_type))
+		resolve_anymultirange_from_others(&poly_actuals);
+
 	if (have_anycompatible_result && !OidIsValid(anyc_actuals.anyelement_type))
 		resolve_anyelement_from_others(&anyc_actuals);
 
@@ -780,6 +868,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),
+								   poly_actuals.anymultirange_type,
+								   -1,
+								   0);
+				/* no collation should be attached to a multirange type */
+				break;
 			case ANYCOMPATIBLEOID:
 			case ANYCOMPATIBLENONARRAYOID:
 				TupleDescInitEntry(tupdesc, i + 1,
@@ -834,6 +930,7 @@ 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;
 	bool		have_anycompatible_result = false;
 	bool		have_anycompatible_array_result = false;
 	bool		have_anycompatible_range_result = false;
@@ -912,6 +1009,24 @@ resolve_polymorphic_argtypes(int numargs, Oid *argtypes, char *argmodes,
 					argtypes[i] = poly_actuals.anyrange_type;
 				}
 				break;
+			case ANYMULTIRANGEOID:
+				if (argmode == PROARGMODE_OUT || argmode == PROARGMODE_TABLE)
+				{
+					have_polymorphic_result = true;
+					have_anymultirange_result = true;
+				}
+				else
+				{
+					if (!OidIsValid(poly_actuals.anymultirange_type))
+					{
+						poly_actuals.anymultirange_type =
+							get_call_expr_argtype(call_expr, inargno);
+						if (!OidIsValid(poly_actuals.anymultirange_type))
+							return false;
+					}
+					argtypes[i] = poly_actuals.anymultirange_type;
+				}
+				break;
 			case ANYCOMPATIBLEOID:
 			case ANYCOMPATIBLENONARRAYOID:
 				if (argmode == PROARGMODE_OUT || argmode == PROARGMODE_TABLE)
@@ -988,6 +1103,9 @@ resolve_polymorphic_argtypes(int numargs, Oid *argtypes, char *argmodes,
 	if (have_anyrange_result && !OidIsValid(poly_actuals.anyrange_type))
 		resolve_anyrange_from_others(&poly_actuals);
 
+	if (have_anymultirange_result && !OidIsValid(poly_actuals.anymultirange_type))
+		resolve_anymultirange_from_others(&poly_actuals);
+
 	if (have_anycompatible_result && !OidIsValid(anyc_actuals.anyelement_type))
 		resolve_anyelement_from_others(&anyc_actuals);
 
@@ -1013,6 +1131,9 @@ resolve_polymorphic_argtypes(int numargs, Oid *argtypes, char *argmodes,
 			case ANYRANGEOID:
 				argtypes[i] = poly_actuals.anyrange_type;
 				break;
+			case ANYMULTIRANGEOID:
+				argtypes[i] = poly_actuals.anymultirange_type;
+				break;
 			case ANYCOMPATIBLEOID:
 			case ANYCOMPATIBLENONARRAYOID:
 				argtypes[i] = anyc_actuals.anyelement_type;
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index ced0681ec3..f8567ad9a2 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -271,7 +271,8 @@ static void dumpSearchPath(Archive *AH);
 static void binary_upgrade_set_type_oids_by_type_oid(Archive *fout,
 													 PQExpBuffer upgrade_buffer,
 													 Oid pg_type_oid,
-													 bool force_array_type);
+													 bool force_array_type,
+													 bool include_multirange_type);
 static bool binary_upgrade_set_type_oids_by_rel_oid(Archive *fout,
 													PQExpBuffer upgrade_buffer, Oid pg_rel_oid);
 static void binary_upgrade_set_pg_class_oids(Archive *fout,
@@ -1580,7 +1581,7 @@ selectDumpableType(TypeInfo *tyinfo, Archive *fout)
 	}
 
 	/* skip auto-generated array types */
-	if (tyinfo->isArray)
+	if (tyinfo->isArray || tyinfo->isMultirange)
 	{
 		tyinfo->dobj.objType = DO_DUMMY_TYPE;
 
@@ -4340,16 +4341,49 @@ append_depends_on_extension(Archive *fout,
 	}
 }
 
+static Oid
+get_next_possible_free_pg_type_oid(Archive *fout, PQExpBuffer upgrade_query)
+{
+	/*
+	 * If the old version didn't assign an array type, but the new version
+	 * does, we must select an unused type OID to assign.  This currently only
+	 * happens for domains, when upgrading pre-v11 to v11 and up.
+	 *
+	 * Note: local state here is kind of ugly, but we must have some, since we
+	 * mustn't choose the same unused OID more than once.
+	 */
+	static Oid	next_possible_free_oid = FirstNormalObjectId;
+	PGresult   *res;
+	bool		is_dup;
+
+	do
+	{
+		++next_possible_free_oid;
+		printfPQExpBuffer(upgrade_query,
+						  "SELECT EXISTS(SELECT 1 "
+						  "FROM pg_catalog.pg_type "
+						  "WHERE oid = '%u'::pg_catalog.oid);",
+						  next_possible_free_oid);
+		res = ExecuteSqlQueryForSingleRow(fout, upgrade_query->data);
+		is_dup = (PQgetvalue(res, 0, 0)[0] == 't');
+		PQclear(res);
+	} while (is_dup);
+
+	return next_possible_free_oid;
+}
 
 static void
 binary_upgrade_set_type_oids_by_type_oid(Archive *fout,
 										 PQExpBuffer upgrade_buffer,
 										 Oid pg_type_oid,
-										 bool force_array_type)
+										 bool force_array_type,
+										 bool include_multirange_type)
 {
 	PQExpBuffer upgrade_query = createPQExpBuffer();
 	PGresult   *res;
 	Oid			pg_type_array_oid;
+	Oid			pg_type_multirange_oid;
+	Oid			pg_type_multirange_array_oid;
 
 	appendPQExpBufferStr(upgrade_buffer, "\n-- For binary upgrade, must preserve pg_type oid\n");
 	appendPQExpBuffer(upgrade_buffer,
@@ -4370,33 +4404,7 @@ binary_upgrade_set_type_oids_by_type_oid(Archive *fout,
 	PQclear(res);
 
 	if (!OidIsValid(pg_type_array_oid) && force_array_type)
-	{
-		/*
-		 * If the old version didn't assign an array type, but the new version
-		 * does, we must select an unused type OID to assign.  This currently
-		 * only happens for domains, when upgrading pre-v11 to v11 and up.
-		 *
-		 * Note: local state here is kind of ugly, but we must have some,
-		 * since we mustn't choose the same unused OID more than once.
-		 */
-		static Oid	next_possible_free_oid = FirstNormalObjectId;
-		bool		is_dup;
-
-		do
-		{
-			++next_possible_free_oid;
-			printfPQExpBuffer(upgrade_query,
-							  "SELECT EXISTS(SELECT 1 "
-							  "FROM pg_catalog.pg_type "
-							  "WHERE oid = '%u'::pg_catalog.oid);",
-							  next_possible_free_oid);
-			res = ExecuteSqlQueryForSingleRow(fout, upgrade_query->data);
-			is_dup = (PQgetvalue(res, 0, 0)[0] == 't');
-			PQclear(res);
-		} while (is_dup);
-
-		pg_type_array_oid = next_possible_free_oid;
-	}
+		pg_type_array_oid = get_next_possible_free_pg_type_oid(fout, upgrade_query);
 
 	if (OidIsValid(pg_type_array_oid))
 	{
@@ -4407,6 +4415,46 @@ binary_upgrade_set_type_oids_by_type_oid(Archive *fout,
 						  pg_type_array_oid);
 	}
 
+	/*
+	 * Pre-set the multirange type oid and its own array type oid.
+	 */
+	if (include_multirange_type)
+	{
+		if (fout->remoteVersion >= 130000)
+		{
+			appendPQExpBuffer(upgrade_query,
+							  "SELECT t.oid, t.typarray "
+							  "FROM pg_catalog.pg_type t "
+							  "JOIN pg_catalog.pg_range r "
+							  "ON t.oid = r.rngmultitypid "
+							  "WHERE r.rngtypid = '%u'::pg_catalog.oid;",
+							  pg_type_oid);
+
+			res = ExecuteSqlQueryForSingleRow(fout, upgrade_query->data);
+
+			pg_type_multirange_oid = atooid(PQgetvalue(res, 0, PQfnumber(res, "oid")));
+			pg_type_multirange_array_oid = atooid(PQgetvalue(res, 0, PQfnumber(res, "typarray")));
+
+			PQclear(res);
+		}
+		else
+		{
+			pg_type_multirange_oid = get_next_possible_free_pg_type_oid(fout, upgrade_query);
+			pg_type_multirange_array_oid = get_next_possible_free_pg_type_oid(fout, upgrade_query);
+		}
+
+		appendPQExpBufferStr(upgrade_buffer,
+							 "\n-- For binary upgrade, must preserve multirange pg_type oid\n");
+		appendPQExpBuffer(upgrade_buffer,
+						  "SELECT pg_catalog.binary_upgrade_set_next_multirange_pg_type_oid('%u'::pg_catalog.oid);\n\n",
+						  pg_type_multirange_oid);
+		appendPQExpBufferStr(upgrade_buffer,
+							 "\n-- For binary upgrade, must preserve multirange pg_type array oid\n");
+		appendPQExpBuffer(upgrade_buffer,
+						  "SELECT pg_catalog.binary_upgrade_set_next_multirange_array_pg_type_oid('%u'::pg_catalog.oid);\n\n",
+						  pg_type_multirange_array_oid);
+	}
+
 	destroyPQExpBuffer(upgrade_query);
 }
 
@@ -4440,7 +4488,7 @@ binary_upgrade_set_type_oids_by_rel_oid(Archive *fout,
 	pg_type_oid = atooid(PQgetvalue(upgrade_res, 0, PQfnumber(upgrade_res, "crel")));
 
 	binary_upgrade_set_type_oids_by_type_oid(fout, upgrade_buffer,
-											 pg_type_oid, false);
+											 pg_type_oid, false, false);
 
 	if (!PQgetisnull(upgrade_res, 0, PQfnumber(upgrade_res, "trel")))
 	{
@@ -4989,6 +5037,11 @@ getTypes(Archive *fout, int *numTypes)
 		else
 			tyinfo[i].isArray = false;
 
+		if (tyinfo[i].typtype == 'm')
+			tyinfo[i].isMultirange = true;
+		else
+			tyinfo[i].isMultirange = false;
+
 		/* Decide whether we want to dump it */
 		selectDumpableType(&tyinfo[i], fout);
 
@@ -8103,9 +8156,12 @@ getProcLangs(Archive *fout, int *numProcLangs)
 
 /*
  * getCasts
- *	  get basic information about every cast in the system
+ *	  get basic information about most casts in the system
  *
  * numCasts is set to the number of casts read in
+ *
+ * Skip casts from a range to its multirange, since we'll create those
+ * automatically.
  */
 CastInfo *
 getCasts(Archive *fout, int *numCasts)
@@ -8123,7 +8179,20 @@ getCasts(Archive *fout, int *numCasts)
 	int			i_castcontext;
 	int			i_castmethod;
 
-	if (fout->remoteVersion >= 80400)
+	if (fout->remoteVersion >= 130000)
+	{
+		appendPQExpBufferStr(query, "SELECT tableoid, oid, "
+							 "castsource, casttarget, castfunc, castcontext, "
+							 "castmethod "
+							 "FROM pg_cast c "
+							 "WHERE NOT EXISTS ( "
+							 "SELECT 1 FROM pg_range r "
+							 "WHERE c.castsource = r.rngtypid "
+							 "AND c.casttarget = r.rngmultitypid "
+							 ") "
+							 "ORDER BY 3,4");
+	}
+	else if (fout->remoteVersion >= 80400)
 	{
 		appendPQExpBufferStr(query, "SELECT tableoid, oid, "
 							 "castsource, casttarget, castfunc, castcontext, "
@@ -10311,7 +10380,7 @@ dumpEnumType(Archive *fout, TypeInfo *tyinfo)
 	if (dopt->binary_upgrade)
 		binary_upgrade_set_type_oids_by_type_oid(fout, q,
 												 tyinfo->dobj.catId.oid,
-												 false);
+												 false, false);
 
 	appendPQExpBuffer(q, "CREATE TYPE %s AS ENUM (",
 					  qualtypname);
@@ -10437,7 +10506,7 @@ dumpRangeType(Archive *fout, TypeInfo *tyinfo)
 	if (dopt->binary_upgrade)
 		binary_upgrade_set_type_oids_by_type_oid(fout, q,
 												 tyinfo->dobj.catId.oid,
-												 false);
+												 false, true);
 
 	appendPQExpBuffer(q, "CREATE TYPE %s AS RANGE (",
 					  qualtypname);
@@ -10543,7 +10612,7 @@ dumpUndefinedType(Archive *fout, TypeInfo *tyinfo)
 	if (dopt->binary_upgrade)
 		binary_upgrade_set_type_oids_by_type_oid(fout, q,
 												 tyinfo->dobj.catId.oid,
-												 false);
+												 false, false);
 
 	appendPQExpBuffer(q, "CREATE TYPE %s;\n",
 					  qualtypname);
@@ -10748,7 +10817,7 @@ dumpBaseType(Archive *fout, TypeInfo *tyinfo)
 	if (dopt->binary_upgrade)
 		binary_upgrade_set_type_oids_by_type_oid(fout, q,
 												 tyinfo->dobj.catId.oid,
-												 false);
+												 false, false);
 
 	appendPQExpBuffer(q,
 					  "CREATE TYPE %s (\n"
@@ -10935,7 +11004,8 @@ dumpDomain(Archive *fout, TypeInfo *tyinfo)
 	if (dopt->binary_upgrade)
 		binary_upgrade_set_type_oids_by_type_oid(fout, q,
 												 tyinfo->dobj.catId.oid,
-												 true); /* force array type */
+												 true,		/* force array type */
+												 false);	/* force multirange type */
 
 	qtypname = pg_strdup(fmtId(tyinfo->dobj.name));
 	qualtypname = pg_strdup(fmtQualifiedDumpable(tyinfo));
@@ -11123,7 +11193,7 @@ dumpCompositeType(Archive *fout, TypeInfo *tyinfo)
 	{
 		binary_upgrade_set_type_oids_by_type_oid(fout, q,
 												 tyinfo->dobj.catId.oid,
-												 false);
+												 false, false);
 		binary_upgrade_set_pg_class_oids(fout, q, tyinfo->typrelid, false);
 	}
 
@@ -11397,7 +11467,7 @@ dumpShellType(Archive *fout, ShellTypeInfo *stinfo)
 	if (dopt->binary_upgrade)
 		binary_upgrade_set_type_oids_by_type_oid(fout, q,
 												 stinfo->baseType->dobj.catId.oid,
-												 false);
+												 false, false);
 
 	appendPQExpBuffer(q, "CREATE TYPE %s;\n",
 					  fmtQualifiedDumpable(stinfo));
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index e0c6444ef6..90a93b4cb2 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -176,6 +176,7 @@ typedef struct _typeInfo
 	char		typrelkind;		/* 'r', 'v', 'c', etc */
 	char		typtype;		/* 'b', 'c', etc */
 	bool		isArray;		/* true if auto-generated array type */
+	bool		isMultirange;	/* true if auto-generated multirange type */
 	bool		isDefined;		/* true if typisdefined */
 	/* If needed, we'll create a "shell type" entry for it; link that here: */
 	struct _shellTypeInfo *shellType;	/* shell-type entry, or NULL */
diff --git a/src/include/access/tupmacs.h b/src/include/access/tupmacs.h
index 70157cf90a..c262265b95 100644
--- a/src/include/access/tupmacs.h
+++ b/src/include/access/tupmacs.h
@@ -139,8 +139,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/binary_upgrade.h b/src/include/catalog/binary_upgrade.h
index 12d94fe1b3..01d95117cd 100644
--- a/src/include/catalog/binary_upgrade.h
+++ b/src/include/catalog/binary_upgrade.h
@@ -16,6 +16,8 @@
 
 extern PGDLLIMPORT Oid binary_upgrade_next_pg_type_oid;
 extern PGDLLIMPORT Oid binary_upgrade_next_array_pg_type_oid;
+extern PGDLLIMPORT Oid binary_upgrade_next_mrng_pg_type_oid;
+extern PGDLLIMPORT Oid binary_upgrade_next_mrng_array_pg_type_oid;
 extern PGDLLIMPORT Oid binary_upgrade_next_toast_pg_type_oid;
 
 extern PGDLLIMPORT Oid binary_upgrade_next_heap_pg_class_oid;
diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h
index 8be303870f..566f9364c5 100644
--- a/src/include/catalog/indexing.h
+++ b/src/include/catalog/indexing.h
@@ -327,6 +327,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_rngmultitypid_index, 8001, on pg_range using btree(rngmultitypid 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_aggregate.dat b/src/include/catalog/pg_aggregate.dat
index ffabe275c0..03bab74253 100644
--- a/src/include/catalog/pg_aggregate.dat
+++ b/src/include/catalog/pg_aggregate.dat
@@ -538,6 +538,17 @@
   aggtransfn => 'bytea_string_agg_transfn',
   aggfinalfn => 'bytea_string_agg_finalfn', aggtranstype => 'internal' },
 
+# range
+{ aggfnoid => 'range_intersect_agg(anyrange)',
+  aggtransfn => 'range_intersect_agg_transfn',
+  aggcombinefn => 'range_intersect_agg_transfn', aggtranstype => 'anyrange' },
+{ aggfnoid => 'range_intersect_agg(anymultirange)',
+  aggtransfn => 'multirange_intersect_agg_transfn',
+  aggcombinefn => 'multirange_intersect_agg_transfn', aggtranstype => 'anymultirange' },
+{ aggfnoid => 'range_agg(anyrange)', aggtransfn => 'range_agg_transfn',
+  aggfinalfn => 'range_agg_finalfn', aggfinalextra => 't',
+  aggtranstype => 'internal' },
+
 # json
 { aggfnoid => 'json_agg', aggtransfn => 'json_agg_transfn',
   aggfinalfn => 'json_agg_finalfn', aggtranstype => 'internal' },
diff --git a/src/include/catalog/pg_amop.dat b/src/include/catalog/pg_amop.dat
index 11aaa519c8..827ac4f03c 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 75c0152b66..cf0cac5641 100644
--- a/src/include/catalog/pg_amproc.dat
+++ b/src/include/catalog/pg_amproc.dat
@@ -285,6 +285,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' },
 
@@ -445,6 +447,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',
@@ -1263,12 +1270,13 @@
   amprocrighttype => 'anyrange', amprocnum => '4',
   amproc => 'brin_inclusion_union' },
 { amprocfamily => 'brin/range_inclusion_ops', amproclefttype => 'anyrange',
-  amprocrighttype => 'anyrange', amprocnum => '11', amproc => 'range_merge' },
+  amprocrighttype => 'anyrange', amprocnum => '11',
+  amproc => 'range_merge(anyrange,anyrange)' },
 { amprocfamily => 'brin/range_inclusion_ops', amproclefttype => 'anyrange',
   amprocrighttype => 'anyrange', amprocnum => '13',
   amproc => 'range_contains' },
 { amprocfamily => 'brin/range_inclusion_ops', amproclefttype => 'anyrange',
-  amprocrighttype => 'anyrange', amprocnum => '14', amproc => 'isempty' },
+  amprocrighttype => 'anyrange', amprocnum => '14', amproc => 'isempty(anyrange)' },
 
 # minmax pg_lsn
 { amprocfamily => 'brin/pg_lsn_minmax_ops', amproclefttype => 'pg_lsn',
diff --git a/src/include/catalog/pg_cast.dat b/src/include/catalog/pg_cast.dat
index 01c5328ddd..f555af90ac 100644
--- a/src/include/catalog/pg_cast.dat
+++ b/src/include/catalog/pg_cast.dat
@@ -526,4 +526,17 @@
 { castsource => 'jsonb', casttarget => 'float8', castfunc => 'float8(jsonb)',
   castcontext => 'e', castmethod => 'f' },
 
+# range to multirange
+{ castsource => 'int4range', casttarget => 'int4multirange', castfunc => 'int4multirange(int4range)',
+  castcontext => 'e', castmethod => 'f' },
+{ castsource => 'int8range', casttarget => 'int8multirange', castfunc => 'int8multirange(int8range)',
+  castcontext => 'e', castmethod => 'f' },
+{ castsource => 'numrange', casttarget => 'nummultirange', castfunc => 'nummultirange(numrange)',
+  castcontext => 'e', castmethod => 'f' },
+{ castsource => 'daterange', casttarget => 'datemultirange', castfunc => 'datemultirange(daterange)',
+  castcontext => 'e', castmethod => 'f' },
+{ castsource => 'tsrange', casttarget => 'tsmultirange', castfunc => 'tsmultirange(tsrange)',
+  castcontext => 'e', castmethod => 'f' },
+{ castsource => 'tstzrange', casttarget => 'tstzmultirange', castfunc => 'tstzmultirange(tstzrange)',
+  castcontext => 'e', castmethod => 'f' },
 ]
diff --git a/src/include/catalog/pg_opclass.dat b/src/include/catalog/pg_opclass.dat
index ab2f50c9eb..eebebbcdb4 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 7c135da3b1..24f4a32203 100644
--- a/src/include/catalog/pg_operator.dat
+++ b/src/include/catalog/pg_operator.dat
@@ -3297,5 +3297,174 @@
   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' },
+{ oid => '8073', oid_symbol => 'OID_RANGE_OVERLAPS_MULTIRANGE_OP',
+  descr => 'overlaps',
+  oprname => '&&', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '&&(anymultirange,anyrange)',
+  oprcode => 'range_overlaps_multirange', oprrest => 'areasel',
+  oprjoin => 'areajoinsel' },
+{ oid => '8074', oid_symbol => 'OID_MULTIRANGE_OVERLAPS_RANGE_OP', descr => 'contains',
+  oprname => '&&', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcom => '&&(anyrange,anymultirange)',
+  oprcode => 'multirange_overlaps_range', oprrest => 'areasel',
+  oprjoin => 'areajoinsel' },
+{ oid => '8075', oid_symbol => 'OID_MULTIRANGE_OVERLAPS_MULTIRANGE_OP', descr => 'contains',
+  oprname => '&&', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '&&(anymultirange,anymultirange)',
+  oprcode => 'multirange_overlaps_multirange', oprrest => 'areasel',
+  oprjoin => 'areajoinsel' },
+{ oid => '8064', oid_symbol => 'OID_MULTIRANGE_CONTAINS_ELEM_OP',
+  descr => 'contains',
+  oprname => '@>', oprleft => 'anymultirange', oprright => 'anyelement',
+  oprresult => 'bool', oprcom => '<@(anyelement,anymultirange)',
+  oprcode => 'multirange_contains_elem', oprrest => 'contsel',
+  oprjoin => 'contjoinsel' },
+{ oid => '8065', oid_symbol => 'OID_MULTIRANGE_CONTAINS_RANGE_OP', descr => 'contains',
+  oprname => '@>', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcom => '<@(anyrange,anymultirange)',
+  oprcode => 'multirange_contains_range', oprrest => 'contsel',
+  oprjoin => 'contjoinsel' },
+{ oid => '8066', oid_symbol => 'OID_MULTIRANGE_CONTAINS_MULTIRANGE_OP', descr => 'contains',
+  oprname => '@>', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '<@(anymultirange,anymultirange)',
+  oprcode => 'multirange_contains_multirange', oprrest => 'contsel',
+  oprjoin => 'contjoinsel' },
+{ oid => '8067', oid_symbol => 'OID_MULTIRANGE_ELEM_CONTAINED_OP',
+  descr => 'is contained by',
+  oprname => '<@', oprleft => 'anyelement', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '@>(anymultirange,anyelement)',
+  oprcode => 'elem_contained_by_multirange', oprrest => 'contsel',
+  oprjoin => 'contjoinsel' },
+{ oid => '8068', oid_symbol => 'OID_MULTIRANGE_RANGE_CONTAINED_OP',
+  descr => 'is contained by',
+  oprname => '<@', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '@>(anymultirange,anyrange)',
+  oprcode => 'range_contained_by_multirange', oprrest => 'contsel',
+  oprjoin => 'contjoinsel' },
+{ oid => '8069', oid_symbol => 'OID_MULTIRANGE_MULTIRANGE_CONTAINED_OP',
+  descr => 'is contained by',
+  oprname => '<@', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '@>(anymultirange,anymultirange)',
+  oprcode => 'multirange_contained_by_multirange', oprrest => 'contsel',
+  oprjoin => 'contjoinsel' },
+{ oid => '8106', oid_symbol => 'OID_RANGE_OVERLAPS_LEFT_MULTIRANGE_OP',
+  descr => 'overlaps or is left of',
+  oprname => '&<', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcode => 'range_overleft_multirange', oprrest => 'scalarltsel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8107', oid_symbol => 'OID_MULTIRANGE_OVERLAPS_LEFT_RANGE_OP',
+  descr => 'overlaps or is left of',
+  oprname => '&<', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcode => 'multirange_overleft_range', oprrest => 'scalarltsel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8108', oid_symbol => 'OID_MULTIRANGE_OVERLAPS_LEFT_MULTIRANGE_OP',
+  descr => 'overlaps or is left of',
+  oprname => '&<', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcode => 'multirange_overleft_multirange', oprrest => 'scalarltsel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8109', oid_symbol => 'OID_RANGE_OVERLAPS_RIGHT_MULTIRANGE_OP',
+  descr => 'overlaps or is right of',
+  oprname => '&>', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcode => 'range_overright_multirange', oprrest => 'scalargtsel',
+  oprjoin => 'scalargtjoinsel' },
+{ oid => '8110', oid_symbol => 'OID_MULTIRANGE_OVERLAPS_RIGHT_RANGE_OP',
+  descr => 'overlaps or is right of',
+  oprname => '&>', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcode => 'multirange_overright_range', oprrest => 'scalargtsel',
+  oprjoin => 'scalargtjoinsel' },
+{ oid => '8111', oid_symbol => 'OID_MULTIRANGE_OVERLAPS_RIGHT_MULTIRANGE_OP',
+  descr => 'overlaps or is right of',
+  oprname => '&>', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcode => 'multirange_overright_multirange', oprrest => 'scalargtsel',
+  oprjoin => 'scalargtjoinsel' },
+{ oid => '8091', oid_symbol => 'OID_RANGE_ADJACENT_MULTIRANGE_OP', descr => 'is adjacent to',
+  oprname => '-|-', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '-|-(anymultirange,anyrange)',
+  oprcode => 'range_adjacent_multirange', oprrest => 'scalarltsel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8092', oid_symbol => 'OID_MULTIRANGE_ADJACENT_RANGE_OP', descr => 'is adjacent to',
+  oprname => '-|-', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcom => '-|-(anyrange,anymultirange)',
+  oprcode => 'multirange_adjacent_range', oprrest => 'scalarltsel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8093', oid_symbol => 'OID_MULTIRANGE_ADJACENT_MULTIRANGE_OP', descr => 'is adjacent to',
+  oprname => '-|-', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '-|-(anymultirange,anymultirange)',
+  oprcode => 'multirange_adjacent_multirange', oprrest => 'scalarltsel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8117', descr => 'multirange union',
+  oprname => '+', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'anymultirange', oprcom => '+(anymultirange,anymultirange)',
+  oprcode => 'multirange_union' },
+{ oid => '8123', descr => 'multirange minus',
+  oprname => '-', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'anymultirange', oprcode => 'multirange_minus' },
+{ oid => '8129', descr => 'multirange intersect',
+  oprname => '*', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'anymultirange', oprcom => '*(anymultirange,anymultirange)',
+  oprcode => 'multirange_intersect' },
+{ oid => '8082', oid_symbol => 'OID_RANGE_BEFORE_MULTIRANGE_OP', descr => 'is left of',
+  oprname => '<<', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '>>(anymultirange,anyrange)',
+  oprcode => 'range_before_multirange', oprrest => 'scalarltsel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8083', oid_symbol => 'OID_MULTIRANGE_BEFORE_RANGE_OP', descr => 'is left of',
+  oprname => '<<', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcom => '>>(anyrange,anymultirange)',
+  oprcode => 'multirange_before_range', oprrest => 'scalarltsel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8084', oid_symbol => 'OID_MULTIRANGE_BEFORE_MULTIRANGE_OP', descr => 'is left of',
+  oprname => '<<', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '>>(anymultirange,anymultirange)',
+  oprcode => 'multirange_before_multirange', oprrest => 'scalarltsel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8085', oid_symbol => 'OID_RANGE_AFTER_MULTIRANGE_OP', descr => 'is right of',
+  oprname => '>>', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '<<(anymultirange,anyrange)',
+  oprcode => 'range_after_multirange', oprrest => 'scalargtsel',
+  oprjoin => 'scalargtjoinsel' },
+{ oid => '8086', oid_symbol => 'OID_MULTIRANGE_AFTER_RANGE_OP', descr => 'is right of',
+  oprname => '>>', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcom => '<<(anyrange,anymultirange)',
+  oprcode => 'multirange_after_range', oprrest => 'scalargtsel',
+  oprjoin => 'scalargtjoinsel' },
+{ oid => '8087', oid_symbol => 'OID_MULTIRANGE_AFTER_MULTIRANGE_OP', descr => 'is right of',
+  oprname => '>>', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '<<(anymultirange,anymultirange)',
+  oprcode => 'multirange_after_multirange', oprrest => 'scalargtsel',
+  oprjoin => 'scalargtjoinsel' },
 
 ]
diff --git a/src/include/catalog/pg_opfamily.dat b/src/include/catalog/pg_opfamily.dat
index 26227df216..901fd7b303 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 87d25d4a4b..b3b208ca3f 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -9695,6 +9695,10 @@
   descr => 'the smallest range which includes both of the given ranges',
   proname => 'range_merge', prorettype => 'anyrange',
   proargtypes => 'anyrange anyrange', prosrc => 'range_merge' },
+{ oid => '8143',
+  descr => 'the smallest range which includes the whole multirange',
+  proname => 'range_merge', prorettype => 'anyrange',
+  proargtypes => 'anymultirange', prosrc => 'range_merge_from_multirange' },
 { oid => '3868',
   proname => 'range_intersect', prorettype => 'anyrange',
   proargtypes => 'anyrange anyrange', prosrc => 'range_intersect' },
@@ -9744,6 +9748,13 @@
 { oid => '3169', descr => 'restriction selectivity for range operators',
   proname => 'rangesel', provolatile => 's', prorettype => 'float8',
   proargtypes => 'internal oid internal int4', prosrc => 'rangesel' },
+{ oid => '8133', descr => 'range aggregate by intersecting',
+  proname => 'range_intersect_agg_transfn', prorettype => 'anyrange',
+  proargtypes => 'anyrange anyrange', prosrc => 'range_intersect_agg_transfn'},
+{ oid => '8134', descr => 'range aggregate by intersecting',
+  proname => 'range_intersect_agg', prokind => 'a', proisstrict => 'f',
+  prorettype => 'anyrange', proargtypes => 'anyrange',
+  prosrc => 'aggregate_dummy'},
 
 { oid => '3914', descr => 'convert an int4 range to canonical form',
   proname => 'int4range_canonical', prorettype => 'int4range',
@@ -9812,6 +9823,258 @@
   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 => '8094', descr => 'lower bound of multirange',
+  proname => 'lower', prorettype => 'anyelement', proargtypes => 'anymultirange',
+  prosrc => 'multirange_lower' },
+{ oid => '8095', descr => 'upper bound of multirange',
+  proname => 'upper', prorettype => 'anyelement', proargtypes => 'anymultirange',
+  prosrc => 'multirange_upper' },
+{ oid => '8057', descr => 'is the multirange empty?',
+  proname => 'isempty', prorettype => 'bool',
+  proargtypes => 'anymultirange', prosrc => 'multirange_empty' },
+{ oid => '8096', descr => 'is the multirange\'s lower bound inclusive?',
+  proname => 'lower_inc', prorettype => 'bool', proargtypes => 'anymultirange',
+  prosrc => 'multirange_lower_inc' },
+{ oid => '8097', descr => 'is the multirange\'s upper bound inclusive?',
+  proname => 'upper_inc', prorettype => 'bool', proargtypes => 'anymultirange',
+  prosrc => 'multirange_upper_inc' },
+{ oid => '8098', descr => 'is the multirange\'s lower bound infinite?',
+  proname => 'lower_inf', prorettype => 'bool', proargtypes => 'anymultirange',
+  prosrc => 'multirange_lower_inf' },
+{ oid => '8099', descr => 'is the multirange\'s upper bound infinite?',
+  proname => 'upper_inf', prorettype => 'bool', proargtypes => 'anymultirange',
+  prosrc => 'multirange_upper_inf' },
+{ oid => '8008', descr => 'multirange typanalyze',
+  proname => 'multirange_typanalyze', provolatile => 's', prorettype => 'bool',
+  proargtypes => 'internal', prosrc => 'multirange_typanalyze' },
+{ oid => '8033',
+  proname => 'multirange_eq', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_eq' },
+{ oid => '8034',
+  proname => 'multirange_ne', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_ne' },
+{ oid => '8070',
+  proname => 'range_overlaps_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_overlaps_multirange' },
+{ oid => '8071',
+  proname => 'multirange_overlaps_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_overlaps_range' },
+{ oid => '8072',
+  proname => 'multirange_overlaps_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_overlaps_multirange' },
+{ oid => '8058',
+  proname => 'multirange_contains_elem', prorettype => 'bool',
+  proargtypes => 'anymultirange anyelement', prosrc => 'multirange_contains_elem' },
+{ oid => '8059',
+  proname => 'multirange_contains_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_contains_range' },
+{ oid => '8060',
+  proname => 'multirange_contains_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_contains_multirange' },
+{ oid => '8061',
+  proname => 'elem_contained_by_multirange', prorettype => 'bool',
+  proargtypes => 'anyelement anymultirange', prosrc => 'elem_contained_by_multirange' },
+{ oid => '8062',
+  proname => 'range_contained_by_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_contained_by_multirange' },
+{ oid => '8063',
+  proname => 'multirange_contained_by_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_contained_by_multirange' },
+{ oid => '8088',
+  proname => 'range_adjacent_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_adjacent_multirange' },
+{ oid => '8089',
+  proname => 'multirange_adjacent_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_adjacent_multirange' },
+{ oid => '8090',
+  proname => 'multirange_adjacent_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_adjacent_range' },
+{ oid => '8076',
+  proname => 'range_before_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_before_multirange' },
+{ oid => '8077',
+  proname => 'multirange_before_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_before_range' },
+{ oid => '8078',
+  proname => 'multirange_before_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_before_multirange' },
+{ oid => '8079',
+  proname => 'range_after_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_after_multirange' },
+{ oid => '8080',
+  proname => 'multirange_after_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_after_range' },
+{ oid => '8081',
+  proname => 'multirange_after_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_after_multirange' },
+{ oid => '8100',
+  proname => 'range_overleft_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_overleft_multirange' },
+{ oid => '8101',
+  proname => 'multirange_overleft_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_overleft_range' },
+{ oid => '8102',
+  proname => 'multirange_overleft_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_overleft_multirange' },
+{ oid => '8103',
+  proname => 'range_overright_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_overright_multirange' },
+{ oid => '8104',
+  proname => 'multirange_overright_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_overright_range' },
+{ oid => '8105',
+  proname => 'multirange_overright_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_overright_multirange' },
+{ oid => '8114',
+  proname => 'multirange_union', prorettype => 'anymultirange',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_union' },
+{ oid => '8120',
+  proname => 'multirange_minus', prorettype => 'anymultirange',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_minus' },
+{ oid => '8126',
+  proname => 'multirange_intersect', prorettype => 'anymultirange',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_intersect' },
+{ oid => '8022', descr => 'less-equal-greater',
+  proname => 'multirange_cmp', prorettype => 'int4',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_cmp' },
+{ oid => '8023',
+  proname => 'multirange_lt', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_lt' },
+{ oid => '8024',
+  proname => 'multirange_le', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_le' },
+{ oid => '8025',
+  proname => 'multirange_ge', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_ge' },
+{ oid => '8026',
+  proname => 'multirange_gt', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_gt' },
+{ oid => '8038', descr => 'hash a multirange',
+  proname => 'hash_multirange', prorettype => 'int4', proargtypes => 'anymultirange',
+  prosrc => 'hash_multirange' },
+{ oid => '8039', descr => 'hash a multirange',
+  proname => 'hash_multirange_extended', prorettype => 'int8',
+  proargtypes => 'anymultirange int8', prosrc => 'hash_multirange_extended' },
+
+{ oid => '8045', descr => 'int4multirange constructor',
+  proname => 'int4multirange', proisstrict => 'f',
+  prorettype => 'int4multirange', proargtypes => '',
+  prosrc => 'multirange_constructor0' },
+{ oid => '8146', descr => 'int4multirange constructor',
+  proname => 'int4multirange', proisstrict => 't',
+  prorettype => 'int4multirange', proargtypes => 'int4range',
+  prosrc => 'multirange_constructor1' },
+{ oid => '8046', descr => 'int4multirange constructor',
+  proname => 'int4multirange', provariadic => 'int4range', proisstrict => 'f',
+  prorettype => 'int4multirange', proargtypes => '_int4range',
+  proallargtypes => '{_int4range}', proargmodes => '{v}',
+  prosrc => 'multirange_constructor2' },
+{ oid => '8047', descr => 'nummultirange constructor',
+  proname => 'nummultirange', proisstrict => 'f',
+  prorettype => 'nummultirange', proargtypes => '',
+  prosrc => 'multirange_constructor0' },
+{ oid => '8147', descr => 'nummultirange constructor',
+  proname => 'nummultirange', proisstrict => 't',
+  prorettype => 'nummultirange', proargtypes => 'numrange',
+  prosrc => 'multirange_constructor1' },
+{ oid => '8048', descr => 'nummultirange constructor',
+  proname => 'nummultirange', provariadic => 'numrange', proisstrict => 'f',
+  prorettype => 'nummultirange', proargtypes => '_numrange',
+  proallargtypes => '{_numrange}', proargmodes => '{v}',
+  prosrc => 'multirange_constructor2' },
+{ oid => '8049', descr => 'tsmultirange constructor',
+  proname => 'tsmultirange', proisstrict => 'f',
+  prorettype => 'tsmultirange', proargtypes => '',
+  prosrc => 'multirange_constructor0' },
+{ oid => '8148', descr => 'tsmultirange constructor',
+  proname => 'tsmultirange', proisstrict => 't',
+  prorettype => 'tsmultirange', proargtypes => 'tsrange',
+  prosrc => 'multirange_constructor1' },
+{ oid => '8050', descr => 'tsmultirange constructor',
+  proname => 'tsmultirange', provariadic => 'tsrange', proisstrict => 'f',
+  prorettype => 'tsmultirange', proargtypes => '_tsrange',
+  proallargtypes => '{_tsrange}', proargmodes => '{v}',
+  prosrc => 'multirange_constructor2' },
+{ oid => '8051', descr => 'tstzmultirange constructor',
+  proname => 'tstzmultirange', proisstrict => 'f',
+  prorettype => 'tstzmultirange', proargtypes => '',
+  prosrc => 'multirange_constructor0' },
+{ oid => '8149', descr => 'tstzmultirange constructor',
+  proname => 'tstzmultirange', proisstrict => 't',
+  prorettype => 'tstzmultirange', proargtypes => 'tstzrange',
+  prosrc => 'multirange_constructor1' },
+{ oid => '8052', descr => 'tstzmultirange constructor',
+  proname => 'tstzmultirange', provariadic => 'tstzrange', proisstrict => 'f',
+  prorettype => 'tstzmultirange', proargtypes => '_tstzrange',
+  proallargtypes => '{_tstzrange}', proargmodes => '{v}',
+  prosrc => 'multirange_constructor2' },
+{ oid => '8053', descr => 'datemultirange constructor',
+  proname => 'datemultirange', proisstrict => 'f',
+  prorettype => 'datemultirange', proargtypes => '',
+  prosrc => 'multirange_constructor0' },
+{ oid => '8150', descr => 'datemultirange constructor',
+  proname => 'datemultirange', proisstrict => 't',
+  prorettype => 'datemultirange', proargtypes => 'daterange',
+  prosrc => 'multirange_constructor1' },
+{ oid => '8054', descr => 'datemultirange constructor',
+  proname => 'datemultirange', provariadic => 'daterange', proisstrict => 'f',
+  prorettype => 'datemultirange', proargtypes => '_daterange',
+  proallargtypes => '{_daterange}', proargmodes => '{v}',
+  prosrc => 'multirange_constructor2' },
+{ oid => '8055', descr => 'int8multirange constructor',
+  proname => 'int8multirange', proisstrict => 'f',
+  prorettype => 'int8multirange', proargtypes => '',
+  prosrc => 'multirange_constructor0' },
+{ oid => '8151', descr => 'int8multirange constructor',
+  proname => 'int8multirange', proisstrict => 't',
+  prorettype => 'int8multirange', proargtypes => 'int8range',
+  prosrc => 'multirange_constructor1' },
+{ oid => '8056', descr => 'int8multirange constructor',
+  proname => 'int8multirange', provariadic => 'int8range', proisstrict => 'f',
+  prorettype => 'int8multirange', proargtypes => '_int8range',
+  proallargtypes => '{_int8range}', proargmodes => '{v}',
+  prosrc => 'multirange_constructor2' },
+{ oid => '8152', descr => 'anymultirange cast',
+  proname => 'multirange', proisstrict => 't',
+  prorettype => 'anymultirange', proargtypes => 'anyrange',
+  prosrc => 'multirange_constructor1' },
+{ oid => '8130', descr => 'aggregate transition function',
+  proname => 'range_agg_transfn', proisstrict => 'f', prorettype => 'internal',
+  proargtypes => 'internal anyrange', prosrc => 'range_agg_transfn' },
+{ oid => '8131', descr => 'aggregate final function',
+  proname => 'range_agg_finalfn', proisstrict => 'f', prorettype => 'anymultirange',
+  proargtypes => 'internal anyrange', prosrc => 'range_agg_finalfn' },
+{ oid => '8132', descr => 'combine aggregate input into a multirange',
+  proname => 'range_agg', prokind => 'a', proisstrict => 'f',
+  prorettype => 'anymultirange', proargtypes => 'anyrange',
+  prosrc => 'aggregate_dummy' },
+{ oid => '8135', descr => 'range aggregate by intersecting',
+  proname => 'multirange_intersect_agg_transfn', prorettype => 'anymultirange',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_intersect_agg_transfn'},
+{ oid => '8136', descr => 'range aggregate by intersecting',
+  proname => 'range_intersect_agg', prokind => 'a', proisstrict => 'f',
+  prorettype => 'anymultirange', proargtypes => 'anymultirange',
+  prosrc => 'aggregate_dummy'},
+
 # date, time, timestamp constructors
 { oid => '3846', descr => 'construct date',
   proname => 'make_date', prorettype => 'date', proargtypes => 'int4 int4 int4',
@@ -10194,6 +10457,14 @@
   proname => 'binary_upgrade_set_next_array_pg_type_oid', provolatile => 'v',
   proparallel => 'r', prorettype => 'void', proargtypes => 'oid',
   prosrc => 'binary_upgrade_set_next_array_pg_type_oid' },
+{ oid => '8144', descr => 'for use by pg_upgrade',
+  proname => 'binary_upgrade_set_next_multirange_pg_type_oid', provolatile => 'v',
+  proparallel => 'r', prorettype => 'void', proargtypes => 'oid',
+  prosrc => 'binary_upgrade_set_next_multirange_pg_type_oid' },
+{ oid => '8145', descr => 'for use by pg_upgrade',
+  proname => 'binary_upgrade_set_next_multirange_array_pg_type_oid', provolatile => 'v',
+  proparallel => 'r', prorettype => 'void', proargtypes => 'oid',
+  prosrc => 'binary_upgrade_set_next_multirange_array_pg_type_oid' },
 { oid => '3585', descr => 'for use by pg_upgrade',
   proname => 'binary_upgrade_set_next_toast_pg_type_oid', provolatile => 'v',
   proparallel => 'r', prorettype => 'void', proargtypes => 'oid',
diff --git a/src/include/catalog/pg_range.dat b/src/include/catalog/pg_range.dat
index 479754c245..10060255c9 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',
+  rngmultitypid => 'int4multirange' },
 { rngtypid => 'numrange', rngsubtype => 'numeric',
   rngsubopc => 'btree/numeric_ops', rngcanonical => '-',
-  rngsubdiff => 'numrange_subdiff' },
+  rngsubdiff => 'numrange_subdiff', rngmultitypid => 'nummultirange' },
 { rngtypid => 'tsrange', rngsubtype => 'timestamp',
   rngsubopc => 'btree/timestamp_ops', rngcanonical => '-',
-  rngsubdiff => 'tsrange_subdiff' },
+  rngsubdiff => 'tsrange_subdiff', rngmultitypid => 'tsmultirange' },
 { rngtypid => 'tstzrange', rngsubtype => 'timestamptz',
   rngsubopc => 'btree/timestamptz_ops', rngcanonical => '-',
-  rngsubdiff => 'tstzrange_subdiff' },
+  rngsubdiff => 'tstzrange_subdiff', rngmultitypid => 'tstzmultirange' },
 { rngtypid => 'daterange', rngsubtype => 'date', rngsubopc => 'btree/date_ops',
-  rngcanonical => 'daterange_canonical', rngsubdiff => 'daterange_subdiff' },
+  rngcanonical => 'daterange_canonical', rngsubdiff => 'daterange_subdiff',
+  rngmultitypid => 'datemultirange' },
 { rngtypid => 'int8range', rngsubtype => 'int8', rngsubopc => 'btree/int8_ops',
-  rngcanonical => 'int8range_canonical', rngsubdiff => 'int8range_subdiff' },
+  rngcanonical => 'int8range_canonical', rngsubdiff => 'int8range_subdiff',
+  rngmultitypid => 'int8multirange' },
 
 ]
diff --git a/src/include/catalog/pg_range.h b/src/include/catalog/pg_range.h
index 98172bb1b6..60a8fde518 100644
--- a/src/include/catalog/pg_range.h
+++ b/src/include/catalog/pg_range.h
@@ -34,6 +34,9 @@ CATALOG(pg_range,3541,RangeRelationId)
 	/* OID of range's element type (subtype) */
 	Oid			rngsubtype BKI_LOOKUP(pg_type);
 
+	/* OID of the range's multirange type */
+	Oid			rngmultitypid BKI_LOOKUP(pg_type);
+
 	/* collation for this range type, or 0 */
 	Oid			rngcollation BKI_DEFAULT(0);
 
@@ -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 2e6110e3f2..cb3d159bf1 100644
--- a/src/include/catalog/pg_type.dat
+++ b/src/include/catalog/pg_type.dat
@@ -491,6 +491,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 => 'R', 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 => 'R', 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 => 'R', 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 => 'R', 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 => 'R', 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 => 'R', 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
@@ -620,5 +654,10 @@
   typtype => 'p', typcategory => 'P', typinput => 'anycompatiblerange_in',
   typoutput => 'anycompatiblerange_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 7b37562648..f1818d9a52 100644
--- a/src/include/catalog/pg_type.h
+++ b/src/include/catalog/pg_type.h
@@ -263,6 +263,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 */
 
@@ -304,7 +305,8 @@ typedef FormData_pg_type *Form_pg_type;
 	 (typid) == ANYARRAYOID || \
 	 (typid) == ANYNONARRAYOID || \
 	 (typid) == ANYENUMOID || \
-	 (typid) == ANYRANGEOID)
+	 (typid) == ANYRANGEOID || \
+	 (typid) == ANYMULTIRANGEOID)
 
 #define IsPolymorphicTypeFamily2(typid)  \
 	((typid) == ANYCOMPATIBLEOID || \
@@ -369,4 +371,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/commands/typecmds.h b/src/include/commands/typecmds.h
index 0162bc2ffe..80e3a5e399 100644
--- a/src/include/commands/typecmds.h
+++ b/src/include/commands/typecmds.h
@@ -29,6 +29,8 @@ extern ObjectAddress DefineRange(CreateRangeStmt *stmt);
 extern ObjectAddress AlterEnum(AlterEnumStmt *stmt);
 extern ObjectAddress DefineCompositeType(RangeVar *typevar, List *coldeflist);
 extern Oid	AssignTypeArrayOid(void);
+extern Oid	AssignTypeMultirangeOid(void);
+extern Oid	AssignTypeMultirangeArrayOid(void);
 
 extern ObjectAddress AlterDomainDefault(List *names, Node *defaultRaw);
 extern ObjectAddress AlterDomainNotNull(List *names, bool notNull);
diff --git a/src/include/utils/lsyscache.h b/src/include/utils/lsyscache.h
index 4e646c55e9..80410dcd04 100644
--- a/src/include/utils/lsyscache.h
+++ b/src/include/utils/lsyscache.h
@@ -155,6 +155,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);
@@ -181,6 +182,8 @@ 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_collation(Oid rangeOid);
+extern Oid	get_range_multirange(Oid rangeOid);
+extern Oid	get_range_multirange_subtype(Oid multirangeOid);
 extern Oid	get_index_column_opclass(Oid index_oid, int attno);
 extern bool	get_index_isreplident(Oid index_oid);
 extern bool get_index_isvalid(Oid index_oid);
diff --git a/src/include/utils/multirangetypes.h b/src/include/utils/multirangetypes.h
new file mode 100644
index 0000000000..469444776c
--- /dev/null
+++ b/src/include/utils/multirangetypes.h
@@ -0,0 +1,103 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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
+{
+	int32		vl_len_;		/* 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);
+extern bool multirange_contains_elem_internal(TypeCacheEntry *typcache, MultirangeType *mr,
+											  Datum elem);
+extern bool multirange_contains_range_internal(TypeCacheEntry *typcache, MultirangeType *mr,
+											   RangeType *r);
+extern bool multirange_contains_multirange_internal(TypeCacheEntry *typcache,
+													MultirangeType *mr1,
+													MultirangeType *mr2);
+extern bool range_overlaps_multirange_internal(TypeCacheEntry *typcache, RangeType *r,
+											   MultirangeType *mr);
+extern bool multirange_overlaps_multirange_internal(TypeCacheEntry *typcache,
+													MultirangeType *mr1,
+													MultirangeType *mr2);
+extern bool range_before_multirange_internal(TypeCacheEntry *typcache, RangeType *r,
+											 MultirangeType *mr);
+extern bool range_after_multirange_internal(TypeCacheEntry *typcache, RangeType *r,
+											MultirangeType *mr);
+extern bool multirange_before_multirange_internal(TypeCacheEntry *typcache,
+												  MultirangeType *mr1,
+												  MultirangeType *mr2);
+extern MultirangeType *multirange_minus_internal(Oid mltrngtypoid,
+												 TypeCacheEntry *rangetyp,
+												 int32 range_count1,
+												 RangeType **ranges1,
+												 int32 range_count2,
+												 RangeType **ranges2);
+extern MultirangeType *multirange_intersect_internal(Oid mltrngtypoid,
+													 TypeCacheEntry *rangetyp,
+													 int32 range_count1,
+													 RangeType **ranges1,
+													 int32 range_count2,
+													 RangeType **ranges2);
+
+/* 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 0cbbf09033..204aee4054 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)
 
@@ -92,13 +94,21 @@ typedef struct
  * prototypes for functions defined in rangetypes.c
  */
 
-extern bool range_contains_elem_internal(TypeCacheEntry *typcache, const RangeType *r, Datum val);
-
 /* internal versions of the above */
+extern int	range_cmp_internal(TypeCacheEntry *typcache, const RangeType *r1,
+							   const RangeType *r2);
+extern uint32 hash_range_internal(TypeCacheEntry *typcache, const RangeType *r);
+extern uint64 hash_range_extended_internal(TypeCacheEntry *typcache, const RangeType *r,
+										   Datum seed);
+extern Datum range_lower_internal(TypeCacheEntry *typcache, const RangeType *r1,
+								  bool *isnull);
+extern Datum range_upper_internal(TypeCacheEntry *typcache, const RangeType *r1,
+								  bool *isnull);
 extern bool range_eq_internal(TypeCacheEntry *typcache, const RangeType *r1,
 							  const RangeType *r2);
 extern bool range_ne_internal(TypeCacheEntry *typcache, const RangeType *r1,
 							  const RangeType *r2);
+extern bool range_contains_elem_internal(TypeCacheEntry *typcache, const RangeType *r, Datum val);
 extern bool range_contains_internal(TypeCacheEntry *typcache, const RangeType *r1,
 									const RangeType *r2);
 extern bool range_contained_by_internal(TypeCacheEntry *typcache, const RangeType *r1,
@@ -115,6 +125,12 @@ extern bool range_overleft_internal(TypeCacheEntry *typcache, const RangeType *r
 									const RangeType *r2);
 extern bool range_overright_internal(TypeCacheEntry *typcache, const RangeType *r1,
 									 const RangeType *r2);
+extern RangeType *range_union_internal(TypeCacheEntry *typcache, RangeType *r1,
+									   RangeType *r2, bool strict);
+extern RangeType *range_minus_internal(TypeCacheEntry *typcache, RangeType *r1,
+									   RangeType *r2);
+extern RangeType *range_intersect_internal(TypeCacheEntry *typcache, const RangeType *r1,
+										   const RangeType *r2);
 
 /* assorted support functions */
 extern TypeCacheEntry *range_get_typcache(FunctionCallInfo fcinfo,
@@ -125,6 +141,7 @@ extern void range_deserialize(TypeCacheEntry *typcache, const RangeType *range,
 							  RangeBound *lower, RangeBound *upper,
 							  bool *empty);
 extern char range_get_flags(const RangeType *range);
+extern bool range_has_flag(const RangeType *range, char flag);
 extern void range_set_contain_empty(RangeType *range);
 extern RangeType *make_range(TypeCacheEntry *typcache, RangeBound *lower,
 							 RangeBound *upper, bool empty);
@@ -132,8 +149,12 @@ extern int range_cmp_bounds(TypeCacheEntry *typcache, const RangeBound *b1,
 							const RangeBound *b2);
 extern int range_cmp_bound_values(TypeCacheEntry *typcache, const RangeBound *b1,
 								  const RangeBound *b2);
-extern bool bounds_adjacent(TypeCacheEntry *typcache, RangeBound bound1,
-							RangeBound bound2);
+extern int range_compare(const void *key1, const void *key2, void *arg);
+extern bool bounds_adjacent(TypeCacheEntry *typcache, const RangeBound bound1,
+							const RangeBound bound2);
 extern RangeType *make_empty_range(TypeCacheEntry *typcache);
+extern bool range_split_internal(TypeCacheEntry *typcache, const RangeType *r1,
+								 const RangeType *r2, RangeType **output1,
+								 RangeType **output2);
 
 #endif							/* RANGETYPES_H */
diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h
index f27b73d76d..e8f393520a 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -79,6 +79,7 @@ enum SysCacheIdentifier
 	PUBLICATIONOID,
 	PUBLICATIONREL,
 	PUBLICATIONRELMAP,
+	RANGEMULTIRANGE,
 	RANGETYPE,
 	RELNAMENSP,
 	RELOID,
diff --git a/src/include/utils/typcache.h b/src/include/utils/typcache.h
index cdd20e56d7..33f332a141 100644
--- a/src/include/utils/typcache.h
+++ b/src/include/utils/typcache.h
@@ -101,6 +101,11 @@ typedef struct TypeCacheEntry
 	FmgrInfo	rng_subdiff_finfo;	/* difference function, if any */
 
 	/*
+	 * Fields computed when TYPCACHE_MULTIRANGE_INFO is required.
+	 */
+	struct TypeCacheEntry *rngtype;	/* multirange's range underlying type */
+
+	/*
 	 * Domain's base type and typmod if it's a domain type.  Zeroes if not
 	 * domain, or if information hasn't been requested.
 	 */
@@ -143,6 +148,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 828ff5a288..1e3ff02289 100644
--- a/src/pl/plpgsql/src/pl_comp.c
+++ b/src/pl/plpgsql/src/pl_comp.c
@@ -513,6 +513,8 @@ do_compile(FunctionCallInfo fcinfo,
 					else if (rettypeid == ANYRANGEOID ||
 							 rettypeid == ANYCOMPATIBLERANGEOID)
 						rettypeid = INT4RANGEOID;
+					else if (rettypeid == ANYMULTIRANGEOID)
+						rettypeid = INT4MULTIRANGEOID;
 					else		/* ANYELEMENT or ANYNONARRAY or ANYCOMPATIBLE */
 						rettypeid = INT4OID;
 					/* XXX what could we use for ANYENUM? */
@@ -2089,6 +2091,7 @@ build_datatype(HeapTuple typeTup, int32 typmod,
 		case TYPTYPE_BASE:
 		case TYPTYPE_ENUM:
 		case TYPTYPE_RANGE:
+		case TYPTYPE_MULTIRANGE:
 			typ->ttype = PLPGSQL_TTYPE_SCALAR;
 			break;
 		case TYPTYPE_COMPOSITE:
@@ -2507,6 +2510,9 @@ plpgsql_resolve_polymorphic_argtypes(int numargs,
 				case ANYCOMPATIBLERANGEOID:
 					argtypes[i] = INT4RANGEOID;
 					break;
+				case ANYMULTIRANGEOID:
+					argtypes[i] = INT4MULTIRANGEOID;
+					break;
 				default:
 					break;
 			}
diff --git a/src/test/regress/expected/dependency.out b/src/test/regress/expected/dependency.out
index b4a11b8aa9..8232795148 100644
--- a/src/test/regress/expected/dependency.out
+++ b/src/test/regress/expected/dependency.out
@@ -140,6 +140,7 @@ owner of sequence deptest_a_seq
 owner of table deptest
 owner of function deptest_func()
 owner of type deptest_enum
+owner of type deptest_multirange
 owner of type deptest_range
 owner of table deptest2
 owner of sequence ss1
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..23a06b301f
--- /dev/null
+++ b/src/test/regress/expected/multirangetypes.out
@@ -0,0 +1,2395 @@
+-- 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)
+
+--
+-- test casts, both a built-in range type and a user-defined one:
+--
+select 'empty'::int4range::int4multirange;
+ int4multirange 
+----------------
+ {}
+(1 row)
+
+select int4range(1, 3)::int4multirange;
+ int4range 
+-----------
+ {[1,3)}
+(1 row)
+
+select int4range(1, null)::int4multirange;
+ int4range 
+-----------
+ {[1,)}
+(1 row)
+
+select int4range(null, null)::int4multirange;
+ int4range 
+-----------
+ {(,)}
+(1 row)
+
+select 'empty'::textrange::textmultirange;
+ textmultirange 
+----------------
+ {}
+(1 row)
+
+select textrange('a', 'c')::textmultirange;
+ textrange 
+-----------
+ {[a,c)}
+(1 row)
+
+select textrange('a', null)::textmultirange;
+ textrange 
+-----------
+ {[a,)}
+(1 row)
+
+select textrange(null, null)::textmultirange;
+ textrange 
+-----------
+ {(,)}
+(1 row)
+
+--
+-- create some test data and test the operators
+--
+CREATE TABLE nummultirange_test (nmr NUMMULTIRANGE);
+CREATE INDEX nummultirange_test_btree ON nummultirange_test(nmr);
+INSERT INTO nummultirange_test VALUES('{}');
+INSERT INTO nummultirange_test VALUES('{[,)}');
+INSERT INTO nummultirange_test VALUES('{[3,]}');
+INSERT INTO nummultirange_test VALUES('{[,), [3,]}');
+INSERT INTO nummultirange_test VALUES('{[, 5)}');
+INSERT INTO nummultirange_test VALUES(nummultirange());
+INSERT INTO nummultirange_test VALUES(nummultirange(variadic '{}'::numrange[]));
+INSERT INTO nummultirange_test VALUES(nummultirange(numrange(1.1, 2.2)));
+INSERT INTO nummultirange_test VALUES('{empty}');
+INSERT INTO nummultirange_test VALUES(nummultirange(numrange(1.7, 1.7, '[]'), numrange(1.7, 1.9)));
+INSERT INTO nummultirange_test VALUES(nummultirange(numrange(1.7, 1.7, '[]'), numrange(1.9, 2.1)));
+SELECT nmr, isempty(nmr), lower(nmr), upper(nmr) FROM nummultirange_test ORDER BY nmr;
+          nmr          | isempty | lower | upper 
+-----------------------+---------+-------+-------
+ {}                    | t       |       |      
+ {}                    | t       |       |      
+ {}                    | t       |       |      
+ {}                    | t       |       |      
+ {(,5)}                | f       |       |     5
+ {(,)}                 | f       |       |      
+ {(,)}                 | f       |       |      
+ {[1.1,2.2)}           | f       |   1.1 |   2.2
+ {[1.7,1.7],[1.9,2.1)} | f       |   1.7 |   2.1
+ {[1.7,1.9)}           | f       |   1.7 |   1.9
+ {[3,)}                | f       |     3 |      
+(11 rows)
+
+SELECT nmr, lower_inc(nmr), lower_inf(nmr), upper_inc(nmr), upper_inf(nmr) FROM nummultirange_test ORDER BY nmr;
+          nmr          | lower_inc | lower_inf | upper_inc | upper_inf 
+-----------------------+-----------+-----------+-----------+-----------
+ {}                    | f         | f         | f         | f
+ {}                    | f         | f         | f         | f
+ {}                    | f         | f         | f         | f
+ {}                    | f         | f         | f         | f
+ {(,5)}                | f         | t         | f         | f
+ {(,)}                 | f         | t         | f         | t
+ {(,)}                 | f         | t         | f         | t
+ {[1.1,2.2)}           | t         | f         | f         | f
+ {[1.7,1.7],[1.9,2.1)} | t         | f         | f         | f
+ {[1.7,1.9)}           | t         | f         | f         | f
+ {[3,)}                | t         | f         | f         | t
+(11 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr = '{}';
+ nmr 
+-----
+ {}
+ {}
+ {}
+ {}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr = '{(,5)}';
+  nmr   
+--------
+ {(,5)}
+(1 row)
+
+SELECT * FROM nummultirange_test WHERE nmr = '{[3,)}';
+  nmr   
+--------
+ {[3,)}
+(1 row)
+
+SELECT * FROM nummultirange_test WHERE nmr = '{[1.7,1.7]}';
+ nmr 
+-----
+(0 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr = '{[1.7,1.7],[1.9,2.1)}';
+          nmr          
+-----------------------
+ {[1.7,1.7],[1.9,2.1)}
+(1 row)
+
+SELECT * FROM nummultirange_test WHERE nmr < '{}';
+ nmr 
+-----
+(0 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr < '{[-1000.0, -1000.0]}';
+  nmr   
+--------
+ {}
+ {(,)}
+ {(,)}
+ {(,5)}
+ {}
+ {}
+ {}
+(7 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr < '{[0.0, 1.0]}';
+  nmr   
+--------
+ {}
+ {(,)}
+ {(,)}
+ {(,5)}
+ {}
+ {}
+ {}
+(7 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr < '{[1000.0, 1001.0]}';
+          nmr          
+-----------------------
+ {}
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+ {}
+ {}
+ {[1.1,2.2)}
+ {}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(11 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr <= '{}';
+ nmr 
+-----
+ {}
+ {}
+ {}
+ {}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr <= '{[3,)}';
+          nmr          
+-----------------------
+ {}
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+ {}
+ {}
+ {[1.1,2.2)}
+ {}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(11 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr >= '{}';
+          nmr          
+-----------------------
+ {}
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+ {}
+ {}
+ {[1.1,2.2)}
+ {}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(11 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr >= '{[3,)}';
+  nmr   
+--------
+ {[3,)}
+(1 row)
+
+SELECT * FROM nummultirange_test WHERE nmr > '{}';
+          nmr          
+-----------------------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+ {[1.1,2.2)}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(7 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr > '{[-1000.0, -1000.0]}';
+          nmr          
+-----------------------
+ {[3,)}
+ {[1.1,2.2)}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr > '{[0.0, 1.0]}';
+          nmr          
+-----------------------
+ {[3,)}
+ {[1.1,2.2)}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr > '{[1000.0, 1001.0]}';
+ nmr 
+-----
+(0 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr <> '{}';
+          nmr          
+-----------------------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+ {[1.1,2.2)}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(7 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr <> '{(,5)}';
+          nmr          
+-----------------------
+ {}
+ {(,)}
+ {[3,)}
+ {(,)}
+ {}
+ {}
+ {[1.1,2.2)}
+ {}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(10 rows)
+
+select nummultirange(numrange(2.0, 1.0));
+ERROR:  range lower bound must be less than or equal to range upper bound
+select nummultirange(numrange(5.0, 6.0), numrange(1.0, 2.0));
+     nummultirange     
+-----------------------
+ {[1.0,2.0),[5.0,6.0)}
+(1 row)
+
+-- overlaps
+SELECT * FROM nummultirange_test WHERE range_overlaps_multirange(numrange(4.0, 4.2), nmr);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE numrange(4.0, 4.2) && nmr;
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE multirange_overlaps_range(nmr, numrange(4.0, 4.2));
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr && numrange(4.0, 4.2);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE multirange_overlaps_multirange(nmr, nummultirange(numrange(4.0, 4.2), numrange(6.0, 7.0)));
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr && nummultirange(numrange(4.0, 4.2), numrange(6.0, 7.0));
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr && nummultirange(numrange(6.0, 7.0));
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+(3 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr && nummultirange(numrange(6.0, 7.0), numrange(8.0, 9.0));
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+(3 rows)
+
+-- mr contains x
+SELECT * FROM nummultirange_test WHERE multirange_contains_elem(nmr, 4.0);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr @> 4.0;
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE multirange_contains_range(nmr, numrange(4.0, 4.2));
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr @> numrange(4.0, 4.2);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE multirange_contains_multirange(nmr, '{[4.0,4.2), [6.0, 8.0)}');
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+(3 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr @> '{[4.0,4.2), [6.0, 8.0)}'::nummultirange;
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+(3 rows)
+
+-- x is contained by mr
+SELECT * FROM nummultirange_test WHERE elem_contained_by_multirange(4.0, nmr);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE 4.0 <@ nmr;
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE range_contained_by_multirange(numrange(4.0, 4.2), nmr);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE numrange(4.0, 4.2) <@ nmr;
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE multirange_contained_by_multirange('{[4.0,4.2), [6.0, 8.0)}', nmr);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+(3 rows)
+
+SELECT * FROM nummultirange_test WHERE '{[4.0,4.2), [6.0, 8.0)}'::nummultirange <@ nmr;
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+(3 rows)
+
+-- overlaps
+SELECT 'empty'::numrange && nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT 'empty'::numrange && nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() && 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) && 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() && nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() && nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) && nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) && nummultirange(numrange(1,2), numrange(7,8));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(7,8)) && nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) && nummultirange(numrange(1,2), numrange(3.5,8));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(3.5,8)) && numrange(3,4);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(3.5,8)) && nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+-- contains
+SELECT nummultirange() @> nummultirange();
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange() @> 'empty'::numrange;
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(null,null)) @> numrange(1,2);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(null,null)) @> numrange(null,2);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(null,null)) @> numrange(2,null);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(null,5)) @> numrange(null,3);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(null,5)) @> numrange(null,8);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(5,null)) @> numrange(8,null);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(5,null)) @> numrange(3,null);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,5)) @> numrange(8,9);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,5)) @> numrange(3,9);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,5)) @> numrange(1,4);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,5)) @> numrange(1,5);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(-4,-2), numrange(1,5)) @> numrange(1,5);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,5), numrange(8,9)) @> numrange(1,5);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,5), numrange(8,9)) @> numrange(6,7);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,5), numrange(6,9)) @> numrange(6,7);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[1,5)}'::nummultirange @> '{[1,5)}';
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[-4,-2), [1,5)}'::nummultirange @> '{[1,5)}';
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[1,5), [8,9)}'::nummultirange @> '{[1,5)}';
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[1,5), [8,9)}'::nummultirange @> '{[6,7)}';
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT '{[1,5), [6,9)}'::nummultirange @> '{[6,7)}';
+ ?column? 
+----------
+ t
+(1 row)
+
+-- is contained by
+SELECT nummultirange() <@ nummultirange();
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT 'empty'::numrange <@ nummultirange();
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,2) <@ nummultirange(numrange(null,null));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(null,2) <@ nummultirange(numrange(null,null));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(2,null) <@ nummultirange(numrange(null,null));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(null,3) <@ nummultirange(numrange(null,5));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(null,8) <@ nummultirange(numrange(null,5));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(8,null) <@ nummultirange(numrange(5,null));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(3,null) <@ nummultirange(numrange(5,null));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(8,9) <@ nummultirange(numrange(1,5));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(3,9) <@ nummultirange(numrange(1,5));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(1,4) <@ nummultirange(numrange(1,5));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,5) <@ nummultirange(numrange(1,5));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,5) <@ nummultirange(numrange(-4,-2), numrange(1,5));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,5) <@ nummultirange(numrange(1,5), numrange(8,9));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(6,7) <@ nummultirange(numrange(1,5), numrange(8,9));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(6,7) <@ nummultirange(numrange(1,5), numrange(6,9));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[1,5)}' <@ '{[1,5)}'::nummultirange;
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[1,5)}' <@ '{[-4,-2), [1,5)}'::nummultirange;
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[1,5)}' <@ '{[1,5), [8,9)}'::nummultirange;
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[6,7)}' <@ '{[1,5), [8,9)}'::nummultirange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT '{[6,7)}' <@ '{[1,5), [6,9)}'::nummultirange;
+ ?column? 
+----------
+ t
+(1 row)
+
+-- overleft
+SELECT 'empty'::numrange &< nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT 'empty'::numrange &< nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() &< 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) &< 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() &< nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) &< nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() &< nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(6,7) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(1,2) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,4) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,6) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(3.5,6) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(6,7)) &< numrange(3,4);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) &< numrange(3,4);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,4)) &< numrange(3,4);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,6)) &< numrange(3,4);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3.5,6)) &< numrange(3,4);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(6,7)) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,4)) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,6)) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3.5,6)) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+-- overright
+SELECT nummultirange() &> 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) &> 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT 'empty'::numrange &> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT 'empty'::numrange &> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() &> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() &> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) &> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> numrange(6,7);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> numrange(1,2);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> numrange(1,4);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> numrange(1,6);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> numrange(3.5,6);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(3,4) &> nummultirange(numrange(6,7));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(3,4) &> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(3,4) &> nummultirange(numrange(1,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(3,4) &> nummultirange(numrange(1,6));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(3,4) &> nummultirange(numrange(3.5,6));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(6,7));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(1,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(1,6));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(3.5,6));
+ ?column? 
+----------
+ f
+(1 row)
+
+-- meets
+SELECT 'empty'::numrange -|- nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT 'empty'::numrange -|- nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() -|- 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() -|- nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() -|- nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(1,2) -|- nummultirange(numrange(2,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,2) -|- nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- numrange(2,4);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- numrange(3,4);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- nummultirange(numrange(2,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(5,6)) -|- nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(5,6)) -|- nummultirange(numrange(6,7));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(5,6)) -|- nummultirange(numrange(8,9));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- nummultirange(numrange(2,4), numrange(6,7));
+ ?column? 
+----------
+ t
+(1 row)
+
+-- strictly left
+select 'empty'::numrange << nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(1,2) << nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(1,2) << nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+select numrange(1,2) << nummultirange(numrange(0,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(1,2) << nummultirange(numrange(0,4), numrange(7,8));
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange() << 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange() << numrange(1,2);
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(3,4)) << numrange(3,6);
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(0,2)) << numrange(3,6);
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange(numrange(0,2), numrange(7,8)) << numrange(3,6);
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(-4,-2), numrange(0,2)) << numrange(3,6);
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange() << nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange() << nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(1,2)) << nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(1,2)) << nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(1,2)) << nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange(numrange(1,2)) << nummultirange(numrange(3,4), numrange(7,8));
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange(numrange(1,2), numrange(4,5)) << nummultirange(numrange(3,4), numrange(7,8));
+ ?column? 
+----------
+ f
+(1 row)
+
+-- strictly right
+select nummultirange() >> 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange() >> numrange(1,2);
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(3,4)) >> numrange(1,2);
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange(numrange(0,4)) >> numrange(1,2);
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(0,4), numrange(7,8)) >> numrange(1,2);
+ ?column? 
+----------
+ f
+(1 row)
+
+select 'empty'::numrange >> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(1,2) >> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(3,6) >> nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(3,6) >> nummultirange(numrange(0,2));
+ ?column? 
+----------
+ t
+(1 row)
+
+select numrange(3,6) >> nummultirange(numrange(0,2), numrange(7,8));
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(3,6) >> nummultirange(numrange(-4,-2), numrange(0,2));
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange() >> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(1,2)) >> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange() >> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(1,2)) >> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(3,4)) >> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange(numrange(3,4), numrange(7,8)) >> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange(numrange(3,4), numrange(7,8)) >> nummultirange(numrange(1,2), numrange(4,5));
+ ?column? 
+----------
+ f
+(1 row)
+
+-- union
+SELECT nummultirange() + nummultirange();
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange() + nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) + nummultirange();
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) + nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) + nummultirange(numrange(2,4));
+ ?column? 
+----------
+ {[1,4)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) + nummultirange(numrange(3,4));
+   ?column?    
+---------------
+ {[1,2),[3,4)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) + nummultirange(numrange(2,4));
+ ?column? 
+----------
+ {[1,5)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) + nummultirange(numrange(3,4));
+   ?column?    
+---------------
+ {[1,2),[3,5)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) + nummultirange(numrange(0,9));
+ ?column? 
+----------
+ {[0,9)}
+(1 row)
+
+-- merge
+SELECT range_merge(nummultirange());
+ range_merge 
+-------------
+ empty
+(1 row)
+
+SELECT range_merge(nummultirange(numrange(1,2)));
+ range_merge 
+-------------
+ [1,2)
+(1 row)
+
+SELECT range_merge(nummultirange(numrange(1,2), numrange(7,8)));
+ range_merge 
+-------------
+ [1,8)
+(1 row)
+
+-- minus
+SELECT nummultirange() - nummultirange();
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange() - nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) - nummultirange();
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(3,4)) - nummultirange();
+   ?column?    
+---------------
+ {[1,2),[3,4)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) - nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) - nummultirange(numrange(2,4));
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) - nummultirange(numrange(3,4));
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {[2,4)}
+(1 row)
+
+SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(2,3));
+   ?column?    
+---------------
+ {[1,2),[3,4)}
+(1 row)
+
+SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(0,8));
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(0,2));
+ ?column? 
+----------
+ {[2,4)}
+(1 row)
+
+SELECT nummultirange(numrange(1,8)) - nummultirange(numrange(0,2), numrange(3,4));
+   ?column?    
+---------------
+ {[2,3),[4,8)}
+(1 row)
+
+SELECT nummultirange(numrange(1,8)) - nummultirange(numrange(2,3), numrange(5,null));
+   ?column?    
+---------------
+ {[1,2),[3,5)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(-2,0));
+   ?column?    
+---------------
+ {[1,2),[4,5)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(2,4));
+   ?column?    
+---------------
+ {[1,2),[4,5)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(3,5));
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(0,9));
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange(numrange(1,3), numrange(4,5)) - nummultirange(numrange(2,9));
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(8,9));
+   ?column?    
+---------------
+ {[1,2),[4,5)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(-2,0), numrange(8,9));
+   ?column?    
+---------------
+ {[1,2),[4,5)}
+(1 row)
+
+-- intersection
+SELECT nummultirange() * nummultirange();
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange() * nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) * nummultirange();
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT '{[1,3)}'::nummultirange * '{[1,5)}'::nummultirange;
+ ?column? 
+----------
+ {[1,3)}
+(1 row)
+
+SELECT '{[1,3)}'::nummultirange * '{[0,5)}'::nummultirange;
+ ?column? 
+----------
+ {[1,3)}
+(1 row)
+
+SELECT '{[1,3)}'::nummultirange * '{[0,2)}'::nummultirange;
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT '{[1,3)}'::nummultirange * '{[2,5)}'::nummultirange;
+ ?column? 
+----------
+ {[2,3)}
+(1 row)
+
+SELECT '{[1,4)}'::nummultirange * '{[2,3)}'::nummultirange;
+ ?column? 
+----------
+ {[2,3)}
+(1 row)
+
+SELECT '{[1,4)}'::nummultirange * '{[0,2), [3,5)}'::nummultirange;
+   ?column?    
+---------------
+ {[1,2),[3,4)}
+(1 row)
+
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[0,8), [9,12)}'::nummultirange;
+       ?column?       
+----------------------
+ {[1,4),[7,8),[9,10)}
+(1 row)
+
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[9,12)}'::nummultirange;
+ ?column? 
+----------
+ {[9,10)}
+(1 row)
+
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[-5,-4), [5,6), [9,12)}'::nummultirange;
+ ?column? 
+----------
+ {[9,10)}
+(1 row)
+
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[0,2), [3,8), [9,12)}'::nummultirange;
+          ?column?          
+----------------------------
+ {[1,2),[3,4),[7,8),[9,10)}
+(1 row)
+
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[0,2), [3,8), [9,12)}'::nummultirange;
+          ?column?          
+----------------------------
+ {[1,2),[3,4),[7,8),[9,10)}
+(1 row)
+
+--
+-- range_agg function
+--
+create table reservations ( room_id integer not null, booked_during daterange );
+insert into reservations values
+-- 1: has a meets and a gap
+(1, daterange('2018-07-01', '2018-07-07')),
+(1, daterange('2018-07-07', '2018-07-14')),
+(1, daterange('2018-07-20', '2018-07-22')),
+-- 2: just a single row
+(2, daterange('2018-07-01', '2018-07-03')),
+-- 3: one null range
+(3, NULL),
+-- 4: two null ranges
+(4, NULL),
+(4, NULL),
+-- 5: a null range and a non-null range
+(5, NULL),
+(5, daterange('2018-07-01', '2018-07-03')),
+-- 6: has overlap
+(6, daterange('2018-07-01', '2018-07-07')),
+(6, daterange('2018-07-05', '2018-07-10')),
+-- 7: two ranges that meet: no gap or overlap
+(7, daterange('2018-07-01', '2018-07-07')),
+(7, daterange('2018-07-07', '2018-07-14')),
+-- 8: an empty range
+(8, 'empty'::daterange)
+;
+SELECT   room_id, range_agg(booked_during)
+FROM     reservations
+GROUP BY room_id
+ORDER BY room_id;
+ room_id |                     range_agg                     
+---------+---------------------------------------------------
+       1 | {[07-01-2018,07-14-2018),[07-20-2018,07-22-2018)}
+       2 | {[07-01-2018,07-03-2018)}
+       3 | 
+       4 | 
+       5 | {[07-01-2018,07-03-2018)}
+       6 | {[07-01-2018,07-10-2018)}
+       7 | {[07-01-2018,07-14-2018)}
+       8 | {}
+(8 rows)
+
+-- range_agg on a custom range type too
+SELECT  range_agg(r)
+FROM    (VALUES
+          ('[a,c]'::textrange),
+          ('[b,b]'::textrange),
+          ('[c,f]'::textrange),
+          ('[g,h)'::textrange),
+          ('[h,j)'::textrange)
+        ) t(r);
+   range_agg   
+---------------
+ {[a,f],[g,j)}
+(1 row)
+
+select range_intersect_agg(nmr) from nummultirange_test;
+ range_intersect_agg 
+---------------------
+ {}
+(1 row)
+
+select range_intersect_agg(nmr) from nummultirange_test where false;
+ range_intersect_agg 
+---------------------
+ 
+(1 row)
+
+-- test with just one input:
+select range_intersect_agg(nmr) from (values ('{[1,2]}'::nummultirange)) t(nmr);
+ range_intersect_agg 
+---------------------
+ {[1,2]}
+(1 row)
+
+select range_intersect_agg(nmr) from nummultirange_test where nmr @> 4.0;
+ range_intersect_agg 
+---------------------
+ {[3,5)}
+(1 row)
+
+create table nummultirange_test2(nmr nummultirange);
+create index nummultirange_test2_hash_idx on nummultirange_test2 using hash (nmr);
+INSERT INTO nummultirange_test2 VALUES('{[, 5)}');
+INSERT INTO nummultirange_test2 VALUES(nummultirange(numrange(1.1, 2.2)));
+INSERT INTO nummultirange_test2 VALUES(nummultirange(numrange(1.1, 2.2)));
+INSERT INTO nummultirange_test2 VALUES(nummultirange(numrange(1.1, 2.2,'()')));
+INSERT INTO nummultirange_test2 VALUES('{}');
+select * from nummultirange_test2 where nmr = '{}';
+ nmr 
+-----
+ {}
+(1 row)
+
+select * from nummultirange_test2 where nmr = nummultirange(numrange(1.1, 2.2));
+     nmr     
+-------------
+ {[1.1,2.2)}
+ {[1.1,2.2)}
+(2 rows)
+
+select * from nummultirange_test2 where nmr = nummultirange(numrange(1.1, 2.3));
+ nmr 
+-----
+(0 rows)
+
+set enable_nestloop=t;
+set enable_hashjoin=f;
+set enable_mergejoin=f;
+select * from nummultirange_test natural join nummultirange_test2 order by nmr;
+     nmr     
+-------------
+ {}
+ {}
+ {}
+ {}
+ {(,5)}
+ {[1.1,2.2)}
+ {[1.1,2.2)}
+(7 rows)
+
+set enable_nestloop=f;
+set enable_hashjoin=t;
+set enable_mergejoin=f;
+select * from nummultirange_test natural join nummultirange_test2 order by nmr;
+     nmr     
+-------------
+ {}
+ {}
+ {}
+ {}
+ {(,5)}
+ {[1.1,2.2)}
+ {[1.1,2.2)}
+(7 rows)
+
+set enable_nestloop=f;
+set enable_hashjoin=f;
+set enable_mergejoin=t;
+select * from nummultirange_test natural join nummultirange_test2 order by nmr;
+     nmr     
+-------------
+ {}
+ {}
+ {}
+ {}
+ {(,5)}
+ {[1.1,2.2)}
+ {[1.1,2.2)}
+(7 rows)
+
+set enable_nestloop to default;
+set enable_hashjoin to default;
+set enable_mergejoin to default;
+DROP TABLE nummultirange_test2;
+--
+-- Test user-defined multirange of floats
+--
+select '{[123.001, 5.e9)}'::float8multirange @> 888.882::float8;
+ ?column? 
+----------
+ t
+(1 row)
+
+create table float8multirange_test(f8mr float8multirange, i int);
+insert into float8multirange_test values(float8multirange(float8range(-100.00007, '1.111113e9')), 42);
+select * from float8multirange_test;
+           f8mr            | i  
+---------------------------+----
+ {[-100.00007,1111113000)} | 42
+(1 row)
+
+drop table float8multirange_test;
+--
+-- Test multirange types over domains
+--
+create domain mydomain as int4;
+create type mydomainrange as range(subtype=mydomain);
+select '{[4,50)}'::mydomainmultirange @> 7::mydomain;
+ ?column? 
+----------
+ t
+(1 row)
+
+drop domain mydomain cascade;
+NOTICE:  drop cascades to type mydomainrange
+--
+-- Test domains over multirange types
+--
+create domain restrictedmultirange as int4multirange check (upper(value) < 10);
+select '{[4,5)}'::restrictedmultirange @> 7;
+ ?column? 
+----------
+ f
+(1 row)
+
+select '{[4,50)}'::restrictedmultirange @> 7; -- should fail
+ERROR:  value for domain restrictedmultirange violates check constraint "restrictedmultirange_check"
+drop domain restrictedmultirange;
+--
+-- Test multiple multirange types over the same subtype
+--
+create type textrange1 as range(subtype=text, collation="C");
+create type textrange2 as range(subtype=text, collation="C");
+select textmultirange1(textrange2('a','Z'));  -- should fail
+ERROR:  function textmultirange1(textrange2) does not exist
+LINE 1: select textmultirange1(textrange2('a','Z'));
+               ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+select textmultirange1(textrange1('a','Z')) @> 'b'::text;
+ERROR:  range lower bound must be less than or equal to range upper bound
+select textmultirange2(textrange2('a','z')) @> 'b'::text;
+ ?column? 
+----------
+ t
+(1 row)
+
+drop type textrange1;
+drop type textrange2;
+--
+-- Test polymorphic type system
+--
+create function anyarray_anymultirange_func(a anyarray, r anymultirange)
+  returns anyelement as 'select $1[1] + lower($2);' language sql;
+select anyarray_anymultirange_func(ARRAY[1,2], int4multirange(int4range(10,20)));
+ anyarray_anymultirange_func 
+-----------------------------
+                          11
+(1 row)
+
+-- should fail
+select anyarray_anymultirange_func(ARRAY[1,2], nummultirange(numrange(10,20)));
+ERROR:  function anyarray_anymultirange_func(integer[], nummultirange) does not exist
+LINE 1: select anyarray_anymultirange_func(ARRAY[1,2], nummultirange...
+               ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+drop function anyarray_anymultirange_func(anyarray, anymultirange);
+-- should fail
+create function bogus_func(anyelement)
+  returns anymultirange as 'select int4multirange(int4range(1,10))' language sql;
+ERROR:  cannot determine result data type
+DETAIL:  A result of type anymultirange requires at least one input of type anyrange or anymultirange.
+-- should fail
+create function bogus_func(int)
+  returns anymultirange as 'select int4multirange(int4range(1,10))' language sql;
+ERROR:  cannot determine result data type
+DETAIL:  A result of type anymultirange requires at least one input of type anyrange or anymultirange.
+create function range_add_bounds(anymultirange)
+  returns anyelement as 'select lower($1) + upper($1)' language sql;
+select range_add_bounds(int4multirange(int4range(1, 17)));
+ range_add_bounds 
+------------------
+               18
+(1 row)
+
+select range_add_bounds(nummultirange(numrange(1.0001, 123.123)));
+ range_add_bounds 
+------------------
+         124.1231
+(1 row)
+
+create function multirangetypes_sql(q anymultirange, b anyarray, out c anyelement)
+  as $$ select upper($1) + $2[1] $$
+  language sql;
+select multirangetypes_sql(int4multirange(int4range(1,10)), ARRAY[2,20]);
+ multirangetypes_sql 
+---------------------
+                  12
+(1 row)
+
+select multirangetypes_sql(nummultirange(numrange(1,10)), ARRAY[2,20]);  -- match failure
+ERROR:  function multirangetypes_sql(nummultirange, integer[]) does not exist
+LINE 1: select multirangetypes_sql(nummultirange(numrange(1,10)), AR...
+               ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+--
+-- Arrays of multiranges
+--
+select ARRAY[nummultirange(numrange(1.1, 1.2)), nummultirange(numrange(12.3, 155.5))];
+              array               
+----------------------------------
+ {"{[1.1,1.2)}","{[12.3,155.5)}"}
+(1 row)
+
+create table i8mr_array (f1 int, f2 int8multirange[]);
+insert into i8mr_array values (42, array[int8multirange(int8range(1,10)), int8multirange(int8range(2,20))]);
+select * from i8mr_array;
+ f1 |           f2            
+----+-------------------------
+ 42 | {"{[1,10)}","{[2,20)}"}
+(1 row)
+
+drop table i8mr_array;
+--
+-- Multiranges of arrays
+--
+select arraymultirange(arrayrange(ARRAY[1,2], ARRAY[2,1]));
+   arraymultirange   
+---------------------
+ {["{1,2}","{2,1}")}
+(1 row)
+
+select arraymultirange(arrayrange(ARRAY[2,1], ARRAY[1,2]));  -- fail
+ERROR:  range lower bound must be less than or equal to range upper bound
+select array[1,1] <@ arraymultirange(arrayrange(array[1,2], array[2,1]));
+ ?column? 
+----------
+ f
+(1 row)
+
+select array[1,3] <@ arraymultirange(arrayrange(array[1,2], array[2,1]));
+ ?column? 
+----------
+ t
+(1 row)
+
+--
+-- Ranges of composites
+--
+create type two_ints as (a int, b int);
+create type two_ints_range as range (subtype = two_ints);
+-- with force_parallel_mode on, this exercises tqueue.c's range remapping
+select *, row_to_json(upper(t)) as u from
+  (values (two_ints_multirange(two_ints_range(row(1,2), row(3,4)))),
+          (two_ints_multirange(two_ints_range(row(5,6), row(7,8))))) v(t);
+          t          |       u       
+---------------------+---------------
+ {["(1,2)","(3,4)")} | {"a":3,"b":4}
+ {["(5,6)","(7,8)")} | {"a":7,"b":8}
+(2 rows)
+
+drop type two_ints cascade;
+NOTICE:  drop cascades to type two_ints_range
+--
+-- Check behavior when subtype lacks a hash function
+--
+set enable_sort = off;  -- try to make it pick a hash setop implementation
+select '{(2,5)}'::cashmultirange except select '{(5,6)}'::cashmultirange;
+ cashmultirange  
+-----------------
+ {($2.00,$5.00)}
+(1 row)
+
+reset enable_sort;
+--
+-- OUT/INOUT/TABLE functions
+--
+-- infer anymultirange from anymultirange
+create function mr_outparam_succeed(i anymultirange, out r anymultirange, out t text)
+  as $$ select $1, 'foo'::text $$ language sql;
+select * from mr_outparam_succeed(int4multirange(int4range(1,2)));
+    r    |  t  
+---------+-----
+ {[1,2)} | foo
+(1 row)
+
+-- infer anyarray from anymultirange
+create function mr_outparam_succeed2(i anymultirange, out r anyarray, out t text)
+  as $$ select ARRAY[upper($1)], 'foo'::text $$ language sql;
+select * from mr_outparam_succeed2(int4multirange(int4range(1,2)));
+  r  |  t  
+-----+-----
+ {2} | foo
+(1 row)
+
+-- infer anyrange from anymultirange
+create function mr_outparam_succeed3(i anymultirange, out r anyrange, out t text)
+  as $$ select range_merge($1), 'foo'::text $$ language sql;
+select * from mr_outparam_succeed3(int4multirange(int4range(1,2)));
+   r   |  t  
+-------+-----
+ [1,2) | foo
+(1 row)
+
+-- infer anymultirange from anyrange
+create function mr_outparam_succeed4(i anyrange, out r anymultirange, out t text)
+  as $$ select multirange($1), 'foo'::text $$ language sql;
+select * from mr_outparam_succeed4(int4range(1,2));
+    r    |  t  
+---------+-----
+ {[1,2)} | foo
+(1 row)
+
+-- infer anyelement from anymultirange
+create function mr_inoutparam_succeed(out i anyelement, inout r anymultirange)
+  as $$ select upper($1), $1 $$ language sql;
+select * from mr_inoutparam_succeed(int4multirange(int4range(1,2)));
+ i |    r    
+---+---------
+ 2 | {[1,2)}
+(1 row)
+
+-- infer anyelement+anymultirange from anyelement+anymultirange
+create function mr_table_succeed(i anyelement, r anymultirange) returns table(i anyelement, r anymultirange)
+  as $$ select $1, $2 $$ language sql;
+select * from mr_table_succeed(123, int4multirange(int4range(1,11)));
+  i  |    r     
+-----+----------
+ 123 | {[1,11)}
+(1 row)
+
+-- use anymultirange in plpgsql
+create function mr_polymorphic(i anyrange) returns anymultirange
+  as $$ begin return multirange($1); end; $$ language plpgsql;
+select mr_polymorphic(int4range(1, 4));
+ mr_polymorphic 
+----------------
+ {[1,4)}
+(1 row)
+
+-- should fail
+create function mr_outparam_fail(i anyelement, out r anymultirange, out t text)
+  as $$ select '[1,10]', 'foo' $$ language sql;
+ERROR:  cannot determine result data type
+DETAIL:  A result of type anymultirange requires at least one input of type anyrange or anymultirange.
+--should fail
+create function mr_inoutparam_fail(inout i anyelement, out r anymultirange)
+  as $$ select $1, '[1,10]' $$ language sql;
+ERROR:  cannot determine result data type
+DETAIL:  A result of type anymultirange requires at least one input of type anyrange or anymultirange.
+--should fail
+create function mr_table_fail(i anyelement) returns table(i anyelement, r anymultirange)
+  as $$ select $1, '[1,10]' $$ language sql;
+ERROR:  cannot determine result data type
+DETAIL:  A result of type anymultirange requires at least one input of type anyrange or anymultirange.
diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out
index 3c0b21d633..4ec99ea800 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 
@@ -335,7 +346,8 @@ WHERE p1.prorettype IN
      '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     
 ------+----------------
@@ -350,20 +362,25 @@ ORDER BY 2;
  3532 | enum_recv
 (9 rows)
 
--- anyrange is tighter than the rest, can only resolve from anyrange input
+-- anyrange and anymultirange are tighter than the rest, can only resolve
+-- from each other
 SELECT p1.oid, p1.proname
 FROM pg_proc as p1
-WHERE p1.prorettype = 'anyrange'::regtype
+WHERE p1.prorettype IN ('anyrange'::regtype, 'anymultirange'::regtype)
   AND NOT
-     'anyrange'::regtype = ANY (p1.proargtypes)
+    ('anyrange'::regtype = ANY (p1.proargtypes) OR
+      'anymultirange'::regtype = ANY (p1.proargtypes))
 ORDER BY 2;
  oid  |     proname      
 ------+------------------
+ 8002 | anymultirange_in
  3832 | anyrange_in
+ 8004 | multirange_in
+ 8006 | multirange_recv
  3876 | range_gist_union
  3834 | range_in
  3836 | range_recv
-(4 rows)
+(7 rows)
 
 -- similarly for the anycompatible family
 SELECT p1.oid, p1.proname
@@ -2181,13 +2198,14 @@ ORDER BY 1, 2, 3;
                     | float_ops        | float4_ops       | real
                     | float_ops        | float8_ops       | double precision
                     | jsonb_ops        | jsonb_ops        | jsonb
+                    | multirange_ops   | multirange_ops   | anymultirange
                     | numeric_ops      | numeric_ops      | numeric
                     | range_ops        | range_ops        | anyrange
                     | record_image_ops | record_image_ops | record
                     | record_ops       | record_ops       | record
                     | tsquery_ops      | tsquery_ops      | tsquery
                     | tsvector_ops     | tsvector_ops     | tsvector
-(14 rows)
+(15 rows)
 
 -- **************** pg_index ****************
 -- Look for illegal values in pg_index fields.
diff --git a/src/test/regress/expected/plpgsql.out b/src/test/regress/expected/plpgsql.out
index d0a6b630b8..65827c03e6 100644
--- a/src/test/regress/expected/plpgsql.out
+++ b/src/test/regress/expected/plpgsql.out
@@ -1811,7 +1811,7 @@ begin
   return array[x + 1, x + 2];
 end$$ language plpgsql;
 ERROR:  cannot determine result data type
-DETAIL:  A result of type anyrange requires at least one input of type anyrange.
+DETAIL:  A result of type anyrange requires at least one input of type anyrange or anymultirange.
 create function f1(x anyrange) returns anyarray as $$
 begin
   return array[lower(x), upper(x)];
diff --git a/src/test/regress/expected/polymorphism.out b/src/test/regress/expected/polymorphism.out
index d86a7004a6..e087db3ea6 100644
--- a/src/test/regress/expected/polymorphism.out
+++ b/src/test/regress/expected/polymorphism.out
@@ -61,7 +61,7 @@ create function polyf(x anyelement) returns anyrange as $$
   select array[x + 1, x + 2]
 $$ language sql;
 ERROR:  cannot determine result data type
-DETAIL:  A result of type anyrange requires at least one input of type anyrange.
+DETAIL:  A result of type anyrange requires at least one input of type anyrange or anymultirange.
 create function polyf(x anyrange) returns anyarray as $$
   select array[lower(x), upper(x)]
 $$ language sql;
@@ -231,7 +231,7 @@ CREATE AGGREGATE myaggp01a(*) (SFUNC = stfnp, STYPE = int4[],
 CREATE AGGREGATE myaggp02a(*) (SFUNC = stfnp, STYPE = anyarray,
   FINALFUNC = ffp, INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 --     N    P
 -- should CREATE
 CREATE AGGREGATE myaggp03a(*) (SFUNC = stfp, STYPE = int4[],
@@ -243,11 +243,11 @@ CREATE AGGREGATE myaggp03b(*) (SFUNC = stfp, STYPE = int4[],
 CREATE AGGREGATE myaggp04a(*) (SFUNC = stfp, STYPE = anyarray,
   FINALFUNC = ffp, INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 CREATE AGGREGATE myaggp04b(*) (SFUNC = stfp, STYPE = anyarray,
   INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 --    Case2 (R = P) && ((B = P) || (B = N))
 --    -------------------------------------
 --    S    tf1      B    tf2
@@ -302,13 +302,13 @@ ERROR:  function tfp(integer[], anyelement) does not exist
 CREATE AGGREGATE myaggp13a(BASETYPE = int, SFUNC = tfnp, STYPE = anyarray,
   FINALFUNC = ffp, INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 --    P    N        N    P
 -- should ERROR: tf2p(anyarray, int) not matched by tf2p(int[],anyelement)
 CREATE AGGREGATE myaggp14a(BASETYPE = int, SFUNC = tf2p, STYPE = anyarray,
   FINALFUNC = ffp, INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 --    P    N        P    N
 -- should ERROR: tfnp(anyarray, anyelement) not matched by tfnp(int[],int)
 CREATE AGGREGATE myaggp15a(BASETYPE = anyelement, SFUNC = tfnp,
@@ -324,21 +324,21 @@ ERROR:  function tf2p(anyarray, anyelement) does not exist
 CREATE AGGREGATE myaggp17a(BASETYPE = int, SFUNC = tf1p, STYPE = anyarray,
   FINALFUNC = ffp, INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 CREATE AGGREGATE myaggp17b(BASETYPE = int, SFUNC = tf1p, STYPE = anyarray,
   INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 --    P    P        N    P
 -- should ERROR: tfp(anyarray, int) not matched by tfp(anyarray, anyelement)
 CREATE AGGREGATE myaggp18a(BASETYPE = int, SFUNC = tfp, STYPE = anyarray,
   FINALFUNC = ffp, INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 CREATE AGGREGATE myaggp18b(BASETYPE = int, SFUNC = tfp, STYPE = anyarray,
   INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 --    P    P        P    N
 -- should ERROR: tf1p(anyarray, anyelement) not matched by tf1p(anyarray, int)
 CREATE AGGREGATE myaggp19a(BASETYPE = anyelement, SFUNC = tf1p,
@@ -368,11 +368,11 @@ CREATE AGGREGATE myaggn01b(*) (SFUNC = stfnp, STYPE = int4[],
 CREATE AGGREGATE myaggn02a(*) (SFUNC = stfnp, STYPE = anyarray,
   FINALFUNC = ffnp, INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 CREATE AGGREGATE myaggn02b(*) (SFUNC = stfnp, STYPE = anyarray,
   INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 --     N    P
 -- should CREATE
 CREATE AGGREGATE myaggn03a(*) (SFUNC = stfp, STYPE = int4[],
@@ -382,7 +382,7 @@ CREATE AGGREGATE myaggn03a(*) (SFUNC = stfp, STYPE = int4[],
 CREATE AGGREGATE myaggn04a(*) (SFUNC = stfp, STYPE = anyarray,
   FINALFUNC = ffnp, INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 --    Case4 (R = N) && ((B = P) || (B = N))
 --    -------------------------------------
 --    S    tf1      B    tf2
@@ -436,21 +436,21 @@ ERROR:  function tfp(integer[], anyelement) does not exist
 CREATE AGGREGATE myaggn13a(BASETYPE = int, SFUNC = tfnp, STYPE = anyarray,
   FINALFUNC = ffnp, INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 CREATE AGGREGATE myaggn13b(BASETYPE = int, SFUNC = tfnp, STYPE = anyarray,
   INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 --    P    N        N    P
 -- should ERROR: tf2p(anyarray, int) not matched by tf2p(int[],anyelement)
 CREATE AGGREGATE myaggn14a(BASETYPE = int, SFUNC = tf2p, STYPE = anyarray,
   FINALFUNC = ffnp, INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 CREATE AGGREGATE myaggn14b(BASETYPE = int, SFUNC = tf2p, STYPE = anyarray,
   INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 --    P    N        P    N
 -- should ERROR: tfnp(anyarray, anyelement) not matched by tfnp(int[],int)
 CREATE AGGREGATE myaggn15a(BASETYPE = anyelement, SFUNC = tfnp,
@@ -472,13 +472,13 @@ ERROR:  function tf2p(anyarray, anyelement) does not exist
 CREATE AGGREGATE myaggn17a(BASETYPE = int, SFUNC = tf1p, STYPE = anyarray,
   FINALFUNC = ffnp, INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 --    P    P        N    P
 -- should ERROR: tfp(anyarray, int) not matched by tfp(anyarray, anyelement)
 CREATE AGGREGATE myaggn18a(BASETYPE = int, SFUNC = tfp, STYPE = anyarray,
   FINALFUNC = ffnp, INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 --    P    P        P    N
 -- should ERROR: tf1p(anyarray, anyelement) not matched by tf1p(anyarray, int)
 CREATE AGGREGATE myaggn19a(BASETYPE = anyelement, SFUNC = tf1p,
diff --git a/src/test/regress/expected/rangefuncs.out b/src/test/regress/expected/rangefuncs.out
index 7eced28452..71e2769f44 100644
--- a/src/test/regress/expected/rangefuncs.out
+++ b/src/test/regress/expected/rangefuncs.out
@@ -1556,7 +1556,7 @@ DROP FUNCTION dup(anyelement);
 CREATE FUNCTION bad (f1 int, out f2 anyelement, out f3 anyarray)
 AS 'select $1, array[$1,$1]' LANGUAGE sql;
 ERROR:  cannot determine result data type
-DETAIL:  A result of type anyelement requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyelement requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 CREATE FUNCTION dup (f1 anycompatible, f2 anycompatiblearray, f3 out anycompatible, f4 out anycompatiblearray)
 AS 'select $1, $2' LANGUAGE sql;
 SELECT dup(22, array[44]);
diff --git a/src/test/regress/expected/rangetypes.out b/src/test/regress/expected/rangetypes.out
index c421f5394f..763e3819a5 100644
--- a/src/test/regress/expected/rangetypes.out
+++ b/src/test/regress/expected/rangetypes.out
@@ -518,6 +518,24 @@ select numrange(1.0, 2.0) * numrange(2.5, 3.0);
  empty
 (1 row)
 
+select range_intersect_agg(nr) from numrange_test;
+ range_intersect_agg 
+---------------------
+ empty
+(1 row)
+
+select range_intersect_agg(nr) from numrange_test where false;
+ range_intersect_agg 
+---------------------
+ 
+(1 row)
+
+select range_intersect_agg(nr) from numrange_test where nr @> 4.0;
+ range_intersect_agg 
+---------------------
+ [3,5)
+(1 row)
+
 create table numrange_test2(nr numrange);
 create index numrange_test2_hash_idx on numrange_test2 using hash (nr);
 INSERT INTO numrange_test2 VALUES('[, 5)');
@@ -1371,12 +1389,12 @@ drop function anyarray_anyrange_func(anyarray, anyrange);
 create function bogus_func(anyelement)
   returns anyrange as 'select int4range(1,10)' language sql;
 ERROR:  cannot determine result data type
-DETAIL:  A result of type anyrange requires at least one input of type anyrange.
+DETAIL:  A result of type anyrange requires at least one input of type anyrange or anymultirange.
 -- should fail
 create function bogus_func(int)
   returns anyrange as 'select int4range(1,10)' language sql;
 ERROR:  cannot determine result data type
-DETAIL:  A result of type anyrange requires at least one input of type anyrange.
+DETAIL:  A result of type anyrange requires at least one input of type anyrange or anymultirange.
 create function range_add_bounds(anyrange)
   returns anyelement as 'select lower($1) + upper($1)' language sql;
 select range_add_bounds(int4range(1, 17));
@@ -1508,6 +1526,7 @@ reset enable_sort;
 --
 -- OUT/INOUT/TABLE functions
 --
+-- infer anyrange from anyrange
 create function outparam_succeed(i anyrange, out r anyrange, out t text)
   as $$ select $1, 'foo'::text $$ language sql;
 select * from outparam_succeed(int4range(1,2));
@@ -1525,6 +1544,16 @@ select * from outparam2_succeed(int4range(1,11));
  {1,11} | {11,1}
 (1 row)
 
+-- infer anyarray from anyrange
+create function outparam_succeed2(i anyrange, out r anyarray, out t text)
+  as $$ select ARRAY[upper($1)], 'foo'::text $$ language sql;
+select * from outparam_succeed2(int4range(int4range(1,2)));
+  r  |  t  
+-----+-----
+ {2} | foo
+(1 row)
+
+-- infer anyelement from anyrange
 create function inoutparam_succeed(out i anyelement, inout r anyrange)
   as $$ select upper($1), $1 $$ language sql;
 select * from inoutparam_succeed(int4range(1,2));
@@ -1547,14 +1576,14 @@ select * from table_succeed(int4range(1,11));
 create function outparam_fail(i anyelement, out r anyrange, out t text)
   as $$ select '[1,10]', 'foo' $$ language sql;
 ERROR:  cannot determine result data type
-DETAIL:  A result of type anyrange requires at least one input of type anyrange.
+DETAIL:  A result of type anyrange requires at least one input of type anyrange or anymultirange.
 --should fail
 create function inoutparam_fail(inout i anyelement, out r anyrange)
   as $$ select $1, '[1,10]' $$ language sql;
 ERROR:  cannot determine result data type
-DETAIL:  A result of type anyrange requires at least one input of type anyrange.
+DETAIL:  A result of type anyrange requires at least one input of type anyrange or anymultirange.
 --should fail
 create function table_fail(i anyelement) returns table(i anyelement, r anyrange)
   as $$ select $1, '[1,10]' $$ language sql;
 ERROR:  cannot determine result data type
-DETAIL:  A result of type anyrange requires at least one input of type anyrange.
+DETAIL:  A result of type anyrange requires at least one input of type anyrange or anymultirange.
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index 192445878d..d9ce961be2 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -92,6 +92,7 @@ num_exp_sqrt|t
 num_exp_sub|t
 num_input_test|f
 num_result|f
+nummultirange_test|t
 numrange_test|t
 onek|t
 onek2|t
@@ -172,6 +173,7 @@ quad_poly_tbl|t
 radix_text_tbl|t
 ramp|f
 real_city|f
+reservations|f
 road|t
 shighway|t
 slow_emp4000|f
diff --git a/src/test/regress/expected/type_sanity.out b/src/test/regress/expected/type_sanity.out
index 274130e706..8df1ae47e4 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'));
@@ -195,18 +195,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
@@ -240,17 +241,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
@@ -318,18 +320,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
@@ -363,17 +366,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
@@ -631,3 +635,11 @@ WHERE pronargs != 2
 ----------+------------+---------
 (0 rows)
 
+-- every range should have a valid multirange
+SELECT p1.rngtypid, p1.rngsubtype, p1.rngmultitypid
+FROM pg_range p1
+WHERE p1.rngmultitypid IS NULL OR p1.rngmultitypid = 0;
+ rngtypid | rngsubtype | rngmultitypid 
+----------+------------+---------------
+(0 rows)
+
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index d2b17dd3ea..5355da7b01 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 acba391332..1f677f63af 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..0f70919e1a
--- /dev/null
+++ b/src/test/regress/sql/multirangetypes.sql
@@ -0,0 +1,630 @@
+-- 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'));
+
+--
+-- test casts, both a built-in range type and a user-defined one:
+--
+select 'empty'::int4range::int4multirange;
+select int4range(1, 3)::int4multirange;
+select int4range(1, null)::int4multirange;
+select int4range(null, null)::int4multirange;
+select 'empty'::textrange::textmultirange;
+select textrange('a', 'c')::textmultirange;
+select textrange('a', null)::textmultirange;
+select textrange(null, null)::textmultirange;
+
+--
+-- create some test data and test the operators
+--
+
+CREATE TABLE nummultirange_test (nmr NUMMULTIRANGE);
+CREATE INDEX nummultirange_test_btree ON nummultirange_test(nmr);
+
+INSERT INTO nummultirange_test VALUES('{}');
+INSERT INTO nummultirange_test VALUES('{[,)}');
+INSERT INTO nummultirange_test VALUES('{[3,]}');
+INSERT INTO nummultirange_test VALUES('{[,), [3,]}');
+INSERT INTO nummultirange_test VALUES('{[, 5)}');
+INSERT INTO nummultirange_test VALUES(nummultirange());
+INSERT INTO nummultirange_test VALUES(nummultirange(variadic '{}'::numrange[]));
+INSERT INTO nummultirange_test VALUES(nummultirange(numrange(1.1, 2.2)));
+INSERT INTO nummultirange_test VALUES('{empty}');
+INSERT INTO nummultirange_test VALUES(nummultirange(numrange(1.7, 1.7, '[]'), numrange(1.7, 1.9)));
+INSERT INTO nummultirange_test VALUES(nummultirange(numrange(1.7, 1.7, '[]'), numrange(1.9, 2.1)));
+
+SELECT nmr, isempty(nmr), lower(nmr), upper(nmr) FROM nummultirange_test ORDER BY nmr;
+SELECT nmr, lower_inc(nmr), lower_inf(nmr), upper_inc(nmr), upper_inf(nmr) FROM nummultirange_test ORDER BY nmr;
+
+SELECT * FROM nummultirange_test WHERE nmr = '{}';
+SELECT * FROM nummultirange_test WHERE nmr = '{(,5)}';
+SELECT * FROM nummultirange_test WHERE nmr = '{[3,)}';
+SELECT * FROM nummultirange_test WHERE nmr = '{[1.7,1.7]}';
+SELECT * FROM nummultirange_test WHERE nmr = '{[1.7,1.7],[1.9,2.1)}';
+SELECT * FROM nummultirange_test WHERE nmr < '{}';
+SELECT * FROM nummultirange_test WHERE nmr < '{[-1000.0, -1000.0]}';
+SELECT * FROM nummultirange_test WHERE nmr < '{[0.0, 1.0]}';
+SELECT * FROM nummultirange_test WHERE nmr < '{[1000.0, 1001.0]}';
+SELECT * FROM nummultirange_test WHERE nmr <= '{}';
+SELECT * FROM nummultirange_test WHERE nmr <= '{[3,)}';
+SELECT * FROM nummultirange_test WHERE nmr >= '{}';
+SELECT * FROM nummultirange_test WHERE nmr >= '{[3,)}';
+SELECT * FROM nummultirange_test WHERE nmr > '{}';
+SELECT * FROM nummultirange_test WHERE nmr > '{[-1000.0, -1000.0]}';
+SELECT * FROM nummultirange_test WHERE nmr > '{[0.0, 1.0]}';
+SELECT * FROM nummultirange_test WHERE nmr > '{[1000.0, 1001.0]}';
+SELECT * FROM nummultirange_test WHERE nmr <> '{}';
+SELECT * FROM nummultirange_test WHERE nmr <> '{(,5)}';
+
+select nummultirange(numrange(2.0, 1.0));
+select nummultirange(numrange(5.0, 6.0), numrange(1.0, 2.0));
+
+-- overlaps
+SELECT * FROM nummultirange_test WHERE range_overlaps_multirange(numrange(4.0, 4.2), nmr);
+SELECT * FROM nummultirange_test WHERE numrange(4.0, 4.2) && nmr;
+SELECT * FROM nummultirange_test WHERE multirange_overlaps_range(nmr, numrange(4.0, 4.2));
+SELECT * FROM nummultirange_test WHERE nmr && numrange(4.0, 4.2);
+SELECT * FROM nummultirange_test WHERE multirange_overlaps_multirange(nmr, nummultirange(numrange(4.0, 4.2), numrange(6.0, 7.0)));
+SELECT * FROM nummultirange_test WHERE nmr && nummultirange(numrange(4.0, 4.2), numrange(6.0, 7.0));
+SELECT * FROM nummultirange_test WHERE nmr && nummultirange(numrange(6.0, 7.0));
+SELECT * FROM nummultirange_test WHERE nmr && nummultirange(numrange(6.0, 7.0), numrange(8.0, 9.0));
+
+-- mr contains x
+SELECT * FROM nummultirange_test WHERE multirange_contains_elem(nmr, 4.0);
+SELECT * FROM nummultirange_test WHERE nmr @> 4.0;
+SELECT * FROM nummultirange_test WHERE multirange_contains_range(nmr, numrange(4.0, 4.2));
+SELECT * FROM nummultirange_test WHERE nmr @> numrange(4.0, 4.2);
+SELECT * FROM nummultirange_test WHERE multirange_contains_multirange(nmr, '{[4.0,4.2), [6.0, 8.0)}');
+SELECT * FROM nummultirange_test WHERE nmr @> '{[4.0,4.2), [6.0, 8.0)}'::nummultirange;
+
+-- x is contained by mr
+SELECT * FROM nummultirange_test WHERE elem_contained_by_multirange(4.0, nmr);
+SELECT * FROM nummultirange_test WHERE 4.0 <@ nmr;
+SELECT * FROM nummultirange_test WHERE range_contained_by_multirange(numrange(4.0, 4.2), nmr);
+SELECT * FROM nummultirange_test WHERE numrange(4.0, 4.2) <@ nmr;
+SELECT * FROM nummultirange_test WHERE multirange_contained_by_multirange('{[4.0,4.2), [6.0, 8.0)}', nmr);
+SELECT * FROM nummultirange_test WHERE '{[4.0,4.2), [6.0, 8.0)}'::nummultirange <@ nmr;
+
+-- overlaps
+SELECT 'empty'::numrange && nummultirange();
+SELECT 'empty'::numrange && nummultirange(numrange(1,2));
+SELECT nummultirange() && 'empty'::numrange;
+SELECT nummultirange(numrange(1,2)) && 'empty'::numrange;
+SELECT nummultirange() && nummultirange();
+SELECT nummultirange() && nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) && nummultirange();
+SELECT nummultirange(numrange(3,4)) && nummultirange(numrange(1,2), numrange(7,8));
+SELECT nummultirange(numrange(1,2), numrange(7,8)) && nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(3,4)) && nummultirange(numrange(1,2), numrange(3.5,8));
+SELECT nummultirange(numrange(1,2), numrange(3.5,8)) && numrange(3,4);
+SELECT nummultirange(numrange(1,2), numrange(3.5,8)) && nummultirange(numrange(3,4));
+
+-- contains
+SELECT nummultirange() @> nummultirange();
+SELECT nummultirange() @> 'empty'::numrange;
+SELECT nummultirange(numrange(null,null)) @> numrange(1,2);
+SELECT nummultirange(numrange(null,null)) @> numrange(null,2);
+SELECT nummultirange(numrange(null,null)) @> numrange(2,null);
+SELECT nummultirange(numrange(null,5)) @> numrange(null,3);
+SELECT nummultirange(numrange(null,5)) @> numrange(null,8);
+SELECT nummultirange(numrange(5,null)) @> numrange(8,null);
+SELECT nummultirange(numrange(5,null)) @> numrange(3,null);
+SELECT nummultirange(numrange(1,5)) @> numrange(8,9);
+SELECT nummultirange(numrange(1,5)) @> numrange(3,9);
+SELECT nummultirange(numrange(1,5)) @> numrange(1,4);
+SELECT nummultirange(numrange(1,5)) @> numrange(1,5);
+SELECT nummultirange(numrange(-4,-2), numrange(1,5)) @> numrange(1,5);
+SELECT nummultirange(numrange(1,5), numrange(8,9)) @> numrange(1,5);
+SELECT nummultirange(numrange(1,5), numrange(8,9)) @> numrange(6,7);
+SELECT nummultirange(numrange(1,5), numrange(6,9)) @> numrange(6,7);
+SELECT '{[1,5)}'::nummultirange @> '{[1,5)}';
+SELECT '{[-4,-2), [1,5)}'::nummultirange @> '{[1,5)}';
+SELECT '{[1,5), [8,9)}'::nummultirange @> '{[1,5)}';
+SELECT '{[1,5), [8,9)}'::nummultirange @> '{[6,7)}';
+SELECT '{[1,5), [6,9)}'::nummultirange @> '{[6,7)}';
+
+-- is contained by
+SELECT nummultirange() <@ nummultirange();
+SELECT 'empty'::numrange <@ nummultirange();
+SELECT numrange(1,2) <@ nummultirange(numrange(null,null));
+SELECT numrange(null,2) <@ nummultirange(numrange(null,null));
+SELECT numrange(2,null) <@ nummultirange(numrange(null,null));
+SELECT numrange(null,3) <@ nummultirange(numrange(null,5));
+SELECT numrange(null,8) <@ nummultirange(numrange(null,5));
+SELECT numrange(8,null) <@ nummultirange(numrange(5,null));
+SELECT numrange(3,null) <@ nummultirange(numrange(5,null));
+SELECT numrange(8,9) <@ nummultirange(numrange(1,5));
+SELECT numrange(3,9) <@ nummultirange(numrange(1,5));
+SELECT numrange(1,4) <@ nummultirange(numrange(1,5));
+SELECT numrange(1,5) <@ nummultirange(numrange(1,5));
+SELECT numrange(1,5) <@ nummultirange(numrange(-4,-2), numrange(1,5));
+SELECT numrange(1,5) <@ nummultirange(numrange(1,5), numrange(8,9));
+SELECT numrange(6,7) <@ nummultirange(numrange(1,5), numrange(8,9));
+SELECT numrange(6,7) <@ nummultirange(numrange(1,5), numrange(6,9));
+SELECT '{[1,5)}' <@ '{[1,5)}'::nummultirange;
+SELECT '{[1,5)}' <@ '{[-4,-2), [1,5)}'::nummultirange;
+SELECT '{[1,5)}' <@ '{[1,5), [8,9)}'::nummultirange;
+SELECT '{[6,7)}' <@ '{[1,5), [8,9)}'::nummultirange;
+SELECT '{[6,7)}' <@ '{[1,5), [6,9)}'::nummultirange;
+
+-- overleft
+SELECT 'empty'::numrange &< nummultirange();
+SELECT 'empty'::numrange &< nummultirange(numrange(1,2));
+SELECT nummultirange() &< 'empty'::numrange;
+SELECT nummultirange(numrange(1,2)) &< 'empty'::numrange;
+SELECT nummultirange() &< nummultirange();
+SELECT nummultirange(numrange(1,2)) &< nummultirange();
+SELECT nummultirange() &< nummultirange(numrange(1,2));
+SELECT numrange(6,7) &< nummultirange(numrange(3,4));
+SELECT numrange(1,2) &< nummultirange(numrange(3,4));
+SELECT numrange(1,4) &< nummultirange(numrange(3,4));
+SELECT numrange(1,6) &< nummultirange(numrange(3,4));
+SELECT numrange(3.5,6) &< nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(6,7)) &< numrange(3,4);
+SELECT nummultirange(numrange(1,2)) &< numrange(3,4);
+SELECT nummultirange(numrange(1,4)) &< numrange(3,4);
+SELECT nummultirange(numrange(1,6)) &< numrange(3,4);
+SELECT nummultirange(numrange(3.5,6)) &< numrange(3,4);
+SELECT nummultirange(numrange(6,7)) &< nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,2)) &< nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,4)) &< nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,6)) &< nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(3.5,6)) &< nummultirange(numrange(3,4));
+
+-- overright
+SELECT nummultirange() &> 'empty'::numrange;
+SELECT nummultirange(numrange(1,2)) &> 'empty'::numrange;
+SELECT 'empty'::numrange &> nummultirange();
+SELECT 'empty'::numrange &> nummultirange(numrange(1,2));
+SELECT nummultirange() &> nummultirange();
+SELECT nummultirange() &> nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) &> nummultirange();
+SELECT nummultirange(numrange(3,4)) &> numrange(6,7);
+SELECT nummultirange(numrange(3,4)) &> numrange(1,2);
+SELECT nummultirange(numrange(3,4)) &> numrange(1,4);
+SELECT nummultirange(numrange(3,4)) &> numrange(1,6);
+SELECT nummultirange(numrange(3,4)) &> numrange(3.5,6);
+SELECT numrange(3,4) &> nummultirange(numrange(6,7));
+SELECT numrange(3,4) &> nummultirange(numrange(1,2));
+SELECT numrange(3,4) &> nummultirange(numrange(1,4));
+SELECT numrange(3,4) &> nummultirange(numrange(1,6));
+SELECT numrange(3,4) &> nummultirange(numrange(3.5,6));
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(6,7));
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(1,4));
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(1,6));
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(3.5,6));
+
+-- meets
+SELECT 'empty'::numrange -|- nummultirange();
+SELECT 'empty'::numrange -|- nummultirange(numrange(1,2));
+SELECT nummultirange() -|- 'empty'::numrange;
+SELECT nummultirange(numrange(1,2)) -|- 'empty'::numrange;
+SELECT nummultirange() -|- nummultirange();
+SELECT nummultirange(numrange(1,2)) -|- nummultirange();
+SELECT nummultirange() -|- nummultirange(numrange(1,2));
+SELECT numrange(1,2) -|- nummultirange(numrange(2,4));
+SELECT numrange(1,2) -|- nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,2)) -|- numrange(2,4);
+SELECT nummultirange(numrange(1,2)) -|- numrange(3,4);
+SELECT nummultirange(numrange(1,2)) -|- nummultirange(numrange(2,4));
+SELECT nummultirange(numrange(1,2)) -|- nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,2), numrange(5,6)) -|- nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,2), numrange(5,6)) -|- nummultirange(numrange(6,7));
+SELECT nummultirange(numrange(1,2), numrange(5,6)) -|- nummultirange(numrange(8,9));
+SELECT nummultirange(numrange(1,2)) -|- nummultirange(numrange(2,4), numrange(6,7));
+
+-- strictly left
+select 'empty'::numrange << nummultirange();
+select numrange(1,2) << nummultirange();
+select numrange(1,2) << nummultirange(numrange(3,4));
+select numrange(1,2) << nummultirange(numrange(0,4));
+select numrange(1,2) << nummultirange(numrange(0,4), numrange(7,8));
+select nummultirange() << 'empty'::numrange;
+select nummultirange() << numrange(1,2);
+select nummultirange(numrange(3,4)) << numrange(3,6);
+select nummultirange(numrange(0,2)) << numrange(3,6);
+select nummultirange(numrange(0,2), numrange(7,8)) << numrange(3,6);
+select nummultirange(numrange(-4,-2), numrange(0,2)) << numrange(3,6);
+select nummultirange() << nummultirange();
+select nummultirange() << nummultirange(numrange(1,2));
+select nummultirange(numrange(1,2)) << nummultirange();
+select nummultirange(numrange(1,2)) << nummultirange(numrange(1,2));
+select nummultirange(numrange(1,2)) << nummultirange(numrange(3,4));
+select nummultirange(numrange(1,2)) << nummultirange(numrange(3,4), numrange(7,8));
+select nummultirange(numrange(1,2), numrange(4,5)) << nummultirange(numrange(3,4), numrange(7,8));
+
+-- strictly right
+select nummultirange() >> 'empty'::numrange;
+select nummultirange() >> numrange(1,2);
+select nummultirange(numrange(3,4)) >> numrange(1,2);
+select nummultirange(numrange(0,4)) >> numrange(1,2);
+select nummultirange(numrange(0,4), numrange(7,8)) >> numrange(1,2);
+select 'empty'::numrange >> nummultirange();
+select numrange(1,2) >> nummultirange();
+select numrange(3,6) >> nummultirange(numrange(3,4));
+select numrange(3,6) >> nummultirange(numrange(0,2));
+select numrange(3,6) >> nummultirange(numrange(0,2), numrange(7,8));
+select numrange(3,6) >> nummultirange(numrange(-4,-2), numrange(0,2));
+select nummultirange() >> nummultirange();
+select nummultirange(numrange(1,2)) >> nummultirange();
+select nummultirange() >> nummultirange(numrange(1,2));
+select nummultirange(numrange(1,2)) >> nummultirange(numrange(1,2));
+select nummultirange(numrange(3,4)) >> nummultirange(numrange(1,2));
+select nummultirange(numrange(3,4), numrange(7,8)) >> nummultirange(numrange(1,2));
+select nummultirange(numrange(3,4), numrange(7,8)) >> nummultirange(numrange(1,2), numrange(4,5));
+
+-- union
+SELECT nummultirange() + nummultirange();
+SELECT nummultirange() + nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) + nummultirange();
+SELECT nummultirange(numrange(1,2)) + nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) + nummultirange(numrange(2,4));
+SELECT nummultirange(numrange(1,2)) + nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) + nummultirange(numrange(2,4));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) + nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) + nummultirange(numrange(0,9));
+
+-- merge
+SELECT range_merge(nummultirange());
+SELECT range_merge(nummultirange(numrange(1,2)));
+SELECT range_merge(nummultirange(numrange(1,2), numrange(7,8)));
+
+-- minus
+SELECT nummultirange() - nummultirange();
+SELECT nummultirange() - nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) - nummultirange();
+SELECT nummultirange(numrange(1,2), numrange(3,4)) - nummultirange();
+SELECT nummultirange(numrange(1,2)) - nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) - nummultirange(numrange(2,4));
+SELECT nummultirange(numrange(1,2)) - nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(2,3));
+SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(0,8));
+SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(0,2));
+SELECT nummultirange(numrange(1,8)) - nummultirange(numrange(0,2), numrange(3,4));
+SELECT nummultirange(numrange(1,8)) - nummultirange(numrange(2,3), numrange(5,null));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(-2,0));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(2,4));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(3,5));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(0,9));
+SELECT nummultirange(numrange(1,3), numrange(4,5)) - nummultirange(numrange(2,9));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(8,9));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(-2,0), numrange(8,9));
+
+-- intersection
+SELECT nummultirange() * nummultirange();
+SELECT nummultirange() * nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) * nummultirange();
+SELECT '{[1,3)}'::nummultirange * '{[1,5)}'::nummultirange;
+SELECT '{[1,3)}'::nummultirange * '{[0,5)}'::nummultirange;
+SELECT '{[1,3)}'::nummultirange * '{[0,2)}'::nummultirange;
+SELECT '{[1,3)}'::nummultirange * '{[2,5)}'::nummultirange;
+SELECT '{[1,4)}'::nummultirange * '{[2,3)}'::nummultirange;
+SELECT '{[1,4)}'::nummultirange * '{[0,2), [3,5)}'::nummultirange;
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[0,8), [9,12)}'::nummultirange;
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[9,12)}'::nummultirange;
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[-5,-4), [5,6), [9,12)}'::nummultirange;
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[0,2), [3,8), [9,12)}'::nummultirange;
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[0,2), [3,8), [9,12)}'::nummultirange;
+
+--
+-- range_agg function
+--
+create table reservations ( room_id integer not null, booked_during daterange );
+insert into reservations values
+-- 1: has a meets and a gap
+(1, daterange('2018-07-01', '2018-07-07')),
+(1, daterange('2018-07-07', '2018-07-14')),
+(1, daterange('2018-07-20', '2018-07-22')),
+-- 2: just a single row
+(2, daterange('2018-07-01', '2018-07-03')),
+-- 3: one null range
+(3, NULL),
+-- 4: two null ranges
+(4, NULL),
+(4, NULL),
+-- 5: a null range and a non-null range
+(5, NULL),
+(5, daterange('2018-07-01', '2018-07-03')),
+-- 6: has overlap
+(6, daterange('2018-07-01', '2018-07-07')),
+(6, daterange('2018-07-05', '2018-07-10')),
+-- 7: two ranges that meet: no gap or overlap
+(7, daterange('2018-07-01', '2018-07-07')),
+(7, daterange('2018-07-07', '2018-07-14')),
+-- 8: an empty range
+(8, 'empty'::daterange)
+;
+SELECT   room_id, range_agg(booked_during)
+FROM     reservations
+GROUP BY room_id
+ORDER BY room_id;
+
+-- range_agg on a custom range type too
+SELECT  range_agg(r)
+FROM    (VALUES
+          ('[a,c]'::textrange),
+          ('[b,b]'::textrange),
+          ('[c,f]'::textrange),
+          ('[g,h)'::textrange),
+          ('[h,j)'::textrange)
+        ) t(r);
+
+select range_intersect_agg(nmr) from nummultirange_test;
+select range_intersect_agg(nmr) from nummultirange_test where false;
+-- test with just one input:
+select range_intersect_agg(nmr) from (values ('{[1,2]}'::nummultirange)) t(nmr);
+select range_intersect_agg(nmr) from nummultirange_test where nmr @> 4.0;
+
+create table nummultirange_test2(nmr nummultirange);
+create index nummultirange_test2_hash_idx on nummultirange_test2 using hash (nmr);
+
+INSERT INTO nummultirange_test2 VALUES('{[, 5)}');
+INSERT INTO nummultirange_test2 VALUES(nummultirange(numrange(1.1, 2.2)));
+INSERT INTO nummultirange_test2 VALUES(nummultirange(numrange(1.1, 2.2)));
+INSERT INTO nummultirange_test2 VALUES(nummultirange(numrange(1.1, 2.2,'()')));
+INSERT INTO nummultirange_test2 VALUES('{}');
+
+select * from nummultirange_test2 where nmr = '{}';
+select * from nummultirange_test2 where nmr = nummultirange(numrange(1.1, 2.2));
+select * from nummultirange_test2 where nmr = nummultirange(numrange(1.1, 2.3));
+
+set enable_nestloop=t;
+set enable_hashjoin=f;
+set enable_mergejoin=f;
+select * from nummultirange_test natural join nummultirange_test2 order by nmr;
+set enable_nestloop=f;
+set enable_hashjoin=t;
+set enable_mergejoin=f;
+select * from nummultirange_test natural join nummultirange_test2 order by nmr;
+set enable_nestloop=f;
+set enable_hashjoin=f;
+set enable_mergejoin=t;
+select * from nummultirange_test natural join nummultirange_test2 order by nmr;
+
+set enable_nestloop to default;
+set enable_hashjoin to default;
+set enable_mergejoin to default;
+
+DROP TABLE nummultirange_test2;
+
+--
+-- Test user-defined multirange of floats
+--
+
+select '{[123.001, 5.e9)}'::float8multirange @> 888.882::float8;
+create table float8multirange_test(f8mr float8multirange, i int);
+insert into float8multirange_test values(float8multirange(float8range(-100.00007, '1.111113e9')), 42);
+select * from float8multirange_test;
+drop table float8multirange_test;
+
+--
+-- Test multirange types over domains
+--
+
+create domain mydomain as int4;
+create type mydomainrange as range(subtype=mydomain);
+select '{[4,50)}'::mydomainmultirange @> 7::mydomain;
+drop domain mydomain cascade;
+
+--
+-- Test domains over multirange types
+--
+
+create domain restrictedmultirange as int4multirange check (upper(value) < 10);
+select '{[4,5)}'::restrictedmultirange @> 7;
+select '{[4,50)}'::restrictedmultirange @> 7; -- should fail
+drop domain restrictedmultirange;
+
+--
+-- Test multiple multirange types over the same subtype
+--
+
+create type textrange1 as range(subtype=text, collation="C");
+create type textrange2 as range(subtype=text, collation="C");
+
+select textmultirange1(textrange2('a','Z'));  -- should fail
+select textmultirange1(textrange1('a','Z')) @> 'b'::text;
+select textmultirange2(textrange2('a','z')) @> 'b'::text;
+
+drop type textrange1;
+drop type textrange2;
+
+--
+-- Test polymorphic type system
+--
+
+create function anyarray_anymultirange_func(a anyarray, r anymultirange)
+  returns anyelement as 'select $1[1] + lower($2);' language sql;
+
+select anyarray_anymultirange_func(ARRAY[1,2], int4multirange(int4range(10,20)));
+
+-- should fail
+select anyarray_anymultirange_func(ARRAY[1,2], nummultirange(numrange(10,20)));
+
+drop function anyarray_anymultirange_func(anyarray, anymultirange);
+
+-- should fail
+create function bogus_func(anyelement)
+  returns anymultirange as 'select int4multirange(int4range(1,10))' language sql;
+
+-- should fail
+create function bogus_func(int)
+  returns anymultirange as 'select int4multirange(int4range(1,10))' language sql;
+
+create function range_add_bounds(anymultirange)
+  returns anyelement as 'select lower($1) + upper($1)' language sql;
+
+select range_add_bounds(int4multirange(int4range(1, 17)));
+select range_add_bounds(nummultirange(numrange(1.0001, 123.123)));
+
+create function multirangetypes_sql(q anymultirange, b anyarray, out c anyelement)
+  as $$ select upper($1) + $2[1] $$
+  language sql;
+
+select multirangetypes_sql(int4multirange(int4range(1,10)), ARRAY[2,20]);
+select multirangetypes_sql(nummultirange(numrange(1,10)), ARRAY[2,20]);  -- match failure
+
+--
+-- Arrays of multiranges
+--
+
+select ARRAY[nummultirange(numrange(1.1, 1.2)), nummultirange(numrange(12.3, 155.5))];
+
+create table i8mr_array (f1 int, f2 int8multirange[]);
+insert into i8mr_array values (42, array[int8multirange(int8range(1,10)), int8multirange(int8range(2,20))]);
+select * from i8mr_array;
+drop table i8mr_array;
+
+--
+-- Multiranges of arrays
+--
+
+select arraymultirange(arrayrange(ARRAY[1,2], ARRAY[2,1]));
+select arraymultirange(arrayrange(ARRAY[2,1], ARRAY[1,2]));  -- fail
+
+select array[1,1] <@ arraymultirange(arrayrange(array[1,2], array[2,1]));
+select array[1,3] <@ arraymultirange(arrayrange(array[1,2], array[2,1]));
+
+--
+-- Ranges of composites
+--
+
+create type two_ints as (a int, b int);
+create type two_ints_range as range (subtype = two_ints);
+
+-- with force_parallel_mode on, this exercises tqueue.c's range remapping
+select *, row_to_json(upper(t)) as u from
+  (values (two_ints_multirange(two_ints_range(row(1,2), row(3,4)))),
+          (two_ints_multirange(two_ints_range(row(5,6), row(7,8))))) v(t);
+
+drop type two_ints cascade;
+
+--
+-- Check behavior when subtype lacks a hash function
+--
+
+set enable_sort = off;  -- try to make it pick a hash setop implementation
+
+select '{(2,5)}'::cashmultirange except select '{(5,6)}'::cashmultirange;
+
+reset enable_sort;
+
+--
+-- OUT/INOUT/TABLE functions
+--
+
+-- infer anymultirange from anymultirange
+create function mr_outparam_succeed(i anymultirange, out r anymultirange, out t text)
+  as $$ select $1, 'foo'::text $$ language sql;
+
+select * from mr_outparam_succeed(int4multirange(int4range(1,2)));
+
+-- infer anyarray from anymultirange
+create function mr_outparam_succeed2(i anymultirange, out r anyarray, out t text)
+  as $$ select ARRAY[upper($1)], 'foo'::text $$ language sql;
+
+select * from mr_outparam_succeed2(int4multirange(int4range(1,2)));
+
+-- infer anyrange from anymultirange
+create function mr_outparam_succeed3(i anymultirange, out r anyrange, out t text)
+  as $$ select range_merge($1), 'foo'::text $$ language sql;
+select * from mr_outparam_succeed3(int4multirange(int4range(1,2)));
+
+-- infer anymultirange from anyrange
+create function mr_outparam_succeed4(i anyrange, out r anymultirange, out t text)
+  as $$ select multirange($1), 'foo'::text $$ language sql;
+
+select * from mr_outparam_succeed4(int4range(1,2));
+
+-- infer anyelement from anymultirange
+create function mr_inoutparam_succeed(out i anyelement, inout r anymultirange)
+  as $$ select upper($1), $1 $$ language sql;
+
+select * from mr_inoutparam_succeed(int4multirange(int4range(1,2)));
+
+-- infer anyelement+anymultirange from anyelement+anymultirange
+create function mr_table_succeed(i anyelement, r anymultirange) returns table(i anyelement, r anymultirange)
+  as $$ select $1, $2 $$ language sql;
+
+select * from mr_table_succeed(123, int4multirange(int4range(1,11)));
+
+-- use anymultirange in plpgsql
+create function mr_polymorphic(i anyrange) returns anymultirange
+  as $$ begin return multirange($1); end; $$ language plpgsql;
+select mr_polymorphic(int4range(1, 4));
+
+-- should fail
+create function mr_outparam_fail(i anyelement, out r anymultirange, out t text)
+  as $$ select '[1,10]', 'foo' $$ language sql;
+
+--should fail
+create function mr_inoutparam_fail(inout i anyelement, out r anymultirange)
+  as $$ select $1, '[1,10]' $$ language sql;
+
+--should fail
+create function mr_table_fail(i anyelement) returns table(i anyelement, r anymultirange)
+  as $$ select $1, '[1,10]' $$ language sql;
diff --git a/src/test/regress/sql/opr_sanity.sql b/src/test/regress/sql/opr_sanity.sql
index 389d5b2464..f1e1bfb7d6 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;
 
@@ -279,16 +290,19 @@ WHERE p1.prorettype IN
      '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;
 
--- anyrange is tighter than the rest, can only resolve from anyrange input
+-- anyrange and anymultirange are tighter than the rest, can only resolve
+-- from each other
 
 SELECT p1.oid, p1.proname
 FROM pg_proc as p1
-WHERE p1.prorettype = 'anyrange'::regtype
+WHERE p1.prorettype IN ('anyrange'::regtype, 'anymultirange'::regtype)
   AND NOT
-     'anyrange'::regtype = ANY (p1.proargtypes)
+    ('anyrange'::regtype = ANY (p1.proargtypes) OR
+      'anymultirange'::regtype = ANY (p1.proargtypes))
 ORDER BY 2;
 
 -- similarly for the anycompatible family
diff --git a/src/test/regress/sql/rangetypes.sql b/src/test/regress/sql/rangetypes.sql
index 4048b1d185..0276d19136 100644
--- a/src/test/regress/sql/rangetypes.sql
+++ b/src/test/regress/sql/rangetypes.sql
@@ -118,6 +118,10 @@ select numrange(1.0, 2.0) * numrange(2.0, 3.0);
 select numrange(1.0, 2.0) * numrange(1.5, 3.0);
 select numrange(1.0, 2.0) * numrange(2.5, 3.0);
 
+select range_intersect_agg(nr) from numrange_test;
+select range_intersect_agg(nr) from numrange_test where false;
+select range_intersect_agg(nr) from numrange_test where nr @> 4.0;
+
 create table numrange_test2(nr numrange);
 create index numrange_test2_hash_idx on numrange_test2 using hash (nr);
 
@@ -528,6 +532,7 @@ reset enable_sort;
 -- OUT/INOUT/TABLE functions
 --
 
+-- infer anyrange from anyrange
 create function outparam_succeed(i anyrange, out r anyrange, out t text)
   as $$ select $1, 'foo'::text $$ language sql;
 
@@ -539,6 +544,13 @@ create function outparam2_succeed(r anyrange, out lu anyarray, out ul anyarray)
 
 select * from outparam2_succeed(int4range(1,11));
 
+-- infer anyarray from anyrange
+create function outparam_succeed2(i anyrange, out r anyarray, out t text)
+  as $$ select ARRAY[upper($1)], 'foo'::text $$ language sql;
+
+select * from outparam_succeed2(int4range(int4range(1,2)));
+
+-- infer anyelement from anyrange
 create function inoutparam_succeed(out i anyelement, inout r anyrange)
   as $$ select upper($1), $1 $$ language sql;
 
diff --git a/src/test/regress/sql/type_sanity.sql b/src/test/regress/sql/type_sanity.sql
index 4b492ce062..07879d9a57 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'));
@@ -153,7 +153,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')
@@ -182,7 +182,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')
@@ -234,7 +234,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')
@@ -263,7 +263,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')
@@ -467,3 +467,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.rngmultitypid
+FROM pg_range p1
+WHERE p1.rngmultitypid IS NULL OR p1.rngmultitypid = 0;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index fcfcf56f4f..34a06fe20f 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1356,6 +1356,9 @@ MultiXactMember
 MultiXactOffset
 MultiXactStateData
 MultiXactStatus
+MultirangeIOData
+MultirangeParseState
+MultirangeType
 MyData
 NDBOX
 NODE
#115Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Paul A Jungwirth (#109)
Re: range_agg

On 2020-Mar-14, Paul A Jungwirth wrote:

On Fri, Mar 13, 2020 at 2:39 PM Alvaro Herrera <alvherre@2ndquadrant.com> wrote:

Here's the rebased version.

I just realized I didn't include the API change I proposed in
/messages/by-id/20200306200343.GA625@alvherre.pgsql ...

Thanks for your help with this Alvaro!

I was just adding your changes to my own branch and I noticed your
v12-0001 has different parameter names here:

static MultirangeIOData *
-get_multirange_io_data(FunctionCallInfo fcinfo, Oid mltrngtypid,
IOFuncSelector func)
+get_multirange_io_data(FunctionCallInfo fcinfo, Oid rngtypid,
IOFuncSelector func)

I'm pretty sure mltrngtypid is the correct name here. Right? Let me
know if I'm missing something. :-)

Heh. The intention here was to abbreviate to "typid", but if you want
to keep the longer name, it's OK too. I don't think that name is
particularly critical, since it should be obvious that it must be a
multirange type.

--
�lvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

#116Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Paul A Jungwirth (#114)
5 attachment(s)
Re: range_agg

On 2020-Mar-19, Paul A Jungwirth wrote:

On Thu, Mar 19, 2020 at 1:43 PM Paul A Jungwirth
<pj@illuminatedcomputing.com> wrote:

On Thu, Mar 19, 2020 at 1:42 PM Alvaro Herrera <alvherre@2ndquadrant.com> wrote:

There's been another flurry of commits in the polymorphic types area.
Can you please rebase again?

I noticed that too. :-) I'm about halfway through a rebase right now.
I can probably finish it up tonight.

Here is that patch. I should probably add an anycompatiblemultirange
type now too? I'll get started on that tomorrow.

Thanks for the new version. Here's a few minor adjustments while I
continue to read through it.

Thinking about the on-disk representation, can we do better than putting
the contained ranges in long-varlena format, including padding; also we
include the type OID with each element. Sounds wasteful. A more
compact representation might be to allow short varlenas and doing away
with the alignment padding, put the the type OID just once. This is
important because we cannot change it later.

I'm also wondering if multirange_in() is the right strategy. Would it
be sensible to give each range to range_parse or range_parse_bounde, so
that it determines where each range starts and ends? Then that function
doesn't have to worry about each quote and escape, duplicating range
parsing code. (This will probably require changing signature of the
rangetypes.c function, and exporting it; for example have
range_parse_bound allow bound_str to be NULL and in that case don't mess
with the StringInfo and just return the end position of the parsed
bound.)

--
�lvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

Attachments:

0001-Fix-typo.patchtext/x-diff; charset=us-asciiDownload
From bd8b814426bef6f5a620351557cdc953ba0ae22e Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Mon, 23 Mar 2020 18:50:02 -0300
Subject: [PATCH 1/5] Fix typo

---
 src/backend/utils/adt/multirangetypes.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/backend/utils/adt/multirangetypes.c b/src/backend/utils/adt/multirangetypes.c
index bd3cf3dc93..6b3cde8eca 100644
--- a/src/backend/utils/adt/multirangetypes.c
+++ b/src/backend/utils/adt/multirangetypes.c
@@ -79,7 +79,7 @@ static int32 multirange_canonicalize(TypeCacheEntry *rangetyp, int32 input_range
  * 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
+ * and a quoted string can escape quote marks with either backslashes
  * or double double-quotes.
  */
 Datum
-- 
2.20.1

0002-Remove-trailing-useless.patchtext/x-diff; charset=us-asciiDownload
From afd017faab3d6aff3ca8618d95a6f9c918ab8cca Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Mon, 23 Mar 2020 18:50:19 -0300
Subject: [PATCH 2/5] Remove trailing useless ;

---
 src/backend/utils/adt/multirangetypes.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/backend/utils/adt/multirangetypes.c b/src/backend/utils/adt/multirangetypes.c
index 6b3cde8eca..6e9cf77651 100644
--- a/src/backend/utils/adt/multirangetypes.c
+++ b/src/backend/utils/adt/multirangetypes.c
@@ -131,7 +131,7 @@ multirange_in(PG_FUNCTION_ARGS)
 							input_str),
 					 errdetail("Unexpected end of input.")));
 
-		 /* skip whitespace */ ;
+		/* skip whitespace */
 		if (isspace((unsigned char) ch))
 			continue;
 
-- 
2.20.1

0003-reduce-palloc-strlcpy-to-pnstrdup.patchtext/x-diff; charset=us-asciiDownload
From d7c1ea171de018d025ab5de57f572a9353b02fdf Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Mon, 23 Mar 2020 18:50:40 -0300
Subject: [PATCH 3/5] reduce palloc+strlcpy to pnstrdup

---
 src/backend/utils/adt/multirangetypes.c | 5 ++---
 1 file changed, 2 insertions(+), 3 deletions(-)

diff --git a/src/backend/utils/adt/multirangetypes.c b/src/backend/utils/adt/multirangetypes.c
index 6e9cf77651..5b57416cfd 100644
--- a/src/backend/utils/adt/multirangetypes.c
+++ b/src/backend/utils/adt/multirangetypes.c
@@ -167,9 +167,8 @@ multirange_in(PG_FUNCTION_ARGS)
 					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);
+					range_str_len = ptr - range_str + 1;
+					range_str_copy = pnstrdup(range_str, range_str_len);
 					if (range_capacity == range_count)
 					{
 						range_capacity *= 2;
-- 
2.20.1

0004-silence-compiler-warning.patchtext/x-diff; charset=us-asciiDownload
From 7698f57d1f5ab870c89a84da7bea24bef241a458 Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Mon, 23 Mar 2020 18:50:59 -0300
Subject: [PATCH 4/5] silence compiler warning

---
 src/backend/utils/fmgr/funcapi.c | 13 ++++++++-----
 1 file changed, 8 insertions(+), 5 deletions(-)

diff --git a/src/backend/utils/fmgr/funcapi.c b/src/backend/utils/fmgr/funcapi.c
index ab24359981..3bfefcf48a 100644
--- a/src/backend/utils/fmgr/funcapi.c
+++ b/src/backend/utils/fmgr/funcapi.c
@@ -508,10 +508,13 @@ resolve_anyelement_from_others(polymorphic_actuals *actuals)
 	else if (OidIsValid(actuals->anymultirange_type))
 	{
 		/* Use the element type based on the multirange type */
-		Oid			multirange_base_type = getBaseType(actuals->anymultirange_type);
-		Oid			multirange_typelem =
-			get_range_multirange_subtype(multirange_base_type);
+		Oid			multirange_base_type;
+		Oid			multirange_typelem;
+		Oid			range_base_type;
+		Oid			range_typelem;
 
+		multirange_base_type = getBaseType(actuals->anymultirange_type);
+		multirange_typelem = get_range_multirange_subtype(multirange_base_type);
 		if (!OidIsValid(multirange_typelem))
 			ereport(ERROR,
 					(errcode(ERRCODE_DATATYPE_MISMATCH),
@@ -519,8 +522,8 @@ resolve_anyelement_from_others(polymorphic_actuals *actuals)
 							"anymultirange",
 							format_type_be(multirange_base_type))));
 
-		Oid			range_base_type = getBaseType(multirange_typelem);
-		Oid			range_typelem = get_range_subtype(range_base_type);
+		range_base_type = getBaseType(multirange_typelem);
+		range_typelem = get_range_subtype(range_base_type);
 
 		if (!OidIsValid(range_typelem))
 			ereport(ERROR,
-- 
2.20.1

0005-rename-get_range_multirange_subtype-to-get_multirang.patchtext/x-diff; charset=us-asciiDownload
From b70eb4fcff3819a3671f21164319750a5a174083 Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Mon, 23 Mar 2020 18:58:14 -0300
Subject: [PATCH 5/5] rename get_range_multirange_subtype to
 get_multirange_range

---
 src/backend/parser/parse_coerce.c   | 4 ++--
 src/backend/utils/cache/lsyscache.c | 8 ++++----
 src/backend/utils/cache/typcache.c  | 2 +-
 src/backend/utils/fmgr/funcapi.c    | 4 ++--
 src/include/utils/lsyscache.h       | 2 +-
 5 files changed, 10 insertions(+), 10 deletions(-)

diff --git a/src/backend/parser/parse_coerce.c b/src/backend/parser/parse_coerce.c
index fb8c3e5cb0..91f80a0656 100644
--- a/src/backend/parser/parse_coerce.c
+++ b/src/backend/parser/parse_coerce.c
@@ -1757,7 +1757,7 @@ check_generic_type_consistency(const Oid *actual_arg_types,
 	{
 		Oid			multirange_typelem;
 
-		multirange_typelem = get_range_multirange_subtype(multirange_typeid);
+		multirange_typelem = get_multirange_range(multirange_typeid);
 		if (!OidIsValid(multirange_typelem))
 			return false;		/* should be a multirange, but isn't */
 
@@ -2222,7 +2222,7 @@ enforce_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_range_multirange_subtype(multirange_typeid);
+			multirange_typelem = get_multirange_range(multirange_typeid);
 			if (!OidIsValid(multirange_typelem))
 				ereport(ERROR,
 						(errcode(ERRCODE_DATATYPE_MISMATCH),
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index c6c079c623..8d773d40c4 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -3238,13 +3238,13 @@ get_range_multirange(Oid rangeOid)
 }
 
 /*
- * get_range_multirange_subtype
- *		Returns the subtype of a given multirange type
+ * get_multirange_range
+ *		Returns the range type of a given multirange
  *
- * Returns InvalidOid if the type is not a multirange type.
+ * Returns InvalidOid if the type is not a multirange.
  */
 Oid
-get_range_multirange_subtype(Oid multirangeOid)
+get_multirange_range(Oid multirangeOid)
 {
 	HeapTuple	tp;
 
diff --git a/src/backend/utils/cache/typcache.c b/src/backend/utils/cache/typcache.c
index 160846cf10..f1d3cb97d0 100644
--- a/src/backend/utils/cache/typcache.c
+++ b/src/backend/utils/cache/typcache.c
@@ -964,7 +964,7 @@ load_multirangetype_info(TypeCacheEntry *typentry)
 {
 	Oid			rangetypeOid;
 
-	rangetypeOid = get_range_multirange_subtype(typentry->type_id);
+	rangetypeOid = get_multirange_range(typentry->type_id);
 	if (!OidIsValid(rangetypeOid))
 		elog(ERROR, "cache lookup failed for multirange type %u",
 			 typentry->type_id);
diff --git a/src/backend/utils/fmgr/funcapi.c b/src/backend/utils/fmgr/funcapi.c
index 3bfefcf48a..ce09d0d128 100644
--- a/src/backend/utils/fmgr/funcapi.c
+++ b/src/backend/utils/fmgr/funcapi.c
@@ -514,7 +514,7 @@ resolve_anyelement_from_others(polymorphic_actuals *actuals)
 		Oid			range_typelem;
 
 		multirange_base_type = getBaseType(actuals->anymultirange_type);
-		multirange_typelem = get_range_multirange_subtype(multirange_base_type);
+		multirange_typelem = get_multirange_range(multirange_base_type);
 		if (!OidIsValid(multirange_typelem))
 			ereport(ERROR,
 					(errcode(ERRCODE_DATATYPE_MISMATCH),
@@ -579,7 +579,7 @@ resolve_anyrange_from_others(polymorphic_actuals *actuals)
 		/* Use the element type based on the multirange type */
 		Oid			multirange_base_type = getBaseType(actuals->anymultirange_type);
 		Oid			multirange_typelem =
-			get_range_multirange_subtype(multirange_base_type);
+			get_multirange_range(multirange_base_type);
 
 		if (!OidIsValid(multirange_typelem))
 			ereport(ERROR,
diff --git a/src/include/utils/lsyscache.h b/src/include/utils/lsyscache.h
index 80410dcd04..e361a7943b 100644
--- a/src/include/utils/lsyscache.h
+++ b/src/include/utils/lsyscache.h
@@ -183,7 +183,7 @@ extern char *get_namespace_name_or_temp(Oid nspid);
 extern Oid	get_range_subtype(Oid rangeOid);
 extern Oid	get_range_collation(Oid rangeOid);
 extern Oid	get_range_multirange(Oid rangeOid);
-extern Oid	get_range_multirange_subtype(Oid multirangeOid);
+extern Oid	get_multirange_range(Oid multirangeOid);
 extern Oid	get_index_column_opclass(Oid index_oid, int attno);
 extern bool	get_index_isreplident(Oid index_oid);
 extern bool get_index_isvalid(Oid index_oid);
-- 
2.20.1

#117Paul Jungwirth
pj@illuminatedcomputing.com
In reply to: Alvaro Herrera (#116)
Re: range_agg

Thanks Alvaro!

On Mon, Mar 23, 2020 at 4:33 PM Alvaro Herrera
<alvherre@2ndquadrant.com> wrote:

Thinking about the on-disk representation, can we do better than putting
the contained ranges in long-varlena format, including padding; also we
include the type OID with each element. �Sounds wasteful. �A more
compact representation might be to allow short varlenas and doing away
with the alignment padding, put the the type OID just once. �This is
important because we cannot change it later.

Can you give me some guidance on this? I don't know how to make the
on-disk format different from the in-memory format. (And for the
in-memory format, I think it's important to have actual RangeTypes
inside the multirange.) Is there something in the documentation, or a
README in the repo, or even another type I can follow?

I'm also wondering if multirange_in() is the right strategy. �Would

it> be sensible to give each range to range_parse or range_parse_bounde, so

that it determines where each range starts and ends? �Then that function
doesn't have to worry about each quote and escape, duplicating range
parsing code. �(This will probably require changing signature of the
rangetypes.c function, and exporting it; for example have
range_parse_bound allow bound_str to be NULL and in that case don't mess
with the StringInfo and just return the end position of the parsed
bound.)

Yeah, I really wanted to do it that way originally too. As you say it
would require passing back more information from the range-parsing code.
I can take a stab at making the necessary changes. I'm a bit more
confident now than I was then in changing the range code we have already.

Regards,

--
Paul ~{:-)
pj@illuminatedcomputing.com

#118Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Paul Jungwirth (#117)
1 attachment(s)
Re: range_agg

On 2020-Mar-23, Paul Jungwirth wrote:

On Mon, Mar 23, 2020 at 4:33 PM Alvaro Herrera <alvherre@2ndquadrant.com>
wrote:

Thinking about the on-disk representation, can we do better than putting
the contained ranges in long-varlena format, including padding; also we
include the type OID with each element. �Sounds wasteful. �A more
compact representation might be to allow short varlenas and doing away
with the alignment padding, put the the type OID just once. �This is
important because we cannot change it later.

Can you give me some guidance on this? I don't know how to make the on-disk
format different from the in-memory format. (And for the in-memory format, I
think it's important to have actual RangeTypes inside the multirange.) Is
there something in the documentation, or a README in the repo, or even
another type I can follow?

Sorry I didn't reply earlier, but I didn't know the answer then and I
still don't know the answer now.

Anyway, I rebased this to verify that the code hasn't broken, and it
hasn't -- the tests still pass. There was a minor conflict in
pg_operator.dat which I fixed.

--
�lvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

Attachments:

v16-multirange.patchtext/x-diff; charset=us-asciiDownload
diff --git a/doc/src/sgml/extend.sgml b/doc/src/sgml/extend.sgml
index 930aeb767c..febb02e80a 100644
--- a/doc/src/sgml/extend.sgml
+++ b/doc/src/sgml/extend.sgml
@@ -285,6 +285,14 @@
         </entry>
        </row>
 
+       <row>
+        <entry><type>anymultirange</type></entry>
+        <entry>Simple</entry>
+        <entry>Indicates that a function accepts any multirange data type
+        (see <xref linkend="rangetypes"/>)
+        </entry>
+       </row>
+
        <row>
         <entry><type>anycompatible</type></entry>
         <entry>Common</entry>
@@ -343,17 +351,15 @@
      position declared as <type>anyarray</type> can have any array data type,
      but similarly they must all be the same type.  And similarly,
      positions declared as <type>anyrange</type> must all be the same range
-     type.  Furthermore, if there are
+     type.  Likewise for <type>anymultirange</type>.
+    </para>
+
+    <para>
+     Furthermore, if there are
      positions declared <type>anyarray</type> and others declared
      <type>anyelement</type>, the actual array type in the
      <type>anyarray</type> positions must be an array whose elements are
      the same type appearing in the <type>anyelement</type> positions.
-     Similarly, if there are positions declared <type>anyrange</type>
-     and others declared <type>anyelement</type> or <type>anyarray</type>,
-     the actual range type in the <type>anyrange</type> positions must be a
-     range whose subtype is the same type appearing in
-     the <type>anyelement</type> positions and the same as the element type
-     of the <type>anyarray</type> positions.
      <type>anynonarray</type> is treated exactly the same as <type>anyelement</type>,
      but adds the additional constraint that the actual type must not be
      an array type.
@@ -362,6 +368,19 @@
      be an enum type.
     </para>
 
+    <para>
+     Similarly, if there are positions declared <type>anyrange</type>
+     and others declared <type>anyelement</type> or <type>anyarray</type>,
+     the actual range type in the <type>anyrange</type> positions must be a
+     range whose subtype is the same type appearing in
+     the <type>anyelement</type> positions and the same as the element type
+     of the <type>anyarray</type> positions.
+     If there are positions declared <type>anymultirange</type>,
+     their actual multirange type must contain ranges matching parameters declared
+     <type>anyrange</type> and base elements matching parameters declared
+     <type>anyelement</type> and <type>anyarray</type>.
+    </para>
+
     <para>
      Thus, when more than one argument position is declared with a polymorphic
      type, the net effect is that only certain combinations of actual argument
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 4d88b45e72..b2168f3801 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -13609,7 +13609,7 @@ SELECT NULLIF(value, '(none)') ...
        <row>
         <entry>Operator</entry>
         <entry>Description</entry>
-        <entry>Example</entry>
+        <entry>Examples</entry>
         <entry>Result</entry>
        </row>
       </thead>
@@ -14080,12 +14080,14 @@ NULL baz</literallayout>(3 rows)</entry>
   <title>Range Functions and Operators</title>
 
   <para>
-   See <xref linkend="rangetypes"/> for an overview of range types.
+   See <xref linkend="rangetypes"/> for an overview of range and multirange types.
   </para>
 
   <para>
    <xref linkend="range-operators-table"/> shows the operators
-   available for range types.
+   available for range and multirange types.
+   Many of these operators will accept either a range or multirange
+   on either side.
   </para>
 
     <table id="range-operators-table">
@@ -14103,134 +14105,215 @@ NULL baz</literallayout>(3 rows)</entry>
        <row>
         <entry> <literal>=</literal> </entry>
         <entry>equal</entry>
-        <entry><literal>int4range(1,5) = '[1,4]'::int4range</literal></entry>
-        <entry><literal>t</literal></entry>
+        <entry><literallayout class="monospaced">int4range(1,5) = '[1,4]'::int4range
+'{[1,5)}'::int4multirange = '{[1,4]}'::int4multirange</literallayout></entry>
+        <entry><literallayout class="monospaced">t</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>&lt;&gt;</literal> </entry>
         <entry>not equal</entry>
-        <entry><literal>numrange(1.1,2.2) &lt;&gt; numrange(1.1,2.3)</literal></entry>
-        <entry><literal>t</literal></entry>
+        <entry><literallayout class="monospaced">numrange(1.1,2.2) &lt;&gt; numrange(1.1,2.3)
+'{[1.1,2.2)}'::nummultirange &lt;&gt; '{[1.1,2.3)}'::nummultirange</literallayout></entry>
+        <entry><literallayout class="monospaced">t</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>&lt;</literal> </entry>
         <entry>less than</entry>
-        <entry><literal>int4range(1,10) &lt; int4range(2,3)</literal></entry>
-        <entry><literal>t</literal></entry>
+        <entry><literallayout class="monospaced">int4range(1,10) &lt; int4range(2,3)
+'{[1,10)}'::int4multirange &lt; '{[2,3)}'::int4multirange</literallayout>
+        </entry>
+        <entry><literallayout class="monospaced">t</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>&gt;</literal> </entry>
         <entry>greater than</entry>
-        <entry><literal>int4range(1,10) &gt; int4range(1,5)</literal></entry>
-        <entry><literal>t</literal></entry>
+        <entry>
+          <literallayout class="monospaced">int4range(1,10) &gt; int4range(1,5)
+'{[1,10)}'::int4multirange &gt; '{[1,5)}'::int4multirange</literallayout>
+        </entry>
+        <entry><literallayout class="monospaced">t</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>&lt;=</literal> </entry>
         <entry>less than or equal</entry>
-        <entry><literal>numrange(1.1,2.2) &lt;= numrange(1.1,2.2)</literal></entry>
-        <entry><literal>t</literal></entry>
+        <entry>
+          <literallayout class="monospaced">numrange(1.1,2.2) &lt;= numrange(1.1,2.2)
+'{[1.1,2.2)}'::nummultirange &lt;= '{[1.1,2.2)}'::nummultirange</literallayout>
+        </entry>
+        <entry><literallayout class="monospaced">t</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>&gt;=</literal> </entry>
         <entry>greater than or equal</entry>
-        <entry><literal>numrange(1.1,2.2) &gt;= numrange(1.1,2.0)</literal></entry>
-        <entry><literal>t</literal></entry>
+        <entry>
+          <literallayout class="monospaced">numrange(1.1,2.2) &gt;= numrange(1.1,2.0)
+'{[1.1,2.2)}'::nummultirange &gt;= '{[1.1,2.0)}'::nummultirange</literallayout>
+        </entry>
+        <entry><literallayout class="monospaced">t</literallayout></entry>
+       </row>
+
+       <row>
+        <entry> <literal>@&gt;</literal> </entry>
+        <entry>contains multirange</entry>
+        <entry>
+          <literallayout class="monospaced">int4range(2,4) @&gt; '{[2,3)}'::int4multirange
+'{[2,4)}'::int4multirange @&gt; '{[2,3)}'::int4multirange</literallayout>
+        </entry>
+        <entry><literallayout class="monospaced">t</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>@&gt;</literal> </entry>
         <entry>contains range</entry>
-        <entry><literal>int4range(2,4) @&gt; int4range(2,3)</literal></entry>
-        <entry><literal>t</literal></entry>
+        <entry>
+          <literallayout class="monospaced">int4range(2,4) @&gt; int4range(2,3)
+'{[2,4)}'::int4multirange @&gt; int4range(2,3)</literallayout>
+        </entry>
+        <entry><literallayout class="monospaced">t</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>@&gt;</literal> </entry>
         <entry>contains element</entry>
-        <entry><literal>'[2011-01-01,2011-03-01)'::tsrange @&gt; '2011-01-10'::timestamp</literal></entry>
-        <entry><literal>t</literal></entry>
+        <entry>
+          <literallayout class="monospaced">'[2011-01-01,2011-03-01)'::tsrange @&gt; '2011-01-10'::timestamp
+'{[2011-01-01,2011-03-01)}'::tsmultirange @&gt; '2011-01-10'::timestamp</literallayout>
+        </entry>
+        <entry><literallayout class="monospaced">t</literallayout></entry>
+       </row>
+
+       <row>
+        <entry> <literal>&lt;@</literal> </entry>
+        <entry>multirange is contained by</entry>
+        <entry>
+          <literallayout class="monospaced">'{[2,4)}'::int4multirange &lt;@ int4range(1,7)
+'{[2,4)}'::int4multirange &lt;@ '{[1,7)}'::int4multirange</literallayout>
+        </entry>
+        <entry><literallayout class="monospaced">t</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>&lt;@</literal> </entry>
         <entry>range is contained by</entry>
-        <entry><literal>int4range(2,4) &lt;@ int4range(1,7)</literal></entry>
-        <entry><literal>t</literal></entry>
+        <entry>
+          <literallayout class="monospaced">int4range(2,4) &lt;@ int4range(1,7)
+int4range(2,4) &lt;@ '{[1,7)}'::int4multirange</literallayout>
+        </entry>
+        <entry><literallayout class="monospaced">t</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>&lt;@</literal> </entry>
         <entry>element is contained by</entry>
-        <entry><literal>42 &lt;@ int4range(1,7)</literal></entry>
-        <entry><literal>f</literal></entry>
+        <entry>
+          <literallayout class="monospaced">42 &lt;@ int4range(1,7)
+42 &lt;@ '{[1,7)}'::int4multirange</literallayout>
+        </entry>
+        <entry><literallayout class="monospaced">f</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>&amp;&amp;</literal> </entry>
         <entry>overlap (have points in common)</entry>
-        <entry><literal>int8range(3,7) &amp;&amp; int8range(4,12)</literal></entry>
-        <entry><literal>t</literal></entry>
+        <entry>
+          <literallayout class="monospaced">int8range(3,7) &amp;&amp; int8range(4,12)
+int8range(3,7) &amp;&amp; '{[4,12)}'::int8multirange
+'{[3,7)}'::int8multirange &amp;&amp; int8range(4,12)
+'{[3,7)}'::int8multirange &amp;&amp; '{[4,12)}'::int8multirange</literallayout>
+        </entry>
+        <entry><literallayout class="monospaced">t</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>&lt;&lt;</literal> </entry>
         <entry>strictly left of</entry>
-        <entry><literal>int8range(1,10) &lt;&lt; int8range(100,110)</literal></entry>
-        <entry><literal>t</literal></entry>
+        <entry>
+          <literallayout class="monospaced">int8range(1,10) &lt;&lt; int8range(100,110)
+int8range(1,10) &lt;&lt; '{[100,110)}'::int8multirange
+'{[1,10)}'::int8multirange &lt;&lt; int8range(100,110)
+'{[1,10)}'::int8multirange &lt;&lt; '{[100,110)}'::int8multirange</literallayout>
+        </entry>
+        <entry><literallayout class="monospaced">t</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>&gt;&gt;</literal> </entry>
         <entry>strictly right of</entry>
-        <entry><literal>int8range(50,60) &gt;&gt; int8range(20,30)</literal></entry>
-        <entry><literal>t</literal></entry>
+        <entry>
+          <literallayout class="monospaced">int8range(50,60) &gt;&gt; int8range(20,30)
+int8range(50,60) &gt;&gt; '{[20,30)}'::int8multirange
+'{[50,60)}'::int8multirange &gt;&gt; int8range(20,30)
+'{[50,60)}'::int8multirange &gt;&gt; '{[20,30)}'::int8multirange</literallayout>
+        </entry>
+        <entry><literallayout class="monospaced">t</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>&amp;&lt;</literal> </entry>
         <entry>does not extend to the right of</entry>
-        <entry><literal>int8range(1,20) &amp;&lt; int8range(18,20)</literal></entry>
-        <entry><literal>t</literal></entry>
+        <entry>
+          <literallayout class="monospaced">int8range(1,20) &amp;&lt; int8range(18,20)
+int8range(1,20) &amp;&lt; '{[18,20)}'::int8multirange
+'{[1,20)}'::int8multirange &amp;&lt; int8range(18,20)
+'{[1,20)}'::int8multirange &amp;&lt; '{[18,20)}'::int8multirange</literallayout>
+        </entry>
+        <entry><literallayout class="monospaced">t</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>&amp;&gt;</literal> </entry>
         <entry>does not extend to the left of</entry>
-        <entry><literal>int8range(7,20) &amp;&gt; int8range(5,10)</literal></entry>
-        <entry><literal>t</literal></entry>
+        <entry>
+          <literallayout class="monospaced">int8range(7,20) &amp;&gt; int8range(5,10)
+int8range(7,20) &amp;&gt; '{[5,10)}'::int8multirange
+'{[7,20)}'::int8multirange &amp;&gt; int8range(5,10)
+'{[7,20)}'::int8multirange &amp;&gt; '{[5,10)}'::int8multirange</literallayout>
+        </entry>
+        <entry><literallayout class="monospaced">t</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>-|-</literal> </entry>
         <entry>is adjacent to</entry>
-        <entry><literal>numrange(1.1,2.2) -|- numrange(2.2,3.3)</literal></entry>
-        <entry><literal>t</literal></entry>
+        <entry>
+          <literallayout class="monospaced">numrange(1.1,2.2) -|- numrange(2.2,3.3)
+numrange(1.1,2.2) -|- '{[2.2,3.3)}'::nummultirange
+'{[1.1,2.2)}'::nummultirange -|- numrange(2.2,3.3)
+'{[1.1,2.2)}'::nummultirange -|- '{[2.2,3.3)}'::nummultirange</literallayout>
+        </entry>
+        <entry><literallayout class="monospaced">t</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>+</literal> </entry>
         <entry>union</entry>
-        <entry><literal>numrange(5,15) + numrange(10,20)</literal></entry>
-        <entry><literal>[5,20)</literal></entry>
+        <entry><literallayout class="monospaced">numrange(5,15) + numrange(10,20)
+'{[5,10)}'::nummultirange + '{[15,20)}'::nummultirange</literallayout></entry>
+        <entry><literallayout class="monospaced">[5,20)
+{[5,10), [15,20)}</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>*</literal> </entry>
         <entry>intersection</entry>
-        <entry><literal>int8range(5,15) * int8range(10,20)</literal></entry>
-        <entry><literal>[10,15)</literal></entry>
+        <entry><literallayout class="monospaced">int8range(5,15) * int8range(10,20)
+'{[5,15)}'::int8multirange * '{[10,20)}'::int8multirange</literallayout></entry>
+        <entry><literallayout class="monospaced">[10,15)
+{[10,15)}</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>-</literal> </entry>
         <entry>difference</entry>
-        <entry><literal>int8range(5,15) - int8range(10,20)</literal></entry>
-        <entry><literal>[5,10)</literal></entry>
+        <entry><literallayout class="monospaced">int8range(5,15) - int8range(10,20)
+'{[5,20)}'::int8multirange - '{[10,15)}'::int8multirange</literallayout></entry>
+        <entry><literallayout class="monospaced">[5,10)
+{[5,10), [15,20)}</literallayout></entry>
        </row>
 
       </tbody>
@@ -14248,19 +14331,31 @@ NULL baz</literallayout>(3 rows)</entry>
 
   <para>
    The left-of/right-of/adjacent operators always return false when an empty
-   range is involved; that is, an empty range is not considered to be either
-   before or after any other range.
+   range or multirange is involved; that is, an empty range is not considered to
+   be either before or after any other range.
+  </para>
+
+  <para>
+   Elsewhere empty ranges and multiranges are treated as the additive identity:
+   anything unioned with an empty value is itself. Anything minus an empty
+   value is itself. An empty multirange has exactly the same points as an empty
+   range. Every range contains the empty range. Every multirange contains as many
+   empty ranges as you like.
   </para>
 
   <para>
    The union and difference operators will fail if the resulting range would
    need to contain two disjoint sub-ranges, as such a range cannot be
-   represented.
+   represented. There are separate operators for union and difference that take
+   multirange parameters and return a multirange, and they do not fail even if
+   their arguments are disjoint. So if you need a union or difference operation
+   for ranges that may be disjoint, you can avoid errors by first casting your
+   ranges to multiranges.
   </para>
 
   <para>
    <xref linkend="range-functions-table"/> shows the functions
-   available for use with range types.
+   available for use with range and multirange types.
   </para>
 
   <indexterm>
@@ -14309,6 +14404,17 @@ NULL baz</literallayout>(3 rows)</entry>
         <entry><literal>lower(numrange(1.1,2.2))</literal></entry>
         <entry><literal>1.1</literal></entry>
        </row>
+       <row>
+        <entry>
+         <literal>
+          <function>lower</function>(<type>anymultirange</type>)
+         </literal>
+        </entry>
+        <entry>multirange's element type</entry>
+        <entry>lower bound of multirange</entry>
+        <entry><literal>lower('{[1.1,2.2)}'::nummultirange)</literal></entry>
+        <entry><literal>1.1</literal></entry>
+       </row>
        <row>
         <entry>
          <literal>
@@ -14320,6 +14426,17 @@ NULL baz</literallayout>(3 rows)</entry>
         <entry><literal>upper(numrange(1.1,2.2))</literal></entry>
         <entry><literal>2.2</literal></entry>
        </row>
+       <row>
+        <entry>
+         <literal>
+          <function>upper</function>(<type>anymultirange</type>)
+         </literal>
+        </entry>
+        <entry>multirange's element type</entry>
+        <entry>upper bound of multirange</entry>
+        <entry><literal>upper('{[1.1,2.2)}'::nummultirange)</literal></entry>
+        <entry><literal>2.2</literal></entry>
+       </row>
        <row>
         <entry>
          <literal>
@@ -14331,6 +14448,17 @@ NULL baz</literallayout>(3 rows)</entry>
         <entry><literal>isempty(numrange(1.1,2.2))</literal></entry>
         <entry><literal>false</literal></entry>
        </row>
+       <row>
+        <entry>
+         <literal>
+          <function>isempty</function>(<type>anymultirange</type>)
+         </literal>
+        </entry>
+        <entry><type>boolean</type></entry>
+        <entry>is the multirange empty?</entry>
+        <entry><literal>isempty('{[1.1,2.2)}'::nummultirange)</literal></entry>
+        <entry><literal>false</literal></entry>
+       </row>
        <row>
         <entry>
          <literal>
@@ -14342,6 +14470,17 @@ NULL baz</literallayout>(3 rows)</entry>
         <entry><literal>lower_inc(numrange(1.1,2.2))</literal></entry>
         <entry><literal>true</literal></entry>
        </row>
+       <row>
+        <entry>
+         <literal>
+          <function>lower_inc</function>(<type>anymultirange</type>)
+         </literal>
+        </entry>
+        <entry><type>boolean</type></entry>
+        <entry>is the lower bound inclusive?</entry>
+        <entry><literal>lower_inc('{[1.1,2.2)}'::nummultirange)</literal></entry>
+        <entry><literal>true</literal></entry>
+       </row>
        <row>
         <entry>
          <literal>
@@ -14353,6 +14492,17 @@ NULL baz</literallayout>(3 rows)</entry>
         <entry><literal>upper_inc(numrange(1.1,2.2))</literal></entry>
         <entry><literal>false</literal></entry>
        </row>
+       <row>
+        <entry>
+         <literal>
+          <function>upper_inc</function>(<type>anymultirange</type>)
+         </literal>
+        </entry>
+        <entry><type>boolean</type></entry>
+        <entry>is the upper bound inclusive?</entry>
+        <entry><literal>upper_inc('{[1.1,2.2)}'::nummultirange)</literal></entry>
+        <entry><literal>false</literal></entry>
+       </row>
        <row>
         <entry>
          <literal>
@@ -14364,6 +14514,17 @@ NULL baz</literallayout>(3 rows)</entry>
         <entry><literal>lower_inf('(,)'::daterange)</literal></entry>
         <entry><literal>true</literal></entry>
        </row>
+       <row>
+        <entry>
+         <literal>
+          <function>lower_inf</function>(<type>anymultirange</type>)
+         </literal>
+        </entry>
+        <entry><type>boolean</type></entry>
+        <entry>is the lower bound infinite?</entry>
+        <entry><literal>lower_inf('{(,)}'::datemultirange)</literal></entry>
+        <entry><literal>true</literal></entry>
+       </row>
        <row>
         <entry>
          <literal>
@@ -14375,6 +14536,17 @@ NULL baz</literallayout>(3 rows)</entry>
         <entry><literal>upper_inf('(,)'::daterange)</literal></entry>
         <entry><literal>true</literal></entry>
        </row>
+       <row>
+        <entry>
+         <literal>
+          <function>upper_inf</function>(<type>anymultirange</type>)
+         </literal>
+        </entry>
+        <entry><type>boolean</type></entry>
+        <entry>is the upper bound infinite?</entry>
+        <entry><literal>upper_inf('{(,)}'::datemultirange)</literal></entry>
+        <entry><literal>true</literal></entry>
+       </row>
        <row>
         <entry>
          <literal>
@@ -14386,16 +14558,38 @@ NULL baz</literallayout>(3 rows)</entry>
         <entry><literal>range_merge('[1,2)'::int4range, '[3,4)'::int4range)</literal></entry>
         <entry><literal>[1,4)</literal></entry>
        </row>
+       <row>
+        <entry>
+         <literal>
+          <function>range_merge</function>(<type>anymultirange</type>)
+         </literal>
+        </entry>
+        <entry><type>anyrange</type></entry>
+        <entry>the smallest range which includes the entire multirange</entry>
+        <entry><literal>range_merge('{[1,2), [3,4)}'::int4multirange)</literal></entry>
+        <entry><literal>[1,4)</literal></entry>
+       </row>
+       <row>
+        <entry>
+         <literal>
+          <function>multirange</function>(<type>anyrange</type>)
+         </literal>
+        </entry>
+        <entry><type>anymultirange</type></entry>
+        <entry>a multirange containing just the given range</entry>
+        <entry><literal>multirange('[1,2)'::int4range)</literal></entry>
+        <entry><literal>{[1,2)}</literal></entry>
+       </row>
       </tbody>
      </tgroup>
     </table>
 
   <para>
    The <function>lower</function> and  <function>upper</function> functions return null
-   if the range is empty or the requested bound is infinite.
+   if the input range/multirange is empty or the requested bound is infinite.
    The <function>lower_inc</function>, <function>upper_inc</function>,
    <function>lower_inf</function>, and <function>upper_inf</function>
-   functions all return false for an empty range.
+   functions all return false for an empty range or multirange.
   </para>
   </sect1>
 
@@ -14714,6 +14908,44 @@ NULL baz</literallayout>(3 rows)</entry>
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <indexterm>
+        <primary>range_agg</primary>
+       </indexterm>
+       <function>
+         range_agg(<replaceable class="parameter">expression</replaceable>)
+       </function>
+      </entry>
+      <entry>
+       <type>anyrange</type>
+      </entry>
+      <entry>
+       <type>multirange</type> of the argument range type
+      </entry>
+      <entry>Yes</entry>
+      <entry>union of the non-null input values</entry>
+     </row>
+
+     <row>
+      <entry>
+       <indexterm>
+        <primary>range_intersect_agg</primary>
+       </indexterm>
+       <function>
+         range_intersect_agg(<replaceable class="parameter">expression</replaceable>)
+       </function>
+      </entry>
+      <entry>
+       <type>anyrange</type>
+      </entry>
+      <entry>
+       <type>multirange</type> of the argument range type
+      </entry>
+      <entry>No</entry>
+      <entry>intersection of the non-null input values</entry>
+     </row>
+
      <row>
       <entry>
        <indexterm>
diff --git a/doc/src/sgml/rangetypes.sgml b/doc/src/sgml/rangetypes.sgml
index b75fb3a392..c8784bdce3 100644
--- a/doc/src/sgml/rangetypes.sgml
+++ b/doc/src/sgml/rangetypes.sgml
@@ -27,6 +27,13 @@
   ranges from an instrument, and so forth can also be useful.
  </para>
 
+ <para>
+  Every range type has a corresponding multirange type. A multirange is
+  an ordered list of non-continguous, non-empty, non-null ranges. Most
+  range operators also work on multiranges, and they have a few functions
+  of their own.
+ </para>
+
  <sect2 id="rangetypes-builtin">
   <title>Built-in Range Types</title>
 
@@ -232,10 +239,30 @@ SELECT '[4,4]'::int4range;
 SELECT '[4,4)'::int4range;
 </programlisting>
   </para>
+
+  <para>
+   The input for a multirange is curly brackets (<literal>{</literal> and
+   <literal>}</literal>) containing zero or more valid ranges,
+   separated by commas. Whitespace is permitted around the brackets and
+   commas. This is intended to be reminiscent of array syntax, although
+   multiranges are much simpler: they have just one dimension and there is
+   no need to quote their contents. (The bounds of their ranges may be
+   quoted as above however.)
+  </para>
+
+  <para>
+  Examples:
+<programlisting>
+SELECT '{}'::int4multirange;
+SELECT '{[3,7)}'::int4multirange;
+SELECT '{[3,7), [8,9)}'::int4multirange;
+</programlisting>
+  </para>
+
  </sect2>
 
  <sect2 id="rangetypes-construct">
-  <title>Constructing Ranges</title>
+  <title>Constructing Ranges and Multiranges</title>
 
   <para>
    Each range type has a constructor function with the same name as the range
@@ -267,6 +294,19 @@ SELECT int8range(1, 14, '(]');
 
 -- Using NULL for either bound causes the range to be unbounded on that side.
 SELECT numrange(NULL, 2.2);
+</programlisting>
+  </para>
+
+  <para>
+   Each range type also has a multirange constructor with the same name as the
+   multirange type.  The constructor function takes zero or more arguments
+   which are all ranges of the appropriate type.
+   For example:
+
+<programlisting>
+SELECT nummultirange();
+SELECT nummultirange(numrange(1.0, 14.0));
+SELECT nummultirange(numrange(1.0, 14.0), numrange(20.0, 25.0));
 </programlisting>
   </para>
  </sect2>
@@ -341,6 +381,11 @@ SELECT '[1.234, 5.678]'::floatrange;
    function in this example.
   </para>
 
+  <para>
+   When you define your own range you automatically get a corresponding
+   multirange type.
+  </para>
+
   <para>
    Defining your own range type also allows you to specify a different
    subtype B-tree operator class or collation to use, so as to change the sort
diff --git a/src/backend/catalog/pg_range.c b/src/backend/catalog/pg_range.c
index b5bc36c2bd..1217a6ddd0 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_rngmultitypid - 1] = ObjectIdGetDatum(multirangeTypeOid);
 
 	tup = heap_form_tuple(RelationGetDescr(pg_range), values, nulls);
 
@@ -100,6 +102,12 @@ 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 cd56714968..c359f27c74 100644
--- a/src/backend/catalog/pg_type.c
+++ b/src/backend/catalog/pg_type.c
@@ -27,6 +27,7 @@
 #include "catalog/pg_proc.h"
 #include "catalog/pg_type.h"
 #include "commands/typecmds.h"
+#include "mb/pg_wchar.h"
 #include "miscadmin.h"
 #include "parser/scansup.h"
 #include "utils/acl.h"
@@ -36,6 +37,9 @@
 #include "utils/rel.h"
 #include "utils/syscache.h"
 
+static char *makeUniqueTypeName(const char *typeName, Oid typeNamespace,
+								bool tryOriginal);
+
 /* Potentially set by pg_upgrade_support functions */
 Oid			binary_upgrade_next_pg_type_oid = InvalidOid;
 
@@ -809,31 +813,10 @@ RenameTypeInternal(Oid typeOid, const char *newTypeName, Oid typeNamespace)
 char *
 makeArrayTypeName(const char *typeName, Oid typeNamespace)
 {
-	char	   *arr = (char *) palloc(NAMEDATALEN);
-	int			namelen = strlen(typeName);
-	int			i;
+	char	   *arr;
 
-	/*
-	 * 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)
+	arr = makeUniqueTypeName(typeName, typeNamespace, false);
+	if (arr == NULL)
 		ereport(ERROR,
 				(errcode(ERRCODE_DUPLICATE_OBJECT),
 				 errmsg("could not form array type name for type \"%s\"",
@@ -904,3 +887,91 @@ moveArrayTypeName(Oid typeOid, const char *typeName, Oid typeNamespace)
 
 	return true;
 }
+
+
+/*
+ * makeMultirangeTypeName
+ *	  - given a range type name, make a multirange type name for it
+ *
+ * caller is responsible for pfreeing the result
+ */
+char *
+makeMultirangeTypeName(const char *rangeTypeName, Oid typeNamespace)
+{
+	char	   *buf;
+	char	   *mrname;
+	char	   *rangestr;
+
+	/*
+	 * 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...
+	 */
+	rangestr = strstr(rangeTypeName, "range");
+	if (rangestr)
+	{
+		char	*prefix = pnstrdup(rangeTypeName, rangestr - rangeTypeName);
+
+		buf = psprintf("%s%s%s", prefix, "multi", rangestr);
+	}
+	else
+		buf = psprintf("%s_multirange", pnstrdup(rangeTypeName, NAMEDATALEN - 12));
+
+	/* clip it at NAMEDATALEN-1 bytes */
+	buf[pg_mbcliplen(buf, strlen(buf), NAMEDATALEN - 1)] = '\0';
+
+	mrname = makeUniqueTypeName(buf, typeNamespace, true);
+	if (mrname == NULL)
+		ereport(ERROR,
+				(errcode(ERRCODE_DUPLICATE_OBJECT),
+				 errmsg("could not form multirange type name for type \"%s\"",
+						rangeTypeName)));
+
+	return mrname;
+}
+
+/*
+ * makeUniqueTypeName
+ *		Generate a unique name for a prospective new type
+ *
+ * Given a typeName, return a new palloc'ed name by preprending underscores
+ * until a non-conflicting name results.
+ *
+ * If tryOriginal, first try with zero underscores.
+ */
+static char *
+makeUniqueTypeName(const char *typeName, Oid typeNamespace, bool tryOriginal)
+{
+	int			i;
+	int			namelen;
+	char		dest[NAMEDATALEN];
+
+	Assert(strlen(typeName) <= NAMEDATALEN - 1);
+
+	if (tryOriginal &&
+		!SearchSysCacheExists2(TYPENAMENSP,
+							   CStringGetDatum(typeName),
+							   ObjectIdGetDatum(typeNamespace)))
+		return pstrdup(typeName);
+
+	/*
+	 * The idea is to prepend underscores as needed until we make a name that
+	 * doesn't collide with anything ...
+	 */
+	namelen = strlen(typeName);
+	for (i = 1; i < NAMEDATALEN - 1; i++)
+	{
+		dest[i - 1] = '_';
+		strlcpy(dest + i, typeName, NAMEDATALEN - i);
+		if (namelen + i >= NAMEDATALEN)
+			truncate_identifier(dest, NAMEDATALEN, false);
+
+		if (!SearchSysCacheExists2(TYPENAMENSP,
+								   CStringGetDatum(dest),
+								   ObjectIdGetDatum(typeNamespace)))
+			return pstrdup(dest);
+	}
+
+	return NULL;
+}
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index 8891b1d564..dfb2edd3d0 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -105,9 +105,14 @@ typedef struct
 
 /* Potentially set by pg_upgrade_support functions */
 Oid			binary_upgrade_next_array_pg_type_oid = InvalidOid;
+Oid			binary_upgrade_next_mrng_pg_type_oid = InvalidOid;
+Oid			binary_upgrade_next_mrng_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 rangeOid,
+									   Oid rangeArrayOid, Oid *castFuncOid);
 static Oid	findTypeInputFunction(List *procname, Oid typeOid);
 static Oid	findTypeOutputFunction(List *procname, Oid typeOid);
 static Oid	findTypeReceiveFunction(List *procname, Oid typeOid);
@@ -738,7 +743,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",
@@ -1279,6 +1285,11 @@ checkEnumOwner(HeapTuple tup)
 /*
  * DefineRange
  *		Registers a new range type.
+ *
+ * Perhaps it might be worthwhile to set pg_type.typelem to the base type,
+ * and likewise on multiranges to set it to the range type. But having a
+ * non-zero typelem is treated elsewhere as a synonym for being an array,
+ * and users might have queries with that same assumption.
  */
 ObjectAddress
 DefineRange(CreateRangeStmt *stmt)
@@ -1287,7 +1298,11 @@ DefineRange(CreateRangeStmt *stmt)
 	Oid			typeNamespace;
 	Oid			typoid;
 	char	   *rangeArrayName;
+	char	   *multirangeTypeName;
+	char	   *multirangeArrayName;
 	Oid			rangeArrayOid;
+	Oid			multirangeOid;
+	Oid			multirangeArrayOid;
 	Oid			rangeSubtype = InvalidOid;
 	List	   *rangeSubOpclassName = NIL;
 	List	   *rangeCollationName = NIL;
@@ -1304,6 +1319,8 @@ DefineRange(CreateRangeStmt *stmt)
 	AclResult	aclresult;
 	ListCell   *lc;
 	ObjectAddress address;
+	ObjectAddress mltrngaddress;
+	Oid			castFuncOid;
 
 	/* Convert list of names to a name and namespace */
 	typeNamespace = QualifiedNameGetCreationNamespace(stmt->typeName,
@@ -1452,8 +1469,10 @@ DefineRange(CreateRangeStmt *stmt)
 	/* alignment must be TYPALIGN_INT or TYPALIGN_DOUBLE for ranges */
 	alignment = (subtypalign == TYPALIGN_DOUBLE) ? TYPALIGN_DOUBLE : TYPALIGN_INT;
 
-	/* Allocate OID for array type */
+	/* Allocate OID for array type, its multirange, and its multirange array */
 	rangeArrayOid = AssignTypeArrayOid();
+	multirangeOid = AssignTypeMultirangeOid();
+	multirangeArrayOid = AssignTypeMultirangeArrayOid();
 
 	/* Create the pg_type entry */
 	address =
@@ -1491,9 +1510,46 @@ DefineRange(CreateRangeStmt *stmt)
 	Assert(typoid == InvalidOid || typoid == address.objectId);
 	typoid = address.objectId;
 
+	/* Create the multirange that goes with it */
+	multirangeTypeName = makeMultirangeTypeName(typeName, typeNamespace);
+
+	mltrngaddress =
+		TypeCreate(multirangeOid,	/* force assignment of this 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_RANGE,	/* type-category (range 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) */
+	Assert(multirangeOid == mltrngaddress.objectId);
+
 	/* Create the entry in pg_range */
 	RangeCreate(typoid, rangeSubtype, rangeCollation, rangeSubOpclass,
-				rangeCanonical, rangeSubtypeDiff);
+				rangeCanonical, rangeSubtypeDiff, multirangeOid);
 
 	/*
 	 * Create the array type that goes with it.
@@ -1534,8 +1590,53 @@ 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 */
+			   multirangeOid,	/* 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,
+							   multirangeOid, typoid, rangeArrayOid,
+							   &castFuncOid);
+
+	/* Create cast from the range type to its multirange type */
+	CastCreate(typoid, multirangeOid, castFuncOid, 'e', 'f', DEPENDENCY_INTERNAL);
+
+	pfree(multirangeTypeName);
+	pfree(multirangeArrayName);
 
 	return address;
 }
@@ -1613,6 +1714,147 @@ 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.
+ *
+ * Sets castFuncOid to the oid of the new constructor that can be used
+ * to cast from a range to a multirange.
+ */
+static void
+makeMultirangeConstructors(const char *name, Oid namespace,
+						   Oid multirangeOid, Oid rangeOid, Oid rangeArrayOid,
+						   Oid *castFuncOid)
+{
+	ObjectAddress myself,
+				referenced;
+	oidvector  *argtypes;
+	Datum		allParamTypes;
+	ArrayType  *allParameterTypes;
+	Datum		paramModes;
+	ArrayType  *parameterModes;
+
+	referenced.classId = TypeRelationId;
+	referenced.objectId = multirangeOid;
+	referenced.objectSubId = 0;
+
+	/* 0-arg constructor - for empty multiranges */
+	argtypes = buildoidvector(NULL, 0);
+	myself = ProcedureCreate(name,	/* name: same as multirange type */
+							 namespace,
+							 false,	/* replace */
+							 false,	/* returns set */
+							 multirangeOid,	/* return type */
+							 BOOTSTRAP_SUPERUSERID,	/* proowner */
+							 INTERNALlanguageId,	/* language */
+							 F_FMGR_INTERNAL_VALIDATOR,
+							 "multirange_constructor0",	/* prosrc */
+							 NULL,	/* probin */
+							 PROKIND_FUNCTION,
+							 false,	/* security_definer */
+							 false,	/* leakproof */
+							 false, /* isStrict */
+							 PROVOLATILE_IMMUTABLE,	/* volatility */
+							 PROPARALLEL_SAFE,	/* parallel safety */
+							 argtypes,	/* parameterTypes */
+							 PointerGetDatum(NULL),	/* allParameterTypes */
+							 PointerGetDatum(NULL),	/* parameterModes */
+							 PointerGetDatum(NULL),	/* parameterNames */
+							 NIL,	/* parameterDefaults */
+							 PointerGetDatum(NULL),	/* trftypes */
+							 PointerGetDatum(NULL),	/* proconfig */
+							 InvalidOid,	/* prosupport */
+							 1.0,	/* procost */
+							 0.0);	/* prorows */
+	/*
+	 * Make the constructor 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);
+	pfree(argtypes);
+
+	/*
+	 * 1-arg constructor - for casts
+	 *
+	 * In theory we shouldn't need both this and the vararg (n-arg) constructor,
+	 * but having a separate 1-arg function lets us define casts against it.
+	 */
+	argtypes = buildoidvector(&rangeOid, 1);
+	myself = ProcedureCreate(name,	/* name: same as multirange type */
+							 namespace,
+							 false,	/* replace */
+							 false,	/* returns set */
+							 multirangeOid,	/* return type */
+							 BOOTSTRAP_SUPERUSERID,	/* proowner */
+							 INTERNALlanguageId,	/* language */
+							 F_FMGR_INTERNAL_VALIDATOR,
+							 "multirange_constructor1",	/* prosrc */
+							 NULL,	/* probin */
+							 PROKIND_FUNCTION,
+							 false,	/* security_definer */
+							 false,	/* leakproof */
+							 true, /* isStrict */
+							 PROVOLATILE_IMMUTABLE,	/* volatility */
+							 PROPARALLEL_SAFE,	/* parallel safety */
+							 argtypes,	/* parameterTypes */
+							 PointerGetDatum(NULL),	/* allParameterTypes */
+							 PointerGetDatum(NULL),	/* parameterModes */
+							 PointerGetDatum(NULL),	/* parameterNames */
+							 NIL,	/* parameterDefaults */
+							 PointerGetDatum(NULL),	/* trftypes */
+							 PointerGetDatum(NULL),	/* proconfig */
+							 InvalidOid,	/* prosupport */
+							 1.0,	/* procost */
+							 0.0);	/* prorows */
+	/* ditto */
+	recordDependencyOn(&myself, &referenced, DEPENDENCY_INTERNAL);
+	pfree(argtypes);
+	*castFuncOid = myself.objectId;
+
+	/* n-arg constructor - vararg */
+	argtypes = buildoidvector(&rangeArrayOid, 1);
+	allParamTypes = ObjectIdGetDatum(rangeArrayOid);
+	allParameterTypes = construct_array(&allParamTypes,
+										1, OIDOID,
+										sizeof(Oid), true, 'i');
+	paramModes = CharGetDatum(FUNC_PARAM_VARIADIC);
+	parameterModes = construct_array(&paramModes, 1, CHAROID,
+									 1, true, 'c');
+	myself = ProcedureCreate(name,	/* name: same as multirange type */
+							 namespace,
+							 false,	/* replace */
+							 false,	/* returns set */
+							 multirangeOid,	/* return type */
+							 BOOTSTRAP_SUPERUSERID,	/* proowner */
+							 INTERNALlanguageId,	/* language */
+							 F_FMGR_INTERNAL_VALIDATOR,
+							 "multirange_constructor2",	/* prosrc */
+							 NULL,	/* probin */
+							 PROKIND_FUNCTION,
+							 false,	/* security_definer */
+							 false,	/* leakproof */
+							 false, /* isStrict */
+							 PROVOLATILE_IMMUTABLE,	/* volatility */
+							 PROPARALLEL_SAFE,	/* parallel safety */
+							 argtypes,	/* parameterTypes */
+							 PointerGetDatum(allParameterTypes),	/* allParameterTypes */
+							 PointerGetDatum(parameterModes),	/* parameterModes */
+							 PointerGetDatum(NULL),	/* parameterNames */
+							 NIL,	/* parameterDefaults */
+							 PointerGetDatum(NULL),	/* trftypes */
+							 PointerGetDatum(NULL),	/* proconfig */
+							 InvalidOid,	/* prosupport */
+							 1.0,	/* procost */
+							 0.0);	/* prorows */
+	/* ditto */
+	recordDependencyOn(&myself, &referenced, DEPENDENCY_INTERNAL);
+	pfree(argtypes);
+	pfree(allParameterTypes);
+	pfree(parameterModes);
+}
 
 /*
  * Find suitable I/O functions for a type.
@@ -2045,6 +2287,72 @@ AssignTypeArrayOid(void)
 	return type_array_oid;
 }
 
+/*
+ *	AssignTypeMultirangeOid
+ *
+ *	Pre-assign the range type's multirange OID for use in pg_type.oid
+ */
+Oid
+AssignTypeMultirangeOid(void)
+{
+	Oid			type_multirange_oid;
+
+	/* Use binary-upgrade override for pg_type.oid? */
+	if (IsBinaryUpgrade)
+	{
+		if (!OidIsValid(binary_upgrade_next_mrng_pg_type_oid))
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+					 errmsg("pg_type multirange OID value not set when in binary upgrade mode")));
+
+		type_multirange_oid = binary_upgrade_next_mrng_pg_type_oid;
+		binary_upgrade_next_mrng_pg_type_oid = InvalidOid;
+	}
+	else
+	{
+		Relation	pg_type = table_open(TypeRelationId, AccessShareLock);
+
+		type_multirange_oid = GetNewOidWithIndex(pg_type, TypeOidIndexId,
+												 Anum_pg_type_oid);
+		table_close(pg_type, AccessShareLock);
+	}
+
+	return type_multirange_oid;
+}
+
+/*
+ *	AssignTypeMultirangeArrayOid
+ *
+ *	Pre-assign the range type's multirange array OID for use in pg_type.typarray
+ */
+Oid
+AssignTypeMultirangeArrayOid(void)
+{
+	Oid			type_multirange_array_oid;
+
+	/* Use binary-upgrade override for pg_type.oid? */
+	if (IsBinaryUpgrade)
+	{
+		if (!OidIsValid(binary_upgrade_next_mrng_array_pg_type_oid))
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+					 errmsg("pg_type multirange array OID value not set when in binary upgrade mode")));
+
+		type_multirange_array_oid = binary_upgrade_next_mrng_array_pg_type_oid;
+		binary_upgrade_next_mrng_array_pg_type_oid = InvalidOid;
+	}
+	else
+	{
+		Relation	pg_type = table_open(TypeRelationId, AccessShareLock);
+
+		type_multirange_array_oid = GetNewOidWithIndex(pg_type, TypeOidIndexId,
+													   Anum_pg_type_oid);
+		table_close(pg_type, AccessShareLock);
+	}
+
+	return type_multirange_array_oid;
+}
+
 
 /*-------------------------------------------------------------------
  * DefineCompositeType
diff --git a/src/backend/parser/parse_coerce.c b/src/backend/parser/parse_coerce.c
index 645e4aa4ce..91f80a0656 100644
--- a/src/backend/parser/parse_coerce.c
+++ b/src/backend/parser/parse_coerce.c
@@ -188,6 +188,7 @@ coerce_type(ParseState *pstate, Node *node,
 	if (targetTypeId == ANYARRAYOID ||
 		targetTypeId == ANYENUMOID ||
 		targetTypeId == ANYRANGEOID ||
+		targetTypeId == ANYMULTIRANGEOID ||
 		targetTypeId == ANYCOMPATIBLEARRAYOID ||
 		targetTypeId == ANYCOMPATIBLERANGEOID)
 	{
@@ -195,12 +196,12 @@ coerce_type(ParseState *pstate, Node *node,
 		 * 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 the pseudotype's input
-		 * function, 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
+		 * the pseudotype's input function, 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
@@ -1531,8 +1532,8 @@ coerce_to_common_type(ParseState *pstate, Node *node,
  * 1) All arguments declared ANYELEMENT must have the same datatype.
  * 2) All arguments declared ANYARRAY must have the same datatype,
  *	  which must be a varlena array type.
- * 3) All arguments declared ANYRANGE must have the same datatype,
- *	  which must be a range type.
+ * 3) All arguments declared ANYRANGE or ANYMULTIRANGE must be a range or
+ *	  multirange type, all derived from the same base datatype.
  * 4) If there are arguments of more than one of these polymorphic types,
  *	  the array element type and/or range subtype must be the same as each
  *	  other and the same as the ANYELEMENT type.
@@ -1582,8 +1583,10 @@ check_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			anycompatible_range_typeid = InvalidOid;
 	Oid			anycompatible_range_typelem = InvalidOid;
+	Oid			range_typelem = InvalidOid;
 	bool		have_anynonarray = false;
 	bool		have_anyenum = false;
 	bool		have_anycompatible_nonarray = false;
@@ -1632,6 +1635,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;
+		}
 		else if (decl_type == ANYCOMPATIBLEOID ||
 				 decl_type == ANYCOMPATIBLENONARRAYOID)
 		{
@@ -1722,8 +1734,6 @@ check_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))
 	{
-		Oid			range_typelem;
-
 		range_typelem = get_range_subtype(range_typeid);
 		if (!OidIsValid(range_typelem))
 			return false;		/* should be a range, but isn't */
@@ -1742,6 +1752,45 @@ 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))
+	{
+		Oid			multirange_typelem;
+
+		multirange_typelem = get_multirange_range(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 */
+		}
+		else if (multirange_typelem != range_typeid)
+		{
+			/* otherwise, they better match */
+			return false;
+		}
+
+		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 */
@@ -1820,16 +1869,23 @@ check_generic_type_consistency(const Oid *actual_arg_types,
  * 2) If return type is ANYARRAY, and any argument is ANYARRAY, use the
  *	  argument's actual type as the function's return type.
  * 3) Similarly, if return type is ANYRANGE, and any argument is ANYRANGE,
- *	  use the argument's actual type as the function's return type.
- * 4) Otherwise, if return type is ANYELEMENT or ANYARRAY, there should be
- *	  at least one ANYELEMENT, ANYARRAY, or ANYRANGE input; deduce the
- *	  return type from those inputs, or throw error if we can't.
- * 5) Otherwise, if return type is ANYRANGE, throw error.  (There should
- *	  be at least one ANYRANGE input, since CREATE FUNCTION enforces that.)
- * 6) ANYENUM is treated the same as ANYELEMENT except that if it is used
+ *	  use the argument's actual type as the function's return type. Or
+ *	  if any argument is ANYMULTIRANGE, use its range type as the function's
+ *	  return type.
+ * 4) Otherwise, if return type is ANYMULTIRANGE, and any argument is
+ *	  ANYMULTIRANGE, use the argument's actual type as the function's return
+ *	  type. Or if any argument is ANYRANGE, use its multirange type as the
+ *	  function's return type.
+ * 5) Otherwise, if return type is ANYELEMENT or ANYARRAY, there should be
+ *	  at least one ANYELEMENT, ANYARRAY, ANYRANGE, or ANYMULTIRANGE input;
+ *	  deduce the return type from those inputs, or throw error if we can't.
+ * 6) Otherwise, if return type is ANYRANGE, throw error.  (There should
+ *	  be at least one ANYRANGE or ANYMULTIRANGE input, since CREATE FUNCTION
+ *	  enforces that.)
+ * 7) ANYENUM is treated the same as ANYELEMENT except that if it is used
  *	  (alone or in combination with plain ANYELEMENT), we add the extra
  *	  condition that the ANYELEMENT type must be an enum.
- * 7) ANYNONARRAY is treated the same as ANYELEMENT except that if it is used,
+ * 8) ANYNONARRAY is treated the same as ANYELEMENT except that if it is used,
  *	  we add the extra condition that the ANYELEMENT type must not be an array.
  *	  (This is a no-op if used in combination with ANYARRAY or ANYENUM, but
  *	  is an extra restriction if not.)
@@ -1882,10 +1938,13 @@ 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			anycompatible_typeid = InvalidOid;
 	Oid			anycompatible_array_typeid = InvalidOid;
 	Oid			anycompatible_range_typeid = InvalidOid;
 	Oid			anycompatible_range_typelem = InvalidOid;
+	Oid			range_typelem;
+	Oid			multirange_typelem;
 	bool		have_anynonarray = (rettype == ANYNONARRAYOID);
 	bool		have_anyenum = (rettype == ANYENUMOID);
 	bool		have_anycompatible_nonarray = (rettype == ANYCOMPATIBLENONARRAYOID);
@@ -1970,6 +2029,26 @@ enforce_generic_type_consistency(const Oid *actual_arg_types,
 								   format_type_be(actual_type))));
 			range_typeid = actual_type;
 		}
+		else if (decl_type == ANYMULTIRANGEOID)
+		{
+			n_poly_args++;
+			if (actual_type == UNKNOWNOID)
+			{
+				have_poly_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;
+		}
 		else if (decl_type == ANYCOMPATIBLEOID ||
 				 decl_type == ANYCOMPATIBLENONARRAYOID)
 		{
@@ -2109,8 +2188,6 @@ 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))
 		{
-			Oid			range_typelem;
-
 			range_typelem = get_range_subtype(range_typeid);
 			if (!OidIsValid(range_typelem))
 				ereport(ERROR,
@@ -2139,6 +2216,61 @@ enforce_generic_type_consistency(const Oid *actual_arg_types,
 								   format_type_be(elem_typeid))));
 			}
 		}
+		else
+			range_typelem = InvalidOid;
+
+		/* Get the element type based on the multirange type, if we have one */
+		if (OidIsValid(multirange_typeid))
+		{
+			multirange_typelem = get_multirange_range(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))));
+			}
+
+			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 */
+				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))));
+			}
+		}
+		else
+			multirange_typelem = InvalidOid;
 
 		if (!OidIsValid(elem_typeid))
 		{
@@ -2147,6 +2279,7 @@ enforce_generic_type_consistency(const Oid *actual_arg_types,
 				elem_typeid = ANYELEMENTOID;
 				array_typeid = ANYARRAYOID;
 				range_typeid = ANYRANGEOID;
+				multirange_typeid = ANYMULTIRANGEOID;
 			}
 			else
 			{
@@ -2327,6 +2460,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;
+			}
 		}
 	}
 
@@ -2363,6 +2507,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 ANYCOMPATIBLE use the appropriate type */
 	if (rettype == ANYCOMPATIBLEOID ||
 		rettype == ANYCOMPATIBLENONARRAYOID)
@@ -2414,20 +2574,36 @@ check_valid_polymorphic_signature(Oid ret_type,
 								  const Oid *declared_arg_types,
 								  int nargs)
 {
-	if (ret_type == ANYRANGEOID || ret_type == ANYCOMPATIBLERANGEOID)
+	if (ret_type == ANYRANGEOID || ret_type == ANYMULTIRANGEOID)
 	{
 		/*
-		 * ANYRANGE requires an ANYRANGE input, else we can't tell which of
-		 * several range types with the same element type to use.  Likewise
-		 * for ANYCOMPATIBLERANGE.
+		 * ANYRANGE and ANYMULTIRANGE require an ANYRANGE or ANYMULTIRANGE input,
+		 * else we can't tell which of several range types with the same element
+		 * type to use.
+		 */
+		for (int i = 0; i < nargs; i++)
+		{
+			if (declared_arg_types[i] == ANYRANGEOID ||
+				declared_arg_types[i] == ANYMULTIRANGEOID)
+				return NULL;	/* OK */
+		}
+		return psprintf(_("A result of type %s requires at least one input of type anyrange or anymultirange."),
+						format_type_be(ret_type));
+	}
+	else if (ret_type == ANYCOMPATIBLERANGEOID)
+	{
+		/*
+		 * ANYCOMPATIBLERANGE requires an ANYCOMPATIBLERANGE input,
+		 * else we can't tell which of several range types with the same element
+		 * type to use.
 		 */
 		for (int i = 0; i < nargs; i++)
 		{
 			if (declared_arg_types[i] == ret_type)
 				return NULL;	/* OK */
 		}
-		return psprintf(_("A result of type %s requires at least one input of type %s."),
-						format_type_be(ret_type), format_type_be(ret_type));
+		return psprintf(_("A result of type %s requires at least one input of type anycompatiblerange."),
+						format_type_be(ret_type));
 	}
 	else if (IsPolymorphicTypeFamily1(ret_type))
 	{
@@ -2438,7 +2614,7 @@ check_valid_polymorphic_signature(Oid ret_type,
 				return NULL;	/* OK */
 		}
 		/* Keep this list in sync with IsPolymorphicTypeFamily1! */
-		return psprintf(_("A result of type %s requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange."),
+		return psprintf(_("A result of type %s requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange."),
 						format_type_be(ret_type));
 	}
 	else if (IsPolymorphicTypeFamily2(ret_type))
@@ -2590,6 +2766,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..da78227b01
--- /dev/null
+++ b/src/backend/utils/adt/multirangetypes.c
@@ -0,0 +1,2181 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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/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 */
+	FmgrInfo	typioproc;		/* range type's I/O proc */
+	Oid			typioparam;		/* range type's I/O parameter */
+} 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 mltrngtypid,
+												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 with 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;
+	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 = NULL;
+	int32		range_str_len;
+	char	   *range_str_copy;
+
+	cache = get_multirange_io_data(fcinfo, mltrngtypoid, IOFunc_input);
+	rangetyp = cache->typcache->rngtype;
+
+	/* 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 + 1;
+					range_str_copy = pnstrdup(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->typioproc,
+																 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->typioproc, RangeTypePGetDatum(range));
+		appendStringInfoString(&buf, rangeStr);
+		ptr += MAXALIGN(VARSIZE(range));
+		range_count++;
+	}
+
+	appendStringInfoChar(&buf, '}');
+
+	PG_RETURN_CSTRING(buf.data);
+}
+
+/*
+ * Binary representation: First a int32-sized count of ranges, followed by
+ * ranges in their native binary representation.
+ */
+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;
+	uint32		range_count;
+	RangeType **ranges;
+	MultirangeType *ret;
+	StringInfoData tmpbuf;
+
+	cache = get_multirange_io_data(fcinfo, mltrngtypoid, IOFunc_receive);
+
+	range_count = pq_getmsgint(buf, 4);
+	ranges = palloc(range_count * sizeof(RangeType *));
+
+	initStringInfo(&tmpbuf);
+	for (int i = 0; i < range_count; i++)
+	{
+		uint32		range_len = pq_getmsgint(buf, 4);
+		const char *range_data = pq_getmsgbytes(buf, range_len);
+
+		resetStringInfo(&tmpbuf);
+		appendBinaryStringInfo(&tmpbuf, range_data, range_len);
+
+		ranges[i] = DatumGetRangeTypeP(ReceiveFunctionCall(&cache->typioproc,
+														   &tmpbuf,
+														   cache->typioparam,
+														   typmod));
+	}
+	pfree(tmpbuf.data);
+
+	pq_getmsgend(buf);
+
+	ret = make_multirange(mltrngtypoid, cache->typcache->rngtype,
+						  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;
+	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 (int i = 0; i < range_count; i++)
+	{
+		Datum		range;
+
+		range = RangeTypePGetDatum(ranges[i]);
+		range = PointerGetDatum(SendFunctionCall(&cache->typioproc, range));
+
+		pq_sendint32(buf, VARSIZE(range) - VARHDRSZ);
+		pq_sendbytes(buf, VARDATA(range), VARSIZE(range) - VARHDRSZ);
+	}
+
+	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)
+	{
+		Oid			typiofunc;
+		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,
+						 &typiofunc);
+
+		if (!OidIsValid(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(typiofunc, &cache->typioproc,
+					  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;
+	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++)
+		bytelen += MAXALIGN(VARSIZE(ranges[i]));
+
+	/* 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++)
+	{
+		memcpy(ptr, ranges[i], VARSIZE(ranges[i]));
+		ptr += MAXALIGN(VARSIZE(ranges[i]));
+	}
+
+	return multirange;
+}
+
+/*
+ * Converts a list of arbitrary ranges 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_constructor2(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,
+				(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+				 errmsg("multirange values cannot contain NULL members")));
+
+	rangeArray = PG_GETARG_ARRAYTYPE_P(0);
+
+	dims = ARR_NDIM(rangeArray);
+	if (dims > 1)
+		ereport(ERROR,
+				(errcode(ERRCODE_CARDINALITY_VIOLATION),
+				 errmsg("multiranges cannot be constructed from multi-dimensional arrays")));
+
+	rngtypid = ARR_ELEMTYPE(rangeArray);
+	if (rngtypid != rangetyp->type_id)
+		ereport(ERROR,
+				(errcode(ERRCODE_DATATYPE_MISMATCH),
+				 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,
+						(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+						 errmsg("multirange values cannot contain NULL members")));
+
+			/* make_multirange will do its own copy */
+			ranges[i] = DatumGetRangeTypeP(elements[i]);
+		}
+	}
+
+	PG_RETURN_MULTIRANGE_P(make_multirange(mltrngtypid, rangetyp, range_count, ranges));
+}
+
+/*
+ * Construct multirange value from a single range.
+ * It'd be nice if we could just use multirange_constructor2
+ * for this case, but we need a non-variadic single-arg function
+ * to let us define a CAST from a range to its multirange.
+ */
+Datum
+multirange_constructor1(PG_FUNCTION_ARGS)
+{
+	Oid			mltrngtypid = get_fn_expr_rettype(fcinfo->flinfo);
+	Oid			rngtypid;
+	TypeCacheEntry *typcache;
+	TypeCacheEntry *rangetyp;
+	RangeType  *range;
+
+	typcache = multirange_get_typcache(fcinfo, mltrngtypid);
+	rangetyp = typcache->rngtype;
+
+	/*
+	 * These checks should be guaranteed by our signature, but let's do them
+	 * just in case.
+	 */
+
+	if (PG_ARGISNULL(0))
+				ereport(ERROR,
+						(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+						 errmsg("multirange values cannot contain NULL members")));
+
+	range = PG_GETARG_RANGE_P(0);
+
+	/* Make sure the range type matches. */
+	rngtypid = RangeTypeGetOid(range);
+	if (rngtypid != rangetyp->type_id)
+		ereport(ERROR,
+				(errcode(ERRCODE_DATATYPE_MISMATCH),
+				 errmsg("type %u does not match constructor type", rngtypid)));
+
+	PG_RETURN_MULTIRANGE_P(make_multirange(mltrngtypid, rangetyp, 1, &range));
+}
+
+/*
+ * 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
+		elog(ERROR,		/* can't happen */
+			 "niladic multirange constructor must not receive arguments");
+}
+
+
+/* multirange, multirange -> multirange type functions */
+
+/* multirange union */
+Datum
+multirange_union(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
+	MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count1;
+	int32		range_count2;
+	int32		range_count3;
+	RangeType **ranges1;
+	RangeType **ranges2;
+	RangeType **ranges3;
+
+	if (MultirangeIsEmpty(mr1))
+		PG_RETURN_MULTIRANGE_P(mr2);
+	if (MultirangeIsEmpty(mr2))
+		PG_RETURN_MULTIRANGE_P(mr1);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr1));
+
+	multirange_deserialize(mr1, &range_count1, &ranges1);
+	multirange_deserialize(mr2, &range_count2, &ranges2);
+
+	range_count3 = range_count1 + range_count2;
+	ranges3 = palloc0(range_count3 * sizeof(RangeType *));
+	memcpy(ranges3, ranges1, range_count1 * sizeof(RangeType *));
+	memcpy(ranges3 + range_count1, ranges2, range_count2 * sizeof(RangeType *));
+	PG_RETURN_MULTIRANGE_P(make_multirange(typcache->type_id, typcache->rngtype,
+										   range_count3, ranges3));
+}
+
+/* multirange minus */
+Datum
+multirange_minus(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
+	MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
+	Oid			mltrngtypoid = MultirangeTypeGetOid(mr1);
+	TypeCacheEntry *typcache;
+	TypeCacheEntry *rangetyp;
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+
+	typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
+	rangetyp = typcache->rngtype;
+
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		PG_RETURN_MULTIRANGE_P(mr1);
+
+	multirange_deserialize(mr1, &range_count1, &ranges1);
+	multirange_deserialize(mr2, &range_count2, &ranges2);
+
+	PG_RETURN_MULTIRANGE_P(multirange_minus_internal(mltrngtypoid,
+													 rangetyp,
+													 range_count1,
+													 ranges1,
+													 range_count2,
+													 ranges2));
+}
+
+MultirangeType *
+multirange_minus_internal(Oid mltrngtypoid, TypeCacheEntry *rangetyp,
+						  int32 range_count1, RangeType **ranges1,
+						  int32 range_count2, RangeType **ranges2)
+{
+	RangeType  *r1;
+	RangeType  *r2;
+	RangeType **ranges3;
+	int32		range_count3;
+	int32		i1;
+	int32		i2;
+
+	/*
+	 * Worst case: every range in ranges1 makes a different cut to some range
+	 * in ranges2.
+	 */
+	ranges3 = palloc0((range_count1 + range_count2) * sizeof(RangeType *));
+	range_count3 = 0;
+
+	/*
+	 * For each range in mr1, keep subtracting until it's gone or the ranges
+	 * in mr2 have passed it. After a subtraction we assign what's left back
+	 * to r1. The parallel progress through mr1 and mr2 is similar to
+	 * multirange_overlaps_multirange_internal.
+	 */
+	r2 = ranges2[0];
+	for (i1 = 0, i2 = 0; i1 < range_count1; i1++)
+	{
+		r1 = ranges1[i1];
+
+		/* Discard r2s while r2 << r1 */
+		while (r2 != NULL && range_before_internal(rangetyp, r2, r1))
+		{
+			r2 = ++i2 >= range_count2 ? NULL : ranges2[i2];
+		}
+
+		while (r2 != NULL)
+		{
+			if (range_split_internal(rangetyp, r1, r2, &ranges3[range_count3], &r1))
+			{
+				/*
+				 * If r2 takes a bite out of the middle of r1, we need two
+				 * outputs
+				 */
+				range_count3++;
+				r2 = ++i2 >= range_count2 ? NULL : ranges2[i2];
+
+			}
+			else if (range_overlaps_internal(rangetyp, r1, r2))
+			{
+				/*
+				 * If r2 overlaps r1, replace r1 with r1 - r2.
+				 */
+				r1 = range_minus_internal(rangetyp, r1, r2);
+
+				/*
+				 * If r2 goes past r1, then we need to stay with it, in case
+				 * it hits future r1s. Otherwise we need to keep r1, in case
+				 * future r2s hit it. Since we already subtracted, there's no
+				 * point in using the overright/overleft calls.
+				 */
+				if (RangeIsEmpty(r1) || range_before_internal(rangetyp, r1, r2))
+					break;
+				else
+					r2 = ++i2 >= range_count2 ? NULL : ranges2[i2];
+
+			}
+			else
+			{
+				/*
+				 * This and all future r2s are past r1, so keep them. Also
+				 * assign whatever is left of r1 to the result.
+				 */
+				break;
+			}
+		}
+
+		/*
+		 * Nothing else can remove anything from r1, so keep it. Even if r1 is
+		 * empty here, make_multirange will remove it.
+		 */
+		ranges3[range_count3++] = r1;
+	}
+
+	return make_multirange(mltrngtypoid, rangetyp, range_count3, ranges3);
+}
+
+/* multirange intersection */
+Datum
+multirange_intersect(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
+	MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
+	Oid			mltrngtypoid = MultirangeTypeGetOid(mr1);
+	TypeCacheEntry *typcache;
+	TypeCacheEntry *rangetyp;
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+
+	typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
+	rangetyp = typcache->rngtype;
+
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		PG_RETURN_MULTIRANGE_P(make_empty_multirange(mltrngtypoid, rangetyp));
+
+	multirange_deserialize(mr1, &range_count1, &ranges1);
+	multirange_deserialize(mr2, &range_count2, &ranges2);
+
+	PG_RETURN_MULTIRANGE_P(multirange_intersect_internal(mltrngtypoid,
+														 rangetyp,
+														 range_count1,
+														 ranges1,
+														 range_count2,
+														 ranges2));
+}
+
+MultirangeType *
+multirange_intersect_internal(Oid mltrngtypoid, TypeCacheEntry *rangetyp,
+							  int32 range_count1, RangeType **ranges1,
+							  int32 range_count2, RangeType **ranges2)
+{
+	RangeType  *r1;
+	RangeType  *r2;
+	RangeType **ranges3;
+	int32		range_count3;
+	int32		i1;
+	int32		i2;
+
+	if (range_count1 == 0 || range_count2 == 0)
+		return make_multirange(mltrngtypoid, rangetyp, 0, NULL);
+
+	/*-----------------------------------------------
+	 * Worst case is a stitching pattern like this:
+	 *
+	 * mr1: --- --- --- ---
+	 * mr2:   --- --- ---
+	 * mr3:   - - - - - -
+	 *
+	 * That seems to be range_count1 + range_count2 - 1,
+	 * but one extra won't hurt.
+	 *-----------------------------------------------
+	 */
+	ranges3 = palloc0((range_count1 + range_count2) * sizeof(RangeType *));
+	range_count3 = 0;
+
+	/*
+	 * For each range in mr1, keep intersecting until the ranges in mr2 have
+	 * passed it. The parallel progress through mr1 and mr2 is similar to
+	 * multirange_minus_multirange_internal, but we don't have to assign back
+	 * to r1.
+	 */
+	r2 = ranges2[0];
+	for (i1 = 0, i2 = 0; i1 < range_count1; i1++)
+	{
+		r1 = ranges1[i1];
+
+		/* Discard r2s while r2 << r1 */
+		while (r2 != NULL && range_before_internal(rangetyp, r2, r1))
+		{
+			r2 = ++i2 >= range_count2 ? NULL : ranges2[i2];
+		}
+
+		while (r2 != NULL)
+		{
+			if (range_overlaps_internal(rangetyp, r1, r2))
+			{
+				/* Keep the overlapping part */
+				ranges3[range_count3++] = range_intersect_internal(rangetyp, r1, r2);
+
+				/* If we "used up" all of r2, go to the next one... */
+				if (range_overleft_internal(rangetyp, r2, r1))
+					r2 = ++i2 >= range_count2 ? NULL : ranges2[i2];
+
+				/* ...otherwise go to the next r1 */
+				else
+					break;
+			}
+			else
+				/* We're past r1, so move to the next one */
+				break;
+		}
+
+		/* If we're out of r2s, there can be no more intersections */
+		if (r2 == NULL)
+			break;
+	}
+
+	return make_multirange(mltrngtypoid, rangetyp, range_count3, ranges3);
+}
+
+/*
+ * range_agg_transfn: combine adjacent/overlapping ranges.
+ *
+ * All we do here is gather the input ranges into an array
+ * so that the finalfn can sort and combine them.
+ */
+Datum
+range_agg_transfn(PG_FUNCTION_ARGS)
+{
+	MemoryContext aggContext;
+	Oid			rngtypoid;
+	ArrayBuildState *state;
+
+	if (!AggCheckCallContext(fcinfo, &aggContext))
+		elog(ERROR, "range_agg_transfn called in non-aggregate context");
+
+	rngtypoid = get_fn_expr_argtype(fcinfo->flinfo, 1);
+	if (!type_is_range(rngtypoid))
+		ereport(ERROR,
+				(errcode(ERRCODE_DATATYPE_MISMATCH),
+				 errmsg("range_agg must be called with a range")));
+
+	if (PG_ARGISNULL(0))
+		state = initArrayResult(rngtypoid, aggContext, false);
+	else
+		state = (ArrayBuildState *) PG_GETARG_POINTER(0);
+
+	/* skip NULLs */
+	if (!PG_ARGISNULL(1))
+		accumArrayResult(state, PG_GETARG_DATUM(1), false, rngtypoid, aggContext);
+
+	PG_RETURN_POINTER(state);
+}
+
+/*
+ * range_agg_finalfn: use our internal array to merge touching ranges.
+ */
+Datum
+range_agg_finalfn(PG_FUNCTION_ARGS)
+{
+	MemoryContext aggContext;
+	Oid			mltrngtypoid;
+	TypeCacheEntry *typcache;
+	ArrayBuildState *state;
+	int32		range_count;
+	RangeType **ranges;
+	int			i;
+
+	if (!AggCheckCallContext(fcinfo, &aggContext))
+		elog(ERROR, "range_agg_finalfn called in non-aggregate context");
+
+	state = PG_ARGISNULL(0) ? NULL : (ArrayBuildState *) PG_GETARG_POINTER(0);
+	if (state == NULL)
+		/* This shouldn't be possible, but just in case.... */
+		PG_RETURN_NULL();
+
+	/* Also return NULL if we had zero inputs, like other aggregates */
+	range_count = state->nelems;
+	if (range_count == 0)
+		PG_RETURN_NULL();
+
+	mltrngtypoid = get_fn_expr_rettype(fcinfo->flinfo);
+	typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
+
+	ranges = palloc0(range_count * sizeof(RangeType *));
+	for (i = 0; i < range_count; i++)
+		ranges[i] = DatumGetRangeTypeP(state->dvalues[i]);
+
+	PG_RETURN_MULTIRANGE_P(make_multirange(mltrngtypoid, typcache->rngtype, range_count, ranges));
+}
+
+Datum
+multirange_intersect_agg_transfn(PG_FUNCTION_ARGS)
+{
+	MemoryContext aggContext;
+	Oid			mltrngtypoid;
+	TypeCacheEntry *typcache;
+	MultirangeType *result;
+	MultirangeType *current;
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+
+	if (!AggCheckCallContext(fcinfo, &aggContext))
+		elog(ERROR, "multirange_intersect_agg_transfn called in non-aggregate context");
+
+	mltrngtypoid = get_fn_expr_argtype(fcinfo->flinfo, 1);
+	if (!type_is_multirange(mltrngtypoid))
+		ereport(ERROR,
+				(errcode(ERRCODE_DATATYPE_MISMATCH),
+				 errmsg("range_intersect_agg must be called with a multirange")));
+
+	typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
+
+	/* strictness ensures these are non-null */
+	result = PG_GETARG_MULTIRANGE_P(0);
+	current = PG_GETARG_MULTIRANGE_P(1);
+
+	multirange_deserialize(result, &range_count1, &ranges1);
+	multirange_deserialize(current, &range_count2, &ranges2);
+
+	result = multirange_intersect_internal(mltrngtypoid,
+										   typcache->rngtype,
+										   range_count1,
+										   ranges1,
+										   range_count2,
+										   ranges2);
+	PG_RETURN_RANGE_P(result);
+}
+
+
+/* multirange -> element type functions */
+
+/* extract lower bound value */
+Datum
+multirange_lower(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+	bool		isnull;
+	Datum		result;
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_NULL();
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	result = range_lower_internal(typcache->rngtype, ranges[0], &isnull);
+
+	if (isnull)
+		PG_RETURN_NULL();
+	else
+		PG_RETURN_DATUM(result);
+}
+
+/* extract upper bound value */
+Datum
+multirange_upper(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+	bool		isnull;
+	Datum		result;
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_NULL();
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	result = range_upper_internal(typcache->rngtype, ranges[range_count - 1], &isnull);
+
+	if (isnull)
+		PG_RETURN_NULL();
+	else
+		PG_RETURN_DATUM(result);
+}
+
+
+/* multirange -> bool functions */
+
+/* is multirange empty? */
+Datum
+multirange_empty(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+
+	PG_RETURN_BOOL(mr->rangeCount == 0);
+}
+
+/* is lower bound inclusive? */
+Datum
+multirange_lower_inc(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	int32		range_count;
+	RangeType **ranges;
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_has_flag(ranges[0], RANGE_LB_INC));
+}
+
+/* is upper bound inclusive? */
+Datum
+multirange_upper_inc(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	int32		range_count;
+	RangeType **ranges;
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_has_flag(ranges[range_count - 1], RANGE_UB_INC));
+}
+
+/* is lower bound infinite? */
+Datum
+multirange_lower_inf(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	int32		range_count;
+	RangeType **ranges;
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_has_flag(ranges[0], RANGE_LB_INF));
+}
+
+/* is upper bound infinite? */
+Datum
+multirange_upper_inf(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	int32		range_count;
+	RangeType **ranges;
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_has_flag(ranges[range_count - 1], RANGE_UB_INF));
+}
+
+
+
+/* multirange, element -> bool functions */
+
+/* contains? */
+Datum
+multirange_contains_elem(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	Datum		val = PG_GETARG_DATUM(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(multirange_contains_elem_internal(typcache, mr, val));
+}
+
+/* contained by? */
+Datum
+elem_contained_by_multirange(PG_FUNCTION_ARGS)
+{
+	Datum		val = PG_GETARG_DATUM(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(multirange_contains_elem_internal(typcache, mr, val));
+}
+
+/*
+ * Test whether multirange mr contains a specific element value.
+ */
+bool
+multirange_contains_elem_internal(TypeCacheEntry *typcache, MultirangeType *mr, Datum val)
+{
+	TypeCacheEntry *rangetyp;
+	int32		range_count;
+	RangeType **ranges;
+	RangeType  *r;
+	int			i;
+
+	rangetyp = typcache->rngtype;
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	for (i = 0; i < range_count; i++)
+	{
+		r = ranges[i];
+		if (range_contains_elem_internal(rangetyp, r, val))
+			return true;
+	}
+
+	return false;
+}
+
+/* multirange, range -> bool functions */
+
+/* contains? */
+Datum
+multirange_contains_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(multirange_contains_range_internal(typcache, mr, r));
+}
+
+/* contained by? */
+Datum
+range_contained_by_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(multirange_contains_range_internal(typcache, mr, r));
+}
+
+/*
+ * Test whether multirange mr contains a specific range r.
+ */
+bool
+multirange_contains_range_internal(TypeCacheEntry *typcache, MultirangeType *mr, RangeType *r)
+{
+	TypeCacheEntry *rangetyp;
+	int32		range_count;
+	RangeType **ranges;
+	RangeType  *mrr;
+	int			i;
+
+	rangetyp = typcache->rngtype;
+
+	/*
+	 * Every multirange contains an infinite number of empty ranges, even an
+	 * empty one.
+	 */
+	if (RangeIsEmpty(r))
+		return true;
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	for (i = 0; i < range_count; i++)
+	{
+		mrr = ranges[i];
+		if (range_contains_internal(rangetyp, mrr, r))
+			return true;
+	}
+
+	return false;
+}
+
+
+/* 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));
+}
+
+/* overlaps? */
+Datum
+range_overlaps_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(range_overlaps_multirange_internal(typcache, r, mr));
+}
+
+Datum
+multirange_overlaps_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(range_overlaps_multirange_internal(typcache, r, mr));
+}
+
+Datum
+multirange_overlaps_multirange(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_overlaps_multirange_internal(typcache, mr1, mr2));
+}
+
+bool
+range_overlaps_multirange_internal(TypeCacheEntry *typcache, RangeType *r, MultirangeType *mr)
+{
+	TypeCacheEntry *rangetyp;
+	int32		range_count;
+	int32		i;
+	RangeType **ranges;
+	RangeType  *mrr;
+
+	/*
+	 * Empties never overlap, even with empties. (This seems strange since
+	 * they *do* contain each other, but we want to follow how ranges work.)
+	 */
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		return false;
+
+	rangetyp = typcache->rngtype;
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	/* Scan through mrr and once it gets to r they either overlap or not */
+	mrr = ranges[0];
+
+	/* Discard mrrs while mrr << r */
+	i = 0;
+	while (range_before_internal(rangetyp, mrr, r))
+	{
+		if (++i >= range_count)
+			return false;
+		mrr = ranges[i];
+	}
+
+	/* Now either we overlap or we passed r */
+	return range_overlaps_internal(rangetyp, mrr, r);
+}
+
+bool
+multirange_overlaps_multirange_internal(TypeCacheEntry *typcache, MultirangeType *mr1,
+										MultirangeType *mr2)
+{
+	TypeCacheEntry *rangetyp;
+	int32		range_count1;
+	int32		range_count2;
+	int32		i1;
+	int32		i2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+	RangeType  *r1;
+	RangeType  *r2;
+
+	/*
+	 * Empties never overlap, even with empties. (This seems strange since
+	 * they *do* contain each other, but we want to follow how ranges work.)
+	 */
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		return false;
+
+	rangetyp = typcache->rngtype;
+
+	multirange_deserialize(mr1, &range_count1, &ranges1);
+	multirange_deserialize(mr2, &range_count2, &ranges2);
+
+	/*
+	 * Every range in mr1 gets a chance to overlap with the ranges in mr2, but
+	 * we can use their ordering to avoid O(n^2). This is similar to
+	 * range_overlaps_multirange where r1 : r2 :: mrr : r, but there if we
+	 * don't find an overlap with r we're done, and here if we don't find an
+	 * overlap with r2 we try the next r2.
+	 */
+	r1 = ranges1[0];
+	for (i1 = 0, i2 = 0; i2 < range_count2; i2++)
+	{
+		r2 = ranges2[i2];
+
+		/* Discard r1s while r1 << r2 */
+		while (range_before_internal(rangetyp, r1, r2))
+		{
+			if (++i1 >= range_count1)
+				return false;
+			r1 = ranges1[i1];
+		}
+
+		/*
+		 * If r1 && r2, we're done, otherwise we failed to find an overlap for
+		 * r2, so go to the next one.
+		 */
+		if (range_overlaps_internal(rangetyp, r1, r2))
+			return true;
+	}
+
+	/* We looked through all of mr2 without finding an overlap */
+	return false;
+}
+
+/* does not extend to right of? */
+Datum
+range_overleft_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_overleft_internal(typcache->rngtype, r, ranges[range_count - 1]));
+}
+
+Datum
+multirange_overleft_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_overleft_internal(typcache->rngtype,
+										   ranges[range_count - 1], r));
+}
+
+Datum
+multirange_overleft_multirange(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
+	MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr1));
+
+	multirange_deserialize(mr1, &range_count1, &ranges1);
+	multirange_deserialize(mr2, &range_count2, &ranges2);
+
+	PG_RETURN_BOOL(range_overleft_internal(typcache->rngtype,
+										   ranges1[range_count1 - 1],
+										   ranges2[range_count2 - 1]));
+}
+
+/* does not extend to left of? */
+Datum
+range_overright_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_overright_internal(typcache->rngtype, r, ranges[0]));
+}
+
+Datum
+multirange_overright_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_overright_internal(typcache->rngtype, ranges[0], r));
+}
+
+Datum
+multirange_overright_multirange(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
+	MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr1));
+
+	multirange_deserialize(mr1, &range_count1, &ranges1);
+	multirange_deserialize(mr2, &range_count2, &ranges2);
+
+	PG_RETURN_BOOL(range_overright_internal(typcache->rngtype,
+											ranges1[0],
+											ranges2[0]));
+}
+
+/* contains? */
+Datum
+multirange_contains_multirange(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_contains_multirange_internal(typcache, mr1, mr2));
+}
+
+/* contained by? */
+Datum
+multirange_contained_by_multirange(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_contains_multirange_internal(typcache, mr2, mr1));
+}
+
+/*
+ * Test whether multirange mr1 contains every range from another multirange mr2.
+ */
+bool
+multirange_contains_multirange_internal(TypeCacheEntry *typcache,
+										MultirangeType *mr1, MultirangeType *mr2)
+{
+	TypeCacheEntry *rangetyp;
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+	RangeType  *r1;
+	RangeType  *r2;
+	int			i1,
+				i2;
+
+	rangetyp = typcache->rngtype;
+
+	multirange_deserialize(mr1, &range_count1, &ranges1);
+	multirange_deserialize(mr2, &range_count2, &ranges2);
+
+	/*
+	 * We follow the same logic for empties as ranges: - an empty multirange
+	 * contains an empty range/multirange. - an empty multirange can't contain
+	 * any other range/multirange. - an empty multirange is contained by any
+	 * other range/multirange.
+	 */
+
+	if (range_count2 == 0)
+		return true;
+	if (range_count1 == 0)
+		return false;
+
+	/*
+	 * Every range in mr2 must be contained by some range in mr1. To avoid
+	 * O(n^2) we walk through both ranges in tandem.
+	 */
+	r1 = ranges1[0];
+	for (i1 = 0, i2 = 0; i2 < range_count2; i2++)
+	{
+		r2 = ranges2[i2];
+
+		/* Discard r1s while r1 << r2 */
+		while (range_before_internal(rangetyp, r1, r2))
+		{
+			if (++i1 >= range_count1)
+				return false;
+			r1 = ranges1[i1];
+		}
+
+		/*
+		 * If r1 @> r2, go to the next r2, otherwise return false (since every
+		 * r1[n] and r1[n+1] must have a gap). Note this will give weird
+		 * answers if you don't canonicalize, e.g. with a custom
+		 * int2multirange {[1,1], [2,2]} there is a "gap". But that is
+		 * consistent with other range operators, e.g. '[1,1]'::int2range -|-
+		 * '[2,2]'::int2range is false.
+		 */
+		if (!range_contains_internal(rangetyp, r1, r2))
+			return false;
+	}
+
+	/* All ranges in mr2 are satisfied */
+	return true;
+}
+
+/* strictly left of? */
+Datum
+range_before_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(range_before_multirange_internal(typcache, r, mr));
+}
+
+Datum
+multirange_before_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(range_after_multirange_internal(typcache, r, mr));
+}
+
+Datum
+multirange_before_multirange(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_before_multirange_internal(typcache, mr1, mr2));
+}
+
+/* strictly right of? */
+Datum
+range_after_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(range_after_multirange_internal(typcache, r, mr));
+}
+
+Datum
+multirange_after_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(range_before_multirange_internal(typcache, r, mr));
+}
+
+Datum
+multirange_after_multirange(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_before_multirange_internal(typcache, mr2, mr1));
+}
+
+/* strictly left of? (internal version) */
+bool
+range_before_multirange_internal(TypeCacheEntry *typcache, RangeType *r,
+								 MultirangeType *mr)
+{
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		return false;
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	return range_before_internal(typcache->rngtype, r, ranges[0]);
+}
+
+bool
+multirange_before_multirange_internal(TypeCacheEntry *typcache, MultirangeType *mr1,
+									  MultirangeType *mr2)
+{
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		return false;
+
+	multirange_deserialize(mr1, &range_count1, &ranges1);
+	multirange_deserialize(mr2, &range_count2, &ranges2);
+
+	return range_before_internal(typcache->rngtype, ranges1[range_count1 - 1],
+								 ranges2[0]);
+}
+
+/* strictly right of? (internal version) */
+bool
+range_after_multirange_internal(TypeCacheEntry *typcache, RangeType *r,
+								MultirangeType *mr)
+{
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		return false;
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	return range_after_internal(typcache->rngtype, r, ranges[range_count - 1]);
+}
+
+/* adjacent to? */
+Datum
+range_adjacent_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		return false;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_adjacent_internal(typcache->rngtype, r, ranges[0]));
+}
+
+Datum
+multirange_adjacent_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		return false;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_adjacent_internal(typcache->rngtype, ranges[range_count - 1], r));
+}
+
+Datum
+multirange_adjacent_multirange(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
+	MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		return false;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr1));
+
+	multirange_deserialize(mr1, &range_count1, &ranges1);
+	multirange_deserialize(mr2, &range_count2, &ranges2);
+
+	PG_RETURN_BOOL(range_adjacent_internal(typcache->rngtype, ranges1[range_count1 - 1], ranges2[0]));
+}
+
+/* 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);
+}
+
+/* multirange -> range functions */
+
+/* Find the smallest range that includes everything in the multirange */
+Datum
+range_merge_from_multirange(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	Oid			mltrngtypoid = MultirangeTypeGetOid(mr);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_RANGE_P(make_empty_range(typcache->rngtype));
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_RANGE_P(range_union_internal(typcache->rngtype, ranges[0],
+										   ranges[range_count - 1], false));
+}
+
+/* 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/pg_upgrade_support.c b/src/backend/utils/adt/pg_upgrade_support.c
index 18f2ee8226..248ea7f44a 100644
--- a/src/backend/utils/adt/pg_upgrade_support.c
+++ b/src/backend/utils/adt/pg_upgrade_support.c
@@ -51,6 +51,28 @@ binary_upgrade_set_next_array_pg_type_oid(PG_FUNCTION_ARGS)
 	PG_RETURN_VOID();
 }
 
+Datum
+binary_upgrade_set_next_multirange_pg_type_oid(PG_FUNCTION_ARGS)
+{
+	Oid			typoid = PG_GETARG_OID(0);
+
+	CHECK_IS_BINARY_UPGRADE;
+	binary_upgrade_next_mrng_pg_type_oid = typoid;
+
+	PG_RETURN_VOID();
+}
+
+Datum
+binary_upgrade_set_next_multirange_array_pg_type_oid(PG_FUNCTION_ARGS)
+{
+	Oid			typoid = PG_GETARG_OID(0);
+
+	CHECK_IS_BINARY_UPGRADE;
+	binary_upgrade_next_mrng_array_pg_type_oid = typoid;
+
+	PG_RETURN_VOID();
+}
+
 Datum
 binary_upgrade_set_next_toast_pg_type_oid(PG_FUNCTION_ARGS)
 {
diff --git a/src/backend/utils/adt/pseudotypes.c b/src/backend/utils/adt/pseudotypes.c
index 3d6b2f9093..4fcd8ce6de 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"
 
 
 /*
@@ -227,6 +228,30 @@ anycompatiblerange_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
  *
diff --git a/src/backend/utils/adt/rangetypes.c b/src/backend/utils/adt/rangetypes.c
index 01ad8bc240..0af3c67296 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
 {
@@ -431,19 +429,37 @@ range_lower(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
 	TypeCacheEntry *typcache;
+	bool		isnull;
+	Datum		result;
+
+	typcache = range_get_typcache(fcinfo, RangeTypeGetOid(r1));
+
+	result = range_lower_internal(typcache, r1, &isnull);
+
+	if (isnull)
+		PG_RETURN_NULL();
+	else
+		PG_RETURN_DATUM(result);
+}
+
+Datum
+range_lower_internal(TypeCacheEntry *typcache, const RangeType *r1, bool *isnull)
+{
 	RangeBound	lower;
 	RangeBound	upper;
 	bool		empty;
 
-	typcache = range_get_typcache(fcinfo, RangeTypeGetOid(r1));
-
 	range_deserialize(typcache, r1, &lower, &upper, &empty);
 
 	/* Return NULL if there's no finite lower bound */
 	if (empty || lower.infinite)
-		PG_RETURN_NULL();
+	{
+		*isnull = true;
+		return 0;
+	}
 
-	PG_RETURN_DATUM(lower.val);
+	*isnull = false;
+	return lower.val;
 }
 
 /* extract upper bound value */
@@ -452,19 +468,37 @@ range_upper(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
 	TypeCacheEntry *typcache;
+	bool		isnull;
+	Datum		result;
+
+	typcache = range_get_typcache(fcinfo, RangeTypeGetOid(r1));
+
+	result = range_upper_internal(typcache, r1, &isnull);
+
+	if (isnull)
+		PG_RETURN_NULL();
+	else
+		PG_RETURN_DATUM(result);
+}
+
+Datum
+range_upper_internal(TypeCacheEntry *typcache, const RangeType *r1, bool *isnull)
+{
 	RangeBound	lower;
 	RangeBound	upper;
 	bool		empty;
 
-	typcache = range_get_typcache(fcinfo, RangeTypeGetOid(r1));
-
 	range_deserialize(typcache, r1, &lower, &upper, &empty);
 
 	/* Return NULL if there's no finite upper bound */
 	if (empty || upper.infinite)
-		PG_RETURN_NULL();
+	{
+		*isnull = true;
+		return 0;
+	}
 
-	PG_RETURN_DATUM(upper.val);
+	*isnull = false;
+	return upper.val;
 }
 
 
@@ -475,9 +509,8 @@ Datum
 range_empty(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
-	char		flags = range_get_flags(r1);
 
-	PG_RETURN_BOOL(flags & RANGE_EMPTY);
+	PG_RETURN_BOOL(range_has_flag(r1, RANGE_EMPTY));
 }
 
 /* is lower bound inclusive? */
@@ -485,9 +518,8 @@ Datum
 range_lower_inc(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
-	char		flags = range_get_flags(r1);
 
-	PG_RETURN_BOOL(flags & RANGE_LB_INC);
+	PG_RETURN_BOOL(range_has_flag(r1, RANGE_LB_INC));
 }
 
 /* is upper bound inclusive? */
@@ -495,9 +527,8 @@ Datum
 range_upper_inc(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
-	char		flags = range_get_flags(r1);
 
-	PG_RETURN_BOOL(flags & RANGE_UB_INC);
+	PG_RETURN_BOOL(range_has_flag(r1, RANGE_UB_INC));
 }
 
 /* is lower bound infinite? */
@@ -505,9 +536,8 @@ Datum
 range_lower_inf(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
-	char		flags = range_get_flags(r1);
 
-	PG_RETURN_BOOL(flags & RANGE_LB_INF);
+	PG_RETURN_BOOL(range_has_flag(r1, RANGE_LB_INF));
 }
 
 /* is upper bound infinite? */
@@ -515,9 +545,8 @@ Datum
 range_upper_inf(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
-	char		flags = range_get_flags(r1);
 
-	PG_RETURN_BOOL(flags & RANGE_UB_INF);
+	PG_RETURN_BOOL(range_has_flag(r1, RANGE_UB_INF));
 }
 
 
@@ -957,7 +986,25 @@ range_minus(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
 	RangeType  *r2 = PG_GETARG_RANGE_P(1);
+	RangeType  *ret;
 	TypeCacheEntry *typcache;
+
+	/* 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));
+
+	ret = range_minus_internal(typcache, r1, r2);
+	if (ret)
+		PG_RETURN_RANGE_P(ret);
+	else
+		PG_RETURN_NULL();
+}
+
+RangeType *
+range_minus_internal(TypeCacheEntry *typcache, RangeType *r1, RangeType *r2)
+{
 	RangeBound	lower1,
 				lower2;
 	RangeBound	upper1,
@@ -969,18 +1016,12 @@ range_minus(PG_FUNCTION_ARGS)
 				cmp_u1l2,
 				cmp_u1u2;
 
-	/* 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));
-
 	range_deserialize(typcache, r1, &lower1, &upper1, &empty1);
 	range_deserialize(typcache, r2, &lower2, &upper2, &empty2);
 
 	/* if either is empty, r1 is the correct answer */
 	if (empty1 || empty2)
-		PG_RETURN_RANGE_P(r1);
+		return r1;
 
 	cmp_l1l2 = range_cmp_bounds(typcache, &lower1, &lower2);
 	cmp_l1u2 = range_cmp_bounds(typcache, &lower1, &upper2);
@@ -993,34 +1034,34 @@ range_minus(PG_FUNCTION_ARGS)
 				 errmsg("result of range difference would not be contiguous")));
 
 	if (cmp_l1u2 > 0 || cmp_u1l2 < 0)
-		PG_RETURN_RANGE_P(r1);
+		return r1;
 
 	if (cmp_l1l2 >= 0 && cmp_u1u2 <= 0)
-		PG_RETURN_RANGE_P(make_empty_range(typcache));
+		return make_empty_range(typcache);
 
 	if (cmp_l1l2 <= 0 && cmp_u1l2 >= 0 && cmp_u1u2 <= 0)
 	{
 		lower2.inclusive = !lower2.inclusive;
 		lower2.lower = false;	/* it will become the upper bound */
-		PG_RETURN_RANGE_P(make_range(typcache, &lower1, &lower2, false));
+		return make_range(typcache, &lower1, &lower2, false);
 	}
 
 	if (cmp_l1l2 >= 0 && cmp_u1u2 >= 0 && cmp_l1u2 <= 0)
 	{
 		upper2.inclusive = !upper2.inclusive;
 		upper2.lower = true;	/* it will become the lower bound */
-		PG_RETURN_RANGE_P(make_range(typcache, &upper2, &upper1, false));
+		return make_range(typcache, &upper2, &upper1, false);
 	}
 
 	elog(ERROR, "unexpected case in range_minus");
-	PG_RETURN_NULL();
+	return NULL;
 }
 
 /*
  * Set union.  If strict is true, it is an error that the two input ranges
  * are not adjacent or overlapping.
  */
-static RangeType *
+RangeType *
 range_union_internal(TypeCacheEntry *typcache, RangeType *r1, RangeType *r2,
 					 bool strict)
 {
@@ -1101,6 +1142,19 @@ range_intersect(PG_FUNCTION_ARGS)
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
 	RangeType  *r2 = PG_GETARG_RANGE_P(1);
 	TypeCacheEntry *typcache;
+
+	/* 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));
+
+	PG_RETURN_RANGE_P(range_intersect_internal(typcache, r1, r2));
+}
+
+RangeType *
+range_intersect_internal(TypeCacheEntry *typcache, const RangeType *r1, const RangeType *r2)
+{
 	RangeBound	lower1,
 				lower2;
 	RangeBound	upper1,
@@ -1110,17 +1164,11 @@ range_intersect(PG_FUNCTION_ARGS)
 	RangeBound *result_lower;
 	RangeBound *result_upper;
 
-	/* 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));
-
 	range_deserialize(typcache, r1, &lower1, &upper1, &empty1);
 	range_deserialize(typcache, r2, &lower2, &upper2, &empty2);
 
-	if (empty1 || empty2 || !DatumGetBool(range_overlaps(fcinfo)))
-		PG_RETURN_RANGE_P(make_empty_range(typcache));
+	if (empty1 || empty2 || !range_overlaps_internal(typcache, r1, r2))
+		return make_empty_range(typcache);
 
 	if (range_cmp_bounds(typcache, &lower1, &lower2) >= 0)
 		result_lower = &lower1;
@@ -1132,9 +1180,81 @@ range_intersect(PG_FUNCTION_ARGS)
 	else
 		result_upper = &upper2;
 
-	PG_RETURN_RANGE_P(make_range(typcache, result_lower, result_upper, false));
+	return make_range(typcache, result_lower, result_upper, false);
 }
 
+/* range, range -> range, range functions */
+
+/*
+ * range_split_internal - if r2 intersects the middle of r1, leaving non-empty
+ * ranges on both sides, then return true and set output1 and output2 to the
+ * results of r1 - r2 (in order). Otherwise return false and don't set output1
+ * or output2. Neither input range should be empty.
+ */
+bool
+range_split_internal(TypeCacheEntry *typcache, const RangeType *r1, const RangeType *r2,
+					 RangeType **output1, RangeType **output2)
+{
+	RangeBound	lower1,
+				lower2;
+	RangeBound	upper1,
+				upper2;
+	bool		empty1,
+				empty2;
+
+	range_deserialize(typcache, r1, &lower1, &upper1, &empty1);
+	range_deserialize(typcache, r2, &lower2, &upper2, &empty2);
+
+	if (range_cmp_bounds(typcache, &lower1, &lower2) < 0 &&
+		range_cmp_bounds(typcache, &upper1, &upper2) > 0)
+	{
+		/*
+		 * Need to invert inclusive/exclusive for the lower2 and upper2
+		 * points. They can't be infinite though. We're allowed to overwrite
+		 * these RangeBounds since they only exist locally.
+		 */
+		lower2.inclusive = !lower2.inclusive;
+		lower2.lower = false;
+		upper2.inclusive = !upper2.inclusive;
+		upper2.lower = true;
+
+		*output1 = make_range(typcache, &lower1, &lower2, false);
+		*output2 = make_range(typcache, &upper2, &upper1, false);
+		return true;
+	}
+
+	return false;
+}
+
+/* range -> range aggregate functions */
+
+Datum
+range_intersect_agg_transfn(PG_FUNCTION_ARGS)
+{
+	MemoryContext aggContext;
+	Oid			rngtypoid;
+	TypeCacheEntry *typcache;
+	RangeType  *result;
+	RangeType  *current;
+
+	if (!AggCheckCallContext(fcinfo, &aggContext))
+		elog(ERROR, "range_intersect_agg_transfn called in non-aggregate context");
+
+	rngtypoid = get_fn_expr_argtype(fcinfo->flinfo, 1);
+	if (!type_is_range(rngtypoid))
+		ereport(ERROR, (errmsg("range_intersect_agg must be called with a range")));
+
+	typcache = range_get_typcache(fcinfo, rngtypoid);
+
+	/* strictness ensures these are non-null */
+	result = PG_GETARG_RANGE_P(0);
+	current = PG_GETARG_RANGE_P(1);
+
+	result = range_intersect_internal(typcache, result, current);
+	PG_RETURN_RANGE_P(result);
+}
+
+
 /* Btree support */
 
 /* btree comparator */
@@ -1144,12 +1264,6 @@ range_cmp(PG_FUNCTION_ARGS)
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
 	RangeType  *r2 = PG_GETARG_RANGE_P(1);
 	TypeCacheEntry *typcache;
-	RangeBound	lower1,
-				lower2;
-	RangeBound	upper1,
-				upper2;
-	bool		empty1,
-				empty2;
 	int			cmp;
 
 	check_stack_depth();		/* recurses when subtype is a range type */
@@ -1160,6 +1274,28 @@ range_cmp(PG_FUNCTION_ARGS)
 
 	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, const RangeType *r1, const 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);
 
@@ -1177,10 +1313,7 @@ range_cmp(PG_FUNCTION_ARGS)
 			cmp = range_cmp_bounds(typcache, &upper1, &upper2);
 	}
 
-	PG_FREE_IF_COPY(r1, 0);
-	PG_FREE_IF_COPY(r2, 1);
-
-	PG_RETURN_INT32(cmp);
+	return cmp;
 }
 
 /* inequality operators using the range_cmp function */
@@ -1218,13 +1351,10 @@ range_gt(PG_FUNCTION_ARGS)
 
 /* Hash support */
 
-/* hash a range value */
-Datum
-hash_range(PG_FUNCTION_ARGS)
+uint32
+hash_range_internal(TypeCacheEntry *typcache, const RangeType *r)
 {
-	RangeType  *r = PG_GETARG_RANGE_P(0);
 	uint32		result;
-	TypeCacheEntry *typcache;
 	TypeCacheEntry *scache;
 	RangeBound	lower;
 	RangeBound	upper;
@@ -1233,10 +1363,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 +1404,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, const RangeType *r, Datum seed)
+{
+	uint64		result;
 	TypeCacheEntry *scache;
 	RangeBound	lower;
 	RangeBound	upper;
@@ -1300,10 +1433,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 +1471,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));
 }
 
 /*
@@ -1769,6 +1916,21 @@ range_get_flags(const RangeType *range)
 	return *((char *) range + VARSIZE(range) - 1);
 }
 
+/*
+ * range_has_flag: set whether a range has a specific flag.
+ *
+ * This lets expose some of our functions that just check flags
+ * to the rest of the code base (like to multiranges)
+ * without writing full-fledged *_internal versions.
+ */
+bool
+range_has_flag(const RangeType *r1, char flag)
+{
+	char		flags = range_get_flags(r1);
+
+	return flags & flag;
+}
+
 /*
  * range_set_contain_empty: set the RANGE_CONTAIN_EMPTY bit in the value.
  *
@@ -1937,6 +2099,46 @@ range_cmp_bound_values(TypeCacheEntry *typcache, const RangeBound *b1,
 										   b1->val, b2->val));
 }
 
+/*
+ * qsort callback for sorting ranges.
+ *
+ * Two empty ranges compare equal; an empty range sorts to the left of any
+ * non-empty range.  Two non-empty ranges are sorted by lower bound first
+ * and by upper bound next.
+ */
+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 0a6db0d478..6e9a48b314 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -2529,6 +2529,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
  *
@@ -3184,7 +3194,7 @@ get_namespace_name_or_temp(Oid nspid)
 		return get_namespace_name(nspid);
 }
 
-/*				---------- PG_RANGE CACHE ----------				 */
+/*				---------- PG_RANGE CACHES ----------				 */
 
 /*
  * get_range_subtype
@@ -3237,6 +3247,56 @@ get_range_collation(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->rngmultitypid;
+		ReleaseSysCache(tp);
+		return result;
+	}
+	else
+		return InvalidOid;
+}
+
+/*
+ * get_multirange_range
+ *		Returns the range type of a given multirange
+ *
+ * Returns InvalidOid if the type is not a multirange.
+ */
+Oid
+get_multirange_range(Oid multirangeOid)
+{
+	HeapTuple	tp;
+
+	tp = SearchSysCache1(RANGEMULTIRANGE, 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 53d9ddf159..7654331a59 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -651,6 +651,18 @@ static const struct cachedesc cacheinfo[] = {
 		},
 		64
 	},
+	{RangeRelationId,			/* RANGEMULTIRANGE */
+		RangeMultirangeTypidIndexId,
+		1,
+		{
+			Anum_pg_range_rngmultitypid,
+			0,
+			0,
+			0
+		},
+		4
+	},
+
 	{RangeRelationId,			/* RANGETYPE */
 		RangeTypidIndexId,
 		1,
diff --git a/src/backend/utils/cache/typcache.c b/src/backend/utils/cache/typcache.c
index 854f133f9b..f1d3cb97d0 100644
--- a/src/backend/utils/cache/typcache.c
+++ b/src/backend/utils/cache/typcache.c
@@ -286,6 +286,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);
@@ -302,6 +303,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 TypeCacheTypCallback(Datum arg, int cacheid, uint32 hashvalue);
 static void TypeCacheOpcCallback(Datum arg, int cacheid, uint32 hashvalue);
@@ -553,8 +557,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))
@@ -591,7 +595,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))
@@ -616,7 +620,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))
@@ -641,7 +645,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))
@@ -693,6 +697,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;
@@ -737,6 +748,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;
@@ -816,6 +834,16 @@ lookup_type_cache(Oid type_id, int flags)
 			(void) lookup_type_cache(typentry->rngelemtype->type_id, 0);
 	}
 
+	/*
+	 * 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
 	 */
@@ -927,6 +955,22 @@ load_rangetype_info(TypeCacheEntry *typentry)
 	typentry->rngelemtype = lookup_type_cache(subtypeOid, 0);
 }
 
+/*
+ * load_multirangetype_info --- helper routine to set up multirange type
+ * information
+ */
+static void
+load_multirangetype_info(TypeCacheEntry *typentry)
+{
+	Oid			rangetypeOid;
+
+	rangetypeOid = get_multirange_range(typentry->type_id);
+	if (!OidIsValid(rangetypeOid))
+		elog(ERROR, "cache lookup failed for multirange type %u",
+			 typentry->type_id);
+
+	typentry->rngtype = lookup_type_cache(rangetypeOid, TYPECACHE_RANGE_INFO);
+}
 
 /*
  * load_domaintype_info --- helper routine to set up domain constraint info
@@ -1527,11 +1571,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
@@ -1574,6 +1618,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 78ed857203..ce09d0d128 100644
--- a/src/backend/utils/fmgr/funcapi.c
+++ b/src/backend/utils/fmgr/funcapi.c
@@ -35,6 +35,7 @@ typedef struct polymorphic_actuals
 	Oid			anyelement_type;	/* anyelement mapping, if known */
 	Oid			anyarray_type;	/* anyarray mapping, if known */
 	Oid			anyrange_type;	/* anyrange mapping, if known */
+	Oid			anymultirange_type;	/* anymultirange mapping, if known */
 } polymorphic_actuals;
 
 static void shutdown_MultiFuncCall(Datum arg);
@@ -46,6 +47,7 @@ static TypeFuncClass internal_get_result_type(Oid funcid,
 static void resolve_anyelement_from_others(polymorphic_actuals *actuals);
 static void resolve_anyarray_from_others(polymorphic_actuals *actuals);
 static void resolve_anyrange_from_others(polymorphic_actuals *actuals);
+static void resolve_anymultirange_from_others(polymorphic_actuals *actuals);
 static bool resolve_polymorphic_tupdesc(TupleDesc tupdesc,
 										oidvector *declared_args,
 										Node *call_expr);
@@ -503,6 +505,34 @@ resolve_anyelement_from_others(polymorphic_actuals *actuals)
 							format_type_be(range_base_type))));
 		actuals->anyelement_type = range_typelem;
 	}
+	else if (OidIsValid(actuals->anymultirange_type))
+	{
+		/* Use the element type based on the multirange type */
+		Oid			multirange_base_type;
+		Oid			multirange_typelem;
+		Oid			range_base_type;
+		Oid			range_typelem;
+
+		multirange_base_type = getBaseType(actuals->anymultirange_type);
+		multirange_typelem = get_multirange_range(multirange_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(multirange_base_type))));
+
+		range_base_type = getBaseType(multirange_typelem);
+		range_typelem = get_range_subtype(range_base_type);
+
+		if (!OidIsValid(range_typelem))
+			ereport(ERROR,
+					(errcode(ERRCODE_DATATYPE_MISMATCH),
+					 errmsg("argument declared %s does not contain a range type but type %s",
+							"anymultirange",
+							format_type_be(range_base_type))));
+		actuals->anyelement_type = range_typelem;
+	}
 	else
 		elog(ERROR, "could not determine polymorphic type");
 }
@@ -540,10 +570,54 @@ static void
 resolve_anyrange_from_others(polymorphic_actuals *actuals)
 {
 	/*
-	 * We can't deduce a range type from other polymorphic inputs, because
-	 * there may be multiple range types with the same subtype.
+	 * We can't deduce a range type from other polymorphic array or base types,
+	 * because there may be multiple range types with the same subtype, but we
+	 * can deduce it from a polymorphic multirange type.
 	 */
-	elog(ERROR, "could not determine polymorphic type");
+	if (OidIsValid(actuals->anymultirange_type))
+	{
+		/* Use the element type based on the multirange type */
+		Oid			multirange_base_type = getBaseType(actuals->anymultirange_type);
+		Oid			multirange_typelem =
+			get_multirange_range(multirange_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(multirange_base_type))));
+		actuals->anyrange_type = multirange_typelem;
+	}
+	else
+		elog(ERROR, "could not determine polymorphic type");
+}
+
+/*
+ * Resolve actual type of ANYMULTIRANGE from other polymorphic inputs
+ */
+static void
+resolve_anymultirange_from_others(polymorphic_actuals *actuals)
+{
+	/*
+	 * We can't deduce a multirange type from polymorphic array or base types,
+	 * because there may be multiple range types with the same subtype, but we
+	 * can deduce it from a polymorphic range type.
+	 */
+	if (OidIsValid(actuals->anyrange_type))
+	{
+		Oid			range_base_type = getBaseType(actuals->anyrange_type);
+		Oid			multirange_typeid = get_range_multirange(range_base_type);
+
+		if (!OidIsValid(multirange_typeid))
+			ereport(ERROR,
+					(errcode(ERRCODE_UNDEFINED_OBJECT),
+					 errmsg("could not find multirange type for data type %s",
+							format_type_be(actuals->anyrange_type))));
+		actuals->anymultirange_type = multirange_typeid;
+	}
+	else
+		elog(ERROR, "could not determine polymorphic type");
 }
 
 /*
@@ -566,6 +640,7 @@ 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_anycompatible_result = false;
 	bool		have_anycompatible_array_result = false;
 	bool		have_anycompatible_range_result = false;
@@ -594,6 +669,10 @@ resolve_polymorphic_tupdesc(TupleDesc tupdesc, oidvector *declared_args,
 				have_polymorphic_result = true;
 				have_anyrange_result = true;
 				break;
+			case ANYMULTIRANGEOID:
+				have_polymorphic_result = true;
+				have_anymultirange_result = true;
+				break;
 			case ANYCOMPATIBLEOID:
 			case ANYCOMPATIBLENONARRAYOID:
 				have_polymorphic_result = true;
@@ -660,6 +739,15 @@ resolve_polymorphic_tupdesc(TupleDesc tupdesc, oidvector *declared_args,
 						return false;
 				}
 				break;
+			case ANYMULTIRANGEOID:
+				if (!OidIsValid(poly_actuals.anymultirange_type))
+				{
+					poly_actuals.anymultirange_type =
+						get_call_expr_argtype(call_expr, i);
+					if (!OidIsValid(poly_actuals.anymultirange_type))
+						return false;
+				}
+				break;
 			case ANYCOMPATIBLEOID:
 			case ANYCOMPATIBLENONARRAYOID:
 				if (!OidIsValid(anyc_actuals.anyelement_type))
@@ -703,6 +791,9 @@ resolve_polymorphic_tupdesc(TupleDesc tupdesc, oidvector *declared_args,
 	if (have_anyrange_result && !OidIsValid(poly_actuals.anyrange_type))
 		resolve_anyrange_from_others(&poly_actuals);
 
+	if (have_anymultirange_result && !OidIsValid(poly_actuals.anymultirange_type))
+		resolve_anymultirange_from_others(&poly_actuals);
+
 	if (have_anycompatible_result && !OidIsValid(anyc_actuals.anyelement_type))
 		resolve_anyelement_from_others(&anyc_actuals);
 
@@ -780,6 +871,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),
+								   poly_actuals.anymultirange_type,
+								   -1,
+								   0);
+				/* no collation should be attached to a multirange type */
+				break;
 			case ANYCOMPATIBLEOID:
 			case ANYCOMPATIBLENONARRAYOID:
 				TupleDescInitEntry(tupdesc, i + 1,
@@ -834,6 +933,7 @@ 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;
 	bool		have_anycompatible_result = false;
 	bool		have_anycompatible_array_result = false;
 	bool		have_anycompatible_range_result = false;
@@ -912,6 +1012,24 @@ resolve_polymorphic_argtypes(int numargs, Oid *argtypes, char *argmodes,
 					argtypes[i] = poly_actuals.anyrange_type;
 				}
 				break;
+			case ANYMULTIRANGEOID:
+				if (argmode == PROARGMODE_OUT || argmode == PROARGMODE_TABLE)
+				{
+					have_polymorphic_result = true;
+					have_anymultirange_result = true;
+				}
+				else
+				{
+					if (!OidIsValid(poly_actuals.anymultirange_type))
+					{
+						poly_actuals.anymultirange_type =
+							get_call_expr_argtype(call_expr, inargno);
+						if (!OidIsValid(poly_actuals.anymultirange_type))
+							return false;
+					}
+					argtypes[i] = poly_actuals.anymultirange_type;
+				}
+				break;
 			case ANYCOMPATIBLEOID:
 			case ANYCOMPATIBLENONARRAYOID:
 				if (argmode == PROARGMODE_OUT || argmode == PROARGMODE_TABLE)
@@ -988,6 +1106,9 @@ resolve_polymorphic_argtypes(int numargs, Oid *argtypes, char *argmodes,
 	if (have_anyrange_result && !OidIsValid(poly_actuals.anyrange_type))
 		resolve_anyrange_from_others(&poly_actuals);
 
+	if (have_anymultirange_result && !OidIsValid(poly_actuals.anymultirange_type))
+		resolve_anymultirange_from_others(&poly_actuals);
+
 	if (have_anycompatible_result && !OidIsValid(anyc_actuals.anyelement_type))
 		resolve_anyelement_from_others(&anyc_actuals);
 
@@ -1013,6 +1134,9 @@ resolve_polymorphic_argtypes(int numargs, Oid *argtypes, char *argmodes,
 			case ANYRANGEOID:
 				argtypes[i] = poly_actuals.anyrange_type;
 				break;
+			case ANYMULTIRANGEOID:
+				argtypes[i] = poly_actuals.anymultirange_type;
+				break;
 			case ANYCOMPATIBLEOID:
 			case ANYCOMPATIBLENONARRAYOID:
 				argtypes[i] = anyc_actuals.anyelement_type;
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 408637cfec..cf4ce0a5a6 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -276,7 +276,8 @@ static void dumpSearchPath(Archive *AH);
 static void binary_upgrade_set_type_oids_by_type_oid(Archive *fout,
 													 PQExpBuffer upgrade_buffer,
 													 Oid pg_type_oid,
-													 bool force_array_type);
+													 bool force_array_type,
+													 bool include_multirange_type);
 static bool binary_upgrade_set_type_oids_by_rel_oid(Archive *fout,
 													PQExpBuffer upgrade_buffer, Oid pg_rel_oid);
 static void binary_upgrade_set_pg_class_oids(Archive *fout,
@@ -1648,7 +1649,7 @@ selectDumpableType(TypeInfo *tyinfo, Archive *fout)
 	}
 
 	/* skip auto-generated array types */
-	if (tyinfo->isArray)
+	if (tyinfo->isArray || tyinfo->isMultirange)
 	{
 		tyinfo->dobj.objType = DO_DUMMY_TYPE;
 
@@ -4416,16 +4417,49 @@ append_depends_on_extension(Archive *fout,
 	}
 }
 
+static Oid
+get_next_possible_free_pg_type_oid(Archive *fout, PQExpBuffer upgrade_query)
+{
+	/*
+	 * If the old version didn't assign an array type, but the new version
+	 * does, we must select an unused type OID to assign.  This currently only
+	 * happens for domains, when upgrading pre-v11 to v11 and up.
+	 *
+	 * Note: local state here is kind of ugly, but we must have some, since we
+	 * mustn't choose the same unused OID more than once.
+	 */
+	static Oid	next_possible_free_oid = FirstNormalObjectId;
+	PGresult   *res;
+	bool		is_dup;
+
+	do
+	{
+		++next_possible_free_oid;
+		printfPQExpBuffer(upgrade_query,
+						  "SELECT EXISTS(SELECT 1 "
+						  "FROM pg_catalog.pg_type "
+						  "WHERE oid = '%u'::pg_catalog.oid);",
+						  next_possible_free_oid);
+		res = ExecuteSqlQueryForSingleRow(fout, upgrade_query->data);
+		is_dup = (PQgetvalue(res, 0, 0)[0] == 't');
+		PQclear(res);
+	} while (is_dup);
+
+	return next_possible_free_oid;
+}
 
 static void
 binary_upgrade_set_type_oids_by_type_oid(Archive *fout,
 										 PQExpBuffer upgrade_buffer,
 										 Oid pg_type_oid,
-										 bool force_array_type)
+										 bool force_array_type,
+										 bool include_multirange_type)
 {
 	PQExpBuffer upgrade_query = createPQExpBuffer();
 	PGresult   *res;
 	Oid			pg_type_array_oid;
+	Oid			pg_type_multirange_oid;
+	Oid			pg_type_multirange_array_oid;
 
 	appendPQExpBufferStr(upgrade_buffer, "\n-- For binary upgrade, must preserve pg_type oid\n");
 	appendPQExpBuffer(upgrade_buffer,
@@ -4446,33 +4480,7 @@ binary_upgrade_set_type_oids_by_type_oid(Archive *fout,
 	PQclear(res);
 
 	if (!OidIsValid(pg_type_array_oid) && force_array_type)
-	{
-		/*
-		 * If the old version didn't assign an array type, but the new version
-		 * does, we must select an unused type OID to assign.  This currently
-		 * only happens for domains, when upgrading pre-v11 to v11 and up.
-		 *
-		 * Note: local state here is kind of ugly, but we must have some,
-		 * since we mustn't choose the same unused OID more than once.
-		 */
-		static Oid	next_possible_free_oid = FirstNormalObjectId;
-		bool		is_dup;
-
-		do
-		{
-			++next_possible_free_oid;
-			printfPQExpBuffer(upgrade_query,
-							  "SELECT EXISTS(SELECT 1 "
-							  "FROM pg_catalog.pg_type "
-							  "WHERE oid = '%u'::pg_catalog.oid);",
-							  next_possible_free_oid);
-			res = ExecuteSqlQueryForSingleRow(fout, upgrade_query->data);
-			is_dup = (PQgetvalue(res, 0, 0)[0] == 't');
-			PQclear(res);
-		} while (is_dup);
-
-		pg_type_array_oid = next_possible_free_oid;
-	}
+		pg_type_array_oid = get_next_possible_free_pg_type_oid(fout, upgrade_query);
 
 	if (OidIsValid(pg_type_array_oid))
 	{
@@ -4483,6 +4491,46 @@ binary_upgrade_set_type_oids_by_type_oid(Archive *fout,
 						  pg_type_array_oid);
 	}
 
+	/*
+	 * Pre-set the multirange type oid and its own array type oid.
+	 */
+	if (include_multirange_type)
+	{
+		if (fout->remoteVersion >= 130000)
+		{
+			appendPQExpBuffer(upgrade_query,
+							  "SELECT t.oid, t.typarray "
+							  "FROM pg_catalog.pg_type t "
+							  "JOIN pg_catalog.pg_range r "
+							  "ON t.oid = r.rngmultitypid "
+							  "WHERE r.rngtypid = '%u'::pg_catalog.oid;",
+							  pg_type_oid);
+
+			res = ExecuteSqlQueryForSingleRow(fout, upgrade_query->data);
+
+			pg_type_multirange_oid = atooid(PQgetvalue(res, 0, PQfnumber(res, "oid")));
+			pg_type_multirange_array_oid = atooid(PQgetvalue(res, 0, PQfnumber(res, "typarray")));
+
+			PQclear(res);
+		}
+		else
+		{
+			pg_type_multirange_oid = get_next_possible_free_pg_type_oid(fout, upgrade_query);
+			pg_type_multirange_array_oid = get_next_possible_free_pg_type_oid(fout, upgrade_query);
+		}
+
+		appendPQExpBufferStr(upgrade_buffer,
+							 "\n-- For binary upgrade, must preserve multirange pg_type oid\n");
+		appendPQExpBuffer(upgrade_buffer,
+						  "SELECT pg_catalog.binary_upgrade_set_next_multirange_pg_type_oid('%u'::pg_catalog.oid);\n\n",
+						  pg_type_multirange_oid);
+		appendPQExpBufferStr(upgrade_buffer,
+							 "\n-- For binary upgrade, must preserve multirange pg_type array oid\n");
+		appendPQExpBuffer(upgrade_buffer,
+						  "SELECT pg_catalog.binary_upgrade_set_next_multirange_array_pg_type_oid('%u'::pg_catalog.oid);\n\n",
+						  pg_type_multirange_array_oid);
+	}
+
 	destroyPQExpBuffer(upgrade_query);
 }
 
@@ -4516,7 +4564,7 @@ binary_upgrade_set_type_oids_by_rel_oid(Archive *fout,
 	pg_type_oid = atooid(PQgetvalue(upgrade_res, 0, PQfnumber(upgrade_res, "crel")));
 
 	binary_upgrade_set_type_oids_by_type_oid(fout, upgrade_buffer,
-											 pg_type_oid, false);
+											 pg_type_oid, false, false);
 
 	if (!PQgetisnull(upgrade_res, 0, PQfnumber(upgrade_res, "trel")))
 	{
@@ -5065,6 +5113,11 @@ getTypes(Archive *fout, int *numTypes)
 		else
 			tyinfo[i].isArray = false;
 
+		if (tyinfo[i].typtype == 'm')
+			tyinfo[i].isMultirange = true;
+		else
+			tyinfo[i].isMultirange = false;
+
 		/* Decide whether we want to dump it */
 		selectDumpableType(&tyinfo[i], fout);
 
@@ -8203,9 +8256,12 @@ getProcLangs(Archive *fout, int *numProcLangs)
 
 /*
  * getCasts
- *	  get basic information about every cast in the system
+ *	  get basic information about most casts in the system
  *
  * numCasts is set to the number of casts read in
+ *
+ * Skip casts from a range to its multirange, since we'll create those
+ * automatically.
  */
 CastInfo *
 getCasts(Archive *fout, int *numCasts)
@@ -8223,7 +8279,20 @@ getCasts(Archive *fout, int *numCasts)
 	int			i_castcontext;
 	int			i_castmethod;
 
-	if (fout->remoteVersion >= 80400)
+	if (fout->remoteVersion >= 130000)
+	{
+		appendPQExpBufferStr(query, "SELECT tableoid, oid, "
+							 "castsource, casttarget, castfunc, castcontext, "
+							 "castmethod "
+							 "FROM pg_cast c "
+							 "WHERE NOT EXISTS ( "
+							 "SELECT 1 FROM pg_range r "
+							 "WHERE c.castsource = r.rngtypid "
+							 "AND c.casttarget = r.rngmultitypid "
+							 ") "
+							 "ORDER BY 3,4");
+	}
+	else if (fout->remoteVersion >= 80400)
 	{
 		appendPQExpBufferStr(query, "SELECT tableoid, oid, "
 							 "castsource, casttarget, castfunc, castcontext, "
@@ -10411,7 +10480,7 @@ dumpEnumType(Archive *fout, TypeInfo *tyinfo)
 	if (dopt->binary_upgrade)
 		binary_upgrade_set_type_oids_by_type_oid(fout, q,
 												 tyinfo->dobj.catId.oid,
-												 false);
+												 false, false);
 
 	appendPQExpBuffer(q, "CREATE TYPE %s AS ENUM (",
 					  qualtypname);
@@ -10537,7 +10606,7 @@ dumpRangeType(Archive *fout, TypeInfo *tyinfo)
 	if (dopt->binary_upgrade)
 		binary_upgrade_set_type_oids_by_type_oid(fout, q,
 												 tyinfo->dobj.catId.oid,
-												 false);
+												 false, true);
 
 	appendPQExpBuffer(q, "CREATE TYPE %s AS RANGE (",
 					  qualtypname);
@@ -10643,7 +10712,7 @@ dumpUndefinedType(Archive *fout, TypeInfo *tyinfo)
 	if (dopt->binary_upgrade)
 		binary_upgrade_set_type_oids_by_type_oid(fout, q,
 												 tyinfo->dobj.catId.oid,
-												 false);
+												 false, false);
 
 	appendPQExpBuffer(q, "CREATE TYPE %s;\n",
 					  qualtypname);
@@ -10848,7 +10917,7 @@ dumpBaseType(Archive *fout, TypeInfo *tyinfo)
 	if (dopt->binary_upgrade)
 		binary_upgrade_set_type_oids_by_type_oid(fout, q,
 												 tyinfo->dobj.catId.oid,
-												 false);
+												 false, false);
 
 	appendPQExpBuffer(q,
 					  "CREATE TYPE %s (\n"
@@ -11035,7 +11104,8 @@ dumpDomain(Archive *fout, TypeInfo *tyinfo)
 	if (dopt->binary_upgrade)
 		binary_upgrade_set_type_oids_by_type_oid(fout, q,
 												 tyinfo->dobj.catId.oid,
-												 true); /* force array type */
+												 true,		/* force array type */
+												 false);	/* force multirange type */
 
 	qtypname = pg_strdup(fmtId(tyinfo->dobj.name));
 	qualtypname = pg_strdup(fmtQualifiedDumpable(tyinfo));
@@ -11223,7 +11293,7 @@ dumpCompositeType(Archive *fout, TypeInfo *tyinfo)
 	{
 		binary_upgrade_set_type_oids_by_type_oid(fout, q,
 												 tyinfo->dobj.catId.oid,
-												 false);
+												 false, false);
 		binary_upgrade_set_pg_class_oids(fout, q, tyinfo->typrelid, false);
 	}
 
@@ -11497,7 +11567,7 @@ dumpShellType(Archive *fout, ShellTypeInfo *stinfo)
 	if (dopt->binary_upgrade)
 		binary_upgrade_set_type_oids_by_type_oid(fout, q,
 												 stinfo->baseType->dobj.catId.oid,
-												 false);
+												 false, false);
 
 	appendPQExpBuffer(q, "CREATE TYPE %s;\n",
 					  fmtQualifiedDumpable(stinfo));
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 3e11166615..0b717412b4 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -176,6 +176,7 @@ typedef struct _typeInfo
 	char		typrelkind;		/* 'r', 'v', 'c', etc */
 	char		typtype;		/* 'b', 'c', etc */
 	bool		isArray;		/* true if auto-generated array type */
+	bool		isMultirange;	/* true if auto-generated multirange type */
 	bool		isDefined;		/* true if typisdefined */
 	/* If needed, we'll create a "shell type" entry for it; link that here: */
 	struct _shellTypeInfo *shellType;	/* shell-type entry, or NULL */
diff --git a/src/include/access/tupmacs.h b/src/include/access/tupmacs.h
index 70157cf90a..c262265b95 100644
--- a/src/include/access/tupmacs.h
+++ b/src/include/access/tupmacs.h
@@ -139,8 +139,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/binary_upgrade.h b/src/include/catalog/binary_upgrade.h
index 12d94fe1b3..01d95117cd 100644
--- a/src/include/catalog/binary_upgrade.h
+++ b/src/include/catalog/binary_upgrade.h
@@ -16,6 +16,8 @@
 
 extern PGDLLIMPORT Oid binary_upgrade_next_pg_type_oid;
 extern PGDLLIMPORT Oid binary_upgrade_next_array_pg_type_oid;
+extern PGDLLIMPORT Oid binary_upgrade_next_mrng_pg_type_oid;
+extern PGDLLIMPORT Oid binary_upgrade_next_mrng_array_pg_type_oid;
 extern PGDLLIMPORT Oid binary_upgrade_next_toast_pg_type_oid;
 
 extern PGDLLIMPORT Oid binary_upgrade_next_heap_pg_class_oid;
diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h
index 8be303870f..566f9364c5 100644
--- a/src/include/catalog/indexing.h
+++ b/src/include/catalog/indexing.h
@@ -327,6 +327,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_rngmultitypid_index, 8001, on pg_range using btree(rngmultitypid 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_aggregate.dat b/src/include/catalog/pg_aggregate.dat
index ffabe275c0..03bab74253 100644
--- a/src/include/catalog/pg_aggregate.dat
+++ b/src/include/catalog/pg_aggregate.dat
@@ -538,6 +538,17 @@
   aggtransfn => 'bytea_string_agg_transfn',
   aggfinalfn => 'bytea_string_agg_finalfn', aggtranstype => 'internal' },
 
+# range
+{ aggfnoid => 'range_intersect_agg(anyrange)',
+  aggtransfn => 'range_intersect_agg_transfn',
+  aggcombinefn => 'range_intersect_agg_transfn', aggtranstype => 'anyrange' },
+{ aggfnoid => 'range_intersect_agg(anymultirange)',
+  aggtransfn => 'multirange_intersect_agg_transfn',
+  aggcombinefn => 'multirange_intersect_agg_transfn', aggtranstype => 'anymultirange' },
+{ aggfnoid => 'range_agg(anyrange)', aggtransfn => 'range_agg_transfn',
+  aggfinalfn => 'range_agg_finalfn', aggfinalextra => 't',
+  aggtranstype => 'internal' },
+
 # json
 { aggfnoid => 'json_agg', aggtransfn => 'json_agg_transfn',
   aggfinalfn => 'json_agg_finalfn', aggtranstype => 'internal' },
diff --git a/src/include/catalog/pg_amop.dat b/src/include/catalog/pg_amop.dat
index 11aaa519c8..827ac4f03c 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 cef63b2a71..5ac1c478cb 100644
--- a/src/include/catalog/pg_amproc.dat
+++ b/src/include/catalog/pg_amproc.dat
@@ -285,6 +285,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' },
 
@@ -445,6 +447,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',
@@ -1266,12 +1273,13 @@
   amprocrighttype => 'anyrange', amprocnum => '4',
   amproc => 'brin_inclusion_union' },
 { amprocfamily => 'brin/range_inclusion_ops', amproclefttype => 'anyrange',
-  amprocrighttype => 'anyrange', amprocnum => '11', amproc => 'range_merge' },
+  amprocrighttype => 'anyrange', amprocnum => '11',
+  amproc => 'range_merge(anyrange,anyrange)' },
 { amprocfamily => 'brin/range_inclusion_ops', amproclefttype => 'anyrange',
   amprocrighttype => 'anyrange', amprocnum => '13',
   amproc => 'range_contains' },
 { amprocfamily => 'brin/range_inclusion_ops', amproclefttype => 'anyrange',
-  amprocrighttype => 'anyrange', amprocnum => '14', amproc => 'isempty' },
+  amprocrighttype => 'anyrange', amprocnum => '14', amproc => 'isempty(anyrange)' },
 
 # minmax pg_lsn
 { amprocfamily => 'brin/pg_lsn_minmax_ops', amproclefttype => 'pg_lsn',
diff --git a/src/include/catalog/pg_cast.dat b/src/include/catalog/pg_cast.dat
index 01c5328ddd..f555af90ac 100644
--- a/src/include/catalog/pg_cast.dat
+++ b/src/include/catalog/pg_cast.dat
@@ -526,4 +526,17 @@
 { castsource => 'jsonb', casttarget => 'float8', castfunc => 'float8(jsonb)',
   castcontext => 'e', castmethod => 'f' },
 
+# range to multirange
+{ castsource => 'int4range', casttarget => 'int4multirange', castfunc => 'int4multirange(int4range)',
+  castcontext => 'e', castmethod => 'f' },
+{ castsource => 'int8range', casttarget => 'int8multirange', castfunc => 'int8multirange(int8range)',
+  castcontext => 'e', castmethod => 'f' },
+{ castsource => 'numrange', casttarget => 'nummultirange', castfunc => 'nummultirange(numrange)',
+  castcontext => 'e', castmethod => 'f' },
+{ castsource => 'daterange', casttarget => 'datemultirange', castfunc => 'datemultirange(daterange)',
+  castcontext => 'e', castmethod => 'f' },
+{ castsource => 'tsrange', casttarget => 'tsmultirange', castfunc => 'tsmultirange(tsrange)',
+  castcontext => 'e', castmethod => 'f' },
+{ castsource => 'tstzrange', casttarget => 'tstzmultirange', castfunc => 'tstzmultirange(tstzrange)',
+  castcontext => 'e', castmethod => 'f' },
 ]
diff --git a/src/include/catalog/pg_opclass.dat b/src/include/catalog/pg_opclass.dat
index ab2f50c9eb..eebebbcdb4 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 65c7fedf23..08cda3ba36 100644
--- a/src/include/catalog/pg_operator.dat
+++ b/src/include/catalog/pg_operator.dat
@@ -3302,5 +3302,174 @@
   oprname => '@@', oprleft => 'jsonb', oprright => 'jsonpath',
   oprresult => 'bool', oprcode => 'jsonb_path_match_opr(jsonb,jsonpath)',
   oprrest => 'matchingsel', oprjoin => 'matchingjoinsel' },
+{ 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' },
+{ oid => '8073', oid_symbol => 'OID_RANGE_OVERLAPS_MULTIRANGE_OP',
+  descr => 'overlaps',
+  oprname => '&&', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '&&(anymultirange,anyrange)',
+  oprcode => 'range_overlaps_multirange', oprrest => 'areasel',
+  oprjoin => 'areajoinsel' },
+{ oid => '8074', oid_symbol => 'OID_MULTIRANGE_OVERLAPS_RANGE_OP', descr => 'contains',
+  oprname => '&&', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcom => '&&(anyrange,anymultirange)',
+  oprcode => 'multirange_overlaps_range', oprrest => 'areasel',
+  oprjoin => 'areajoinsel' },
+{ oid => '8075', oid_symbol => 'OID_MULTIRANGE_OVERLAPS_MULTIRANGE_OP', descr => 'contains',
+  oprname => '&&', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '&&(anymultirange,anymultirange)',
+  oprcode => 'multirange_overlaps_multirange', oprrest => 'areasel',
+  oprjoin => 'areajoinsel' },
+{ oid => '8064', oid_symbol => 'OID_MULTIRANGE_CONTAINS_ELEM_OP',
+  descr => 'contains',
+  oprname => '@>', oprleft => 'anymultirange', oprright => 'anyelement',
+  oprresult => 'bool', oprcom => '<@(anyelement,anymultirange)',
+  oprcode => 'multirange_contains_elem', oprrest => 'contsel',
+  oprjoin => 'contjoinsel' },
+{ oid => '8065', oid_symbol => 'OID_MULTIRANGE_CONTAINS_RANGE_OP', descr => 'contains',
+  oprname => '@>', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcom => '<@(anyrange,anymultirange)',
+  oprcode => 'multirange_contains_range', oprrest => 'contsel',
+  oprjoin => 'contjoinsel' },
+{ oid => '8066', oid_symbol => 'OID_MULTIRANGE_CONTAINS_MULTIRANGE_OP', descr => 'contains',
+  oprname => '@>', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '<@(anymultirange,anymultirange)',
+  oprcode => 'multirange_contains_multirange', oprrest => 'contsel',
+  oprjoin => 'contjoinsel' },
+{ oid => '8067', oid_symbol => 'OID_MULTIRANGE_ELEM_CONTAINED_OP',
+  descr => 'is contained by',
+  oprname => '<@', oprleft => 'anyelement', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '@>(anymultirange,anyelement)',
+  oprcode => 'elem_contained_by_multirange', oprrest => 'contsel',
+  oprjoin => 'contjoinsel' },
+{ oid => '8068', oid_symbol => 'OID_MULTIRANGE_RANGE_CONTAINED_OP',
+  descr => 'is contained by',
+  oprname => '<@', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '@>(anymultirange,anyrange)',
+  oprcode => 'range_contained_by_multirange', oprrest => 'contsel',
+  oprjoin => 'contjoinsel' },
+{ oid => '8069', oid_symbol => 'OID_MULTIRANGE_MULTIRANGE_CONTAINED_OP',
+  descr => 'is contained by',
+  oprname => '<@', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '@>(anymultirange,anymultirange)',
+  oprcode => 'multirange_contained_by_multirange', oprrest => 'contsel',
+  oprjoin => 'contjoinsel' },
+{ oid => '8106', oid_symbol => 'OID_RANGE_OVERLAPS_LEFT_MULTIRANGE_OP',
+  descr => 'overlaps or is left of',
+  oprname => '&<', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcode => 'range_overleft_multirange', oprrest => 'scalarltsel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8107', oid_symbol => 'OID_MULTIRANGE_OVERLAPS_LEFT_RANGE_OP',
+  descr => 'overlaps or is left of',
+  oprname => '&<', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcode => 'multirange_overleft_range', oprrest => 'scalarltsel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8108', oid_symbol => 'OID_MULTIRANGE_OVERLAPS_LEFT_MULTIRANGE_OP',
+  descr => 'overlaps or is left of',
+  oprname => '&<', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcode => 'multirange_overleft_multirange', oprrest => 'scalarltsel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8109', oid_symbol => 'OID_RANGE_OVERLAPS_RIGHT_MULTIRANGE_OP',
+  descr => 'overlaps or is right of',
+  oprname => '&>', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcode => 'range_overright_multirange', oprrest => 'scalargtsel',
+  oprjoin => 'scalargtjoinsel' },
+{ oid => '8110', oid_symbol => 'OID_MULTIRANGE_OVERLAPS_RIGHT_RANGE_OP',
+  descr => 'overlaps or is right of',
+  oprname => '&>', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcode => 'multirange_overright_range', oprrest => 'scalargtsel',
+  oprjoin => 'scalargtjoinsel' },
+{ oid => '8111', oid_symbol => 'OID_MULTIRANGE_OVERLAPS_RIGHT_MULTIRANGE_OP',
+  descr => 'overlaps or is right of',
+  oprname => '&>', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcode => 'multirange_overright_multirange', oprrest => 'scalargtsel',
+  oprjoin => 'scalargtjoinsel' },
+{ oid => '8091', oid_symbol => 'OID_RANGE_ADJACENT_MULTIRANGE_OP', descr => 'is adjacent to',
+  oprname => '-|-', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '-|-(anymultirange,anyrange)',
+  oprcode => 'range_adjacent_multirange', oprrest => 'scalarltsel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8092', oid_symbol => 'OID_MULTIRANGE_ADJACENT_RANGE_OP', descr => 'is adjacent to',
+  oprname => '-|-', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcom => '-|-(anyrange,anymultirange)',
+  oprcode => 'multirange_adjacent_range', oprrest => 'scalarltsel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8093', oid_symbol => 'OID_MULTIRANGE_ADJACENT_MULTIRANGE_OP', descr => 'is adjacent to',
+  oprname => '-|-', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '-|-(anymultirange,anymultirange)',
+  oprcode => 'multirange_adjacent_multirange', oprrest => 'scalarltsel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8117', descr => 'multirange union',
+  oprname => '+', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'anymultirange', oprcom => '+(anymultirange,anymultirange)',
+  oprcode => 'multirange_union' },
+{ oid => '8123', descr => 'multirange minus',
+  oprname => '-', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'anymultirange', oprcode => 'multirange_minus' },
+{ oid => '8129', descr => 'multirange intersect',
+  oprname => '*', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'anymultirange', oprcom => '*(anymultirange,anymultirange)',
+  oprcode => 'multirange_intersect' },
+{ oid => '8082', oid_symbol => 'OID_RANGE_BEFORE_MULTIRANGE_OP', descr => 'is left of',
+  oprname => '<<', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '>>(anymultirange,anyrange)',
+  oprcode => 'range_before_multirange', oprrest => 'scalarltsel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8083', oid_symbol => 'OID_MULTIRANGE_BEFORE_RANGE_OP', descr => 'is left of',
+  oprname => '<<', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcom => '>>(anyrange,anymultirange)',
+  oprcode => 'multirange_before_range', oprrest => 'scalarltsel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8084', oid_symbol => 'OID_MULTIRANGE_BEFORE_MULTIRANGE_OP', descr => 'is left of',
+  oprname => '<<', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '>>(anymultirange,anymultirange)',
+  oprcode => 'multirange_before_multirange', oprrest => 'scalarltsel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8085', oid_symbol => 'OID_RANGE_AFTER_MULTIRANGE_OP', descr => 'is right of',
+  oprname => '>>', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '<<(anymultirange,anyrange)',
+  oprcode => 'range_after_multirange', oprrest => 'scalargtsel',
+  oprjoin => 'scalargtjoinsel' },
+{ oid => '8086', oid_symbol => 'OID_MULTIRANGE_AFTER_RANGE_OP', descr => 'is right of',
+  oprname => '>>', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcom => '<<(anyrange,anymultirange)',
+  oprcode => 'multirange_after_range', oprrest => 'scalargtsel',
+  oprjoin => 'scalargtjoinsel' },
+{ oid => '8087', oid_symbol => 'OID_MULTIRANGE_AFTER_MULTIRANGE_OP', descr => 'is right of',
+  oprname => '>>', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '<<(anymultirange,anymultirange)',
+  oprcode => 'multirange_after_multirange', oprrest => 'scalargtsel',
+  oprjoin => 'scalargtjoinsel' },
 
 ]
diff --git a/src/include/catalog/pg_opfamily.dat b/src/include/catalog/pg_opfamily.dat
index 26227df216..901fd7b303 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 a649e44d08..d6afad645b 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -9718,6 +9718,10 @@
   descr => 'the smallest range which includes both of the given ranges',
   proname => 'range_merge', prorettype => 'anyrange',
   proargtypes => 'anyrange anyrange', prosrc => 'range_merge' },
+{ oid => '8143',
+  descr => 'the smallest range which includes the whole multirange',
+  proname => 'range_merge', prorettype => 'anyrange',
+  proargtypes => 'anymultirange', prosrc => 'range_merge_from_multirange' },
 { oid => '3868',
   proname => 'range_intersect', prorettype => 'anyrange',
   proargtypes => 'anyrange anyrange', prosrc => 'range_intersect' },
@@ -9767,6 +9771,13 @@
 { oid => '3169', descr => 'restriction selectivity for range operators',
   proname => 'rangesel', provolatile => 's', prorettype => 'float8',
   proargtypes => 'internal oid internal int4', prosrc => 'rangesel' },
+{ oid => '8133', descr => 'range aggregate by intersecting',
+  proname => 'range_intersect_agg_transfn', prorettype => 'anyrange',
+  proargtypes => 'anyrange anyrange', prosrc => 'range_intersect_agg_transfn'},
+{ oid => '8134', descr => 'range aggregate by intersecting',
+  proname => 'range_intersect_agg', prokind => 'a', proisstrict => 'f',
+  prorettype => 'anyrange', proargtypes => 'anyrange',
+  prosrc => 'aggregate_dummy'},
 
 { oid => '3914', descr => 'convert an int4 range to canonical form',
   proname => 'int4range_canonical', prorettype => 'int4range',
@@ -9835,6 +9846,258 @@
   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 => '8094', descr => 'lower bound of multirange',
+  proname => 'lower', prorettype => 'anyelement', proargtypes => 'anymultirange',
+  prosrc => 'multirange_lower' },
+{ oid => '8095', descr => 'upper bound of multirange',
+  proname => 'upper', prorettype => 'anyelement', proargtypes => 'anymultirange',
+  prosrc => 'multirange_upper' },
+{ oid => '8057', descr => 'is the multirange empty?',
+  proname => 'isempty', prorettype => 'bool',
+  proargtypes => 'anymultirange', prosrc => 'multirange_empty' },
+{ oid => '8096', descr => 'is the multirange\'s lower bound inclusive?',
+  proname => 'lower_inc', prorettype => 'bool', proargtypes => 'anymultirange',
+  prosrc => 'multirange_lower_inc' },
+{ oid => '8097', descr => 'is the multirange\'s upper bound inclusive?',
+  proname => 'upper_inc', prorettype => 'bool', proargtypes => 'anymultirange',
+  prosrc => 'multirange_upper_inc' },
+{ oid => '8098', descr => 'is the multirange\'s lower bound infinite?',
+  proname => 'lower_inf', prorettype => 'bool', proargtypes => 'anymultirange',
+  prosrc => 'multirange_lower_inf' },
+{ oid => '8099', descr => 'is the multirange\'s upper bound infinite?',
+  proname => 'upper_inf', prorettype => 'bool', proargtypes => 'anymultirange',
+  prosrc => 'multirange_upper_inf' },
+{ oid => '8008', descr => 'multirange typanalyze',
+  proname => 'multirange_typanalyze', provolatile => 's', prorettype => 'bool',
+  proargtypes => 'internal', prosrc => 'multirange_typanalyze' },
+{ oid => '8033',
+  proname => 'multirange_eq', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_eq' },
+{ oid => '8034',
+  proname => 'multirange_ne', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_ne' },
+{ oid => '8070',
+  proname => 'range_overlaps_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_overlaps_multirange' },
+{ oid => '8071',
+  proname => 'multirange_overlaps_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_overlaps_range' },
+{ oid => '8072',
+  proname => 'multirange_overlaps_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_overlaps_multirange' },
+{ oid => '8058',
+  proname => 'multirange_contains_elem', prorettype => 'bool',
+  proargtypes => 'anymultirange anyelement', prosrc => 'multirange_contains_elem' },
+{ oid => '8059',
+  proname => 'multirange_contains_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_contains_range' },
+{ oid => '8060',
+  proname => 'multirange_contains_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_contains_multirange' },
+{ oid => '8061',
+  proname => 'elem_contained_by_multirange', prorettype => 'bool',
+  proargtypes => 'anyelement anymultirange', prosrc => 'elem_contained_by_multirange' },
+{ oid => '8062',
+  proname => 'range_contained_by_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_contained_by_multirange' },
+{ oid => '8063',
+  proname => 'multirange_contained_by_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_contained_by_multirange' },
+{ oid => '8088',
+  proname => 'range_adjacent_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_adjacent_multirange' },
+{ oid => '8089',
+  proname => 'multirange_adjacent_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_adjacent_multirange' },
+{ oid => '8090',
+  proname => 'multirange_adjacent_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_adjacent_range' },
+{ oid => '8076',
+  proname => 'range_before_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_before_multirange' },
+{ oid => '8077',
+  proname => 'multirange_before_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_before_range' },
+{ oid => '8078',
+  proname => 'multirange_before_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_before_multirange' },
+{ oid => '8079',
+  proname => 'range_after_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_after_multirange' },
+{ oid => '8080',
+  proname => 'multirange_after_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_after_range' },
+{ oid => '8081',
+  proname => 'multirange_after_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_after_multirange' },
+{ oid => '8100',
+  proname => 'range_overleft_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_overleft_multirange' },
+{ oid => '8101',
+  proname => 'multirange_overleft_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_overleft_range' },
+{ oid => '8102',
+  proname => 'multirange_overleft_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_overleft_multirange' },
+{ oid => '8103',
+  proname => 'range_overright_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_overright_multirange' },
+{ oid => '8104',
+  proname => 'multirange_overright_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_overright_range' },
+{ oid => '8105',
+  proname => 'multirange_overright_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_overright_multirange' },
+{ oid => '8114',
+  proname => 'multirange_union', prorettype => 'anymultirange',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_union' },
+{ oid => '8120',
+  proname => 'multirange_minus', prorettype => 'anymultirange',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_minus' },
+{ oid => '8126',
+  proname => 'multirange_intersect', prorettype => 'anymultirange',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_intersect' },
+{ oid => '8022', descr => 'less-equal-greater',
+  proname => 'multirange_cmp', prorettype => 'int4',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_cmp' },
+{ oid => '8023',
+  proname => 'multirange_lt', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_lt' },
+{ oid => '8024',
+  proname => 'multirange_le', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_le' },
+{ oid => '8025',
+  proname => 'multirange_ge', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_ge' },
+{ oid => '8026',
+  proname => 'multirange_gt', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_gt' },
+{ oid => '8038', descr => 'hash a multirange',
+  proname => 'hash_multirange', prorettype => 'int4', proargtypes => 'anymultirange',
+  prosrc => 'hash_multirange' },
+{ oid => '8039', descr => 'hash a multirange',
+  proname => 'hash_multirange_extended', prorettype => 'int8',
+  proargtypes => 'anymultirange int8', prosrc => 'hash_multirange_extended' },
+
+{ oid => '8045', descr => 'int4multirange constructor',
+  proname => 'int4multirange', proisstrict => 'f',
+  prorettype => 'int4multirange', proargtypes => '',
+  prosrc => 'multirange_constructor0' },
+{ oid => '8146', descr => 'int4multirange constructor',
+  proname => 'int4multirange', proisstrict => 't',
+  prorettype => 'int4multirange', proargtypes => 'int4range',
+  prosrc => 'multirange_constructor1' },
+{ oid => '8046', descr => 'int4multirange constructor',
+  proname => 'int4multirange', provariadic => 'int4range', proisstrict => 'f',
+  prorettype => 'int4multirange', proargtypes => '_int4range',
+  proallargtypes => '{_int4range}', proargmodes => '{v}',
+  prosrc => 'multirange_constructor2' },
+{ oid => '8047', descr => 'nummultirange constructor',
+  proname => 'nummultirange', proisstrict => 'f',
+  prorettype => 'nummultirange', proargtypes => '',
+  prosrc => 'multirange_constructor0' },
+{ oid => '8147', descr => 'nummultirange constructor',
+  proname => 'nummultirange', proisstrict => 't',
+  prorettype => 'nummultirange', proargtypes => 'numrange',
+  prosrc => 'multirange_constructor1' },
+{ oid => '8048', descr => 'nummultirange constructor',
+  proname => 'nummultirange', provariadic => 'numrange', proisstrict => 'f',
+  prorettype => 'nummultirange', proargtypes => '_numrange',
+  proallargtypes => '{_numrange}', proargmodes => '{v}',
+  prosrc => 'multirange_constructor2' },
+{ oid => '8049', descr => 'tsmultirange constructor',
+  proname => 'tsmultirange', proisstrict => 'f',
+  prorettype => 'tsmultirange', proargtypes => '',
+  prosrc => 'multirange_constructor0' },
+{ oid => '8148', descr => 'tsmultirange constructor',
+  proname => 'tsmultirange', proisstrict => 't',
+  prorettype => 'tsmultirange', proargtypes => 'tsrange',
+  prosrc => 'multirange_constructor1' },
+{ oid => '8050', descr => 'tsmultirange constructor',
+  proname => 'tsmultirange', provariadic => 'tsrange', proisstrict => 'f',
+  prorettype => 'tsmultirange', proargtypes => '_tsrange',
+  proallargtypes => '{_tsrange}', proargmodes => '{v}',
+  prosrc => 'multirange_constructor2' },
+{ oid => '8051', descr => 'tstzmultirange constructor',
+  proname => 'tstzmultirange', proisstrict => 'f',
+  prorettype => 'tstzmultirange', proargtypes => '',
+  prosrc => 'multirange_constructor0' },
+{ oid => '8149', descr => 'tstzmultirange constructor',
+  proname => 'tstzmultirange', proisstrict => 't',
+  prorettype => 'tstzmultirange', proargtypes => 'tstzrange',
+  prosrc => 'multirange_constructor1' },
+{ oid => '8052', descr => 'tstzmultirange constructor',
+  proname => 'tstzmultirange', provariadic => 'tstzrange', proisstrict => 'f',
+  prorettype => 'tstzmultirange', proargtypes => '_tstzrange',
+  proallargtypes => '{_tstzrange}', proargmodes => '{v}',
+  prosrc => 'multirange_constructor2' },
+{ oid => '8053', descr => 'datemultirange constructor',
+  proname => 'datemultirange', proisstrict => 'f',
+  prorettype => 'datemultirange', proargtypes => '',
+  prosrc => 'multirange_constructor0' },
+{ oid => '8150', descr => 'datemultirange constructor',
+  proname => 'datemultirange', proisstrict => 't',
+  prorettype => 'datemultirange', proargtypes => 'daterange',
+  prosrc => 'multirange_constructor1' },
+{ oid => '8054', descr => 'datemultirange constructor',
+  proname => 'datemultirange', provariadic => 'daterange', proisstrict => 'f',
+  prorettype => 'datemultirange', proargtypes => '_daterange',
+  proallargtypes => '{_daterange}', proargmodes => '{v}',
+  prosrc => 'multirange_constructor2' },
+{ oid => '8055', descr => 'int8multirange constructor',
+  proname => 'int8multirange', proisstrict => 'f',
+  prorettype => 'int8multirange', proargtypes => '',
+  prosrc => 'multirange_constructor0' },
+{ oid => '8151', descr => 'int8multirange constructor',
+  proname => 'int8multirange', proisstrict => 't',
+  prorettype => 'int8multirange', proargtypes => 'int8range',
+  prosrc => 'multirange_constructor1' },
+{ oid => '8056', descr => 'int8multirange constructor',
+  proname => 'int8multirange', provariadic => 'int8range', proisstrict => 'f',
+  prorettype => 'int8multirange', proargtypes => '_int8range',
+  proallargtypes => '{_int8range}', proargmodes => '{v}',
+  prosrc => 'multirange_constructor2' },
+{ oid => '8152', descr => 'anymultirange cast',
+  proname => 'multirange', proisstrict => 't',
+  prorettype => 'anymultirange', proargtypes => 'anyrange',
+  prosrc => 'multirange_constructor1' },
+{ oid => '8130', descr => 'aggregate transition function',
+  proname => 'range_agg_transfn', proisstrict => 'f', prorettype => 'internal',
+  proargtypes => 'internal anyrange', prosrc => 'range_agg_transfn' },
+{ oid => '8131', descr => 'aggregate final function',
+  proname => 'range_agg_finalfn', proisstrict => 'f', prorettype => 'anymultirange',
+  proargtypes => 'internal anyrange', prosrc => 'range_agg_finalfn' },
+{ oid => '8132', descr => 'combine aggregate input into a multirange',
+  proname => 'range_agg', prokind => 'a', proisstrict => 'f',
+  prorettype => 'anymultirange', proargtypes => 'anyrange',
+  prosrc => 'aggregate_dummy' },
+{ oid => '8135', descr => 'range aggregate by intersecting',
+  proname => 'multirange_intersect_agg_transfn', prorettype => 'anymultirange',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_intersect_agg_transfn'},
+{ oid => '8136', descr => 'range aggregate by intersecting',
+  proname => 'range_intersect_agg', prokind => 'a', proisstrict => 'f',
+  prorettype => 'anymultirange', proargtypes => 'anymultirange',
+  prosrc => 'aggregate_dummy'},
+
 # date, time, timestamp constructors
 { oid => '3846', descr => 'construct date',
   proname => 'make_date', prorettype => 'date', proargtypes => 'int4 int4 int4',
@@ -10217,6 +10480,14 @@
   proname => 'binary_upgrade_set_next_array_pg_type_oid', provolatile => 'v',
   proparallel => 'r', prorettype => 'void', proargtypes => 'oid',
   prosrc => 'binary_upgrade_set_next_array_pg_type_oid' },
+{ oid => '8144', descr => 'for use by pg_upgrade',
+  proname => 'binary_upgrade_set_next_multirange_pg_type_oid', provolatile => 'v',
+  proparallel => 'r', prorettype => 'void', proargtypes => 'oid',
+  prosrc => 'binary_upgrade_set_next_multirange_pg_type_oid' },
+{ oid => '8145', descr => 'for use by pg_upgrade',
+  proname => 'binary_upgrade_set_next_multirange_array_pg_type_oid', provolatile => 'v',
+  proparallel => 'r', prorettype => 'void', proargtypes => 'oid',
+  prosrc => 'binary_upgrade_set_next_multirange_array_pg_type_oid' },
 { oid => '3585', descr => 'for use by pg_upgrade',
   proname => 'binary_upgrade_set_next_toast_pg_type_oid', provolatile => 'v',
   proparallel => 'r', prorettype => 'void', proargtypes => 'oid',
diff --git a/src/include/catalog/pg_range.dat b/src/include/catalog/pg_range.dat
index 479754c245..10060255c9 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',
+  rngmultitypid => 'int4multirange' },
 { rngtypid => 'numrange', rngsubtype => 'numeric',
   rngsubopc => 'btree/numeric_ops', rngcanonical => '-',
-  rngsubdiff => 'numrange_subdiff' },
+  rngsubdiff => 'numrange_subdiff', rngmultitypid => 'nummultirange' },
 { rngtypid => 'tsrange', rngsubtype => 'timestamp',
   rngsubopc => 'btree/timestamp_ops', rngcanonical => '-',
-  rngsubdiff => 'tsrange_subdiff' },
+  rngsubdiff => 'tsrange_subdiff', rngmultitypid => 'tsmultirange' },
 { rngtypid => 'tstzrange', rngsubtype => 'timestamptz',
   rngsubopc => 'btree/timestamptz_ops', rngcanonical => '-',
-  rngsubdiff => 'tstzrange_subdiff' },
+  rngsubdiff => 'tstzrange_subdiff', rngmultitypid => 'tstzmultirange' },
 { rngtypid => 'daterange', rngsubtype => 'date', rngsubopc => 'btree/date_ops',
-  rngcanonical => 'daterange_canonical', rngsubdiff => 'daterange_subdiff' },
+  rngcanonical => 'daterange_canonical', rngsubdiff => 'daterange_subdiff',
+  rngmultitypid => 'datemultirange' },
 { rngtypid => 'int8range', rngsubtype => 'int8', rngsubopc => 'btree/int8_ops',
-  rngcanonical => 'int8range_canonical', rngsubdiff => 'int8range_subdiff' },
+  rngcanonical => 'int8range_canonical', rngsubdiff => 'int8range_subdiff',
+  rngmultitypid => 'int8multirange' },
 
 ]
diff --git a/src/include/catalog/pg_range.h b/src/include/catalog/pg_range.h
index 98172bb1b6..60a8fde518 100644
--- a/src/include/catalog/pg_range.h
+++ b/src/include/catalog/pg_range.h
@@ -34,6 +34,9 @@ CATALOG(pg_range,3541,RangeRelationId)
 	/* OID of range's element type (subtype) */
 	Oid			rngsubtype BKI_LOOKUP(pg_type);
 
+	/* OID of the range's multirange type */
+	Oid			rngmultitypid BKI_LOOKUP(pg_type);
+
 	/* collation for this range type, or 0 */
 	Oid			rngcollation BKI_DEFAULT(0);
 
@@ -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 2e6110e3f2..cb3d159bf1 100644
--- a/src/include/catalog/pg_type.dat
+++ b/src/include/catalog/pg_type.dat
@@ -491,6 +491,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 => 'R', 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 => 'R', 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 => 'R', 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 => 'R', 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 => 'R', 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 => 'R', 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
@@ -620,5 +654,10 @@
   typtype => 'p', typcategory => 'P', typinput => 'anycompatiblerange_in',
   typoutput => 'anycompatiblerange_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 7b37562648..f1818d9a52 100644
--- a/src/include/catalog/pg_type.h
+++ b/src/include/catalog/pg_type.h
@@ -263,6 +263,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 */
 
@@ -304,7 +305,8 @@ typedef FormData_pg_type *Form_pg_type;
 	 (typid) == ANYARRAYOID || \
 	 (typid) == ANYNONARRAYOID || \
 	 (typid) == ANYENUMOID || \
-	 (typid) == ANYRANGEOID)
+	 (typid) == ANYRANGEOID || \
+	 (typid) == ANYMULTIRANGEOID)
 
 #define IsPolymorphicTypeFamily2(typid)  \
 	((typid) == ANYCOMPATIBLEOID || \
@@ -369,4 +371,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/commands/typecmds.h b/src/include/commands/typecmds.h
index 23130895af..989914dfe1 100644
--- a/src/include/commands/typecmds.h
+++ b/src/include/commands/typecmds.h
@@ -29,6 +29,8 @@ extern ObjectAddress DefineRange(CreateRangeStmt *stmt);
 extern ObjectAddress AlterEnum(AlterEnumStmt *stmt);
 extern ObjectAddress DefineCompositeType(RangeVar *typevar, List *coldeflist);
 extern Oid	AssignTypeArrayOid(void);
+extern Oid	AssignTypeMultirangeOid(void);
+extern Oid	AssignTypeMultirangeArrayOid(void);
 
 extern ObjectAddress AlterDomainDefault(List *names, Node *defaultRaw);
 extern ObjectAddress AlterDomainNotNull(List *names, bool notNull);
diff --git a/src/include/utils/lsyscache.h b/src/include/utils/lsyscache.h
index 374f57fb43..f2e73eabb0 100644
--- a/src/include/utils/lsyscache.h
+++ b/src/include/utils/lsyscache.h
@@ -156,6 +156,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);
@@ -182,6 +183,8 @@ 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_collation(Oid rangeOid);
+extern Oid	get_range_multirange(Oid rangeOid);
+extern Oid	get_multirange_range(Oid multirangeOid);
 extern Oid	get_index_column_opclass(Oid index_oid, int attno);
 extern bool	get_index_isreplident(Oid index_oid);
 extern bool get_index_isvalid(Oid index_oid);
diff --git a/src/include/utils/multirangetypes.h b/src/include/utils/multirangetypes.h
new file mode 100644
index 0000000000..b270eb7f51
--- /dev/null
+++ b/src/include/utils/multirangetypes.h
@@ -0,0 +1,103 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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
+{
+	int32		vl_len_;		/* varlena header (do not touch directly!) */
+	Oid			multirangetypid;	/* multirange type's own OID */
+	uint32		rangeCount;		/* the number of ranges */
+
+	/*
+	 * Following the count 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);
+extern bool multirange_contains_elem_internal(TypeCacheEntry *typcache, MultirangeType *mr,
+											  Datum elem);
+extern bool multirange_contains_range_internal(TypeCacheEntry *typcache, MultirangeType *mr,
+											   RangeType *r);
+extern bool multirange_contains_multirange_internal(TypeCacheEntry *typcache,
+													MultirangeType *mr1,
+													MultirangeType *mr2);
+extern bool range_overlaps_multirange_internal(TypeCacheEntry *typcache, RangeType *r,
+											   MultirangeType *mr);
+extern bool multirange_overlaps_multirange_internal(TypeCacheEntry *typcache,
+													MultirangeType *mr1,
+													MultirangeType *mr2);
+extern bool range_before_multirange_internal(TypeCacheEntry *typcache, RangeType *r,
+											 MultirangeType *mr);
+extern bool range_after_multirange_internal(TypeCacheEntry *typcache, RangeType *r,
+											MultirangeType *mr);
+extern bool multirange_before_multirange_internal(TypeCacheEntry *typcache,
+												  MultirangeType *mr1,
+												  MultirangeType *mr2);
+extern MultirangeType *multirange_minus_internal(Oid mltrngtypoid,
+												 TypeCacheEntry *rangetyp,
+												 int32 range_count1,
+												 RangeType **ranges1,
+												 int32 range_count2,
+												 RangeType **ranges2);
+extern MultirangeType *multirange_intersect_internal(Oid mltrngtypoid,
+													 TypeCacheEntry *rangetyp,
+													 int32 range_count1,
+													 RangeType **ranges1,
+													 int32 range_count2,
+													 RangeType **ranges2);
+
+/* 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 0cbbf09033..204aee4054 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)
 
@@ -92,13 +94,21 @@ typedef struct
  * prototypes for functions defined in rangetypes.c
  */
 
-extern bool range_contains_elem_internal(TypeCacheEntry *typcache, const RangeType *r, Datum val);
-
 /* internal versions of the above */
+extern int	range_cmp_internal(TypeCacheEntry *typcache, const RangeType *r1,
+							   const RangeType *r2);
+extern uint32 hash_range_internal(TypeCacheEntry *typcache, const RangeType *r);
+extern uint64 hash_range_extended_internal(TypeCacheEntry *typcache, const RangeType *r,
+										   Datum seed);
+extern Datum range_lower_internal(TypeCacheEntry *typcache, const RangeType *r1,
+								  bool *isnull);
+extern Datum range_upper_internal(TypeCacheEntry *typcache, const RangeType *r1,
+								  bool *isnull);
 extern bool range_eq_internal(TypeCacheEntry *typcache, const RangeType *r1,
 							  const RangeType *r2);
 extern bool range_ne_internal(TypeCacheEntry *typcache, const RangeType *r1,
 							  const RangeType *r2);
+extern bool range_contains_elem_internal(TypeCacheEntry *typcache, const RangeType *r, Datum val);
 extern bool range_contains_internal(TypeCacheEntry *typcache, const RangeType *r1,
 									const RangeType *r2);
 extern bool range_contained_by_internal(TypeCacheEntry *typcache, const RangeType *r1,
@@ -115,6 +125,12 @@ extern bool range_overleft_internal(TypeCacheEntry *typcache, const RangeType *r
 									const RangeType *r2);
 extern bool range_overright_internal(TypeCacheEntry *typcache, const RangeType *r1,
 									 const RangeType *r2);
+extern RangeType *range_union_internal(TypeCacheEntry *typcache, RangeType *r1,
+									   RangeType *r2, bool strict);
+extern RangeType *range_minus_internal(TypeCacheEntry *typcache, RangeType *r1,
+									   RangeType *r2);
+extern RangeType *range_intersect_internal(TypeCacheEntry *typcache, const RangeType *r1,
+										   const RangeType *r2);
 
 /* assorted support functions */
 extern TypeCacheEntry *range_get_typcache(FunctionCallInfo fcinfo,
@@ -125,6 +141,7 @@ extern void range_deserialize(TypeCacheEntry *typcache, const RangeType *range,
 							  RangeBound *lower, RangeBound *upper,
 							  bool *empty);
 extern char range_get_flags(const RangeType *range);
+extern bool range_has_flag(const RangeType *range, char flag);
 extern void range_set_contain_empty(RangeType *range);
 extern RangeType *make_range(TypeCacheEntry *typcache, RangeBound *lower,
 							 RangeBound *upper, bool empty);
@@ -132,8 +149,12 @@ extern int range_cmp_bounds(TypeCacheEntry *typcache, const RangeBound *b1,
 							const RangeBound *b2);
 extern int range_cmp_bound_values(TypeCacheEntry *typcache, const RangeBound *b1,
 								  const RangeBound *b2);
-extern bool bounds_adjacent(TypeCacheEntry *typcache, RangeBound bound1,
-							RangeBound bound2);
+extern int range_compare(const void *key1, const void *key2, void *arg);
+extern bool bounds_adjacent(TypeCacheEntry *typcache, const RangeBound bound1,
+							const RangeBound bound2);
 extern RangeType *make_empty_range(TypeCacheEntry *typcache);
+extern bool range_split_internal(TypeCacheEntry *typcache, const RangeType *r1,
+								 const RangeType *r2, RangeType **output1,
+								 RangeType **output2);
 
 #endif							/* RANGETYPES_H */
diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h
index f27b73d76d..e8f393520a 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -79,6 +79,7 @@ enum SysCacheIdentifier
 	PUBLICATIONOID,
 	PUBLICATIONREL,
 	PUBLICATIONRELMAP,
+	RANGEMULTIRANGE,
 	RANGETYPE,
 	RELNAMENSP,
 	RELOID,
diff --git a/src/include/utils/typcache.h b/src/include/utils/typcache.h
index cdd20e56d7..33f332a141 100644
--- a/src/include/utils/typcache.h
+++ b/src/include/utils/typcache.h
@@ -100,6 +100,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;	/* multirange's range underlying type */
+
 	/*
 	 * Domain's base type and typmod if it's a domain type.  Zeroes if not
 	 * domain, or if information hasn't been requested.
@@ -143,6 +148,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 828ff5a288..1e3ff02289 100644
--- a/src/pl/plpgsql/src/pl_comp.c
+++ b/src/pl/plpgsql/src/pl_comp.c
@@ -513,6 +513,8 @@ do_compile(FunctionCallInfo fcinfo,
 					else if (rettypeid == ANYRANGEOID ||
 							 rettypeid == ANYCOMPATIBLERANGEOID)
 						rettypeid = INT4RANGEOID;
+					else if (rettypeid == ANYMULTIRANGEOID)
+						rettypeid = INT4MULTIRANGEOID;
 					else		/* ANYELEMENT or ANYNONARRAY or ANYCOMPATIBLE */
 						rettypeid = INT4OID;
 					/* XXX what could we use for ANYENUM? */
@@ -2089,6 +2091,7 @@ build_datatype(HeapTuple typeTup, int32 typmod,
 		case TYPTYPE_BASE:
 		case TYPTYPE_ENUM:
 		case TYPTYPE_RANGE:
+		case TYPTYPE_MULTIRANGE:
 			typ->ttype = PLPGSQL_TTYPE_SCALAR;
 			break;
 		case TYPTYPE_COMPOSITE:
@@ -2507,6 +2510,9 @@ plpgsql_resolve_polymorphic_argtypes(int numargs,
 				case ANYCOMPATIBLERANGEOID:
 					argtypes[i] = INT4RANGEOID;
 					break;
+				case ANYMULTIRANGEOID:
+					argtypes[i] = INT4MULTIRANGEOID;
+					break;
 				default:
 					break;
 			}
diff --git a/src/test/regress/expected/dependency.out b/src/test/regress/expected/dependency.out
index b4a11b8aa9..8232795148 100644
--- a/src/test/regress/expected/dependency.out
+++ b/src/test/regress/expected/dependency.out
@@ -140,6 +140,7 @@ owner of sequence deptest_a_seq
 owner of table deptest
 owner of function deptest_func()
 owner of type deptest_enum
+owner of type deptest_multirange
 owner of type deptest_range
 owner of table deptest2
 owner of sequence ss1
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..23a06b301f
--- /dev/null
+++ b/src/test/regress/expected/multirangetypes.out
@@ -0,0 +1,2395 @@
+-- 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)
+
+--
+-- test casts, both a built-in range type and a user-defined one:
+--
+select 'empty'::int4range::int4multirange;
+ int4multirange 
+----------------
+ {}
+(1 row)
+
+select int4range(1, 3)::int4multirange;
+ int4range 
+-----------
+ {[1,3)}
+(1 row)
+
+select int4range(1, null)::int4multirange;
+ int4range 
+-----------
+ {[1,)}
+(1 row)
+
+select int4range(null, null)::int4multirange;
+ int4range 
+-----------
+ {(,)}
+(1 row)
+
+select 'empty'::textrange::textmultirange;
+ textmultirange 
+----------------
+ {}
+(1 row)
+
+select textrange('a', 'c')::textmultirange;
+ textrange 
+-----------
+ {[a,c)}
+(1 row)
+
+select textrange('a', null)::textmultirange;
+ textrange 
+-----------
+ {[a,)}
+(1 row)
+
+select textrange(null, null)::textmultirange;
+ textrange 
+-----------
+ {(,)}
+(1 row)
+
+--
+-- create some test data and test the operators
+--
+CREATE TABLE nummultirange_test (nmr NUMMULTIRANGE);
+CREATE INDEX nummultirange_test_btree ON nummultirange_test(nmr);
+INSERT INTO nummultirange_test VALUES('{}');
+INSERT INTO nummultirange_test VALUES('{[,)}');
+INSERT INTO nummultirange_test VALUES('{[3,]}');
+INSERT INTO nummultirange_test VALUES('{[,), [3,]}');
+INSERT INTO nummultirange_test VALUES('{[, 5)}');
+INSERT INTO nummultirange_test VALUES(nummultirange());
+INSERT INTO nummultirange_test VALUES(nummultirange(variadic '{}'::numrange[]));
+INSERT INTO nummultirange_test VALUES(nummultirange(numrange(1.1, 2.2)));
+INSERT INTO nummultirange_test VALUES('{empty}');
+INSERT INTO nummultirange_test VALUES(nummultirange(numrange(1.7, 1.7, '[]'), numrange(1.7, 1.9)));
+INSERT INTO nummultirange_test VALUES(nummultirange(numrange(1.7, 1.7, '[]'), numrange(1.9, 2.1)));
+SELECT nmr, isempty(nmr), lower(nmr), upper(nmr) FROM nummultirange_test ORDER BY nmr;
+          nmr          | isempty | lower | upper 
+-----------------------+---------+-------+-------
+ {}                    | t       |       |      
+ {}                    | t       |       |      
+ {}                    | t       |       |      
+ {}                    | t       |       |      
+ {(,5)}                | f       |       |     5
+ {(,)}                 | f       |       |      
+ {(,)}                 | f       |       |      
+ {[1.1,2.2)}           | f       |   1.1 |   2.2
+ {[1.7,1.7],[1.9,2.1)} | f       |   1.7 |   2.1
+ {[1.7,1.9)}           | f       |   1.7 |   1.9
+ {[3,)}                | f       |     3 |      
+(11 rows)
+
+SELECT nmr, lower_inc(nmr), lower_inf(nmr), upper_inc(nmr), upper_inf(nmr) FROM nummultirange_test ORDER BY nmr;
+          nmr          | lower_inc | lower_inf | upper_inc | upper_inf 
+-----------------------+-----------+-----------+-----------+-----------
+ {}                    | f         | f         | f         | f
+ {}                    | f         | f         | f         | f
+ {}                    | f         | f         | f         | f
+ {}                    | f         | f         | f         | f
+ {(,5)}                | f         | t         | f         | f
+ {(,)}                 | f         | t         | f         | t
+ {(,)}                 | f         | t         | f         | t
+ {[1.1,2.2)}           | t         | f         | f         | f
+ {[1.7,1.7],[1.9,2.1)} | t         | f         | f         | f
+ {[1.7,1.9)}           | t         | f         | f         | f
+ {[3,)}                | t         | f         | f         | t
+(11 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr = '{}';
+ nmr 
+-----
+ {}
+ {}
+ {}
+ {}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr = '{(,5)}';
+  nmr   
+--------
+ {(,5)}
+(1 row)
+
+SELECT * FROM nummultirange_test WHERE nmr = '{[3,)}';
+  nmr   
+--------
+ {[3,)}
+(1 row)
+
+SELECT * FROM nummultirange_test WHERE nmr = '{[1.7,1.7]}';
+ nmr 
+-----
+(0 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr = '{[1.7,1.7],[1.9,2.1)}';
+          nmr          
+-----------------------
+ {[1.7,1.7],[1.9,2.1)}
+(1 row)
+
+SELECT * FROM nummultirange_test WHERE nmr < '{}';
+ nmr 
+-----
+(0 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr < '{[-1000.0, -1000.0]}';
+  nmr   
+--------
+ {}
+ {(,)}
+ {(,)}
+ {(,5)}
+ {}
+ {}
+ {}
+(7 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr < '{[0.0, 1.0]}';
+  nmr   
+--------
+ {}
+ {(,)}
+ {(,)}
+ {(,5)}
+ {}
+ {}
+ {}
+(7 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr < '{[1000.0, 1001.0]}';
+          nmr          
+-----------------------
+ {}
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+ {}
+ {}
+ {[1.1,2.2)}
+ {}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(11 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr <= '{}';
+ nmr 
+-----
+ {}
+ {}
+ {}
+ {}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr <= '{[3,)}';
+          nmr          
+-----------------------
+ {}
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+ {}
+ {}
+ {[1.1,2.2)}
+ {}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(11 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr >= '{}';
+          nmr          
+-----------------------
+ {}
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+ {}
+ {}
+ {[1.1,2.2)}
+ {}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(11 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr >= '{[3,)}';
+  nmr   
+--------
+ {[3,)}
+(1 row)
+
+SELECT * FROM nummultirange_test WHERE nmr > '{}';
+          nmr          
+-----------------------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+ {[1.1,2.2)}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(7 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr > '{[-1000.0, -1000.0]}';
+          nmr          
+-----------------------
+ {[3,)}
+ {[1.1,2.2)}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr > '{[0.0, 1.0]}';
+          nmr          
+-----------------------
+ {[3,)}
+ {[1.1,2.2)}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr > '{[1000.0, 1001.0]}';
+ nmr 
+-----
+(0 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr <> '{}';
+          nmr          
+-----------------------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+ {[1.1,2.2)}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(7 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr <> '{(,5)}';
+          nmr          
+-----------------------
+ {}
+ {(,)}
+ {[3,)}
+ {(,)}
+ {}
+ {}
+ {[1.1,2.2)}
+ {}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(10 rows)
+
+select nummultirange(numrange(2.0, 1.0));
+ERROR:  range lower bound must be less than or equal to range upper bound
+select nummultirange(numrange(5.0, 6.0), numrange(1.0, 2.0));
+     nummultirange     
+-----------------------
+ {[1.0,2.0),[5.0,6.0)}
+(1 row)
+
+-- overlaps
+SELECT * FROM nummultirange_test WHERE range_overlaps_multirange(numrange(4.0, 4.2), nmr);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE numrange(4.0, 4.2) && nmr;
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE multirange_overlaps_range(nmr, numrange(4.0, 4.2));
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr && numrange(4.0, 4.2);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE multirange_overlaps_multirange(nmr, nummultirange(numrange(4.0, 4.2), numrange(6.0, 7.0)));
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr && nummultirange(numrange(4.0, 4.2), numrange(6.0, 7.0));
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr && nummultirange(numrange(6.0, 7.0));
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+(3 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr && nummultirange(numrange(6.0, 7.0), numrange(8.0, 9.0));
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+(3 rows)
+
+-- mr contains x
+SELECT * FROM nummultirange_test WHERE multirange_contains_elem(nmr, 4.0);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr @> 4.0;
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE multirange_contains_range(nmr, numrange(4.0, 4.2));
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr @> numrange(4.0, 4.2);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE multirange_contains_multirange(nmr, '{[4.0,4.2), [6.0, 8.0)}');
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+(3 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr @> '{[4.0,4.2), [6.0, 8.0)}'::nummultirange;
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+(3 rows)
+
+-- x is contained by mr
+SELECT * FROM nummultirange_test WHERE elem_contained_by_multirange(4.0, nmr);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE 4.0 <@ nmr;
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE range_contained_by_multirange(numrange(4.0, 4.2), nmr);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE numrange(4.0, 4.2) <@ nmr;
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE multirange_contained_by_multirange('{[4.0,4.2), [6.0, 8.0)}', nmr);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+(3 rows)
+
+SELECT * FROM nummultirange_test WHERE '{[4.0,4.2), [6.0, 8.0)}'::nummultirange <@ nmr;
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+(3 rows)
+
+-- overlaps
+SELECT 'empty'::numrange && nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT 'empty'::numrange && nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() && 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) && 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() && nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() && nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) && nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) && nummultirange(numrange(1,2), numrange(7,8));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(7,8)) && nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) && nummultirange(numrange(1,2), numrange(3.5,8));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(3.5,8)) && numrange(3,4);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(3.5,8)) && nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+-- contains
+SELECT nummultirange() @> nummultirange();
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange() @> 'empty'::numrange;
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(null,null)) @> numrange(1,2);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(null,null)) @> numrange(null,2);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(null,null)) @> numrange(2,null);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(null,5)) @> numrange(null,3);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(null,5)) @> numrange(null,8);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(5,null)) @> numrange(8,null);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(5,null)) @> numrange(3,null);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,5)) @> numrange(8,9);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,5)) @> numrange(3,9);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,5)) @> numrange(1,4);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,5)) @> numrange(1,5);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(-4,-2), numrange(1,5)) @> numrange(1,5);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,5), numrange(8,9)) @> numrange(1,5);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,5), numrange(8,9)) @> numrange(6,7);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,5), numrange(6,9)) @> numrange(6,7);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[1,5)}'::nummultirange @> '{[1,5)}';
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[-4,-2), [1,5)}'::nummultirange @> '{[1,5)}';
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[1,5), [8,9)}'::nummultirange @> '{[1,5)}';
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[1,5), [8,9)}'::nummultirange @> '{[6,7)}';
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT '{[1,5), [6,9)}'::nummultirange @> '{[6,7)}';
+ ?column? 
+----------
+ t
+(1 row)
+
+-- is contained by
+SELECT nummultirange() <@ nummultirange();
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT 'empty'::numrange <@ nummultirange();
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,2) <@ nummultirange(numrange(null,null));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(null,2) <@ nummultirange(numrange(null,null));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(2,null) <@ nummultirange(numrange(null,null));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(null,3) <@ nummultirange(numrange(null,5));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(null,8) <@ nummultirange(numrange(null,5));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(8,null) <@ nummultirange(numrange(5,null));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(3,null) <@ nummultirange(numrange(5,null));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(8,9) <@ nummultirange(numrange(1,5));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(3,9) <@ nummultirange(numrange(1,5));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(1,4) <@ nummultirange(numrange(1,5));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,5) <@ nummultirange(numrange(1,5));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,5) <@ nummultirange(numrange(-4,-2), numrange(1,5));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,5) <@ nummultirange(numrange(1,5), numrange(8,9));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(6,7) <@ nummultirange(numrange(1,5), numrange(8,9));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(6,7) <@ nummultirange(numrange(1,5), numrange(6,9));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[1,5)}' <@ '{[1,5)}'::nummultirange;
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[1,5)}' <@ '{[-4,-2), [1,5)}'::nummultirange;
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[1,5)}' <@ '{[1,5), [8,9)}'::nummultirange;
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[6,7)}' <@ '{[1,5), [8,9)}'::nummultirange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT '{[6,7)}' <@ '{[1,5), [6,9)}'::nummultirange;
+ ?column? 
+----------
+ t
+(1 row)
+
+-- overleft
+SELECT 'empty'::numrange &< nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT 'empty'::numrange &< nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() &< 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) &< 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() &< nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) &< nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() &< nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(6,7) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(1,2) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,4) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,6) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(3.5,6) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(6,7)) &< numrange(3,4);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) &< numrange(3,4);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,4)) &< numrange(3,4);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,6)) &< numrange(3,4);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3.5,6)) &< numrange(3,4);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(6,7)) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,4)) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,6)) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3.5,6)) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+-- overright
+SELECT nummultirange() &> 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) &> 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT 'empty'::numrange &> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT 'empty'::numrange &> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() &> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() &> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) &> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> numrange(6,7);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> numrange(1,2);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> numrange(1,4);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> numrange(1,6);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> numrange(3.5,6);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(3,4) &> nummultirange(numrange(6,7));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(3,4) &> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(3,4) &> nummultirange(numrange(1,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(3,4) &> nummultirange(numrange(1,6));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(3,4) &> nummultirange(numrange(3.5,6));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(6,7));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(1,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(1,6));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(3.5,6));
+ ?column? 
+----------
+ f
+(1 row)
+
+-- meets
+SELECT 'empty'::numrange -|- nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT 'empty'::numrange -|- nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() -|- 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() -|- nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() -|- nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(1,2) -|- nummultirange(numrange(2,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,2) -|- nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- numrange(2,4);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- numrange(3,4);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- nummultirange(numrange(2,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(5,6)) -|- nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(5,6)) -|- nummultirange(numrange(6,7));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(5,6)) -|- nummultirange(numrange(8,9));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- nummultirange(numrange(2,4), numrange(6,7));
+ ?column? 
+----------
+ t
+(1 row)
+
+-- strictly left
+select 'empty'::numrange << nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(1,2) << nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(1,2) << nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+select numrange(1,2) << nummultirange(numrange(0,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(1,2) << nummultirange(numrange(0,4), numrange(7,8));
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange() << 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange() << numrange(1,2);
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(3,4)) << numrange(3,6);
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(0,2)) << numrange(3,6);
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange(numrange(0,2), numrange(7,8)) << numrange(3,6);
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(-4,-2), numrange(0,2)) << numrange(3,6);
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange() << nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange() << nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(1,2)) << nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(1,2)) << nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(1,2)) << nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange(numrange(1,2)) << nummultirange(numrange(3,4), numrange(7,8));
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange(numrange(1,2), numrange(4,5)) << nummultirange(numrange(3,4), numrange(7,8));
+ ?column? 
+----------
+ f
+(1 row)
+
+-- strictly right
+select nummultirange() >> 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange() >> numrange(1,2);
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(3,4)) >> numrange(1,2);
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange(numrange(0,4)) >> numrange(1,2);
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(0,4), numrange(7,8)) >> numrange(1,2);
+ ?column? 
+----------
+ f
+(1 row)
+
+select 'empty'::numrange >> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(1,2) >> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(3,6) >> nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(3,6) >> nummultirange(numrange(0,2));
+ ?column? 
+----------
+ t
+(1 row)
+
+select numrange(3,6) >> nummultirange(numrange(0,2), numrange(7,8));
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(3,6) >> nummultirange(numrange(-4,-2), numrange(0,2));
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange() >> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(1,2)) >> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange() >> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(1,2)) >> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(3,4)) >> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange(numrange(3,4), numrange(7,8)) >> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange(numrange(3,4), numrange(7,8)) >> nummultirange(numrange(1,2), numrange(4,5));
+ ?column? 
+----------
+ f
+(1 row)
+
+-- union
+SELECT nummultirange() + nummultirange();
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange() + nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) + nummultirange();
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) + nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) + nummultirange(numrange(2,4));
+ ?column? 
+----------
+ {[1,4)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) + nummultirange(numrange(3,4));
+   ?column?    
+---------------
+ {[1,2),[3,4)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) + nummultirange(numrange(2,4));
+ ?column? 
+----------
+ {[1,5)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) + nummultirange(numrange(3,4));
+   ?column?    
+---------------
+ {[1,2),[3,5)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) + nummultirange(numrange(0,9));
+ ?column? 
+----------
+ {[0,9)}
+(1 row)
+
+-- merge
+SELECT range_merge(nummultirange());
+ range_merge 
+-------------
+ empty
+(1 row)
+
+SELECT range_merge(nummultirange(numrange(1,2)));
+ range_merge 
+-------------
+ [1,2)
+(1 row)
+
+SELECT range_merge(nummultirange(numrange(1,2), numrange(7,8)));
+ range_merge 
+-------------
+ [1,8)
+(1 row)
+
+-- minus
+SELECT nummultirange() - nummultirange();
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange() - nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) - nummultirange();
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(3,4)) - nummultirange();
+   ?column?    
+---------------
+ {[1,2),[3,4)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) - nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) - nummultirange(numrange(2,4));
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) - nummultirange(numrange(3,4));
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {[2,4)}
+(1 row)
+
+SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(2,3));
+   ?column?    
+---------------
+ {[1,2),[3,4)}
+(1 row)
+
+SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(0,8));
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(0,2));
+ ?column? 
+----------
+ {[2,4)}
+(1 row)
+
+SELECT nummultirange(numrange(1,8)) - nummultirange(numrange(0,2), numrange(3,4));
+   ?column?    
+---------------
+ {[2,3),[4,8)}
+(1 row)
+
+SELECT nummultirange(numrange(1,8)) - nummultirange(numrange(2,3), numrange(5,null));
+   ?column?    
+---------------
+ {[1,2),[3,5)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(-2,0));
+   ?column?    
+---------------
+ {[1,2),[4,5)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(2,4));
+   ?column?    
+---------------
+ {[1,2),[4,5)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(3,5));
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(0,9));
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange(numrange(1,3), numrange(4,5)) - nummultirange(numrange(2,9));
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(8,9));
+   ?column?    
+---------------
+ {[1,2),[4,5)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(-2,0), numrange(8,9));
+   ?column?    
+---------------
+ {[1,2),[4,5)}
+(1 row)
+
+-- intersection
+SELECT nummultirange() * nummultirange();
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange() * nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) * nummultirange();
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT '{[1,3)}'::nummultirange * '{[1,5)}'::nummultirange;
+ ?column? 
+----------
+ {[1,3)}
+(1 row)
+
+SELECT '{[1,3)}'::nummultirange * '{[0,5)}'::nummultirange;
+ ?column? 
+----------
+ {[1,3)}
+(1 row)
+
+SELECT '{[1,3)}'::nummultirange * '{[0,2)}'::nummultirange;
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT '{[1,3)}'::nummultirange * '{[2,5)}'::nummultirange;
+ ?column? 
+----------
+ {[2,3)}
+(1 row)
+
+SELECT '{[1,4)}'::nummultirange * '{[2,3)}'::nummultirange;
+ ?column? 
+----------
+ {[2,3)}
+(1 row)
+
+SELECT '{[1,4)}'::nummultirange * '{[0,2), [3,5)}'::nummultirange;
+   ?column?    
+---------------
+ {[1,2),[3,4)}
+(1 row)
+
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[0,8), [9,12)}'::nummultirange;
+       ?column?       
+----------------------
+ {[1,4),[7,8),[9,10)}
+(1 row)
+
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[9,12)}'::nummultirange;
+ ?column? 
+----------
+ {[9,10)}
+(1 row)
+
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[-5,-4), [5,6), [9,12)}'::nummultirange;
+ ?column? 
+----------
+ {[9,10)}
+(1 row)
+
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[0,2), [3,8), [9,12)}'::nummultirange;
+          ?column?          
+----------------------------
+ {[1,2),[3,4),[7,8),[9,10)}
+(1 row)
+
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[0,2), [3,8), [9,12)}'::nummultirange;
+          ?column?          
+----------------------------
+ {[1,2),[3,4),[7,8),[9,10)}
+(1 row)
+
+--
+-- range_agg function
+--
+create table reservations ( room_id integer not null, booked_during daterange );
+insert into reservations values
+-- 1: has a meets and a gap
+(1, daterange('2018-07-01', '2018-07-07')),
+(1, daterange('2018-07-07', '2018-07-14')),
+(1, daterange('2018-07-20', '2018-07-22')),
+-- 2: just a single row
+(2, daterange('2018-07-01', '2018-07-03')),
+-- 3: one null range
+(3, NULL),
+-- 4: two null ranges
+(4, NULL),
+(4, NULL),
+-- 5: a null range and a non-null range
+(5, NULL),
+(5, daterange('2018-07-01', '2018-07-03')),
+-- 6: has overlap
+(6, daterange('2018-07-01', '2018-07-07')),
+(6, daterange('2018-07-05', '2018-07-10')),
+-- 7: two ranges that meet: no gap or overlap
+(7, daterange('2018-07-01', '2018-07-07')),
+(7, daterange('2018-07-07', '2018-07-14')),
+-- 8: an empty range
+(8, 'empty'::daterange)
+;
+SELECT   room_id, range_agg(booked_during)
+FROM     reservations
+GROUP BY room_id
+ORDER BY room_id;
+ room_id |                     range_agg                     
+---------+---------------------------------------------------
+       1 | {[07-01-2018,07-14-2018),[07-20-2018,07-22-2018)}
+       2 | {[07-01-2018,07-03-2018)}
+       3 | 
+       4 | 
+       5 | {[07-01-2018,07-03-2018)}
+       6 | {[07-01-2018,07-10-2018)}
+       7 | {[07-01-2018,07-14-2018)}
+       8 | {}
+(8 rows)
+
+-- range_agg on a custom range type too
+SELECT  range_agg(r)
+FROM    (VALUES
+          ('[a,c]'::textrange),
+          ('[b,b]'::textrange),
+          ('[c,f]'::textrange),
+          ('[g,h)'::textrange),
+          ('[h,j)'::textrange)
+        ) t(r);
+   range_agg   
+---------------
+ {[a,f],[g,j)}
+(1 row)
+
+select range_intersect_agg(nmr) from nummultirange_test;
+ range_intersect_agg 
+---------------------
+ {}
+(1 row)
+
+select range_intersect_agg(nmr) from nummultirange_test where false;
+ range_intersect_agg 
+---------------------
+ 
+(1 row)
+
+-- test with just one input:
+select range_intersect_agg(nmr) from (values ('{[1,2]}'::nummultirange)) t(nmr);
+ range_intersect_agg 
+---------------------
+ {[1,2]}
+(1 row)
+
+select range_intersect_agg(nmr) from nummultirange_test where nmr @> 4.0;
+ range_intersect_agg 
+---------------------
+ {[3,5)}
+(1 row)
+
+create table nummultirange_test2(nmr nummultirange);
+create index nummultirange_test2_hash_idx on nummultirange_test2 using hash (nmr);
+INSERT INTO nummultirange_test2 VALUES('{[, 5)}');
+INSERT INTO nummultirange_test2 VALUES(nummultirange(numrange(1.1, 2.2)));
+INSERT INTO nummultirange_test2 VALUES(nummultirange(numrange(1.1, 2.2)));
+INSERT INTO nummultirange_test2 VALUES(nummultirange(numrange(1.1, 2.2,'()')));
+INSERT INTO nummultirange_test2 VALUES('{}');
+select * from nummultirange_test2 where nmr = '{}';
+ nmr 
+-----
+ {}
+(1 row)
+
+select * from nummultirange_test2 where nmr = nummultirange(numrange(1.1, 2.2));
+     nmr     
+-------------
+ {[1.1,2.2)}
+ {[1.1,2.2)}
+(2 rows)
+
+select * from nummultirange_test2 where nmr = nummultirange(numrange(1.1, 2.3));
+ nmr 
+-----
+(0 rows)
+
+set enable_nestloop=t;
+set enable_hashjoin=f;
+set enable_mergejoin=f;
+select * from nummultirange_test natural join nummultirange_test2 order by nmr;
+     nmr     
+-------------
+ {}
+ {}
+ {}
+ {}
+ {(,5)}
+ {[1.1,2.2)}
+ {[1.1,2.2)}
+(7 rows)
+
+set enable_nestloop=f;
+set enable_hashjoin=t;
+set enable_mergejoin=f;
+select * from nummultirange_test natural join nummultirange_test2 order by nmr;
+     nmr     
+-------------
+ {}
+ {}
+ {}
+ {}
+ {(,5)}
+ {[1.1,2.2)}
+ {[1.1,2.2)}
+(7 rows)
+
+set enable_nestloop=f;
+set enable_hashjoin=f;
+set enable_mergejoin=t;
+select * from nummultirange_test natural join nummultirange_test2 order by nmr;
+     nmr     
+-------------
+ {}
+ {}
+ {}
+ {}
+ {(,5)}
+ {[1.1,2.2)}
+ {[1.1,2.2)}
+(7 rows)
+
+set enable_nestloop to default;
+set enable_hashjoin to default;
+set enable_mergejoin to default;
+DROP TABLE nummultirange_test2;
+--
+-- Test user-defined multirange of floats
+--
+select '{[123.001, 5.e9)}'::float8multirange @> 888.882::float8;
+ ?column? 
+----------
+ t
+(1 row)
+
+create table float8multirange_test(f8mr float8multirange, i int);
+insert into float8multirange_test values(float8multirange(float8range(-100.00007, '1.111113e9')), 42);
+select * from float8multirange_test;
+           f8mr            | i  
+---------------------------+----
+ {[-100.00007,1111113000)} | 42
+(1 row)
+
+drop table float8multirange_test;
+--
+-- Test multirange types over domains
+--
+create domain mydomain as int4;
+create type mydomainrange as range(subtype=mydomain);
+select '{[4,50)}'::mydomainmultirange @> 7::mydomain;
+ ?column? 
+----------
+ t
+(1 row)
+
+drop domain mydomain cascade;
+NOTICE:  drop cascades to type mydomainrange
+--
+-- Test domains over multirange types
+--
+create domain restrictedmultirange as int4multirange check (upper(value) < 10);
+select '{[4,5)}'::restrictedmultirange @> 7;
+ ?column? 
+----------
+ f
+(1 row)
+
+select '{[4,50)}'::restrictedmultirange @> 7; -- should fail
+ERROR:  value for domain restrictedmultirange violates check constraint "restrictedmultirange_check"
+drop domain restrictedmultirange;
+--
+-- Test multiple multirange types over the same subtype
+--
+create type textrange1 as range(subtype=text, collation="C");
+create type textrange2 as range(subtype=text, collation="C");
+select textmultirange1(textrange2('a','Z'));  -- should fail
+ERROR:  function textmultirange1(textrange2) does not exist
+LINE 1: select textmultirange1(textrange2('a','Z'));
+               ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+select textmultirange1(textrange1('a','Z')) @> 'b'::text;
+ERROR:  range lower bound must be less than or equal to range upper bound
+select textmultirange2(textrange2('a','z')) @> 'b'::text;
+ ?column? 
+----------
+ t
+(1 row)
+
+drop type textrange1;
+drop type textrange2;
+--
+-- Test polymorphic type system
+--
+create function anyarray_anymultirange_func(a anyarray, r anymultirange)
+  returns anyelement as 'select $1[1] + lower($2);' language sql;
+select anyarray_anymultirange_func(ARRAY[1,2], int4multirange(int4range(10,20)));
+ anyarray_anymultirange_func 
+-----------------------------
+                          11
+(1 row)
+
+-- should fail
+select anyarray_anymultirange_func(ARRAY[1,2], nummultirange(numrange(10,20)));
+ERROR:  function anyarray_anymultirange_func(integer[], nummultirange) does not exist
+LINE 1: select anyarray_anymultirange_func(ARRAY[1,2], nummultirange...
+               ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+drop function anyarray_anymultirange_func(anyarray, anymultirange);
+-- should fail
+create function bogus_func(anyelement)
+  returns anymultirange as 'select int4multirange(int4range(1,10))' language sql;
+ERROR:  cannot determine result data type
+DETAIL:  A result of type anymultirange requires at least one input of type anyrange or anymultirange.
+-- should fail
+create function bogus_func(int)
+  returns anymultirange as 'select int4multirange(int4range(1,10))' language sql;
+ERROR:  cannot determine result data type
+DETAIL:  A result of type anymultirange requires at least one input of type anyrange or anymultirange.
+create function range_add_bounds(anymultirange)
+  returns anyelement as 'select lower($1) + upper($1)' language sql;
+select range_add_bounds(int4multirange(int4range(1, 17)));
+ range_add_bounds 
+------------------
+               18
+(1 row)
+
+select range_add_bounds(nummultirange(numrange(1.0001, 123.123)));
+ range_add_bounds 
+------------------
+         124.1231
+(1 row)
+
+create function multirangetypes_sql(q anymultirange, b anyarray, out c anyelement)
+  as $$ select upper($1) + $2[1] $$
+  language sql;
+select multirangetypes_sql(int4multirange(int4range(1,10)), ARRAY[2,20]);
+ multirangetypes_sql 
+---------------------
+                  12
+(1 row)
+
+select multirangetypes_sql(nummultirange(numrange(1,10)), ARRAY[2,20]);  -- match failure
+ERROR:  function multirangetypes_sql(nummultirange, integer[]) does not exist
+LINE 1: select multirangetypes_sql(nummultirange(numrange(1,10)), AR...
+               ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+--
+-- Arrays of multiranges
+--
+select ARRAY[nummultirange(numrange(1.1, 1.2)), nummultirange(numrange(12.3, 155.5))];
+              array               
+----------------------------------
+ {"{[1.1,1.2)}","{[12.3,155.5)}"}
+(1 row)
+
+create table i8mr_array (f1 int, f2 int8multirange[]);
+insert into i8mr_array values (42, array[int8multirange(int8range(1,10)), int8multirange(int8range(2,20))]);
+select * from i8mr_array;
+ f1 |           f2            
+----+-------------------------
+ 42 | {"{[1,10)}","{[2,20)}"}
+(1 row)
+
+drop table i8mr_array;
+--
+-- Multiranges of arrays
+--
+select arraymultirange(arrayrange(ARRAY[1,2], ARRAY[2,1]));
+   arraymultirange   
+---------------------
+ {["{1,2}","{2,1}")}
+(1 row)
+
+select arraymultirange(arrayrange(ARRAY[2,1], ARRAY[1,2]));  -- fail
+ERROR:  range lower bound must be less than or equal to range upper bound
+select array[1,1] <@ arraymultirange(arrayrange(array[1,2], array[2,1]));
+ ?column? 
+----------
+ f
+(1 row)
+
+select array[1,3] <@ arraymultirange(arrayrange(array[1,2], array[2,1]));
+ ?column? 
+----------
+ t
+(1 row)
+
+--
+-- Ranges of composites
+--
+create type two_ints as (a int, b int);
+create type two_ints_range as range (subtype = two_ints);
+-- with force_parallel_mode on, this exercises tqueue.c's range remapping
+select *, row_to_json(upper(t)) as u from
+  (values (two_ints_multirange(two_ints_range(row(1,2), row(3,4)))),
+          (two_ints_multirange(two_ints_range(row(5,6), row(7,8))))) v(t);
+          t          |       u       
+---------------------+---------------
+ {["(1,2)","(3,4)")} | {"a":3,"b":4}
+ {["(5,6)","(7,8)")} | {"a":7,"b":8}
+(2 rows)
+
+drop type two_ints cascade;
+NOTICE:  drop cascades to type two_ints_range
+--
+-- Check behavior when subtype lacks a hash function
+--
+set enable_sort = off;  -- try to make it pick a hash setop implementation
+select '{(2,5)}'::cashmultirange except select '{(5,6)}'::cashmultirange;
+ cashmultirange  
+-----------------
+ {($2.00,$5.00)}
+(1 row)
+
+reset enable_sort;
+--
+-- OUT/INOUT/TABLE functions
+--
+-- infer anymultirange from anymultirange
+create function mr_outparam_succeed(i anymultirange, out r anymultirange, out t text)
+  as $$ select $1, 'foo'::text $$ language sql;
+select * from mr_outparam_succeed(int4multirange(int4range(1,2)));
+    r    |  t  
+---------+-----
+ {[1,2)} | foo
+(1 row)
+
+-- infer anyarray from anymultirange
+create function mr_outparam_succeed2(i anymultirange, out r anyarray, out t text)
+  as $$ select ARRAY[upper($1)], 'foo'::text $$ language sql;
+select * from mr_outparam_succeed2(int4multirange(int4range(1,2)));
+  r  |  t  
+-----+-----
+ {2} | foo
+(1 row)
+
+-- infer anyrange from anymultirange
+create function mr_outparam_succeed3(i anymultirange, out r anyrange, out t text)
+  as $$ select range_merge($1), 'foo'::text $$ language sql;
+select * from mr_outparam_succeed3(int4multirange(int4range(1,2)));
+   r   |  t  
+-------+-----
+ [1,2) | foo
+(1 row)
+
+-- infer anymultirange from anyrange
+create function mr_outparam_succeed4(i anyrange, out r anymultirange, out t text)
+  as $$ select multirange($1), 'foo'::text $$ language sql;
+select * from mr_outparam_succeed4(int4range(1,2));
+    r    |  t  
+---------+-----
+ {[1,2)} | foo
+(1 row)
+
+-- infer anyelement from anymultirange
+create function mr_inoutparam_succeed(out i anyelement, inout r anymultirange)
+  as $$ select upper($1), $1 $$ language sql;
+select * from mr_inoutparam_succeed(int4multirange(int4range(1,2)));
+ i |    r    
+---+---------
+ 2 | {[1,2)}
+(1 row)
+
+-- infer anyelement+anymultirange from anyelement+anymultirange
+create function mr_table_succeed(i anyelement, r anymultirange) returns table(i anyelement, r anymultirange)
+  as $$ select $1, $2 $$ language sql;
+select * from mr_table_succeed(123, int4multirange(int4range(1,11)));
+  i  |    r     
+-----+----------
+ 123 | {[1,11)}
+(1 row)
+
+-- use anymultirange in plpgsql
+create function mr_polymorphic(i anyrange) returns anymultirange
+  as $$ begin return multirange($1); end; $$ language plpgsql;
+select mr_polymorphic(int4range(1, 4));
+ mr_polymorphic 
+----------------
+ {[1,4)}
+(1 row)
+
+-- should fail
+create function mr_outparam_fail(i anyelement, out r anymultirange, out t text)
+  as $$ select '[1,10]', 'foo' $$ language sql;
+ERROR:  cannot determine result data type
+DETAIL:  A result of type anymultirange requires at least one input of type anyrange or anymultirange.
+--should fail
+create function mr_inoutparam_fail(inout i anyelement, out r anymultirange)
+  as $$ select $1, '[1,10]' $$ language sql;
+ERROR:  cannot determine result data type
+DETAIL:  A result of type anymultirange requires at least one input of type anyrange or anymultirange.
+--should fail
+create function mr_table_fail(i anyelement) returns table(i anyelement, r anymultirange)
+  as $$ select $1, '[1,10]' $$ language sql;
+ERROR:  cannot determine result data type
+DETAIL:  A result of type anymultirange requires at least one input of type anyrange or anymultirange.
diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out
index 2efd7d7ec7..c02443ee40 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 
@@ -335,7 +346,8 @@ WHERE p1.prorettype IN
      '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     
 ------+----------------
@@ -350,20 +362,25 @@ ORDER BY 2;
  3532 | enum_recv
 (9 rows)
 
--- anyrange is tighter than the rest, can only resolve from anyrange input
+-- anyrange and anymultirange are tighter than the rest, can only resolve
+-- from each other
 SELECT p1.oid, p1.proname
 FROM pg_proc as p1
-WHERE p1.prorettype = 'anyrange'::regtype
+WHERE p1.prorettype IN ('anyrange'::regtype, 'anymultirange'::regtype)
   AND NOT
-     'anyrange'::regtype = ANY (p1.proargtypes)
+    ('anyrange'::regtype = ANY (p1.proargtypes) OR
+      'anymultirange'::regtype = ANY (p1.proargtypes))
 ORDER BY 2;
  oid  |     proname      
 ------+------------------
+ 8002 | anymultirange_in
  3832 | anyrange_in
+ 8004 | multirange_in
+ 8006 | multirange_recv
  3876 | range_gist_union
  3834 | range_in
  3836 | range_recv
-(4 rows)
+(7 rows)
 
 -- similarly for the anycompatible family
 SELECT p1.oid, p1.proname
@@ -2181,13 +2198,14 @@ ORDER BY 1, 2, 3;
                     | float_ops        | float4_ops       | real
                     | float_ops        | float8_ops       | double precision
                     | jsonb_ops        | jsonb_ops        | jsonb
+                    | multirange_ops   | multirange_ops   | anymultirange
                     | numeric_ops      | numeric_ops      | numeric
                     | range_ops        | range_ops        | anyrange
                     | record_image_ops | record_image_ops | record
                     | record_ops       | record_ops       | record
                     | tsquery_ops      | tsquery_ops      | tsquery
                     | tsvector_ops     | tsvector_ops     | tsvector
-(14 rows)
+(15 rows)
 
 -- **************** pg_index ****************
 -- Look for illegal values in pg_index fields.
diff --git a/src/test/regress/expected/plpgsql.out b/src/test/regress/expected/plpgsql.out
index d0a6b630b8..65827c03e6 100644
--- a/src/test/regress/expected/plpgsql.out
+++ b/src/test/regress/expected/plpgsql.out
@@ -1811,7 +1811,7 @@ begin
   return array[x + 1, x + 2];
 end$$ language plpgsql;
 ERROR:  cannot determine result data type
-DETAIL:  A result of type anyrange requires at least one input of type anyrange.
+DETAIL:  A result of type anyrange requires at least one input of type anyrange or anymultirange.
 create function f1(x anyrange) returns anyarray as $$
 begin
   return array[lower(x), upper(x)];
diff --git a/src/test/regress/expected/polymorphism.out b/src/test/regress/expected/polymorphism.out
index d86a7004a6..e087db3ea6 100644
--- a/src/test/regress/expected/polymorphism.out
+++ b/src/test/regress/expected/polymorphism.out
@@ -61,7 +61,7 @@ create function polyf(x anyelement) returns anyrange as $$
   select array[x + 1, x + 2]
 $$ language sql;
 ERROR:  cannot determine result data type
-DETAIL:  A result of type anyrange requires at least one input of type anyrange.
+DETAIL:  A result of type anyrange requires at least one input of type anyrange or anymultirange.
 create function polyf(x anyrange) returns anyarray as $$
   select array[lower(x), upper(x)]
 $$ language sql;
@@ -231,7 +231,7 @@ CREATE AGGREGATE myaggp01a(*) (SFUNC = stfnp, STYPE = int4[],
 CREATE AGGREGATE myaggp02a(*) (SFUNC = stfnp, STYPE = anyarray,
   FINALFUNC = ffp, INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 --     N    P
 -- should CREATE
 CREATE AGGREGATE myaggp03a(*) (SFUNC = stfp, STYPE = int4[],
@@ -243,11 +243,11 @@ CREATE AGGREGATE myaggp03b(*) (SFUNC = stfp, STYPE = int4[],
 CREATE AGGREGATE myaggp04a(*) (SFUNC = stfp, STYPE = anyarray,
   FINALFUNC = ffp, INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 CREATE AGGREGATE myaggp04b(*) (SFUNC = stfp, STYPE = anyarray,
   INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 --    Case2 (R = P) && ((B = P) || (B = N))
 --    -------------------------------------
 --    S    tf1      B    tf2
@@ -302,13 +302,13 @@ ERROR:  function tfp(integer[], anyelement) does not exist
 CREATE AGGREGATE myaggp13a(BASETYPE = int, SFUNC = tfnp, STYPE = anyarray,
   FINALFUNC = ffp, INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 --    P    N        N    P
 -- should ERROR: tf2p(anyarray, int) not matched by tf2p(int[],anyelement)
 CREATE AGGREGATE myaggp14a(BASETYPE = int, SFUNC = tf2p, STYPE = anyarray,
   FINALFUNC = ffp, INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 --    P    N        P    N
 -- should ERROR: tfnp(anyarray, anyelement) not matched by tfnp(int[],int)
 CREATE AGGREGATE myaggp15a(BASETYPE = anyelement, SFUNC = tfnp,
@@ -324,21 +324,21 @@ ERROR:  function tf2p(anyarray, anyelement) does not exist
 CREATE AGGREGATE myaggp17a(BASETYPE = int, SFUNC = tf1p, STYPE = anyarray,
   FINALFUNC = ffp, INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 CREATE AGGREGATE myaggp17b(BASETYPE = int, SFUNC = tf1p, STYPE = anyarray,
   INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 --    P    P        N    P
 -- should ERROR: tfp(anyarray, int) not matched by tfp(anyarray, anyelement)
 CREATE AGGREGATE myaggp18a(BASETYPE = int, SFUNC = tfp, STYPE = anyarray,
   FINALFUNC = ffp, INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 CREATE AGGREGATE myaggp18b(BASETYPE = int, SFUNC = tfp, STYPE = anyarray,
   INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 --    P    P        P    N
 -- should ERROR: tf1p(anyarray, anyelement) not matched by tf1p(anyarray, int)
 CREATE AGGREGATE myaggp19a(BASETYPE = anyelement, SFUNC = tf1p,
@@ -368,11 +368,11 @@ CREATE AGGREGATE myaggn01b(*) (SFUNC = stfnp, STYPE = int4[],
 CREATE AGGREGATE myaggn02a(*) (SFUNC = stfnp, STYPE = anyarray,
   FINALFUNC = ffnp, INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 CREATE AGGREGATE myaggn02b(*) (SFUNC = stfnp, STYPE = anyarray,
   INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 --     N    P
 -- should CREATE
 CREATE AGGREGATE myaggn03a(*) (SFUNC = stfp, STYPE = int4[],
@@ -382,7 +382,7 @@ CREATE AGGREGATE myaggn03a(*) (SFUNC = stfp, STYPE = int4[],
 CREATE AGGREGATE myaggn04a(*) (SFUNC = stfp, STYPE = anyarray,
   FINALFUNC = ffnp, INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 --    Case4 (R = N) && ((B = P) || (B = N))
 --    -------------------------------------
 --    S    tf1      B    tf2
@@ -436,21 +436,21 @@ ERROR:  function tfp(integer[], anyelement) does not exist
 CREATE AGGREGATE myaggn13a(BASETYPE = int, SFUNC = tfnp, STYPE = anyarray,
   FINALFUNC = ffnp, INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 CREATE AGGREGATE myaggn13b(BASETYPE = int, SFUNC = tfnp, STYPE = anyarray,
   INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 --    P    N        N    P
 -- should ERROR: tf2p(anyarray, int) not matched by tf2p(int[],anyelement)
 CREATE AGGREGATE myaggn14a(BASETYPE = int, SFUNC = tf2p, STYPE = anyarray,
   FINALFUNC = ffnp, INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 CREATE AGGREGATE myaggn14b(BASETYPE = int, SFUNC = tf2p, STYPE = anyarray,
   INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 --    P    N        P    N
 -- should ERROR: tfnp(anyarray, anyelement) not matched by tfnp(int[],int)
 CREATE AGGREGATE myaggn15a(BASETYPE = anyelement, SFUNC = tfnp,
@@ -472,13 +472,13 @@ ERROR:  function tf2p(anyarray, anyelement) does not exist
 CREATE AGGREGATE myaggn17a(BASETYPE = int, SFUNC = tf1p, STYPE = anyarray,
   FINALFUNC = ffnp, INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 --    P    P        N    P
 -- should ERROR: tfp(anyarray, int) not matched by tfp(anyarray, anyelement)
 CREATE AGGREGATE myaggn18a(BASETYPE = int, SFUNC = tfp, STYPE = anyarray,
   FINALFUNC = ffnp, INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 --    P    P        P    N
 -- should ERROR: tf1p(anyarray, anyelement) not matched by tf1p(anyarray, int)
 CREATE AGGREGATE myaggn19a(BASETYPE = anyelement, SFUNC = tf1p,
diff --git a/src/test/regress/expected/rangefuncs.out b/src/test/regress/expected/rangefuncs.out
index 7eced28452..71e2769f44 100644
--- a/src/test/regress/expected/rangefuncs.out
+++ b/src/test/regress/expected/rangefuncs.out
@@ -1556,7 +1556,7 @@ DROP FUNCTION dup(anyelement);
 CREATE FUNCTION bad (f1 int, out f2 anyelement, out f3 anyarray)
 AS 'select $1, array[$1,$1]' LANGUAGE sql;
 ERROR:  cannot determine result data type
-DETAIL:  A result of type anyelement requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyelement requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 CREATE FUNCTION dup (f1 anycompatible, f2 anycompatiblearray, f3 out anycompatible, f4 out anycompatiblearray)
 AS 'select $1, $2' LANGUAGE sql;
 SELECT dup(22, array[44]);
diff --git a/src/test/regress/expected/rangetypes.out b/src/test/regress/expected/rangetypes.out
index c421f5394f..763e3819a5 100644
--- a/src/test/regress/expected/rangetypes.out
+++ b/src/test/regress/expected/rangetypes.out
@@ -518,6 +518,24 @@ select numrange(1.0, 2.0) * numrange(2.5, 3.0);
  empty
 (1 row)
 
+select range_intersect_agg(nr) from numrange_test;
+ range_intersect_agg 
+---------------------
+ empty
+(1 row)
+
+select range_intersect_agg(nr) from numrange_test where false;
+ range_intersect_agg 
+---------------------
+ 
+(1 row)
+
+select range_intersect_agg(nr) from numrange_test where nr @> 4.0;
+ range_intersect_agg 
+---------------------
+ [3,5)
+(1 row)
+
 create table numrange_test2(nr numrange);
 create index numrange_test2_hash_idx on numrange_test2 using hash (nr);
 INSERT INTO numrange_test2 VALUES('[, 5)');
@@ -1371,12 +1389,12 @@ drop function anyarray_anyrange_func(anyarray, anyrange);
 create function bogus_func(anyelement)
   returns anyrange as 'select int4range(1,10)' language sql;
 ERROR:  cannot determine result data type
-DETAIL:  A result of type anyrange requires at least one input of type anyrange.
+DETAIL:  A result of type anyrange requires at least one input of type anyrange or anymultirange.
 -- should fail
 create function bogus_func(int)
   returns anyrange as 'select int4range(1,10)' language sql;
 ERROR:  cannot determine result data type
-DETAIL:  A result of type anyrange requires at least one input of type anyrange.
+DETAIL:  A result of type anyrange requires at least one input of type anyrange or anymultirange.
 create function range_add_bounds(anyrange)
   returns anyelement as 'select lower($1) + upper($1)' language sql;
 select range_add_bounds(int4range(1, 17));
@@ -1508,6 +1526,7 @@ reset enable_sort;
 --
 -- OUT/INOUT/TABLE functions
 --
+-- infer anyrange from anyrange
 create function outparam_succeed(i anyrange, out r anyrange, out t text)
   as $$ select $1, 'foo'::text $$ language sql;
 select * from outparam_succeed(int4range(1,2));
@@ -1525,6 +1544,16 @@ select * from outparam2_succeed(int4range(1,11));
  {1,11} | {11,1}
 (1 row)
 
+-- infer anyarray from anyrange
+create function outparam_succeed2(i anyrange, out r anyarray, out t text)
+  as $$ select ARRAY[upper($1)], 'foo'::text $$ language sql;
+select * from outparam_succeed2(int4range(int4range(1,2)));
+  r  |  t  
+-----+-----
+ {2} | foo
+(1 row)
+
+-- infer anyelement from anyrange
 create function inoutparam_succeed(out i anyelement, inout r anyrange)
   as $$ select upper($1), $1 $$ language sql;
 select * from inoutparam_succeed(int4range(1,2));
@@ -1547,14 +1576,14 @@ select * from table_succeed(int4range(1,11));
 create function outparam_fail(i anyelement, out r anyrange, out t text)
   as $$ select '[1,10]', 'foo' $$ language sql;
 ERROR:  cannot determine result data type
-DETAIL:  A result of type anyrange requires at least one input of type anyrange.
+DETAIL:  A result of type anyrange requires at least one input of type anyrange or anymultirange.
 --should fail
 create function inoutparam_fail(inout i anyelement, out r anyrange)
   as $$ select $1, '[1,10]' $$ language sql;
 ERROR:  cannot determine result data type
-DETAIL:  A result of type anyrange requires at least one input of type anyrange.
+DETAIL:  A result of type anyrange requires at least one input of type anyrange or anymultirange.
 --should fail
 create function table_fail(i anyelement) returns table(i anyelement, r anyrange)
   as $$ select $1, '[1,10]' $$ language sql;
 ERROR:  cannot determine result data type
-DETAIL:  A result of type anyrange requires at least one input of type anyrange.
+DETAIL:  A result of type anyrange requires at least one input of type anyrange or anymultirange.
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index 192445878d..d9ce961be2 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -92,6 +92,7 @@ num_exp_sqrt|t
 num_exp_sub|t
 num_input_test|f
 num_result|f
+nummultirange_test|t
 numrange_test|t
 onek|t
 onek2|t
@@ -172,6 +173,7 @@ quad_poly_tbl|t
 radix_text_tbl|t
 ramp|f
 real_city|f
+reservations|f
 road|t
 shighway|t
 slow_emp4000|f
diff --git a/src/test/regress/expected/type_sanity.out b/src/test/regress/expected/type_sanity.out
index 274130e706..8df1ae47e4 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'));
@@ -195,18 +195,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
@@ -240,17 +241,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
@@ -318,18 +320,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
@@ -363,17 +366,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
@@ -631,3 +635,11 @@ WHERE pronargs != 2
 ----------+------------+---------
 (0 rows)
 
+-- every range should have a valid multirange
+SELECT p1.rngtypid, p1.rngsubtype, p1.rngmultitypid
+FROM pg_range p1
+WHERE p1.rngmultitypid IS NULL OR p1.rngmultitypid = 0;
+ rngtypid | rngsubtype | rngmultitypid 
+----------+------------+---------------
+(0 rows)
+
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index a98dba7b2f..18c25f4d8b 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 3f66e0b859..dca9f91afb 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..0f70919e1a
--- /dev/null
+++ b/src/test/regress/sql/multirangetypes.sql
@@ -0,0 +1,630 @@
+-- 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'));
+
+--
+-- test casts, both a built-in range type and a user-defined one:
+--
+select 'empty'::int4range::int4multirange;
+select int4range(1, 3)::int4multirange;
+select int4range(1, null)::int4multirange;
+select int4range(null, null)::int4multirange;
+select 'empty'::textrange::textmultirange;
+select textrange('a', 'c')::textmultirange;
+select textrange('a', null)::textmultirange;
+select textrange(null, null)::textmultirange;
+
+--
+-- create some test data and test the operators
+--
+
+CREATE TABLE nummultirange_test (nmr NUMMULTIRANGE);
+CREATE INDEX nummultirange_test_btree ON nummultirange_test(nmr);
+
+INSERT INTO nummultirange_test VALUES('{}');
+INSERT INTO nummultirange_test VALUES('{[,)}');
+INSERT INTO nummultirange_test VALUES('{[3,]}');
+INSERT INTO nummultirange_test VALUES('{[,), [3,]}');
+INSERT INTO nummultirange_test VALUES('{[, 5)}');
+INSERT INTO nummultirange_test VALUES(nummultirange());
+INSERT INTO nummultirange_test VALUES(nummultirange(variadic '{}'::numrange[]));
+INSERT INTO nummultirange_test VALUES(nummultirange(numrange(1.1, 2.2)));
+INSERT INTO nummultirange_test VALUES('{empty}');
+INSERT INTO nummultirange_test VALUES(nummultirange(numrange(1.7, 1.7, '[]'), numrange(1.7, 1.9)));
+INSERT INTO nummultirange_test VALUES(nummultirange(numrange(1.7, 1.7, '[]'), numrange(1.9, 2.1)));
+
+SELECT nmr, isempty(nmr), lower(nmr), upper(nmr) FROM nummultirange_test ORDER BY nmr;
+SELECT nmr, lower_inc(nmr), lower_inf(nmr), upper_inc(nmr), upper_inf(nmr) FROM nummultirange_test ORDER BY nmr;
+
+SELECT * FROM nummultirange_test WHERE nmr = '{}';
+SELECT * FROM nummultirange_test WHERE nmr = '{(,5)}';
+SELECT * FROM nummultirange_test WHERE nmr = '{[3,)}';
+SELECT * FROM nummultirange_test WHERE nmr = '{[1.7,1.7]}';
+SELECT * FROM nummultirange_test WHERE nmr = '{[1.7,1.7],[1.9,2.1)}';
+SELECT * FROM nummultirange_test WHERE nmr < '{}';
+SELECT * FROM nummultirange_test WHERE nmr < '{[-1000.0, -1000.0]}';
+SELECT * FROM nummultirange_test WHERE nmr < '{[0.0, 1.0]}';
+SELECT * FROM nummultirange_test WHERE nmr < '{[1000.0, 1001.0]}';
+SELECT * FROM nummultirange_test WHERE nmr <= '{}';
+SELECT * FROM nummultirange_test WHERE nmr <= '{[3,)}';
+SELECT * FROM nummultirange_test WHERE nmr >= '{}';
+SELECT * FROM nummultirange_test WHERE nmr >= '{[3,)}';
+SELECT * FROM nummultirange_test WHERE nmr > '{}';
+SELECT * FROM nummultirange_test WHERE nmr > '{[-1000.0, -1000.0]}';
+SELECT * FROM nummultirange_test WHERE nmr > '{[0.0, 1.0]}';
+SELECT * FROM nummultirange_test WHERE nmr > '{[1000.0, 1001.0]}';
+SELECT * FROM nummultirange_test WHERE nmr <> '{}';
+SELECT * FROM nummultirange_test WHERE nmr <> '{(,5)}';
+
+select nummultirange(numrange(2.0, 1.0));
+select nummultirange(numrange(5.0, 6.0), numrange(1.0, 2.0));
+
+-- overlaps
+SELECT * FROM nummultirange_test WHERE range_overlaps_multirange(numrange(4.0, 4.2), nmr);
+SELECT * FROM nummultirange_test WHERE numrange(4.0, 4.2) && nmr;
+SELECT * FROM nummultirange_test WHERE multirange_overlaps_range(nmr, numrange(4.0, 4.2));
+SELECT * FROM nummultirange_test WHERE nmr && numrange(4.0, 4.2);
+SELECT * FROM nummultirange_test WHERE multirange_overlaps_multirange(nmr, nummultirange(numrange(4.0, 4.2), numrange(6.0, 7.0)));
+SELECT * FROM nummultirange_test WHERE nmr && nummultirange(numrange(4.0, 4.2), numrange(6.0, 7.0));
+SELECT * FROM nummultirange_test WHERE nmr && nummultirange(numrange(6.0, 7.0));
+SELECT * FROM nummultirange_test WHERE nmr && nummultirange(numrange(6.0, 7.0), numrange(8.0, 9.0));
+
+-- mr contains x
+SELECT * FROM nummultirange_test WHERE multirange_contains_elem(nmr, 4.0);
+SELECT * FROM nummultirange_test WHERE nmr @> 4.0;
+SELECT * FROM nummultirange_test WHERE multirange_contains_range(nmr, numrange(4.0, 4.2));
+SELECT * FROM nummultirange_test WHERE nmr @> numrange(4.0, 4.2);
+SELECT * FROM nummultirange_test WHERE multirange_contains_multirange(nmr, '{[4.0,4.2), [6.0, 8.0)}');
+SELECT * FROM nummultirange_test WHERE nmr @> '{[4.0,4.2), [6.0, 8.0)}'::nummultirange;
+
+-- x is contained by mr
+SELECT * FROM nummultirange_test WHERE elem_contained_by_multirange(4.0, nmr);
+SELECT * FROM nummultirange_test WHERE 4.0 <@ nmr;
+SELECT * FROM nummultirange_test WHERE range_contained_by_multirange(numrange(4.0, 4.2), nmr);
+SELECT * FROM nummultirange_test WHERE numrange(4.0, 4.2) <@ nmr;
+SELECT * FROM nummultirange_test WHERE multirange_contained_by_multirange('{[4.0,4.2), [6.0, 8.0)}', nmr);
+SELECT * FROM nummultirange_test WHERE '{[4.0,4.2), [6.0, 8.0)}'::nummultirange <@ nmr;
+
+-- overlaps
+SELECT 'empty'::numrange && nummultirange();
+SELECT 'empty'::numrange && nummultirange(numrange(1,2));
+SELECT nummultirange() && 'empty'::numrange;
+SELECT nummultirange(numrange(1,2)) && 'empty'::numrange;
+SELECT nummultirange() && nummultirange();
+SELECT nummultirange() && nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) && nummultirange();
+SELECT nummultirange(numrange(3,4)) && nummultirange(numrange(1,2), numrange(7,8));
+SELECT nummultirange(numrange(1,2), numrange(7,8)) && nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(3,4)) && nummultirange(numrange(1,2), numrange(3.5,8));
+SELECT nummultirange(numrange(1,2), numrange(3.5,8)) && numrange(3,4);
+SELECT nummultirange(numrange(1,2), numrange(3.5,8)) && nummultirange(numrange(3,4));
+
+-- contains
+SELECT nummultirange() @> nummultirange();
+SELECT nummultirange() @> 'empty'::numrange;
+SELECT nummultirange(numrange(null,null)) @> numrange(1,2);
+SELECT nummultirange(numrange(null,null)) @> numrange(null,2);
+SELECT nummultirange(numrange(null,null)) @> numrange(2,null);
+SELECT nummultirange(numrange(null,5)) @> numrange(null,3);
+SELECT nummultirange(numrange(null,5)) @> numrange(null,8);
+SELECT nummultirange(numrange(5,null)) @> numrange(8,null);
+SELECT nummultirange(numrange(5,null)) @> numrange(3,null);
+SELECT nummultirange(numrange(1,5)) @> numrange(8,9);
+SELECT nummultirange(numrange(1,5)) @> numrange(3,9);
+SELECT nummultirange(numrange(1,5)) @> numrange(1,4);
+SELECT nummultirange(numrange(1,5)) @> numrange(1,5);
+SELECT nummultirange(numrange(-4,-2), numrange(1,5)) @> numrange(1,5);
+SELECT nummultirange(numrange(1,5), numrange(8,9)) @> numrange(1,5);
+SELECT nummultirange(numrange(1,5), numrange(8,9)) @> numrange(6,7);
+SELECT nummultirange(numrange(1,5), numrange(6,9)) @> numrange(6,7);
+SELECT '{[1,5)}'::nummultirange @> '{[1,5)}';
+SELECT '{[-4,-2), [1,5)}'::nummultirange @> '{[1,5)}';
+SELECT '{[1,5), [8,9)}'::nummultirange @> '{[1,5)}';
+SELECT '{[1,5), [8,9)}'::nummultirange @> '{[6,7)}';
+SELECT '{[1,5), [6,9)}'::nummultirange @> '{[6,7)}';
+
+-- is contained by
+SELECT nummultirange() <@ nummultirange();
+SELECT 'empty'::numrange <@ nummultirange();
+SELECT numrange(1,2) <@ nummultirange(numrange(null,null));
+SELECT numrange(null,2) <@ nummultirange(numrange(null,null));
+SELECT numrange(2,null) <@ nummultirange(numrange(null,null));
+SELECT numrange(null,3) <@ nummultirange(numrange(null,5));
+SELECT numrange(null,8) <@ nummultirange(numrange(null,5));
+SELECT numrange(8,null) <@ nummultirange(numrange(5,null));
+SELECT numrange(3,null) <@ nummultirange(numrange(5,null));
+SELECT numrange(8,9) <@ nummultirange(numrange(1,5));
+SELECT numrange(3,9) <@ nummultirange(numrange(1,5));
+SELECT numrange(1,4) <@ nummultirange(numrange(1,5));
+SELECT numrange(1,5) <@ nummultirange(numrange(1,5));
+SELECT numrange(1,5) <@ nummultirange(numrange(-4,-2), numrange(1,5));
+SELECT numrange(1,5) <@ nummultirange(numrange(1,5), numrange(8,9));
+SELECT numrange(6,7) <@ nummultirange(numrange(1,5), numrange(8,9));
+SELECT numrange(6,7) <@ nummultirange(numrange(1,5), numrange(6,9));
+SELECT '{[1,5)}' <@ '{[1,5)}'::nummultirange;
+SELECT '{[1,5)}' <@ '{[-4,-2), [1,5)}'::nummultirange;
+SELECT '{[1,5)}' <@ '{[1,5), [8,9)}'::nummultirange;
+SELECT '{[6,7)}' <@ '{[1,5), [8,9)}'::nummultirange;
+SELECT '{[6,7)}' <@ '{[1,5), [6,9)}'::nummultirange;
+
+-- overleft
+SELECT 'empty'::numrange &< nummultirange();
+SELECT 'empty'::numrange &< nummultirange(numrange(1,2));
+SELECT nummultirange() &< 'empty'::numrange;
+SELECT nummultirange(numrange(1,2)) &< 'empty'::numrange;
+SELECT nummultirange() &< nummultirange();
+SELECT nummultirange(numrange(1,2)) &< nummultirange();
+SELECT nummultirange() &< nummultirange(numrange(1,2));
+SELECT numrange(6,7) &< nummultirange(numrange(3,4));
+SELECT numrange(1,2) &< nummultirange(numrange(3,4));
+SELECT numrange(1,4) &< nummultirange(numrange(3,4));
+SELECT numrange(1,6) &< nummultirange(numrange(3,4));
+SELECT numrange(3.5,6) &< nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(6,7)) &< numrange(3,4);
+SELECT nummultirange(numrange(1,2)) &< numrange(3,4);
+SELECT nummultirange(numrange(1,4)) &< numrange(3,4);
+SELECT nummultirange(numrange(1,6)) &< numrange(3,4);
+SELECT nummultirange(numrange(3.5,6)) &< numrange(3,4);
+SELECT nummultirange(numrange(6,7)) &< nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,2)) &< nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,4)) &< nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,6)) &< nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(3.5,6)) &< nummultirange(numrange(3,4));
+
+-- overright
+SELECT nummultirange() &> 'empty'::numrange;
+SELECT nummultirange(numrange(1,2)) &> 'empty'::numrange;
+SELECT 'empty'::numrange &> nummultirange();
+SELECT 'empty'::numrange &> nummultirange(numrange(1,2));
+SELECT nummultirange() &> nummultirange();
+SELECT nummultirange() &> nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) &> nummultirange();
+SELECT nummultirange(numrange(3,4)) &> numrange(6,7);
+SELECT nummultirange(numrange(3,4)) &> numrange(1,2);
+SELECT nummultirange(numrange(3,4)) &> numrange(1,4);
+SELECT nummultirange(numrange(3,4)) &> numrange(1,6);
+SELECT nummultirange(numrange(3,4)) &> numrange(3.5,6);
+SELECT numrange(3,4) &> nummultirange(numrange(6,7));
+SELECT numrange(3,4) &> nummultirange(numrange(1,2));
+SELECT numrange(3,4) &> nummultirange(numrange(1,4));
+SELECT numrange(3,4) &> nummultirange(numrange(1,6));
+SELECT numrange(3,4) &> nummultirange(numrange(3.5,6));
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(6,7));
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(1,4));
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(1,6));
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(3.5,6));
+
+-- meets
+SELECT 'empty'::numrange -|- nummultirange();
+SELECT 'empty'::numrange -|- nummultirange(numrange(1,2));
+SELECT nummultirange() -|- 'empty'::numrange;
+SELECT nummultirange(numrange(1,2)) -|- 'empty'::numrange;
+SELECT nummultirange() -|- nummultirange();
+SELECT nummultirange(numrange(1,2)) -|- nummultirange();
+SELECT nummultirange() -|- nummultirange(numrange(1,2));
+SELECT numrange(1,2) -|- nummultirange(numrange(2,4));
+SELECT numrange(1,2) -|- nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,2)) -|- numrange(2,4);
+SELECT nummultirange(numrange(1,2)) -|- numrange(3,4);
+SELECT nummultirange(numrange(1,2)) -|- nummultirange(numrange(2,4));
+SELECT nummultirange(numrange(1,2)) -|- nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,2), numrange(5,6)) -|- nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,2), numrange(5,6)) -|- nummultirange(numrange(6,7));
+SELECT nummultirange(numrange(1,2), numrange(5,6)) -|- nummultirange(numrange(8,9));
+SELECT nummultirange(numrange(1,2)) -|- nummultirange(numrange(2,4), numrange(6,7));
+
+-- strictly left
+select 'empty'::numrange << nummultirange();
+select numrange(1,2) << nummultirange();
+select numrange(1,2) << nummultirange(numrange(3,4));
+select numrange(1,2) << nummultirange(numrange(0,4));
+select numrange(1,2) << nummultirange(numrange(0,4), numrange(7,8));
+select nummultirange() << 'empty'::numrange;
+select nummultirange() << numrange(1,2);
+select nummultirange(numrange(3,4)) << numrange(3,6);
+select nummultirange(numrange(0,2)) << numrange(3,6);
+select nummultirange(numrange(0,2), numrange(7,8)) << numrange(3,6);
+select nummultirange(numrange(-4,-2), numrange(0,2)) << numrange(3,6);
+select nummultirange() << nummultirange();
+select nummultirange() << nummultirange(numrange(1,2));
+select nummultirange(numrange(1,2)) << nummultirange();
+select nummultirange(numrange(1,2)) << nummultirange(numrange(1,2));
+select nummultirange(numrange(1,2)) << nummultirange(numrange(3,4));
+select nummultirange(numrange(1,2)) << nummultirange(numrange(3,4), numrange(7,8));
+select nummultirange(numrange(1,2), numrange(4,5)) << nummultirange(numrange(3,4), numrange(7,8));
+
+-- strictly right
+select nummultirange() >> 'empty'::numrange;
+select nummultirange() >> numrange(1,2);
+select nummultirange(numrange(3,4)) >> numrange(1,2);
+select nummultirange(numrange(0,4)) >> numrange(1,2);
+select nummultirange(numrange(0,4), numrange(7,8)) >> numrange(1,2);
+select 'empty'::numrange >> nummultirange();
+select numrange(1,2) >> nummultirange();
+select numrange(3,6) >> nummultirange(numrange(3,4));
+select numrange(3,6) >> nummultirange(numrange(0,2));
+select numrange(3,6) >> nummultirange(numrange(0,2), numrange(7,8));
+select numrange(3,6) >> nummultirange(numrange(-4,-2), numrange(0,2));
+select nummultirange() >> nummultirange();
+select nummultirange(numrange(1,2)) >> nummultirange();
+select nummultirange() >> nummultirange(numrange(1,2));
+select nummultirange(numrange(1,2)) >> nummultirange(numrange(1,2));
+select nummultirange(numrange(3,4)) >> nummultirange(numrange(1,2));
+select nummultirange(numrange(3,4), numrange(7,8)) >> nummultirange(numrange(1,2));
+select nummultirange(numrange(3,4), numrange(7,8)) >> nummultirange(numrange(1,2), numrange(4,5));
+
+-- union
+SELECT nummultirange() + nummultirange();
+SELECT nummultirange() + nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) + nummultirange();
+SELECT nummultirange(numrange(1,2)) + nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) + nummultirange(numrange(2,4));
+SELECT nummultirange(numrange(1,2)) + nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) + nummultirange(numrange(2,4));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) + nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) + nummultirange(numrange(0,9));
+
+-- merge
+SELECT range_merge(nummultirange());
+SELECT range_merge(nummultirange(numrange(1,2)));
+SELECT range_merge(nummultirange(numrange(1,2), numrange(7,8)));
+
+-- minus
+SELECT nummultirange() - nummultirange();
+SELECT nummultirange() - nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) - nummultirange();
+SELECT nummultirange(numrange(1,2), numrange(3,4)) - nummultirange();
+SELECT nummultirange(numrange(1,2)) - nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) - nummultirange(numrange(2,4));
+SELECT nummultirange(numrange(1,2)) - nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(2,3));
+SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(0,8));
+SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(0,2));
+SELECT nummultirange(numrange(1,8)) - nummultirange(numrange(0,2), numrange(3,4));
+SELECT nummultirange(numrange(1,8)) - nummultirange(numrange(2,3), numrange(5,null));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(-2,0));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(2,4));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(3,5));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(0,9));
+SELECT nummultirange(numrange(1,3), numrange(4,5)) - nummultirange(numrange(2,9));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(8,9));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(-2,0), numrange(8,9));
+
+-- intersection
+SELECT nummultirange() * nummultirange();
+SELECT nummultirange() * nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) * nummultirange();
+SELECT '{[1,3)}'::nummultirange * '{[1,5)}'::nummultirange;
+SELECT '{[1,3)}'::nummultirange * '{[0,5)}'::nummultirange;
+SELECT '{[1,3)}'::nummultirange * '{[0,2)}'::nummultirange;
+SELECT '{[1,3)}'::nummultirange * '{[2,5)}'::nummultirange;
+SELECT '{[1,4)}'::nummultirange * '{[2,3)}'::nummultirange;
+SELECT '{[1,4)}'::nummultirange * '{[0,2), [3,5)}'::nummultirange;
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[0,8), [9,12)}'::nummultirange;
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[9,12)}'::nummultirange;
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[-5,-4), [5,6), [9,12)}'::nummultirange;
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[0,2), [3,8), [9,12)}'::nummultirange;
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[0,2), [3,8), [9,12)}'::nummultirange;
+
+--
+-- range_agg function
+--
+create table reservations ( room_id integer not null, booked_during daterange );
+insert into reservations values
+-- 1: has a meets and a gap
+(1, daterange('2018-07-01', '2018-07-07')),
+(1, daterange('2018-07-07', '2018-07-14')),
+(1, daterange('2018-07-20', '2018-07-22')),
+-- 2: just a single row
+(2, daterange('2018-07-01', '2018-07-03')),
+-- 3: one null range
+(3, NULL),
+-- 4: two null ranges
+(4, NULL),
+(4, NULL),
+-- 5: a null range and a non-null range
+(5, NULL),
+(5, daterange('2018-07-01', '2018-07-03')),
+-- 6: has overlap
+(6, daterange('2018-07-01', '2018-07-07')),
+(6, daterange('2018-07-05', '2018-07-10')),
+-- 7: two ranges that meet: no gap or overlap
+(7, daterange('2018-07-01', '2018-07-07')),
+(7, daterange('2018-07-07', '2018-07-14')),
+-- 8: an empty range
+(8, 'empty'::daterange)
+;
+SELECT   room_id, range_agg(booked_during)
+FROM     reservations
+GROUP BY room_id
+ORDER BY room_id;
+
+-- range_agg on a custom range type too
+SELECT  range_agg(r)
+FROM    (VALUES
+          ('[a,c]'::textrange),
+          ('[b,b]'::textrange),
+          ('[c,f]'::textrange),
+          ('[g,h)'::textrange),
+          ('[h,j)'::textrange)
+        ) t(r);
+
+select range_intersect_agg(nmr) from nummultirange_test;
+select range_intersect_agg(nmr) from nummultirange_test where false;
+-- test with just one input:
+select range_intersect_agg(nmr) from (values ('{[1,2]}'::nummultirange)) t(nmr);
+select range_intersect_agg(nmr) from nummultirange_test where nmr @> 4.0;
+
+create table nummultirange_test2(nmr nummultirange);
+create index nummultirange_test2_hash_idx on nummultirange_test2 using hash (nmr);
+
+INSERT INTO nummultirange_test2 VALUES('{[, 5)}');
+INSERT INTO nummultirange_test2 VALUES(nummultirange(numrange(1.1, 2.2)));
+INSERT INTO nummultirange_test2 VALUES(nummultirange(numrange(1.1, 2.2)));
+INSERT INTO nummultirange_test2 VALUES(nummultirange(numrange(1.1, 2.2,'()')));
+INSERT INTO nummultirange_test2 VALUES('{}');
+
+select * from nummultirange_test2 where nmr = '{}';
+select * from nummultirange_test2 where nmr = nummultirange(numrange(1.1, 2.2));
+select * from nummultirange_test2 where nmr = nummultirange(numrange(1.1, 2.3));
+
+set enable_nestloop=t;
+set enable_hashjoin=f;
+set enable_mergejoin=f;
+select * from nummultirange_test natural join nummultirange_test2 order by nmr;
+set enable_nestloop=f;
+set enable_hashjoin=t;
+set enable_mergejoin=f;
+select * from nummultirange_test natural join nummultirange_test2 order by nmr;
+set enable_nestloop=f;
+set enable_hashjoin=f;
+set enable_mergejoin=t;
+select * from nummultirange_test natural join nummultirange_test2 order by nmr;
+
+set enable_nestloop to default;
+set enable_hashjoin to default;
+set enable_mergejoin to default;
+
+DROP TABLE nummultirange_test2;
+
+--
+-- Test user-defined multirange of floats
+--
+
+select '{[123.001, 5.e9)}'::float8multirange @> 888.882::float8;
+create table float8multirange_test(f8mr float8multirange, i int);
+insert into float8multirange_test values(float8multirange(float8range(-100.00007, '1.111113e9')), 42);
+select * from float8multirange_test;
+drop table float8multirange_test;
+
+--
+-- Test multirange types over domains
+--
+
+create domain mydomain as int4;
+create type mydomainrange as range(subtype=mydomain);
+select '{[4,50)}'::mydomainmultirange @> 7::mydomain;
+drop domain mydomain cascade;
+
+--
+-- Test domains over multirange types
+--
+
+create domain restrictedmultirange as int4multirange check (upper(value) < 10);
+select '{[4,5)}'::restrictedmultirange @> 7;
+select '{[4,50)}'::restrictedmultirange @> 7; -- should fail
+drop domain restrictedmultirange;
+
+--
+-- Test multiple multirange types over the same subtype
+--
+
+create type textrange1 as range(subtype=text, collation="C");
+create type textrange2 as range(subtype=text, collation="C");
+
+select textmultirange1(textrange2('a','Z'));  -- should fail
+select textmultirange1(textrange1('a','Z')) @> 'b'::text;
+select textmultirange2(textrange2('a','z')) @> 'b'::text;
+
+drop type textrange1;
+drop type textrange2;
+
+--
+-- Test polymorphic type system
+--
+
+create function anyarray_anymultirange_func(a anyarray, r anymultirange)
+  returns anyelement as 'select $1[1] + lower($2);' language sql;
+
+select anyarray_anymultirange_func(ARRAY[1,2], int4multirange(int4range(10,20)));
+
+-- should fail
+select anyarray_anymultirange_func(ARRAY[1,2], nummultirange(numrange(10,20)));
+
+drop function anyarray_anymultirange_func(anyarray, anymultirange);
+
+-- should fail
+create function bogus_func(anyelement)
+  returns anymultirange as 'select int4multirange(int4range(1,10))' language sql;
+
+-- should fail
+create function bogus_func(int)
+  returns anymultirange as 'select int4multirange(int4range(1,10))' language sql;
+
+create function range_add_bounds(anymultirange)
+  returns anyelement as 'select lower($1) + upper($1)' language sql;
+
+select range_add_bounds(int4multirange(int4range(1, 17)));
+select range_add_bounds(nummultirange(numrange(1.0001, 123.123)));
+
+create function multirangetypes_sql(q anymultirange, b anyarray, out c anyelement)
+  as $$ select upper($1) + $2[1] $$
+  language sql;
+
+select multirangetypes_sql(int4multirange(int4range(1,10)), ARRAY[2,20]);
+select multirangetypes_sql(nummultirange(numrange(1,10)), ARRAY[2,20]);  -- match failure
+
+--
+-- Arrays of multiranges
+--
+
+select ARRAY[nummultirange(numrange(1.1, 1.2)), nummultirange(numrange(12.3, 155.5))];
+
+create table i8mr_array (f1 int, f2 int8multirange[]);
+insert into i8mr_array values (42, array[int8multirange(int8range(1,10)), int8multirange(int8range(2,20))]);
+select * from i8mr_array;
+drop table i8mr_array;
+
+--
+-- Multiranges of arrays
+--
+
+select arraymultirange(arrayrange(ARRAY[1,2], ARRAY[2,1]));
+select arraymultirange(arrayrange(ARRAY[2,1], ARRAY[1,2]));  -- fail
+
+select array[1,1] <@ arraymultirange(arrayrange(array[1,2], array[2,1]));
+select array[1,3] <@ arraymultirange(arrayrange(array[1,2], array[2,1]));
+
+--
+-- Ranges of composites
+--
+
+create type two_ints as (a int, b int);
+create type two_ints_range as range (subtype = two_ints);
+
+-- with force_parallel_mode on, this exercises tqueue.c's range remapping
+select *, row_to_json(upper(t)) as u from
+  (values (two_ints_multirange(two_ints_range(row(1,2), row(3,4)))),
+          (two_ints_multirange(two_ints_range(row(5,6), row(7,8))))) v(t);
+
+drop type two_ints cascade;
+
+--
+-- Check behavior when subtype lacks a hash function
+--
+
+set enable_sort = off;  -- try to make it pick a hash setop implementation
+
+select '{(2,5)}'::cashmultirange except select '{(5,6)}'::cashmultirange;
+
+reset enable_sort;
+
+--
+-- OUT/INOUT/TABLE functions
+--
+
+-- infer anymultirange from anymultirange
+create function mr_outparam_succeed(i anymultirange, out r anymultirange, out t text)
+  as $$ select $1, 'foo'::text $$ language sql;
+
+select * from mr_outparam_succeed(int4multirange(int4range(1,2)));
+
+-- infer anyarray from anymultirange
+create function mr_outparam_succeed2(i anymultirange, out r anyarray, out t text)
+  as $$ select ARRAY[upper($1)], 'foo'::text $$ language sql;
+
+select * from mr_outparam_succeed2(int4multirange(int4range(1,2)));
+
+-- infer anyrange from anymultirange
+create function mr_outparam_succeed3(i anymultirange, out r anyrange, out t text)
+  as $$ select range_merge($1), 'foo'::text $$ language sql;
+select * from mr_outparam_succeed3(int4multirange(int4range(1,2)));
+
+-- infer anymultirange from anyrange
+create function mr_outparam_succeed4(i anyrange, out r anymultirange, out t text)
+  as $$ select multirange($1), 'foo'::text $$ language sql;
+
+select * from mr_outparam_succeed4(int4range(1,2));
+
+-- infer anyelement from anymultirange
+create function mr_inoutparam_succeed(out i anyelement, inout r anymultirange)
+  as $$ select upper($1), $1 $$ language sql;
+
+select * from mr_inoutparam_succeed(int4multirange(int4range(1,2)));
+
+-- infer anyelement+anymultirange from anyelement+anymultirange
+create function mr_table_succeed(i anyelement, r anymultirange) returns table(i anyelement, r anymultirange)
+  as $$ select $1, $2 $$ language sql;
+
+select * from mr_table_succeed(123, int4multirange(int4range(1,11)));
+
+-- use anymultirange in plpgsql
+create function mr_polymorphic(i anyrange) returns anymultirange
+  as $$ begin return multirange($1); end; $$ language plpgsql;
+select mr_polymorphic(int4range(1, 4));
+
+-- should fail
+create function mr_outparam_fail(i anyelement, out r anymultirange, out t text)
+  as $$ select '[1,10]', 'foo' $$ language sql;
+
+--should fail
+create function mr_inoutparam_fail(inout i anyelement, out r anymultirange)
+  as $$ select $1, '[1,10]' $$ language sql;
+
+--should fail
+create function mr_table_fail(i anyelement) returns table(i anyelement, r anymultirange)
+  as $$ select $1, '[1,10]' $$ language sql;
diff --git a/src/test/regress/sql/opr_sanity.sql b/src/test/regress/sql/opr_sanity.sql
index 9a1ea3d999..69c4859102 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;
 
@@ -279,16 +290,19 @@ WHERE p1.prorettype IN
      '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;
 
--- anyrange is tighter than the rest, can only resolve from anyrange input
+-- anyrange and anymultirange are tighter than the rest, can only resolve
+-- from each other
 
 SELECT p1.oid, p1.proname
 FROM pg_proc as p1
-WHERE p1.prorettype = 'anyrange'::regtype
+WHERE p1.prorettype IN ('anyrange'::regtype, 'anymultirange'::regtype)
   AND NOT
-     'anyrange'::regtype = ANY (p1.proargtypes)
+    ('anyrange'::regtype = ANY (p1.proargtypes) OR
+      'anymultirange'::regtype = ANY (p1.proargtypes))
 ORDER BY 2;
 
 -- similarly for the anycompatible family
diff --git a/src/test/regress/sql/rangetypes.sql b/src/test/regress/sql/rangetypes.sql
index 4048b1d185..0276d19136 100644
--- a/src/test/regress/sql/rangetypes.sql
+++ b/src/test/regress/sql/rangetypes.sql
@@ -118,6 +118,10 @@ select numrange(1.0, 2.0) * numrange(2.0, 3.0);
 select numrange(1.0, 2.0) * numrange(1.5, 3.0);
 select numrange(1.0, 2.0) * numrange(2.5, 3.0);
 
+select range_intersect_agg(nr) from numrange_test;
+select range_intersect_agg(nr) from numrange_test where false;
+select range_intersect_agg(nr) from numrange_test where nr @> 4.0;
+
 create table numrange_test2(nr numrange);
 create index numrange_test2_hash_idx on numrange_test2 using hash (nr);
 
@@ -528,6 +532,7 @@ reset enable_sort;
 -- OUT/INOUT/TABLE functions
 --
 
+-- infer anyrange from anyrange
 create function outparam_succeed(i anyrange, out r anyrange, out t text)
   as $$ select $1, 'foo'::text $$ language sql;
 
@@ -539,6 +544,13 @@ create function outparam2_succeed(r anyrange, out lu anyarray, out ul anyarray)
 
 select * from outparam2_succeed(int4range(1,11));
 
+-- infer anyarray from anyrange
+create function outparam_succeed2(i anyrange, out r anyarray, out t text)
+  as $$ select ARRAY[upper($1)], 'foo'::text $$ language sql;
+
+select * from outparam_succeed2(int4range(int4range(1,2)));
+
+-- infer anyelement from anyrange
 create function inoutparam_succeed(out i anyelement, inout r anyrange)
   as $$ select upper($1), $1 $$ language sql;
 
diff --git a/src/test/regress/sql/type_sanity.sql b/src/test/regress/sql/type_sanity.sql
index 4b492ce062..07879d9a57 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'));
@@ -153,7 +153,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')
@@ -182,7 +182,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')
@@ -234,7 +234,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')
@@ -263,7 +263,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')
@@ -467,3 +467,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.rngmultitypid
+FROM pg_range p1
+WHERE p1.rngmultitypid IS NULL OR p1.rngmultitypid = 0;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 34623523a7..a2481e3837 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1362,6 +1362,9 @@ MultiXactMember
 MultiXactOffset
 MultiXactStateData
 MultiXactStatus
+MultirangeIOData
+MultirangeParseState
+MultirangeType
 MyData
 NDBOX
 NODE
#119Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Alvaro Herrera (#118)
Re: range_agg

v17 is a rebase fixing a minor parse_coerce.c edit; v16 lasted little
:-( I chose to change the wording of the conflicting comment in
enforce_generic_type_consistency():

* 3) Similarly, if return type is ANYRANGE or ANYMULTIRANGE, and any
* argument is ANYRANGE or ANYMULTIRANGE, use that argument's
* actual type, range type or multirange type as the function's return
* type.

This wording is less precise, in that it doesn't say exactly which of
the three types is the actual result for each of the possible four cases
(r->r, r->m, m->m, m->r) but I think it should be straightforward.

--
�lvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

#120Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Alvaro Herrera (#119)
1 attachment(s)
Re: range_agg

On 2020-Apr-05, Alvaro Herrera wrote:

v17 is a rebase fixing a minor parse_coerce.c edit; v16 lasted little
:-( I chose to change the wording of the conflicting comment in
enforce_generic_type_consistency():

Hm, attached.

--
�lvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

Attachments:

v17-multirange.patchtext/x-diff; charset=us-asciiDownload
diff --git a/doc/src/sgml/extend.sgml b/doc/src/sgml/extend.sgml
index 930aeb767c..febb02e80a 100644
--- a/doc/src/sgml/extend.sgml
+++ b/doc/src/sgml/extend.sgml
@@ -285,6 +285,14 @@
         </entry>
        </row>
 
+       <row>
+        <entry><type>anymultirange</type></entry>
+        <entry>Simple</entry>
+        <entry>Indicates that a function accepts any multirange data type
+        (see <xref linkend="rangetypes"/>)
+        </entry>
+       </row>
+
        <row>
         <entry><type>anycompatible</type></entry>
         <entry>Common</entry>
@@ -343,17 +351,15 @@
      position declared as <type>anyarray</type> can have any array data type,
      but similarly they must all be the same type.  And similarly,
      positions declared as <type>anyrange</type> must all be the same range
-     type.  Furthermore, if there are
+     type.  Likewise for <type>anymultirange</type>.
+    </para>
+
+    <para>
+     Furthermore, if there are
      positions declared <type>anyarray</type> and others declared
      <type>anyelement</type>, the actual array type in the
      <type>anyarray</type> positions must be an array whose elements are
      the same type appearing in the <type>anyelement</type> positions.
-     Similarly, if there are positions declared <type>anyrange</type>
-     and others declared <type>anyelement</type> or <type>anyarray</type>,
-     the actual range type in the <type>anyrange</type> positions must be a
-     range whose subtype is the same type appearing in
-     the <type>anyelement</type> positions and the same as the element type
-     of the <type>anyarray</type> positions.
      <type>anynonarray</type> is treated exactly the same as <type>anyelement</type>,
      but adds the additional constraint that the actual type must not be
      an array type.
@@ -362,6 +368,19 @@
      be an enum type.
     </para>
 
+    <para>
+     Similarly, if there are positions declared <type>anyrange</type>
+     and others declared <type>anyelement</type> or <type>anyarray</type>,
+     the actual range type in the <type>anyrange</type> positions must be a
+     range whose subtype is the same type appearing in
+     the <type>anyelement</type> positions and the same as the element type
+     of the <type>anyarray</type> positions.
+     If there are positions declared <type>anymultirange</type>,
+     their actual multirange type must contain ranges matching parameters declared
+     <type>anyrange</type> and base elements matching parameters declared
+     <type>anyelement</type> and <type>anyarray</type>.
+    </para>
+
     <para>
      Thus, when more than one argument position is declared with a polymorphic
      type, the net effect is that only certain combinations of actual argument
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 4d88b45e72..b2168f3801 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -13609,7 +13609,7 @@ SELECT NULLIF(value, '(none)') ...
        <row>
         <entry>Operator</entry>
         <entry>Description</entry>
-        <entry>Example</entry>
+        <entry>Examples</entry>
         <entry>Result</entry>
        </row>
       </thead>
@@ -14080,12 +14080,14 @@ NULL baz</literallayout>(3 rows)</entry>
   <title>Range Functions and Operators</title>
 
   <para>
-   See <xref linkend="rangetypes"/> for an overview of range types.
+   See <xref linkend="rangetypes"/> for an overview of range and multirange types.
   </para>
 
   <para>
    <xref linkend="range-operators-table"/> shows the operators
-   available for range types.
+   available for range and multirange types.
+   Many of these operators will accept either a range or multirange
+   on either side.
   </para>
 
     <table id="range-operators-table">
@@ -14103,134 +14105,215 @@ NULL baz</literallayout>(3 rows)</entry>
        <row>
         <entry> <literal>=</literal> </entry>
         <entry>equal</entry>
-        <entry><literal>int4range(1,5) = '[1,4]'::int4range</literal></entry>
-        <entry><literal>t</literal></entry>
+        <entry><literallayout class="monospaced">int4range(1,5) = '[1,4]'::int4range
+'{[1,5)}'::int4multirange = '{[1,4]}'::int4multirange</literallayout></entry>
+        <entry><literallayout class="monospaced">t</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>&lt;&gt;</literal> </entry>
         <entry>not equal</entry>
-        <entry><literal>numrange(1.1,2.2) &lt;&gt; numrange(1.1,2.3)</literal></entry>
-        <entry><literal>t</literal></entry>
+        <entry><literallayout class="monospaced">numrange(1.1,2.2) &lt;&gt; numrange(1.1,2.3)
+'{[1.1,2.2)}'::nummultirange &lt;&gt; '{[1.1,2.3)}'::nummultirange</literallayout></entry>
+        <entry><literallayout class="monospaced">t</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>&lt;</literal> </entry>
         <entry>less than</entry>
-        <entry><literal>int4range(1,10) &lt; int4range(2,3)</literal></entry>
-        <entry><literal>t</literal></entry>
+        <entry><literallayout class="monospaced">int4range(1,10) &lt; int4range(2,3)
+'{[1,10)}'::int4multirange &lt; '{[2,3)}'::int4multirange</literallayout>
+        </entry>
+        <entry><literallayout class="monospaced">t</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>&gt;</literal> </entry>
         <entry>greater than</entry>
-        <entry><literal>int4range(1,10) &gt; int4range(1,5)</literal></entry>
-        <entry><literal>t</literal></entry>
+        <entry>
+          <literallayout class="monospaced">int4range(1,10) &gt; int4range(1,5)
+'{[1,10)}'::int4multirange &gt; '{[1,5)}'::int4multirange</literallayout>
+        </entry>
+        <entry><literallayout class="monospaced">t</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>&lt;=</literal> </entry>
         <entry>less than or equal</entry>
-        <entry><literal>numrange(1.1,2.2) &lt;= numrange(1.1,2.2)</literal></entry>
-        <entry><literal>t</literal></entry>
+        <entry>
+          <literallayout class="monospaced">numrange(1.1,2.2) &lt;= numrange(1.1,2.2)
+'{[1.1,2.2)}'::nummultirange &lt;= '{[1.1,2.2)}'::nummultirange</literallayout>
+        </entry>
+        <entry><literallayout class="monospaced">t</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>&gt;=</literal> </entry>
         <entry>greater than or equal</entry>
-        <entry><literal>numrange(1.1,2.2) &gt;= numrange(1.1,2.0)</literal></entry>
-        <entry><literal>t</literal></entry>
+        <entry>
+          <literallayout class="monospaced">numrange(1.1,2.2) &gt;= numrange(1.1,2.0)
+'{[1.1,2.2)}'::nummultirange &gt;= '{[1.1,2.0)}'::nummultirange</literallayout>
+        </entry>
+        <entry><literallayout class="monospaced">t</literallayout></entry>
+       </row>
+
+       <row>
+        <entry> <literal>@&gt;</literal> </entry>
+        <entry>contains multirange</entry>
+        <entry>
+          <literallayout class="monospaced">int4range(2,4) @&gt; '{[2,3)}'::int4multirange
+'{[2,4)}'::int4multirange @&gt; '{[2,3)}'::int4multirange</literallayout>
+        </entry>
+        <entry><literallayout class="monospaced">t</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>@&gt;</literal> </entry>
         <entry>contains range</entry>
-        <entry><literal>int4range(2,4) @&gt; int4range(2,3)</literal></entry>
-        <entry><literal>t</literal></entry>
+        <entry>
+          <literallayout class="monospaced">int4range(2,4) @&gt; int4range(2,3)
+'{[2,4)}'::int4multirange @&gt; int4range(2,3)</literallayout>
+        </entry>
+        <entry><literallayout class="monospaced">t</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>@&gt;</literal> </entry>
         <entry>contains element</entry>
-        <entry><literal>'[2011-01-01,2011-03-01)'::tsrange @&gt; '2011-01-10'::timestamp</literal></entry>
-        <entry><literal>t</literal></entry>
+        <entry>
+          <literallayout class="monospaced">'[2011-01-01,2011-03-01)'::tsrange @&gt; '2011-01-10'::timestamp
+'{[2011-01-01,2011-03-01)}'::tsmultirange @&gt; '2011-01-10'::timestamp</literallayout>
+        </entry>
+        <entry><literallayout class="monospaced">t</literallayout></entry>
+       </row>
+
+       <row>
+        <entry> <literal>&lt;@</literal> </entry>
+        <entry>multirange is contained by</entry>
+        <entry>
+          <literallayout class="monospaced">'{[2,4)}'::int4multirange &lt;@ int4range(1,7)
+'{[2,4)}'::int4multirange &lt;@ '{[1,7)}'::int4multirange</literallayout>
+        </entry>
+        <entry><literallayout class="monospaced">t</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>&lt;@</literal> </entry>
         <entry>range is contained by</entry>
-        <entry><literal>int4range(2,4) &lt;@ int4range(1,7)</literal></entry>
-        <entry><literal>t</literal></entry>
+        <entry>
+          <literallayout class="monospaced">int4range(2,4) &lt;@ int4range(1,7)
+int4range(2,4) &lt;@ '{[1,7)}'::int4multirange</literallayout>
+        </entry>
+        <entry><literallayout class="monospaced">t</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>&lt;@</literal> </entry>
         <entry>element is contained by</entry>
-        <entry><literal>42 &lt;@ int4range(1,7)</literal></entry>
-        <entry><literal>f</literal></entry>
+        <entry>
+          <literallayout class="monospaced">42 &lt;@ int4range(1,7)
+42 &lt;@ '{[1,7)}'::int4multirange</literallayout>
+        </entry>
+        <entry><literallayout class="monospaced">f</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>&amp;&amp;</literal> </entry>
         <entry>overlap (have points in common)</entry>
-        <entry><literal>int8range(3,7) &amp;&amp; int8range(4,12)</literal></entry>
-        <entry><literal>t</literal></entry>
+        <entry>
+          <literallayout class="monospaced">int8range(3,7) &amp;&amp; int8range(4,12)
+int8range(3,7) &amp;&amp; '{[4,12)}'::int8multirange
+'{[3,7)}'::int8multirange &amp;&amp; int8range(4,12)
+'{[3,7)}'::int8multirange &amp;&amp; '{[4,12)}'::int8multirange</literallayout>
+        </entry>
+        <entry><literallayout class="monospaced">t</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>&lt;&lt;</literal> </entry>
         <entry>strictly left of</entry>
-        <entry><literal>int8range(1,10) &lt;&lt; int8range(100,110)</literal></entry>
-        <entry><literal>t</literal></entry>
+        <entry>
+          <literallayout class="monospaced">int8range(1,10) &lt;&lt; int8range(100,110)
+int8range(1,10) &lt;&lt; '{[100,110)}'::int8multirange
+'{[1,10)}'::int8multirange &lt;&lt; int8range(100,110)
+'{[1,10)}'::int8multirange &lt;&lt; '{[100,110)}'::int8multirange</literallayout>
+        </entry>
+        <entry><literallayout class="monospaced">t</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>&gt;&gt;</literal> </entry>
         <entry>strictly right of</entry>
-        <entry><literal>int8range(50,60) &gt;&gt; int8range(20,30)</literal></entry>
-        <entry><literal>t</literal></entry>
+        <entry>
+          <literallayout class="monospaced">int8range(50,60) &gt;&gt; int8range(20,30)
+int8range(50,60) &gt;&gt; '{[20,30)}'::int8multirange
+'{[50,60)}'::int8multirange &gt;&gt; int8range(20,30)
+'{[50,60)}'::int8multirange &gt;&gt; '{[20,30)}'::int8multirange</literallayout>
+        </entry>
+        <entry><literallayout class="monospaced">t</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>&amp;&lt;</literal> </entry>
         <entry>does not extend to the right of</entry>
-        <entry><literal>int8range(1,20) &amp;&lt; int8range(18,20)</literal></entry>
-        <entry><literal>t</literal></entry>
+        <entry>
+          <literallayout class="monospaced">int8range(1,20) &amp;&lt; int8range(18,20)
+int8range(1,20) &amp;&lt; '{[18,20)}'::int8multirange
+'{[1,20)}'::int8multirange &amp;&lt; int8range(18,20)
+'{[1,20)}'::int8multirange &amp;&lt; '{[18,20)}'::int8multirange</literallayout>
+        </entry>
+        <entry><literallayout class="monospaced">t</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>&amp;&gt;</literal> </entry>
         <entry>does not extend to the left of</entry>
-        <entry><literal>int8range(7,20) &amp;&gt; int8range(5,10)</literal></entry>
-        <entry><literal>t</literal></entry>
+        <entry>
+          <literallayout class="monospaced">int8range(7,20) &amp;&gt; int8range(5,10)
+int8range(7,20) &amp;&gt; '{[5,10)}'::int8multirange
+'{[7,20)}'::int8multirange &amp;&gt; int8range(5,10)
+'{[7,20)}'::int8multirange &amp;&gt; '{[5,10)}'::int8multirange</literallayout>
+        </entry>
+        <entry><literallayout class="monospaced">t</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>-|-</literal> </entry>
         <entry>is adjacent to</entry>
-        <entry><literal>numrange(1.1,2.2) -|- numrange(2.2,3.3)</literal></entry>
-        <entry><literal>t</literal></entry>
+        <entry>
+          <literallayout class="monospaced">numrange(1.1,2.2) -|- numrange(2.2,3.3)
+numrange(1.1,2.2) -|- '{[2.2,3.3)}'::nummultirange
+'{[1.1,2.2)}'::nummultirange -|- numrange(2.2,3.3)
+'{[1.1,2.2)}'::nummultirange -|- '{[2.2,3.3)}'::nummultirange</literallayout>
+        </entry>
+        <entry><literallayout class="monospaced">t</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>+</literal> </entry>
         <entry>union</entry>
-        <entry><literal>numrange(5,15) + numrange(10,20)</literal></entry>
-        <entry><literal>[5,20)</literal></entry>
+        <entry><literallayout class="monospaced">numrange(5,15) + numrange(10,20)
+'{[5,10)}'::nummultirange + '{[15,20)}'::nummultirange</literallayout></entry>
+        <entry><literallayout class="monospaced">[5,20)
+{[5,10), [15,20)}</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>*</literal> </entry>
         <entry>intersection</entry>
-        <entry><literal>int8range(5,15) * int8range(10,20)</literal></entry>
-        <entry><literal>[10,15)</literal></entry>
+        <entry><literallayout class="monospaced">int8range(5,15) * int8range(10,20)
+'{[5,15)}'::int8multirange * '{[10,20)}'::int8multirange</literallayout></entry>
+        <entry><literallayout class="monospaced">[10,15)
+{[10,15)}</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>-</literal> </entry>
         <entry>difference</entry>
-        <entry><literal>int8range(5,15) - int8range(10,20)</literal></entry>
-        <entry><literal>[5,10)</literal></entry>
+        <entry><literallayout class="monospaced">int8range(5,15) - int8range(10,20)
+'{[5,20)}'::int8multirange - '{[10,15)}'::int8multirange</literallayout></entry>
+        <entry><literallayout class="monospaced">[5,10)
+{[5,10), [15,20)}</literallayout></entry>
        </row>
 
       </tbody>
@@ -14248,19 +14331,31 @@ NULL baz</literallayout>(3 rows)</entry>
 
   <para>
    The left-of/right-of/adjacent operators always return false when an empty
-   range is involved; that is, an empty range is not considered to be either
-   before or after any other range.
+   range or multirange is involved; that is, an empty range is not considered to
+   be either before or after any other range.
+  </para>
+
+  <para>
+   Elsewhere empty ranges and multiranges are treated as the additive identity:
+   anything unioned with an empty value is itself. Anything minus an empty
+   value is itself. An empty multirange has exactly the same points as an empty
+   range. Every range contains the empty range. Every multirange contains as many
+   empty ranges as you like.
   </para>
 
   <para>
    The union and difference operators will fail if the resulting range would
    need to contain two disjoint sub-ranges, as such a range cannot be
-   represented.
+   represented. There are separate operators for union and difference that take
+   multirange parameters and return a multirange, and they do not fail even if
+   their arguments are disjoint. So if you need a union or difference operation
+   for ranges that may be disjoint, you can avoid errors by first casting your
+   ranges to multiranges.
   </para>
 
   <para>
    <xref linkend="range-functions-table"/> shows the functions
-   available for use with range types.
+   available for use with range and multirange types.
   </para>
 
   <indexterm>
@@ -14309,6 +14404,17 @@ NULL baz</literallayout>(3 rows)</entry>
         <entry><literal>lower(numrange(1.1,2.2))</literal></entry>
         <entry><literal>1.1</literal></entry>
        </row>
+       <row>
+        <entry>
+         <literal>
+          <function>lower</function>(<type>anymultirange</type>)
+         </literal>
+        </entry>
+        <entry>multirange's element type</entry>
+        <entry>lower bound of multirange</entry>
+        <entry><literal>lower('{[1.1,2.2)}'::nummultirange)</literal></entry>
+        <entry><literal>1.1</literal></entry>
+       </row>
        <row>
         <entry>
          <literal>
@@ -14320,6 +14426,17 @@ NULL baz</literallayout>(3 rows)</entry>
         <entry><literal>upper(numrange(1.1,2.2))</literal></entry>
         <entry><literal>2.2</literal></entry>
        </row>
+       <row>
+        <entry>
+         <literal>
+          <function>upper</function>(<type>anymultirange</type>)
+         </literal>
+        </entry>
+        <entry>multirange's element type</entry>
+        <entry>upper bound of multirange</entry>
+        <entry><literal>upper('{[1.1,2.2)}'::nummultirange)</literal></entry>
+        <entry><literal>2.2</literal></entry>
+       </row>
        <row>
         <entry>
          <literal>
@@ -14331,6 +14448,17 @@ NULL baz</literallayout>(3 rows)</entry>
         <entry><literal>isempty(numrange(1.1,2.2))</literal></entry>
         <entry><literal>false</literal></entry>
        </row>
+       <row>
+        <entry>
+         <literal>
+          <function>isempty</function>(<type>anymultirange</type>)
+         </literal>
+        </entry>
+        <entry><type>boolean</type></entry>
+        <entry>is the multirange empty?</entry>
+        <entry><literal>isempty('{[1.1,2.2)}'::nummultirange)</literal></entry>
+        <entry><literal>false</literal></entry>
+       </row>
        <row>
         <entry>
          <literal>
@@ -14342,6 +14470,17 @@ NULL baz</literallayout>(3 rows)</entry>
         <entry><literal>lower_inc(numrange(1.1,2.2))</literal></entry>
         <entry><literal>true</literal></entry>
        </row>
+       <row>
+        <entry>
+         <literal>
+          <function>lower_inc</function>(<type>anymultirange</type>)
+         </literal>
+        </entry>
+        <entry><type>boolean</type></entry>
+        <entry>is the lower bound inclusive?</entry>
+        <entry><literal>lower_inc('{[1.1,2.2)}'::nummultirange)</literal></entry>
+        <entry><literal>true</literal></entry>
+       </row>
        <row>
         <entry>
          <literal>
@@ -14353,6 +14492,17 @@ NULL baz</literallayout>(3 rows)</entry>
         <entry><literal>upper_inc(numrange(1.1,2.2))</literal></entry>
         <entry><literal>false</literal></entry>
        </row>
+       <row>
+        <entry>
+         <literal>
+          <function>upper_inc</function>(<type>anymultirange</type>)
+         </literal>
+        </entry>
+        <entry><type>boolean</type></entry>
+        <entry>is the upper bound inclusive?</entry>
+        <entry><literal>upper_inc('{[1.1,2.2)}'::nummultirange)</literal></entry>
+        <entry><literal>false</literal></entry>
+       </row>
        <row>
         <entry>
          <literal>
@@ -14364,6 +14514,17 @@ NULL baz</literallayout>(3 rows)</entry>
         <entry><literal>lower_inf('(,)'::daterange)</literal></entry>
         <entry><literal>true</literal></entry>
        </row>
+       <row>
+        <entry>
+         <literal>
+          <function>lower_inf</function>(<type>anymultirange</type>)
+         </literal>
+        </entry>
+        <entry><type>boolean</type></entry>
+        <entry>is the lower bound infinite?</entry>
+        <entry><literal>lower_inf('{(,)}'::datemultirange)</literal></entry>
+        <entry><literal>true</literal></entry>
+       </row>
        <row>
         <entry>
          <literal>
@@ -14375,6 +14536,17 @@ NULL baz</literallayout>(3 rows)</entry>
         <entry><literal>upper_inf('(,)'::daterange)</literal></entry>
         <entry><literal>true</literal></entry>
        </row>
+       <row>
+        <entry>
+         <literal>
+          <function>upper_inf</function>(<type>anymultirange</type>)
+         </literal>
+        </entry>
+        <entry><type>boolean</type></entry>
+        <entry>is the upper bound infinite?</entry>
+        <entry><literal>upper_inf('{(,)}'::datemultirange)</literal></entry>
+        <entry><literal>true</literal></entry>
+       </row>
        <row>
         <entry>
          <literal>
@@ -14386,16 +14558,38 @@ NULL baz</literallayout>(3 rows)</entry>
         <entry><literal>range_merge('[1,2)'::int4range, '[3,4)'::int4range)</literal></entry>
         <entry><literal>[1,4)</literal></entry>
        </row>
+       <row>
+        <entry>
+         <literal>
+          <function>range_merge</function>(<type>anymultirange</type>)
+         </literal>
+        </entry>
+        <entry><type>anyrange</type></entry>
+        <entry>the smallest range which includes the entire multirange</entry>
+        <entry><literal>range_merge('{[1,2), [3,4)}'::int4multirange)</literal></entry>
+        <entry><literal>[1,4)</literal></entry>
+       </row>
+       <row>
+        <entry>
+         <literal>
+          <function>multirange</function>(<type>anyrange</type>)
+         </literal>
+        </entry>
+        <entry><type>anymultirange</type></entry>
+        <entry>a multirange containing just the given range</entry>
+        <entry><literal>multirange('[1,2)'::int4range)</literal></entry>
+        <entry><literal>{[1,2)}</literal></entry>
+       </row>
       </tbody>
      </tgroup>
     </table>
 
   <para>
    The <function>lower</function> and  <function>upper</function> functions return null
-   if the range is empty or the requested bound is infinite.
+   if the input range/multirange is empty or the requested bound is infinite.
    The <function>lower_inc</function>, <function>upper_inc</function>,
    <function>lower_inf</function>, and <function>upper_inf</function>
-   functions all return false for an empty range.
+   functions all return false for an empty range or multirange.
   </para>
   </sect1>
 
@@ -14714,6 +14908,44 @@ NULL baz</literallayout>(3 rows)</entry>
       </entry>
      </row>
 
+     <row>
+      <entry>
+       <indexterm>
+        <primary>range_agg</primary>
+       </indexterm>
+       <function>
+         range_agg(<replaceable class="parameter">expression</replaceable>)
+       </function>
+      </entry>
+      <entry>
+       <type>anyrange</type>
+      </entry>
+      <entry>
+       <type>multirange</type> of the argument range type
+      </entry>
+      <entry>Yes</entry>
+      <entry>union of the non-null input values</entry>
+     </row>
+
+     <row>
+      <entry>
+       <indexterm>
+        <primary>range_intersect_agg</primary>
+       </indexterm>
+       <function>
+         range_intersect_agg(<replaceable class="parameter">expression</replaceable>)
+       </function>
+      </entry>
+      <entry>
+       <type>anyrange</type>
+      </entry>
+      <entry>
+       <type>multirange</type> of the argument range type
+      </entry>
+      <entry>No</entry>
+      <entry>intersection of the non-null input values</entry>
+     </row>
+
      <row>
       <entry>
        <indexterm>
diff --git a/doc/src/sgml/rangetypes.sgml b/doc/src/sgml/rangetypes.sgml
index b75fb3a392..c8784bdce3 100644
--- a/doc/src/sgml/rangetypes.sgml
+++ b/doc/src/sgml/rangetypes.sgml
@@ -27,6 +27,13 @@
   ranges from an instrument, and so forth can also be useful.
  </para>
 
+ <para>
+  Every range type has a corresponding multirange type. A multirange is
+  an ordered list of non-continguous, non-empty, non-null ranges. Most
+  range operators also work on multiranges, and they have a few functions
+  of their own.
+ </para>
+
  <sect2 id="rangetypes-builtin">
   <title>Built-in Range Types</title>
 
@@ -232,10 +239,30 @@ SELECT '[4,4]'::int4range;
 SELECT '[4,4)'::int4range;
 </programlisting>
   </para>
+
+  <para>
+   The input for a multirange is curly brackets (<literal>{</literal> and
+   <literal>}</literal>) containing zero or more valid ranges,
+   separated by commas. Whitespace is permitted around the brackets and
+   commas. This is intended to be reminiscent of array syntax, although
+   multiranges are much simpler: they have just one dimension and there is
+   no need to quote their contents. (The bounds of their ranges may be
+   quoted as above however.)
+  </para>
+
+  <para>
+  Examples:
+<programlisting>
+SELECT '{}'::int4multirange;
+SELECT '{[3,7)}'::int4multirange;
+SELECT '{[3,7), [8,9)}'::int4multirange;
+</programlisting>
+  </para>
+
  </sect2>
 
  <sect2 id="rangetypes-construct">
-  <title>Constructing Ranges</title>
+  <title>Constructing Ranges and Multiranges</title>
 
   <para>
    Each range type has a constructor function with the same name as the range
@@ -267,6 +294,19 @@ SELECT int8range(1, 14, '(]');
 
 -- Using NULL for either bound causes the range to be unbounded on that side.
 SELECT numrange(NULL, 2.2);
+</programlisting>
+  </para>
+
+  <para>
+   Each range type also has a multirange constructor with the same name as the
+   multirange type.  The constructor function takes zero or more arguments
+   which are all ranges of the appropriate type.
+   For example:
+
+<programlisting>
+SELECT nummultirange();
+SELECT nummultirange(numrange(1.0, 14.0));
+SELECT nummultirange(numrange(1.0, 14.0), numrange(20.0, 25.0));
 </programlisting>
   </para>
  </sect2>
@@ -341,6 +381,11 @@ SELECT '[1.234, 5.678]'::floatrange;
    function in this example.
   </para>
 
+  <para>
+   When you define your own range you automatically get a corresponding
+   multirange type.
+  </para>
+
   <para>
    Defining your own range type also allows you to specify a different
    subtype B-tree operator class or collation to use, so as to change the sort
diff --git a/src/backend/catalog/pg_range.c b/src/backend/catalog/pg_range.c
index b5bc36c2bd..1217a6ddd0 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_rngmultitypid - 1] = ObjectIdGetDatum(multirangeTypeOid);
 
 	tup = heap_form_tuple(RelationGetDescr(pg_range), values, nulls);
 
@@ -100,6 +102,12 @@ 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 cd56714968..c359f27c74 100644
--- a/src/backend/catalog/pg_type.c
+++ b/src/backend/catalog/pg_type.c
@@ -27,6 +27,7 @@
 #include "catalog/pg_proc.h"
 #include "catalog/pg_type.h"
 #include "commands/typecmds.h"
+#include "mb/pg_wchar.h"
 #include "miscadmin.h"
 #include "parser/scansup.h"
 #include "utils/acl.h"
@@ -36,6 +37,9 @@
 #include "utils/rel.h"
 #include "utils/syscache.h"
 
+static char *makeUniqueTypeName(const char *typeName, Oid typeNamespace,
+								bool tryOriginal);
+
 /* Potentially set by pg_upgrade_support functions */
 Oid			binary_upgrade_next_pg_type_oid = InvalidOid;
 
@@ -809,31 +813,10 @@ RenameTypeInternal(Oid typeOid, const char *newTypeName, Oid typeNamespace)
 char *
 makeArrayTypeName(const char *typeName, Oid typeNamespace)
 {
-	char	   *arr = (char *) palloc(NAMEDATALEN);
-	int			namelen = strlen(typeName);
-	int			i;
+	char	   *arr;
 
-	/*
-	 * 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)
+	arr = makeUniqueTypeName(typeName, typeNamespace, false);
+	if (arr == NULL)
 		ereport(ERROR,
 				(errcode(ERRCODE_DUPLICATE_OBJECT),
 				 errmsg("could not form array type name for type \"%s\"",
@@ -904,3 +887,91 @@ moveArrayTypeName(Oid typeOid, const char *typeName, Oid typeNamespace)
 
 	return true;
 }
+
+
+/*
+ * makeMultirangeTypeName
+ *	  - given a range type name, make a multirange type name for it
+ *
+ * caller is responsible for pfreeing the result
+ */
+char *
+makeMultirangeTypeName(const char *rangeTypeName, Oid typeNamespace)
+{
+	char	   *buf;
+	char	   *mrname;
+	char	   *rangestr;
+
+	/*
+	 * 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...
+	 */
+	rangestr = strstr(rangeTypeName, "range");
+	if (rangestr)
+	{
+		char	*prefix = pnstrdup(rangeTypeName, rangestr - rangeTypeName);
+
+		buf = psprintf("%s%s%s", prefix, "multi", rangestr);
+	}
+	else
+		buf = psprintf("%s_multirange", pnstrdup(rangeTypeName, NAMEDATALEN - 12));
+
+	/* clip it at NAMEDATALEN-1 bytes */
+	buf[pg_mbcliplen(buf, strlen(buf), NAMEDATALEN - 1)] = '\0';
+
+	mrname = makeUniqueTypeName(buf, typeNamespace, true);
+	if (mrname == NULL)
+		ereport(ERROR,
+				(errcode(ERRCODE_DUPLICATE_OBJECT),
+				 errmsg("could not form multirange type name for type \"%s\"",
+						rangeTypeName)));
+
+	return mrname;
+}
+
+/*
+ * makeUniqueTypeName
+ *		Generate a unique name for a prospective new type
+ *
+ * Given a typeName, return a new palloc'ed name by preprending underscores
+ * until a non-conflicting name results.
+ *
+ * If tryOriginal, first try with zero underscores.
+ */
+static char *
+makeUniqueTypeName(const char *typeName, Oid typeNamespace, bool tryOriginal)
+{
+	int			i;
+	int			namelen;
+	char		dest[NAMEDATALEN];
+
+	Assert(strlen(typeName) <= NAMEDATALEN - 1);
+
+	if (tryOriginal &&
+		!SearchSysCacheExists2(TYPENAMENSP,
+							   CStringGetDatum(typeName),
+							   ObjectIdGetDatum(typeNamespace)))
+		return pstrdup(typeName);
+
+	/*
+	 * The idea is to prepend underscores as needed until we make a name that
+	 * doesn't collide with anything ...
+	 */
+	namelen = strlen(typeName);
+	for (i = 1; i < NAMEDATALEN - 1; i++)
+	{
+		dest[i - 1] = '_';
+		strlcpy(dest + i, typeName, NAMEDATALEN - i);
+		if (namelen + i >= NAMEDATALEN)
+			truncate_identifier(dest, NAMEDATALEN, false);
+
+		if (!SearchSysCacheExists2(TYPENAMENSP,
+								   CStringGetDatum(dest),
+								   ObjectIdGetDatum(typeNamespace)))
+			return pstrdup(dest);
+	}
+
+	return NULL;
+}
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index 8891b1d564..dfb2edd3d0 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -105,9 +105,14 @@ typedef struct
 
 /* Potentially set by pg_upgrade_support functions */
 Oid			binary_upgrade_next_array_pg_type_oid = InvalidOid;
+Oid			binary_upgrade_next_mrng_pg_type_oid = InvalidOid;
+Oid			binary_upgrade_next_mrng_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 rangeOid,
+									   Oid rangeArrayOid, Oid *castFuncOid);
 static Oid	findTypeInputFunction(List *procname, Oid typeOid);
 static Oid	findTypeOutputFunction(List *procname, Oid typeOid);
 static Oid	findTypeReceiveFunction(List *procname, Oid typeOid);
@@ -738,7 +743,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",
@@ -1279,6 +1285,11 @@ checkEnumOwner(HeapTuple tup)
 /*
  * DefineRange
  *		Registers a new range type.
+ *
+ * Perhaps it might be worthwhile to set pg_type.typelem to the base type,
+ * and likewise on multiranges to set it to the range type. But having a
+ * non-zero typelem is treated elsewhere as a synonym for being an array,
+ * and users might have queries with that same assumption.
  */
 ObjectAddress
 DefineRange(CreateRangeStmt *stmt)
@@ -1287,7 +1298,11 @@ DefineRange(CreateRangeStmt *stmt)
 	Oid			typeNamespace;
 	Oid			typoid;
 	char	   *rangeArrayName;
+	char	   *multirangeTypeName;
+	char	   *multirangeArrayName;
 	Oid			rangeArrayOid;
+	Oid			multirangeOid;
+	Oid			multirangeArrayOid;
 	Oid			rangeSubtype = InvalidOid;
 	List	   *rangeSubOpclassName = NIL;
 	List	   *rangeCollationName = NIL;
@@ -1304,6 +1319,8 @@ DefineRange(CreateRangeStmt *stmt)
 	AclResult	aclresult;
 	ListCell   *lc;
 	ObjectAddress address;
+	ObjectAddress mltrngaddress;
+	Oid			castFuncOid;
 
 	/* Convert list of names to a name and namespace */
 	typeNamespace = QualifiedNameGetCreationNamespace(stmt->typeName,
@@ -1452,8 +1469,10 @@ DefineRange(CreateRangeStmt *stmt)
 	/* alignment must be TYPALIGN_INT or TYPALIGN_DOUBLE for ranges */
 	alignment = (subtypalign == TYPALIGN_DOUBLE) ? TYPALIGN_DOUBLE : TYPALIGN_INT;
 
-	/* Allocate OID for array type */
+	/* Allocate OID for array type, its multirange, and its multirange array */
 	rangeArrayOid = AssignTypeArrayOid();
+	multirangeOid = AssignTypeMultirangeOid();
+	multirangeArrayOid = AssignTypeMultirangeArrayOid();
 
 	/* Create the pg_type entry */
 	address =
@@ -1491,9 +1510,46 @@ DefineRange(CreateRangeStmt *stmt)
 	Assert(typoid == InvalidOid || typoid == address.objectId);
 	typoid = address.objectId;
 
+	/* Create the multirange that goes with it */
+	multirangeTypeName = makeMultirangeTypeName(typeName, typeNamespace);
+
+	mltrngaddress =
+		TypeCreate(multirangeOid,	/* force assignment of this 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_RANGE,	/* type-category (range 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) */
+	Assert(multirangeOid == mltrngaddress.objectId);
+
 	/* Create the entry in pg_range */
 	RangeCreate(typoid, rangeSubtype, rangeCollation, rangeSubOpclass,
-				rangeCanonical, rangeSubtypeDiff);
+				rangeCanonical, rangeSubtypeDiff, multirangeOid);
 
 	/*
 	 * Create the array type that goes with it.
@@ -1534,8 +1590,53 @@ 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 */
+			   multirangeOid,	/* 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,
+							   multirangeOid, typoid, rangeArrayOid,
+							   &castFuncOid);
+
+	/* Create cast from the range type to its multirange type */
+	CastCreate(typoid, multirangeOid, castFuncOid, 'e', 'f', DEPENDENCY_INTERNAL);
+
+	pfree(multirangeTypeName);
+	pfree(multirangeArrayName);
 
 	return address;
 }
@@ -1613,6 +1714,147 @@ 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.
+ *
+ * Sets castFuncOid to the oid of the new constructor that can be used
+ * to cast from a range to a multirange.
+ */
+static void
+makeMultirangeConstructors(const char *name, Oid namespace,
+						   Oid multirangeOid, Oid rangeOid, Oid rangeArrayOid,
+						   Oid *castFuncOid)
+{
+	ObjectAddress myself,
+				referenced;
+	oidvector  *argtypes;
+	Datum		allParamTypes;
+	ArrayType  *allParameterTypes;
+	Datum		paramModes;
+	ArrayType  *parameterModes;
+
+	referenced.classId = TypeRelationId;
+	referenced.objectId = multirangeOid;
+	referenced.objectSubId = 0;
+
+	/* 0-arg constructor - for empty multiranges */
+	argtypes = buildoidvector(NULL, 0);
+	myself = ProcedureCreate(name,	/* name: same as multirange type */
+							 namespace,
+							 false,	/* replace */
+							 false,	/* returns set */
+							 multirangeOid,	/* return type */
+							 BOOTSTRAP_SUPERUSERID,	/* proowner */
+							 INTERNALlanguageId,	/* language */
+							 F_FMGR_INTERNAL_VALIDATOR,
+							 "multirange_constructor0",	/* prosrc */
+							 NULL,	/* probin */
+							 PROKIND_FUNCTION,
+							 false,	/* security_definer */
+							 false,	/* leakproof */
+							 false, /* isStrict */
+							 PROVOLATILE_IMMUTABLE,	/* volatility */
+							 PROPARALLEL_SAFE,	/* parallel safety */
+							 argtypes,	/* parameterTypes */
+							 PointerGetDatum(NULL),	/* allParameterTypes */
+							 PointerGetDatum(NULL),	/* parameterModes */
+							 PointerGetDatum(NULL),	/* parameterNames */
+							 NIL,	/* parameterDefaults */
+							 PointerGetDatum(NULL),	/* trftypes */
+							 PointerGetDatum(NULL),	/* proconfig */
+							 InvalidOid,	/* prosupport */
+							 1.0,	/* procost */
+							 0.0);	/* prorows */
+	/*
+	 * Make the constructor 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);
+	pfree(argtypes);
+
+	/*
+	 * 1-arg constructor - for casts
+	 *
+	 * In theory we shouldn't need both this and the vararg (n-arg) constructor,
+	 * but having a separate 1-arg function lets us define casts against it.
+	 */
+	argtypes = buildoidvector(&rangeOid, 1);
+	myself = ProcedureCreate(name,	/* name: same as multirange type */
+							 namespace,
+							 false,	/* replace */
+							 false,	/* returns set */
+							 multirangeOid,	/* return type */
+							 BOOTSTRAP_SUPERUSERID,	/* proowner */
+							 INTERNALlanguageId,	/* language */
+							 F_FMGR_INTERNAL_VALIDATOR,
+							 "multirange_constructor1",	/* prosrc */
+							 NULL,	/* probin */
+							 PROKIND_FUNCTION,
+							 false,	/* security_definer */
+							 false,	/* leakproof */
+							 true, /* isStrict */
+							 PROVOLATILE_IMMUTABLE,	/* volatility */
+							 PROPARALLEL_SAFE,	/* parallel safety */
+							 argtypes,	/* parameterTypes */
+							 PointerGetDatum(NULL),	/* allParameterTypes */
+							 PointerGetDatum(NULL),	/* parameterModes */
+							 PointerGetDatum(NULL),	/* parameterNames */
+							 NIL,	/* parameterDefaults */
+							 PointerGetDatum(NULL),	/* trftypes */
+							 PointerGetDatum(NULL),	/* proconfig */
+							 InvalidOid,	/* prosupport */
+							 1.0,	/* procost */
+							 0.0);	/* prorows */
+	/* ditto */
+	recordDependencyOn(&myself, &referenced, DEPENDENCY_INTERNAL);
+	pfree(argtypes);
+	*castFuncOid = myself.objectId;
+
+	/* n-arg constructor - vararg */
+	argtypes = buildoidvector(&rangeArrayOid, 1);
+	allParamTypes = ObjectIdGetDatum(rangeArrayOid);
+	allParameterTypes = construct_array(&allParamTypes,
+										1, OIDOID,
+										sizeof(Oid), true, 'i');
+	paramModes = CharGetDatum(FUNC_PARAM_VARIADIC);
+	parameterModes = construct_array(&paramModes, 1, CHAROID,
+									 1, true, 'c');
+	myself = ProcedureCreate(name,	/* name: same as multirange type */
+							 namespace,
+							 false,	/* replace */
+							 false,	/* returns set */
+							 multirangeOid,	/* return type */
+							 BOOTSTRAP_SUPERUSERID,	/* proowner */
+							 INTERNALlanguageId,	/* language */
+							 F_FMGR_INTERNAL_VALIDATOR,
+							 "multirange_constructor2",	/* prosrc */
+							 NULL,	/* probin */
+							 PROKIND_FUNCTION,
+							 false,	/* security_definer */
+							 false,	/* leakproof */
+							 false, /* isStrict */
+							 PROVOLATILE_IMMUTABLE,	/* volatility */
+							 PROPARALLEL_SAFE,	/* parallel safety */
+							 argtypes,	/* parameterTypes */
+							 PointerGetDatum(allParameterTypes),	/* allParameterTypes */
+							 PointerGetDatum(parameterModes),	/* parameterModes */
+							 PointerGetDatum(NULL),	/* parameterNames */
+							 NIL,	/* parameterDefaults */
+							 PointerGetDatum(NULL),	/* trftypes */
+							 PointerGetDatum(NULL),	/* proconfig */
+							 InvalidOid,	/* prosupport */
+							 1.0,	/* procost */
+							 0.0);	/* prorows */
+	/* ditto */
+	recordDependencyOn(&myself, &referenced, DEPENDENCY_INTERNAL);
+	pfree(argtypes);
+	pfree(allParameterTypes);
+	pfree(parameterModes);
+}
 
 /*
  * Find suitable I/O functions for a type.
@@ -2045,6 +2287,72 @@ AssignTypeArrayOid(void)
 	return type_array_oid;
 }
 
+/*
+ *	AssignTypeMultirangeOid
+ *
+ *	Pre-assign the range type's multirange OID for use in pg_type.oid
+ */
+Oid
+AssignTypeMultirangeOid(void)
+{
+	Oid			type_multirange_oid;
+
+	/* Use binary-upgrade override for pg_type.oid? */
+	if (IsBinaryUpgrade)
+	{
+		if (!OidIsValid(binary_upgrade_next_mrng_pg_type_oid))
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+					 errmsg("pg_type multirange OID value not set when in binary upgrade mode")));
+
+		type_multirange_oid = binary_upgrade_next_mrng_pg_type_oid;
+		binary_upgrade_next_mrng_pg_type_oid = InvalidOid;
+	}
+	else
+	{
+		Relation	pg_type = table_open(TypeRelationId, AccessShareLock);
+
+		type_multirange_oid = GetNewOidWithIndex(pg_type, TypeOidIndexId,
+												 Anum_pg_type_oid);
+		table_close(pg_type, AccessShareLock);
+	}
+
+	return type_multirange_oid;
+}
+
+/*
+ *	AssignTypeMultirangeArrayOid
+ *
+ *	Pre-assign the range type's multirange array OID for use in pg_type.typarray
+ */
+Oid
+AssignTypeMultirangeArrayOid(void)
+{
+	Oid			type_multirange_array_oid;
+
+	/* Use binary-upgrade override for pg_type.oid? */
+	if (IsBinaryUpgrade)
+	{
+		if (!OidIsValid(binary_upgrade_next_mrng_array_pg_type_oid))
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+					 errmsg("pg_type multirange array OID value not set when in binary upgrade mode")));
+
+		type_multirange_array_oid = binary_upgrade_next_mrng_array_pg_type_oid;
+		binary_upgrade_next_mrng_array_pg_type_oid = InvalidOid;
+	}
+	else
+	{
+		Relation	pg_type = table_open(TypeRelationId, AccessShareLock);
+
+		type_multirange_array_oid = GetNewOidWithIndex(pg_type, TypeOidIndexId,
+													   Anum_pg_type_oid);
+		table_close(pg_type, AccessShareLock);
+	}
+
+	return type_multirange_array_oid;
+}
+
 
 /*-------------------------------------------------------------------
  * DefineCompositeType
diff --git a/src/backend/parser/parse_coerce.c b/src/backend/parser/parse_coerce.c
index 6cab20d09a..66552e1078 100644
--- a/src/backend/parser/parse_coerce.c
+++ b/src/backend/parser/parse_coerce.c
@@ -188,6 +188,7 @@ coerce_type(ParseState *pstate, Node *node,
 	if (targetTypeId == ANYARRAYOID ||
 		targetTypeId == ANYENUMOID ||
 		targetTypeId == ANYRANGEOID ||
+		targetTypeId == ANYMULTIRANGEOID ||
 		targetTypeId == ANYCOMPATIBLEARRAYOID ||
 		targetTypeId == ANYCOMPATIBLERANGEOID)
 	{
@@ -195,12 +196,12 @@ coerce_type(ParseState *pstate, Node *node,
 		 * 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 the pseudotype's input
-		 * function, 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
+		 * the pseudotype's input function, 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
@@ -1531,8 +1532,8 @@ coerce_to_common_type(ParseState *pstate, Node *node,
  * 1) All arguments declared ANYELEMENT must have the same datatype.
  * 2) All arguments declared ANYARRAY must have the same datatype,
  *	  which must be a varlena array type.
- * 3) All arguments declared ANYRANGE must have the same datatype,
- *	  which must be a range type.
+ * 3) All arguments declared ANYRANGE or ANYMULTIRANGE must be a range or
+ *	  multirange type, all derived from the same base datatype.
  * 4) If there are arguments of more than one of these polymorphic types,
  *	  the array element type and/or range subtype must be the same as each
  *	  other and the same as the ANYELEMENT type.
@@ -1582,8 +1583,10 @@ check_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			anycompatible_range_typeid = InvalidOid;
 	Oid			anycompatible_range_typelem = InvalidOid;
+	Oid			range_typelem = InvalidOid;
 	bool		have_anynonarray = false;
 	bool		have_anyenum = false;
 	bool		have_anycompatible_nonarray = false;
@@ -1632,6 +1635,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;
+		}
 		else if (decl_type == ANYCOMPATIBLEOID ||
 				 decl_type == ANYCOMPATIBLENONARRAYOID)
 		{
@@ -1722,8 +1734,6 @@ check_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))
 	{
-		Oid			range_typelem;
-
 		range_typelem = get_range_subtype(range_typeid);
 		if (!OidIsValid(range_typelem))
 			return false;		/* should be a range, but isn't */
@@ -1742,6 +1752,45 @@ 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))
+	{
+		Oid			multirange_typelem;
+
+		multirange_typelem = get_multirange_range(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 */
+		}
+		else if (multirange_typelem != range_typeid)
+		{
+			/* otherwise, they better match */
+			return false;
+		}
+
+		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 */
@@ -1820,8 +1869,10 @@ check_generic_type_consistency(const Oid *actual_arg_types,
  *	  argument's actual type as the function's return type.
  * 2) If return type is ANYARRAY, and any argument is ANYARRAY, use the
  *	  argument's actual type as the function's return type.
- * 3) Similarly, if return type is ANYRANGE, and any argument is ANYRANGE,
- *	  use the argument's actual type as the function's return type.
+ * 3) Similarly, if return type is ANYRANGE or ANYMULTIRANGE, and any
+ *	  argument is ANYRANGE or ANYMULTIRANGE, use that argument's
+ *	  actual type, range type or multirange type as the function's return
+ *	  type.
  * 4) Otherwise, if return type is ANYELEMENT or ANYARRAY, and there is
  *	  at least one ANYELEMENT, ANYARRAY, or ANYRANGE input, deduce the
  *	  return type from those inputs, or throw error if we can't.
@@ -1888,10 +1939,13 @@ 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			anycompatible_typeid = InvalidOid;
 	Oid			anycompatible_array_typeid = InvalidOid;
 	Oid			anycompatible_range_typeid = InvalidOid;
 	Oid			anycompatible_range_typelem = InvalidOid;
+	Oid			range_typelem;
+	Oid			multirange_typelem;
 	bool		have_anynonarray = (rettype == ANYNONARRAYOID);
 	bool		have_anyenum = (rettype == ANYENUMOID);
 	bool		have_anycompatible_nonarray = (rettype == ANYCOMPATIBLENONARRAYOID);
@@ -1976,6 +2030,26 @@ enforce_generic_type_consistency(const Oid *actual_arg_types,
 								   format_type_be(actual_type))));
 			range_typeid = actual_type;
 		}
+		else if (decl_type == ANYMULTIRANGEOID)
+		{
+			n_poly_args++;
+			if (actual_type == UNKNOWNOID)
+			{
+				have_poly_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;
+		}
 		else if (decl_type == ANYCOMPATIBLEOID ||
 				 decl_type == ANYCOMPATIBLENONARRAYOID)
 		{
@@ -2112,8 +2186,6 @@ 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))
 		{
-			Oid			range_typelem;
-
 			range_typelem = get_range_subtype(range_typeid);
 			if (!OidIsValid(range_typelem))
 				ereport(ERROR,
@@ -2142,6 +2214,61 @@ enforce_generic_type_consistency(const Oid *actual_arg_types,
 								   format_type_be(elem_typeid))));
 			}
 		}
+		else
+			range_typelem = InvalidOid;
+
+		/* Get the element type based on the multirange type, if we have one */
+		if (OidIsValid(multirange_typeid))
+		{
+			multirange_typelem = get_multirange_range(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))));
+			}
+
+			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 */
+				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))));
+			}
+		}
+		else
+			multirange_typelem = InvalidOid;
 
 		if (!OidIsValid(elem_typeid))
 		{
@@ -2150,6 +2277,7 @@ enforce_generic_type_consistency(const Oid *actual_arg_types,
 				elem_typeid = ANYELEMENTOID;
 				array_typeid = ANYARRAYOID;
 				range_typeid = ANYRANGEOID;
+				multirange_typeid = ANYMULTIRANGEOID;
 			}
 			else
 			{
@@ -2330,6 +2458,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;
+			}
 		}
 	}
 
@@ -2366,6 +2505,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 ANYCOMPATIBLE use the appropriate type */
 	if (rettype == ANYCOMPATIBLEOID ||
 		rettype == ANYCOMPATIBLENONARRAYOID)
@@ -2417,20 +2572,36 @@ check_valid_polymorphic_signature(Oid ret_type,
 								  const Oid *declared_arg_types,
 								  int nargs)
 {
-	if (ret_type == ANYRANGEOID || ret_type == ANYCOMPATIBLERANGEOID)
+	if (ret_type == ANYRANGEOID || ret_type == ANYMULTIRANGEOID)
 	{
 		/*
-		 * ANYRANGE requires an ANYRANGE input, else we can't tell which of
-		 * several range types with the same element type to use.  Likewise
-		 * for ANYCOMPATIBLERANGE.
+		 * ANYRANGE and ANYMULTIRANGE require an ANYRANGE or ANYMULTIRANGE input,
+		 * else we can't tell which of several range types with the same element
+		 * type to use.
+		 */
+		for (int i = 0; i < nargs; i++)
+		{
+			if (declared_arg_types[i] == ANYRANGEOID ||
+				declared_arg_types[i] == ANYMULTIRANGEOID)
+				return NULL;	/* OK */
+		}
+		return psprintf(_("A result of type %s requires at least one input of type anyrange or anymultirange."),
+						format_type_be(ret_type));
+	}
+	else if (ret_type == ANYCOMPATIBLERANGEOID)
+	{
+		/*
+		 * ANYCOMPATIBLERANGE requires an ANYCOMPATIBLERANGE input,
+		 * else we can't tell which of several range types with the same element
+		 * type to use.
 		 */
 		for (int i = 0; i < nargs; i++)
 		{
 			if (declared_arg_types[i] == ret_type)
 				return NULL;	/* OK */
 		}
-		return psprintf(_("A result of type %s requires at least one input of type %s."),
-						format_type_be(ret_type), format_type_be(ret_type));
+		return psprintf(_("A result of type %s requires at least one input of type anycompatiblerange."),
+						format_type_be(ret_type));
 	}
 	else if (IsPolymorphicTypeFamily1(ret_type))
 	{
@@ -2441,7 +2612,7 @@ check_valid_polymorphic_signature(Oid ret_type,
 				return NULL;	/* OK */
 		}
 		/* Keep this list in sync with IsPolymorphicTypeFamily1! */
-		return psprintf(_("A result of type %s requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange."),
+		return psprintf(_("A result of type %s requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange."),
 						format_type_be(ret_type));
 	}
 	else if (IsPolymorphicTypeFamily2(ret_type))
@@ -2593,6 +2764,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..da78227b01
--- /dev/null
+++ b/src/backend/utils/adt/multirangetypes.c
@@ -0,0 +1,2181 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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/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 */
+	FmgrInfo	typioproc;		/* range type's I/O proc */
+	Oid			typioparam;		/* range type's I/O parameter */
+} 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 mltrngtypid,
+												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 with 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;
+	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 = NULL;
+	int32		range_str_len;
+	char	   *range_str_copy;
+
+	cache = get_multirange_io_data(fcinfo, mltrngtypoid, IOFunc_input);
+	rangetyp = cache->typcache->rngtype;
+
+	/* 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 + 1;
+					range_str_copy = pnstrdup(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->typioproc,
+																 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->typioproc, RangeTypePGetDatum(range));
+		appendStringInfoString(&buf, rangeStr);
+		ptr += MAXALIGN(VARSIZE(range));
+		range_count++;
+	}
+
+	appendStringInfoChar(&buf, '}');
+
+	PG_RETURN_CSTRING(buf.data);
+}
+
+/*
+ * Binary representation: First a int32-sized count of ranges, followed by
+ * ranges in their native binary representation.
+ */
+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;
+	uint32		range_count;
+	RangeType **ranges;
+	MultirangeType *ret;
+	StringInfoData tmpbuf;
+
+	cache = get_multirange_io_data(fcinfo, mltrngtypoid, IOFunc_receive);
+
+	range_count = pq_getmsgint(buf, 4);
+	ranges = palloc(range_count * sizeof(RangeType *));
+
+	initStringInfo(&tmpbuf);
+	for (int i = 0; i < range_count; i++)
+	{
+		uint32		range_len = pq_getmsgint(buf, 4);
+		const char *range_data = pq_getmsgbytes(buf, range_len);
+
+		resetStringInfo(&tmpbuf);
+		appendBinaryStringInfo(&tmpbuf, range_data, range_len);
+
+		ranges[i] = DatumGetRangeTypeP(ReceiveFunctionCall(&cache->typioproc,
+														   &tmpbuf,
+														   cache->typioparam,
+														   typmod));
+	}
+	pfree(tmpbuf.data);
+
+	pq_getmsgend(buf);
+
+	ret = make_multirange(mltrngtypoid, cache->typcache->rngtype,
+						  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;
+	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 (int i = 0; i < range_count; i++)
+	{
+		Datum		range;
+
+		range = RangeTypePGetDatum(ranges[i]);
+		range = PointerGetDatum(SendFunctionCall(&cache->typioproc, range));
+
+		pq_sendint32(buf, VARSIZE(range) - VARHDRSZ);
+		pq_sendbytes(buf, VARDATA(range), VARSIZE(range) - VARHDRSZ);
+	}
+
+	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)
+	{
+		Oid			typiofunc;
+		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,
+						 &typiofunc);
+
+		if (!OidIsValid(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(typiofunc, &cache->typioproc,
+					  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;
+	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++)
+		bytelen += MAXALIGN(VARSIZE(ranges[i]));
+
+	/* 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++)
+	{
+		memcpy(ptr, ranges[i], VARSIZE(ranges[i]));
+		ptr += MAXALIGN(VARSIZE(ranges[i]));
+	}
+
+	return multirange;
+}
+
+/*
+ * Converts a list of arbitrary ranges 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_constructor2(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,
+				(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+				 errmsg("multirange values cannot contain NULL members")));
+
+	rangeArray = PG_GETARG_ARRAYTYPE_P(0);
+
+	dims = ARR_NDIM(rangeArray);
+	if (dims > 1)
+		ereport(ERROR,
+				(errcode(ERRCODE_CARDINALITY_VIOLATION),
+				 errmsg("multiranges cannot be constructed from multi-dimensional arrays")));
+
+	rngtypid = ARR_ELEMTYPE(rangeArray);
+	if (rngtypid != rangetyp->type_id)
+		ereport(ERROR,
+				(errcode(ERRCODE_DATATYPE_MISMATCH),
+				 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,
+						(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+						 errmsg("multirange values cannot contain NULL members")));
+
+			/* make_multirange will do its own copy */
+			ranges[i] = DatumGetRangeTypeP(elements[i]);
+		}
+	}
+
+	PG_RETURN_MULTIRANGE_P(make_multirange(mltrngtypid, rangetyp, range_count, ranges));
+}
+
+/*
+ * Construct multirange value from a single range.
+ * It'd be nice if we could just use multirange_constructor2
+ * for this case, but we need a non-variadic single-arg function
+ * to let us define a CAST from a range to its multirange.
+ */
+Datum
+multirange_constructor1(PG_FUNCTION_ARGS)
+{
+	Oid			mltrngtypid = get_fn_expr_rettype(fcinfo->flinfo);
+	Oid			rngtypid;
+	TypeCacheEntry *typcache;
+	TypeCacheEntry *rangetyp;
+	RangeType  *range;
+
+	typcache = multirange_get_typcache(fcinfo, mltrngtypid);
+	rangetyp = typcache->rngtype;
+
+	/*
+	 * These checks should be guaranteed by our signature, but let's do them
+	 * just in case.
+	 */
+
+	if (PG_ARGISNULL(0))
+				ereport(ERROR,
+						(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+						 errmsg("multirange values cannot contain NULL members")));
+
+	range = PG_GETARG_RANGE_P(0);
+
+	/* Make sure the range type matches. */
+	rngtypid = RangeTypeGetOid(range);
+	if (rngtypid != rangetyp->type_id)
+		ereport(ERROR,
+				(errcode(ERRCODE_DATATYPE_MISMATCH),
+				 errmsg("type %u does not match constructor type", rngtypid)));
+
+	PG_RETURN_MULTIRANGE_P(make_multirange(mltrngtypid, rangetyp, 1, &range));
+}
+
+/*
+ * 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
+		elog(ERROR,		/* can't happen */
+			 "niladic multirange constructor must not receive arguments");
+}
+
+
+/* multirange, multirange -> multirange type functions */
+
+/* multirange union */
+Datum
+multirange_union(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
+	MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count1;
+	int32		range_count2;
+	int32		range_count3;
+	RangeType **ranges1;
+	RangeType **ranges2;
+	RangeType **ranges3;
+
+	if (MultirangeIsEmpty(mr1))
+		PG_RETURN_MULTIRANGE_P(mr2);
+	if (MultirangeIsEmpty(mr2))
+		PG_RETURN_MULTIRANGE_P(mr1);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr1));
+
+	multirange_deserialize(mr1, &range_count1, &ranges1);
+	multirange_deserialize(mr2, &range_count2, &ranges2);
+
+	range_count3 = range_count1 + range_count2;
+	ranges3 = palloc0(range_count3 * sizeof(RangeType *));
+	memcpy(ranges3, ranges1, range_count1 * sizeof(RangeType *));
+	memcpy(ranges3 + range_count1, ranges2, range_count2 * sizeof(RangeType *));
+	PG_RETURN_MULTIRANGE_P(make_multirange(typcache->type_id, typcache->rngtype,
+										   range_count3, ranges3));
+}
+
+/* multirange minus */
+Datum
+multirange_minus(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
+	MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
+	Oid			mltrngtypoid = MultirangeTypeGetOid(mr1);
+	TypeCacheEntry *typcache;
+	TypeCacheEntry *rangetyp;
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+
+	typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
+	rangetyp = typcache->rngtype;
+
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		PG_RETURN_MULTIRANGE_P(mr1);
+
+	multirange_deserialize(mr1, &range_count1, &ranges1);
+	multirange_deserialize(mr2, &range_count2, &ranges2);
+
+	PG_RETURN_MULTIRANGE_P(multirange_minus_internal(mltrngtypoid,
+													 rangetyp,
+													 range_count1,
+													 ranges1,
+													 range_count2,
+													 ranges2));
+}
+
+MultirangeType *
+multirange_minus_internal(Oid mltrngtypoid, TypeCacheEntry *rangetyp,
+						  int32 range_count1, RangeType **ranges1,
+						  int32 range_count2, RangeType **ranges2)
+{
+	RangeType  *r1;
+	RangeType  *r2;
+	RangeType **ranges3;
+	int32		range_count3;
+	int32		i1;
+	int32		i2;
+
+	/*
+	 * Worst case: every range in ranges1 makes a different cut to some range
+	 * in ranges2.
+	 */
+	ranges3 = palloc0((range_count1 + range_count2) * sizeof(RangeType *));
+	range_count3 = 0;
+
+	/*
+	 * For each range in mr1, keep subtracting until it's gone or the ranges
+	 * in mr2 have passed it. After a subtraction we assign what's left back
+	 * to r1. The parallel progress through mr1 and mr2 is similar to
+	 * multirange_overlaps_multirange_internal.
+	 */
+	r2 = ranges2[0];
+	for (i1 = 0, i2 = 0; i1 < range_count1; i1++)
+	{
+		r1 = ranges1[i1];
+
+		/* Discard r2s while r2 << r1 */
+		while (r2 != NULL && range_before_internal(rangetyp, r2, r1))
+		{
+			r2 = ++i2 >= range_count2 ? NULL : ranges2[i2];
+		}
+
+		while (r2 != NULL)
+		{
+			if (range_split_internal(rangetyp, r1, r2, &ranges3[range_count3], &r1))
+			{
+				/*
+				 * If r2 takes a bite out of the middle of r1, we need two
+				 * outputs
+				 */
+				range_count3++;
+				r2 = ++i2 >= range_count2 ? NULL : ranges2[i2];
+
+			}
+			else if (range_overlaps_internal(rangetyp, r1, r2))
+			{
+				/*
+				 * If r2 overlaps r1, replace r1 with r1 - r2.
+				 */
+				r1 = range_minus_internal(rangetyp, r1, r2);
+
+				/*
+				 * If r2 goes past r1, then we need to stay with it, in case
+				 * it hits future r1s. Otherwise we need to keep r1, in case
+				 * future r2s hit it. Since we already subtracted, there's no
+				 * point in using the overright/overleft calls.
+				 */
+				if (RangeIsEmpty(r1) || range_before_internal(rangetyp, r1, r2))
+					break;
+				else
+					r2 = ++i2 >= range_count2 ? NULL : ranges2[i2];
+
+			}
+			else
+			{
+				/*
+				 * This and all future r2s are past r1, so keep them. Also
+				 * assign whatever is left of r1 to the result.
+				 */
+				break;
+			}
+		}
+
+		/*
+		 * Nothing else can remove anything from r1, so keep it. Even if r1 is
+		 * empty here, make_multirange will remove it.
+		 */
+		ranges3[range_count3++] = r1;
+	}
+
+	return make_multirange(mltrngtypoid, rangetyp, range_count3, ranges3);
+}
+
+/* multirange intersection */
+Datum
+multirange_intersect(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
+	MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
+	Oid			mltrngtypoid = MultirangeTypeGetOid(mr1);
+	TypeCacheEntry *typcache;
+	TypeCacheEntry *rangetyp;
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+
+	typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
+	rangetyp = typcache->rngtype;
+
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		PG_RETURN_MULTIRANGE_P(make_empty_multirange(mltrngtypoid, rangetyp));
+
+	multirange_deserialize(mr1, &range_count1, &ranges1);
+	multirange_deserialize(mr2, &range_count2, &ranges2);
+
+	PG_RETURN_MULTIRANGE_P(multirange_intersect_internal(mltrngtypoid,
+														 rangetyp,
+														 range_count1,
+														 ranges1,
+														 range_count2,
+														 ranges2));
+}
+
+MultirangeType *
+multirange_intersect_internal(Oid mltrngtypoid, TypeCacheEntry *rangetyp,
+							  int32 range_count1, RangeType **ranges1,
+							  int32 range_count2, RangeType **ranges2)
+{
+	RangeType  *r1;
+	RangeType  *r2;
+	RangeType **ranges3;
+	int32		range_count3;
+	int32		i1;
+	int32		i2;
+
+	if (range_count1 == 0 || range_count2 == 0)
+		return make_multirange(mltrngtypoid, rangetyp, 0, NULL);
+
+	/*-----------------------------------------------
+	 * Worst case is a stitching pattern like this:
+	 *
+	 * mr1: --- --- --- ---
+	 * mr2:   --- --- ---
+	 * mr3:   - - - - - -
+	 *
+	 * That seems to be range_count1 + range_count2 - 1,
+	 * but one extra won't hurt.
+	 *-----------------------------------------------
+	 */
+	ranges3 = palloc0((range_count1 + range_count2) * sizeof(RangeType *));
+	range_count3 = 0;
+
+	/*
+	 * For each range in mr1, keep intersecting until the ranges in mr2 have
+	 * passed it. The parallel progress through mr1 and mr2 is similar to
+	 * multirange_minus_multirange_internal, but we don't have to assign back
+	 * to r1.
+	 */
+	r2 = ranges2[0];
+	for (i1 = 0, i2 = 0; i1 < range_count1; i1++)
+	{
+		r1 = ranges1[i1];
+
+		/* Discard r2s while r2 << r1 */
+		while (r2 != NULL && range_before_internal(rangetyp, r2, r1))
+		{
+			r2 = ++i2 >= range_count2 ? NULL : ranges2[i2];
+		}
+
+		while (r2 != NULL)
+		{
+			if (range_overlaps_internal(rangetyp, r1, r2))
+			{
+				/* Keep the overlapping part */
+				ranges3[range_count3++] = range_intersect_internal(rangetyp, r1, r2);
+
+				/* If we "used up" all of r2, go to the next one... */
+				if (range_overleft_internal(rangetyp, r2, r1))
+					r2 = ++i2 >= range_count2 ? NULL : ranges2[i2];
+
+				/* ...otherwise go to the next r1 */
+				else
+					break;
+			}
+			else
+				/* We're past r1, so move to the next one */
+				break;
+		}
+
+		/* If we're out of r2s, there can be no more intersections */
+		if (r2 == NULL)
+			break;
+	}
+
+	return make_multirange(mltrngtypoid, rangetyp, range_count3, ranges3);
+}
+
+/*
+ * range_agg_transfn: combine adjacent/overlapping ranges.
+ *
+ * All we do here is gather the input ranges into an array
+ * so that the finalfn can sort and combine them.
+ */
+Datum
+range_agg_transfn(PG_FUNCTION_ARGS)
+{
+	MemoryContext aggContext;
+	Oid			rngtypoid;
+	ArrayBuildState *state;
+
+	if (!AggCheckCallContext(fcinfo, &aggContext))
+		elog(ERROR, "range_agg_transfn called in non-aggregate context");
+
+	rngtypoid = get_fn_expr_argtype(fcinfo->flinfo, 1);
+	if (!type_is_range(rngtypoid))
+		ereport(ERROR,
+				(errcode(ERRCODE_DATATYPE_MISMATCH),
+				 errmsg("range_agg must be called with a range")));
+
+	if (PG_ARGISNULL(0))
+		state = initArrayResult(rngtypoid, aggContext, false);
+	else
+		state = (ArrayBuildState *) PG_GETARG_POINTER(0);
+
+	/* skip NULLs */
+	if (!PG_ARGISNULL(1))
+		accumArrayResult(state, PG_GETARG_DATUM(1), false, rngtypoid, aggContext);
+
+	PG_RETURN_POINTER(state);
+}
+
+/*
+ * range_agg_finalfn: use our internal array to merge touching ranges.
+ */
+Datum
+range_agg_finalfn(PG_FUNCTION_ARGS)
+{
+	MemoryContext aggContext;
+	Oid			mltrngtypoid;
+	TypeCacheEntry *typcache;
+	ArrayBuildState *state;
+	int32		range_count;
+	RangeType **ranges;
+	int			i;
+
+	if (!AggCheckCallContext(fcinfo, &aggContext))
+		elog(ERROR, "range_agg_finalfn called in non-aggregate context");
+
+	state = PG_ARGISNULL(0) ? NULL : (ArrayBuildState *) PG_GETARG_POINTER(0);
+	if (state == NULL)
+		/* This shouldn't be possible, but just in case.... */
+		PG_RETURN_NULL();
+
+	/* Also return NULL if we had zero inputs, like other aggregates */
+	range_count = state->nelems;
+	if (range_count == 0)
+		PG_RETURN_NULL();
+
+	mltrngtypoid = get_fn_expr_rettype(fcinfo->flinfo);
+	typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
+
+	ranges = palloc0(range_count * sizeof(RangeType *));
+	for (i = 0; i < range_count; i++)
+		ranges[i] = DatumGetRangeTypeP(state->dvalues[i]);
+
+	PG_RETURN_MULTIRANGE_P(make_multirange(mltrngtypoid, typcache->rngtype, range_count, ranges));
+}
+
+Datum
+multirange_intersect_agg_transfn(PG_FUNCTION_ARGS)
+{
+	MemoryContext aggContext;
+	Oid			mltrngtypoid;
+	TypeCacheEntry *typcache;
+	MultirangeType *result;
+	MultirangeType *current;
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+
+	if (!AggCheckCallContext(fcinfo, &aggContext))
+		elog(ERROR, "multirange_intersect_agg_transfn called in non-aggregate context");
+
+	mltrngtypoid = get_fn_expr_argtype(fcinfo->flinfo, 1);
+	if (!type_is_multirange(mltrngtypoid))
+		ereport(ERROR,
+				(errcode(ERRCODE_DATATYPE_MISMATCH),
+				 errmsg("range_intersect_agg must be called with a multirange")));
+
+	typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
+
+	/* strictness ensures these are non-null */
+	result = PG_GETARG_MULTIRANGE_P(0);
+	current = PG_GETARG_MULTIRANGE_P(1);
+
+	multirange_deserialize(result, &range_count1, &ranges1);
+	multirange_deserialize(current, &range_count2, &ranges2);
+
+	result = multirange_intersect_internal(mltrngtypoid,
+										   typcache->rngtype,
+										   range_count1,
+										   ranges1,
+										   range_count2,
+										   ranges2);
+	PG_RETURN_RANGE_P(result);
+}
+
+
+/* multirange -> element type functions */
+
+/* extract lower bound value */
+Datum
+multirange_lower(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+	bool		isnull;
+	Datum		result;
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_NULL();
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	result = range_lower_internal(typcache->rngtype, ranges[0], &isnull);
+
+	if (isnull)
+		PG_RETURN_NULL();
+	else
+		PG_RETURN_DATUM(result);
+}
+
+/* extract upper bound value */
+Datum
+multirange_upper(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+	bool		isnull;
+	Datum		result;
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_NULL();
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	result = range_upper_internal(typcache->rngtype, ranges[range_count - 1], &isnull);
+
+	if (isnull)
+		PG_RETURN_NULL();
+	else
+		PG_RETURN_DATUM(result);
+}
+
+
+/* multirange -> bool functions */
+
+/* is multirange empty? */
+Datum
+multirange_empty(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+
+	PG_RETURN_BOOL(mr->rangeCount == 0);
+}
+
+/* is lower bound inclusive? */
+Datum
+multirange_lower_inc(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	int32		range_count;
+	RangeType **ranges;
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_has_flag(ranges[0], RANGE_LB_INC));
+}
+
+/* is upper bound inclusive? */
+Datum
+multirange_upper_inc(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	int32		range_count;
+	RangeType **ranges;
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_has_flag(ranges[range_count - 1], RANGE_UB_INC));
+}
+
+/* is lower bound infinite? */
+Datum
+multirange_lower_inf(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	int32		range_count;
+	RangeType **ranges;
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_has_flag(ranges[0], RANGE_LB_INF));
+}
+
+/* is upper bound infinite? */
+Datum
+multirange_upper_inf(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	int32		range_count;
+	RangeType **ranges;
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_has_flag(ranges[range_count - 1], RANGE_UB_INF));
+}
+
+
+
+/* multirange, element -> bool functions */
+
+/* contains? */
+Datum
+multirange_contains_elem(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	Datum		val = PG_GETARG_DATUM(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(multirange_contains_elem_internal(typcache, mr, val));
+}
+
+/* contained by? */
+Datum
+elem_contained_by_multirange(PG_FUNCTION_ARGS)
+{
+	Datum		val = PG_GETARG_DATUM(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(multirange_contains_elem_internal(typcache, mr, val));
+}
+
+/*
+ * Test whether multirange mr contains a specific element value.
+ */
+bool
+multirange_contains_elem_internal(TypeCacheEntry *typcache, MultirangeType *mr, Datum val)
+{
+	TypeCacheEntry *rangetyp;
+	int32		range_count;
+	RangeType **ranges;
+	RangeType  *r;
+	int			i;
+
+	rangetyp = typcache->rngtype;
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	for (i = 0; i < range_count; i++)
+	{
+		r = ranges[i];
+		if (range_contains_elem_internal(rangetyp, r, val))
+			return true;
+	}
+
+	return false;
+}
+
+/* multirange, range -> bool functions */
+
+/* contains? */
+Datum
+multirange_contains_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(multirange_contains_range_internal(typcache, mr, r));
+}
+
+/* contained by? */
+Datum
+range_contained_by_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(multirange_contains_range_internal(typcache, mr, r));
+}
+
+/*
+ * Test whether multirange mr contains a specific range r.
+ */
+bool
+multirange_contains_range_internal(TypeCacheEntry *typcache, MultirangeType *mr, RangeType *r)
+{
+	TypeCacheEntry *rangetyp;
+	int32		range_count;
+	RangeType **ranges;
+	RangeType  *mrr;
+	int			i;
+
+	rangetyp = typcache->rngtype;
+
+	/*
+	 * Every multirange contains an infinite number of empty ranges, even an
+	 * empty one.
+	 */
+	if (RangeIsEmpty(r))
+		return true;
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	for (i = 0; i < range_count; i++)
+	{
+		mrr = ranges[i];
+		if (range_contains_internal(rangetyp, mrr, r))
+			return true;
+	}
+
+	return false;
+}
+
+
+/* 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));
+}
+
+/* overlaps? */
+Datum
+range_overlaps_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(range_overlaps_multirange_internal(typcache, r, mr));
+}
+
+Datum
+multirange_overlaps_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(range_overlaps_multirange_internal(typcache, r, mr));
+}
+
+Datum
+multirange_overlaps_multirange(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_overlaps_multirange_internal(typcache, mr1, mr2));
+}
+
+bool
+range_overlaps_multirange_internal(TypeCacheEntry *typcache, RangeType *r, MultirangeType *mr)
+{
+	TypeCacheEntry *rangetyp;
+	int32		range_count;
+	int32		i;
+	RangeType **ranges;
+	RangeType  *mrr;
+
+	/*
+	 * Empties never overlap, even with empties. (This seems strange since
+	 * they *do* contain each other, but we want to follow how ranges work.)
+	 */
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		return false;
+
+	rangetyp = typcache->rngtype;
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	/* Scan through mrr and once it gets to r they either overlap or not */
+	mrr = ranges[0];
+
+	/* Discard mrrs while mrr << r */
+	i = 0;
+	while (range_before_internal(rangetyp, mrr, r))
+	{
+		if (++i >= range_count)
+			return false;
+		mrr = ranges[i];
+	}
+
+	/* Now either we overlap or we passed r */
+	return range_overlaps_internal(rangetyp, mrr, r);
+}
+
+bool
+multirange_overlaps_multirange_internal(TypeCacheEntry *typcache, MultirangeType *mr1,
+										MultirangeType *mr2)
+{
+	TypeCacheEntry *rangetyp;
+	int32		range_count1;
+	int32		range_count2;
+	int32		i1;
+	int32		i2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+	RangeType  *r1;
+	RangeType  *r2;
+
+	/*
+	 * Empties never overlap, even with empties. (This seems strange since
+	 * they *do* contain each other, but we want to follow how ranges work.)
+	 */
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		return false;
+
+	rangetyp = typcache->rngtype;
+
+	multirange_deserialize(mr1, &range_count1, &ranges1);
+	multirange_deserialize(mr2, &range_count2, &ranges2);
+
+	/*
+	 * Every range in mr1 gets a chance to overlap with the ranges in mr2, but
+	 * we can use their ordering to avoid O(n^2). This is similar to
+	 * range_overlaps_multirange where r1 : r2 :: mrr : r, but there if we
+	 * don't find an overlap with r we're done, and here if we don't find an
+	 * overlap with r2 we try the next r2.
+	 */
+	r1 = ranges1[0];
+	for (i1 = 0, i2 = 0; i2 < range_count2; i2++)
+	{
+		r2 = ranges2[i2];
+
+		/* Discard r1s while r1 << r2 */
+		while (range_before_internal(rangetyp, r1, r2))
+		{
+			if (++i1 >= range_count1)
+				return false;
+			r1 = ranges1[i1];
+		}
+
+		/*
+		 * If r1 && r2, we're done, otherwise we failed to find an overlap for
+		 * r2, so go to the next one.
+		 */
+		if (range_overlaps_internal(rangetyp, r1, r2))
+			return true;
+	}
+
+	/* We looked through all of mr2 without finding an overlap */
+	return false;
+}
+
+/* does not extend to right of? */
+Datum
+range_overleft_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_overleft_internal(typcache->rngtype, r, ranges[range_count - 1]));
+}
+
+Datum
+multirange_overleft_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_overleft_internal(typcache->rngtype,
+										   ranges[range_count - 1], r));
+}
+
+Datum
+multirange_overleft_multirange(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
+	MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr1));
+
+	multirange_deserialize(mr1, &range_count1, &ranges1);
+	multirange_deserialize(mr2, &range_count2, &ranges2);
+
+	PG_RETURN_BOOL(range_overleft_internal(typcache->rngtype,
+										   ranges1[range_count1 - 1],
+										   ranges2[range_count2 - 1]));
+}
+
+/* does not extend to left of? */
+Datum
+range_overright_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_overright_internal(typcache->rngtype, r, ranges[0]));
+}
+
+Datum
+multirange_overright_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_overright_internal(typcache->rngtype, ranges[0], r));
+}
+
+Datum
+multirange_overright_multirange(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
+	MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr1));
+
+	multirange_deserialize(mr1, &range_count1, &ranges1);
+	multirange_deserialize(mr2, &range_count2, &ranges2);
+
+	PG_RETURN_BOOL(range_overright_internal(typcache->rngtype,
+											ranges1[0],
+											ranges2[0]));
+}
+
+/* contains? */
+Datum
+multirange_contains_multirange(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_contains_multirange_internal(typcache, mr1, mr2));
+}
+
+/* contained by? */
+Datum
+multirange_contained_by_multirange(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_contains_multirange_internal(typcache, mr2, mr1));
+}
+
+/*
+ * Test whether multirange mr1 contains every range from another multirange mr2.
+ */
+bool
+multirange_contains_multirange_internal(TypeCacheEntry *typcache,
+										MultirangeType *mr1, MultirangeType *mr2)
+{
+	TypeCacheEntry *rangetyp;
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+	RangeType  *r1;
+	RangeType  *r2;
+	int			i1,
+				i2;
+
+	rangetyp = typcache->rngtype;
+
+	multirange_deserialize(mr1, &range_count1, &ranges1);
+	multirange_deserialize(mr2, &range_count2, &ranges2);
+
+	/*
+	 * We follow the same logic for empties as ranges: - an empty multirange
+	 * contains an empty range/multirange. - an empty multirange can't contain
+	 * any other range/multirange. - an empty multirange is contained by any
+	 * other range/multirange.
+	 */
+
+	if (range_count2 == 0)
+		return true;
+	if (range_count1 == 0)
+		return false;
+
+	/*
+	 * Every range in mr2 must be contained by some range in mr1. To avoid
+	 * O(n^2) we walk through both ranges in tandem.
+	 */
+	r1 = ranges1[0];
+	for (i1 = 0, i2 = 0; i2 < range_count2; i2++)
+	{
+		r2 = ranges2[i2];
+
+		/* Discard r1s while r1 << r2 */
+		while (range_before_internal(rangetyp, r1, r2))
+		{
+			if (++i1 >= range_count1)
+				return false;
+			r1 = ranges1[i1];
+		}
+
+		/*
+		 * If r1 @> r2, go to the next r2, otherwise return false (since every
+		 * r1[n] and r1[n+1] must have a gap). Note this will give weird
+		 * answers if you don't canonicalize, e.g. with a custom
+		 * int2multirange {[1,1], [2,2]} there is a "gap". But that is
+		 * consistent with other range operators, e.g. '[1,1]'::int2range -|-
+		 * '[2,2]'::int2range is false.
+		 */
+		if (!range_contains_internal(rangetyp, r1, r2))
+			return false;
+	}
+
+	/* All ranges in mr2 are satisfied */
+	return true;
+}
+
+/* strictly left of? */
+Datum
+range_before_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(range_before_multirange_internal(typcache, r, mr));
+}
+
+Datum
+multirange_before_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(range_after_multirange_internal(typcache, r, mr));
+}
+
+Datum
+multirange_before_multirange(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_before_multirange_internal(typcache, mr1, mr2));
+}
+
+/* strictly right of? */
+Datum
+range_after_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(range_after_multirange_internal(typcache, r, mr));
+}
+
+Datum
+multirange_after_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(range_before_multirange_internal(typcache, r, mr));
+}
+
+Datum
+multirange_after_multirange(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_before_multirange_internal(typcache, mr2, mr1));
+}
+
+/* strictly left of? (internal version) */
+bool
+range_before_multirange_internal(TypeCacheEntry *typcache, RangeType *r,
+								 MultirangeType *mr)
+{
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		return false;
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	return range_before_internal(typcache->rngtype, r, ranges[0]);
+}
+
+bool
+multirange_before_multirange_internal(TypeCacheEntry *typcache, MultirangeType *mr1,
+									  MultirangeType *mr2)
+{
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		return false;
+
+	multirange_deserialize(mr1, &range_count1, &ranges1);
+	multirange_deserialize(mr2, &range_count2, &ranges2);
+
+	return range_before_internal(typcache->rngtype, ranges1[range_count1 - 1],
+								 ranges2[0]);
+}
+
+/* strictly right of? (internal version) */
+bool
+range_after_multirange_internal(TypeCacheEntry *typcache, RangeType *r,
+								MultirangeType *mr)
+{
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		return false;
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	return range_after_internal(typcache->rngtype, r, ranges[range_count - 1]);
+}
+
+/* adjacent to? */
+Datum
+range_adjacent_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		return false;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_adjacent_internal(typcache->rngtype, r, ranges[0]));
+}
+
+Datum
+multirange_adjacent_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		return false;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_adjacent_internal(typcache->rngtype, ranges[range_count - 1], r));
+}
+
+Datum
+multirange_adjacent_multirange(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
+	MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		return false;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr1));
+
+	multirange_deserialize(mr1, &range_count1, &ranges1);
+	multirange_deserialize(mr2, &range_count2, &ranges2);
+
+	PG_RETURN_BOOL(range_adjacent_internal(typcache->rngtype, ranges1[range_count1 - 1], ranges2[0]));
+}
+
+/* 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);
+}
+
+/* multirange -> range functions */
+
+/* Find the smallest range that includes everything in the multirange */
+Datum
+range_merge_from_multirange(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	Oid			mltrngtypoid = MultirangeTypeGetOid(mr);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_RANGE_P(make_empty_range(typcache->rngtype));
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_RANGE_P(range_union_internal(typcache->rngtype, ranges[0],
+										   ranges[range_count - 1], false));
+}
+
+/* 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/pg_upgrade_support.c b/src/backend/utils/adt/pg_upgrade_support.c
index 18f2ee8226..248ea7f44a 100644
--- a/src/backend/utils/adt/pg_upgrade_support.c
+++ b/src/backend/utils/adt/pg_upgrade_support.c
@@ -51,6 +51,28 @@ binary_upgrade_set_next_array_pg_type_oid(PG_FUNCTION_ARGS)
 	PG_RETURN_VOID();
 }
 
+Datum
+binary_upgrade_set_next_multirange_pg_type_oid(PG_FUNCTION_ARGS)
+{
+	Oid			typoid = PG_GETARG_OID(0);
+
+	CHECK_IS_BINARY_UPGRADE;
+	binary_upgrade_next_mrng_pg_type_oid = typoid;
+
+	PG_RETURN_VOID();
+}
+
+Datum
+binary_upgrade_set_next_multirange_array_pg_type_oid(PG_FUNCTION_ARGS)
+{
+	Oid			typoid = PG_GETARG_OID(0);
+
+	CHECK_IS_BINARY_UPGRADE;
+	binary_upgrade_next_mrng_array_pg_type_oid = typoid;
+
+	PG_RETURN_VOID();
+}
+
 Datum
 binary_upgrade_set_next_toast_pg_type_oid(PG_FUNCTION_ARGS)
 {
diff --git a/src/backend/utils/adt/pseudotypes.c b/src/backend/utils/adt/pseudotypes.c
index 3d6b2f9093..4fcd8ce6de 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"
 
 
 /*
@@ -227,6 +228,30 @@ anycompatiblerange_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
  *
diff --git a/src/backend/utils/adt/rangetypes.c b/src/backend/utils/adt/rangetypes.c
index 01ad8bc240..0af3c67296 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
 {
@@ -431,19 +429,37 @@ range_lower(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
 	TypeCacheEntry *typcache;
+	bool		isnull;
+	Datum		result;
+
+	typcache = range_get_typcache(fcinfo, RangeTypeGetOid(r1));
+
+	result = range_lower_internal(typcache, r1, &isnull);
+
+	if (isnull)
+		PG_RETURN_NULL();
+	else
+		PG_RETURN_DATUM(result);
+}
+
+Datum
+range_lower_internal(TypeCacheEntry *typcache, const RangeType *r1, bool *isnull)
+{
 	RangeBound	lower;
 	RangeBound	upper;
 	bool		empty;
 
-	typcache = range_get_typcache(fcinfo, RangeTypeGetOid(r1));
-
 	range_deserialize(typcache, r1, &lower, &upper, &empty);
 
 	/* Return NULL if there's no finite lower bound */
 	if (empty || lower.infinite)
-		PG_RETURN_NULL();
+	{
+		*isnull = true;
+		return 0;
+	}
 
-	PG_RETURN_DATUM(lower.val);
+	*isnull = false;
+	return lower.val;
 }
 
 /* extract upper bound value */
@@ -452,19 +468,37 @@ range_upper(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
 	TypeCacheEntry *typcache;
+	bool		isnull;
+	Datum		result;
+
+	typcache = range_get_typcache(fcinfo, RangeTypeGetOid(r1));
+
+	result = range_upper_internal(typcache, r1, &isnull);
+
+	if (isnull)
+		PG_RETURN_NULL();
+	else
+		PG_RETURN_DATUM(result);
+}
+
+Datum
+range_upper_internal(TypeCacheEntry *typcache, const RangeType *r1, bool *isnull)
+{
 	RangeBound	lower;
 	RangeBound	upper;
 	bool		empty;
 
-	typcache = range_get_typcache(fcinfo, RangeTypeGetOid(r1));
-
 	range_deserialize(typcache, r1, &lower, &upper, &empty);
 
 	/* Return NULL if there's no finite upper bound */
 	if (empty || upper.infinite)
-		PG_RETURN_NULL();
+	{
+		*isnull = true;
+		return 0;
+	}
 
-	PG_RETURN_DATUM(upper.val);
+	*isnull = false;
+	return upper.val;
 }
 
 
@@ -475,9 +509,8 @@ Datum
 range_empty(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
-	char		flags = range_get_flags(r1);
 
-	PG_RETURN_BOOL(flags & RANGE_EMPTY);
+	PG_RETURN_BOOL(range_has_flag(r1, RANGE_EMPTY));
 }
 
 /* is lower bound inclusive? */
@@ -485,9 +518,8 @@ Datum
 range_lower_inc(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
-	char		flags = range_get_flags(r1);
 
-	PG_RETURN_BOOL(flags & RANGE_LB_INC);
+	PG_RETURN_BOOL(range_has_flag(r1, RANGE_LB_INC));
 }
 
 /* is upper bound inclusive? */
@@ -495,9 +527,8 @@ Datum
 range_upper_inc(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
-	char		flags = range_get_flags(r1);
 
-	PG_RETURN_BOOL(flags & RANGE_UB_INC);
+	PG_RETURN_BOOL(range_has_flag(r1, RANGE_UB_INC));
 }
 
 /* is lower bound infinite? */
@@ -505,9 +536,8 @@ Datum
 range_lower_inf(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
-	char		flags = range_get_flags(r1);
 
-	PG_RETURN_BOOL(flags & RANGE_LB_INF);
+	PG_RETURN_BOOL(range_has_flag(r1, RANGE_LB_INF));
 }
 
 /* is upper bound infinite? */
@@ -515,9 +545,8 @@ Datum
 range_upper_inf(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
-	char		flags = range_get_flags(r1);
 
-	PG_RETURN_BOOL(flags & RANGE_UB_INF);
+	PG_RETURN_BOOL(range_has_flag(r1, RANGE_UB_INF));
 }
 
 
@@ -957,7 +986,25 @@ range_minus(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
 	RangeType  *r2 = PG_GETARG_RANGE_P(1);
+	RangeType  *ret;
 	TypeCacheEntry *typcache;
+
+	/* 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));
+
+	ret = range_minus_internal(typcache, r1, r2);
+	if (ret)
+		PG_RETURN_RANGE_P(ret);
+	else
+		PG_RETURN_NULL();
+}
+
+RangeType *
+range_minus_internal(TypeCacheEntry *typcache, RangeType *r1, RangeType *r2)
+{
 	RangeBound	lower1,
 				lower2;
 	RangeBound	upper1,
@@ -969,18 +1016,12 @@ range_minus(PG_FUNCTION_ARGS)
 				cmp_u1l2,
 				cmp_u1u2;
 
-	/* 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));
-
 	range_deserialize(typcache, r1, &lower1, &upper1, &empty1);
 	range_deserialize(typcache, r2, &lower2, &upper2, &empty2);
 
 	/* if either is empty, r1 is the correct answer */
 	if (empty1 || empty2)
-		PG_RETURN_RANGE_P(r1);
+		return r1;
 
 	cmp_l1l2 = range_cmp_bounds(typcache, &lower1, &lower2);
 	cmp_l1u2 = range_cmp_bounds(typcache, &lower1, &upper2);
@@ -993,34 +1034,34 @@ range_minus(PG_FUNCTION_ARGS)
 				 errmsg("result of range difference would not be contiguous")));
 
 	if (cmp_l1u2 > 0 || cmp_u1l2 < 0)
-		PG_RETURN_RANGE_P(r1);
+		return r1;
 
 	if (cmp_l1l2 >= 0 && cmp_u1u2 <= 0)
-		PG_RETURN_RANGE_P(make_empty_range(typcache));
+		return make_empty_range(typcache);
 
 	if (cmp_l1l2 <= 0 && cmp_u1l2 >= 0 && cmp_u1u2 <= 0)
 	{
 		lower2.inclusive = !lower2.inclusive;
 		lower2.lower = false;	/* it will become the upper bound */
-		PG_RETURN_RANGE_P(make_range(typcache, &lower1, &lower2, false));
+		return make_range(typcache, &lower1, &lower2, false);
 	}
 
 	if (cmp_l1l2 >= 0 && cmp_u1u2 >= 0 && cmp_l1u2 <= 0)
 	{
 		upper2.inclusive = !upper2.inclusive;
 		upper2.lower = true;	/* it will become the lower bound */
-		PG_RETURN_RANGE_P(make_range(typcache, &upper2, &upper1, false));
+		return make_range(typcache, &upper2, &upper1, false);
 	}
 
 	elog(ERROR, "unexpected case in range_minus");
-	PG_RETURN_NULL();
+	return NULL;
 }
 
 /*
  * Set union.  If strict is true, it is an error that the two input ranges
  * are not adjacent or overlapping.
  */
-static RangeType *
+RangeType *
 range_union_internal(TypeCacheEntry *typcache, RangeType *r1, RangeType *r2,
 					 bool strict)
 {
@@ -1101,6 +1142,19 @@ range_intersect(PG_FUNCTION_ARGS)
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
 	RangeType  *r2 = PG_GETARG_RANGE_P(1);
 	TypeCacheEntry *typcache;
+
+	/* 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));
+
+	PG_RETURN_RANGE_P(range_intersect_internal(typcache, r1, r2));
+}
+
+RangeType *
+range_intersect_internal(TypeCacheEntry *typcache, const RangeType *r1, const RangeType *r2)
+{
 	RangeBound	lower1,
 				lower2;
 	RangeBound	upper1,
@@ -1110,17 +1164,11 @@ range_intersect(PG_FUNCTION_ARGS)
 	RangeBound *result_lower;
 	RangeBound *result_upper;
 
-	/* 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));
-
 	range_deserialize(typcache, r1, &lower1, &upper1, &empty1);
 	range_deserialize(typcache, r2, &lower2, &upper2, &empty2);
 
-	if (empty1 || empty2 || !DatumGetBool(range_overlaps(fcinfo)))
-		PG_RETURN_RANGE_P(make_empty_range(typcache));
+	if (empty1 || empty2 || !range_overlaps_internal(typcache, r1, r2))
+		return make_empty_range(typcache);
 
 	if (range_cmp_bounds(typcache, &lower1, &lower2) >= 0)
 		result_lower = &lower1;
@@ -1132,9 +1180,81 @@ range_intersect(PG_FUNCTION_ARGS)
 	else
 		result_upper = &upper2;
 
-	PG_RETURN_RANGE_P(make_range(typcache, result_lower, result_upper, false));
+	return make_range(typcache, result_lower, result_upper, false);
 }
 
+/* range, range -> range, range functions */
+
+/*
+ * range_split_internal - if r2 intersects the middle of r1, leaving non-empty
+ * ranges on both sides, then return true and set output1 and output2 to the
+ * results of r1 - r2 (in order). Otherwise return false and don't set output1
+ * or output2. Neither input range should be empty.
+ */
+bool
+range_split_internal(TypeCacheEntry *typcache, const RangeType *r1, const RangeType *r2,
+					 RangeType **output1, RangeType **output2)
+{
+	RangeBound	lower1,
+				lower2;
+	RangeBound	upper1,
+				upper2;
+	bool		empty1,
+				empty2;
+
+	range_deserialize(typcache, r1, &lower1, &upper1, &empty1);
+	range_deserialize(typcache, r2, &lower2, &upper2, &empty2);
+
+	if (range_cmp_bounds(typcache, &lower1, &lower2) < 0 &&
+		range_cmp_bounds(typcache, &upper1, &upper2) > 0)
+	{
+		/*
+		 * Need to invert inclusive/exclusive for the lower2 and upper2
+		 * points. They can't be infinite though. We're allowed to overwrite
+		 * these RangeBounds since they only exist locally.
+		 */
+		lower2.inclusive = !lower2.inclusive;
+		lower2.lower = false;
+		upper2.inclusive = !upper2.inclusive;
+		upper2.lower = true;
+
+		*output1 = make_range(typcache, &lower1, &lower2, false);
+		*output2 = make_range(typcache, &upper2, &upper1, false);
+		return true;
+	}
+
+	return false;
+}
+
+/* range -> range aggregate functions */
+
+Datum
+range_intersect_agg_transfn(PG_FUNCTION_ARGS)
+{
+	MemoryContext aggContext;
+	Oid			rngtypoid;
+	TypeCacheEntry *typcache;
+	RangeType  *result;
+	RangeType  *current;
+
+	if (!AggCheckCallContext(fcinfo, &aggContext))
+		elog(ERROR, "range_intersect_agg_transfn called in non-aggregate context");
+
+	rngtypoid = get_fn_expr_argtype(fcinfo->flinfo, 1);
+	if (!type_is_range(rngtypoid))
+		ereport(ERROR, (errmsg("range_intersect_agg must be called with a range")));
+
+	typcache = range_get_typcache(fcinfo, rngtypoid);
+
+	/* strictness ensures these are non-null */
+	result = PG_GETARG_RANGE_P(0);
+	current = PG_GETARG_RANGE_P(1);
+
+	result = range_intersect_internal(typcache, result, current);
+	PG_RETURN_RANGE_P(result);
+}
+
+
 /* Btree support */
 
 /* btree comparator */
@@ -1144,12 +1264,6 @@ range_cmp(PG_FUNCTION_ARGS)
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
 	RangeType  *r2 = PG_GETARG_RANGE_P(1);
 	TypeCacheEntry *typcache;
-	RangeBound	lower1,
-				lower2;
-	RangeBound	upper1,
-				upper2;
-	bool		empty1,
-				empty2;
 	int			cmp;
 
 	check_stack_depth();		/* recurses when subtype is a range type */
@@ -1160,6 +1274,28 @@ range_cmp(PG_FUNCTION_ARGS)
 
 	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, const RangeType *r1, const 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);
 
@@ -1177,10 +1313,7 @@ range_cmp(PG_FUNCTION_ARGS)
 			cmp = range_cmp_bounds(typcache, &upper1, &upper2);
 	}
 
-	PG_FREE_IF_COPY(r1, 0);
-	PG_FREE_IF_COPY(r2, 1);
-
-	PG_RETURN_INT32(cmp);
+	return cmp;
 }
 
 /* inequality operators using the range_cmp function */
@@ -1218,13 +1351,10 @@ range_gt(PG_FUNCTION_ARGS)
 
 /* Hash support */
 
-/* hash a range value */
-Datum
-hash_range(PG_FUNCTION_ARGS)
+uint32
+hash_range_internal(TypeCacheEntry *typcache, const RangeType *r)
 {
-	RangeType  *r = PG_GETARG_RANGE_P(0);
 	uint32		result;
-	TypeCacheEntry *typcache;
 	TypeCacheEntry *scache;
 	RangeBound	lower;
 	RangeBound	upper;
@@ -1233,10 +1363,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 +1404,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, const RangeType *r, Datum seed)
+{
+	uint64		result;
 	TypeCacheEntry *scache;
 	RangeBound	lower;
 	RangeBound	upper;
@@ -1300,10 +1433,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 +1471,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));
 }
 
 /*
@@ -1769,6 +1916,21 @@ range_get_flags(const RangeType *range)
 	return *((char *) range + VARSIZE(range) - 1);
 }
 
+/*
+ * range_has_flag: set whether a range has a specific flag.
+ *
+ * This lets expose some of our functions that just check flags
+ * to the rest of the code base (like to multiranges)
+ * without writing full-fledged *_internal versions.
+ */
+bool
+range_has_flag(const RangeType *r1, char flag)
+{
+	char		flags = range_get_flags(r1);
+
+	return flags & flag;
+}
+
 /*
  * range_set_contain_empty: set the RANGE_CONTAIN_EMPTY bit in the value.
  *
@@ -1937,6 +2099,46 @@ range_cmp_bound_values(TypeCacheEntry *typcache, const RangeBound *b1,
 										   b1->val, b2->val));
 }
 
+/*
+ * qsort callback for sorting ranges.
+ *
+ * Two empty ranges compare equal; an empty range sorts to the left of any
+ * non-empty range.  Two non-empty ranges are sorted by lower bound first
+ * and by upper bound next.
+ */
+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 0a6db0d478..6e9a48b314 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -2529,6 +2529,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
  *
@@ -3184,7 +3194,7 @@ get_namespace_name_or_temp(Oid nspid)
 		return get_namespace_name(nspid);
 }
 
-/*				---------- PG_RANGE CACHE ----------				 */
+/*				---------- PG_RANGE CACHES ----------				 */
 
 /*
  * get_range_subtype
@@ -3237,6 +3247,56 @@ get_range_collation(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->rngmultitypid;
+		ReleaseSysCache(tp);
+		return result;
+	}
+	else
+		return InvalidOid;
+}
+
+/*
+ * get_multirange_range
+ *		Returns the range type of a given multirange
+ *
+ * Returns InvalidOid if the type is not a multirange.
+ */
+Oid
+get_multirange_range(Oid multirangeOid)
+{
+	HeapTuple	tp;
+
+	tp = SearchSysCache1(RANGEMULTIRANGE, 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 53d9ddf159..7654331a59 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -651,6 +651,18 @@ static const struct cachedesc cacheinfo[] = {
 		},
 		64
 	},
+	{RangeRelationId,			/* RANGEMULTIRANGE */
+		RangeMultirangeTypidIndexId,
+		1,
+		{
+			Anum_pg_range_rngmultitypid,
+			0,
+			0,
+			0
+		},
+		4
+	},
+
 	{RangeRelationId,			/* RANGETYPE */
 		RangeTypidIndexId,
 		1,
diff --git a/src/backend/utils/cache/typcache.c b/src/backend/utils/cache/typcache.c
index 854f133f9b..f1d3cb97d0 100644
--- a/src/backend/utils/cache/typcache.c
+++ b/src/backend/utils/cache/typcache.c
@@ -286,6 +286,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);
@@ -302,6 +303,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 TypeCacheTypCallback(Datum arg, int cacheid, uint32 hashvalue);
 static void TypeCacheOpcCallback(Datum arg, int cacheid, uint32 hashvalue);
@@ -553,8 +557,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))
@@ -591,7 +595,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))
@@ -616,7 +620,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))
@@ -641,7 +645,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))
@@ -693,6 +697,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;
@@ -737,6 +748,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;
@@ -816,6 +834,16 @@ lookup_type_cache(Oid type_id, int flags)
 			(void) lookup_type_cache(typentry->rngelemtype->type_id, 0);
 	}
 
+	/*
+	 * 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
 	 */
@@ -927,6 +955,22 @@ load_rangetype_info(TypeCacheEntry *typentry)
 	typentry->rngelemtype = lookup_type_cache(subtypeOid, 0);
 }
 
+/*
+ * load_multirangetype_info --- helper routine to set up multirange type
+ * information
+ */
+static void
+load_multirangetype_info(TypeCacheEntry *typentry)
+{
+	Oid			rangetypeOid;
+
+	rangetypeOid = get_multirange_range(typentry->type_id);
+	if (!OidIsValid(rangetypeOid))
+		elog(ERROR, "cache lookup failed for multirange type %u",
+			 typentry->type_id);
+
+	typentry->rngtype = lookup_type_cache(rangetypeOid, TYPECACHE_RANGE_INFO);
+}
 
 /*
  * load_domaintype_info --- helper routine to set up domain constraint info
@@ -1527,11 +1571,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
@@ -1574,6 +1618,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 78ed857203..ce09d0d128 100644
--- a/src/backend/utils/fmgr/funcapi.c
+++ b/src/backend/utils/fmgr/funcapi.c
@@ -35,6 +35,7 @@ typedef struct polymorphic_actuals
 	Oid			anyelement_type;	/* anyelement mapping, if known */
 	Oid			anyarray_type;	/* anyarray mapping, if known */
 	Oid			anyrange_type;	/* anyrange mapping, if known */
+	Oid			anymultirange_type;	/* anymultirange mapping, if known */
 } polymorphic_actuals;
 
 static void shutdown_MultiFuncCall(Datum arg);
@@ -46,6 +47,7 @@ static TypeFuncClass internal_get_result_type(Oid funcid,
 static void resolve_anyelement_from_others(polymorphic_actuals *actuals);
 static void resolve_anyarray_from_others(polymorphic_actuals *actuals);
 static void resolve_anyrange_from_others(polymorphic_actuals *actuals);
+static void resolve_anymultirange_from_others(polymorphic_actuals *actuals);
 static bool resolve_polymorphic_tupdesc(TupleDesc tupdesc,
 										oidvector *declared_args,
 										Node *call_expr);
@@ -503,6 +505,34 @@ resolve_anyelement_from_others(polymorphic_actuals *actuals)
 							format_type_be(range_base_type))));
 		actuals->anyelement_type = range_typelem;
 	}
+	else if (OidIsValid(actuals->anymultirange_type))
+	{
+		/* Use the element type based on the multirange type */
+		Oid			multirange_base_type;
+		Oid			multirange_typelem;
+		Oid			range_base_type;
+		Oid			range_typelem;
+
+		multirange_base_type = getBaseType(actuals->anymultirange_type);
+		multirange_typelem = get_multirange_range(multirange_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(multirange_base_type))));
+
+		range_base_type = getBaseType(multirange_typelem);
+		range_typelem = get_range_subtype(range_base_type);
+
+		if (!OidIsValid(range_typelem))
+			ereport(ERROR,
+					(errcode(ERRCODE_DATATYPE_MISMATCH),
+					 errmsg("argument declared %s does not contain a range type but type %s",
+							"anymultirange",
+							format_type_be(range_base_type))));
+		actuals->anyelement_type = range_typelem;
+	}
 	else
 		elog(ERROR, "could not determine polymorphic type");
 }
@@ -540,10 +570,54 @@ static void
 resolve_anyrange_from_others(polymorphic_actuals *actuals)
 {
 	/*
-	 * We can't deduce a range type from other polymorphic inputs, because
-	 * there may be multiple range types with the same subtype.
+	 * We can't deduce a range type from other polymorphic array or base types,
+	 * because there may be multiple range types with the same subtype, but we
+	 * can deduce it from a polymorphic multirange type.
 	 */
-	elog(ERROR, "could not determine polymorphic type");
+	if (OidIsValid(actuals->anymultirange_type))
+	{
+		/* Use the element type based on the multirange type */
+		Oid			multirange_base_type = getBaseType(actuals->anymultirange_type);
+		Oid			multirange_typelem =
+			get_multirange_range(multirange_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(multirange_base_type))));
+		actuals->anyrange_type = multirange_typelem;
+	}
+	else
+		elog(ERROR, "could not determine polymorphic type");
+}
+
+/*
+ * Resolve actual type of ANYMULTIRANGE from other polymorphic inputs
+ */
+static void
+resolve_anymultirange_from_others(polymorphic_actuals *actuals)
+{
+	/*
+	 * We can't deduce a multirange type from polymorphic array or base types,
+	 * because there may be multiple range types with the same subtype, but we
+	 * can deduce it from a polymorphic range type.
+	 */
+	if (OidIsValid(actuals->anyrange_type))
+	{
+		Oid			range_base_type = getBaseType(actuals->anyrange_type);
+		Oid			multirange_typeid = get_range_multirange(range_base_type);
+
+		if (!OidIsValid(multirange_typeid))
+			ereport(ERROR,
+					(errcode(ERRCODE_UNDEFINED_OBJECT),
+					 errmsg("could not find multirange type for data type %s",
+							format_type_be(actuals->anyrange_type))));
+		actuals->anymultirange_type = multirange_typeid;
+	}
+	else
+		elog(ERROR, "could not determine polymorphic type");
 }
 
 /*
@@ -566,6 +640,7 @@ 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_anycompatible_result = false;
 	bool		have_anycompatible_array_result = false;
 	bool		have_anycompatible_range_result = false;
@@ -594,6 +669,10 @@ resolve_polymorphic_tupdesc(TupleDesc tupdesc, oidvector *declared_args,
 				have_polymorphic_result = true;
 				have_anyrange_result = true;
 				break;
+			case ANYMULTIRANGEOID:
+				have_polymorphic_result = true;
+				have_anymultirange_result = true;
+				break;
 			case ANYCOMPATIBLEOID:
 			case ANYCOMPATIBLENONARRAYOID:
 				have_polymorphic_result = true;
@@ -660,6 +739,15 @@ resolve_polymorphic_tupdesc(TupleDesc tupdesc, oidvector *declared_args,
 						return false;
 				}
 				break;
+			case ANYMULTIRANGEOID:
+				if (!OidIsValid(poly_actuals.anymultirange_type))
+				{
+					poly_actuals.anymultirange_type =
+						get_call_expr_argtype(call_expr, i);
+					if (!OidIsValid(poly_actuals.anymultirange_type))
+						return false;
+				}
+				break;
 			case ANYCOMPATIBLEOID:
 			case ANYCOMPATIBLENONARRAYOID:
 				if (!OidIsValid(anyc_actuals.anyelement_type))
@@ -703,6 +791,9 @@ resolve_polymorphic_tupdesc(TupleDesc tupdesc, oidvector *declared_args,
 	if (have_anyrange_result && !OidIsValid(poly_actuals.anyrange_type))
 		resolve_anyrange_from_others(&poly_actuals);
 
+	if (have_anymultirange_result && !OidIsValid(poly_actuals.anymultirange_type))
+		resolve_anymultirange_from_others(&poly_actuals);
+
 	if (have_anycompatible_result && !OidIsValid(anyc_actuals.anyelement_type))
 		resolve_anyelement_from_others(&anyc_actuals);
 
@@ -780,6 +871,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),
+								   poly_actuals.anymultirange_type,
+								   -1,
+								   0);
+				/* no collation should be attached to a multirange type */
+				break;
 			case ANYCOMPATIBLEOID:
 			case ANYCOMPATIBLENONARRAYOID:
 				TupleDescInitEntry(tupdesc, i + 1,
@@ -834,6 +933,7 @@ 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;
 	bool		have_anycompatible_result = false;
 	bool		have_anycompatible_array_result = false;
 	bool		have_anycompatible_range_result = false;
@@ -912,6 +1012,24 @@ resolve_polymorphic_argtypes(int numargs, Oid *argtypes, char *argmodes,
 					argtypes[i] = poly_actuals.anyrange_type;
 				}
 				break;
+			case ANYMULTIRANGEOID:
+				if (argmode == PROARGMODE_OUT || argmode == PROARGMODE_TABLE)
+				{
+					have_polymorphic_result = true;
+					have_anymultirange_result = true;
+				}
+				else
+				{
+					if (!OidIsValid(poly_actuals.anymultirange_type))
+					{
+						poly_actuals.anymultirange_type =
+							get_call_expr_argtype(call_expr, inargno);
+						if (!OidIsValid(poly_actuals.anymultirange_type))
+							return false;
+					}
+					argtypes[i] = poly_actuals.anymultirange_type;
+				}
+				break;
 			case ANYCOMPATIBLEOID:
 			case ANYCOMPATIBLENONARRAYOID:
 				if (argmode == PROARGMODE_OUT || argmode == PROARGMODE_TABLE)
@@ -988,6 +1106,9 @@ resolve_polymorphic_argtypes(int numargs, Oid *argtypes, char *argmodes,
 	if (have_anyrange_result && !OidIsValid(poly_actuals.anyrange_type))
 		resolve_anyrange_from_others(&poly_actuals);
 
+	if (have_anymultirange_result && !OidIsValid(poly_actuals.anymultirange_type))
+		resolve_anymultirange_from_others(&poly_actuals);
+
 	if (have_anycompatible_result && !OidIsValid(anyc_actuals.anyelement_type))
 		resolve_anyelement_from_others(&anyc_actuals);
 
@@ -1013,6 +1134,9 @@ resolve_polymorphic_argtypes(int numargs, Oid *argtypes, char *argmodes,
 			case ANYRANGEOID:
 				argtypes[i] = poly_actuals.anyrange_type;
 				break;
+			case ANYMULTIRANGEOID:
+				argtypes[i] = poly_actuals.anymultirange_type;
+				break;
 			case ANYCOMPATIBLEOID:
 			case ANYCOMPATIBLENONARRAYOID:
 				argtypes[i] = anyc_actuals.anyelement_type;
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 408637cfec..cf4ce0a5a6 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -276,7 +276,8 @@ static void dumpSearchPath(Archive *AH);
 static void binary_upgrade_set_type_oids_by_type_oid(Archive *fout,
 													 PQExpBuffer upgrade_buffer,
 													 Oid pg_type_oid,
-													 bool force_array_type);
+													 bool force_array_type,
+													 bool include_multirange_type);
 static bool binary_upgrade_set_type_oids_by_rel_oid(Archive *fout,
 													PQExpBuffer upgrade_buffer, Oid pg_rel_oid);
 static void binary_upgrade_set_pg_class_oids(Archive *fout,
@@ -1648,7 +1649,7 @@ selectDumpableType(TypeInfo *tyinfo, Archive *fout)
 	}
 
 	/* skip auto-generated array types */
-	if (tyinfo->isArray)
+	if (tyinfo->isArray || tyinfo->isMultirange)
 	{
 		tyinfo->dobj.objType = DO_DUMMY_TYPE;
 
@@ -4416,16 +4417,49 @@ append_depends_on_extension(Archive *fout,
 	}
 }
 
+static Oid
+get_next_possible_free_pg_type_oid(Archive *fout, PQExpBuffer upgrade_query)
+{
+	/*
+	 * If the old version didn't assign an array type, but the new version
+	 * does, we must select an unused type OID to assign.  This currently only
+	 * happens for domains, when upgrading pre-v11 to v11 and up.
+	 *
+	 * Note: local state here is kind of ugly, but we must have some, since we
+	 * mustn't choose the same unused OID more than once.
+	 */
+	static Oid	next_possible_free_oid = FirstNormalObjectId;
+	PGresult   *res;
+	bool		is_dup;
+
+	do
+	{
+		++next_possible_free_oid;
+		printfPQExpBuffer(upgrade_query,
+						  "SELECT EXISTS(SELECT 1 "
+						  "FROM pg_catalog.pg_type "
+						  "WHERE oid = '%u'::pg_catalog.oid);",
+						  next_possible_free_oid);
+		res = ExecuteSqlQueryForSingleRow(fout, upgrade_query->data);
+		is_dup = (PQgetvalue(res, 0, 0)[0] == 't');
+		PQclear(res);
+	} while (is_dup);
+
+	return next_possible_free_oid;
+}
 
 static void
 binary_upgrade_set_type_oids_by_type_oid(Archive *fout,
 										 PQExpBuffer upgrade_buffer,
 										 Oid pg_type_oid,
-										 bool force_array_type)
+										 bool force_array_type,
+										 bool include_multirange_type)
 {
 	PQExpBuffer upgrade_query = createPQExpBuffer();
 	PGresult   *res;
 	Oid			pg_type_array_oid;
+	Oid			pg_type_multirange_oid;
+	Oid			pg_type_multirange_array_oid;
 
 	appendPQExpBufferStr(upgrade_buffer, "\n-- For binary upgrade, must preserve pg_type oid\n");
 	appendPQExpBuffer(upgrade_buffer,
@@ -4446,33 +4480,7 @@ binary_upgrade_set_type_oids_by_type_oid(Archive *fout,
 	PQclear(res);
 
 	if (!OidIsValid(pg_type_array_oid) && force_array_type)
-	{
-		/*
-		 * If the old version didn't assign an array type, but the new version
-		 * does, we must select an unused type OID to assign.  This currently
-		 * only happens for domains, when upgrading pre-v11 to v11 and up.
-		 *
-		 * Note: local state here is kind of ugly, but we must have some,
-		 * since we mustn't choose the same unused OID more than once.
-		 */
-		static Oid	next_possible_free_oid = FirstNormalObjectId;
-		bool		is_dup;
-
-		do
-		{
-			++next_possible_free_oid;
-			printfPQExpBuffer(upgrade_query,
-							  "SELECT EXISTS(SELECT 1 "
-							  "FROM pg_catalog.pg_type "
-							  "WHERE oid = '%u'::pg_catalog.oid);",
-							  next_possible_free_oid);
-			res = ExecuteSqlQueryForSingleRow(fout, upgrade_query->data);
-			is_dup = (PQgetvalue(res, 0, 0)[0] == 't');
-			PQclear(res);
-		} while (is_dup);
-
-		pg_type_array_oid = next_possible_free_oid;
-	}
+		pg_type_array_oid = get_next_possible_free_pg_type_oid(fout, upgrade_query);
 
 	if (OidIsValid(pg_type_array_oid))
 	{
@@ -4483,6 +4491,46 @@ binary_upgrade_set_type_oids_by_type_oid(Archive *fout,
 						  pg_type_array_oid);
 	}
 
+	/*
+	 * Pre-set the multirange type oid and its own array type oid.
+	 */
+	if (include_multirange_type)
+	{
+		if (fout->remoteVersion >= 130000)
+		{
+			appendPQExpBuffer(upgrade_query,
+							  "SELECT t.oid, t.typarray "
+							  "FROM pg_catalog.pg_type t "
+							  "JOIN pg_catalog.pg_range r "
+							  "ON t.oid = r.rngmultitypid "
+							  "WHERE r.rngtypid = '%u'::pg_catalog.oid;",
+							  pg_type_oid);
+
+			res = ExecuteSqlQueryForSingleRow(fout, upgrade_query->data);
+
+			pg_type_multirange_oid = atooid(PQgetvalue(res, 0, PQfnumber(res, "oid")));
+			pg_type_multirange_array_oid = atooid(PQgetvalue(res, 0, PQfnumber(res, "typarray")));
+
+			PQclear(res);
+		}
+		else
+		{
+			pg_type_multirange_oid = get_next_possible_free_pg_type_oid(fout, upgrade_query);
+			pg_type_multirange_array_oid = get_next_possible_free_pg_type_oid(fout, upgrade_query);
+		}
+
+		appendPQExpBufferStr(upgrade_buffer,
+							 "\n-- For binary upgrade, must preserve multirange pg_type oid\n");
+		appendPQExpBuffer(upgrade_buffer,
+						  "SELECT pg_catalog.binary_upgrade_set_next_multirange_pg_type_oid('%u'::pg_catalog.oid);\n\n",
+						  pg_type_multirange_oid);
+		appendPQExpBufferStr(upgrade_buffer,
+							 "\n-- For binary upgrade, must preserve multirange pg_type array oid\n");
+		appendPQExpBuffer(upgrade_buffer,
+						  "SELECT pg_catalog.binary_upgrade_set_next_multirange_array_pg_type_oid('%u'::pg_catalog.oid);\n\n",
+						  pg_type_multirange_array_oid);
+	}
+
 	destroyPQExpBuffer(upgrade_query);
 }
 
@@ -4516,7 +4564,7 @@ binary_upgrade_set_type_oids_by_rel_oid(Archive *fout,
 	pg_type_oid = atooid(PQgetvalue(upgrade_res, 0, PQfnumber(upgrade_res, "crel")));
 
 	binary_upgrade_set_type_oids_by_type_oid(fout, upgrade_buffer,
-											 pg_type_oid, false);
+											 pg_type_oid, false, false);
 
 	if (!PQgetisnull(upgrade_res, 0, PQfnumber(upgrade_res, "trel")))
 	{
@@ -5065,6 +5113,11 @@ getTypes(Archive *fout, int *numTypes)
 		else
 			tyinfo[i].isArray = false;
 
+		if (tyinfo[i].typtype == 'm')
+			tyinfo[i].isMultirange = true;
+		else
+			tyinfo[i].isMultirange = false;
+
 		/* Decide whether we want to dump it */
 		selectDumpableType(&tyinfo[i], fout);
 
@@ -8203,9 +8256,12 @@ getProcLangs(Archive *fout, int *numProcLangs)
 
 /*
  * getCasts
- *	  get basic information about every cast in the system
+ *	  get basic information about most casts in the system
  *
  * numCasts is set to the number of casts read in
+ *
+ * Skip casts from a range to its multirange, since we'll create those
+ * automatically.
  */
 CastInfo *
 getCasts(Archive *fout, int *numCasts)
@@ -8223,7 +8279,20 @@ getCasts(Archive *fout, int *numCasts)
 	int			i_castcontext;
 	int			i_castmethod;
 
-	if (fout->remoteVersion >= 80400)
+	if (fout->remoteVersion >= 130000)
+	{
+		appendPQExpBufferStr(query, "SELECT tableoid, oid, "
+							 "castsource, casttarget, castfunc, castcontext, "
+							 "castmethod "
+							 "FROM pg_cast c "
+							 "WHERE NOT EXISTS ( "
+							 "SELECT 1 FROM pg_range r "
+							 "WHERE c.castsource = r.rngtypid "
+							 "AND c.casttarget = r.rngmultitypid "
+							 ") "
+							 "ORDER BY 3,4");
+	}
+	else if (fout->remoteVersion >= 80400)
 	{
 		appendPQExpBufferStr(query, "SELECT tableoid, oid, "
 							 "castsource, casttarget, castfunc, castcontext, "
@@ -10411,7 +10480,7 @@ dumpEnumType(Archive *fout, TypeInfo *tyinfo)
 	if (dopt->binary_upgrade)
 		binary_upgrade_set_type_oids_by_type_oid(fout, q,
 												 tyinfo->dobj.catId.oid,
-												 false);
+												 false, false);
 
 	appendPQExpBuffer(q, "CREATE TYPE %s AS ENUM (",
 					  qualtypname);
@@ -10537,7 +10606,7 @@ dumpRangeType(Archive *fout, TypeInfo *tyinfo)
 	if (dopt->binary_upgrade)
 		binary_upgrade_set_type_oids_by_type_oid(fout, q,
 												 tyinfo->dobj.catId.oid,
-												 false);
+												 false, true);
 
 	appendPQExpBuffer(q, "CREATE TYPE %s AS RANGE (",
 					  qualtypname);
@@ -10643,7 +10712,7 @@ dumpUndefinedType(Archive *fout, TypeInfo *tyinfo)
 	if (dopt->binary_upgrade)
 		binary_upgrade_set_type_oids_by_type_oid(fout, q,
 												 tyinfo->dobj.catId.oid,
-												 false);
+												 false, false);
 
 	appendPQExpBuffer(q, "CREATE TYPE %s;\n",
 					  qualtypname);
@@ -10848,7 +10917,7 @@ dumpBaseType(Archive *fout, TypeInfo *tyinfo)
 	if (dopt->binary_upgrade)
 		binary_upgrade_set_type_oids_by_type_oid(fout, q,
 												 tyinfo->dobj.catId.oid,
-												 false);
+												 false, false);
 
 	appendPQExpBuffer(q,
 					  "CREATE TYPE %s (\n"
@@ -11035,7 +11104,8 @@ dumpDomain(Archive *fout, TypeInfo *tyinfo)
 	if (dopt->binary_upgrade)
 		binary_upgrade_set_type_oids_by_type_oid(fout, q,
 												 tyinfo->dobj.catId.oid,
-												 true); /* force array type */
+												 true,		/* force array type */
+												 false);	/* force multirange type */
 
 	qtypname = pg_strdup(fmtId(tyinfo->dobj.name));
 	qualtypname = pg_strdup(fmtQualifiedDumpable(tyinfo));
@@ -11223,7 +11293,7 @@ dumpCompositeType(Archive *fout, TypeInfo *tyinfo)
 	{
 		binary_upgrade_set_type_oids_by_type_oid(fout, q,
 												 tyinfo->dobj.catId.oid,
-												 false);
+												 false, false);
 		binary_upgrade_set_pg_class_oids(fout, q, tyinfo->typrelid, false);
 	}
 
@@ -11497,7 +11567,7 @@ dumpShellType(Archive *fout, ShellTypeInfo *stinfo)
 	if (dopt->binary_upgrade)
 		binary_upgrade_set_type_oids_by_type_oid(fout, q,
 												 stinfo->baseType->dobj.catId.oid,
-												 false);
+												 false, false);
 
 	appendPQExpBuffer(q, "CREATE TYPE %s;\n",
 					  fmtQualifiedDumpable(stinfo));
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 3e11166615..0b717412b4 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -176,6 +176,7 @@ typedef struct _typeInfo
 	char		typrelkind;		/* 'r', 'v', 'c', etc */
 	char		typtype;		/* 'b', 'c', etc */
 	bool		isArray;		/* true if auto-generated array type */
+	bool		isMultirange;	/* true if auto-generated multirange type */
 	bool		isDefined;		/* true if typisdefined */
 	/* If needed, we'll create a "shell type" entry for it; link that here: */
 	struct _shellTypeInfo *shellType;	/* shell-type entry, or NULL */
diff --git a/src/include/access/tupmacs.h b/src/include/access/tupmacs.h
index 70157cf90a..c262265b95 100644
--- a/src/include/access/tupmacs.h
+++ b/src/include/access/tupmacs.h
@@ -139,8 +139,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/binary_upgrade.h b/src/include/catalog/binary_upgrade.h
index 12d94fe1b3..01d95117cd 100644
--- a/src/include/catalog/binary_upgrade.h
+++ b/src/include/catalog/binary_upgrade.h
@@ -16,6 +16,8 @@
 
 extern PGDLLIMPORT Oid binary_upgrade_next_pg_type_oid;
 extern PGDLLIMPORT Oid binary_upgrade_next_array_pg_type_oid;
+extern PGDLLIMPORT Oid binary_upgrade_next_mrng_pg_type_oid;
+extern PGDLLIMPORT Oid binary_upgrade_next_mrng_array_pg_type_oid;
 extern PGDLLIMPORT Oid binary_upgrade_next_toast_pg_type_oid;
 
 extern PGDLLIMPORT Oid binary_upgrade_next_heap_pg_class_oid;
diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h
index 8be303870f..566f9364c5 100644
--- a/src/include/catalog/indexing.h
+++ b/src/include/catalog/indexing.h
@@ -327,6 +327,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_rngmultitypid_index, 8001, on pg_range using btree(rngmultitypid 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_aggregate.dat b/src/include/catalog/pg_aggregate.dat
index ffabe275c0..03bab74253 100644
--- a/src/include/catalog/pg_aggregate.dat
+++ b/src/include/catalog/pg_aggregate.dat
@@ -538,6 +538,17 @@
   aggtransfn => 'bytea_string_agg_transfn',
   aggfinalfn => 'bytea_string_agg_finalfn', aggtranstype => 'internal' },
 
+# range
+{ aggfnoid => 'range_intersect_agg(anyrange)',
+  aggtransfn => 'range_intersect_agg_transfn',
+  aggcombinefn => 'range_intersect_agg_transfn', aggtranstype => 'anyrange' },
+{ aggfnoid => 'range_intersect_agg(anymultirange)',
+  aggtransfn => 'multirange_intersect_agg_transfn',
+  aggcombinefn => 'multirange_intersect_agg_transfn', aggtranstype => 'anymultirange' },
+{ aggfnoid => 'range_agg(anyrange)', aggtransfn => 'range_agg_transfn',
+  aggfinalfn => 'range_agg_finalfn', aggfinalextra => 't',
+  aggtranstype => 'internal' },
+
 # json
 { aggfnoid => 'json_agg', aggtransfn => 'json_agg_transfn',
   aggfinalfn => 'json_agg_finalfn', aggtranstype => 'internal' },
diff --git a/src/include/catalog/pg_amop.dat b/src/include/catalog/pg_amop.dat
index 11aaa519c8..827ac4f03c 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 cef63b2a71..5ac1c478cb 100644
--- a/src/include/catalog/pg_amproc.dat
+++ b/src/include/catalog/pg_amproc.dat
@@ -285,6 +285,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' },
 
@@ -445,6 +447,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',
@@ -1266,12 +1273,13 @@
   amprocrighttype => 'anyrange', amprocnum => '4',
   amproc => 'brin_inclusion_union' },
 { amprocfamily => 'brin/range_inclusion_ops', amproclefttype => 'anyrange',
-  amprocrighttype => 'anyrange', amprocnum => '11', amproc => 'range_merge' },
+  amprocrighttype => 'anyrange', amprocnum => '11',
+  amproc => 'range_merge(anyrange,anyrange)' },
 { amprocfamily => 'brin/range_inclusion_ops', amproclefttype => 'anyrange',
   amprocrighttype => 'anyrange', amprocnum => '13',
   amproc => 'range_contains' },
 { amprocfamily => 'brin/range_inclusion_ops', amproclefttype => 'anyrange',
-  amprocrighttype => 'anyrange', amprocnum => '14', amproc => 'isempty' },
+  amprocrighttype => 'anyrange', amprocnum => '14', amproc => 'isempty(anyrange)' },
 
 # minmax pg_lsn
 { amprocfamily => 'brin/pg_lsn_minmax_ops', amproclefttype => 'pg_lsn',
diff --git a/src/include/catalog/pg_cast.dat b/src/include/catalog/pg_cast.dat
index 01c5328ddd..f555af90ac 100644
--- a/src/include/catalog/pg_cast.dat
+++ b/src/include/catalog/pg_cast.dat
@@ -526,4 +526,17 @@
 { castsource => 'jsonb', casttarget => 'float8', castfunc => 'float8(jsonb)',
   castcontext => 'e', castmethod => 'f' },
 
+# range to multirange
+{ castsource => 'int4range', casttarget => 'int4multirange', castfunc => 'int4multirange(int4range)',
+  castcontext => 'e', castmethod => 'f' },
+{ castsource => 'int8range', casttarget => 'int8multirange', castfunc => 'int8multirange(int8range)',
+  castcontext => 'e', castmethod => 'f' },
+{ castsource => 'numrange', casttarget => 'nummultirange', castfunc => 'nummultirange(numrange)',
+  castcontext => 'e', castmethod => 'f' },
+{ castsource => 'daterange', casttarget => 'datemultirange', castfunc => 'datemultirange(daterange)',
+  castcontext => 'e', castmethod => 'f' },
+{ castsource => 'tsrange', casttarget => 'tsmultirange', castfunc => 'tsmultirange(tsrange)',
+  castcontext => 'e', castmethod => 'f' },
+{ castsource => 'tstzrange', casttarget => 'tstzmultirange', castfunc => 'tstzmultirange(tstzrange)',
+  castcontext => 'e', castmethod => 'f' },
 ]
diff --git a/src/include/catalog/pg_opclass.dat b/src/include/catalog/pg_opclass.dat
index ab2f50c9eb..eebebbcdb4 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 65c7fedf23..08cda3ba36 100644
--- a/src/include/catalog/pg_operator.dat
+++ b/src/include/catalog/pg_operator.dat
@@ -3302,5 +3302,174 @@
   oprname => '@@', oprleft => 'jsonb', oprright => 'jsonpath',
   oprresult => 'bool', oprcode => 'jsonb_path_match_opr(jsonb,jsonpath)',
   oprrest => 'matchingsel', oprjoin => 'matchingjoinsel' },
+{ 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' },
+{ oid => '8073', oid_symbol => 'OID_RANGE_OVERLAPS_MULTIRANGE_OP',
+  descr => 'overlaps',
+  oprname => '&&', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '&&(anymultirange,anyrange)',
+  oprcode => 'range_overlaps_multirange', oprrest => 'areasel',
+  oprjoin => 'areajoinsel' },
+{ oid => '8074', oid_symbol => 'OID_MULTIRANGE_OVERLAPS_RANGE_OP', descr => 'contains',
+  oprname => '&&', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcom => '&&(anyrange,anymultirange)',
+  oprcode => 'multirange_overlaps_range', oprrest => 'areasel',
+  oprjoin => 'areajoinsel' },
+{ oid => '8075', oid_symbol => 'OID_MULTIRANGE_OVERLAPS_MULTIRANGE_OP', descr => 'contains',
+  oprname => '&&', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '&&(anymultirange,anymultirange)',
+  oprcode => 'multirange_overlaps_multirange', oprrest => 'areasel',
+  oprjoin => 'areajoinsel' },
+{ oid => '8064', oid_symbol => 'OID_MULTIRANGE_CONTAINS_ELEM_OP',
+  descr => 'contains',
+  oprname => '@>', oprleft => 'anymultirange', oprright => 'anyelement',
+  oprresult => 'bool', oprcom => '<@(anyelement,anymultirange)',
+  oprcode => 'multirange_contains_elem', oprrest => 'contsel',
+  oprjoin => 'contjoinsel' },
+{ oid => '8065', oid_symbol => 'OID_MULTIRANGE_CONTAINS_RANGE_OP', descr => 'contains',
+  oprname => '@>', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcom => '<@(anyrange,anymultirange)',
+  oprcode => 'multirange_contains_range', oprrest => 'contsel',
+  oprjoin => 'contjoinsel' },
+{ oid => '8066', oid_symbol => 'OID_MULTIRANGE_CONTAINS_MULTIRANGE_OP', descr => 'contains',
+  oprname => '@>', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '<@(anymultirange,anymultirange)',
+  oprcode => 'multirange_contains_multirange', oprrest => 'contsel',
+  oprjoin => 'contjoinsel' },
+{ oid => '8067', oid_symbol => 'OID_MULTIRANGE_ELEM_CONTAINED_OP',
+  descr => 'is contained by',
+  oprname => '<@', oprleft => 'anyelement', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '@>(anymultirange,anyelement)',
+  oprcode => 'elem_contained_by_multirange', oprrest => 'contsel',
+  oprjoin => 'contjoinsel' },
+{ oid => '8068', oid_symbol => 'OID_MULTIRANGE_RANGE_CONTAINED_OP',
+  descr => 'is contained by',
+  oprname => '<@', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '@>(anymultirange,anyrange)',
+  oprcode => 'range_contained_by_multirange', oprrest => 'contsel',
+  oprjoin => 'contjoinsel' },
+{ oid => '8069', oid_symbol => 'OID_MULTIRANGE_MULTIRANGE_CONTAINED_OP',
+  descr => 'is contained by',
+  oprname => '<@', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '@>(anymultirange,anymultirange)',
+  oprcode => 'multirange_contained_by_multirange', oprrest => 'contsel',
+  oprjoin => 'contjoinsel' },
+{ oid => '8106', oid_symbol => 'OID_RANGE_OVERLAPS_LEFT_MULTIRANGE_OP',
+  descr => 'overlaps or is left of',
+  oprname => '&<', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcode => 'range_overleft_multirange', oprrest => 'scalarltsel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8107', oid_symbol => 'OID_MULTIRANGE_OVERLAPS_LEFT_RANGE_OP',
+  descr => 'overlaps or is left of',
+  oprname => '&<', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcode => 'multirange_overleft_range', oprrest => 'scalarltsel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8108', oid_symbol => 'OID_MULTIRANGE_OVERLAPS_LEFT_MULTIRANGE_OP',
+  descr => 'overlaps or is left of',
+  oprname => '&<', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcode => 'multirange_overleft_multirange', oprrest => 'scalarltsel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8109', oid_symbol => 'OID_RANGE_OVERLAPS_RIGHT_MULTIRANGE_OP',
+  descr => 'overlaps or is right of',
+  oprname => '&>', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcode => 'range_overright_multirange', oprrest => 'scalargtsel',
+  oprjoin => 'scalargtjoinsel' },
+{ oid => '8110', oid_symbol => 'OID_MULTIRANGE_OVERLAPS_RIGHT_RANGE_OP',
+  descr => 'overlaps or is right of',
+  oprname => '&>', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcode => 'multirange_overright_range', oprrest => 'scalargtsel',
+  oprjoin => 'scalargtjoinsel' },
+{ oid => '8111', oid_symbol => 'OID_MULTIRANGE_OVERLAPS_RIGHT_MULTIRANGE_OP',
+  descr => 'overlaps or is right of',
+  oprname => '&>', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcode => 'multirange_overright_multirange', oprrest => 'scalargtsel',
+  oprjoin => 'scalargtjoinsel' },
+{ oid => '8091', oid_symbol => 'OID_RANGE_ADJACENT_MULTIRANGE_OP', descr => 'is adjacent to',
+  oprname => '-|-', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '-|-(anymultirange,anyrange)',
+  oprcode => 'range_adjacent_multirange', oprrest => 'scalarltsel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8092', oid_symbol => 'OID_MULTIRANGE_ADJACENT_RANGE_OP', descr => 'is adjacent to',
+  oprname => '-|-', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcom => '-|-(anyrange,anymultirange)',
+  oprcode => 'multirange_adjacent_range', oprrest => 'scalarltsel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8093', oid_symbol => 'OID_MULTIRANGE_ADJACENT_MULTIRANGE_OP', descr => 'is adjacent to',
+  oprname => '-|-', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '-|-(anymultirange,anymultirange)',
+  oprcode => 'multirange_adjacent_multirange', oprrest => 'scalarltsel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8117', descr => 'multirange union',
+  oprname => '+', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'anymultirange', oprcom => '+(anymultirange,anymultirange)',
+  oprcode => 'multirange_union' },
+{ oid => '8123', descr => 'multirange minus',
+  oprname => '-', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'anymultirange', oprcode => 'multirange_minus' },
+{ oid => '8129', descr => 'multirange intersect',
+  oprname => '*', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'anymultirange', oprcom => '*(anymultirange,anymultirange)',
+  oprcode => 'multirange_intersect' },
+{ oid => '8082', oid_symbol => 'OID_RANGE_BEFORE_MULTIRANGE_OP', descr => 'is left of',
+  oprname => '<<', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '>>(anymultirange,anyrange)',
+  oprcode => 'range_before_multirange', oprrest => 'scalarltsel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8083', oid_symbol => 'OID_MULTIRANGE_BEFORE_RANGE_OP', descr => 'is left of',
+  oprname => '<<', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcom => '>>(anyrange,anymultirange)',
+  oprcode => 'multirange_before_range', oprrest => 'scalarltsel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8084', oid_symbol => 'OID_MULTIRANGE_BEFORE_MULTIRANGE_OP', descr => 'is left of',
+  oprname => '<<', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '>>(anymultirange,anymultirange)',
+  oprcode => 'multirange_before_multirange', oprrest => 'scalarltsel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8085', oid_symbol => 'OID_RANGE_AFTER_MULTIRANGE_OP', descr => 'is right of',
+  oprname => '>>', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '<<(anymultirange,anyrange)',
+  oprcode => 'range_after_multirange', oprrest => 'scalargtsel',
+  oprjoin => 'scalargtjoinsel' },
+{ oid => '8086', oid_symbol => 'OID_MULTIRANGE_AFTER_RANGE_OP', descr => 'is right of',
+  oprname => '>>', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcom => '<<(anyrange,anymultirange)',
+  oprcode => 'multirange_after_range', oprrest => 'scalargtsel',
+  oprjoin => 'scalargtjoinsel' },
+{ oid => '8087', oid_symbol => 'OID_MULTIRANGE_AFTER_MULTIRANGE_OP', descr => 'is right of',
+  oprname => '>>', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '<<(anymultirange,anymultirange)',
+  oprcode => 'multirange_after_multirange', oprrest => 'scalargtsel',
+  oprjoin => 'scalargtjoinsel' },
 
 ]
diff --git a/src/include/catalog/pg_opfamily.dat b/src/include/catalog/pg_opfamily.dat
index 26227df216..901fd7b303 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 a649e44d08..d6afad645b 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -9718,6 +9718,10 @@
   descr => 'the smallest range which includes both of the given ranges',
   proname => 'range_merge', prorettype => 'anyrange',
   proargtypes => 'anyrange anyrange', prosrc => 'range_merge' },
+{ oid => '8143',
+  descr => 'the smallest range which includes the whole multirange',
+  proname => 'range_merge', prorettype => 'anyrange',
+  proargtypes => 'anymultirange', prosrc => 'range_merge_from_multirange' },
 { oid => '3868',
   proname => 'range_intersect', prorettype => 'anyrange',
   proargtypes => 'anyrange anyrange', prosrc => 'range_intersect' },
@@ -9767,6 +9771,13 @@
 { oid => '3169', descr => 'restriction selectivity for range operators',
   proname => 'rangesel', provolatile => 's', prorettype => 'float8',
   proargtypes => 'internal oid internal int4', prosrc => 'rangesel' },
+{ oid => '8133', descr => 'range aggregate by intersecting',
+  proname => 'range_intersect_agg_transfn', prorettype => 'anyrange',
+  proargtypes => 'anyrange anyrange', prosrc => 'range_intersect_agg_transfn'},
+{ oid => '8134', descr => 'range aggregate by intersecting',
+  proname => 'range_intersect_agg', prokind => 'a', proisstrict => 'f',
+  prorettype => 'anyrange', proargtypes => 'anyrange',
+  prosrc => 'aggregate_dummy'},
 
 { oid => '3914', descr => 'convert an int4 range to canonical form',
   proname => 'int4range_canonical', prorettype => 'int4range',
@@ -9835,6 +9846,258 @@
   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 => '8094', descr => 'lower bound of multirange',
+  proname => 'lower', prorettype => 'anyelement', proargtypes => 'anymultirange',
+  prosrc => 'multirange_lower' },
+{ oid => '8095', descr => 'upper bound of multirange',
+  proname => 'upper', prorettype => 'anyelement', proargtypes => 'anymultirange',
+  prosrc => 'multirange_upper' },
+{ oid => '8057', descr => 'is the multirange empty?',
+  proname => 'isempty', prorettype => 'bool',
+  proargtypes => 'anymultirange', prosrc => 'multirange_empty' },
+{ oid => '8096', descr => 'is the multirange\'s lower bound inclusive?',
+  proname => 'lower_inc', prorettype => 'bool', proargtypes => 'anymultirange',
+  prosrc => 'multirange_lower_inc' },
+{ oid => '8097', descr => 'is the multirange\'s upper bound inclusive?',
+  proname => 'upper_inc', prorettype => 'bool', proargtypes => 'anymultirange',
+  prosrc => 'multirange_upper_inc' },
+{ oid => '8098', descr => 'is the multirange\'s lower bound infinite?',
+  proname => 'lower_inf', prorettype => 'bool', proargtypes => 'anymultirange',
+  prosrc => 'multirange_lower_inf' },
+{ oid => '8099', descr => 'is the multirange\'s upper bound infinite?',
+  proname => 'upper_inf', prorettype => 'bool', proargtypes => 'anymultirange',
+  prosrc => 'multirange_upper_inf' },
+{ oid => '8008', descr => 'multirange typanalyze',
+  proname => 'multirange_typanalyze', provolatile => 's', prorettype => 'bool',
+  proargtypes => 'internal', prosrc => 'multirange_typanalyze' },
+{ oid => '8033',
+  proname => 'multirange_eq', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_eq' },
+{ oid => '8034',
+  proname => 'multirange_ne', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_ne' },
+{ oid => '8070',
+  proname => 'range_overlaps_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_overlaps_multirange' },
+{ oid => '8071',
+  proname => 'multirange_overlaps_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_overlaps_range' },
+{ oid => '8072',
+  proname => 'multirange_overlaps_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_overlaps_multirange' },
+{ oid => '8058',
+  proname => 'multirange_contains_elem', prorettype => 'bool',
+  proargtypes => 'anymultirange anyelement', prosrc => 'multirange_contains_elem' },
+{ oid => '8059',
+  proname => 'multirange_contains_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_contains_range' },
+{ oid => '8060',
+  proname => 'multirange_contains_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_contains_multirange' },
+{ oid => '8061',
+  proname => 'elem_contained_by_multirange', prorettype => 'bool',
+  proargtypes => 'anyelement anymultirange', prosrc => 'elem_contained_by_multirange' },
+{ oid => '8062',
+  proname => 'range_contained_by_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_contained_by_multirange' },
+{ oid => '8063',
+  proname => 'multirange_contained_by_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_contained_by_multirange' },
+{ oid => '8088',
+  proname => 'range_adjacent_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_adjacent_multirange' },
+{ oid => '8089',
+  proname => 'multirange_adjacent_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_adjacent_multirange' },
+{ oid => '8090',
+  proname => 'multirange_adjacent_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_adjacent_range' },
+{ oid => '8076',
+  proname => 'range_before_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_before_multirange' },
+{ oid => '8077',
+  proname => 'multirange_before_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_before_range' },
+{ oid => '8078',
+  proname => 'multirange_before_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_before_multirange' },
+{ oid => '8079',
+  proname => 'range_after_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_after_multirange' },
+{ oid => '8080',
+  proname => 'multirange_after_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_after_range' },
+{ oid => '8081',
+  proname => 'multirange_after_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_after_multirange' },
+{ oid => '8100',
+  proname => 'range_overleft_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_overleft_multirange' },
+{ oid => '8101',
+  proname => 'multirange_overleft_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_overleft_range' },
+{ oid => '8102',
+  proname => 'multirange_overleft_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_overleft_multirange' },
+{ oid => '8103',
+  proname => 'range_overright_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_overright_multirange' },
+{ oid => '8104',
+  proname => 'multirange_overright_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_overright_range' },
+{ oid => '8105',
+  proname => 'multirange_overright_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_overright_multirange' },
+{ oid => '8114',
+  proname => 'multirange_union', prorettype => 'anymultirange',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_union' },
+{ oid => '8120',
+  proname => 'multirange_minus', prorettype => 'anymultirange',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_minus' },
+{ oid => '8126',
+  proname => 'multirange_intersect', prorettype => 'anymultirange',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_intersect' },
+{ oid => '8022', descr => 'less-equal-greater',
+  proname => 'multirange_cmp', prorettype => 'int4',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_cmp' },
+{ oid => '8023',
+  proname => 'multirange_lt', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_lt' },
+{ oid => '8024',
+  proname => 'multirange_le', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_le' },
+{ oid => '8025',
+  proname => 'multirange_ge', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_ge' },
+{ oid => '8026',
+  proname => 'multirange_gt', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_gt' },
+{ oid => '8038', descr => 'hash a multirange',
+  proname => 'hash_multirange', prorettype => 'int4', proargtypes => 'anymultirange',
+  prosrc => 'hash_multirange' },
+{ oid => '8039', descr => 'hash a multirange',
+  proname => 'hash_multirange_extended', prorettype => 'int8',
+  proargtypes => 'anymultirange int8', prosrc => 'hash_multirange_extended' },
+
+{ oid => '8045', descr => 'int4multirange constructor',
+  proname => 'int4multirange', proisstrict => 'f',
+  prorettype => 'int4multirange', proargtypes => '',
+  prosrc => 'multirange_constructor0' },
+{ oid => '8146', descr => 'int4multirange constructor',
+  proname => 'int4multirange', proisstrict => 't',
+  prorettype => 'int4multirange', proargtypes => 'int4range',
+  prosrc => 'multirange_constructor1' },
+{ oid => '8046', descr => 'int4multirange constructor',
+  proname => 'int4multirange', provariadic => 'int4range', proisstrict => 'f',
+  prorettype => 'int4multirange', proargtypes => '_int4range',
+  proallargtypes => '{_int4range}', proargmodes => '{v}',
+  prosrc => 'multirange_constructor2' },
+{ oid => '8047', descr => 'nummultirange constructor',
+  proname => 'nummultirange', proisstrict => 'f',
+  prorettype => 'nummultirange', proargtypes => '',
+  prosrc => 'multirange_constructor0' },
+{ oid => '8147', descr => 'nummultirange constructor',
+  proname => 'nummultirange', proisstrict => 't',
+  prorettype => 'nummultirange', proargtypes => 'numrange',
+  prosrc => 'multirange_constructor1' },
+{ oid => '8048', descr => 'nummultirange constructor',
+  proname => 'nummultirange', provariadic => 'numrange', proisstrict => 'f',
+  prorettype => 'nummultirange', proargtypes => '_numrange',
+  proallargtypes => '{_numrange}', proargmodes => '{v}',
+  prosrc => 'multirange_constructor2' },
+{ oid => '8049', descr => 'tsmultirange constructor',
+  proname => 'tsmultirange', proisstrict => 'f',
+  prorettype => 'tsmultirange', proargtypes => '',
+  prosrc => 'multirange_constructor0' },
+{ oid => '8148', descr => 'tsmultirange constructor',
+  proname => 'tsmultirange', proisstrict => 't',
+  prorettype => 'tsmultirange', proargtypes => 'tsrange',
+  prosrc => 'multirange_constructor1' },
+{ oid => '8050', descr => 'tsmultirange constructor',
+  proname => 'tsmultirange', provariadic => 'tsrange', proisstrict => 'f',
+  prorettype => 'tsmultirange', proargtypes => '_tsrange',
+  proallargtypes => '{_tsrange}', proargmodes => '{v}',
+  prosrc => 'multirange_constructor2' },
+{ oid => '8051', descr => 'tstzmultirange constructor',
+  proname => 'tstzmultirange', proisstrict => 'f',
+  prorettype => 'tstzmultirange', proargtypes => '',
+  prosrc => 'multirange_constructor0' },
+{ oid => '8149', descr => 'tstzmultirange constructor',
+  proname => 'tstzmultirange', proisstrict => 't',
+  prorettype => 'tstzmultirange', proargtypes => 'tstzrange',
+  prosrc => 'multirange_constructor1' },
+{ oid => '8052', descr => 'tstzmultirange constructor',
+  proname => 'tstzmultirange', provariadic => 'tstzrange', proisstrict => 'f',
+  prorettype => 'tstzmultirange', proargtypes => '_tstzrange',
+  proallargtypes => '{_tstzrange}', proargmodes => '{v}',
+  prosrc => 'multirange_constructor2' },
+{ oid => '8053', descr => 'datemultirange constructor',
+  proname => 'datemultirange', proisstrict => 'f',
+  prorettype => 'datemultirange', proargtypes => '',
+  prosrc => 'multirange_constructor0' },
+{ oid => '8150', descr => 'datemultirange constructor',
+  proname => 'datemultirange', proisstrict => 't',
+  prorettype => 'datemultirange', proargtypes => 'daterange',
+  prosrc => 'multirange_constructor1' },
+{ oid => '8054', descr => 'datemultirange constructor',
+  proname => 'datemultirange', provariadic => 'daterange', proisstrict => 'f',
+  prorettype => 'datemultirange', proargtypes => '_daterange',
+  proallargtypes => '{_daterange}', proargmodes => '{v}',
+  prosrc => 'multirange_constructor2' },
+{ oid => '8055', descr => 'int8multirange constructor',
+  proname => 'int8multirange', proisstrict => 'f',
+  prorettype => 'int8multirange', proargtypes => '',
+  prosrc => 'multirange_constructor0' },
+{ oid => '8151', descr => 'int8multirange constructor',
+  proname => 'int8multirange', proisstrict => 't',
+  prorettype => 'int8multirange', proargtypes => 'int8range',
+  prosrc => 'multirange_constructor1' },
+{ oid => '8056', descr => 'int8multirange constructor',
+  proname => 'int8multirange', provariadic => 'int8range', proisstrict => 'f',
+  prorettype => 'int8multirange', proargtypes => '_int8range',
+  proallargtypes => '{_int8range}', proargmodes => '{v}',
+  prosrc => 'multirange_constructor2' },
+{ oid => '8152', descr => 'anymultirange cast',
+  proname => 'multirange', proisstrict => 't',
+  prorettype => 'anymultirange', proargtypes => 'anyrange',
+  prosrc => 'multirange_constructor1' },
+{ oid => '8130', descr => 'aggregate transition function',
+  proname => 'range_agg_transfn', proisstrict => 'f', prorettype => 'internal',
+  proargtypes => 'internal anyrange', prosrc => 'range_agg_transfn' },
+{ oid => '8131', descr => 'aggregate final function',
+  proname => 'range_agg_finalfn', proisstrict => 'f', prorettype => 'anymultirange',
+  proargtypes => 'internal anyrange', prosrc => 'range_agg_finalfn' },
+{ oid => '8132', descr => 'combine aggregate input into a multirange',
+  proname => 'range_agg', prokind => 'a', proisstrict => 'f',
+  prorettype => 'anymultirange', proargtypes => 'anyrange',
+  prosrc => 'aggregate_dummy' },
+{ oid => '8135', descr => 'range aggregate by intersecting',
+  proname => 'multirange_intersect_agg_transfn', prorettype => 'anymultirange',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_intersect_agg_transfn'},
+{ oid => '8136', descr => 'range aggregate by intersecting',
+  proname => 'range_intersect_agg', prokind => 'a', proisstrict => 'f',
+  prorettype => 'anymultirange', proargtypes => 'anymultirange',
+  prosrc => 'aggregate_dummy'},
+
 # date, time, timestamp constructors
 { oid => '3846', descr => 'construct date',
   proname => 'make_date', prorettype => 'date', proargtypes => 'int4 int4 int4',
@@ -10217,6 +10480,14 @@
   proname => 'binary_upgrade_set_next_array_pg_type_oid', provolatile => 'v',
   proparallel => 'r', prorettype => 'void', proargtypes => 'oid',
   prosrc => 'binary_upgrade_set_next_array_pg_type_oid' },
+{ oid => '8144', descr => 'for use by pg_upgrade',
+  proname => 'binary_upgrade_set_next_multirange_pg_type_oid', provolatile => 'v',
+  proparallel => 'r', prorettype => 'void', proargtypes => 'oid',
+  prosrc => 'binary_upgrade_set_next_multirange_pg_type_oid' },
+{ oid => '8145', descr => 'for use by pg_upgrade',
+  proname => 'binary_upgrade_set_next_multirange_array_pg_type_oid', provolatile => 'v',
+  proparallel => 'r', prorettype => 'void', proargtypes => 'oid',
+  prosrc => 'binary_upgrade_set_next_multirange_array_pg_type_oid' },
 { oid => '3585', descr => 'for use by pg_upgrade',
   proname => 'binary_upgrade_set_next_toast_pg_type_oid', provolatile => 'v',
   proparallel => 'r', prorettype => 'void', proargtypes => 'oid',
diff --git a/src/include/catalog/pg_range.dat b/src/include/catalog/pg_range.dat
index 479754c245..10060255c9 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',
+  rngmultitypid => 'int4multirange' },
 { rngtypid => 'numrange', rngsubtype => 'numeric',
   rngsubopc => 'btree/numeric_ops', rngcanonical => '-',
-  rngsubdiff => 'numrange_subdiff' },
+  rngsubdiff => 'numrange_subdiff', rngmultitypid => 'nummultirange' },
 { rngtypid => 'tsrange', rngsubtype => 'timestamp',
   rngsubopc => 'btree/timestamp_ops', rngcanonical => '-',
-  rngsubdiff => 'tsrange_subdiff' },
+  rngsubdiff => 'tsrange_subdiff', rngmultitypid => 'tsmultirange' },
 { rngtypid => 'tstzrange', rngsubtype => 'timestamptz',
   rngsubopc => 'btree/timestamptz_ops', rngcanonical => '-',
-  rngsubdiff => 'tstzrange_subdiff' },
+  rngsubdiff => 'tstzrange_subdiff', rngmultitypid => 'tstzmultirange' },
 { rngtypid => 'daterange', rngsubtype => 'date', rngsubopc => 'btree/date_ops',
-  rngcanonical => 'daterange_canonical', rngsubdiff => 'daterange_subdiff' },
+  rngcanonical => 'daterange_canonical', rngsubdiff => 'daterange_subdiff',
+  rngmultitypid => 'datemultirange' },
 { rngtypid => 'int8range', rngsubtype => 'int8', rngsubopc => 'btree/int8_ops',
-  rngcanonical => 'int8range_canonical', rngsubdiff => 'int8range_subdiff' },
+  rngcanonical => 'int8range_canonical', rngsubdiff => 'int8range_subdiff',
+  rngmultitypid => 'int8multirange' },
 
 ]
diff --git a/src/include/catalog/pg_range.h b/src/include/catalog/pg_range.h
index 98172bb1b6..60a8fde518 100644
--- a/src/include/catalog/pg_range.h
+++ b/src/include/catalog/pg_range.h
@@ -34,6 +34,9 @@ CATALOG(pg_range,3541,RangeRelationId)
 	/* OID of range's element type (subtype) */
 	Oid			rngsubtype BKI_LOOKUP(pg_type);
 
+	/* OID of the range's multirange type */
+	Oid			rngmultitypid BKI_LOOKUP(pg_type);
+
 	/* collation for this range type, or 0 */
 	Oid			rngcollation BKI_DEFAULT(0);
 
@@ -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 2e6110e3f2..cb3d159bf1 100644
--- a/src/include/catalog/pg_type.dat
+++ b/src/include/catalog/pg_type.dat
@@ -491,6 +491,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 => 'R', 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 => 'R', 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 => 'R', 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 => 'R', 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 => 'R', 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 => 'R', 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
@@ -620,5 +654,10 @@
   typtype => 'p', typcategory => 'P', typinput => 'anycompatiblerange_in',
   typoutput => 'anycompatiblerange_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 7b37562648..f1818d9a52 100644
--- a/src/include/catalog/pg_type.h
+++ b/src/include/catalog/pg_type.h
@@ -263,6 +263,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 */
 
@@ -304,7 +305,8 @@ typedef FormData_pg_type *Form_pg_type;
 	 (typid) == ANYARRAYOID || \
 	 (typid) == ANYNONARRAYOID || \
 	 (typid) == ANYENUMOID || \
-	 (typid) == ANYRANGEOID)
+	 (typid) == ANYRANGEOID || \
+	 (typid) == ANYMULTIRANGEOID)
 
 #define IsPolymorphicTypeFamily2(typid)  \
 	((typid) == ANYCOMPATIBLEOID || \
@@ -369,4 +371,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/commands/typecmds.h b/src/include/commands/typecmds.h
index 23130895af..989914dfe1 100644
--- a/src/include/commands/typecmds.h
+++ b/src/include/commands/typecmds.h
@@ -29,6 +29,8 @@ extern ObjectAddress DefineRange(CreateRangeStmt *stmt);
 extern ObjectAddress AlterEnum(AlterEnumStmt *stmt);
 extern ObjectAddress DefineCompositeType(RangeVar *typevar, List *coldeflist);
 extern Oid	AssignTypeArrayOid(void);
+extern Oid	AssignTypeMultirangeOid(void);
+extern Oid	AssignTypeMultirangeArrayOid(void);
 
 extern ObjectAddress AlterDomainDefault(List *names, Node *defaultRaw);
 extern ObjectAddress AlterDomainNotNull(List *names, bool notNull);
diff --git a/src/include/utils/lsyscache.h b/src/include/utils/lsyscache.h
index 374f57fb43..f2e73eabb0 100644
--- a/src/include/utils/lsyscache.h
+++ b/src/include/utils/lsyscache.h
@@ -156,6 +156,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);
@@ -182,6 +183,8 @@ 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_collation(Oid rangeOid);
+extern Oid	get_range_multirange(Oid rangeOid);
+extern Oid	get_multirange_range(Oid multirangeOid);
 extern Oid	get_index_column_opclass(Oid index_oid, int attno);
 extern bool	get_index_isreplident(Oid index_oid);
 extern bool get_index_isvalid(Oid index_oid);
diff --git a/src/include/utils/multirangetypes.h b/src/include/utils/multirangetypes.h
new file mode 100644
index 0000000000..b270eb7f51
--- /dev/null
+++ b/src/include/utils/multirangetypes.h
@@ -0,0 +1,103 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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
+{
+	int32		vl_len_;		/* varlena header (do not touch directly!) */
+	Oid			multirangetypid;	/* multirange type's own OID */
+	uint32		rangeCount;		/* the number of ranges */
+
+	/*
+	 * Following the count 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);
+extern bool multirange_contains_elem_internal(TypeCacheEntry *typcache, MultirangeType *mr,
+											  Datum elem);
+extern bool multirange_contains_range_internal(TypeCacheEntry *typcache, MultirangeType *mr,
+											   RangeType *r);
+extern bool multirange_contains_multirange_internal(TypeCacheEntry *typcache,
+													MultirangeType *mr1,
+													MultirangeType *mr2);
+extern bool range_overlaps_multirange_internal(TypeCacheEntry *typcache, RangeType *r,
+											   MultirangeType *mr);
+extern bool multirange_overlaps_multirange_internal(TypeCacheEntry *typcache,
+													MultirangeType *mr1,
+													MultirangeType *mr2);
+extern bool range_before_multirange_internal(TypeCacheEntry *typcache, RangeType *r,
+											 MultirangeType *mr);
+extern bool range_after_multirange_internal(TypeCacheEntry *typcache, RangeType *r,
+											MultirangeType *mr);
+extern bool multirange_before_multirange_internal(TypeCacheEntry *typcache,
+												  MultirangeType *mr1,
+												  MultirangeType *mr2);
+extern MultirangeType *multirange_minus_internal(Oid mltrngtypoid,
+												 TypeCacheEntry *rangetyp,
+												 int32 range_count1,
+												 RangeType **ranges1,
+												 int32 range_count2,
+												 RangeType **ranges2);
+extern MultirangeType *multirange_intersect_internal(Oid mltrngtypoid,
+													 TypeCacheEntry *rangetyp,
+													 int32 range_count1,
+													 RangeType **ranges1,
+													 int32 range_count2,
+													 RangeType **ranges2);
+
+/* 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 0cbbf09033..204aee4054 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)
 
@@ -92,13 +94,21 @@ typedef struct
  * prototypes for functions defined in rangetypes.c
  */
 
-extern bool range_contains_elem_internal(TypeCacheEntry *typcache, const RangeType *r, Datum val);
-
 /* internal versions of the above */
+extern int	range_cmp_internal(TypeCacheEntry *typcache, const RangeType *r1,
+							   const RangeType *r2);
+extern uint32 hash_range_internal(TypeCacheEntry *typcache, const RangeType *r);
+extern uint64 hash_range_extended_internal(TypeCacheEntry *typcache, const RangeType *r,
+										   Datum seed);
+extern Datum range_lower_internal(TypeCacheEntry *typcache, const RangeType *r1,
+								  bool *isnull);
+extern Datum range_upper_internal(TypeCacheEntry *typcache, const RangeType *r1,
+								  bool *isnull);
 extern bool range_eq_internal(TypeCacheEntry *typcache, const RangeType *r1,
 							  const RangeType *r2);
 extern bool range_ne_internal(TypeCacheEntry *typcache, const RangeType *r1,
 							  const RangeType *r2);
+extern bool range_contains_elem_internal(TypeCacheEntry *typcache, const RangeType *r, Datum val);
 extern bool range_contains_internal(TypeCacheEntry *typcache, const RangeType *r1,
 									const RangeType *r2);
 extern bool range_contained_by_internal(TypeCacheEntry *typcache, const RangeType *r1,
@@ -115,6 +125,12 @@ extern bool range_overleft_internal(TypeCacheEntry *typcache, const RangeType *r
 									const RangeType *r2);
 extern bool range_overright_internal(TypeCacheEntry *typcache, const RangeType *r1,
 									 const RangeType *r2);
+extern RangeType *range_union_internal(TypeCacheEntry *typcache, RangeType *r1,
+									   RangeType *r2, bool strict);
+extern RangeType *range_minus_internal(TypeCacheEntry *typcache, RangeType *r1,
+									   RangeType *r2);
+extern RangeType *range_intersect_internal(TypeCacheEntry *typcache, const RangeType *r1,
+										   const RangeType *r2);
 
 /* assorted support functions */
 extern TypeCacheEntry *range_get_typcache(FunctionCallInfo fcinfo,
@@ -125,6 +141,7 @@ extern void range_deserialize(TypeCacheEntry *typcache, const RangeType *range,
 							  RangeBound *lower, RangeBound *upper,
 							  bool *empty);
 extern char range_get_flags(const RangeType *range);
+extern bool range_has_flag(const RangeType *range, char flag);
 extern void range_set_contain_empty(RangeType *range);
 extern RangeType *make_range(TypeCacheEntry *typcache, RangeBound *lower,
 							 RangeBound *upper, bool empty);
@@ -132,8 +149,12 @@ extern int range_cmp_bounds(TypeCacheEntry *typcache, const RangeBound *b1,
 							const RangeBound *b2);
 extern int range_cmp_bound_values(TypeCacheEntry *typcache, const RangeBound *b1,
 								  const RangeBound *b2);
-extern bool bounds_adjacent(TypeCacheEntry *typcache, RangeBound bound1,
-							RangeBound bound2);
+extern int range_compare(const void *key1, const void *key2, void *arg);
+extern bool bounds_adjacent(TypeCacheEntry *typcache, const RangeBound bound1,
+							const RangeBound bound2);
 extern RangeType *make_empty_range(TypeCacheEntry *typcache);
+extern bool range_split_internal(TypeCacheEntry *typcache, const RangeType *r1,
+								 const RangeType *r2, RangeType **output1,
+								 RangeType **output2);
 
 #endif							/* RANGETYPES_H */
diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h
index f27b73d76d..e8f393520a 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -79,6 +79,7 @@ enum SysCacheIdentifier
 	PUBLICATIONOID,
 	PUBLICATIONREL,
 	PUBLICATIONRELMAP,
+	RANGEMULTIRANGE,
 	RANGETYPE,
 	RELNAMENSP,
 	RELOID,
diff --git a/src/include/utils/typcache.h b/src/include/utils/typcache.h
index cdd20e56d7..33f332a141 100644
--- a/src/include/utils/typcache.h
+++ b/src/include/utils/typcache.h
@@ -100,6 +100,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;	/* multirange's range underlying type */
+
 	/*
 	 * Domain's base type and typmod if it's a domain type.  Zeroes if not
 	 * domain, or if information hasn't been requested.
@@ -143,6 +148,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 828ff5a288..1e3ff02289 100644
--- a/src/pl/plpgsql/src/pl_comp.c
+++ b/src/pl/plpgsql/src/pl_comp.c
@@ -513,6 +513,8 @@ do_compile(FunctionCallInfo fcinfo,
 					else if (rettypeid == ANYRANGEOID ||
 							 rettypeid == ANYCOMPATIBLERANGEOID)
 						rettypeid = INT4RANGEOID;
+					else if (rettypeid == ANYMULTIRANGEOID)
+						rettypeid = INT4MULTIRANGEOID;
 					else		/* ANYELEMENT or ANYNONARRAY or ANYCOMPATIBLE */
 						rettypeid = INT4OID;
 					/* XXX what could we use for ANYENUM? */
@@ -2089,6 +2091,7 @@ build_datatype(HeapTuple typeTup, int32 typmod,
 		case TYPTYPE_BASE:
 		case TYPTYPE_ENUM:
 		case TYPTYPE_RANGE:
+		case TYPTYPE_MULTIRANGE:
 			typ->ttype = PLPGSQL_TTYPE_SCALAR;
 			break;
 		case TYPTYPE_COMPOSITE:
@@ -2507,6 +2510,9 @@ plpgsql_resolve_polymorphic_argtypes(int numargs,
 				case ANYCOMPATIBLERANGEOID:
 					argtypes[i] = INT4RANGEOID;
 					break;
+				case ANYMULTIRANGEOID:
+					argtypes[i] = INT4MULTIRANGEOID;
+					break;
 				default:
 					break;
 			}
diff --git a/src/test/regress/expected/dependency.out b/src/test/regress/expected/dependency.out
index b4a11b8aa9..8232795148 100644
--- a/src/test/regress/expected/dependency.out
+++ b/src/test/regress/expected/dependency.out
@@ -140,6 +140,7 @@ owner of sequence deptest_a_seq
 owner of table deptest
 owner of function deptest_func()
 owner of type deptest_enum
+owner of type deptest_multirange
 owner of type deptest_range
 owner of table deptest2
 owner of sequence ss1
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..23a06b301f
--- /dev/null
+++ b/src/test/regress/expected/multirangetypes.out
@@ -0,0 +1,2395 @@
+-- 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)
+
+--
+-- test casts, both a built-in range type and a user-defined one:
+--
+select 'empty'::int4range::int4multirange;
+ int4multirange 
+----------------
+ {}
+(1 row)
+
+select int4range(1, 3)::int4multirange;
+ int4range 
+-----------
+ {[1,3)}
+(1 row)
+
+select int4range(1, null)::int4multirange;
+ int4range 
+-----------
+ {[1,)}
+(1 row)
+
+select int4range(null, null)::int4multirange;
+ int4range 
+-----------
+ {(,)}
+(1 row)
+
+select 'empty'::textrange::textmultirange;
+ textmultirange 
+----------------
+ {}
+(1 row)
+
+select textrange('a', 'c')::textmultirange;
+ textrange 
+-----------
+ {[a,c)}
+(1 row)
+
+select textrange('a', null)::textmultirange;
+ textrange 
+-----------
+ {[a,)}
+(1 row)
+
+select textrange(null, null)::textmultirange;
+ textrange 
+-----------
+ {(,)}
+(1 row)
+
+--
+-- create some test data and test the operators
+--
+CREATE TABLE nummultirange_test (nmr NUMMULTIRANGE);
+CREATE INDEX nummultirange_test_btree ON nummultirange_test(nmr);
+INSERT INTO nummultirange_test VALUES('{}');
+INSERT INTO nummultirange_test VALUES('{[,)}');
+INSERT INTO nummultirange_test VALUES('{[3,]}');
+INSERT INTO nummultirange_test VALUES('{[,), [3,]}');
+INSERT INTO nummultirange_test VALUES('{[, 5)}');
+INSERT INTO nummultirange_test VALUES(nummultirange());
+INSERT INTO nummultirange_test VALUES(nummultirange(variadic '{}'::numrange[]));
+INSERT INTO nummultirange_test VALUES(nummultirange(numrange(1.1, 2.2)));
+INSERT INTO nummultirange_test VALUES('{empty}');
+INSERT INTO nummultirange_test VALUES(nummultirange(numrange(1.7, 1.7, '[]'), numrange(1.7, 1.9)));
+INSERT INTO nummultirange_test VALUES(nummultirange(numrange(1.7, 1.7, '[]'), numrange(1.9, 2.1)));
+SELECT nmr, isempty(nmr), lower(nmr), upper(nmr) FROM nummultirange_test ORDER BY nmr;
+          nmr          | isempty | lower | upper 
+-----------------------+---------+-------+-------
+ {}                    | t       |       |      
+ {}                    | t       |       |      
+ {}                    | t       |       |      
+ {}                    | t       |       |      
+ {(,5)}                | f       |       |     5
+ {(,)}                 | f       |       |      
+ {(,)}                 | f       |       |      
+ {[1.1,2.2)}           | f       |   1.1 |   2.2
+ {[1.7,1.7],[1.9,2.1)} | f       |   1.7 |   2.1
+ {[1.7,1.9)}           | f       |   1.7 |   1.9
+ {[3,)}                | f       |     3 |      
+(11 rows)
+
+SELECT nmr, lower_inc(nmr), lower_inf(nmr), upper_inc(nmr), upper_inf(nmr) FROM nummultirange_test ORDER BY nmr;
+          nmr          | lower_inc | lower_inf | upper_inc | upper_inf 
+-----------------------+-----------+-----------+-----------+-----------
+ {}                    | f         | f         | f         | f
+ {}                    | f         | f         | f         | f
+ {}                    | f         | f         | f         | f
+ {}                    | f         | f         | f         | f
+ {(,5)}                | f         | t         | f         | f
+ {(,)}                 | f         | t         | f         | t
+ {(,)}                 | f         | t         | f         | t
+ {[1.1,2.2)}           | t         | f         | f         | f
+ {[1.7,1.7],[1.9,2.1)} | t         | f         | f         | f
+ {[1.7,1.9)}           | t         | f         | f         | f
+ {[3,)}                | t         | f         | f         | t
+(11 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr = '{}';
+ nmr 
+-----
+ {}
+ {}
+ {}
+ {}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr = '{(,5)}';
+  nmr   
+--------
+ {(,5)}
+(1 row)
+
+SELECT * FROM nummultirange_test WHERE nmr = '{[3,)}';
+  nmr   
+--------
+ {[3,)}
+(1 row)
+
+SELECT * FROM nummultirange_test WHERE nmr = '{[1.7,1.7]}';
+ nmr 
+-----
+(0 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr = '{[1.7,1.7],[1.9,2.1)}';
+          nmr          
+-----------------------
+ {[1.7,1.7],[1.9,2.1)}
+(1 row)
+
+SELECT * FROM nummultirange_test WHERE nmr < '{}';
+ nmr 
+-----
+(0 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr < '{[-1000.0, -1000.0]}';
+  nmr   
+--------
+ {}
+ {(,)}
+ {(,)}
+ {(,5)}
+ {}
+ {}
+ {}
+(7 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr < '{[0.0, 1.0]}';
+  nmr   
+--------
+ {}
+ {(,)}
+ {(,)}
+ {(,5)}
+ {}
+ {}
+ {}
+(7 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr < '{[1000.0, 1001.0]}';
+          nmr          
+-----------------------
+ {}
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+ {}
+ {}
+ {[1.1,2.2)}
+ {}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(11 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr <= '{}';
+ nmr 
+-----
+ {}
+ {}
+ {}
+ {}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr <= '{[3,)}';
+          nmr          
+-----------------------
+ {}
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+ {}
+ {}
+ {[1.1,2.2)}
+ {}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(11 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr >= '{}';
+          nmr          
+-----------------------
+ {}
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+ {}
+ {}
+ {[1.1,2.2)}
+ {}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(11 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr >= '{[3,)}';
+  nmr   
+--------
+ {[3,)}
+(1 row)
+
+SELECT * FROM nummultirange_test WHERE nmr > '{}';
+          nmr          
+-----------------------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+ {[1.1,2.2)}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(7 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr > '{[-1000.0, -1000.0]}';
+          nmr          
+-----------------------
+ {[3,)}
+ {[1.1,2.2)}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr > '{[0.0, 1.0]}';
+          nmr          
+-----------------------
+ {[3,)}
+ {[1.1,2.2)}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr > '{[1000.0, 1001.0]}';
+ nmr 
+-----
+(0 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr <> '{}';
+          nmr          
+-----------------------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+ {[1.1,2.2)}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(7 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr <> '{(,5)}';
+          nmr          
+-----------------------
+ {}
+ {(,)}
+ {[3,)}
+ {(,)}
+ {}
+ {}
+ {[1.1,2.2)}
+ {}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(10 rows)
+
+select nummultirange(numrange(2.0, 1.0));
+ERROR:  range lower bound must be less than or equal to range upper bound
+select nummultirange(numrange(5.0, 6.0), numrange(1.0, 2.0));
+     nummultirange     
+-----------------------
+ {[1.0,2.0),[5.0,6.0)}
+(1 row)
+
+-- overlaps
+SELECT * FROM nummultirange_test WHERE range_overlaps_multirange(numrange(4.0, 4.2), nmr);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE numrange(4.0, 4.2) && nmr;
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE multirange_overlaps_range(nmr, numrange(4.0, 4.2));
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr && numrange(4.0, 4.2);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE multirange_overlaps_multirange(nmr, nummultirange(numrange(4.0, 4.2), numrange(6.0, 7.0)));
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr && nummultirange(numrange(4.0, 4.2), numrange(6.0, 7.0));
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr && nummultirange(numrange(6.0, 7.0));
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+(3 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr && nummultirange(numrange(6.0, 7.0), numrange(8.0, 9.0));
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+(3 rows)
+
+-- mr contains x
+SELECT * FROM nummultirange_test WHERE multirange_contains_elem(nmr, 4.0);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr @> 4.0;
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE multirange_contains_range(nmr, numrange(4.0, 4.2));
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr @> numrange(4.0, 4.2);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE multirange_contains_multirange(nmr, '{[4.0,4.2), [6.0, 8.0)}');
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+(3 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr @> '{[4.0,4.2), [6.0, 8.0)}'::nummultirange;
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+(3 rows)
+
+-- x is contained by mr
+SELECT * FROM nummultirange_test WHERE elem_contained_by_multirange(4.0, nmr);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE 4.0 <@ nmr;
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE range_contained_by_multirange(numrange(4.0, 4.2), nmr);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE numrange(4.0, 4.2) <@ nmr;
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE multirange_contained_by_multirange('{[4.0,4.2), [6.0, 8.0)}', nmr);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+(3 rows)
+
+SELECT * FROM nummultirange_test WHERE '{[4.0,4.2), [6.0, 8.0)}'::nummultirange <@ nmr;
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+(3 rows)
+
+-- overlaps
+SELECT 'empty'::numrange && nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT 'empty'::numrange && nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() && 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) && 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() && nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() && nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) && nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) && nummultirange(numrange(1,2), numrange(7,8));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(7,8)) && nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) && nummultirange(numrange(1,2), numrange(3.5,8));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(3.5,8)) && numrange(3,4);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(3.5,8)) && nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+-- contains
+SELECT nummultirange() @> nummultirange();
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange() @> 'empty'::numrange;
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(null,null)) @> numrange(1,2);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(null,null)) @> numrange(null,2);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(null,null)) @> numrange(2,null);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(null,5)) @> numrange(null,3);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(null,5)) @> numrange(null,8);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(5,null)) @> numrange(8,null);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(5,null)) @> numrange(3,null);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,5)) @> numrange(8,9);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,5)) @> numrange(3,9);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,5)) @> numrange(1,4);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,5)) @> numrange(1,5);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(-4,-2), numrange(1,5)) @> numrange(1,5);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,5), numrange(8,9)) @> numrange(1,5);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,5), numrange(8,9)) @> numrange(6,7);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,5), numrange(6,9)) @> numrange(6,7);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[1,5)}'::nummultirange @> '{[1,5)}';
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[-4,-2), [1,5)}'::nummultirange @> '{[1,5)}';
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[1,5), [8,9)}'::nummultirange @> '{[1,5)}';
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[1,5), [8,9)}'::nummultirange @> '{[6,7)}';
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT '{[1,5), [6,9)}'::nummultirange @> '{[6,7)}';
+ ?column? 
+----------
+ t
+(1 row)
+
+-- is contained by
+SELECT nummultirange() <@ nummultirange();
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT 'empty'::numrange <@ nummultirange();
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,2) <@ nummultirange(numrange(null,null));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(null,2) <@ nummultirange(numrange(null,null));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(2,null) <@ nummultirange(numrange(null,null));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(null,3) <@ nummultirange(numrange(null,5));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(null,8) <@ nummultirange(numrange(null,5));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(8,null) <@ nummultirange(numrange(5,null));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(3,null) <@ nummultirange(numrange(5,null));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(8,9) <@ nummultirange(numrange(1,5));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(3,9) <@ nummultirange(numrange(1,5));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(1,4) <@ nummultirange(numrange(1,5));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,5) <@ nummultirange(numrange(1,5));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,5) <@ nummultirange(numrange(-4,-2), numrange(1,5));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,5) <@ nummultirange(numrange(1,5), numrange(8,9));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(6,7) <@ nummultirange(numrange(1,5), numrange(8,9));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(6,7) <@ nummultirange(numrange(1,5), numrange(6,9));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[1,5)}' <@ '{[1,5)}'::nummultirange;
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[1,5)}' <@ '{[-4,-2), [1,5)}'::nummultirange;
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[1,5)}' <@ '{[1,5), [8,9)}'::nummultirange;
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[6,7)}' <@ '{[1,5), [8,9)}'::nummultirange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT '{[6,7)}' <@ '{[1,5), [6,9)}'::nummultirange;
+ ?column? 
+----------
+ t
+(1 row)
+
+-- overleft
+SELECT 'empty'::numrange &< nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT 'empty'::numrange &< nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() &< 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) &< 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() &< nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) &< nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() &< nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(6,7) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(1,2) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,4) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,6) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(3.5,6) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(6,7)) &< numrange(3,4);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) &< numrange(3,4);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,4)) &< numrange(3,4);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,6)) &< numrange(3,4);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3.5,6)) &< numrange(3,4);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(6,7)) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,4)) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,6)) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3.5,6)) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+-- overright
+SELECT nummultirange() &> 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) &> 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT 'empty'::numrange &> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT 'empty'::numrange &> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() &> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() &> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) &> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> numrange(6,7);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> numrange(1,2);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> numrange(1,4);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> numrange(1,6);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> numrange(3.5,6);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(3,4) &> nummultirange(numrange(6,7));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(3,4) &> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(3,4) &> nummultirange(numrange(1,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(3,4) &> nummultirange(numrange(1,6));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(3,4) &> nummultirange(numrange(3.5,6));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(6,7));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(1,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(1,6));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(3.5,6));
+ ?column? 
+----------
+ f
+(1 row)
+
+-- meets
+SELECT 'empty'::numrange -|- nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT 'empty'::numrange -|- nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() -|- 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() -|- nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() -|- nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(1,2) -|- nummultirange(numrange(2,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,2) -|- nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- numrange(2,4);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- numrange(3,4);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- nummultirange(numrange(2,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(5,6)) -|- nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(5,6)) -|- nummultirange(numrange(6,7));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(5,6)) -|- nummultirange(numrange(8,9));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- nummultirange(numrange(2,4), numrange(6,7));
+ ?column? 
+----------
+ t
+(1 row)
+
+-- strictly left
+select 'empty'::numrange << nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(1,2) << nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(1,2) << nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+select numrange(1,2) << nummultirange(numrange(0,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(1,2) << nummultirange(numrange(0,4), numrange(7,8));
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange() << 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange() << numrange(1,2);
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(3,4)) << numrange(3,6);
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(0,2)) << numrange(3,6);
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange(numrange(0,2), numrange(7,8)) << numrange(3,6);
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(-4,-2), numrange(0,2)) << numrange(3,6);
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange() << nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange() << nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(1,2)) << nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(1,2)) << nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(1,2)) << nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange(numrange(1,2)) << nummultirange(numrange(3,4), numrange(7,8));
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange(numrange(1,2), numrange(4,5)) << nummultirange(numrange(3,4), numrange(7,8));
+ ?column? 
+----------
+ f
+(1 row)
+
+-- strictly right
+select nummultirange() >> 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange() >> numrange(1,2);
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(3,4)) >> numrange(1,2);
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange(numrange(0,4)) >> numrange(1,2);
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(0,4), numrange(7,8)) >> numrange(1,2);
+ ?column? 
+----------
+ f
+(1 row)
+
+select 'empty'::numrange >> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(1,2) >> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(3,6) >> nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(3,6) >> nummultirange(numrange(0,2));
+ ?column? 
+----------
+ t
+(1 row)
+
+select numrange(3,6) >> nummultirange(numrange(0,2), numrange(7,8));
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(3,6) >> nummultirange(numrange(-4,-2), numrange(0,2));
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange() >> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(1,2)) >> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange() >> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(1,2)) >> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(3,4)) >> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange(numrange(3,4), numrange(7,8)) >> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange(numrange(3,4), numrange(7,8)) >> nummultirange(numrange(1,2), numrange(4,5));
+ ?column? 
+----------
+ f
+(1 row)
+
+-- union
+SELECT nummultirange() + nummultirange();
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange() + nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) + nummultirange();
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) + nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) + nummultirange(numrange(2,4));
+ ?column? 
+----------
+ {[1,4)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) + nummultirange(numrange(3,4));
+   ?column?    
+---------------
+ {[1,2),[3,4)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) + nummultirange(numrange(2,4));
+ ?column? 
+----------
+ {[1,5)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) + nummultirange(numrange(3,4));
+   ?column?    
+---------------
+ {[1,2),[3,5)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) + nummultirange(numrange(0,9));
+ ?column? 
+----------
+ {[0,9)}
+(1 row)
+
+-- merge
+SELECT range_merge(nummultirange());
+ range_merge 
+-------------
+ empty
+(1 row)
+
+SELECT range_merge(nummultirange(numrange(1,2)));
+ range_merge 
+-------------
+ [1,2)
+(1 row)
+
+SELECT range_merge(nummultirange(numrange(1,2), numrange(7,8)));
+ range_merge 
+-------------
+ [1,8)
+(1 row)
+
+-- minus
+SELECT nummultirange() - nummultirange();
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange() - nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) - nummultirange();
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(3,4)) - nummultirange();
+   ?column?    
+---------------
+ {[1,2),[3,4)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) - nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) - nummultirange(numrange(2,4));
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) - nummultirange(numrange(3,4));
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {[2,4)}
+(1 row)
+
+SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(2,3));
+   ?column?    
+---------------
+ {[1,2),[3,4)}
+(1 row)
+
+SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(0,8));
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(0,2));
+ ?column? 
+----------
+ {[2,4)}
+(1 row)
+
+SELECT nummultirange(numrange(1,8)) - nummultirange(numrange(0,2), numrange(3,4));
+   ?column?    
+---------------
+ {[2,3),[4,8)}
+(1 row)
+
+SELECT nummultirange(numrange(1,8)) - nummultirange(numrange(2,3), numrange(5,null));
+   ?column?    
+---------------
+ {[1,2),[3,5)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(-2,0));
+   ?column?    
+---------------
+ {[1,2),[4,5)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(2,4));
+   ?column?    
+---------------
+ {[1,2),[4,5)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(3,5));
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(0,9));
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange(numrange(1,3), numrange(4,5)) - nummultirange(numrange(2,9));
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(8,9));
+   ?column?    
+---------------
+ {[1,2),[4,5)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(-2,0), numrange(8,9));
+   ?column?    
+---------------
+ {[1,2),[4,5)}
+(1 row)
+
+-- intersection
+SELECT nummultirange() * nummultirange();
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange() * nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) * nummultirange();
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT '{[1,3)}'::nummultirange * '{[1,5)}'::nummultirange;
+ ?column? 
+----------
+ {[1,3)}
+(1 row)
+
+SELECT '{[1,3)}'::nummultirange * '{[0,5)}'::nummultirange;
+ ?column? 
+----------
+ {[1,3)}
+(1 row)
+
+SELECT '{[1,3)}'::nummultirange * '{[0,2)}'::nummultirange;
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT '{[1,3)}'::nummultirange * '{[2,5)}'::nummultirange;
+ ?column? 
+----------
+ {[2,3)}
+(1 row)
+
+SELECT '{[1,4)}'::nummultirange * '{[2,3)}'::nummultirange;
+ ?column? 
+----------
+ {[2,3)}
+(1 row)
+
+SELECT '{[1,4)}'::nummultirange * '{[0,2), [3,5)}'::nummultirange;
+   ?column?    
+---------------
+ {[1,2),[3,4)}
+(1 row)
+
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[0,8), [9,12)}'::nummultirange;
+       ?column?       
+----------------------
+ {[1,4),[7,8),[9,10)}
+(1 row)
+
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[9,12)}'::nummultirange;
+ ?column? 
+----------
+ {[9,10)}
+(1 row)
+
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[-5,-4), [5,6), [9,12)}'::nummultirange;
+ ?column? 
+----------
+ {[9,10)}
+(1 row)
+
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[0,2), [3,8), [9,12)}'::nummultirange;
+          ?column?          
+----------------------------
+ {[1,2),[3,4),[7,8),[9,10)}
+(1 row)
+
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[0,2), [3,8), [9,12)}'::nummultirange;
+          ?column?          
+----------------------------
+ {[1,2),[3,4),[7,8),[9,10)}
+(1 row)
+
+--
+-- range_agg function
+--
+create table reservations ( room_id integer not null, booked_during daterange );
+insert into reservations values
+-- 1: has a meets and a gap
+(1, daterange('2018-07-01', '2018-07-07')),
+(1, daterange('2018-07-07', '2018-07-14')),
+(1, daterange('2018-07-20', '2018-07-22')),
+-- 2: just a single row
+(2, daterange('2018-07-01', '2018-07-03')),
+-- 3: one null range
+(3, NULL),
+-- 4: two null ranges
+(4, NULL),
+(4, NULL),
+-- 5: a null range and a non-null range
+(5, NULL),
+(5, daterange('2018-07-01', '2018-07-03')),
+-- 6: has overlap
+(6, daterange('2018-07-01', '2018-07-07')),
+(6, daterange('2018-07-05', '2018-07-10')),
+-- 7: two ranges that meet: no gap or overlap
+(7, daterange('2018-07-01', '2018-07-07')),
+(7, daterange('2018-07-07', '2018-07-14')),
+-- 8: an empty range
+(8, 'empty'::daterange)
+;
+SELECT   room_id, range_agg(booked_during)
+FROM     reservations
+GROUP BY room_id
+ORDER BY room_id;
+ room_id |                     range_agg                     
+---------+---------------------------------------------------
+       1 | {[07-01-2018,07-14-2018),[07-20-2018,07-22-2018)}
+       2 | {[07-01-2018,07-03-2018)}
+       3 | 
+       4 | 
+       5 | {[07-01-2018,07-03-2018)}
+       6 | {[07-01-2018,07-10-2018)}
+       7 | {[07-01-2018,07-14-2018)}
+       8 | {}
+(8 rows)
+
+-- range_agg on a custom range type too
+SELECT  range_agg(r)
+FROM    (VALUES
+          ('[a,c]'::textrange),
+          ('[b,b]'::textrange),
+          ('[c,f]'::textrange),
+          ('[g,h)'::textrange),
+          ('[h,j)'::textrange)
+        ) t(r);
+   range_agg   
+---------------
+ {[a,f],[g,j)}
+(1 row)
+
+select range_intersect_agg(nmr) from nummultirange_test;
+ range_intersect_agg 
+---------------------
+ {}
+(1 row)
+
+select range_intersect_agg(nmr) from nummultirange_test where false;
+ range_intersect_agg 
+---------------------
+ 
+(1 row)
+
+-- test with just one input:
+select range_intersect_agg(nmr) from (values ('{[1,2]}'::nummultirange)) t(nmr);
+ range_intersect_agg 
+---------------------
+ {[1,2]}
+(1 row)
+
+select range_intersect_agg(nmr) from nummultirange_test where nmr @> 4.0;
+ range_intersect_agg 
+---------------------
+ {[3,5)}
+(1 row)
+
+create table nummultirange_test2(nmr nummultirange);
+create index nummultirange_test2_hash_idx on nummultirange_test2 using hash (nmr);
+INSERT INTO nummultirange_test2 VALUES('{[, 5)}');
+INSERT INTO nummultirange_test2 VALUES(nummultirange(numrange(1.1, 2.2)));
+INSERT INTO nummultirange_test2 VALUES(nummultirange(numrange(1.1, 2.2)));
+INSERT INTO nummultirange_test2 VALUES(nummultirange(numrange(1.1, 2.2,'()')));
+INSERT INTO nummultirange_test2 VALUES('{}');
+select * from nummultirange_test2 where nmr = '{}';
+ nmr 
+-----
+ {}
+(1 row)
+
+select * from nummultirange_test2 where nmr = nummultirange(numrange(1.1, 2.2));
+     nmr     
+-------------
+ {[1.1,2.2)}
+ {[1.1,2.2)}
+(2 rows)
+
+select * from nummultirange_test2 where nmr = nummultirange(numrange(1.1, 2.3));
+ nmr 
+-----
+(0 rows)
+
+set enable_nestloop=t;
+set enable_hashjoin=f;
+set enable_mergejoin=f;
+select * from nummultirange_test natural join nummultirange_test2 order by nmr;
+     nmr     
+-------------
+ {}
+ {}
+ {}
+ {}
+ {(,5)}
+ {[1.1,2.2)}
+ {[1.1,2.2)}
+(7 rows)
+
+set enable_nestloop=f;
+set enable_hashjoin=t;
+set enable_mergejoin=f;
+select * from nummultirange_test natural join nummultirange_test2 order by nmr;
+     nmr     
+-------------
+ {}
+ {}
+ {}
+ {}
+ {(,5)}
+ {[1.1,2.2)}
+ {[1.1,2.2)}
+(7 rows)
+
+set enable_nestloop=f;
+set enable_hashjoin=f;
+set enable_mergejoin=t;
+select * from nummultirange_test natural join nummultirange_test2 order by nmr;
+     nmr     
+-------------
+ {}
+ {}
+ {}
+ {}
+ {(,5)}
+ {[1.1,2.2)}
+ {[1.1,2.2)}
+(7 rows)
+
+set enable_nestloop to default;
+set enable_hashjoin to default;
+set enable_mergejoin to default;
+DROP TABLE nummultirange_test2;
+--
+-- Test user-defined multirange of floats
+--
+select '{[123.001, 5.e9)}'::float8multirange @> 888.882::float8;
+ ?column? 
+----------
+ t
+(1 row)
+
+create table float8multirange_test(f8mr float8multirange, i int);
+insert into float8multirange_test values(float8multirange(float8range(-100.00007, '1.111113e9')), 42);
+select * from float8multirange_test;
+           f8mr            | i  
+---------------------------+----
+ {[-100.00007,1111113000)} | 42
+(1 row)
+
+drop table float8multirange_test;
+--
+-- Test multirange types over domains
+--
+create domain mydomain as int4;
+create type mydomainrange as range(subtype=mydomain);
+select '{[4,50)}'::mydomainmultirange @> 7::mydomain;
+ ?column? 
+----------
+ t
+(1 row)
+
+drop domain mydomain cascade;
+NOTICE:  drop cascades to type mydomainrange
+--
+-- Test domains over multirange types
+--
+create domain restrictedmultirange as int4multirange check (upper(value) < 10);
+select '{[4,5)}'::restrictedmultirange @> 7;
+ ?column? 
+----------
+ f
+(1 row)
+
+select '{[4,50)}'::restrictedmultirange @> 7; -- should fail
+ERROR:  value for domain restrictedmultirange violates check constraint "restrictedmultirange_check"
+drop domain restrictedmultirange;
+--
+-- Test multiple multirange types over the same subtype
+--
+create type textrange1 as range(subtype=text, collation="C");
+create type textrange2 as range(subtype=text, collation="C");
+select textmultirange1(textrange2('a','Z'));  -- should fail
+ERROR:  function textmultirange1(textrange2) does not exist
+LINE 1: select textmultirange1(textrange2('a','Z'));
+               ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+select textmultirange1(textrange1('a','Z')) @> 'b'::text;
+ERROR:  range lower bound must be less than or equal to range upper bound
+select textmultirange2(textrange2('a','z')) @> 'b'::text;
+ ?column? 
+----------
+ t
+(1 row)
+
+drop type textrange1;
+drop type textrange2;
+--
+-- Test polymorphic type system
+--
+create function anyarray_anymultirange_func(a anyarray, r anymultirange)
+  returns anyelement as 'select $1[1] + lower($2);' language sql;
+select anyarray_anymultirange_func(ARRAY[1,2], int4multirange(int4range(10,20)));
+ anyarray_anymultirange_func 
+-----------------------------
+                          11
+(1 row)
+
+-- should fail
+select anyarray_anymultirange_func(ARRAY[1,2], nummultirange(numrange(10,20)));
+ERROR:  function anyarray_anymultirange_func(integer[], nummultirange) does not exist
+LINE 1: select anyarray_anymultirange_func(ARRAY[1,2], nummultirange...
+               ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+drop function anyarray_anymultirange_func(anyarray, anymultirange);
+-- should fail
+create function bogus_func(anyelement)
+  returns anymultirange as 'select int4multirange(int4range(1,10))' language sql;
+ERROR:  cannot determine result data type
+DETAIL:  A result of type anymultirange requires at least one input of type anyrange or anymultirange.
+-- should fail
+create function bogus_func(int)
+  returns anymultirange as 'select int4multirange(int4range(1,10))' language sql;
+ERROR:  cannot determine result data type
+DETAIL:  A result of type anymultirange requires at least one input of type anyrange or anymultirange.
+create function range_add_bounds(anymultirange)
+  returns anyelement as 'select lower($1) + upper($1)' language sql;
+select range_add_bounds(int4multirange(int4range(1, 17)));
+ range_add_bounds 
+------------------
+               18
+(1 row)
+
+select range_add_bounds(nummultirange(numrange(1.0001, 123.123)));
+ range_add_bounds 
+------------------
+         124.1231
+(1 row)
+
+create function multirangetypes_sql(q anymultirange, b anyarray, out c anyelement)
+  as $$ select upper($1) + $2[1] $$
+  language sql;
+select multirangetypes_sql(int4multirange(int4range(1,10)), ARRAY[2,20]);
+ multirangetypes_sql 
+---------------------
+                  12
+(1 row)
+
+select multirangetypes_sql(nummultirange(numrange(1,10)), ARRAY[2,20]);  -- match failure
+ERROR:  function multirangetypes_sql(nummultirange, integer[]) does not exist
+LINE 1: select multirangetypes_sql(nummultirange(numrange(1,10)), AR...
+               ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+--
+-- Arrays of multiranges
+--
+select ARRAY[nummultirange(numrange(1.1, 1.2)), nummultirange(numrange(12.3, 155.5))];
+              array               
+----------------------------------
+ {"{[1.1,1.2)}","{[12.3,155.5)}"}
+(1 row)
+
+create table i8mr_array (f1 int, f2 int8multirange[]);
+insert into i8mr_array values (42, array[int8multirange(int8range(1,10)), int8multirange(int8range(2,20))]);
+select * from i8mr_array;
+ f1 |           f2            
+----+-------------------------
+ 42 | {"{[1,10)}","{[2,20)}"}
+(1 row)
+
+drop table i8mr_array;
+--
+-- Multiranges of arrays
+--
+select arraymultirange(arrayrange(ARRAY[1,2], ARRAY[2,1]));
+   arraymultirange   
+---------------------
+ {["{1,2}","{2,1}")}
+(1 row)
+
+select arraymultirange(arrayrange(ARRAY[2,1], ARRAY[1,2]));  -- fail
+ERROR:  range lower bound must be less than or equal to range upper bound
+select array[1,1] <@ arraymultirange(arrayrange(array[1,2], array[2,1]));
+ ?column? 
+----------
+ f
+(1 row)
+
+select array[1,3] <@ arraymultirange(arrayrange(array[1,2], array[2,1]));
+ ?column? 
+----------
+ t
+(1 row)
+
+--
+-- Ranges of composites
+--
+create type two_ints as (a int, b int);
+create type two_ints_range as range (subtype = two_ints);
+-- with force_parallel_mode on, this exercises tqueue.c's range remapping
+select *, row_to_json(upper(t)) as u from
+  (values (two_ints_multirange(two_ints_range(row(1,2), row(3,4)))),
+          (two_ints_multirange(two_ints_range(row(5,6), row(7,8))))) v(t);
+          t          |       u       
+---------------------+---------------
+ {["(1,2)","(3,4)")} | {"a":3,"b":4}
+ {["(5,6)","(7,8)")} | {"a":7,"b":8}
+(2 rows)
+
+drop type two_ints cascade;
+NOTICE:  drop cascades to type two_ints_range
+--
+-- Check behavior when subtype lacks a hash function
+--
+set enable_sort = off;  -- try to make it pick a hash setop implementation
+select '{(2,5)}'::cashmultirange except select '{(5,6)}'::cashmultirange;
+ cashmultirange  
+-----------------
+ {($2.00,$5.00)}
+(1 row)
+
+reset enable_sort;
+--
+-- OUT/INOUT/TABLE functions
+--
+-- infer anymultirange from anymultirange
+create function mr_outparam_succeed(i anymultirange, out r anymultirange, out t text)
+  as $$ select $1, 'foo'::text $$ language sql;
+select * from mr_outparam_succeed(int4multirange(int4range(1,2)));
+    r    |  t  
+---------+-----
+ {[1,2)} | foo
+(1 row)
+
+-- infer anyarray from anymultirange
+create function mr_outparam_succeed2(i anymultirange, out r anyarray, out t text)
+  as $$ select ARRAY[upper($1)], 'foo'::text $$ language sql;
+select * from mr_outparam_succeed2(int4multirange(int4range(1,2)));
+  r  |  t  
+-----+-----
+ {2} | foo
+(1 row)
+
+-- infer anyrange from anymultirange
+create function mr_outparam_succeed3(i anymultirange, out r anyrange, out t text)
+  as $$ select range_merge($1), 'foo'::text $$ language sql;
+select * from mr_outparam_succeed3(int4multirange(int4range(1,2)));
+   r   |  t  
+-------+-----
+ [1,2) | foo
+(1 row)
+
+-- infer anymultirange from anyrange
+create function mr_outparam_succeed4(i anyrange, out r anymultirange, out t text)
+  as $$ select multirange($1), 'foo'::text $$ language sql;
+select * from mr_outparam_succeed4(int4range(1,2));
+    r    |  t  
+---------+-----
+ {[1,2)} | foo
+(1 row)
+
+-- infer anyelement from anymultirange
+create function mr_inoutparam_succeed(out i anyelement, inout r anymultirange)
+  as $$ select upper($1), $1 $$ language sql;
+select * from mr_inoutparam_succeed(int4multirange(int4range(1,2)));
+ i |    r    
+---+---------
+ 2 | {[1,2)}
+(1 row)
+
+-- infer anyelement+anymultirange from anyelement+anymultirange
+create function mr_table_succeed(i anyelement, r anymultirange) returns table(i anyelement, r anymultirange)
+  as $$ select $1, $2 $$ language sql;
+select * from mr_table_succeed(123, int4multirange(int4range(1,11)));
+  i  |    r     
+-----+----------
+ 123 | {[1,11)}
+(1 row)
+
+-- use anymultirange in plpgsql
+create function mr_polymorphic(i anyrange) returns anymultirange
+  as $$ begin return multirange($1); end; $$ language plpgsql;
+select mr_polymorphic(int4range(1, 4));
+ mr_polymorphic 
+----------------
+ {[1,4)}
+(1 row)
+
+-- should fail
+create function mr_outparam_fail(i anyelement, out r anymultirange, out t text)
+  as $$ select '[1,10]', 'foo' $$ language sql;
+ERROR:  cannot determine result data type
+DETAIL:  A result of type anymultirange requires at least one input of type anyrange or anymultirange.
+--should fail
+create function mr_inoutparam_fail(inout i anyelement, out r anymultirange)
+  as $$ select $1, '[1,10]' $$ language sql;
+ERROR:  cannot determine result data type
+DETAIL:  A result of type anymultirange requires at least one input of type anyrange or anymultirange.
+--should fail
+create function mr_table_fail(i anyelement) returns table(i anyelement, r anymultirange)
+  as $$ select $1, '[1,10]' $$ language sql;
+ERROR:  cannot determine result data type
+DETAIL:  A result of type anymultirange requires at least one input of type anyrange or anymultirange.
diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out
index 2efd7d7ec7..c02443ee40 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 
@@ -335,7 +346,8 @@ WHERE p1.prorettype IN
      '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     
 ------+----------------
@@ -350,20 +362,25 @@ ORDER BY 2;
  3532 | enum_recv
 (9 rows)
 
--- anyrange is tighter than the rest, can only resolve from anyrange input
+-- anyrange and anymultirange are tighter than the rest, can only resolve
+-- from each other
 SELECT p1.oid, p1.proname
 FROM pg_proc as p1
-WHERE p1.prorettype = 'anyrange'::regtype
+WHERE p1.prorettype IN ('anyrange'::regtype, 'anymultirange'::regtype)
   AND NOT
-     'anyrange'::regtype = ANY (p1.proargtypes)
+    ('anyrange'::regtype = ANY (p1.proargtypes) OR
+      'anymultirange'::regtype = ANY (p1.proargtypes))
 ORDER BY 2;
  oid  |     proname      
 ------+------------------
+ 8002 | anymultirange_in
  3832 | anyrange_in
+ 8004 | multirange_in
+ 8006 | multirange_recv
  3876 | range_gist_union
  3834 | range_in
  3836 | range_recv
-(4 rows)
+(7 rows)
 
 -- similarly for the anycompatible family
 SELECT p1.oid, p1.proname
@@ -2181,13 +2198,14 @@ ORDER BY 1, 2, 3;
                     | float_ops        | float4_ops       | real
                     | float_ops        | float8_ops       | double precision
                     | jsonb_ops        | jsonb_ops        | jsonb
+                    | multirange_ops   | multirange_ops   | anymultirange
                     | numeric_ops      | numeric_ops      | numeric
                     | range_ops        | range_ops        | anyrange
                     | record_image_ops | record_image_ops | record
                     | record_ops       | record_ops       | record
                     | tsquery_ops      | tsquery_ops      | tsquery
                     | tsvector_ops     | tsvector_ops     | tsvector
-(14 rows)
+(15 rows)
 
 -- **************** pg_index ****************
 -- Look for illegal values in pg_index fields.
diff --git a/src/test/regress/expected/plpgsql.out b/src/test/regress/expected/plpgsql.out
index d0a6b630b8..65827c03e6 100644
--- a/src/test/regress/expected/plpgsql.out
+++ b/src/test/regress/expected/plpgsql.out
@@ -1811,7 +1811,7 @@ begin
   return array[x + 1, x + 2];
 end$$ language plpgsql;
 ERROR:  cannot determine result data type
-DETAIL:  A result of type anyrange requires at least one input of type anyrange.
+DETAIL:  A result of type anyrange requires at least one input of type anyrange or anymultirange.
 create function f1(x anyrange) returns anyarray as $$
 begin
   return array[lower(x), upper(x)];
diff --git a/src/test/regress/expected/polymorphism.out b/src/test/regress/expected/polymorphism.out
index 1ff40764d9..3a42b30730 100644
--- a/src/test/regress/expected/polymorphism.out
+++ b/src/test/regress/expected/polymorphism.out
@@ -61,7 +61,7 @@ create function polyf(x anyelement) returns anyrange as $$
   select array[x + 1, x + 2]
 $$ language sql;
 ERROR:  cannot determine result data type
-DETAIL:  A result of type anyrange requires at least one input of type anyrange.
+DETAIL:  A result of type anyrange requires at least one input of type anyrange or anymultirange.
 create function polyf(x anyrange) returns anyarray as $$
   select array[lower(x), upper(x)]
 $$ language sql;
@@ -231,7 +231,7 @@ CREATE AGGREGATE myaggp01a(*) (SFUNC = stfnp, STYPE = int4[],
 CREATE AGGREGATE myaggp02a(*) (SFUNC = stfnp, STYPE = anyarray,
   FINALFUNC = ffp, INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 --     N    P
 -- should CREATE
 CREATE AGGREGATE myaggp03a(*) (SFUNC = stfp, STYPE = int4[],
@@ -243,11 +243,11 @@ CREATE AGGREGATE myaggp03b(*) (SFUNC = stfp, STYPE = int4[],
 CREATE AGGREGATE myaggp04a(*) (SFUNC = stfp, STYPE = anyarray,
   FINALFUNC = ffp, INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 CREATE AGGREGATE myaggp04b(*) (SFUNC = stfp, STYPE = anyarray,
   INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 --    Case2 (R = P) && ((B = P) || (B = N))
 --    -------------------------------------
 --    S    tf1      B    tf2
@@ -302,13 +302,13 @@ ERROR:  function tfp(integer[], anyelement) does not exist
 CREATE AGGREGATE myaggp13a(BASETYPE = int, SFUNC = tfnp, STYPE = anyarray,
   FINALFUNC = ffp, INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 --    P    N        N    P
 -- should ERROR: tf2p(anyarray, int) not matched by tf2p(int[],anyelement)
 CREATE AGGREGATE myaggp14a(BASETYPE = int, SFUNC = tf2p, STYPE = anyarray,
   FINALFUNC = ffp, INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 --    P    N        P    N
 -- should ERROR: tfnp(anyarray, anyelement) not matched by tfnp(int[],int)
 CREATE AGGREGATE myaggp15a(BASETYPE = anyelement, SFUNC = tfnp,
@@ -324,21 +324,21 @@ ERROR:  function tf2p(anyarray, anyelement) does not exist
 CREATE AGGREGATE myaggp17a(BASETYPE = int, SFUNC = tf1p, STYPE = anyarray,
   FINALFUNC = ffp, INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 CREATE AGGREGATE myaggp17b(BASETYPE = int, SFUNC = tf1p, STYPE = anyarray,
   INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 --    P    P        N    P
 -- should ERROR: tfp(anyarray, int) not matched by tfp(anyarray, anyelement)
 CREATE AGGREGATE myaggp18a(BASETYPE = int, SFUNC = tfp, STYPE = anyarray,
   FINALFUNC = ffp, INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 CREATE AGGREGATE myaggp18b(BASETYPE = int, SFUNC = tfp, STYPE = anyarray,
   INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 --    P    P        P    N
 -- should ERROR: tf1p(anyarray, anyelement) not matched by tf1p(anyarray, int)
 CREATE AGGREGATE myaggp19a(BASETYPE = anyelement, SFUNC = tf1p,
@@ -368,11 +368,11 @@ CREATE AGGREGATE myaggn01b(*) (SFUNC = stfnp, STYPE = int4[],
 CREATE AGGREGATE myaggn02a(*) (SFUNC = stfnp, STYPE = anyarray,
   FINALFUNC = ffnp, INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 CREATE AGGREGATE myaggn02b(*) (SFUNC = stfnp, STYPE = anyarray,
   INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 --     N    P
 -- should CREATE
 CREATE AGGREGATE myaggn03a(*) (SFUNC = stfp, STYPE = int4[],
@@ -382,7 +382,7 @@ CREATE AGGREGATE myaggn03a(*) (SFUNC = stfp, STYPE = int4[],
 CREATE AGGREGATE myaggn04a(*) (SFUNC = stfp, STYPE = anyarray,
   FINALFUNC = ffnp, INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 --    Case4 (R = N) && ((B = P) || (B = N))
 --    -------------------------------------
 --    S    tf1      B    tf2
@@ -436,21 +436,21 @@ ERROR:  function tfp(integer[], anyelement) does not exist
 CREATE AGGREGATE myaggn13a(BASETYPE = int, SFUNC = tfnp, STYPE = anyarray,
   FINALFUNC = ffnp, INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 CREATE AGGREGATE myaggn13b(BASETYPE = int, SFUNC = tfnp, STYPE = anyarray,
   INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 --    P    N        N    P
 -- should ERROR: tf2p(anyarray, int) not matched by tf2p(int[],anyelement)
 CREATE AGGREGATE myaggn14a(BASETYPE = int, SFUNC = tf2p, STYPE = anyarray,
   FINALFUNC = ffnp, INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 CREATE AGGREGATE myaggn14b(BASETYPE = int, SFUNC = tf2p, STYPE = anyarray,
   INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 --    P    N        P    N
 -- should ERROR: tfnp(anyarray, anyelement) not matched by tfnp(int[],int)
 CREATE AGGREGATE myaggn15a(BASETYPE = anyelement, SFUNC = tfnp,
@@ -472,13 +472,13 @@ ERROR:  function tf2p(anyarray, anyelement) does not exist
 CREATE AGGREGATE myaggn17a(BASETYPE = int, SFUNC = tf1p, STYPE = anyarray,
   FINALFUNC = ffnp, INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 --    P    P        N    P
 -- should ERROR: tfp(anyarray, int) not matched by tfp(anyarray, anyelement)
 CREATE AGGREGATE myaggn18a(BASETYPE = int, SFUNC = tfp, STYPE = anyarray,
   FINALFUNC = ffnp, INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 --    P    P        P    N
 -- should ERROR: tf1p(anyarray, anyelement) not matched by tf1p(anyarray, int)
 CREATE AGGREGATE myaggn19a(BASETYPE = anyelement, SFUNC = tf1p,
diff --git a/src/test/regress/expected/rangefuncs.out b/src/test/regress/expected/rangefuncs.out
index 7eced28452..71e2769f44 100644
--- a/src/test/regress/expected/rangefuncs.out
+++ b/src/test/regress/expected/rangefuncs.out
@@ -1556,7 +1556,7 @@ DROP FUNCTION dup(anyelement);
 CREATE FUNCTION bad (f1 int, out f2 anyelement, out f3 anyarray)
 AS 'select $1, array[$1,$1]' LANGUAGE sql;
 ERROR:  cannot determine result data type
-DETAIL:  A result of type anyelement requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyelement requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 CREATE FUNCTION dup (f1 anycompatible, f2 anycompatiblearray, f3 out anycompatible, f4 out anycompatiblearray)
 AS 'select $1, $2' LANGUAGE sql;
 SELECT dup(22, array[44]);
diff --git a/src/test/regress/expected/rangetypes.out b/src/test/regress/expected/rangetypes.out
index c421f5394f..763e3819a5 100644
--- a/src/test/regress/expected/rangetypes.out
+++ b/src/test/regress/expected/rangetypes.out
@@ -518,6 +518,24 @@ select numrange(1.0, 2.0) * numrange(2.5, 3.0);
  empty
 (1 row)
 
+select range_intersect_agg(nr) from numrange_test;
+ range_intersect_agg 
+---------------------
+ empty
+(1 row)
+
+select range_intersect_agg(nr) from numrange_test where false;
+ range_intersect_agg 
+---------------------
+ 
+(1 row)
+
+select range_intersect_agg(nr) from numrange_test where nr @> 4.0;
+ range_intersect_agg 
+---------------------
+ [3,5)
+(1 row)
+
 create table numrange_test2(nr numrange);
 create index numrange_test2_hash_idx on numrange_test2 using hash (nr);
 INSERT INTO numrange_test2 VALUES('[, 5)');
@@ -1371,12 +1389,12 @@ drop function anyarray_anyrange_func(anyarray, anyrange);
 create function bogus_func(anyelement)
   returns anyrange as 'select int4range(1,10)' language sql;
 ERROR:  cannot determine result data type
-DETAIL:  A result of type anyrange requires at least one input of type anyrange.
+DETAIL:  A result of type anyrange requires at least one input of type anyrange or anymultirange.
 -- should fail
 create function bogus_func(int)
   returns anyrange as 'select int4range(1,10)' language sql;
 ERROR:  cannot determine result data type
-DETAIL:  A result of type anyrange requires at least one input of type anyrange.
+DETAIL:  A result of type anyrange requires at least one input of type anyrange or anymultirange.
 create function range_add_bounds(anyrange)
   returns anyelement as 'select lower($1) + upper($1)' language sql;
 select range_add_bounds(int4range(1, 17));
@@ -1508,6 +1526,7 @@ reset enable_sort;
 --
 -- OUT/INOUT/TABLE functions
 --
+-- infer anyrange from anyrange
 create function outparam_succeed(i anyrange, out r anyrange, out t text)
   as $$ select $1, 'foo'::text $$ language sql;
 select * from outparam_succeed(int4range(1,2));
@@ -1525,6 +1544,16 @@ select * from outparam2_succeed(int4range(1,11));
  {1,11} | {11,1}
 (1 row)
 
+-- infer anyarray from anyrange
+create function outparam_succeed2(i anyrange, out r anyarray, out t text)
+  as $$ select ARRAY[upper($1)], 'foo'::text $$ language sql;
+select * from outparam_succeed2(int4range(int4range(1,2)));
+  r  |  t  
+-----+-----
+ {2} | foo
+(1 row)
+
+-- infer anyelement from anyrange
 create function inoutparam_succeed(out i anyelement, inout r anyrange)
   as $$ select upper($1), $1 $$ language sql;
 select * from inoutparam_succeed(int4range(1,2));
@@ -1547,14 +1576,14 @@ select * from table_succeed(int4range(1,11));
 create function outparam_fail(i anyelement, out r anyrange, out t text)
   as $$ select '[1,10]', 'foo' $$ language sql;
 ERROR:  cannot determine result data type
-DETAIL:  A result of type anyrange requires at least one input of type anyrange.
+DETAIL:  A result of type anyrange requires at least one input of type anyrange or anymultirange.
 --should fail
 create function inoutparam_fail(inout i anyelement, out r anyrange)
   as $$ select $1, '[1,10]' $$ language sql;
 ERROR:  cannot determine result data type
-DETAIL:  A result of type anyrange requires at least one input of type anyrange.
+DETAIL:  A result of type anyrange requires at least one input of type anyrange or anymultirange.
 --should fail
 create function table_fail(i anyelement) returns table(i anyelement, r anyrange)
   as $$ select $1, '[1,10]' $$ language sql;
 ERROR:  cannot determine result data type
-DETAIL:  A result of type anyrange requires at least one input of type anyrange.
+DETAIL:  A result of type anyrange requires at least one input of type anyrange or anymultirange.
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index 192445878d..d9ce961be2 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -92,6 +92,7 @@ num_exp_sqrt|t
 num_exp_sub|t
 num_input_test|f
 num_result|f
+nummultirange_test|t
 numrange_test|t
 onek|t
 onek2|t
@@ -172,6 +173,7 @@ quad_poly_tbl|t
 radix_text_tbl|t
 ramp|f
 real_city|f
+reservations|f
 road|t
 shighway|t
 slow_emp4000|f
diff --git a/src/test/regress/expected/type_sanity.out b/src/test/regress/expected/type_sanity.out
index 274130e706..8df1ae47e4 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'));
@@ -195,18 +195,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
@@ -240,17 +241,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
@@ -318,18 +320,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
@@ -363,17 +366,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
@@ -631,3 +635,11 @@ WHERE pronargs != 2
 ----------+------------+---------
 (0 rows)
 
+-- every range should have a valid multirange
+SELECT p1.rngtypid, p1.rngsubtype, p1.rngmultitypid
+FROM pg_range p1
+WHERE p1.rngmultitypid IS NULL OR p1.rngmultitypid = 0;
+ rngtypid | rngsubtype | rngmultitypid 
+----------+------------+---------------
+(0 rows)
+
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index a98dba7b2f..18c25f4d8b 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 3f66e0b859..dca9f91afb 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..0f70919e1a
--- /dev/null
+++ b/src/test/regress/sql/multirangetypes.sql
@@ -0,0 +1,630 @@
+-- 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'));
+
+--
+-- test casts, both a built-in range type and a user-defined one:
+--
+select 'empty'::int4range::int4multirange;
+select int4range(1, 3)::int4multirange;
+select int4range(1, null)::int4multirange;
+select int4range(null, null)::int4multirange;
+select 'empty'::textrange::textmultirange;
+select textrange('a', 'c')::textmultirange;
+select textrange('a', null)::textmultirange;
+select textrange(null, null)::textmultirange;
+
+--
+-- create some test data and test the operators
+--
+
+CREATE TABLE nummultirange_test (nmr NUMMULTIRANGE);
+CREATE INDEX nummultirange_test_btree ON nummultirange_test(nmr);
+
+INSERT INTO nummultirange_test VALUES('{}');
+INSERT INTO nummultirange_test VALUES('{[,)}');
+INSERT INTO nummultirange_test VALUES('{[3,]}');
+INSERT INTO nummultirange_test VALUES('{[,), [3,]}');
+INSERT INTO nummultirange_test VALUES('{[, 5)}');
+INSERT INTO nummultirange_test VALUES(nummultirange());
+INSERT INTO nummultirange_test VALUES(nummultirange(variadic '{}'::numrange[]));
+INSERT INTO nummultirange_test VALUES(nummultirange(numrange(1.1, 2.2)));
+INSERT INTO nummultirange_test VALUES('{empty}');
+INSERT INTO nummultirange_test VALUES(nummultirange(numrange(1.7, 1.7, '[]'), numrange(1.7, 1.9)));
+INSERT INTO nummultirange_test VALUES(nummultirange(numrange(1.7, 1.7, '[]'), numrange(1.9, 2.1)));
+
+SELECT nmr, isempty(nmr), lower(nmr), upper(nmr) FROM nummultirange_test ORDER BY nmr;
+SELECT nmr, lower_inc(nmr), lower_inf(nmr), upper_inc(nmr), upper_inf(nmr) FROM nummultirange_test ORDER BY nmr;
+
+SELECT * FROM nummultirange_test WHERE nmr = '{}';
+SELECT * FROM nummultirange_test WHERE nmr = '{(,5)}';
+SELECT * FROM nummultirange_test WHERE nmr = '{[3,)}';
+SELECT * FROM nummultirange_test WHERE nmr = '{[1.7,1.7]}';
+SELECT * FROM nummultirange_test WHERE nmr = '{[1.7,1.7],[1.9,2.1)}';
+SELECT * FROM nummultirange_test WHERE nmr < '{}';
+SELECT * FROM nummultirange_test WHERE nmr < '{[-1000.0, -1000.0]}';
+SELECT * FROM nummultirange_test WHERE nmr < '{[0.0, 1.0]}';
+SELECT * FROM nummultirange_test WHERE nmr < '{[1000.0, 1001.0]}';
+SELECT * FROM nummultirange_test WHERE nmr <= '{}';
+SELECT * FROM nummultirange_test WHERE nmr <= '{[3,)}';
+SELECT * FROM nummultirange_test WHERE nmr >= '{}';
+SELECT * FROM nummultirange_test WHERE nmr >= '{[3,)}';
+SELECT * FROM nummultirange_test WHERE nmr > '{}';
+SELECT * FROM nummultirange_test WHERE nmr > '{[-1000.0, -1000.0]}';
+SELECT * FROM nummultirange_test WHERE nmr > '{[0.0, 1.0]}';
+SELECT * FROM nummultirange_test WHERE nmr > '{[1000.0, 1001.0]}';
+SELECT * FROM nummultirange_test WHERE nmr <> '{}';
+SELECT * FROM nummultirange_test WHERE nmr <> '{(,5)}';
+
+select nummultirange(numrange(2.0, 1.0));
+select nummultirange(numrange(5.0, 6.0), numrange(1.0, 2.0));
+
+-- overlaps
+SELECT * FROM nummultirange_test WHERE range_overlaps_multirange(numrange(4.0, 4.2), nmr);
+SELECT * FROM nummultirange_test WHERE numrange(4.0, 4.2) && nmr;
+SELECT * FROM nummultirange_test WHERE multirange_overlaps_range(nmr, numrange(4.0, 4.2));
+SELECT * FROM nummultirange_test WHERE nmr && numrange(4.0, 4.2);
+SELECT * FROM nummultirange_test WHERE multirange_overlaps_multirange(nmr, nummultirange(numrange(4.0, 4.2), numrange(6.0, 7.0)));
+SELECT * FROM nummultirange_test WHERE nmr && nummultirange(numrange(4.0, 4.2), numrange(6.0, 7.0));
+SELECT * FROM nummultirange_test WHERE nmr && nummultirange(numrange(6.0, 7.0));
+SELECT * FROM nummultirange_test WHERE nmr && nummultirange(numrange(6.0, 7.0), numrange(8.0, 9.0));
+
+-- mr contains x
+SELECT * FROM nummultirange_test WHERE multirange_contains_elem(nmr, 4.0);
+SELECT * FROM nummultirange_test WHERE nmr @> 4.0;
+SELECT * FROM nummultirange_test WHERE multirange_contains_range(nmr, numrange(4.0, 4.2));
+SELECT * FROM nummultirange_test WHERE nmr @> numrange(4.0, 4.2);
+SELECT * FROM nummultirange_test WHERE multirange_contains_multirange(nmr, '{[4.0,4.2), [6.0, 8.0)}');
+SELECT * FROM nummultirange_test WHERE nmr @> '{[4.0,4.2), [6.0, 8.0)}'::nummultirange;
+
+-- x is contained by mr
+SELECT * FROM nummultirange_test WHERE elem_contained_by_multirange(4.0, nmr);
+SELECT * FROM nummultirange_test WHERE 4.0 <@ nmr;
+SELECT * FROM nummultirange_test WHERE range_contained_by_multirange(numrange(4.0, 4.2), nmr);
+SELECT * FROM nummultirange_test WHERE numrange(4.0, 4.2) <@ nmr;
+SELECT * FROM nummultirange_test WHERE multirange_contained_by_multirange('{[4.0,4.2), [6.0, 8.0)}', nmr);
+SELECT * FROM nummultirange_test WHERE '{[4.0,4.2), [6.0, 8.0)}'::nummultirange <@ nmr;
+
+-- overlaps
+SELECT 'empty'::numrange && nummultirange();
+SELECT 'empty'::numrange && nummultirange(numrange(1,2));
+SELECT nummultirange() && 'empty'::numrange;
+SELECT nummultirange(numrange(1,2)) && 'empty'::numrange;
+SELECT nummultirange() && nummultirange();
+SELECT nummultirange() && nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) && nummultirange();
+SELECT nummultirange(numrange(3,4)) && nummultirange(numrange(1,2), numrange(7,8));
+SELECT nummultirange(numrange(1,2), numrange(7,8)) && nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(3,4)) && nummultirange(numrange(1,2), numrange(3.5,8));
+SELECT nummultirange(numrange(1,2), numrange(3.5,8)) && numrange(3,4);
+SELECT nummultirange(numrange(1,2), numrange(3.5,8)) && nummultirange(numrange(3,4));
+
+-- contains
+SELECT nummultirange() @> nummultirange();
+SELECT nummultirange() @> 'empty'::numrange;
+SELECT nummultirange(numrange(null,null)) @> numrange(1,2);
+SELECT nummultirange(numrange(null,null)) @> numrange(null,2);
+SELECT nummultirange(numrange(null,null)) @> numrange(2,null);
+SELECT nummultirange(numrange(null,5)) @> numrange(null,3);
+SELECT nummultirange(numrange(null,5)) @> numrange(null,8);
+SELECT nummultirange(numrange(5,null)) @> numrange(8,null);
+SELECT nummultirange(numrange(5,null)) @> numrange(3,null);
+SELECT nummultirange(numrange(1,5)) @> numrange(8,9);
+SELECT nummultirange(numrange(1,5)) @> numrange(3,9);
+SELECT nummultirange(numrange(1,5)) @> numrange(1,4);
+SELECT nummultirange(numrange(1,5)) @> numrange(1,5);
+SELECT nummultirange(numrange(-4,-2), numrange(1,5)) @> numrange(1,5);
+SELECT nummultirange(numrange(1,5), numrange(8,9)) @> numrange(1,5);
+SELECT nummultirange(numrange(1,5), numrange(8,9)) @> numrange(6,7);
+SELECT nummultirange(numrange(1,5), numrange(6,9)) @> numrange(6,7);
+SELECT '{[1,5)}'::nummultirange @> '{[1,5)}';
+SELECT '{[-4,-2), [1,5)}'::nummultirange @> '{[1,5)}';
+SELECT '{[1,5), [8,9)}'::nummultirange @> '{[1,5)}';
+SELECT '{[1,5), [8,9)}'::nummultirange @> '{[6,7)}';
+SELECT '{[1,5), [6,9)}'::nummultirange @> '{[6,7)}';
+
+-- is contained by
+SELECT nummultirange() <@ nummultirange();
+SELECT 'empty'::numrange <@ nummultirange();
+SELECT numrange(1,2) <@ nummultirange(numrange(null,null));
+SELECT numrange(null,2) <@ nummultirange(numrange(null,null));
+SELECT numrange(2,null) <@ nummultirange(numrange(null,null));
+SELECT numrange(null,3) <@ nummultirange(numrange(null,5));
+SELECT numrange(null,8) <@ nummultirange(numrange(null,5));
+SELECT numrange(8,null) <@ nummultirange(numrange(5,null));
+SELECT numrange(3,null) <@ nummultirange(numrange(5,null));
+SELECT numrange(8,9) <@ nummultirange(numrange(1,5));
+SELECT numrange(3,9) <@ nummultirange(numrange(1,5));
+SELECT numrange(1,4) <@ nummultirange(numrange(1,5));
+SELECT numrange(1,5) <@ nummultirange(numrange(1,5));
+SELECT numrange(1,5) <@ nummultirange(numrange(-4,-2), numrange(1,5));
+SELECT numrange(1,5) <@ nummultirange(numrange(1,5), numrange(8,9));
+SELECT numrange(6,7) <@ nummultirange(numrange(1,5), numrange(8,9));
+SELECT numrange(6,7) <@ nummultirange(numrange(1,5), numrange(6,9));
+SELECT '{[1,5)}' <@ '{[1,5)}'::nummultirange;
+SELECT '{[1,5)}' <@ '{[-4,-2), [1,5)}'::nummultirange;
+SELECT '{[1,5)}' <@ '{[1,5), [8,9)}'::nummultirange;
+SELECT '{[6,7)}' <@ '{[1,5), [8,9)}'::nummultirange;
+SELECT '{[6,7)}' <@ '{[1,5), [6,9)}'::nummultirange;
+
+-- overleft
+SELECT 'empty'::numrange &< nummultirange();
+SELECT 'empty'::numrange &< nummultirange(numrange(1,2));
+SELECT nummultirange() &< 'empty'::numrange;
+SELECT nummultirange(numrange(1,2)) &< 'empty'::numrange;
+SELECT nummultirange() &< nummultirange();
+SELECT nummultirange(numrange(1,2)) &< nummultirange();
+SELECT nummultirange() &< nummultirange(numrange(1,2));
+SELECT numrange(6,7) &< nummultirange(numrange(3,4));
+SELECT numrange(1,2) &< nummultirange(numrange(3,4));
+SELECT numrange(1,4) &< nummultirange(numrange(3,4));
+SELECT numrange(1,6) &< nummultirange(numrange(3,4));
+SELECT numrange(3.5,6) &< nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(6,7)) &< numrange(3,4);
+SELECT nummultirange(numrange(1,2)) &< numrange(3,4);
+SELECT nummultirange(numrange(1,4)) &< numrange(3,4);
+SELECT nummultirange(numrange(1,6)) &< numrange(3,4);
+SELECT nummultirange(numrange(3.5,6)) &< numrange(3,4);
+SELECT nummultirange(numrange(6,7)) &< nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,2)) &< nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,4)) &< nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,6)) &< nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(3.5,6)) &< nummultirange(numrange(3,4));
+
+-- overright
+SELECT nummultirange() &> 'empty'::numrange;
+SELECT nummultirange(numrange(1,2)) &> 'empty'::numrange;
+SELECT 'empty'::numrange &> nummultirange();
+SELECT 'empty'::numrange &> nummultirange(numrange(1,2));
+SELECT nummultirange() &> nummultirange();
+SELECT nummultirange() &> nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) &> nummultirange();
+SELECT nummultirange(numrange(3,4)) &> numrange(6,7);
+SELECT nummultirange(numrange(3,4)) &> numrange(1,2);
+SELECT nummultirange(numrange(3,4)) &> numrange(1,4);
+SELECT nummultirange(numrange(3,4)) &> numrange(1,6);
+SELECT nummultirange(numrange(3,4)) &> numrange(3.5,6);
+SELECT numrange(3,4) &> nummultirange(numrange(6,7));
+SELECT numrange(3,4) &> nummultirange(numrange(1,2));
+SELECT numrange(3,4) &> nummultirange(numrange(1,4));
+SELECT numrange(3,4) &> nummultirange(numrange(1,6));
+SELECT numrange(3,4) &> nummultirange(numrange(3.5,6));
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(6,7));
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(1,4));
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(1,6));
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(3.5,6));
+
+-- meets
+SELECT 'empty'::numrange -|- nummultirange();
+SELECT 'empty'::numrange -|- nummultirange(numrange(1,2));
+SELECT nummultirange() -|- 'empty'::numrange;
+SELECT nummultirange(numrange(1,2)) -|- 'empty'::numrange;
+SELECT nummultirange() -|- nummultirange();
+SELECT nummultirange(numrange(1,2)) -|- nummultirange();
+SELECT nummultirange() -|- nummultirange(numrange(1,2));
+SELECT numrange(1,2) -|- nummultirange(numrange(2,4));
+SELECT numrange(1,2) -|- nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,2)) -|- numrange(2,4);
+SELECT nummultirange(numrange(1,2)) -|- numrange(3,4);
+SELECT nummultirange(numrange(1,2)) -|- nummultirange(numrange(2,4));
+SELECT nummultirange(numrange(1,2)) -|- nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,2), numrange(5,6)) -|- nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,2), numrange(5,6)) -|- nummultirange(numrange(6,7));
+SELECT nummultirange(numrange(1,2), numrange(5,6)) -|- nummultirange(numrange(8,9));
+SELECT nummultirange(numrange(1,2)) -|- nummultirange(numrange(2,4), numrange(6,7));
+
+-- strictly left
+select 'empty'::numrange << nummultirange();
+select numrange(1,2) << nummultirange();
+select numrange(1,2) << nummultirange(numrange(3,4));
+select numrange(1,2) << nummultirange(numrange(0,4));
+select numrange(1,2) << nummultirange(numrange(0,4), numrange(7,8));
+select nummultirange() << 'empty'::numrange;
+select nummultirange() << numrange(1,2);
+select nummultirange(numrange(3,4)) << numrange(3,6);
+select nummultirange(numrange(0,2)) << numrange(3,6);
+select nummultirange(numrange(0,2), numrange(7,8)) << numrange(3,6);
+select nummultirange(numrange(-4,-2), numrange(0,2)) << numrange(3,6);
+select nummultirange() << nummultirange();
+select nummultirange() << nummultirange(numrange(1,2));
+select nummultirange(numrange(1,2)) << nummultirange();
+select nummultirange(numrange(1,2)) << nummultirange(numrange(1,2));
+select nummultirange(numrange(1,2)) << nummultirange(numrange(3,4));
+select nummultirange(numrange(1,2)) << nummultirange(numrange(3,4), numrange(7,8));
+select nummultirange(numrange(1,2), numrange(4,5)) << nummultirange(numrange(3,4), numrange(7,8));
+
+-- strictly right
+select nummultirange() >> 'empty'::numrange;
+select nummultirange() >> numrange(1,2);
+select nummultirange(numrange(3,4)) >> numrange(1,2);
+select nummultirange(numrange(0,4)) >> numrange(1,2);
+select nummultirange(numrange(0,4), numrange(7,8)) >> numrange(1,2);
+select 'empty'::numrange >> nummultirange();
+select numrange(1,2) >> nummultirange();
+select numrange(3,6) >> nummultirange(numrange(3,4));
+select numrange(3,6) >> nummultirange(numrange(0,2));
+select numrange(3,6) >> nummultirange(numrange(0,2), numrange(7,8));
+select numrange(3,6) >> nummultirange(numrange(-4,-2), numrange(0,2));
+select nummultirange() >> nummultirange();
+select nummultirange(numrange(1,2)) >> nummultirange();
+select nummultirange() >> nummultirange(numrange(1,2));
+select nummultirange(numrange(1,2)) >> nummultirange(numrange(1,2));
+select nummultirange(numrange(3,4)) >> nummultirange(numrange(1,2));
+select nummultirange(numrange(3,4), numrange(7,8)) >> nummultirange(numrange(1,2));
+select nummultirange(numrange(3,4), numrange(7,8)) >> nummultirange(numrange(1,2), numrange(4,5));
+
+-- union
+SELECT nummultirange() + nummultirange();
+SELECT nummultirange() + nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) + nummultirange();
+SELECT nummultirange(numrange(1,2)) + nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) + nummultirange(numrange(2,4));
+SELECT nummultirange(numrange(1,2)) + nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) + nummultirange(numrange(2,4));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) + nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) + nummultirange(numrange(0,9));
+
+-- merge
+SELECT range_merge(nummultirange());
+SELECT range_merge(nummultirange(numrange(1,2)));
+SELECT range_merge(nummultirange(numrange(1,2), numrange(7,8)));
+
+-- minus
+SELECT nummultirange() - nummultirange();
+SELECT nummultirange() - nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) - nummultirange();
+SELECT nummultirange(numrange(1,2), numrange(3,4)) - nummultirange();
+SELECT nummultirange(numrange(1,2)) - nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) - nummultirange(numrange(2,4));
+SELECT nummultirange(numrange(1,2)) - nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(2,3));
+SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(0,8));
+SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(0,2));
+SELECT nummultirange(numrange(1,8)) - nummultirange(numrange(0,2), numrange(3,4));
+SELECT nummultirange(numrange(1,8)) - nummultirange(numrange(2,3), numrange(5,null));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(-2,0));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(2,4));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(3,5));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(0,9));
+SELECT nummultirange(numrange(1,3), numrange(4,5)) - nummultirange(numrange(2,9));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(8,9));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(-2,0), numrange(8,9));
+
+-- intersection
+SELECT nummultirange() * nummultirange();
+SELECT nummultirange() * nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) * nummultirange();
+SELECT '{[1,3)}'::nummultirange * '{[1,5)}'::nummultirange;
+SELECT '{[1,3)}'::nummultirange * '{[0,5)}'::nummultirange;
+SELECT '{[1,3)}'::nummultirange * '{[0,2)}'::nummultirange;
+SELECT '{[1,3)}'::nummultirange * '{[2,5)}'::nummultirange;
+SELECT '{[1,4)}'::nummultirange * '{[2,3)}'::nummultirange;
+SELECT '{[1,4)}'::nummultirange * '{[0,2), [3,5)}'::nummultirange;
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[0,8), [9,12)}'::nummultirange;
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[9,12)}'::nummultirange;
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[-5,-4), [5,6), [9,12)}'::nummultirange;
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[0,2), [3,8), [9,12)}'::nummultirange;
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[0,2), [3,8), [9,12)}'::nummultirange;
+
+--
+-- range_agg function
+--
+create table reservations ( room_id integer not null, booked_during daterange );
+insert into reservations values
+-- 1: has a meets and a gap
+(1, daterange('2018-07-01', '2018-07-07')),
+(1, daterange('2018-07-07', '2018-07-14')),
+(1, daterange('2018-07-20', '2018-07-22')),
+-- 2: just a single row
+(2, daterange('2018-07-01', '2018-07-03')),
+-- 3: one null range
+(3, NULL),
+-- 4: two null ranges
+(4, NULL),
+(4, NULL),
+-- 5: a null range and a non-null range
+(5, NULL),
+(5, daterange('2018-07-01', '2018-07-03')),
+-- 6: has overlap
+(6, daterange('2018-07-01', '2018-07-07')),
+(6, daterange('2018-07-05', '2018-07-10')),
+-- 7: two ranges that meet: no gap or overlap
+(7, daterange('2018-07-01', '2018-07-07')),
+(7, daterange('2018-07-07', '2018-07-14')),
+-- 8: an empty range
+(8, 'empty'::daterange)
+;
+SELECT   room_id, range_agg(booked_during)
+FROM     reservations
+GROUP BY room_id
+ORDER BY room_id;
+
+-- range_agg on a custom range type too
+SELECT  range_agg(r)
+FROM    (VALUES
+          ('[a,c]'::textrange),
+          ('[b,b]'::textrange),
+          ('[c,f]'::textrange),
+          ('[g,h)'::textrange),
+          ('[h,j)'::textrange)
+        ) t(r);
+
+select range_intersect_agg(nmr) from nummultirange_test;
+select range_intersect_agg(nmr) from nummultirange_test where false;
+-- test with just one input:
+select range_intersect_agg(nmr) from (values ('{[1,2]}'::nummultirange)) t(nmr);
+select range_intersect_agg(nmr) from nummultirange_test where nmr @> 4.0;
+
+create table nummultirange_test2(nmr nummultirange);
+create index nummultirange_test2_hash_idx on nummultirange_test2 using hash (nmr);
+
+INSERT INTO nummultirange_test2 VALUES('{[, 5)}');
+INSERT INTO nummultirange_test2 VALUES(nummultirange(numrange(1.1, 2.2)));
+INSERT INTO nummultirange_test2 VALUES(nummultirange(numrange(1.1, 2.2)));
+INSERT INTO nummultirange_test2 VALUES(nummultirange(numrange(1.1, 2.2,'()')));
+INSERT INTO nummultirange_test2 VALUES('{}');
+
+select * from nummultirange_test2 where nmr = '{}';
+select * from nummultirange_test2 where nmr = nummultirange(numrange(1.1, 2.2));
+select * from nummultirange_test2 where nmr = nummultirange(numrange(1.1, 2.3));
+
+set enable_nestloop=t;
+set enable_hashjoin=f;
+set enable_mergejoin=f;
+select * from nummultirange_test natural join nummultirange_test2 order by nmr;
+set enable_nestloop=f;
+set enable_hashjoin=t;
+set enable_mergejoin=f;
+select * from nummultirange_test natural join nummultirange_test2 order by nmr;
+set enable_nestloop=f;
+set enable_hashjoin=f;
+set enable_mergejoin=t;
+select * from nummultirange_test natural join nummultirange_test2 order by nmr;
+
+set enable_nestloop to default;
+set enable_hashjoin to default;
+set enable_mergejoin to default;
+
+DROP TABLE nummultirange_test2;
+
+--
+-- Test user-defined multirange of floats
+--
+
+select '{[123.001, 5.e9)}'::float8multirange @> 888.882::float8;
+create table float8multirange_test(f8mr float8multirange, i int);
+insert into float8multirange_test values(float8multirange(float8range(-100.00007, '1.111113e9')), 42);
+select * from float8multirange_test;
+drop table float8multirange_test;
+
+--
+-- Test multirange types over domains
+--
+
+create domain mydomain as int4;
+create type mydomainrange as range(subtype=mydomain);
+select '{[4,50)}'::mydomainmultirange @> 7::mydomain;
+drop domain mydomain cascade;
+
+--
+-- Test domains over multirange types
+--
+
+create domain restrictedmultirange as int4multirange check (upper(value) < 10);
+select '{[4,5)}'::restrictedmultirange @> 7;
+select '{[4,50)}'::restrictedmultirange @> 7; -- should fail
+drop domain restrictedmultirange;
+
+--
+-- Test multiple multirange types over the same subtype
+--
+
+create type textrange1 as range(subtype=text, collation="C");
+create type textrange2 as range(subtype=text, collation="C");
+
+select textmultirange1(textrange2('a','Z'));  -- should fail
+select textmultirange1(textrange1('a','Z')) @> 'b'::text;
+select textmultirange2(textrange2('a','z')) @> 'b'::text;
+
+drop type textrange1;
+drop type textrange2;
+
+--
+-- Test polymorphic type system
+--
+
+create function anyarray_anymultirange_func(a anyarray, r anymultirange)
+  returns anyelement as 'select $1[1] + lower($2);' language sql;
+
+select anyarray_anymultirange_func(ARRAY[1,2], int4multirange(int4range(10,20)));
+
+-- should fail
+select anyarray_anymultirange_func(ARRAY[1,2], nummultirange(numrange(10,20)));
+
+drop function anyarray_anymultirange_func(anyarray, anymultirange);
+
+-- should fail
+create function bogus_func(anyelement)
+  returns anymultirange as 'select int4multirange(int4range(1,10))' language sql;
+
+-- should fail
+create function bogus_func(int)
+  returns anymultirange as 'select int4multirange(int4range(1,10))' language sql;
+
+create function range_add_bounds(anymultirange)
+  returns anyelement as 'select lower($1) + upper($1)' language sql;
+
+select range_add_bounds(int4multirange(int4range(1, 17)));
+select range_add_bounds(nummultirange(numrange(1.0001, 123.123)));
+
+create function multirangetypes_sql(q anymultirange, b anyarray, out c anyelement)
+  as $$ select upper($1) + $2[1] $$
+  language sql;
+
+select multirangetypes_sql(int4multirange(int4range(1,10)), ARRAY[2,20]);
+select multirangetypes_sql(nummultirange(numrange(1,10)), ARRAY[2,20]);  -- match failure
+
+--
+-- Arrays of multiranges
+--
+
+select ARRAY[nummultirange(numrange(1.1, 1.2)), nummultirange(numrange(12.3, 155.5))];
+
+create table i8mr_array (f1 int, f2 int8multirange[]);
+insert into i8mr_array values (42, array[int8multirange(int8range(1,10)), int8multirange(int8range(2,20))]);
+select * from i8mr_array;
+drop table i8mr_array;
+
+--
+-- Multiranges of arrays
+--
+
+select arraymultirange(arrayrange(ARRAY[1,2], ARRAY[2,1]));
+select arraymultirange(arrayrange(ARRAY[2,1], ARRAY[1,2]));  -- fail
+
+select array[1,1] <@ arraymultirange(arrayrange(array[1,2], array[2,1]));
+select array[1,3] <@ arraymultirange(arrayrange(array[1,2], array[2,1]));
+
+--
+-- Ranges of composites
+--
+
+create type two_ints as (a int, b int);
+create type two_ints_range as range (subtype = two_ints);
+
+-- with force_parallel_mode on, this exercises tqueue.c's range remapping
+select *, row_to_json(upper(t)) as u from
+  (values (two_ints_multirange(two_ints_range(row(1,2), row(3,4)))),
+          (two_ints_multirange(two_ints_range(row(5,6), row(7,8))))) v(t);
+
+drop type two_ints cascade;
+
+--
+-- Check behavior when subtype lacks a hash function
+--
+
+set enable_sort = off;  -- try to make it pick a hash setop implementation
+
+select '{(2,5)}'::cashmultirange except select '{(5,6)}'::cashmultirange;
+
+reset enable_sort;
+
+--
+-- OUT/INOUT/TABLE functions
+--
+
+-- infer anymultirange from anymultirange
+create function mr_outparam_succeed(i anymultirange, out r anymultirange, out t text)
+  as $$ select $1, 'foo'::text $$ language sql;
+
+select * from mr_outparam_succeed(int4multirange(int4range(1,2)));
+
+-- infer anyarray from anymultirange
+create function mr_outparam_succeed2(i anymultirange, out r anyarray, out t text)
+  as $$ select ARRAY[upper($1)], 'foo'::text $$ language sql;
+
+select * from mr_outparam_succeed2(int4multirange(int4range(1,2)));
+
+-- infer anyrange from anymultirange
+create function mr_outparam_succeed3(i anymultirange, out r anyrange, out t text)
+  as $$ select range_merge($1), 'foo'::text $$ language sql;
+select * from mr_outparam_succeed3(int4multirange(int4range(1,2)));
+
+-- infer anymultirange from anyrange
+create function mr_outparam_succeed4(i anyrange, out r anymultirange, out t text)
+  as $$ select multirange($1), 'foo'::text $$ language sql;
+
+select * from mr_outparam_succeed4(int4range(1,2));
+
+-- infer anyelement from anymultirange
+create function mr_inoutparam_succeed(out i anyelement, inout r anymultirange)
+  as $$ select upper($1), $1 $$ language sql;
+
+select * from mr_inoutparam_succeed(int4multirange(int4range(1,2)));
+
+-- infer anyelement+anymultirange from anyelement+anymultirange
+create function mr_table_succeed(i anyelement, r anymultirange) returns table(i anyelement, r anymultirange)
+  as $$ select $1, $2 $$ language sql;
+
+select * from mr_table_succeed(123, int4multirange(int4range(1,11)));
+
+-- use anymultirange in plpgsql
+create function mr_polymorphic(i anyrange) returns anymultirange
+  as $$ begin return multirange($1); end; $$ language plpgsql;
+select mr_polymorphic(int4range(1, 4));
+
+-- should fail
+create function mr_outparam_fail(i anyelement, out r anymultirange, out t text)
+  as $$ select '[1,10]', 'foo' $$ language sql;
+
+--should fail
+create function mr_inoutparam_fail(inout i anyelement, out r anymultirange)
+  as $$ select $1, '[1,10]' $$ language sql;
+
+--should fail
+create function mr_table_fail(i anyelement) returns table(i anyelement, r anymultirange)
+  as $$ select $1, '[1,10]' $$ language sql;
diff --git a/src/test/regress/sql/opr_sanity.sql b/src/test/regress/sql/opr_sanity.sql
index 9a1ea3d999..69c4859102 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;
 
@@ -279,16 +290,19 @@ WHERE p1.prorettype IN
      '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;
 
--- anyrange is tighter than the rest, can only resolve from anyrange input
+-- anyrange and anymultirange are tighter than the rest, can only resolve
+-- from each other
 
 SELECT p1.oid, p1.proname
 FROM pg_proc as p1
-WHERE p1.prorettype = 'anyrange'::regtype
+WHERE p1.prorettype IN ('anyrange'::regtype, 'anymultirange'::regtype)
   AND NOT
-     'anyrange'::regtype = ANY (p1.proargtypes)
+    ('anyrange'::regtype = ANY (p1.proargtypes) OR
+      'anymultirange'::regtype = ANY (p1.proargtypes))
 ORDER BY 2;
 
 -- similarly for the anycompatible family
diff --git a/src/test/regress/sql/rangetypes.sql b/src/test/regress/sql/rangetypes.sql
index 4048b1d185..0276d19136 100644
--- a/src/test/regress/sql/rangetypes.sql
+++ b/src/test/regress/sql/rangetypes.sql
@@ -118,6 +118,10 @@ select numrange(1.0, 2.0) * numrange(2.0, 3.0);
 select numrange(1.0, 2.0) * numrange(1.5, 3.0);
 select numrange(1.0, 2.0) * numrange(2.5, 3.0);
 
+select range_intersect_agg(nr) from numrange_test;
+select range_intersect_agg(nr) from numrange_test where false;
+select range_intersect_agg(nr) from numrange_test where nr @> 4.0;
+
 create table numrange_test2(nr numrange);
 create index numrange_test2_hash_idx on numrange_test2 using hash (nr);
 
@@ -528,6 +532,7 @@ reset enable_sort;
 -- OUT/INOUT/TABLE functions
 --
 
+-- infer anyrange from anyrange
 create function outparam_succeed(i anyrange, out r anyrange, out t text)
   as $$ select $1, 'foo'::text $$ language sql;
 
@@ -539,6 +544,13 @@ create function outparam2_succeed(r anyrange, out lu anyarray, out ul anyarray)
 
 select * from outparam2_succeed(int4range(1,11));
 
+-- infer anyarray from anyrange
+create function outparam_succeed2(i anyrange, out r anyarray, out t text)
+  as $$ select ARRAY[upper($1)], 'foo'::text $$ language sql;
+
+select * from outparam_succeed2(int4range(int4range(1,2)));
+
+-- infer anyelement from anyrange
 create function inoutparam_succeed(out i anyelement, inout r anyrange)
   as $$ select upper($1), $1 $$ language sql;
 
diff --git a/src/test/regress/sql/type_sanity.sql b/src/test/regress/sql/type_sanity.sql
index 4b492ce062..07879d9a57 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'));
@@ -153,7 +153,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')
@@ -182,7 +182,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')
@@ -234,7 +234,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')
@@ -263,7 +263,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')
@@ -467,3 +467,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.rngmultitypid
+FROM pg_range p1
+WHERE p1.rngmultitypid IS NULL OR p1.rngmultitypid = 0;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 34623523a7..a2481e3837 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1362,6 +1362,9 @@ MultiXactMember
 MultiXactOffset
 MultiXactStateData
 MultiXactStatus
+MultirangeIOData
+MultirangeParseState
+MultirangeType
 MyData
 NDBOX
 NODE
#121Paul A Jungwirth
pj@illuminatedcomputing.com
In reply to: Alvaro Herrera (#118)
1 attachment(s)
Re: range_agg

On Sat, Apr 4, 2020 at 4:10 PM Alvaro Herrera <alvherre@2ndquadrant.com> wrote:

Sorry I didn't reply earlier, but I didn't know the answer then and I
still don't know the answer now.

Okay, thanks Alvaro! I'll see if I can figure it out myself. I assume
it is actually possible, right? I've seen references to on-disk format
vs in-memory format before, but I've never encountered anything in the
code supporting a difference.

Anyway, I rebased this to verify that the code hasn't broken, and it
hasn't -- the tests still pass. There was a minor conflict in
pg_operator.dat which I fixed.

Thanks, and thanks for your v17 also. Here is a patch building on that
and adding support for anycompatiblemultirange.

Regards,
Paul

Attachments:

v18-multirange.patchapplication/octet-stream; name=v18-multirange.patchDownload
diff --git a/doc/src/sgml/datatype.sgml b/doc/src/sgml/datatype.sgml
index c2e42f31c0..22404d18cd 100644
--- a/doc/src/sgml/datatype.sgml
+++ b/doc/src/sgml/datatype.sgml
@@ -4812,6 +4812,10 @@ SELECT * FROM pg_attribute
    </indexterm>
 
    <indexterm zone="datatype-pseudo">
+    <primary>anymultirange</primary>
+   </indexterm>
+
+   <indexterm zone="datatype-pseudo">
     <primary>anycompatible</primary>
    </indexterm>
 
@@ -4828,6 +4832,10 @@ SELECT * FROM pg_attribute
    </indexterm>
 
    <indexterm zone="datatype-pseudo">
+    <primary>anycompatiblemultirange</primary>
+   </indexterm>
+
+   <indexterm zone="datatype-pseudo">
     <primary>void</primary>
    </indexterm>
 
@@ -4937,6 +4945,13 @@ SELECT * FROM pg_attribute
        </row>
 
        <row>
+        <entry><type>anymultirange</type></entry>
+        <entry>Indicates that a function accepts any multirange data type
+        (see <xref linkend="extend-types-polymorphic"/> and
+        <xref linkend="rangetypes"/>).</entry>
+       </row>
+
+       <row>
         <entry><type>anycompatible</type></entry>
         <entry>Indicates that a function accepts any data type,
         with automatic promotion of multiple arguments to a common data type
@@ -4966,6 +4981,14 @@ SELECT * FROM pg_attribute
        </row>
 
        <row>
+        <entry><type>anycompatiblemultirange</type></entry>
+        <entry>Indicates that a function accepts any multirange data type,
+        with automatic promotion of multiple arguments to a common data type
+        (see <xref linkend="extend-types-polymorphic"/> and
+        <xref linkend="rangetypes"/>).</entry>
+       </row>
+
+       <row>
         <entry><type>cstring</type></entry>
         <entry>Indicates that a function accepts or returns a null-terminated C string.</entry>
        </row>
diff --git a/doc/src/sgml/extend.sgml b/doc/src/sgml/extend.sgml
index 930aeb767c..3f62c06daf 100644
--- a/doc/src/sgml/extend.sgml
+++ b/doc/src/sgml/extend.sgml
@@ -286,6 +286,14 @@
        </row>
 
        <row>
+        <entry><type>anymultirange</type></entry>
+        <entry>Simple</entry>
+        <entry>Indicates that a function accepts any multirange data type
+        (see <xref linkend="rangetypes"/>)
+        </entry>
+       </row>
+
+       <row>
         <entry><type>anycompatible</type></entry>
         <entry>Common</entry>
         <entry>Indicates that a function accepts any data type,
@@ -316,6 +324,14 @@
         with automatic promotion of multiple arguments to a common data type
         </entry>
        </row>
+
+       <row>
+        <entry><type>anycompatiblemultirange</type></entry>
+        <entry>Common</entry>
+        <entry>Indicates that a function accepts any multirange data type,
+        with automatic promotion of multiple arguments to a common data type
+        </entry>
+       </row>
       </tbody>
      </tgroup>
     </table>
@@ -343,17 +359,15 @@
      position declared as <type>anyarray</type> can have any array data type,
      but similarly they must all be the same type.  And similarly,
      positions declared as <type>anyrange</type> must all be the same range
-     type.  Furthermore, if there are
+     type.  Likewise for <type>anymultirange</type>.
+    </para>
+
+    <para>
+     Furthermore, if there are
      positions declared <type>anyarray</type> and others declared
      <type>anyelement</type>, the actual array type in the
      <type>anyarray</type> positions must be an array whose elements are
      the same type appearing in the <type>anyelement</type> positions.
-     Similarly, if there are positions declared <type>anyrange</type>
-     and others declared <type>anyelement</type> or <type>anyarray</type>,
-     the actual range type in the <type>anyrange</type> positions must be a
-     range whose subtype is the same type appearing in
-     the <type>anyelement</type> positions and the same as the element type
-     of the <type>anyarray</type> positions.
      <type>anynonarray</type> is treated exactly the same as <type>anyelement</type>,
      but adds the additional constraint that the actual type must not be
      an array type.
@@ -363,6 +377,19 @@
     </para>
 
     <para>
+     Similarly, if there are positions declared <type>anyrange</type>
+     and others declared <type>anyelement</type> or <type>anyarray</type>,
+     the actual range type in the <type>anyrange</type> positions must be a
+     range whose subtype is the same type appearing in
+     the <type>anyelement</type> positions and the same as the element type
+     of the <type>anyarray</type> positions.
+     If there are positions declared <type>anymultirange</type>,
+     their actual multirange type must contain ranges matching parameters declared
+     <type>anyrange</type> and base elements matching parameters declared
+     <type>anyelement</type> and <type>anyarray</type>.
+    </para>
+
+    <para>
      Thus, when more than one argument position is declared with a polymorphic
      type, the net effect is that only certain combinations of actual argument
      types are allowed.  For example, a function declared as
@@ -417,7 +444,8 @@
      Selection of the common type considers the actual types
      of <type>anycompatible</type> and <type>anycompatiblenonarray</type>
      inputs, the array element types of <type>anycompatiblearray</type>
-     inputs, and the range subtypes of <type>anycompatiblerange</type>
+     inputs, the range subtypes of <type>anycompatiblerange</type> inputs,
+     and the multirange subtypes of <type>anycompatiablemultirange</type>
      inputs.  If <type>anycompatiblenonarray</type> is present then the
      common type is required to be a non-array type.  Once a common type is
      identified, arguments in <type>anycompatible</type>
@@ -428,12 +456,15 @@
 
     <para>
      Since there is no way to select a range type knowing only its subtype,
-     use of <type>anycompatiblerange</type> requires that all arguments
-     declared with that type have the same actual range type, and that that
-     type's subtype agree with the selected common type, so that no casting
-     of the range values is required.  As with <type>anyrange</type>, use
-     of <type>anycompatiblerange</type> as a function result type requires
-     that there be an <type>anycompatiblerange</type> argument.
+     use of <type>anycompatiblerange</type> and/or
+     <type>anycompatiblemultirange</type> requires that all arguments declared
+     with that type have the same actual range and/or multirange type, and that
+     that type's subtype agree with the selected common type, so that no casting
+     of the range values is required.  As with <type>anyrange</type> and
+     <type>anymultirange</type>, use of <type>anycompatiblerange</type> and
+     <type>anymultirange</type> as a function result type requires that there be
+     an <type>anycompatiblerange</type> or <type>anycompatiblemultirange</type>
+     argument.
     </para>
 
     <para>
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 12d75b476f..2e7c62f1a0 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -13609,7 +13609,7 @@ SELECT NULLIF(value, '(none)') ...
        <row>
         <entry>Operator</entry>
         <entry>Description</entry>
-        <entry>Example</entry>
+        <entry>Examples</entry>
         <entry>Result</entry>
        </row>
       </thead>
@@ -14080,12 +14080,14 @@ NULL baz</literallayout>(3 rows)</entry>
   <title>Range Functions and Operators</title>
 
   <para>
-   See <xref linkend="rangetypes"/> for an overview of range types.
+   See <xref linkend="rangetypes"/> for an overview of range and multirange types.
   </para>
 
   <para>
    <xref linkend="range-operators-table"/> shows the operators
-   available for range types.
+   available for range and multirange types.
+   Many of these operators will accept either a range or multirange
+   on either side.
   </para>
 
     <table id="range-operators-table">
@@ -14103,134 +14105,215 @@ NULL baz</literallayout>(3 rows)</entry>
        <row>
         <entry> <literal>=</literal> </entry>
         <entry>equal</entry>
-        <entry><literal>int4range(1,5) = '[1,4]'::int4range</literal></entry>
-        <entry><literal>t</literal></entry>
+        <entry><literallayout class="monospaced">int4range(1,5) = '[1,4]'::int4range
+'{[1,5)}'::int4multirange = '{[1,4]}'::int4multirange</literallayout></entry>
+        <entry><literallayout class="monospaced">t</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>&lt;&gt;</literal> </entry>
         <entry>not equal</entry>
-        <entry><literal>numrange(1.1,2.2) &lt;&gt; numrange(1.1,2.3)</literal></entry>
-        <entry><literal>t</literal></entry>
+        <entry><literallayout class="monospaced">numrange(1.1,2.2) &lt;&gt; numrange(1.1,2.3)
+'{[1.1,2.2)}'::nummultirange &lt;&gt; '{[1.1,2.3)}'::nummultirange</literallayout></entry>
+        <entry><literallayout class="monospaced">t</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>&lt;</literal> </entry>
         <entry>less than</entry>
-        <entry><literal>int4range(1,10) &lt; int4range(2,3)</literal></entry>
-        <entry><literal>t</literal></entry>
+        <entry><literallayout class="monospaced">int4range(1,10) &lt; int4range(2,3)
+'{[1,10)}'::int4multirange &lt; '{[2,3)}'::int4multirange</literallayout>
+        </entry>
+        <entry><literallayout class="monospaced">t</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>&gt;</literal> </entry>
         <entry>greater than</entry>
-        <entry><literal>int4range(1,10) &gt; int4range(1,5)</literal></entry>
-        <entry><literal>t</literal></entry>
+        <entry>
+          <literallayout class="monospaced">int4range(1,10) &gt; int4range(1,5)
+'{[1,10)}'::int4multirange &gt; '{[1,5)}'::int4multirange</literallayout>
+        </entry>
+        <entry><literallayout class="monospaced">t</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>&lt;=</literal> </entry>
         <entry>less than or equal</entry>
-        <entry><literal>numrange(1.1,2.2) &lt;= numrange(1.1,2.2)</literal></entry>
-        <entry><literal>t</literal></entry>
+        <entry>
+          <literallayout class="monospaced">numrange(1.1,2.2) &lt;= numrange(1.1,2.2)
+'{[1.1,2.2)}'::nummultirange &lt;= '{[1.1,2.2)}'::nummultirange</literallayout>
+        </entry>
+        <entry><literallayout class="monospaced">t</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>&gt;=</literal> </entry>
         <entry>greater than or equal</entry>
-        <entry><literal>numrange(1.1,2.2) &gt;= numrange(1.1,2.0)</literal></entry>
-        <entry><literal>t</literal></entry>
+        <entry>
+          <literallayout class="monospaced">numrange(1.1,2.2) &gt;= numrange(1.1,2.0)
+'{[1.1,2.2)}'::nummultirange &gt;= '{[1.1,2.0)}'::nummultirange</literallayout>
+        </entry>
+        <entry><literallayout class="monospaced">t</literallayout></entry>
+       </row>
+
+       <row>
+        <entry> <literal>@&gt;</literal> </entry>
+        <entry>contains multirange</entry>
+        <entry>
+          <literallayout class="monospaced">int4range(2,4) @&gt; '{[2,3)}'::int4multirange
+'{[2,4)}'::int4multirange @&gt; '{[2,3)}'::int4multirange</literallayout>
+        </entry>
+        <entry><literallayout class="monospaced">t</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>@&gt;</literal> </entry>
         <entry>contains range</entry>
-        <entry><literal>int4range(2,4) @&gt; int4range(2,3)</literal></entry>
-        <entry><literal>t</literal></entry>
+        <entry>
+          <literallayout class="monospaced">int4range(2,4) @&gt; int4range(2,3)
+'{[2,4)}'::int4multirange @&gt; int4range(2,3)</literallayout>
+        </entry>
+        <entry><literallayout class="monospaced">t</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>@&gt;</literal> </entry>
         <entry>contains element</entry>
-        <entry><literal>'[2011-01-01,2011-03-01)'::tsrange @&gt; '2011-01-10'::timestamp</literal></entry>
-        <entry><literal>t</literal></entry>
+        <entry>
+          <literallayout class="monospaced">'[2011-01-01,2011-03-01)'::tsrange @&gt; '2011-01-10'::timestamp
+'{[2011-01-01,2011-03-01)}'::tsmultirange @&gt; '2011-01-10'::timestamp</literallayout>
+        </entry>
+        <entry><literallayout class="monospaced">t</literallayout></entry>
+       </row>
+
+       <row>
+        <entry> <literal>&lt;@</literal> </entry>
+        <entry>multirange is contained by</entry>
+        <entry>
+          <literallayout class="monospaced">'{[2,4)}'::int4multirange &lt;@ int4range(1,7)
+'{[2,4)}'::int4multirange &lt;@ '{[1,7)}'::int4multirange</literallayout>
+        </entry>
+        <entry><literallayout class="monospaced">t</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>&lt;@</literal> </entry>
         <entry>range is contained by</entry>
-        <entry><literal>int4range(2,4) &lt;@ int4range(1,7)</literal></entry>
-        <entry><literal>t</literal></entry>
+        <entry>
+          <literallayout class="monospaced">int4range(2,4) &lt;@ int4range(1,7)
+int4range(2,4) &lt;@ '{[1,7)}'::int4multirange</literallayout>
+        </entry>
+        <entry><literallayout class="monospaced">t</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>&lt;@</literal> </entry>
         <entry>element is contained by</entry>
-        <entry><literal>42 &lt;@ int4range(1,7)</literal></entry>
-        <entry><literal>f</literal></entry>
+        <entry>
+          <literallayout class="monospaced">42 &lt;@ int4range(1,7)
+42 &lt;@ '{[1,7)}'::int4multirange</literallayout>
+        </entry>
+        <entry><literallayout class="monospaced">f</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>&amp;&amp;</literal> </entry>
         <entry>overlap (have points in common)</entry>
-        <entry><literal>int8range(3,7) &amp;&amp; int8range(4,12)</literal></entry>
-        <entry><literal>t</literal></entry>
+        <entry>
+          <literallayout class="monospaced">int8range(3,7) &amp;&amp; int8range(4,12)
+int8range(3,7) &amp;&amp; '{[4,12)}'::int8multirange
+'{[3,7)}'::int8multirange &amp;&amp; int8range(4,12)
+'{[3,7)}'::int8multirange &amp;&amp; '{[4,12)}'::int8multirange</literallayout>
+        </entry>
+        <entry><literallayout class="monospaced">t</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>&lt;&lt;</literal> </entry>
         <entry>strictly left of</entry>
-        <entry><literal>int8range(1,10) &lt;&lt; int8range(100,110)</literal></entry>
-        <entry><literal>t</literal></entry>
+        <entry>
+          <literallayout class="monospaced">int8range(1,10) &lt;&lt; int8range(100,110)
+int8range(1,10) &lt;&lt; '{[100,110)}'::int8multirange
+'{[1,10)}'::int8multirange &lt;&lt; int8range(100,110)
+'{[1,10)}'::int8multirange &lt;&lt; '{[100,110)}'::int8multirange</literallayout>
+        </entry>
+        <entry><literallayout class="monospaced">t</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>&gt;&gt;</literal> </entry>
         <entry>strictly right of</entry>
-        <entry><literal>int8range(50,60) &gt;&gt; int8range(20,30)</literal></entry>
-        <entry><literal>t</literal></entry>
+        <entry>
+          <literallayout class="monospaced">int8range(50,60) &gt;&gt; int8range(20,30)
+int8range(50,60) &gt;&gt; '{[20,30)}'::int8multirange
+'{[50,60)}'::int8multirange &gt;&gt; int8range(20,30)
+'{[50,60)}'::int8multirange &gt;&gt; '{[20,30)}'::int8multirange</literallayout>
+        </entry>
+        <entry><literallayout class="monospaced">t</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>&amp;&lt;</literal> </entry>
         <entry>does not extend to the right of</entry>
-        <entry><literal>int8range(1,20) &amp;&lt; int8range(18,20)</literal></entry>
-        <entry><literal>t</literal></entry>
+        <entry>
+          <literallayout class="monospaced">int8range(1,20) &amp;&lt; int8range(18,20)
+int8range(1,20) &amp;&lt; '{[18,20)}'::int8multirange
+'{[1,20)}'::int8multirange &amp;&lt; int8range(18,20)
+'{[1,20)}'::int8multirange &amp;&lt; '{[18,20)}'::int8multirange</literallayout>
+        </entry>
+        <entry><literallayout class="monospaced">t</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>&amp;&gt;</literal> </entry>
         <entry>does not extend to the left of</entry>
-        <entry><literal>int8range(7,20) &amp;&gt; int8range(5,10)</literal></entry>
-        <entry><literal>t</literal></entry>
+        <entry>
+          <literallayout class="monospaced">int8range(7,20) &amp;&gt; int8range(5,10)
+int8range(7,20) &amp;&gt; '{[5,10)}'::int8multirange
+'{[7,20)}'::int8multirange &amp;&gt; int8range(5,10)
+'{[7,20)}'::int8multirange &amp;&gt; '{[5,10)}'::int8multirange</literallayout>
+        </entry>
+        <entry><literallayout class="monospaced">t</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>-|-</literal> </entry>
         <entry>is adjacent to</entry>
-        <entry><literal>numrange(1.1,2.2) -|- numrange(2.2,3.3)</literal></entry>
-        <entry><literal>t</literal></entry>
+        <entry>
+          <literallayout class="monospaced">numrange(1.1,2.2) -|- numrange(2.2,3.3)
+numrange(1.1,2.2) -|- '{[2.2,3.3)}'::nummultirange
+'{[1.1,2.2)}'::nummultirange -|- numrange(2.2,3.3)
+'{[1.1,2.2)}'::nummultirange -|- '{[2.2,3.3)}'::nummultirange</literallayout>
+        </entry>
+        <entry><literallayout class="monospaced">t</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>+</literal> </entry>
         <entry>union</entry>
-        <entry><literal>numrange(5,15) + numrange(10,20)</literal></entry>
-        <entry><literal>[5,20)</literal></entry>
+        <entry><literallayout class="monospaced">numrange(5,15) + numrange(10,20)
+'{[5,10)}'::nummultirange + '{[15,20)}'::nummultirange</literallayout></entry>
+        <entry><literallayout class="monospaced">[5,20)
+{[5,10), [15,20)}</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>*</literal> </entry>
         <entry>intersection</entry>
-        <entry><literal>int8range(5,15) * int8range(10,20)</literal></entry>
-        <entry><literal>[10,15)</literal></entry>
+        <entry><literallayout class="monospaced">int8range(5,15) * int8range(10,20)
+'{[5,15)}'::int8multirange * '{[10,20)}'::int8multirange</literallayout></entry>
+        <entry><literallayout class="monospaced">[10,15)
+{[10,15)}</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>-</literal> </entry>
         <entry>difference</entry>
-        <entry><literal>int8range(5,15) - int8range(10,20)</literal></entry>
-        <entry><literal>[5,10)</literal></entry>
+        <entry><literallayout class="monospaced">int8range(5,15) - int8range(10,20)
+'{[5,20)}'::int8multirange - '{[10,15)}'::int8multirange</literallayout></entry>
+        <entry><literallayout class="monospaced">[5,10)
+{[5,10), [15,20)}</literallayout></entry>
        </row>
 
       </tbody>
@@ -14248,19 +14331,31 @@ NULL baz</literallayout>(3 rows)</entry>
 
   <para>
    The left-of/right-of/adjacent operators always return false when an empty
-   range is involved; that is, an empty range is not considered to be either
-   before or after any other range.
+   range or multirange is involved; that is, an empty range is not considered to
+   be either before or after any other range.
+  </para>
+
+  <para>
+   Elsewhere empty ranges and multiranges are treated as the additive identity:
+   anything unioned with an empty value is itself. Anything minus an empty
+   value is itself. An empty multirange has exactly the same points as an empty
+   range. Every range contains the empty range. Every multirange contains as many
+   empty ranges as you like.
   </para>
 
   <para>
    The union and difference operators will fail if the resulting range would
    need to contain two disjoint sub-ranges, as such a range cannot be
-   represented.
+   represented. There are separate operators for union and difference that take
+   multirange parameters and return a multirange, and they do not fail even if
+   their arguments are disjoint. So if you need a union or difference operation
+   for ranges that may be disjoint, you can avoid errors by first casting your
+   ranges to multiranges.
   </para>
 
   <para>
    <xref linkend="range-functions-table"/> shows the functions
-   available for use with range types.
+   available for use with range and multirange types.
   </para>
 
   <indexterm>
@@ -14312,6 +14407,17 @@ NULL baz</literallayout>(3 rows)</entry>
        <row>
         <entry>
          <literal>
+          <function>lower</function>(<type>anymultirange</type>)
+         </literal>
+        </entry>
+        <entry>multirange's element type</entry>
+        <entry>lower bound of multirange</entry>
+        <entry><literal>lower('{[1.1,2.2)}'::nummultirange)</literal></entry>
+        <entry><literal>1.1</literal></entry>
+       </row>
+       <row>
+        <entry>
+         <literal>
           <function>upper</function>(<type>anyrange</type>)
          </literal>
         </entry>
@@ -14323,6 +14429,17 @@ NULL baz</literallayout>(3 rows)</entry>
        <row>
         <entry>
          <literal>
+          <function>upper</function>(<type>anymultirange</type>)
+         </literal>
+        </entry>
+        <entry>multirange's element type</entry>
+        <entry>upper bound of multirange</entry>
+        <entry><literal>upper('{[1.1,2.2)}'::nummultirange)</literal></entry>
+        <entry><literal>2.2</literal></entry>
+       </row>
+       <row>
+        <entry>
+         <literal>
           <function>isempty</function>(<type>anyrange</type>)
          </literal>
         </entry>
@@ -14334,6 +14451,17 @@ NULL baz</literallayout>(3 rows)</entry>
        <row>
         <entry>
          <literal>
+          <function>isempty</function>(<type>anymultirange</type>)
+         </literal>
+        </entry>
+        <entry><type>boolean</type></entry>
+        <entry>is the multirange empty?</entry>
+        <entry><literal>isempty('{[1.1,2.2)}'::nummultirange)</literal></entry>
+        <entry><literal>false</literal></entry>
+       </row>
+       <row>
+        <entry>
+         <literal>
           <function>lower_inc</function>(<type>anyrange</type>)
          </literal>
         </entry>
@@ -14345,6 +14473,17 @@ NULL baz</literallayout>(3 rows)</entry>
        <row>
         <entry>
          <literal>
+          <function>lower_inc</function>(<type>anymultirange</type>)
+         </literal>
+        </entry>
+        <entry><type>boolean</type></entry>
+        <entry>is the lower bound inclusive?</entry>
+        <entry><literal>lower_inc('{[1.1,2.2)}'::nummultirange)</literal></entry>
+        <entry><literal>true</literal></entry>
+       </row>
+       <row>
+        <entry>
+         <literal>
           <function>upper_inc</function>(<type>anyrange</type>)
          </literal>
         </entry>
@@ -14356,6 +14495,17 @@ NULL baz</literallayout>(3 rows)</entry>
        <row>
         <entry>
          <literal>
+          <function>upper_inc</function>(<type>anymultirange</type>)
+         </literal>
+        </entry>
+        <entry><type>boolean</type></entry>
+        <entry>is the upper bound inclusive?</entry>
+        <entry><literal>upper_inc('{[1.1,2.2)}'::nummultirange)</literal></entry>
+        <entry><literal>false</literal></entry>
+       </row>
+       <row>
+        <entry>
+         <literal>
           <function>lower_inf</function>(<type>anyrange</type>)
          </literal>
         </entry>
@@ -14367,6 +14517,17 @@ NULL baz</literallayout>(3 rows)</entry>
        <row>
         <entry>
          <literal>
+          <function>lower_inf</function>(<type>anymultirange</type>)
+         </literal>
+        </entry>
+        <entry><type>boolean</type></entry>
+        <entry>is the lower bound infinite?</entry>
+        <entry><literal>lower_inf('{(,)}'::datemultirange)</literal></entry>
+        <entry><literal>true</literal></entry>
+       </row>
+       <row>
+        <entry>
+         <literal>
           <function>upper_inf</function>(<type>anyrange</type>)
          </literal>
         </entry>
@@ -14378,6 +14539,17 @@ NULL baz</literallayout>(3 rows)</entry>
        <row>
         <entry>
          <literal>
+          <function>upper_inf</function>(<type>anymultirange</type>)
+         </literal>
+        </entry>
+        <entry><type>boolean</type></entry>
+        <entry>is the upper bound infinite?</entry>
+        <entry><literal>upper_inf('{(,)}'::datemultirange)</literal></entry>
+        <entry><literal>true</literal></entry>
+       </row>
+       <row>
+        <entry>
+         <literal>
           <function>range_merge</function>(<type>anyrange</type>, <type>anyrange</type>)
          </literal>
         </entry>
@@ -14386,16 +14558,38 @@ NULL baz</literallayout>(3 rows)</entry>
         <entry><literal>range_merge('[1,2)'::int4range, '[3,4)'::int4range)</literal></entry>
         <entry><literal>[1,4)</literal></entry>
        </row>
+       <row>
+        <entry>
+         <literal>
+          <function>range_merge</function>(<type>anymultirange</type>)
+         </literal>
+        </entry>
+        <entry><type>anyrange</type></entry>
+        <entry>the smallest range which includes the entire multirange</entry>
+        <entry><literal>range_merge('{[1,2), [3,4)}'::int4multirange)</literal></entry>
+        <entry><literal>[1,4)</literal></entry>
+       </row>
+       <row>
+        <entry>
+         <literal>
+          <function>multirange</function>(<type>anyrange</type>)
+         </literal>
+        </entry>
+        <entry><type>anymultirange</type></entry>
+        <entry>a multirange containing just the given range</entry>
+        <entry><literal>multirange('[1,2)'::int4range)</literal></entry>
+        <entry><literal>{[1,2)}</literal></entry>
+       </row>
       </tbody>
      </tgroup>
     </table>
 
   <para>
    The <function>lower</function> and  <function>upper</function> functions return null
-   if the range is empty or the requested bound is infinite.
+   if the input range/multirange is empty or the requested bound is infinite.
    The <function>lower_inc</function>, <function>upper_inc</function>,
    <function>lower_inf</function>, and <function>upper_inf</function>
-   functions all return false for an empty range.
+   functions all return false for an empty range or multirange.
   </para>
   </sect1>
 
@@ -14717,6 +14911,44 @@ NULL baz</literallayout>(3 rows)</entry>
      <row>
       <entry>
        <indexterm>
+        <primary>range_agg</primary>
+       </indexterm>
+       <function>
+         range_agg(<replaceable class="parameter">expression</replaceable>)
+       </function>
+      </entry>
+      <entry>
+       <type>anyrange</type>
+      </entry>
+      <entry>
+       <type>multirange</type> of the argument range type
+      </entry>
+      <entry>Yes</entry>
+      <entry>union of the non-null input values</entry>
+     </row>
+
+     <row>
+      <entry>
+       <indexterm>
+        <primary>range_intersect_agg</primary>
+       </indexterm>
+       <function>
+         range_intersect_agg(<replaceable class="parameter">expression</replaceable>)
+       </function>
+      </entry>
+      <entry>
+       <type>anyrange</type>
+      </entry>
+      <entry>
+       <type>multirange</type> of the argument range type
+      </entry>
+      <entry>No</entry>
+      <entry>intersection of the non-null input values</entry>
+     </row>
+
+     <row>
+      <entry>
+       <indexterm>
         <primary>string_agg</primary>
        </indexterm>
        <function>
diff --git a/doc/src/sgml/rangetypes.sgml b/doc/src/sgml/rangetypes.sgml
index b75fb3a392..c8784bdce3 100644
--- a/doc/src/sgml/rangetypes.sgml
+++ b/doc/src/sgml/rangetypes.sgml
@@ -27,6 +27,13 @@
   ranges from an instrument, and so forth can also be useful.
  </para>
 
+ <para>
+  Every range type has a corresponding multirange type. A multirange is
+  an ordered list of non-continguous, non-empty, non-null ranges. Most
+  range operators also work on multiranges, and they have a few functions
+  of their own.
+ </para>
+
  <sect2 id="rangetypes-builtin">
   <title>Built-in Range Types</title>
 
@@ -232,10 +239,30 @@ SELECT '[4,4]'::int4range;
 SELECT '[4,4)'::int4range;
 </programlisting>
   </para>
+
+  <para>
+   The input for a multirange is curly brackets (<literal>{</literal> and
+   <literal>}</literal>) containing zero or more valid ranges,
+   separated by commas. Whitespace is permitted around the brackets and
+   commas. This is intended to be reminiscent of array syntax, although
+   multiranges are much simpler: they have just one dimension and there is
+   no need to quote their contents. (The bounds of their ranges may be
+   quoted as above however.)
+  </para>
+
+  <para>
+  Examples:
+<programlisting>
+SELECT '{}'::int4multirange;
+SELECT '{[3,7)}'::int4multirange;
+SELECT '{[3,7), [8,9)}'::int4multirange;
+</programlisting>
+  </para>
+
  </sect2>
 
  <sect2 id="rangetypes-construct">
-  <title>Constructing Ranges</title>
+  <title>Constructing Ranges and Multiranges</title>
 
   <para>
    Each range type has a constructor function with the same name as the range
@@ -269,6 +296,19 @@ SELECT int8range(1, 14, '(]');
 SELECT numrange(NULL, 2.2);
 </programlisting>
   </para>
+
+  <para>
+   Each range type also has a multirange constructor with the same name as the
+   multirange type.  The constructor function takes zero or more arguments
+   which are all ranges of the appropriate type.
+   For example:
+
+<programlisting>
+SELECT nummultirange();
+SELECT nummultirange(numrange(1.0, 14.0));
+SELECT nummultirange(numrange(1.0, 14.0), numrange(20.0, 25.0));
+</programlisting>
+  </para>
  </sect2>
 
  <sect2 id="rangetypes-discrete">
@@ -342,6 +382,11 @@ SELECT '[1.234, 5.678]'::floatrange;
   </para>
 
   <para>
+   When you define your own range you automatically get a corresponding
+   multirange type.
+  </para>
+
+  <para>
    Defining your own range type also allows you to specify a different
    subtype B-tree operator class or collation to use, so as to change the sort
    ordering that determines which values fall into a given range.
diff --git a/src/backend/catalog/pg_range.c b/src/backend/catalog/pg_range.c
index b5bc36c2bd..1217a6ddd0 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_rngmultitypid - 1] = ObjectIdGetDatum(multirangeTypeOid);
 
 	tup = heap_form_tuple(RelationGetDescr(pg_range), values, nulls);
 
@@ -100,6 +102,12 @@ 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 cd56714968..c359f27c74 100644
--- a/src/backend/catalog/pg_type.c
+++ b/src/backend/catalog/pg_type.c
@@ -27,6 +27,7 @@
 #include "catalog/pg_proc.h"
 #include "catalog/pg_type.h"
 #include "commands/typecmds.h"
+#include "mb/pg_wchar.h"
 #include "miscadmin.h"
 #include "parser/scansup.h"
 #include "utils/acl.h"
@@ -36,6 +37,9 @@
 #include "utils/rel.h"
 #include "utils/syscache.h"
 
+static char *makeUniqueTypeName(const char *typeName, Oid typeNamespace,
+								bool tryOriginal);
+
 /* Potentially set by pg_upgrade_support functions */
 Oid			binary_upgrade_next_pg_type_oid = InvalidOid;
 
@@ -809,31 +813,10 @@ RenameTypeInternal(Oid typeOid, const char *newTypeName, Oid typeNamespace)
 char *
 makeArrayTypeName(const char *typeName, Oid typeNamespace)
 {
-	char	   *arr = (char *) palloc(NAMEDATALEN);
-	int			namelen = strlen(typeName);
-	int			i;
-
-	/*
-	 * 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;
-	}
+	char	   *arr;
 
-	if (i >= NAMEDATALEN - 1)
+	arr = makeUniqueTypeName(typeName, typeNamespace, false);
+	if (arr == NULL)
 		ereport(ERROR,
 				(errcode(ERRCODE_DUPLICATE_OBJECT),
 				 errmsg("could not form array type name for type \"%s\"",
@@ -904,3 +887,91 @@ moveArrayTypeName(Oid typeOid, const char *typeName, Oid typeNamespace)
 
 	return true;
 }
+
+
+/*
+ * makeMultirangeTypeName
+ *	  - given a range type name, make a multirange type name for it
+ *
+ * caller is responsible for pfreeing the result
+ */
+char *
+makeMultirangeTypeName(const char *rangeTypeName, Oid typeNamespace)
+{
+	char	   *buf;
+	char	   *mrname;
+	char	   *rangestr;
+
+	/*
+	 * 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...
+	 */
+	rangestr = strstr(rangeTypeName, "range");
+	if (rangestr)
+	{
+		char	*prefix = pnstrdup(rangeTypeName, rangestr - rangeTypeName);
+
+		buf = psprintf("%s%s%s", prefix, "multi", rangestr);
+	}
+	else
+		buf = psprintf("%s_multirange", pnstrdup(rangeTypeName, NAMEDATALEN - 12));
+
+	/* clip it at NAMEDATALEN-1 bytes */
+	buf[pg_mbcliplen(buf, strlen(buf), NAMEDATALEN - 1)] = '\0';
+
+	mrname = makeUniqueTypeName(buf, typeNamespace, true);
+	if (mrname == NULL)
+		ereport(ERROR,
+				(errcode(ERRCODE_DUPLICATE_OBJECT),
+				 errmsg("could not form multirange type name for type \"%s\"",
+						rangeTypeName)));
+
+	return mrname;
+}
+
+/*
+ * makeUniqueTypeName
+ *		Generate a unique name for a prospective new type
+ *
+ * Given a typeName, return a new palloc'ed name by preprending underscores
+ * until a non-conflicting name results.
+ *
+ * If tryOriginal, first try with zero underscores.
+ */
+static char *
+makeUniqueTypeName(const char *typeName, Oid typeNamespace, bool tryOriginal)
+{
+	int			i;
+	int			namelen;
+	char		dest[NAMEDATALEN];
+
+	Assert(strlen(typeName) <= NAMEDATALEN - 1);
+
+	if (tryOriginal &&
+		!SearchSysCacheExists2(TYPENAMENSP,
+							   CStringGetDatum(typeName),
+							   ObjectIdGetDatum(typeNamespace)))
+		return pstrdup(typeName);
+
+	/*
+	 * The idea is to prepend underscores as needed until we make a name that
+	 * doesn't collide with anything ...
+	 */
+	namelen = strlen(typeName);
+	for (i = 1; i < NAMEDATALEN - 1; i++)
+	{
+		dest[i - 1] = '_';
+		strlcpy(dest + i, typeName, NAMEDATALEN - i);
+		if (namelen + i >= NAMEDATALEN)
+			truncate_identifier(dest, NAMEDATALEN, false);
+
+		if (!SearchSysCacheExists2(TYPENAMENSP,
+								   CStringGetDatum(dest),
+								   ObjectIdGetDatum(typeNamespace)))
+			return pstrdup(dest);
+	}
+
+	return NULL;
+}
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index 8891b1d564..dfb2edd3d0 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -105,9 +105,14 @@ typedef struct
 
 /* Potentially set by pg_upgrade_support functions */
 Oid			binary_upgrade_next_array_pg_type_oid = InvalidOid;
+Oid			binary_upgrade_next_mrng_pg_type_oid = InvalidOid;
+Oid			binary_upgrade_next_mrng_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 rangeOid,
+									   Oid rangeArrayOid, Oid *castFuncOid);
 static Oid	findTypeInputFunction(List *procname, Oid typeOid);
 static Oid	findTypeOutputFunction(List *procname, Oid typeOid);
 static Oid	findTypeReceiveFunction(List *procname, Oid typeOid);
@@ -738,7 +743,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",
@@ -1279,6 +1285,11 @@ checkEnumOwner(HeapTuple tup)
 /*
  * DefineRange
  *		Registers a new range type.
+ *
+ * Perhaps it might be worthwhile to set pg_type.typelem to the base type,
+ * and likewise on multiranges to set it to the range type. But having a
+ * non-zero typelem is treated elsewhere as a synonym for being an array,
+ * and users might have queries with that same assumption.
  */
 ObjectAddress
 DefineRange(CreateRangeStmt *stmt)
@@ -1287,7 +1298,11 @@ DefineRange(CreateRangeStmt *stmt)
 	Oid			typeNamespace;
 	Oid			typoid;
 	char	   *rangeArrayName;
+	char	   *multirangeTypeName;
+	char	   *multirangeArrayName;
 	Oid			rangeArrayOid;
+	Oid			multirangeOid;
+	Oid			multirangeArrayOid;
 	Oid			rangeSubtype = InvalidOid;
 	List	   *rangeSubOpclassName = NIL;
 	List	   *rangeCollationName = NIL;
@@ -1304,6 +1319,8 @@ DefineRange(CreateRangeStmt *stmt)
 	AclResult	aclresult;
 	ListCell   *lc;
 	ObjectAddress address;
+	ObjectAddress mltrngaddress;
+	Oid			castFuncOid;
 
 	/* Convert list of names to a name and namespace */
 	typeNamespace = QualifiedNameGetCreationNamespace(stmt->typeName,
@@ -1452,8 +1469,10 @@ DefineRange(CreateRangeStmt *stmt)
 	/* alignment must be TYPALIGN_INT or TYPALIGN_DOUBLE for ranges */
 	alignment = (subtypalign == TYPALIGN_DOUBLE) ? TYPALIGN_DOUBLE : TYPALIGN_INT;
 
-	/* Allocate OID for array type */
+	/* Allocate OID for array type, its multirange, and its multirange array */
 	rangeArrayOid = AssignTypeArrayOid();
+	multirangeOid = AssignTypeMultirangeOid();
+	multirangeArrayOid = AssignTypeMultirangeArrayOid();
 
 	/* Create the pg_type entry */
 	address =
@@ -1491,9 +1510,46 @@ DefineRange(CreateRangeStmt *stmt)
 	Assert(typoid == InvalidOid || typoid == address.objectId);
 	typoid = address.objectId;
 
+	/* Create the multirange that goes with it */
+	multirangeTypeName = makeMultirangeTypeName(typeName, typeNamespace);
+
+	mltrngaddress =
+		TypeCreate(multirangeOid,	/* force assignment of this 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_RANGE,	/* type-category (range 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) */
+	Assert(multirangeOid == mltrngaddress.objectId);
+
 	/* Create the entry in pg_range */
 	RangeCreate(typoid, rangeSubtype, rangeCollation, rangeSubOpclass,
-				rangeCanonical, rangeSubtypeDiff);
+				rangeCanonical, rangeSubtypeDiff, multirangeOid);
 
 	/*
 	 * Create the array type that goes with it.
@@ -1534,8 +1590,53 @@ 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 */
+			   multirangeOid,	/* 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,
+							   multirangeOid, typoid, rangeArrayOid,
+							   &castFuncOid);
+
+	/* Create cast from the range type to its multirange type */
+	CastCreate(typoid, multirangeOid, castFuncOid, 'e', 'f', DEPENDENCY_INTERNAL);
+
+	pfree(multirangeTypeName);
+	pfree(multirangeArrayName);
 
 	return address;
 }
@@ -1613,6 +1714,147 @@ 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.
+ *
+ * Sets castFuncOid to the oid of the new constructor that can be used
+ * to cast from a range to a multirange.
+ */
+static void
+makeMultirangeConstructors(const char *name, Oid namespace,
+						   Oid multirangeOid, Oid rangeOid, Oid rangeArrayOid,
+						   Oid *castFuncOid)
+{
+	ObjectAddress myself,
+				referenced;
+	oidvector  *argtypes;
+	Datum		allParamTypes;
+	ArrayType  *allParameterTypes;
+	Datum		paramModes;
+	ArrayType  *parameterModes;
+
+	referenced.classId = TypeRelationId;
+	referenced.objectId = multirangeOid;
+	referenced.objectSubId = 0;
+
+	/* 0-arg constructor - for empty multiranges */
+	argtypes = buildoidvector(NULL, 0);
+	myself = ProcedureCreate(name,	/* name: same as multirange type */
+							 namespace,
+							 false,	/* replace */
+							 false,	/* returns set */
+							 multirangeOid,	/* return type */
+							 BOOTSTRAP_SUPERUSERID,	/* proowner */
+							 INTERNALlanguageId,	/* language */
+							 F_FMGR_INTERNAL_VALIDATOR,
+							 "multirange_constructor0",	/* prosrc */
+							 NULL,	/* probin */
+							 PROKIND_FUNCTION,
+							 false,	/* security_definer */
+							 false,	/* leakproof */
+							 false, /* isStrict */
+							 PROVOLATILE_IMMUTABLE,	/* volatility */
+							 PROPARALLEL_SAFE,	/* parallel safety */
+							 argtypes,	/* parameterTypes */
+							 PointerGetDatum(NULL),	/* allParameterTypes */
+							 PointerGetDatum(NULL),	/* parameterModes */
+							 PointerGetDatum(NULL),	/* parameterNames */
+							 NIL,	/* parameterDefaults */
+							 PointerGetDatum(NULL),	/* trftypes */
+							 PointerGetDatum(NULL),	/* proconfig */
+							 InvalidOid,	/* prosupport */
+							 1.0,	/* procost */
+							 0.0);	/* prorows */
+	/*
+	 * Make the constructor 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);
+	pfree(argtypes);
+
+	/*
+	 * 1-arg constructor - for casts
+	 *
+	 * In theory we shouldn't need both this and the vararg (n-arg) constructor,
+	 * but having a separate 1-arg function lets us define casts against it.
+	 */
+	argtypes = buildoidvector(&rangeOid, 1);
+	myself = ProcedureCreate(name,	/* name: same as multirange type */
+							 namespace,
+							 false,	/* replace */
+							 false,	/* returns set */
+							 multirangeOid,	/* return type */
+							 BOOTSTRAP_SUPERUSERID,	/* proowner */
+							 INTERNALlanguageId,	/* language */
+							 F_FMGR_INTERNAL_VALIDATOR,
+							 "multirange_constructor1",	/* prosrc */
+							 NULL,	/* probin */
+							 PROKIND_FUNCTION,
+							 false,	/* security_definer */
+							 false,	/* leakproof */
+							 true, /* isStrict */
+							 PROVOLATILE_IMMUTABLE,	/* volatility */
+							 PROPARALLEL_SAFE,	/* parallel safety */
+							 argtypes,	/* parameterTypes */
+							 PointerGetDatum(NULL),	/* allParameterTypes */
+							 PointerGetDatum(NULL),	/* parameterModes */
+							 PointerGetDatum(NULL),	/* parameterNames */
+							 NIL,	/* parameterDefaults */
+							 PointerGetDatum(NULL),	/* trftypes */
+							 PointerGetDatum(NULL),	/* proconfig */
+							 InvalidOid,	/* prosupport */
+							 1.0,	/* procost */
+							 0.0);	/* prorows */
+	/* ditto */
+	recordDependencyOn(&myself, &referenced, DEPENDENCY_INTERNAL);
+	pfree(argtypes);
+	*castFuncOid = myself.objectId;
+
+	/* n-arg constructor - vararg */
+	argtypes = buildoidvector(&rangeArrayOid, 1);
+	allParamTypes = ObjectIdGetDatum(rangeArrayOid);
+	allParameterTypes = construct_array(&allParamTypes,
+										1, OIDOID,
+										sizeof(Oid), true, 'i');
+	paramModes = CharGetDatum(FUNC_PARAM_VARIADIC);
+	parameterModes = construct_array(&paramModes, 1, CHAROID,
+									 1, true, 'c');
+	myself = ProcedureCreate(name,	/* name: same as multirange type */
+							 namespace,
+							 false,	/* replace */
+							 false,	/* returns set */
+							 multirangeOid,	/* return type */
+							 BOOTSTRAP_SUPERUSERID,	/* proowner */
+							 INTERNALlanguageId,	/* language */
+							 F_FMGR_INTERNAL_VALIDATOR,
+							 "multirange_constructor2",	/* prosrc */
+							 NULL,	/* probin */
+							 PROKIND_FUNCTION,
+							 false,	/* security_definer */
+							 false,	/* leakproof */
+							 false, /* isStrict */
+							 PROVOLATILE_IMMUTABLE,	/* volatility */
+							 PROPARALLEL_SAFE,	/* parallel safety */
+							 argtypes,	/* parameterTypes */
+							 PointerGetDatum(allParameterTypes),	/* allParameterTypes */
+							 PointerGetDatum(parameterModes),	/* parameterModes */
+							 PointerGetDatum(NULL),	/* parameterNames */
+							 NIL,	/* parameterDefaults */
+							 PointerGetDatum(NULL),	/* trftypes */
+							 PointerGetDatum(NULL),	/* proconfig */
+							 InvalidOid,	/* prosupport */
+							 1.0,	/* procost */
+							 0.0);	/* prorows */
+	/* ditto */
+	recordDependencyOn(&myself, &referenced, DEPENDENCY_INTERNAL);
+	pfree(argtypes);
+	pfree(allParameterTypes);
+	pfree(parameterModes);
+}
 
 /*
  * Find suitable I/O functions for a type.
@@ -2045,6 +2287,72 @@ AssignTypeArrayOid(void)
 	return type_array_oid;
 }
 
+/*
+ *	AssignTypeMultirangeOid
+ *
+ *	Pre-assign the range type's multirange OID for use in pg_type.oid
+ */
+Oid
+AssignTypeMultirangeOid(void)
+{
+	Oid			type_multirange_oid;
+
+	/* Use binary-upgrade override for pg_type.oid? */
+	if (IsBinaryUpgrade)
+	{
+		if (!OidIsValid(binary_upgrade_next_mrng_pg_type_oid))
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+					 errmsg("pg_type multirange OID value not set when in binary upgrade mode")));
+
+		type_multirange_oid = binary_upgrade_next_mrng_pg_type_oid;
+		binary_upgrade_next_mrng_pg_type_oid = InvalidOid;
+	}
+	else
+	{
+		Relation	pg_type = table_open(TypeRelationId, AccessShareLock);
+
+		type_multirange_oid = GetNewOidWithIndex(pg_type, TypeOidIndexId,
+												 Anum_pg_type_oid);
+		table_close(pg_type, AccessShareLock);
+	}
+
+	return type_multirange_oid;
+}
+
+/*
+ *	AssignTypeMultirangeArrayOid
+ *
+ *	Pre-assign the range type's multirange array OID for use in pg_type.typarray
+ */
+Oid
+AssignTypeMultirangeArrayOid(void)
+{
+	Oid			type_multirange_array_oid;
+
+	/* Use binary-upgrade override for pg_type.oid? */
+	if (IsBinaryUpgrade)
+	{
+		if (!OidIsValid(binary_upgrade_next_mrng_array_pg_type_oid))
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+					 errmsg("pg_type multirange array OID value not set when in binary upgrade mode")));
+
+		type_multirange_array_oid = binary_upgrade_next_mrng_array_pg_type_oid;
+		binary_upgrade_next_mrng_array_pg_type_oid = InvalidOid;
+	}
+	else
+	{
+		Relation	pg_type = table_open(TypeRelationId, AccessShareLock);
+
+		type_multirange_array_oid = GetNewOidWithIndex(pg_type, TypeOidIndexId,
+													   Anum_pg_type_oid);
+		table_close(pg_type, AccessShareLock);
+	}
+
+	return type_multirange_array_oid;
+}
+
 
 /*-------------------------------------------------------------------
  * DefineCompositeType
diff --git a/src/backend/executor/functions.c b/src/backend/executor/functions.c
index 1c387a952e..e26822361c 100644
--- a/src/backend/executor/functions.c
+++ b/src/backend/executor/functions.c
@@ -1693,7 +1693,8 @@ check_sql_fn_retval(List *queryTreeList,
 	if (fn_typtype == TYPTYPE_BASE ||
 		fn_typtype == TYPTYPE_DOMAIN ||
 		fn_typtype == TYPTYPE_ENUM ||
-		fn_typtype == TYPTYPE_RANGE)
+		fn_typtype == TYPTYPE_RANGE ||
+		fn_typtype == TYPTYPE_MULTIRANGE)
 	{
 		/*
 		 * For scalar-type returns, the target list must have exactly one
diff --git a/src/backend/parser/parse_coerce.c b/src/backend/parser/parse_coerce.c
index 1b11cf731c..04cbffd7c2 100644
--- a/src/backend/parser/parse_coerce.c
+++ b/src/backend/parser/parse_coerce.c
@@ -189,19 +189,21 @@ coerce_type(ParseState *pstate, Node *node,
 	if (targetTypeId == ANYARRAYOID ||
 		targetTypeId == ANYENUMOID ||
 		targetTypeId == ANYRANGEOID ||
+		targetTypeId == ANYMULTIRANGEOID ||
 		targetTypeId == ANYCOMPATIBLEARRAYOID ||
-		targetTypeId == ANYCOMPATIBLERANGEOID)
+		targetTypeId == ANYCOMPATIBLERANGEOID ||
+		targetTypeId == ANYCOMPATIBLEMULTIRANGEOID)
 	{
 		/*
 		 * 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 the pseudotype's input
-		 * function, 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
+		 * the pseudotype's input function, 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
@@ -1532,8 +1534,8 @@ coerce_to_common_type(ParseState *pstate, Node *node,
  * 1) All arguments declared ANYELEMENT must have the same datatype.
  * 2) All arguments declared ANYARRAY must have the same datatype,
  *	  which must be a varlena array type.
- * 3) All arguments declared ANYRANGE must have the same datatype,
- *	  which must be a range type.
+ * 3) All arguments declared ANYRANGE or ANYMULTIRANGE must be a range or
+ *	  multirange type, all derived from the same base datatype.
  * 4) If there are arguments of more than one of these polymorphic types,
  *	  the array element type and/or range subtype must be the same as each
  *	  other and the same as the ANYELEMENT type.
@@ -1548,8 +1550,8 @@ coerce_to_common_type(ParseState *pstate, Node *node,
  *	  to a common supertype (chosen as per select_common_type's rules).
  *	  ANYCOMPATIBLENONARRAY works like ANYCOMPATIBLE but also requires the
  *	  common supertype to not be an array.  If there are ANYCOMPATIBLEARRAY
- *	  or ANYCOMPATIBLERANGE arguments, their element types or subtypes are
- *	  included while making the choice of common supertype.
+ *	  or ANYCOMPATIBLERANGE or ANYCOMPATIBLEMULTIRANGE arguments, their element
+ *	  types or subtypes are included while making the choice of common supertype.
  * 8) The resolved type of ANYCOMPATIBLEARRAY arguments will be the array
  *	  type over the common supertype (which might not be the same array type
  *	  as any of the original arrays).
@@ -1557,6 +1559,10 @@ coerce_to_common_type(ParseState *pstate, Node *node,
  *	  (after domain flattening), since we have no preference rule that would
  *	  let us choose one over another.  Furthermore, that range's subtype
  *	  must exactly match the common supertype chosen by rule 7.
+ * 10) All ANYCOMPATIBLEMULTIRANGE arguments must be the exact same multirange
+ *	  type (after domain flattening), since we have no preference rule that would
+ *	  let us choose one over another.  Furthermore, that multirange's range's
+ *	  subtype must exactly match the common supertype chosen by rule 7.
  *
  * Domains over arrays match ANYARRAY, and are immediately flattened to their
  * base type.  (Thus, for example, we will consider it a match if one ANYARRAY
@@ -1565,7 +1571,9 @@ coerce_to_common_type(ParseState *pstate, Node *node,
  * for ANYCOMPATIBLEARRAY and ANYCOMPATIBLENONARRAY.
  *
  * Similarly, domains over ranges match ANYRANGE or ANYCOMPATIBLERANGE,
- * and are immediately flattened to their base type.
+ * and are immediately flattened to their base type, and domains over
+ * multiranges match ANYMULTIRANGE or ANYCOMPATIBLEMULTIRANGE and are immediately
+ * flattened to their base type.
  *
  * Note that domains aren't currently considered to match ANYENUM,
  * even if their base type would match.
@@ -1583,8 +1591,12 @@ check_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			anycompatible_range_typeid = InvalidOid;
 	Oid			anycompatible_range_typelem = InvalidOid;
+	Oid			anycompatible_multirange_typeid = InvalidOid;
+	Oid			anycompatible_multirange_typelem = InvalidOid;
+	Oid			range_typelem = InvalidOid;
 	bool		have_anynonarray = false;
 	bool		have_anyenum = false;
 	bool		have_anycompatible_nonarray = false;
@@ -1633,6 +1645,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;
+		}
 		else if (decl_type == ANYCOMPATIBLEOID ||
 				 decl_type == ANYCOMPATIBLENONARRAYOID)
 		{
@@ -1677,6 +1698,42 @@ check_generic_type_consistency(const Oid *actual_arg_types,
 				anycompatible_actual_types[n_anycompatible_args++] = anycompatible_range_typelem;
 			}
 		}
+		else if (decl_type == ANYCOMPATIBLEMULTIRANGEOID)
+		{
+			if (actual_type == UNKNOWNOID)
+				continue;
+			actual_type = getBaseType(actual_type); /* flatten domains */
+			if (OidIsValid(anycompatible_multirange_typeid))
+			{
+				/* All ANYCOMPATIBLEMULTIRANGE arguments must be the same type */
+				if (anycompatible_multirange_typeid != actual_type)
+					return false;
+			}
+			else
+			{
+				anycompatible_multirange_typeid = actual_type;
+				anycompatible_multirange_typelem = get_range_multirange_subtype(actual_type);
+				if (!OidIsValid(anycompatible_multirange_typelem))
+					return false;	/* not a multirange type */
+
+				if (OidIsValid(anycompatible_range_typeid))
+				{
+					/* ANYCOMPATIBLEMULTIRANGE and ANYCOMPATIBLERANGE arguments must match */
+					if (anycompatible_range_typeid != anycompatible_multirange_typelem)
+						return false;
+				}
+				else
+				{
+					anycompatible_range_typeid = anycompatible_multirange_typelem;
+					anycompatible_range_typelem = get_range_subtype(anycompatible_range_typeid);
+					if (!OidIsValid(anycompatible_range_typelem))
+						return false;	/* not a range type */
+				}
+				/* collect the subtype for common-supertype choice */
+				anycompatible_actual_types[n_anycompatible_args++] =
+					anycompatible_range_typelem;
+			}
+		}
 	}
 
 	/* Get the element type based on the array type, if we have one */
@@ -1723,8 +1780,6 @@ check_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))
 	{
-		Oid			range_typelem;
-
 		range_typelem = get_range_subtype(range_typeid);
 		if (!OidIsValid(range_typelem))
 			return false;		/* should be a range, but isn't */
@@ -1743,6 +1798,45 @@ 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))
+	{
+		Oid			multirange_typelem;
+
+		multirange_typelem = get_range_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 */
+		}
+		else if (multirange_typelem != range_typeid)
+		{
+			/* otherwise, they better match */
+			return false;
+		}
+
+		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 */
@@ -1781,8 +1875,10 @@ check_generic_type_consistency(const Oid *actual_arg_types,
 		}
 
 		/*
-		 * the anycompatible type must exactly match the range element type,
-		 * if we were able to identify one
+		 * The anycompatible type must exactly match the range element type,
+		 * if we were able to identify one. This checks compatibility for
+		 * anycompatiblemultirange too since that also sets
+		 * anycompatible_range_typelem above.
 		 */
 		if (OidIsValid(anycompatible_range_typelem) &&
 			anycompatible_range_typelem != anycompatible_typeid)
@@ -1821,21 +1917,27 @@ check_generic_type_consistency(const Oid *actual_arg_types,
  *	  argument's actual type as the function's return type.
  * 2) If return type is ANYARRAY, and any argument is ANYARRAY, use the
  *	  argument's actual type as the function's return type.
- * 3) Similarly, if return type is ANYRANGE, and any argument is ANYRANGE,
- *	  use the argument's actual type as the function's return type.
- * 4) Otherwise, if return type is ANYELEMENT or ANYARRAY, and there is
+ * 3) Similarly, if return type is ANYRANGE or ANYMULTIRANGE, and any
+ *	  argument is ANYRANGE or ANYMULTIRANGE, use that argument's
+ *	  actual type, range type or multirange type as the function's return
+ *	  type.
+ * 4) Otherwise, if return type is ANYMULTIRANGE, and any argument is
+ *	  ANYMULTIRANGE, use the argument's actual type as the function's return
+ *	  type. Or if any argument is ANYRANGE, use its multirange type as the
+ *	  function's return type.
+ * 5) Otherwise, if return type is ANYELEMENT or ANYARRAY, and there is
  *	  at least one ANYELEMENT, ANYARRAY, or ANYRANGE input, deduce the
  *	  return type from those inputs, or throw error if we can't.
- * 5) Otherwise, if return type is ANYRANGE, throw error.  (We have no way to
- *	  select a specific range type if the arguments don't include ANYRANGE.)
- * 6) ANYENUM is treated the same as ANYELEMENT except that if it is used
+ * 6) Otherwise, if return type is ANYRANGE or ANYMULTIRANGE, throw error.
+ *	  (We have no way to select a specific range type if the arguments don't
+ *	  include ANYRANGE.)
  *	  (alone or in combination with plain ANYELEMENT), we add the extra
  *	  condition that the ANYELEMENT type must be an enum.
- * 7) ANYNONARRAY is treated the same as ANYELEMENT except that if it is used,
+ * 8) ANYNONARRAY is treated the same as ANYELEMENT except that if it is used,
  *	  we add the extra condition that the ANYELEMENT type must not be an array.
  *	  (This is a no-op if used in combination with ANYARRAY or ANYENUM, but
  *	  is an extra restriction if not.)
- * 8) ANYCOMPATIBLE, ANYCOMPATIBLEARRAY, ANYCOMPATIBLENONARRAY, and
+ * 9) ANYCOMPATIBLE, ANYCOMPATIBLEARRAY, ANYCOMPATIBLENONARRAY, and
  *	  ANYCOMPATIBLERANGE are handled by resolving the common supertype
  *	  of those arguments (or their element types/subtypes, for array and range
  *	  inputs), and then coercing all those arguments to the common supertype,
@@ -1889,15 +1991,21 @@ 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			anycompatible_typeid = InvalidOid;
 	Oid			anycompatible_array_typeid = InvalidOid;
 	Oid			anycompatible_range_typeid = InvalidOid;
 	Oid			anycompatible_range_typelem = InvalidOid;
+	Oid			anycompatible_multirange_typeid = InvalidOid;
+	Oid			anycompatible_multirange_typelem = InvalidOid;
+	Oid			range_typelem;
+	Oid			multirange_typelem;
 	bool		have_anynonarray = (rettype == ANYNONARRAYOID);
 	bool		have_anyenum = (rettype == ANYENUMOID);
 	bool		have_anycompatible_nonarray = (rettype == ANYCOMPATIBLENONARRAYOID);
 	bool		have_anycompatible_array = (rettype == ANYCOMPATIBLEARRAYOID);
 	bool		have_anycompatible_range = (rettype == ANYCOMPATIBLERANGEOID);
+	bool		have_anycompatible_multirange = (rettype == ANYCOMPATIBLEMULTIRANGEOID);
 	int			n_poly_args = 0;	/* this counts all family-1 arguments */
 	int			n_anycompatible_args = 0;	/* this counts only non-unknowns */
 	Oid			anycompatible_actual_types[FUNC_MAX_ARGS];
@@ -1977,6 +2085,26 @@ enforce_generic_type_consistency(const Oid *actual_arg_types,
 								   format_type_be(actual_type))));
 			range_typeid = actual_type;
 		}
+		else if (decl_type == ANYMULTIRANGEOID)
+		{
+			n_poly_args++;
+			if (actual_type == UNKNOWNOID)
+			{
+				have_poly_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;
+		}
 		else if (decl_type == ANYCOMPATIBLEOID ||
 				 decl_type == ANYCOMPATIBLENONARRAYOID)
 		{
@@ -2045,6 +2173,41 @@ enforce_generic_type_consistency(const Oid *actual_arg_types,
 				anycompatible_actual_types[n_anycompatible_args++] = anycompatible_range_typelem;
 			}
 		}
+		else if (decl_type == ANYCOMPATIBLEMULTIRANGEOID)
+		{
+			have_poly_anycompatible = true;
+			have_anycompatible_multirange = true;
+			if (actual_type == UNKNOWNOID)
+				continue;
+			if (allow_poly && decl_type == actual_type)
+				continue;		/* no new information here */
+			actual_type = getBaseType(actual_type); /* flatten domains */
+			if (OidIsValid(anycompatible_multirange_typeid))
+			{
+				/* All ANYCOMPATIBLEMULTIRANGE arguments must be the same type */
+				if (anycompatible_multirange_typeid != actual_type)
+					ereport(ERROR,
+							(errcode(ERRCODE_DATATYPE_MISMATCH),
+							 errmsg("arguments declared \"anycompatiblemultirange\" are not all alike"),
+							 errdetail("%s versus %s",
+									   format_type_be(anycompatible_multirange_typeid),
+									   format_type_be(actual_type))));
+			}
+			else
+			{
+				anycompatible_multirange_typeid = actual_type;
+				anycompatible_multirange_typelem = get_range_multirange_subtype(actual_type);
+				anycompatible_range_typelem = get_range_subtype(anycompatible_multirange_typelem);
+				if (!OidIsValid(anycompatible_multirange_typelem))
+					ereport(ERROR,
+							(errcode(ERRCODE_DATATYPE_MISMATCH),
+							 errmsg("argument declared %s is not a multirange type but type %s",
+									"anycompatiblemultirange",
+									format_type_be(actual_type))));
+				/* collect the subtype for common-supertype choice */
+				anycompatible_actual_types[n_anycompatible_args++] = anycompatible_range_typelem;
+			}
+		}
 	}
 
 	/*
@@ -2113,8 +2276,6 @@ 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))
 		{
-			Oid			range_typelem;
-
 			range_typelem = get_range_subtype(range_typeid);
 			if (!OidIsValid(range_typelem))
 				ereport(ERROR,
@@ -2143,6 +2304,61 @@ enforce_generic_type_consistency(const Oid *actual_arg_types,
 								   format_type_be(elem_typeid))));
 			}
 		}
+		else
+			range_typelem = InvalidOid;
+
+		/* Get the element type based on the multirange type, if we have one */
+		if (OidIsValid(multirange_typeid))
+		{
+			multirange_typelem = get_range_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))));
+			}
+
+			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 */
+				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))));
+			}
+		}
+		else
+			multirange_typelem = InvalidOid;
 
 		if (!OidIsValid(elem_typeid))
 		{
@@ -2151,6 +2367,7 @@ enforce_generic_type_consistency(const Oid *actual_arg_types,
 				elem_typeid = ANYELEMENTOID;
 				array_typeid = ANYARRAYOID;
 				range_typeid = ANYRANGEOID;
+				multirange_typeid = ANYMULTIRANGEOID;
 			}
 			else
 			{
@@ -2250,6 +2467,7 @@ enforce_generic_type_consistency(const Oid *actual_arg_types,
 				anycompatible_typeid = ANYCOMPATIBLEOID;
 				anycompatible_array_typeid = ANYCOMPATIBLEARRAYOID;
 				anycompatible_range_typeid = ANYCOMPATIBLERANGEOID;
+				anycompatible_multirange_typeid = ANYCOMPATIBLEMULTIRANGEOID;
 			}
 			else
 			{
@@ -2281,6 +2499,8 @@ enforce_generic_type_consistency(const Oid *actual_arg_types,
 				declared_arg_types[j] = anycompatible_array_typeid;
 			else if (decl_type == ANYCOMPATIBLERANGEOID)
 				declared_arg_types[j] = anycompatible_range_typeid;
+			else if (decl_type == ANYCOMPATIBLEMULTIRANGEOID)
+				declared_arg_types[j] = anycompatible_multirange_typeid;
 		}
 	}
 
@@ -2331,6 +2551,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;
+			}
 		}
 	}
 
@@ -2367,6 +2598,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 ANYCOMPATIBLE use the appropriate type */
 	if (rettype == ANYCOMPATIBLEOID ||
 		rettype == ANYCOMPATIBLENONARRAYOID)
@@ -2401,6 +2648,17 @@ enforce_generic_type_consistency(const Oid *actual_arg_types,
 		return anycompatible_range_typeid;
 	}
 
+	/* if we return ANYCOMPATIBLEMULTIRANGE use the appropriate argument type */
+	if (rettype == ANYCOMPATIBLEMULTIRANGEOID)
+	{
+		/* this error is unreachable if the function signature is valid: */
+		if (!OidIsValid(anycompatible_multirange_typeid))
+			ereport(ERROR,
+					(errcode(ERRCODE_DATATYPE_MISMATCH),
+					 errmsg_internal("could not identify anycompatiblemultirange type")));
+		return anycompatible_multirange_typeid;
+	}
+
 	/* we don't return a generic type; send back the original return type */
 	return rettype;
 }
@@ -2418,20 +2676,37 @@ check_valid_polymorphic_signature(Oid ret_type,
 								  const Oid *declared_arg_types,
 								  int nargs)
 {
-	if (ret_type == ANYRANGEOID || ret_type == ANYCOMPATIBLERANGEOID)
+	if (ret_type == ANYRANGEOID || ret_type == ANYMULTIRANGEOID)
 	{
 		/*
-		 * ANYRANGE requires an ANYRANGE input, else we can't tell which of
-		 * several range types with the same element type to use.  Likewise
-		 * for ANYCOMPATIBLERANGE.
+		 * ANYRANGE and ANYMULTIRANGE require an ANYRANGE or ANYMULTIRANGE input,
+		 * else we can't tell which of several range types with the same element
+		 * type to use.
 		 */
 		for (int i = 0; i < nargs; i++)
 		{
-			if (declared_arg_types[i] == ret_type)
+			if (declared_arg_types[i] == ANYRANGEOID ||
+				declared_arg_types[i] == ANYMULTIRANGEOID)
 				return NULL;	/* OK */
 		}
-		return psprintf(_("A result of type %s requires at least one input of type %s."),
-						format_type_be(ret_type), format_type_be(ret_type));
+		return psprintf(_("A result of type %s requires at least one input of type anyrange or anymultirange."),
+						format_type_be(ret_type));
+	}
+	else if (ret_type == ANYCOMPATIBLERANGEOID || ret_type == ANYCOMPATIBLEMULTIRANGEOID)
+	{
+		/*
+		 * ANYCOMPATIBLERANGE and ANYCOMPATIBLEMULTIRANGE require an ANYCOMPATIBLERANGE or
+		 * ANYCOMPATIBLEMULTIRANGE input, else we can't tell which of several range types
+		 * with the same element type to use.
+		 */
+		for (int i = 0; i < nargs; i++)
+		{
+			if (declared_arg_types[i] == ANYCOMPATIBLERANGEOID ||
+				declared_arg_types[i] == ANYCOMPATIBLEMULTIRANGEOID)
+				return NULL;	/* OK */
+		}
+		return psprintf(_("A result of type %s requires at least one input of type anycompatiblerange or anycompatiblemultirange."),
+						format_type_be(ret_type));
 	}
 	else if (IsPolymorphicTypeFamily1(ret_type))
 	{
@@ -2442,7 +2717,7 @@ check_valid_polymorphic_signature(Oid ret_type,
 				return NULL;	/* OK */
 		}
 		/* Keep this list in sync with IsPolymorphicTypeFamily1! */
-		return psprintf(_("A result of type %s requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange."),
+		return psprintf(_("A result of type %s requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange."),
 						format_type_be(ret_type));
 	}
 	else if (IsPolymorphicTypeFamily2(ret_type))
@@ -2594,6 +2869,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 || targettype == ANYCOMPATIBLEMULTIRANGEOID)
+		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 5d2aca8cfe..1a08218de9 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..bd3cf3dc93
--- /dev/null
+++ b/src/backend/utils/adt/multirangetypes.c
@@ -0,0 +1,2186 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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/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 */
+	FmgrInfo	typioproc;		/* range type's I/O proc */
+	Oid			typioparam;		/* range type's I/O parameter */
+} 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 mltrngtypid,
+												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;
+	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 = NULL;
+	int32		range_str_len;
+	char	   *range_str_copy;
+
+	cache = get_multirange_io_data(fcinfo, mltrngtypoid, IOFunc_input);
+	rangetyp = cache->typcache->rngtype;
+
+	/* 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->typioproc,
+																 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->typioproc, 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->typioproc,
+														   &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;
+
+		range = RangeTypePGetDatum(ranges[i]);
+		range = PointerGetDatum(SendFunctionCall(&cache->typioproc, range));
+
+		pq_sendint32(buf, VARSIZE(range) - VARHDRSZ);
+		pq_sendbytes(buf, VARDATA(range), VARSIZE(range) - VARHDRSZ);
+	}
+
+	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)
+	{
+		Oid			typiofunc;
+		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,
+						 &typiofunc);
+
+		if (!OidIsValid(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(typiofunc, &cache->typioproc,
+					  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;
+	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++)
+		bytelen += MAXALIGN(VARSIZE(ranges[i]));
+
+	/* 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++)
+	{
+		memcpy(ptr, ranges[i], VARSIZE(ranges[i]));
+		ptr += MAXALIGN(VARSIZE(ranges[i]));
+	}
+
+	return multirange;
+}
+
+/*
+ * Converts a list of arbitrary ranges 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_constructor2(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,
+				(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+				 errmsg("multirange values cannot contain NULL members")));
+
+	rangeArray = PG_GETARG_ARRAYTYPE_P(0);
+
+	dims = ARR_NDIM(rangeArray);
+	if (dims > 1)
+		ereport(ERROR,
+				(errcode(ERRCODE_CARDINALITY_VIOLATION),
+				 errmsg("multiranges cannot be constructed from multi-dimensional arrays")));
+
+	rngtypid = ARR_ELEMTYPE(rangeArray);
+	if (rngtypid != rangetyp->type_id)
+		ereport(ERROR,
+				(errcode(ERRCODE_DATATYPE_MISMATCH),
+				 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,
+						(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+						 errmsg("multirange values cannot contain NULL members")));
+
+			/* make_multirange will do its own copy */
+			ranges[i] = DatumGetRangeTypeP(elements[i]);
+		}
+	}
+
+	PG_RETURN_MULTIRANGE_P(make_multirange(mltrngtypid, rangetyp, range_count, ranges));
+}
+
+/*
+ * Construct multirange value from a single range.
+ * It'd be nice if we could just use multirange_constructor2
+ * for this case, but we need a non-variadic single-arg function
+ * to let us define a CAST from a range to its multirange.
+ */
+Datum
+multirange_constructor1(PG_FUNCTION_ARGS)
+{
+	Oid			mltrngtypid = get_fn_expr_rettype(fcinfo->flinfo);
+	Oid			rngtypid;
+	TypeCacheEntry *typcache;
+	TypeCacheEntry *rangetyp;
+	RangeType  *range;
+
+	typcache = multirange_get_typcache(fcinfo, mltrngtypid);
+	rangetyp = typcache->rngtype;
+
+	/*
+	 * These checks should be guaranteed by our signature, but let's do them
+	 * just in case.
+	 */
+
+	if (PG_ARGISNULL(0))
+				ereport(ERROR,
+						(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+						 errmsg("multirange values cannot contain NULL members")));
+
+	range = PG_GETARG_RANGE_P(0);
+
+	/* Make sure the range type matches. */
+	rngtypid = RangeTypeGetOid(range);
+	if (rngtypid != rangetyp->type_id)
+		ereport(ERROR,
+				(errcode(ERRCODE_DATATYPE_MISMATCH),
+				 errmsg("type %u does not match constructor type", rngtypid)));
+
+	PG_RETURN_MULTIRANGE_P(make_multirange(mltrngtypid, rangetyp, 1, &range));
+}
+
+/*
+ * 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
+		elog(ERROR,		/* can't happen */
+			 "niladic multirange constructor must not receive arguments");
+}
+
+
+/* multirange, multirange -> multirange type functions */
+
+/* multirange union */
+Datum
+multirange_union(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
+	MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count1;
+	int32		range_count2;
+	int32		range_count3;
+	RangeType **ranges1;
+	RangeType **ranges2;
+	RangeType **ranges3;
+
+	if (MultirangeIsEmpty(mr1))
+		PG_RETURN_MULTIRANGE_P(mr2);
+	if (MultirangeIsEmpty(mr2))
+		PG_RETURN_MULTIRANGE_P(mr1);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr1));
+
+	multirange_deserialize(mr1, &range_count1, &ranges1);
+	multirange_deserialize(mr2, &range_count2, &ranges2);
+
+	range_count3 = range_count1 + range_count2;
+	ranges3 = palloc0(range_count3 * sizeof(RangeType *));
+	memcpy(ranges3, ranges1, range_count1 * sizeof(RangeType *));
+	memcpy(ranges3 + range_count1, ranges2, range_count2 * sizeof(RangeType *));
+	PG_RETURN_MULTIRANGE_P(make_multirange(typcache->type_id, typcache->rngtype,
+										   range_count3, ranges3));
+}
+
+/* multirange minus */
+Datum
+multirange_minus(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
+	MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
+	Oid			mltrngtypoid = MultirangeTypeGetOid(mr1);
+	TypeCacheEntry *typcache;
+	TypeCacheEntry *rangetyp;
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+
+	typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
+	rangetyp = typcache->rngtype;
+
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		PG_RETURN_MULTIRANGE_P(mr1);
+
+	multirange_deserialize(mr1, &range_count1, &ranges1);
+	multirange_deserialize(mr2, &range_count2, &ranges2);
+
+	PG_RETURN_MULTIRANGE_P(multirange_minus_internal(mltrngtypoid,
+													 rangetyp,
+													 range_count1,
+													 ranges1,
+													 range_count2,
+													 ranges2));
+}
+
+MultirangeType *
+multirange_minus_internal(Oid mltrngtypoid, TypeCacheEntry *rangetyp,
+						  int32 range_count1, RangeType **ranges1,
+						  int32 range_count2, RangeType **ranges2)
+{
+	RangeType  *r1;
+	RangeType  *r2;
+	RangeType **ranges3;
+	int32		range_count3;
+	int32		i1;
+	int32		i2;
+
+	/*
+	 * Worst case: every range in ranges1 makes a different cut to some range
+	 * in ranges2.
+	 */
+	ranges3 = palloc0((range_count1 + range_count2) * sizeof(RangeType *));
+	range_count3 = 0;
+
+	/*
+	 * For each range in mr1, keep subtracting until it's gone or the ranges
+	 * in mr2 have passed it. After a subtraction we assign what's left back
+	 * to r1. The parallel progress through mr1 and mr2 is similar to
+	 * multirange_overlaps_multirange_internal.
+	 */
+	r2 = ranges2[0];
+	for (i1 = 0, i2 = 0; i1 < range_count1; i1++)
+	{
+		r1 = ranges1[i1];
+
+		/* Discard r2s while r2 << r1 */
+		while (r2 != NULL && range_before_internal(rangetyp, r2, r1))
+		{
+			r2 = ++i2 >= range_count2 ? NULL : ranges2[i2];
+		}
+
+		while (r2 != NULL)
+		{
+			if (range_split_internal(rangetyp, r1, r2, &ranges3[range_count3], &r1))
+			{
+				/*
+				 * If r2 takes a bite out of the middle of r1, we need two
+				 * outputs
+				 */
+				range_count3++;
+				r2 = ++i2 >= range_count2 ? NULL : ranges2[i2];
+
+			}
+			else if (range_overlaps_internal(rangetyp, r1, r2))
+			{
+				/*
+				 * If r2 overlaps r1, replace r1 with r1 - r2.
+				 */
+				r1 = range_minus_internal(rangetyp, r1, r2);
+
+				/*
+				 * If r2 goes past r1, then we need to stay with it, in case
+				 * it hits future r1s. Otherwise we need to keep r1, in case
+				 * future r2s hit it. Since we already subtracted, there's no
+				 * point in using the overright/overleft calls.
+				 */
+				if (RangeIsEmpty(r1) || range_before_internal(rangetyp, r1, r2))
+					break;
+				else
+					r2 = ++i2 >= range_count2 ? NULL : ranges2[i2];
+
+			}
+			else
+			{
+				/*
+				 * This and all future r2s are past r1, so keep them. Also
+				 * assign whatever is left of r1 to the result.
+				 */
+				break;
+			}
+		}
+
+		/*
+		 * Nothing else can remove anything from r1, so keep it. Even if r1 is
+		 * empty here, make_multirange will remove it.
+		 */
+		ranges3[range_count3++] = r1;
+	}
+
+	return make_multirange(mltrngtypoid, rangetyp, range_count3, ranges3);
+}
+
+/* multirange intersection */
+Datum
+multirange_intersect(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
+	MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
+	Oid			mltrngtypoid = MultirangeTypeGetOid(mr1);
+	TypeCacheEntry *typcache;
+	TypeCacheEntry *rangetyp;
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+
+	typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
+	rangetyp = typcache->rngtype;
+
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		PG_RETURN_MULTIRANGE_P(make_empty_multirange(mltrngtypoid, rangetyp));
+
+	multirange_deserialize(mr1, &range_count1, &ranges1);
+	multirange_deserialize(mr2, &range_count2, &ranges2);
+
+	PG_RETURN_MULTIRANGE_P(multirange_intersect_internal(mltrngtypoid,
+														 rangetyp,
+														 range_count1,
+														 ranges1,
+														 range_count2,
+														 ranges2));
+}
+
+MultirangeType *
+multirange_intersect_internal(Oid mltrngtypoid, TypeCacheEntry *rangetyp,
+							  int32 range_count1, RangeType **ranges1,
+							  int32 range_count2, RangeType **ranges2)
+{
+	RangeType  *r1;
+	RangeType  *r2;
+	RangeType **ranges3;
+	int32		range_count3;
+	int32		i1;
+	int32		i2;
+
+	if (range_count1 == 0 || range_count2 == 0)
+		return make_multirange(mltrngtypoid, rangetyp, 0, NULL);
+
+	/*-----------------------------------------------
+	 * Worst case is a stitching pattern like this:
+	 *
+	 * mr1: --- --- --- ---
+	 * mr2:   --- --- ---
+	 * mr3:   - - - - - -
+	 *
+	 * That seems to be range_count1 + range_count2 - 1,
+	 * but one extra won't hurt.
+	 *-----------------------------------------------
+	 */
+	ranges3 = palloc0((range_count1 + range_count2) * sizeof(RangeType *));
+	range_count3 = 0;
+
+	/*
+	 * For each range in mr1, keep intersecting until the ranges in mr2 have
+	 * passed it. The parallel progress through mr1 and mr2 is similar to
+	 * multirange_minus_multirange_internal, but we don't have to assign back
+	 * to r1.
+	 */
+	r2 = ranges2[0];
+	for (i1 = 0, i2 = 0; i1 < range_count1; i1++)
+	{
+		r1 = ranges1[i1];
+
+		/* Discard r2s while r2 << r1 */
+		while (r2 != NULL && range_before_internal(rangetyp, r2, r1))
+		{
+			r2 = ++i2 >= range_count2 ? NULL : ranges2[i2];
+		}
+
+		while (r2 != NULL)
+		{
+			if (range_overlaps_internal(rangetyp, r1, r2))
+			{
+				/* Keep the overlapping part */
+				ranges3[range_count3++] = range_intersect_internal(rangetyp, r1, r2);
+
+				/* If we "used up" all of r2, go to the next one... */
+				if (range_overleft_internal(rangetyp, r2, r1))
+					r2 = ++i2 >= range_count2 ? NULL : ranges2[i2];
+
+				/* ...otherwise go to the next r1 */
+				else
+					break;
+			}
+			else
+				/* We're past r1, so move to the next one */
+				break;
+		}
+
+		/* If we're out of r2s, there can be no more intersections */
+		if (r2 == NULL)
+			break;
+	}
+
+	return make_multirange(mltrngtypoid, rangetyp, range_count3, ranges3);
+}
+
+/*
+ * range_agg_transfn: combine adjacent/overlapping ranges.
+ *
+ * All we do here is gather the input ranges into an array
+ * so that the finalfn can sort and combine them.
+ */
+Datum
+range_agg_transfn(PG_FUNCTION_ARGS)
+{
+	MemoryContext aggContext;
+	Oid			rngtypoid;
+	ArrayBuildState *state;
+
+	if (!AggCheckCallContext(fcinfo, &aggContext))
+		elog(ERROR, "range_agg_transfn called in non-aggregate context");
+
+	rngtypoid = get_fn_expr_argtype(fcinfo->flinfo, 1);
+	if (!type_is_range(rngtypoid))
+		ereport(ERROR,
+				(errcode(ERRCODE_DATATYPE_MISMATCH),
+				 errmsg("range_agg must be called with a range")));
+
+	if (PG_ARGISNULL(0))
+		state = initArrayResult(rngtypoid, aggContext, false);
+	else
+		state = (ArrayBuildState *) PG_GETARG_POINTER(0);
+
+	/* skip NULLs */
+	if (!PG_ARGISNULL(1))
+		accumArrayResult(state, PG_GETARG_DATUM(1), false, rngtypoid, aggContext);
+
+	PG_RETURN_POINTER(state);
+}
+
+/*
+ * range_agg_finalfn: use our internal array to merge touching ranges.
+ */
+Datum
+range_agg_finalfn(PG_FUNCTION_ARGS)
+{
+	MemoryContext aggContext;
+	Oid			mltrngtypoid;
+	TypeCacheEntry *typcache;
+	ArrayBuildState *state;
+	int32		range_count;
+	RangeType **ranges;
+	int			i;
+
+	if (!AggCheckCallContext(fcinfo, &aggContext))
+		elog(ERROR, "range_agg_finalfn called in non-aggregate context");
+
+	state = PG_ARGISNULL(0) ? NULL : (ArrayBuildState *) PG_GETARG_POINTER(0);
+	if (state == NULL)
+		/* This shouldn't be possible, but just in case.... */
+		PG_RETURN_NULL();
+
+	/* Also return NULL if we had zero inputs, like other aggregates */
+	range_count = state->nelems;
+	if (range_count == 0)
+		PG_RETURN_NULL();
+
+	mltrngtypoid = get_fn_expr_rettype(fcinfo->flinfo);
+	typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
+
+	ranges = palloc0(range_count * sizeof(RangeType *));
+	for (i = 0; i < range_count; i++)
+		ranges[i] = DatumGetRangeTypeP(state->dvalues[i]);
+
+	PG_RETURN_MULTIRANGE_P(make_multirange(mltrngtypoid, typcache->rngtype, range_count, ranges));
+}
+
+Datum
+multirange_intersect_agg_transfn(PG_FUNCTION_ARGS)
+{
+	MemoryContext aggContext;
+	Oid			mltrngtypoid;
+	TypeCacheEntry *typcache;
+	MultirangeType *result;
+	MultirangeType *current;
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+
+	if (!AggCheckCallContext(fcinfo, &aggContext))
+		elog(ERROR, "multirange_intersect_agg_transfn called in non-aggregate context");
+
+	mltrngtypoid = get_fn_expr_argtype(fcinfo->flinfo, 1);
+	if (!type_is_multirange(mltrngtypoid))
+		ereport(ERROR,
+				(errcode(ERRCODE_DATATYPE_MISMATCH),
+				 errmsg("range_intersect_agg must be called with a multirange")));
+
+	typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
+
+	/* strictness ensures these are non-null */
+	result = PG_GETARG_MULTIRANGE_P(0);
+	current = PG_GETARG_MULTIRANGE_P(1);
+
+	multirange_deserialize(result, &range_count1, &ranges1);
+	multirange_deserialize(current, &range_count2, &ranges2);
+
+	result = multirange_intersect_internal(mltrngtypoid,
+										   typcache->rngtype,
+										   range_count1,
+										   ranges1,
+										   range_count2,
+										   ranges2);
+	PG_RETURN_RANGE_P(result);
+}
+
+
+/* multirange -> element type functions */
+
+/* extract lower bound value */
+Datum
+multirange_lower(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+	bool		isnull;
+	Datum		result;
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_NULL();
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	result = range_lower_internal(typcache->rngtype, ranges[0], &isnull);
+
+	if (isnull)
+		PG_RETURN_NULL();
+	else
+		PG_RETURN_DATUM(result);
+}
+
+/* extract upper bound value */
+Datum
+multirange_upper(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+	bool		isnull;
+	Datum		result;
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_NULL();
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	result = range_upper_internal(typcache->rngtype, ranges[range_count - 1], &isnull);
+
+	if (isnull)
+		PG_RETURN_NULL();
+	else
+		PG_RETURN_DATUM(result);
+}
+
+
+/* multirange -> bool functions */
+
+/* is multirange empty? */
+Datum
+multirange_empty(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+
+	PG_RETURN_BOOL(mr->rangeCount == 0);
+}
+
+/* is lower bound inclusive? */
+Datum
+multirange_lower_inc(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	int32		range_count;
+	RangeType **ranges;
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_has_flag(ranges[0], RANGE_LB_INC));
+}
+
+/* is upper bound inclusive? */
+Datum
+multirange_upper_inc(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	int32		range_count;
+	RangeType **ranges;
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_has_flag(ranges[range_count - 1], RANGE_UB_INC));
+}
+
+/* is lower bound infinite? */
+Datum
+multirange_lower_inf(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	int32		range_count;
+	RangeType **ranges;
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_has_flag(ranges[0], RANGE_LB_INF));
+}
+
+/* is upper bound infinite? */
+Datum
+multirange_upper_inf(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	int32		range_count;
+	RangeType **ranges;
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_has_flag(ranges[range_count - 1], RANGE_UB_INF));
+}
+
+
+
+/* multirange, element -> bool functions */
+
+/* contains? */
+Datum
+multirange_contains_elem(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	Datum		val = PG_GETARG_DATUM(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(multirange_contains_elem_internal(typcache, mr, val));
+}
+
+/* contained by? */
+Datum
+elem_contained_by_multirange(PG_FUNCTION_ARGS)
+{
+	Datum		val = PG_GETARG_DATUM(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(multirange_contains_elem_internal(typcache, mr, val));
+}
+
+/*
+ * Test whether multirange mr contains a specific element value.
+ */
+bool
+multirange_contains_elem_internal(TypeCacheEntry *typcache, MultirangeType *mr, Datum val)
+{
+	TypeCacheEntry *rangetyp;
+	int32		range_count;
+	RangeType **ranges;
+	RangeType  *r;
+	int			i;
+
+	rangetyp = typcache->rngtype;
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	for (i = 0; i < range_count; i++)
+	{
+		r = ranges[i];
+		if (range_contains_elem_internal(rangetyp, r, val))
+			return true;
+	}
+
+	return false;
+}
+
+/* multirange, range -> bool functions */
+
+/* contains? */
+Datum
+multirange_contains_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(multirange_contains_range_internal(typcache, mr, r));
+}
+
+/* contained by? */
+Datum
+range_contained_by_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(multirange_contains_range_internal(typcache, mr, r));
+}
+
+/*
+ * Test whether multirange mr contains a specific range r.
+ */
+bool
+multirange_contains_range_internal(TypeCacheEntry *typcache, MultirangeType *mr, RangeType *r)
+{
+	TypeCacheEntry *rangetyp;
+	int32		range_count;
+	RangeType **ranges;
+	RangeType  *mrr;
+	int			i;
+
+	rangetyp = typcache->rngtype;
+
+	/*
+	 * Every multirange contains an infinite number of empty ranges, even an
+	 * empty one.
+	 */
+	if (RangeIsEmpty(r))
+		return true;
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	for (i = 0; i < range_count; i++)
+	{
+		mrr = ranges[i];
+		if (range_contains_internal(rangetyp, mrr, r))
+			return true;
+	}
+
+	return false;
+}
+
+
+/* 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));
+}
+
+/* overlaps? */
+Datum
+range_overlaps_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(range_overlaps_multirange_internal(typcache, r, mr));
+}
+
+Datum
+multirange_overlaps_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(range_overlaps_multirange_internal(typcache, r, mr));
+}
+
+Datum
+multirange_overlaps_multirange(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_overlaps_multirange_internal(typcache, mr1, mr2));
+}
+
+bool
+range_overlaps_multirange_internal(TypeCacheEntry *typcache, RangeType *r, MultirangeType *mr)
+{
+	TypeCacheEntry *rangetyp;
+	int32		range_count;
+	int32		i;
+	RangeType **ranges;
+	RangeType  *mrr;
+
+	/*
+	 * Empties never overlap, even with empties. (This seems strange since
+	 * they *do* contain each other, but we want to follow how ranges work.)
+	 */
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		return false;
+
+	rangetyp = typcache->rngtype;
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	/* Scan through mrr and once it gets to r they either overlap or not */
+	mrr = ranges[0];
+
+	/* Discard mrrs while mrr << r */
+	i = 0;
+	while (range_before_internal(rangetyp, mrr, r))
+	{
+		if (++i >= range_count)
+			return false;
+		mrr = ranges[i];
+	}
+
+	/* Now either we overlap or we passed r */
+	return range_overlaps_internal(rangetyp, mrr, r);
+}
+
+bool
+multirange_overlaps_multirange_internal(TypeCacheEntry *typcache, MultirangeType *mr1,
+										MultirangeType *mr2)
+{
+	TypeCacheEntry *rangetyp;
+	int32		range_count1;
+	int32		range_count2;
+	int32		i1;
+	int32		i2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+	RangeType  *r1;
+	RangeType  *r2;
+
+	/*
+	 * Empties never overlap, even with empties. (This seems strange since
+	 * they *do* contain each other, but we want to follow how ranges work.)
+	 */
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		return false;
+
+	rangetyp = typcache->rngtype;
+
+	multirange_deserialize(mr1, &range_count1, &ranges1);
+	multirange_deserialize(mr2, &range_count2, &ranges2);
+
+	/*
+	 * Every range in mr1 gets a chance to overlap with the ranges in mr2, but
+	 * we can use their ordering to avoid O(n^2). This is similar to
+	 * range_overlaps_multirange where r1 : r2 :: mrr : r, but there if we
+	 * don't find an overlap with r we're done, and here if we don't find an
+	 * overlap with r2 we try the next r2.
+	 */
+	r1 = ranges1[0];
+	for (i1 = 0, i2 = 0; i2 < range_count2; i2++)
+	{
+		r2 = ranges2[i2];
+
+		/* Discard r1s while r1 << r2 */
+		while (range_before_internal(rangetyp, r1, r2))
+		{
+			if (++i1 >= range_count1)
+				return false;
+			r1 = ranges1[i1];
+		}
+
+		/*
+		 * If r1 && r2, we're done, otherwise we failed to find an overlap for
+		 * r2, so go to the next one.
+		 */
+		if (range_overlaps_internal(rangetyp, r1, r2))
+			return true;
+	}
+
+	/* We looked through all of mr2 without finding an overlap */
+	return false;
+}
+
+/* does not extend to right of? */
+Datum
+range_overleft_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_overleft_internal(typcache->rngtype, r, ranges[range_count - 1]));
+}
+
+Datum
+multirange_overleft_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_overleft_internal(typcache->rngtype,
+										   ranges[range_count - 1], r));
+}
+
+Datum
+multirange_overleft_multirange(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
+	MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr1));
+
+	multirange_deserialize(mr1, &range_count1, &ranges1);
+	multirange_deserialize(mr2, &range_count2, &ranges2);
+
+	PG_RETURN_BOOL(range_overleft_internal(typcache->rngtype,
+										   ranges1[range_count1 - 1],
+										   ranges2[range_count2 - 1]));
+}
+
+/* does not extend to left of? */
+Datum
+range_overright_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_overright_internal(typcache->rngtype, r, ranges[0]));
+}
+
+Datum
+multirange_overright_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_overright_internal(typcache->rngtype, ranges[0], r));
+}
+
+Datum
+multirange_overright_multirange(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
+	MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr1));
+
+	multirange_deserialize(mr1, &range_count1, &ranges1);
+	multirange_deserialize(mr2, &range_count2, &ranges2);
+
+	PG_RETURN_BOOL(range_overright_internal(typcache->rngtype,
+											ranges1[0],
+											ranges2[0]));
+}
+
+/* contains? */
+Datum
+multirange_contains_multirange(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_contains_multirange_internal(typcache, mr1, mr2));
+}
+
+/* contained by? */
+Datum
+multirange_contained_by_multirange(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_contains_multirange_internal(typcache, mr2, mr1));
+}
+
+/*
+ * Test whether multirange mr1 contains every range from another multirange mr2.
+ */
+bool
+multirange_contains_multirange_internal(TypeCacheEntry *typcache,
+										MultirangeType *mr1, MultirangeType *mr2)
+{
+	TypeCacheEntry *rangetyp;
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+	RangeType  *r1;
+	RangeType  *r2;
+	int			i1,
+				i2;
+
+	rangetyp = typcache->rngtype;
+
+	multirange_deserialize(mr1, &range_count1, &ranges1);
+	multirange_deserialize(mr2, &range_count2, &ranges2);
+
+	/*
+	 * We follow the same logic for empties as ranges: - an empty multirange
+	 * contains an empty range/multirange. - an empty multirange can't contain
+	 * any other range/multirange. - an empty multirange is contained by any
+	 * other range/multirange.
+	 */
+
+	if (range_count2 == 0)
+		return true;
+	if (range_count1 == 0)
+		return false;
+
+	/*
+	 * Every range in mr2 must be contained by some range in mr1. To avoid
+	 * O(n^2) we walk through both ranges in tandem.
+	 */
+	r1 = ranges1[0];
+	for (i1 = 0, i2 = 0; i2 < range_count2; i2++)
+	{
+		r2 = ranges2[i2];
+
+		/* Discard r1s while r1 << r2 */
+		while (range_before_internal(rangetyp, r1, r2))
+		{
+			if (++i1 >= range_count1)
+				return false;
+			r1 = ranges1[i1];
+		}
+
+		/*
+		 * If r1 @> r2, go to the next r2, otherwise return false (since every
+		 * r1[n] and r1[n+1] must have a gap). Note this will give weird
+		 * answers if you don't canonicalize, e.g. with a custom
+		 * int2multirange {[1,1], [2,2]} there is a "gap". But that is
+		 * consistent with other range operators, e.g. '[1,1]'::int2range -|-
+		 * '[2,2]'::int2range is false.
+		 */
+		if (!range_contains_internal(rangetyp, r1, r2))
+			return false;
+	}
+
+	/* All ranges in mr2 are satisfied */
+	return true;
+}
+
+/* strictly left of? */
+Datum
+range_before_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(range_before_multirange_internal(typcache, r, mr));
+}
+
+Datum
+multirange_before_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(range_after_multirange_internal(typcache, r, mr));
+}
+
+Datum
+multirange_before_multirange(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_before_multirange_internal(typcache, mr1, mr2));
+}
+
+/* strictly right of? */
+Datum
+range_after_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(range_after_multirange_internal(typcache, r, mr));
+}
+
+Datum
+multirange_after_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(range_before_multirange_internal(typcache, r, mr));
+}
+
+Datum
+multirange_after_multirange(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_before_multirange_internal(typcache, mr2, mr1));
+}
+
+/* strictly left of? (internal version) */
+bool
+range_before_multirange_internal(TypeCacheEntry *typcache, RangeType *r,
+								 MultirangeType *mr)
+{
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		return false;
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	return range_before_internal(typcache->rngtype, r, ranges[0]);
+}
+
+bool
+multirange_before_multirange_internal(TypeCacheEntry *typcache, MultirangeType *mr1,
+									  MultirangeType *mr2)
+{
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		return false;
+
+	multirange_deserialize(mr1, &range_count1, &ranges1);
+	multirange_deserialize(mr2, &range_count2, &ranges2);
+
+	return range_before_internal(typcache->rngtype, ranges1[range_count1 - 1],
+								 ranges2[0]);
+}
+
+/* strictly right of? (internal version) */
+bool
+range_after_multirange_internal(TypeCacheEntry *typcache, RangeType *r,
+								MultirangeType *mr)
+{
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		return false;
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	return range_after_internal(typcache->rngtype, r, ranges[range_count - 1]);
+}
+
+/* adjacent to? */
+Datum
+range_adjacent_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		return false;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_adjacent_internal(typcache->rngtype, r, ranges[0]));
+}
+
+Datum
+multirange_adjacent_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		return false;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_adjacent_internal(typcache->rngtype, ranges[range_count - 1], r));
+}
+
+Datum
+multirange_adjacent_multirange(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
+	MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		return false;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr1));
+
+	multirange_deserialize(mr1, &range_count1, &ranges1);
+	multirange_deserialize(mr2, &range_count2, &ranges2);
+
+	PG_RETURN_BOOL(range_adjacent_internal(typcache->rngtype, ranges1[range_count1 - 1], ranges2[0]));
+}
+
+/* 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);
+}
+
+/* multirange -> range functions */
+
+/* Find the smallest range that includes everything in the multirange */
+Datum
+range_merge_from_multirange(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	Oid			mltrngtypoid = MultirangeTypeGetOid(mr);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_RANGE_P(make_empty_range(typcache->rngtype));
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_RANGE_P(range_union_internal(typcache->rngtype, ranges[0],
+										   ranges[range_count - 1], false));
+}
+
+/* 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/pg_upgrade_support.c b/src/backend/utils/adt/pg_upgrade_support.c
index 18f2ee8226..248ea7f44a 100644
--- a/src/backend/utils/adt/pg_upgrade_support.c
+++ b/src/backend/utils/adt/pg_upgrade_support.c
@@ -52,6 +52,28 @@ binary_upgrade_set_next_array_pg_type_oid(PG_FUNCTION_ARGS)
 }
 
 Datum
+binary_upgrade_set_next_multirange_pg_type_oid(PG_FUNCTION_ARGS)
+{
+	Oid			typoid = PG_GETARG_OID(0);
+
+	CHECK_IS_BINARY_UPGRADE;
+	binary_upgrade_next_mrng_pg_type_oid = typoid;
+
+	PG_RETURN_VOID();
+}
+
+Datum
+binary_upgrade_set_next_multirange_array_pg_type_oid(PG_FUNCTION_ARGS)
+{
+	Oid			typoid = PG_GETARG_OID(0);
+
+	CHECK_IS_BINARY_UPGRADE;
+	binary_upgrade_next_mrng_array_pg_type_oid = typoid;
+
+	PG_RETURN_VOID();
+}
+
+Datum
 binary_upgrade_set_next_toast_pg_type_oid(PG_FUNCTION_ARGS)
 {
 	Oid			typoid = PG_GETARG_OID(0);
diff --git a/src/backend/utils/adt/pseudotypes.c b/src/backend/utils/adt/pseudotypes.c
index 3d6b2f9093..99a93271fe 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"
 
 
 /*
@@ -228,6 +229,43 @@ anycompatiblerange_out(PG_FUNCTION_ARGS)
 }
 
 /*
+ * anycompatiblemultirange
+ *
+ * We may as well allow output, since multirange_out will in fact work.
+ */
+PSEUDOTYPE_DUMMY_INPUT_FUNC(anycompatiblemultirange);
+
+Datum
+anycompatiblemultirange_out(PG_FUNCTION_ARGS)
+{
+	return multirange_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
  *
  * We support void_in so that PL functions can return VOID without any
diff --git a/src/backend/utils/adt/rangetypes.c b/src/backend/utils/adt/rangetypes.c
index 01ad8bc240..0af3c67296 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
 {
@@ -431,19 +429,37 @@ range_lower(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
 	TypeCacheEntry *typcache;
+	bool		isnull;
+	Datum		result;
+
+	typcache = range_get_typcache(fcinfo, RangeTypeGetOid(r1));
+
+	result = range_lower_internal(typcache, r1, &isnull);
+
+	if (isnull)
+		PG_RETURN_NULL();
+	else
+		PG_RETURN_DATUM(result);
+}
+
+Datum
+range_lower_internal(TypeCacheEntry *typcache, const RangeType *r1, bool *isnull)
+{
 	RangeBound	lower;
 	RangeBound	upper;
 	bool		empty;
 
-	typcache = range_get_typcache(fcinfo, RangeTypeGetOid(r1));
-
 	range_deserialize(typcache, r1, &lower, &upper, &empty);
 
 	/* Return NULL if there's no finite lower bound */
 	if (empty || lower.infinite)
-		PG_RETURN_NULL();
+	{
+		*isnull = true;
+		return 0;
+	}
 
-	PG_RETURN_DATUM(lower.val);
+	*isnull = false;
+	return lower.val;
 }
 
 /* extract upper bound value */
@@ -452,19 +468,37 @@ range_upper(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
 	TypeCacheEntry *typcache;
+	bool		isnull;
+	Datum		result;
+
+	typcache = range_get_typcache(fcinfo, RangeTypeGetOid(r1));
+
+	result = range_upper_internal(typcache, r1, &isnull);
+
+	if (isnull)
+		PG_RETURN_NULL();
+	else
+		PG_RETURN_DATUM(result);
+}
+
+Datum
+range_upper_internal(TypeCacheEntry *typcache, const RangeType *r1, bool *isnull)
+{
 	RangeBound	lower;
 	RangeBound	upper;
 	bool		empty;
 
-	typcache = range_get_typcache(fcinfo, RangeTypeGetOid(r1));
-
 	range_deserialize(typcache, r1, &lower, &upper, &empty);
 
 	/* Return NULL if there's no finite upper bound */
 	if (empty || upper.infinite)
-		PG_RETURN_NULL();
+	{
+		*isnull = true;
+		return 0;
+	}
 
-	PG_RETURN_DATUM(upper.val);
+	*isnull = false;
+	return upper.val;
 }
 
 
@@ -475,9 +509,8 @@ Datum
 range_empty(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
-	char		flags = range_get_flags(r1);
 
-	PG_RETURN_BOOL(flags & RANGE_EMPTY);
+	PG_RETURN_BOOL(range_has_flag(r1, RANGE_EMPTY));
 }
 
 /* is lower bound inclusive? */
@@ -485,9 +518,8 @@ Datum
 range_lower_inc(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
-	char		flags = range_get_flags(r1);
 
-	PG_RETURN_BOOL(flags & RANGE_LB_INC);
+	PG_RETURN_BOOL(range_has_flag(r1, RANGE_LB_INC));
 }
 
 /* is upper bound inclusive? */
@@ -495,9 +527,8 @@ Datum
 range_upper_inc(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
-	char		flags = range_get_flags(r1);
 
-	PG_RETURN_BOOL(flags & RANGE_UB_INC);
+	PG_RETURN_BOOL(range_has_flag(r1, RANGE_UB_INC));
 }
 
 /* is lower bound infinite? */
@@ -505,9 +536,8 @@ Datum
 range_lower_inf(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
-	char		flags = range_get_flags(r1);
 
-	PG_RETURN_BOOL(flags & RANGE_LB_INF);
+	PG_RETURN_BOOL(range_has_flag(r1, RANGE_LB_INF));
 }
 
 /* is upper bound infinite? */
@@ -515,9 +545,8 @@ Datum
 range_upper_inf(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
-	char		flags = range_get_flags(r1);
 
-	PG_RETURN_BOOL(flags & RANGE_UB_INF);
+	PG_RETURN_BOOL(range_has_flag(r1, RANGE_UB_INF));
 }
 
 
@@ -957,7 +986,25 @@ range_minus(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
 	RangeType  *r2 = PG_GETARG_RANGE_P(1);
+	RangeType  *ret;
 	TypeCacheEntry *typcache;
+
+	/* 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));
+
+	ret = range_minus_internal(typcache, r1, r2);
+	if (ret)
+		PG_RETURN_RANGE_P(ret);
+	else
+		PG_RETURN_NULL();
+}
+
+RangeType *
+range_minus_internal(TypeCacheEntry *typcache, RangeType *r1, RangeType *r2)
+{
 	RangeBound	lower1,
 				lower2;
 	RangeBound	upper1,
@@ -969,18 +1016,12 @@ range_minus(PG_FUNCTION_ARGS)
 				cmp_u1l2,
 				cmp_u1u2;
 
-	/* 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));
-
 	range_deserialize(typcache, r1, &lower1, &upper1, &empty1);
 	range_deserialize(typcache, r2, &lower2, &upper2, &empty2);
 
 	/* if either is empty, r1 is the correct answer */
 	if (empty1 || empty2)
-		PG_RETURN_RANGE_P(r1);
+		return r1;
 
 	cmp_l1l2 = range_cmp_bounds(typcache, &lower1, &lower2);
 	cmp_l1u2 = range_cmp_bounds(typcache, &lower1, &upper2);
@@ -993,34 +1034,34 @@ range_minus(PG_FUNCTION_ARGS)
 				 errmsg("result of range difference would not be contiguous")));
 
 	if (cmp_l1u2 > 0 || cmp_u1l2 < 0)
-		PG_RETURN_RANGE_P(r1);
+		return r1;
 
 	if (cmp_l1l2 >= 0 && cmp_u1u2 <= 0)
-		PG_RETURN_RANGE_P(make_empty_range(typcache));
+		return make_empty_range(typcache);
 
 	if (cmp_l1l2 <= 0 && cmp_u1l2 >= 0 && cmp_u1u2 <= 0)
 	{
 		lower2.inclusive = !lower2.inclusive;
 		lower2.lower = false;	/* it will become the upper bound */
-		PG_RETURN_RANGE_P(make_range(typcache, &lower1, &lower2, false));
+		return make_range(typcache, &lower1, &lower2, false);
 	}
 
 	if (cmp_l1l2 >= 0 && cmp_u1u2 >= 0 && cmp_l1u2 <= 0)
 	{
 		upper2.inclusive = !upper2.inclusive;
 		upper2.lower = true;	/* it will become the lower bound */
-		PG_RETURN_RANGE_P(make_range(typcache, &upper2, &upper1, false));
+		return make_range(typcache, &upper2, &upper1, false);
 	}
 
 	elog(ERROR, "unexpected case in range_minus");
-	PG_RETURN_NULL();
+	return NULL;
 }
 
 /*
  * Set union.  If strict is true, it is an error that the two input ranges
  * are not adjacent or overlapping.
  */
-static RangeType *
+RangeType *
 range_union_internal(TypeCacheEntry *typcache, RangeType *r1, RangeType *r2,
 					 bool strict)
 {
@@ -1101,6 +1142,19 @@ range_intersect(PG_FUNCTION_ARGS)
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
 	RangeType  *r2 = PG_GETARG_RANGE_P(1);
 	TypeCacheEntry *typcache;
+
+	/* 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));
+
+	PG_RETURN_RANGE_P(range_intersect_internal(typcache, r1, r2));
+}
+
+RangeType *
+range_intersect_internal(TypeCacheEntry *typcache, const RangeType *r1, const RangeType *r2)
+{
 	RangeBound	lower1,
 				lower2;
 	RangeBound	upper1,
@@ -1110,17 +1164,11 @@ range_intersect(PG_FUNCTION_ARGS)
 	RangeBound *result_lower;
 	RangeBound *result_upper;
 
-	/* 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));
-
 	range_deserialize(typcache, r1, &lower1, &upper1, &empty1);
 	range_deserialize(typcache, r2, &lower2, &upper2, &empty2);
 
-	if (empty1 || empty2 || !DatumGetBool(range_overlaps(fcinfo)))
-		PG_RETURN_RANGE_P(make_empty_range(typcache));
+	if (empty1 || empty2 || !range_overlaps_internal(typcache, r1, r2))
+		return make_empty_range(typcache);
 
 	if (range_cmp_bounds(typcache, &lower1, &lower2) >= 0)
 		result_lower = &lower1;
@@ -1132,9 +1180,81 @@ range_intersect(PG_FUNCTION_ARGS)
 	else
 		result_upper = &upper2;
 
-	PG_RETURN_RANGE_P(make_range(typcache, result_lower, result_upper, false));
+	return make_range(typcache, result_lower, result_upper, false);
+}
+
+/* range, range -> range, range functions */
+
+/*
+ * range_split_internal - if r2 intersects the middle of r1, leaving non-empty
+ * ranges on both sides, then return true and set output1 and output2 to the
+ * results of r1 - r2 (in order). Otherwise return false and don't set output1
+ * or output2. Neither input range should be empty.
+ */
+bool
+range_split_internal(TypeCacheEntry *typcache, const RangeType *r1, const RangeType *r2,
+					 RangeType **output1, RangeType **output2)
+{
+	RangeBound	lower1,
+				lower2;
+	RangeBound	upper1,
+				upper2;
+	bool		empty1,
+				empty2;
+
+	range_deserialize(typcache, r1, &lower1, &upper1, &empty1);
+	range_deserialize(typcache, r2, &lower2, &upper2, &empty2);
+
+	if (range_cmp_bounds(typcache, &lower1, &lower2) < 0 &&
+		range_cmp_bounds(typcache, &upper1, &upper2) > 0)
+	{
+		/*
+		 * Need to invert inclusive/exclusive for the lower2 and upper2
+		 * points. They can't be infinite though. We're allowed to overwrite
+		 * these RangeBounds since they only exist locally.
+		 */
+		lower2.inclusive = !lower2.inclusive;
+		lower2.lower = false;
+		upper2.inclusive = !upper2.inclusive;
+		upper2.lower = true;
+
+		*output1 = make_range(typcache, &lower1, &lower2, false);
+		*output2 = make_range(typcache, &upper2, &upper1, false);
+		return true;
+	}
+
+	return false;
 }
 
+/* range -> range aggregate functions */
+
+Datum
+range_intersect_agg_transfn(PG_FUNCTION_ARGS)
+{
+	MemoryContext aggContext;
+	Oid			rngtypoid;
+	TypeCacheEntry *typcache;
+	RangeType  *result;
+	RangeType  *current;
+
+	if (!AggCheckCallContext(fcinfo, &aggContext))
+		elog(ERROR, "range_intersect_agg_transfn called in non-aggregate context");
+
+	rngtypoid = get_fn_expr_argtype(fcinfo->flinfo, 1);
+	if (!type_is_range(rngtypoid))
+		ereport(ERROR, (errmsg("range_intersect_agg must be called with a range")));
+
+	typcache = range_get_typcache(fcinfo, rngtypoid);
+
+	/* strictness ensures these are non-null */
+	result = PG_GETARG_RANGE_P(0);
+	current = PG_GETARG_RANGE_P(1);
+
+	result = range_intersect_internal(typcache, result, current);
+	PG_RETURN_RANGE_P(result);
+}
+
+
 /* Btree support */
 
 /* btree comparator */
@@ -1144,12 +1264,6 @@ range_cmp(PG_FUNCTION_ARGS)
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
 	RangeType  *r2 = PG_GETARG_RANGE_P(1);
 	TypeCacheEntry *typcache;
-	RangeBound	lower1,
-				lower2;
-	RangeBound	upper1,
-				upper2;
-	bool		empty1,
-				empty2;
 	int			cmp;
 
 	check_stack_depth();		/* recurses when subtype is a range type */
@@ -1160,6 +1274,28 @@ range_cmp(PG_FUNCTION_ARGS)
 
 	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, const RangeType *r1, const 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);
 
@@ -1177,10 +1313,7 @@ range_cmp(PG_FUNCTION_ARGS)
 			cmp = range_cmp_bounds(typcache, &upper1, &upper2);
 	}
 
-	PG_FREE_IF_COPY(r1, 0);
-	PG_FREE_IF_COPY(r2, 1);
-
-	PG_RETURN_INT32(cmp);
+	return cmp;
 }
 
 /* inequality operators using the range_cmp function */
@@ -1218,13 +1351,10 @@ range_gt(PG_FUNCTION_ARGS)
 
 /* Hash support */
 
-/* hash a range value */
-Datum
-hash_range(PG_FUNCTION_ARGS)
+uint32
+hash_range_internal(TypeCacheEntry *typcache, const RangeType *r)
 {
-	RangeType  *r = PG_GETARG_RANGE_P(0);
 	uint32		result;
-	TypeCacheEntry *typcache;
 	TypeCacheEntry *scache;
 	RangeBound	lower;
 	RangeBound	upper;
@@ -1233,10 +1363,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 +1404,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, const RangeType *r, Datum seed)
+{
+	uint64		result;
 	TypeCacheEntry *scache;
 	RangeBound	lower;
 	RangeBound	upper;
@@ -1300,10 +1433,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 +1471,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));
 }
 
 /*
@@ -1770,6 +1917,21 @@ range_get_flags(const RangeType *range)
 }
 
 /*
+ * range_has_flag: set whether a range has a specific flag.
+ *
+ * This lets expose some of our functions that just check flags
+ * to the rest of the code base (like to multiranges)
+ * without writing full-fledged *_internal versions.
+ */
+bool
+range_has_flag(const RangeType *r1, char flag)
+{
+	char		flags = range_get_flags(r1);
+
+	return flags & flag;
+}
+
+/*
  * range_set_contain_empty: set the RANGE_CONTAIN_EMPTY bit in the value.
  *
  * This is only needed in GiST operations, so we don't include a provision
@@ -1938,6 +2100,46 @@ range_cmp_bound_values(TypeCacheEntry *typcache, const RangeBound *b1,
 }
 
 /*
+ * qsort callback for sorting ranges.
+ *
+ * Two empty ranges compare equal; an empty range sorts to the left of any
+ * non-empty range.  Two non-empty ranges are sorted by lower bound first
+ * and by upper bound next.
+ */
+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.
  */
 RangeType *
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index a7d63f107f..b137461de7 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -2530,6 +2530,16 @@ type_is_range(Oid typid)
 }
 
 /*
+ * 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
  *
  *		Given the type OID, fetch its category and preferred-type status.
@@ -3184,7 +3194,7 @@ get_namespace_name_or_temp(Oid nspid)
 		return get_namespace_name(nspid);
 }
 
-/*				---------- PG_RANGE CACHE ----------				 */
+/*				---------- PG_RANGE CACHES ----------				 */
 
 /*
  * get_range_subtype
@@ -3237,6 +3247,56 @@ get_range_collation(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->rngmultitypid;
+		ReleaseSysCache(tp);
+		return result;
+	}
+	else
+		return InvalidOid;
+}
+
+/*
+ * get_range_multirange_subtype
+ *		Returns the subtype of a given multirange type
+ *
+ * Returns InvalidOid if the type is not a multirange type.
+ */
+Oid
+get_range_multirange_subtype(Oid multirangeOid)
+{
+	HeapTuple	tp;
+
+	tp = SearchSysCache1(RANGEMULTIRANGE, 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 53d9ddf159..7654331a59 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -651,6 +651,18 @@ static const struct cachedesc cacheinfo[] = {
 		},
 		64
 	},
+	{RangeRelationId,			/* RANGEMULTIRANGE */
+		RangeMultirangeTypidIndexId,
+		1,
+		{
+			Anum_pg_range_rngmultitypid,
+			0,
+			0,
+			0
+		},
+		4
+	},
+
 	{RangeRelationId,			/* RANGETYPE */
 		RangeTypidIndexId,
 		1,
diff --git a/src/backend/utils/cache/typcache.c b/src/backend/utils/cache/typcache.c
index 854f133f9b..160846cf10 100644
--- a/src/backend/utils/cache/typcache.c
+++ b/src/backend/utils/cache/typcache.c
@@ -286,6 +286,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);
@@ -302,6 +303,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 TypeCacheTypCallback(Datum arg, int cacheid, uint32 hashvalue);
 static void TypeCacheOpcCallback(Datum arg, int cacheid, uint32 hashvalue);
@@ -553,8 +557,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))
@@ -591,7 +595,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))
@@ -616,7 +620,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))
@@ -641,7 +645,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))
@@ -693,6 +697,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;
@@ -737,6 +748,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;
@@ -817,6 +835,16 @@ lookup_type_cache(Oid type_id, int flags)
 	}
 
 	/*
+	 * 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
 	 */
 	if ((flags & TYPECACHE_DOMAIN_BASE_INFO) &&
@@ -927,6 +955,22 @@ load_rangetype_info(TypeCacheEntry *typentry)
 	typentry->rngelemtype = lookup_type_cache(subtypeOid, 0);
 }
 
+/*
+ * load_multirangetype_info --- helper routine to set up multirange type
+ * information
+ */
+static void
+load_multirangetype_info(TypeCacheEntry *typentry)
+{
+	Oid			rangetypeOid;
+
+	rangetypeOid = get_range_multirange_subtype(typentry->type_id);
+	if (!OidIsValid(rangetypeOid))
+		elog(ERROR, "cache lookup failed for multirange type %u",
+			 typentry->type_id);
+
+	typentry->rngtype = lookup_type_cache(rangetypeOid, TYPECACHE_RANGE_INFO);
+}
 
 /*
  * load_domaintype_info --- helper routine to set up domain constraint info
@@ -1527,11 +1571,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
@@ -1574,6 +1618,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 78ed857203..8a5bd9bf19 100644
--- a/src/backend/utils/fmgr/funcapi.c
+++ b/src/backend/utils/fmgr/funcapi.c
@@ -35,6 +35,7 @@ typedef struct polymorphic_actuals
 	Oid			anyelement_type;	/* anyelement mapping, if known */
 	Oid			anyarray_type;	/* anyarray mapping, if known */
 	Oid			anyrange_type;	/* anyrange mapping, if known */
+	Oid			anymultirange_type;	/* anymultirange mapping, if known */
 } polymorphic_actuals;
 
 static void shutdown_MultiFuncCall(Datum arg);
@@ -46,6 +47,7 @@ static TypeFuncClass internal_get_result_type(Oid funcid,
 static void resolve_anyelement_from_others(polymorphic_actuals *actuals);
 static void resolve_anyarray_from_others(polymorphic_actuals *actuals);
 static void resolve_anyrange_from_others(polymorphic_actuals *actuals);
+static void resolve_anymultirange_from_others(polymorphic_actuals *actuals);
 static bool resolve_polymorphic_tupdesc(TupleDesc tupdesc,
 										oidvector *declared_args,
 										Node *call_expr);
@@ -503,6 +505,31 @@ resolve_anyelement_from_others(polymorphic_actuals *actuals)
 							format_type_be(range_base_type))));
 		actuals->anyelement_type = range_typelem;
 	}
+	else if (OidIsValid(actuals->anymultirange_type))
+	{
+		/* Use the element type based on the multirange type */
+		Oid			multirange_base_type = getBaseType(actuals->anymultirange_type);
+		Oid			multirange_typelem =
+			get_range_multirange_subtype(multirange_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(multirange_base_type))));
+
+		Oid			range_base_type = getBaseType(multirange_typelem);
+		Oid			range_typelem = get_range_subtype(range_base_type);
+
+		if (!OidIsValid(range_typelem))
+			ereport(ERROR,
+					(errcode(ERRCODE_DATATYPE_MISMATCH),
+					 errmsg("argument declared %s does not contain a range type but type %s",
+							"anymultirange",
+							format_type_be(range_base_type))));
+		actuals->anyelement_type = range_typelem;
+	}
 	else
 		elog(ERROR, "could not determine polymorphic type");
 }
@@ -540,10 +567,54 @@ static void
 resolve_anyrange_from_others(polymorphic_actuals *actuals)
 {
 	/*
-	 * We can't deduce a range type from other polymorphic inputs, because
-	 * there may be multiple range types with the same subtype.
+	 * We can't deduce a range type from other polymorphic array or base types,
+	 * because there may be multiple range types with the same subtype, but we
+	 * can deduce it from a polymorphic multirange type.
 	 */
-	elog(ERROR, "could not determine polymorphic type");
+	if (OidIsValid(actuals->anymultirange_type))
+	{
+		/* Use the element type based on the multirange type */
+		Oid			multirange_base_type = getBaseType(actuals->anymultirange_type);
+		Oid			multirange_typelem =
+			get_range_multirange_subtype(multirange_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(multirange_base_type))));
+		actuals->anyrange_type = multirange_typelem;
+	}
+	else
+		elog(ERROR, "could not determine polymorphic type");
+}
+
+/*
+ * Resolve actual type of ANYMULTIRANGE from other polymorphic inputs
+ */
+static void
+resolve_anymultirange_from_others(polymorphic_actuals *actuals)
+{
+	/*
+	 * We can't deduce a multirange type from polymorphic array or base types,
+	 * because there may be multiple range types with the same subtype, but we
+	 * can deduce it from a polymorphic range type.
+	 */
+	if (OidIsValid(actuals->anyrange_type))
+	{
+		Oid			range_base_type = getBaseType(actuals->anyrange_type);
+		Oid			multirange_typeid = get_range_multirange(range_base_type);
+
+		if (!OidIsValid(multirange_typeid))
+			ereport(ERROR,
+					(errcode(ERRCODE_UNDEFINED_OBJECT),
+					 errmsg("could not find multirange type for data type %s",
+							format_type_be(actuals->anyrange_type))));
+		actuals->anymultirange_type = multirange_typeid;
+	}
+	else
+		elog(ERROR, "could not determine polymorphic type");
 }
 
 /*
@@ -566,9 +637,11 @@ 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_anycompatible_result = false;
 	bool		have_anycompatible_array_result = false;
 	bool		have_anycompatible_range_result = false;
+	bool		have_anycompatible_multirange_result = false;
 	polymorphic_actuals poly_actuals;
 	polymorphic_actuals anyc_actuals;
 	Oid			anycollation = InvalidOid;
@@ -594,6 +667,10 @@ resolve_polymorphic_tupdesc(TupleDesc tupdesc, oidvector *declared_args,
 				have_polymorphic_result = true;
 				have_anyrange_result = true;
 				break;
+			case ANYMULTIRANGEOID:
+				have_polymorphic_result = true;
+				have_anymultirange_result = true;
+				break;
 			case ANYCOMPATIBLEOID:
 			case ANYCOMPATIBLENONARRAYOID:
 				have_polymorphic_result = true;
@@ -607,6 +684,10 @@ resolve_polymorphic_tupdesc(TupleDesc tupdesc, oidvector *declared_args,
 				have_polymorphic_result = true;
 				have_anycompatible_range_result = true;
 				break;
+			case ANYCOMPATIBLEMULTIRANGEOID:
+				have_polymorphic_result = true;
+				have_anycompatible_multirange_result = true;
+				break;
 			default:
 				break;
 		}
@@ -660,6 +741,15 @@ resolve_polymorphic_tupdesc(TupleDesc tupdesc, oidvector *declared_args,
 						return false;
 				}
 				break;
+			case ANYMULTIRANGEOID:
+				if (!OidIsValid(poly_actuals.anymultirange_type))
+				{
+					poly_actuals.anymultirange_type =
+						get_call_expr_argtype(call_expr, i);
+					if (!OidIsValid(poly_actuals.anymultirange_type))
+						return false;
+				}
+				break;
 			case ANYCOMPATIBLEOID:
 			case ANYCOMPATIBLENONARRAYOID:
 				if (!OidIsValid(anyc_actuals.anyelement_type))
@@ -688,6 +778,15 @@ resolve_polymorphic_tupdesc(TupleDesc tupdesc, oidvector *declared_args,
 						return false;
 				}
 				break;
+			case ANYCOMPATIBLEMULTIRANGEOID:
+				if (!OidIsValid(anyc_actuals.anymultirange_type))
+				{
+					anyc_actuals.anymultirange_type =
+						get_call_expr_argtype(call_expr, i);
+					if (!OidIsValid(anyc_actuals.anymultirange_type))
+						return false;
+				}
+				break;
 			default:
 				break;
 		}
@@ -703,6 +802,9 @@ resolve_polymorphic_tupdesc(TupleDesc tupdesc, oidvector *declared_args,
 	if (have_anyrange_result && !OidIsValid(poly_actuals.anyrange_type))
 		resolve_anyrange_from_others(&poly_actuals);
 
+	if (have_anymultirange_result && !OidIsValid(poly_actuals.anymultirange_type))
+		resolve_anymultirange_from_others(&poly_actuals);
+
 	if (have_anycompatible_result && !OidIsValid(anyc_actuals.anyelement_type))
 		resolve_anyelement_from_others(&anyc_actuals);
 
@@ -712,6 +814,9 @@ resolve_polymorphic_tupdesc(TupleDesc tupdesc, oidvector *declared_args,
 	if (have_anycompatible_range_result && !OidIsValid(anyc_actuals.anyrange_type))
 		resolve_anyrange_from_others(&anyc_actuals);
 
+	if (have_anycompatible_multirange_result && !OidIsValid(anyc_actuals.anymultirange_type))
+		resolve_anymultirange_from_others(&anyc_actuals);
+
 	/*
 	 * Identify the collation to use for polymorphic OUT parameters. (It'll
 	 * necessarily be the same for both anyelement and anyarray, likewise for
@@ -780,6 +885,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),
+								   poly_actuals.anymultirange_type,
+								   -1,
+								   0);
+				/* no collation should be attached to a multirange type */
+				break;
 			case ANYCOMPATIBLEOID:
 			case ANYCOMPATIBLENONARRAYOID:
 				TupleDescInitEntry(tupdesc, i + 1,
@@ -805,6 +918,14 @@ resolve_polymorphic_tupdesc(TupleDesc tupdesc, oidvector *declared_args,
 								   0);
 				/* no collation should be attached to a range type */
 				break;
+			case ANYCOMPATIBLEMULTIRANGEOID:
+				TupleDescInitEntry(tupdesc, i + 1,
+								   NameStr(att->attname),
+								   anyc_actuals.anymultirange_type,
+								   -1,
+								   0);
+				/* no collation should be attached to a multirange type */
+				break;
 			default:
 				break;
 		}
@@ -834,9 +955,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;
 	bool		have_anycompatible_result = false;
 	bool		have_anycompatible_array_result = false;
 	bool		have_anycompatible_range_result = false;
+	bool		have_anycompatible_multirange_result = false;
 	polymorphic_actuals poly_actuals;
 	polymorphic_actuals anyc_actuals;
 	int			inargno;
@@ -912,6 +1035,24 @@ resolve_polymorphic_argtypes(int numargs, Oid *argtypes, char *argmodes,
 					argtypes[i] = poly_actuals.anyrange_type;
 				}
 				break;
+			case ANYMULTIRANGEOID:
+				if (argmode == PROARGMODE_OUT || argmode == PROARGMODE_TABLE)
+				{
+					have_polymorphic_result = true;
+					have_anymultirange_result = true;
+				}
+				else
+				{
+					if (!OidIsValid(poly_actuals.anymultirange_type))
+					{
+						poly_actuals.anymultirange_type =
+							get_call_expr_argtype(call_expr, inargno);
+						if (!OidIsValid(poly_actuals.anymultirange_type))
+							return false;
+					}
+					argtypes[i] = poly_actuals.anymultirange_type;
+				}
+				break;
 			case ANYCOMPATIBLEOID:
 			case ANYCOMPATIBLENONARRAYOID:
 				if (argmode == PROARGMODE_OUT || argmode == PROARGMODE_TABLE)
@@ -967,6 +1108,24 @@ resolve_polymorphic_argtypes(int numargs, Oid *argtypes, char *argmodes,
 					argtypes[i] = anyc_actuals.anyrange_type;
 				}
 				break;
+			case ANYCOMPATIBLEMULTIRANGEOID:
+				if (argmode == PROARGMODE_OUT || argmode == PROARGMODE_TABLE)
+				{
+					have_polymorphic_result = true;
+					have_anycompatible_multirange_result = true;
+				}
+				else
+				{
+					if (!OidIsValid(anyc_actuals.anymultirange_type))
+					{
+						anyc_actuals.anymultirange_type =
+							get_call_expr_argtype(call_expr, inargno);
+						if (!OidIsValid(anyc_actuals.anymultirange_type))
+							return false;
+					}
+					argtypes[i] = anyc_actuals.anymultirange_type;
+				}
+				break;
 			default:
 				break;
 		}
@@ -988,6 +1147,9 @@ resolve_polymorphic_argtypes(int numargs, Oid *argtypes, char *argmodes,
 	if (have_anyrange_result && !OidIsValid(poly_actuals.anyrange_type))
 		resolve_anyrange_from_others(&poly_actuals);
 
+	if (have_anymultirange_result && !OidIsValid(poly_actuals.anymultirange_type))
+		resolve_anymultirange_from_others(&poly_actuals);
+
 	if (have_anycompatible_result && !OidIsValid(anyc_actuals.anyelement_type))
 		resolve_anyelement_from_others(&anyc_actuals);
 
@@ -997,6 +1159,9 @@ resolve_polymorphic_argtypes(int numargs, Oid *argtypes, char *argmodes,
 	if (have_anycompatible_range_result && !OidIsValid(anyc_actuals.anyrange_type))
 		resolve_anyrange_from_others(&anyc_actuals);
 
+	if (have_anycompatible_multirange_result && !OidIsValid(anyc_actuals.anymultirange_type))
+		resolve_anymultirange_from_others(&anyc_actuals);
+
 	/* And finally replace the output column types as needed */
 	for (i = 0; i < numargs; i++)
 	{
@@ -1013,6 +1178,9 @@ resolve_polymorphic_argtypes(int numargs, Oid *argtypes, char *argmodes,
 			case ANYRANGEOID:
 				argtypes[i] = poly_actuals.anyrange_type;
 				break;
+			case ANYMULTIRANGEOID:
+				argtypes[i] = poly_actuals.anymultirange_type;
+				break;
 			case ANYCOMPATIBLEOID:
 			case ANYCOMPATIBLENONARRAYOID:
 				argtypes[i] = anyc_actuals.anyelement_type;
@@ -1023,6 +1191,9 @@ resolve_polymorphic_argtypes(int numargs, Oid *argtypes, char *argmodes,
 			case ANYCOMPATIBLERANGEOID:
 				argtypes[i] = anyc_actuals.anyrange_type;
 				break;
+			case ANYCOMPATIBLEMULTIRANGEOID:
+				argtypes[i] = anyc_actuals.anymultirange_type;
+				break;
 			default:
 				break;
 		}
@@ -1052,6 +1223,7 @@ get_type_func_class(Oid typid, Oid *base_typeid)
 		case TYPTYPE_BASE:
 		case TYPTYPE_ENUM:
 		case TYPTYPE_RANGE:
+		case TYPTYPE_MULTIRANGE:
 			return TYPEFUNC_SCALAR;
 		case TYPTYPE_DOMAIN:
 			*base_typeid = typid = getBaseType(typid);
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index c579227b19..5d09aa3d1e 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -276,7 +276,8 @@ static void dumpSearchPath(Archive *AH);
 static void binary_upgrade_set_type_oids_by_type_oid(Archive *fout,
 													 PQExpBuffer upgrade_buffer,
 													 Oid pg_type_oid,
-													 bool force_array_type);
+													 bool force_array_type,
+													 bool include_multirange_type);
 static bool binary_upgrade_set_type_oids_by_rel_oid(Archive *fout,
 													PQExpBuffer upgrade_buffer, Oid pg_rel_oid);
 static void binary_upgrade_set_pg_class_oids(Archive *fout,
@@ -1648,7 +1649,7 @@ selectDumpableType(TypeInfo *tyinfo, Archive *fout)
 	}
 
 	/* skip auto-generated array types */
-	if (tyinfo->isArray)
+	if (tyinfo->isArray || tyinfo->isMultirange)
 	{
 		tyinfo->dobj.objType = DO_DUMMY_TYPE;
 
@@ -4432,16 +4433,49 @@ append_depends_on_extension(Archive *fout,
 	}
 }
 
+static Oid
+get_next_possible_free_pg_type_oid(Archive *fout, PQExpBuffer upgrade_query)
+{
+	/*
+	 * If the old version didn't assign an array type, but the new version
+	 * does, we must select an unused type OID to assign.  This currently only
+	 * happens for domains, when upgrading pre-v11 to v11 and up.
+	 *
+	 * Note: local state here is kind of ugly, but we must have some, since we
+	 * mustn't choose the same unused OID more than once.
+	 */
+	static Oid	next_possible_free_oid = FirstNormalObjectId;
+	PGresult   *res;
+	bool		is_dup;
+
+	do
+	{
+		++next_possible_free_oid;
+		printfPQExpBuffer(upgrade_query,
+						  "SELECT EXISTS(SELECT 1 "
+						  "FROM pg_catalog.pg_type "
+						  "WHERE oid = '%u'::pg_catalog.oid);",
+						  next_possible_free_oid);
+		res = ExecuteSqlQueryForSingleRow(fout, upgrade_query->data);
+		is_dup = (PQgetvalue(res, 0, 0)[0] == 't');
+		PQclear(res);
+	} while (is_dup);
+
+	return next_possible_free_oid;
+}
 
 static void
 binary_upgrade_set_type_oids_by_type_oid(Archive *fout,
 										 PQExpBuffer upgrade_buffer,
 										 Oid pg_type_oid,
-										 bool force_array_type)
+										 bool force_array_type,
+										 bool include_multirange_type)
 {
 	PQExpBuffer upgrade_query = createPQExpBuffer();
 	PGresult   *res;
 	Oid			pg_type_array_oid;
+	Oid			pg_type_multirange_oid;
+	Oid			pg_type_multirange_array_oid;
 
 	appendPQExpBufferStr(upgrade_buffer, "\n-- For binary upgrade, must preserve pg_type oid\n");
 	appendPQExpBuffer(upgrade_buffer,
@@ -4462,33 +4496,7 @@ binary_upgrade_set_type_oids_by_type_oid(Archive *fout,
 	PQclear(res);
 
 	if (!OidIsValid(pg_type_array_oid) && force_array_type)
-	{
-		/*
-		 * If the old version didn't assign an array type, but the new version
-		 * does, we must select an unused type OID to assign.  This currently
-		 * only happens for domains, when upgrading pre-v11 to v11 and up.
-		 *
-		 * Note: local state here is kind of ugly, but we must have some,
-		 * since we mustn't choose the same unused OID more than once.
-		 */
-		static Oid	next_possible_free_oid = FirstNormalObjectId;
-		bool		is_dup;
-
-		do
-		{
-			++next_possible_free_oid;
-			printfPQExpBuffer(upgrade_query,
-							  "SELECT EXISTS(SELECT 1 "
-							  "FROM pg_catalog.pg_type "
-							  "WHERE oid = '%u'::pg_catalog.oid);",
-							  next_possible_free_oid);
-			res = ExecuteSqlQueryForSingleRow(fout, upgrade_query->data);
-			is_dup = (PQgetvalue(res, 0, 0)[0] == 't');
-			PQclear(res);
-		} while (is_dup);
-
-		pg_type_array_oid = next_possible_free_oid;
-	}
+		pg_type_array_oid = get_next_possible_free_pg_type_oid(fout, upgrade_query);
 
 	if (OidIsValid(pg_type_array_oid))
 	{
@@ -4499,6 +4507,46 @@ binary_upgrade_set_type_oids_by_type_oid(Archive *fout,
 						  pg_type_array_oid);
 	}
 
+	/*
+	 * Pre-set the multirange type oid and its own array type oid.
+	 */
+	if (include_multirange_type)
+	{
+		if (fout->remoteVersion >= 130000)
+		{
+			appendPQExpBuffer(upgrade_query,
+							  "SELECT t.oid, t.typarray "
+							  "FROM pg_catalog.pg_type t "
+							  "JOIN pg_catalog.pg_range r "
+							  "ON t.oid = r.rngmultitypid "
+							  "WHERE r.rngtypid = '%u'::pg_catalog.oid;",
+							  pg_type_oid);
+
+			res = ExecuteSqlQueryForSingleRow(fout, upgrade_query->data);
+
+			pg_type_multirange_oid = atooid(PQgetvalue(res, 0, PQfnumber(res, "oid")));
+			pg_type_multirange_array_oid = atooid(PQgetvalue(res, 0, PQfnumber(res, "typarray")));
+
+			PQclear(res);
+		}
+		else
+		{
+			pg_type_multirange_oid = get_next_possible_free_pg_type_oid(fout, upgrade_query);
+			pg_type_multirange_array_oid = get_next_possible_free_pg_type_oid(fout, upgrade_query);
+		}
+
+		appendPQExpBufferStr(upgrade_buffer,
+							 "\n-- For binary upgrade, must preserve multirange pg_type oid\n");
+		appendPQExpBuffer(upgrade_buffer,
+						  "SELECT pg_catalog.binary_upgrade_set_next_multirange_pg_type_oid('%u'::pg_catalog.oid);\n\n",
+						  pg_type_multirange_oid);
+		appendPQExpBufferStr(upgrade_buffer,
+							 "\n-- For binary upgrade, must preserve multirange pg_type array oid\n");
+		appendPQExpBuffer(upgrade_buffer,
+						  "SELECT pg_catalog.binary_upgrade_set_next_multirange_array_pg_type_oid('%u'::pg_catalog.oid);\n\n",
+						  pg_type_multirange_array_oid);
+	}
+
 	destroyPQExpBuffer(upgrade_query);
 }
 
@@ -4532,7 +4580,7 @@ binary_upgrade_set_type_oids_by_rel_oid(Archive *fout,
 	pg_type_oid = atooid(PQgetvalue(upgrade_res, 0, PQfnumber(upgrade_res, "crel")));
 
 	binary_upgrade_set_type_oids_by_type_oid(fout, upgrade_buffer,
-											 pg_type_oid, false);
+											 pg_type_oid, false, false);
 
 	if (!PQgetisnull(upgrade_res, 0, PQfnumber(upgrade_res, "trel")))
 	{
@@ -5081,6 +5129,11 @@ getTypes(Archive *fout, int *numTypes)
 		else
 			tyinfo[i].isArray = false;
 
+		if (tyinfo[i].typtype == 'm')
+			tyinfo[i].isMultirange = true;
+		else
+			tyinfo[i].isMultirange = false;
+
 		/* Decide whether we want to dump it */
 		selectDumpableType(&tyinfo[i], fout);
 
@@ -8219,9 +8272,12 @@ getProcLangs(Archive *fout, int *numProcLangs)
 
 /*
  * getCasts
- *	  get basic information about every cast in the system
+ *	  get basic information about most casts in the system
  *
  * numCasts is set to the number of casts read in
+ *
+ * Skip casts from a range to its multirange, since we'll create those
+ * automatically.
  */
 CastInfo *
 getCasts(Archive *fout, int *numCasts)
@@ -8239,7 +8295,20 @@ getCasts(Archive *fout, int *numCasts)
 	int			i_castcontext;
 	int			i_castmethod;
 
-	if (fout->remoteVersion >= 80400)
+	if (fout->remoteVersion >= 130000)
+	{
+		appendPQExpBufferStr(query, "SELECT tableoid, oid, "
+							 "castsource, casttarget, castfunc, castcontext, "
+							 "castmethod "
+							 "FROM pg_cast c "
+							 "WHERE NOT EXISTS ( "
+							 "SELECT 1 FROM pg_range r "
+							 "WHERE c.castsource = r.rngtypid "
+							 "AND c.casttarget = r.rngmultitypid "
+							 ") "
+							 "ORDER BY 3,4");
+	}
+	else if (fout->remoteVersion >= 80400)
 	{
 		appendPQExpBufferStr(query, "SELECT tableoid, oid, "
 							 "castsource, casttarget, castfunc, castcontext, "
@@ -10427,7 +10496,7 @@ dumpEnumType(Archive *fout, TypeInfo *tyinfo)
 	if (dopt->binary_upgrade)
 		binary_upgrade_set_type_oids_by_type_oid(fout, q,
 												 tyinfo->dobj.catId.oid,
-												 false);
+												 false, false);
 
 	appendPQExpBuffer(q, "CREATE TYPE %s AS ENUM (",
 					  qualtypname);
@@ -10553,7 +10622,7 @@ dumpRangeType(Archive *fout, TypeInfo *tyinfo)
 	if (dopt->binary_upgrade)
 		binary_upgrade_set_type_oids_by_type_oid(fout, q,
 												 tyinfo->dobj.catId.oid,
-												 false);
+												 false, true);
 
 	appendPQExpBuffer(q, "CREATE TYPE %s AS RANGE (",
 					  qualtypname);
@@ -10659,7 +10728,7 @@ dumpUndefinedType(Archive *fout, TypeInfo *tyinfo)
 	if (dopt->binary_upgrade)
 		binary_upgrade_set_type_oids_by_type_oid(fout, q,
 												 tyinfo->dobj.catId.oid,
-												 false);
+												 false, false);
 
 	appendPQExpBuffer(q, "CREATE TYPE %s;\n",
 					  qualtypname);
@@ -10864,7 +10933,7 @@ dumpBaseType(Archive *fout, TypeInfo *tyinfo)
 	if (dopt->binary_upgrade)
 		binary_upgrade_set_type_oids_by_type_oid(fout, q,
 												 tyinfo->dobj.catId.oid,
-												 false);
+												 false, false);
 
 	appendPQExpBuffer(q,
 					  "CREATE TYPE %s (\n"
@@ -11051,7 +11120,8 @@ dumpDomain(Archive *fout, TypeInfo *tyinfo)
 	if (dopt->binary_upgrade)
 		binary_upgrade_set_type_oids_by_type_oid(fout, q,
 												 tyinfo->dobj.catId.oid,
-												 true); /* force array type */
+												 true,		/* force array type */
+												 false);	/* force multirange type */
 
 	qtypname = pg_strdup(fmtId(tyinfo->dobj.name));
 	qualtypname = pg_strdup(fmtQualifiedDumpable(tyinfo));
@@ -11239,7 +11309,7 @@ dumpCompositeType(Archive *fout, TypeInfo *tyinfo)
 	{
 		binary_upgrade_set_type_oids_by_type_oid(fout, q,
 												 tyinfo->dobj.catId.oid,
-												 false);
+												 false, false);
 		binary_upgrade_set_pg_class_oids(fout, q, tyinfo->typrelid, false);
 	}
 
@@ -11513,7 +11583,7 @@ dumpShellType(Archive *fout, ShellTypeInfo *stinfo)
 	if (dopt->binary_upgrade)
 		binary_upgrade_set_type_oids_by_type_oid(fout, q,
 												 stinfo->baseType->dobj.catId.oid,
-												 false);
+												 false, false);
 
 	appendPQExpBuffer(q, "CREATE TYPE %s;\n",
 					  fmtQualifiedDumpable(stinfo));
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 61c909e06d..36178db881 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -176,6 +176,7 @@ typedef struct _typeInfo
 	char		typrelkind;		/* 'r', 'v', 'c', etc */
 	char		typtype;		/* 'b', 'c', etc */
 	bool		isArray;		/* true if auto-generated array type */
+	bool		isMultirange;	/* true if auto-generated multirange type */
 	bool		isDefined;		/* true if typisdefined */
 	/* If needed, we'll create a "shell type" entry for it; link that here: */
 	struct _shellTypeInfo *shellType;	/* shell-type entry, or NULL */
diff --git a/src/include/access/tupmacs.h b/src/include/access/tupmacs.h
index 70157cf90a..c262265b95 100644
--- a/src/include/access/tupmacs.h
+++ b/src/include/access/tupmacs.h
@@ -139,8 +139,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/binary_upgrade.h b/src/include/catalog/binary_upgrade.h
index 12d94fe1b3..01d95117cd 100644
--- a/src/include/catalog/binary_upgrade.h
+++ b/src/include/catalog/binary_upgrade.h
@@ -16,6 +16,8 @@
 
 extern PGDLLIMPORT Oid binary_upgrade_next_pg_type_oid;
 extern PGDLLIMPORT Oid binary_upgrade_next_array_pg_type_oid;
+extern PGDLLIMPORT Oid binary_upgrade_next_mrng_pg_type_oid;
+extern PGDLLIMPORT Oid binary_upgrade_next_mrng_array_pg_type_oid;
 extern PGDLLIMPORT Oid binary_upgrade_next_toast_pg_type_oid;
 
 extern PGDLLIMPORT Oid binary_upgrade_next_heap_pg_class_oid;
diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h
index 8be303870f..566f9364c5 100644
--- a/src/include/catalog/indexing.h
+++ b/src/include/catalog/indexing.h
@@ -327,6 +327,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_rngmultitypid_index, 8001, on pg_range using btree(rngmultitypid 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_aggregate.dat b/src/include/catalog/pg_aggregate.dat
index ffabe275c0..03bab74253 100644
--- a/src/include/catalog/pg_aggregate.dat
+++ b/src/include/catalog/pg_aggregate.dat
@@ -538,6 +538,17 @@
   aggtransfn => 'bytea_string_agg_transfn',
   aggfinalfn => 'bytea_string_agg_finalfn', aggtranstype => 'internal' },
 
+# range
+{ aggfnoid => 'range_intersect_agg(anyrange)',
+  aggtransfn => 'range_intersect_agg_transfn',
+  aggcombinefn => 'range_intersect_agg_transfn', aggtranstype => 'anyrange' },
+{ aggfnoid => 'range_intersect_agg(anymultirange)',
+  aggtransfn => 'multirange_intersect_agg_transfn',
+  aggcombinefn => 'multirange_intersect_agg_transfn', aggtranstype => 'anymultirange' },
+{ aggfnoid => 'range_agg(anyrange)', aggtransfn => 'range_agg_transfn',
+  aggfinalfn => 'range_agg_finalfn', aggfinalextra => 't',
+  aggtranstype => 'internal' },
+
 # json
 { aggfnoid => 'json_agg', aggtransfn => 'json_agg_transfn',
   aggfinalfn => 'json_agg_finalfn', aggtranstype => 'internal' },
diff --git a/src/include/catalog/pg_amop.dat b/src/include/catalog/pg_amop.dat
index b5dfaad9ec..604286e2c2 100644
--- a/src/include/catalog/pg_amop.dat
+++ b/src/include/catalog/pg_amop.dat
@@ -1378,6 +1378,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 37b580883f..9842e80acf 100644
--- a/src/include/catalog/pg_amproc.dat
+++ b/src/include/catalog/pg_amproc.dat
@@ -285,6 +285,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' },
 { amprocfamily => 'btree/xid8_ops', amproclefttype => 'xid8',
@@ -453,6 +455,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',
@@ -1274,12 +1281,13 @@
   amprocrighttype => 'anyrange', amprocnum => '4',
   amproc => 'brin_inclusion_union' },
 { amprocfamily => 'brin/range_inclusion_ops', amproclefttype => 'anyrange',
-  amprocrighttype => 'anyrange', amprocnum => '11', amproc => 'range_merge' },
+  amprocrighttype => 'anyrange', amprocnum => '11',
+  amproc => 'range_merge(anyrange,anyrange)' },
 { amprocfamily => 'brin/range_inclusion_ops', amproclefttype => 'anyrange',
   amprocrighttype => 'anyrange', amprocnum => '13',
   amproc => 'range_contains' },
 { amprocfamily => 'brin/range_inclusion_ops', amproclefttype => 'anyrange',
-  amprocrighttype => 'anyrange', amprocnum => '14', amproc => 'isempty' },
+  amprocrighttype => 'anyrange', amprocnum => '14', amproc => 'isempty(anyrange)' },
 
 # minmax pg_lsn
 { amprocfamily => 'brin/pg_lsn_minmax_ops', amproclefttype => 'pg_lsn',
diff --git a/src/include/catalog/pg_cast.dat b/src/include/catalog/pg_cast.dat
index 5a58f50fbb..d6ca624add 100644
--- a/src/include/catalog/pg_cast.dat
+++ b/src/include/catalog/pg_cast.dat
@@ -530,4 +530,17 @@
 { castsource => 'jsonb', casttarget => 'float8', castfunc => 'float8(jsonb)',
   castcontext => 'e', castmethod => 'f' },
 
+# range to multirange
+{ castsource => 'int4range', casttarget => 'int4multirange', castfunc => 'int4multirange(int4range)',
+  castcontext => 'e', castmethod => 'f' },
+{ castsource => 'int8range', casttarget => 'int8multirange', castfunc => 'int8multirange(int8range)',
+  castcontext => 'e', castmethod => 'f' },
+{ castsource => 'numrange', casttarget => 'nummultirange', castfunc => 'nummultirange(numrange)',
+  castcontext => 'e', castmethod => 'f' },
+{ castsource => 'daterange', casttarget => 'datemultirange', castfunc => 'datemultirange(daterange)',
+  castcontext => 'e', castmethod => 'f' },
+{ castsource => 'tsrange', casttarget => 'tsmultirange', castfunc => 'tsmultirange(tsrange)',
+  castcontext => 'e', castmethod => 'f' },
+{ castsource => 'tstzrange', casttarget => 'tstzmultirange', castfunc => 'tstzmultirange(tstzrange)',
+  castcontext => 'e', castmethod => 'f' },
 ]
diff --git a/src/include/catalog/pg_opclass.dat b/src/include/catalog/pg_opclass.dat
index f2342bb328..9b0609defa 100644
--- a/src/include/catalog/pg_opclass.dat
+++ b/src/include/catalog/pg_opclass.dat
@@ -230,6 +230,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 00ada7e48f..071352539f 100644
--- a/src/include/catalog/pg_operator.dat
+++ b/src/include/catalog/pg_operator.dat
@@ -3327,5 +3327,174 @@
   oprname => '@@', oprleft => 'jsonb', oprright => 'jsonpath',
   oprresult => 'bool', oprcode => 'jsonb_path_match_opr(jsonb,jsonpath)',
   oprrest => 'matchingsel', oprjoin => 'matchingjoinsel' },
+{ 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' },
+{ oid => '8073', oid_symbol => 'OID_RANGE_OVERLAPS_MULTIRANGE_OP',
+  descr => 'overlaps',
+  oprname => '&&', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '&&(anymultirange,anyrange)',
+  oprcode => 'range_overlaps_multirange', oprrest => 'areasel',
+  oprjoin => 'areajoinsel' },
+{ oid => '8074', oid_symbol => 'OID_MULTIRANGE_OVERLAPS_RANGE_OP', descr => 'contains',
+  oprname => '&&', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcom => '&&(anyrange,anymultirange)',
+  oprcode => 'multirange_overlaps_range', oprrest => 'areasel',
+  oprjoin => 'areajoinsel' },
+{ oid => '8075', oid_symbol => 'OID_MULTIRANGE_OVERLAPS_MULTIRANGE_OP', descr => 'contains',
+  oprname => '&&', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '&&(anymultirange,anymultirange)',
+  oprcode => 'multirange_overlaps_multirange', oprrest => 'areasel',
+  oprjoin => 'areajoinsel' },
+{ oid => '8064', oid_symbol => 'OID_MULTIRANGE_CONTAINS_ELEM_OP',
+  descr => 'contains',
+  oprname => '@>', oprleft => 'anymultirange', oprright => 'anyelement',
+  oprresult => 'bool', oprcom => '<@(anyelement,anymultirange)',
+  oprcode => 'multirange_contains_elem', oprrest => 'contsel',
+  oprjoin => 'contjoinsel' },
+{ oid => '8065', oid_symbol => 'OID_MULTIRANGE_CONTAINS_RANGE_OP', descr => 'contains',
+  oprname => '@>', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcom => '<@(anyrange,anymultirange)',
+  oprcode => 'multirange_contains_range', oprrest => 'contsel',
+  oprjoin => 'contjoinsel' },
+{ oid => '8066', oid_symbol => 'OID_MULTIRANGE_CONTAINS_MULTIRANGE_OP', descr => 'contains',
+  oprname => '@>', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '<@(anymultirange,anymultirange)',
+  oprcode => 'multirange_contains_multirange', oprrest => 'contsel',
+  oprjoin => 'contjoinsel' },
+{ oid => '8067', oid_symbol => 'OID_MULTIRANGE_ELEM_CONTAINED_OP',
+  descr => 'is contained by',
+  oprname => '<@', oprleft => 'anyelement', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '@>(anymultirange,anyelement)',
+  oprcode => 'elem_contained_by_multirange', oprrest => 'contsel',
+  oprjoin => 'contjoinsel' },
+{ oid => '8068', oid_symbol => 'OID_MULTIRANGE_RANGE_CONTAINED_OP',
+  descr => 'is contained by',
+  oprname => '<@', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '@>(anymultirange,anyrange)',
+  oprcode => 'range_contained_by_multirange', oprrest => 'contsel',
+  oprjoin => 'contjoinsel' },
+{ oid => '8069', oid_symbol => 'OID_MULTIRANGE_MULTIRANGE_CONTAINED_OP',
+  descr => 'is contained by',
+  oprname => '<@', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '@>(anymultirange,anymultirange)',
+  oprcode => 'multirange_contained_by_multirange', oprrest => 'contsel',
+  oprjoin => 'contjoinsel' },
+{ oid => '8106', oid_symbol => 'OID_RANGE_OVERLAPS_LEFT_MULTIRANGE_OP',
+  descr => 'overlaps or is left of',
+  oprname => '&<', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcode => 'range_overleft_multirange', oprrest => 'scalarltsel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8107', oid_symbol => 'OID_MULTIRANGE_OVERLAPS_LEFT_RANGE_OP',
+  descr => 'overlaps or is left of',
+  oprname => '&<', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcode => 'multirange_overleft_range', oprrest => 'scalarltsel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8108', oid_symbol => 'OID_MULTIRANGE_OVERLAPS_LEFT_MULTIRANGE_OP',
+  descr => 'overlaps or is left of',
+  oprname => '&<', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcode => 'multirange_overleft_multirange', oprrest => 'scalarltsel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8109', oid_symbol => 'OID_RANGE_OVERLAPS_RIGHT_MULTIRANGE_OP',
+  descr => 'overlaps or is right of',
+  oprname => '&>', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcode => 'range_overright_multirange', oprrest => 'scalargtsel',
+  oprjoin => 'scalargtjoinsel' },
+{ oid => '8110', oid_symbol => 'OID_MULTIRANGE_OVERLAPS_RIGHT_RANGE_OP',
+  descr => 'overlaps or is right of',
+  oprname => '&>', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcode => 'multirange_overright_range', oprrest => 'scalargtsel',
+  oprjoin => 'scalargtjoinsel' },
+{ oid => '8111', oid_symbol => 'OID_MULTIRANGE_OVERLAPS_RIGHT_MULTIRANGE_OP',
+  descr => 'overlaps or is right of',
+  oprname => '&>', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcode => 'multirange_overright_multirange', oprrest => 'scalargtsel',
+  oprjoin => 'scalargtjoinsel' },
+{ oid => '8091', oid_symbol => 'OID_RANGE_ADJACENT_MULTIRANGE_OP', descr => 'is adjacent to',
+  oprname => '-|-', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '-|-(anymultirange,anyrange)',
+  oprcode => 'range_adjacent_multirange', oprrest => 'scalarltsel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8092', oid_symbol => 'OID_MULTIRANGE_ADJACENT_RANGE_OP', descr => 'is adjacent to',
+  oprname => '-|-', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcom => '-|-(anyrange,anymultirange)',
+  oprcode => 'multirange_adjacent_range', oprrest => 'scalarltsel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8093', oid_symbol => 'OID_MULTIRANGE_ADJACENT_MULTIRANGE_OP', descr => 'is adjacent to',
+  oprname => '-|-', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '-|-(anymultirange,anymultirange)',
+  oprcode => 'multirange_adjacent_multirange', oprrest => 'scalarltsel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8117', descr => 'multirange union',
+  oprname => '+', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'anymultirange', oprcom => '+(anymultirange,anymultirange)',
+  oprcode => 'multirange_union' },
+{ oid => '8123', descr => 'multirange minus',
+  oprname => '-', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'anymultirange', oprcode => 'multirange_minus' },
+{ oid => '8129', descr => 'multirange intersect',
+  oprname => '*', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'anymultirange', oprcom => '*(anymultirange,anymultirange)',
+  oprcode => 'multirange_intersect' },
+{ oid => '8082', oid_symbol => 'OID_RANGE_BEFORE_MULTIRANGE_OP', descr => 'is left of',
+  oprname => '<<', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '>>(anymultirange,anyrange)',
+  oprcode => 'range_before_multirange', oprrest => 'scalarltsel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8083', oid_symbol => 'OID_MULTIRANGE_BEFORE_RANGE_OP', descr => 'is left of',
+  oprname => '<<', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcom => '>>(anyrange,anymultirange)',
+  oprcode => 'multirange_before_range', oprrest => 'scalarltsel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8084', oid_symbol => 'OID_MULTIRANGE_BEFORE_MULTIRANGE_OP', descr => 'is left of',
+  oprname => '<<', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '>>(anymultirange,anymultirange)',
+  oprcode => 'multirange_before_multirange', oprrest => 'scalarltsel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8085', oid_symbol => 'OID_RANGE_AFTER_MULTIRANGE_OP', descr => 'is right of',
+  oprname => '>>', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '<<(anymultirange,anyrange)',
+  oprcode => 'range_after_multirange', oprrest => 'scalargtsel',
+  oprjoin => 'scalargtjoinsel' },
+{ oid => '8086', oid_symbol => 'OID_MULTIRANGE_AFTER_RANGE_OP', descr => 'is right of',
+  oprname => '>>', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcom => '<<(anyrange,anymultirange)',
+  oprcode => 'multirange_after_range', oprrest => 'scalargtsel',
+  oprjoin => 'scalargtjoinsel' },
+{ oid => '8087', oid_symbol => 'OID_MULTIRANGE_AFTER_MULTIRANGE_OP', descr => 'is right of',
+  oprname => '>>', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '<<(anymultirange,anymultirange)',
+  oprcode => 'multirange_after_multirange', oprrest => 'scalargtsel',
+  oprjoin => 'scalargtjoinsel' },
 
 ]
diff --git a/src/include/catalog/pg_opfamily.dat b/src/include/catalog/pg_opfamily.dat
index 4004138d77..dfc13fd6e5 100644
--- a/src/include/catalog/pg_opfamily.dat
+++ b/src/include/catalog/pg_opfamily.dat
@@ -230,5 +230,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 4bce3ad8de..5eb505fdda 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -7208,6 +7208,14 @@
   proname => 'anycompatiblerange_out', provolatile => 's',
   prorettype => 'cstring', proargtypes => 'anycompatiblerange',
   prosrc => 'anycompatiblerange_out' },
+{ oid => '8154', descr => 'I/O',
+  proname => 'anycompatiblemultirange_in', provolatile => 's',
+  prorettype => 'anycompatiblemultirange', proargtypes => 'cstring oid int4',
+  prosrc => 'anycompatiblemultirange_in' },
+{ oid => '8155', descr => 'I/O',
+  proname => 'anycompatiblemultirange_out', provolatile => 's',
+  prorettype => 'cstring', proargtypes => 'anycompatiblemultirange',
+  prosrc => 'anycompatiblemultirange_out' },
 
 # tablesample method handlers
 { oid => '3313', descr => 'BERNOULLI tablesample method handler',
@@ -9797,6 +9805,10 @@
   descr => 'the smallest range which includes both of the given ranges',
   proname => 'range_merge', prorettype => 'anyrange',
   proargtypes => 'anyrange anyrange', prosrc => 'range_merge' },
+{ oid => '8143',
+  descr => 'the smallest range which includes the whole multirange',
+  proname => 'range_merge', prorettype => 'anyrange',
+  proargtypes => 'anymultirange', prosrc => 'range_merge_from_multirange' },
 { oid => '3868',
   proname => 'range_intersect', prorettype => 'anyrange',
   proargtypes => 'anyrange anyrange', prosrc => 'range_intersect' },
@@ -9846,6 +9858,13 @@
 { oid => '3169', descr => 'restriction selectivity for range operators',
   proname => 'rangesel', provolatile => 's', prorettype => 'float8',
   proargtypes => 'internal oid internal int4', prosrc => 'rangesel' },
+{ oid => '8133', descr => 'range aggregate by intersecting',
+  proname => 'range_intersect_agg_transfn', prorettype => 'anyrange',
+  proargtypes => 'anyrange anyrange', prosrc => 'range_intersect_agg_transfn'},
+{ oid => '8134', descr => 'range aggregate by intersecting',
+  proname => 'range_intersect_agg', prokind => 'a', proisstrict => 'f',
+  prorettype => 'anyrange', proargtypes => 'anyrange',
+  prosrc => 'aggregate_dummy'},
 
 { oid => '3914', descr => 'convert an int4 range to canonical form',
   proname => 'int4range_canonical', prorettype => 'int4range',
@@ -9914,6 +9933,258 @@
   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 => '8094', descr => 'lower bound of multirange',
+  proname => 'lower', prorettype => 'anyelement', proargtypes => 'anymultirange',
+  prosrc => 'multirange_lower' },
+{ oid => '8095', descr => 'upper bound of multirange',
+  proname => 'upper', prorettype => 'anyelement', proargtypes => 'anymultirange',
+  prosrc => 'multirange_upper' },
+{ oid => '8057', descr => 'is the multirange empty?',
+  proname => 'isempty', prorettype => 'bool',
+  proargtypes => 'anymultirange', prosrc => 'multirange_empty' },
+{ oid => '8096', descr => 'is the multirange\'s lower bound inclusive?',
+  proname => 'lower_inc', prorettype => 'bool', proargtypes => 'anymultirange',
+  prosrc => 'multirange_lower_inc' },
+{ oid => '8097', descr => 'is the multirange\'s upper bound inclusive?',
+  proname => 'upper_inc', prorettype => 'bool', proargtypes => 'anymultirange',
+  prosrc => 'multirange_upper_inc' },
+{ oid => '8098', descr => 'is the multirange\'s lower bound infinite?',
+  proname => 'lower_inf', prorettype => 'bool', proargtypes => 'anymultirange',
+  prosrc => 'multirange_lower_inf' },
+{ oid => '8099', descr => 'is the multirange\'s upper bound infinite?',
+  proname => 'upper_inf', prorettype => 'bool', proargtypes => 'anymultirange',
+  prosrc => 'multirange_upper_inf' },
+{ oid => '8008', descr => 'multirange typanalyze',
+  proname => 'multirange_typanalyze', provolatile => 's', prorettype => 'bool',
+  proargtypes => 'internal', prosrc => 'multirange_typanalyze' },
+{ oid => '8033',
+  proname => 'multirange_eq', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_eq' },
+{ oid => '8034',
+  proname => 'multirange_ne', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_ne' },
+{ oid => '8070',
+  proname => 'range_overlaps_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_overlaps_multirange' },
+{ oid => '8071',
+  proname => 'multirange_overlaps_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_overlaps_range' },
+{ oid => '8072',
+  proname => 'multirange_overlaps_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_overlaps_multirange' },
+{ oid => '8058',
+  proname => 'multirange_contains_elem', prorettype => 'bool',
+  proargtypes => 'anymultirange anyelement', prosrc => 'multirange_contains_elem' },
+{ oid => '8059',
+  proname => 'multirange_contains_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_contains_range' },
+{ oid => '8060',
+  proname => 'multirange_contains_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_contains_multirange' },
+{ oid => '8061',
+  proname => 'elem_contained_by_multirange', prorettype => 'bool',
+  proargtypes => 'anyelement anymultirange', prosrc => 'elem_contained_by_multirange' },
+{ oid => '8062',
+  proname => 'range_contained_by_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_contained_by_multirange' },
+{ oid => '8063',
+  proname => 'multirange_contained_by_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_contained_by_multirange' },
+{ oid => '8088',
+  proname => 'range_adjacent_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_adjacent_multirange' },
+{ oid => '8089',
+  proname => 'multirange_adjacent_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_adjacent_multirange' },
+{ oid => '8090',
+  proname => 'multirange_adjacent_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_adjacent_range' },
+{ oid => '8076',
+  proname => 'range_before_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_before_multirange' },
+{ oid => '8077',
+  proname => 'multirange_before_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_before_range' },
+{ oid => '8078',
+  proname => 'multirange_before_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_before_multirange' },
+{ oid => '8079',
+  proname => 'range_after_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_after_multirange' },
+{ oid => '8080',
+  proname => 'multirange_after_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_after_range' },
+{ oid => '8081',
+  proname => 'multirange_after_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_after_multirange' },
+{ oid => '8100',
+  proname => 'range_overleft_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_overleft_multirange' },
+{ oid => '8101',
+  proname => 'multirange_overleft_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_overleft_range' },
+{ oid => '8102',
+  proname => 'multirange_overleft_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_overleft_multirange' },
+{ oid => '8103',
+  proname => 'range_overright_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_overright_multirange' },
+{ oid => '8104',
+  proname => 'multirange_overright_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_overright_range' },
+{ oid => '8105',
+  proname => 'multirange_overright_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_overright_multirange' },
+{ oid => '8114',
+  proname => 'multirange_union', prorettype => 'anymultirange',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_union' },
+{ oid => '8120',
+  proname => 'multirange_minus', prorettype => 'anymultirange',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_minus' },
+{ oid => '8126',
+  proname => 'multirange_intersect', prorettype => 'anymultirange',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_intersect' },
+{ oid => '8022', descr => 'less-equal-greater',
+  proname => 'multirange_cmp', prorettype => 'int4',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_cmp' },
+{ oid => '8023',
+  proname => 'multirange_lt', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_lt' },
+{ oid => '8024',
+  proname => 'multirange_le', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_le' },
+{ oid => '8025',
+  proname => 'multirange_ge', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_ge' },
+{ oid => '8026',
+  proname => 'multirange_gt', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_gt' },
+{ oid => '8038', descr => 'hash a multirange',
+  proname => 'hash_multirange', prorettype => 'int4', proargtypes => 'anymultirange',
+  prosrc => 'hash_multirange' },
+{ oid => '8039', descr => 'hash a multirange',
+  proname => 'hash_multirange_extended', prorettype => 'int8',
+  proargtypes => 'anymultirange int8', prosrc => 'hash_multirange_extended' },
+
+{ oid => '8045', descr => 'int4multirange constructor',
+  proname => 'int4multirange', proisstrict => 'f',
+  prorettype => 'int4multirange', proargtypes => '',
+  prosrc => 'multirange_constructor0' },
+{ oid => '8146', descr => 'int4multirange constructor',
+  proname => 'int4multirange', proisstrict => 't',
+  prorettype => 'int4multirange', proargtypes => 'int4range',
+  prosrc => 'multirange_constructor1' },
+{ oid => '8046', descr => 'int4multirange constructor',
+  proname => 'int4multirange', provariadic => 'int4range', proisstrict => 'f',
+  prorettype => 'int4multirange', proargtypes => '_int4range',
+  proallargtypes => '{_int4range}', proargmodes => '{v}',
+  prosrc => 'multirange_constructor2' },
+{ oid => '8047', descr => 'nummultirange constructor',
+  proname => 'nummultirange', proisstrict => 'f',
+  prorettype => 'nummultirange', proargtypes => '',
+  prosrc => 'multirange_constructor0' },
+{ oid => '8147', descr => 'nummultirange constructor',
+  proname => 'nummultirange', proisstrict => 't',
+  prorettype => 'nummultirange', proargtypes => 'numrange',
+  prosrc => 'multirange_constructor1' },
+{ oid => '8048', descr => 'nummultirange constructor',
+  proname => 'nummultirange', provariadic => 'numrange', proisstrict => 'f',
+  prorettype => 'nummultirange', proargtypes => '_numrange',
+  proallargtypes => '{_numrange}', proargmodes => '{v}',
+  prosrc => 'multirange_constructor2' },
+{ oid => '8049', descr => 'tsmultirange constructor',
+  proname => 'tsmultirange', proisstrict => 'f',
+  prorettype => 'tsmultirange', proargtypes => '',
+  prosrc => 'multirange_constructor0' },
+{ oid => '8148', descr => 'tsmultirange constructor',
+  proname => 'tsmultirange', proisstrict => 't',
+  prorettype => 'tsmultirange', proargtypes => 'tsrange',
+  prosrc => 'multirange_constructor1' },
+{ oid => '8050', descr => 'tsmultirange constructor',
+  proname => 'tsmultirange', provariadic => 'tsrange', proisstrict => 'f',
+  prorettype => 'tsmultirange', proargtypes => '_tsrange',
+  proallargtypes => '{_tsrange}', proargmodes => '{v}',
+  prosrc => 'multirange_constructor2' },
+{ oid => '8051', descr => 'tstzmultirange constructor',
+  proname => 'tstzmultirange', proisstrict => 'f',
+  prorettype => 'tstzmultirange', proargtypes => '',
+  prosrc => 'multirange_constructor0' },
+{ oid => '8149', descr => 'tstzmultirange constructor',
+  proname => 'tstzmultirange', proisstrict => 't',
+  prorettype => 'tstzmultirange', proargtypes => 'tstzrange',
+  prosrc => 'multirange_constructor1' },
+{ oid => '8052', descr => 'tstzmultirange constructor',
+  proname => 'tstzmultirange', provariadic => 'tstzrange', proisstrict => 'f',
+  prorettype => 'tstzmultirange', proargtypes => '_tstzrange',
+  proallargtypes => '{_tstzrange}', proargmodes => '{v}',
+  prosrc => 'multirange_constructor2' },
+{ oid => '8053', descr => 'datemultirange constructor',
+  proname => 'datemultirange', proisstrict => 'f',
+  prorettype => 'datemultirange', proargtypes => '',
+  prosrc => 'multirange_constructor0' },
+{ oid => '8150', descr => 'datemultirange constructor',
+  proname => 'datemultirange', proisstrict => 't',
+  prorettype => 'datemultirange', proargtypes => 'daterange',
+  prosrc => 'multirange_constructor1' },
+{ oid => '8054', descr => 'datemultirange constructor',
+  proname => 'datemultirange', provariadic => 'daterange', proisstrict => 'f',
+  prorettype => 'datemultirange', proargtypes => '_daterange',
+  proallargtypes => '{_daterange}', proargmodes => '{v}',
+  prosrc => 'multirange_constructor2' },
+{ oid => '8055', descr => 'int8multirange constructor',
+  proname => 'int8multirange', proisstrict => 'f',
+  prorettype => 'int8multirange', proargtypes => '',
+  prosrc => 'multirange_constructor0' },
+{ oid => '8151', descr => 'int8multirange constructor',
+  proname => 'int8multirange', proisstrict => 't',
+  prorettype => 'int8multirange', proargtypes => 'int8range',
+  prosrc => 'multirange_constructor1' },
+{ oid => '8056', descr => 'int8multirange constructor',
+  proname => 'int8multirange', provariadic => 'int8range', proisstrict => 'f',
+  prorettype => 'int8multirange', proargtypes => '_int8range',
+  proallargtypes => '{_int8range}', proargmodes => '{v}',
+  prosrc => 'multirange_constructor2' },
+{ oid => '8152', descr => 'anymultirange cast',
+  proname => 'multirange', proisstrict => 't',
+  prorettype => 'anymultirange', proargtypes => 'anyrange',
+  prosrc => 'multirange_constructor1' },
+{ oid => '8130', descr => 'aggregate transition function',
+  proname => 'range_agg_transfn', proisstrict => 'f', prorettype => 'internal',
+  proargtypes => 'internal anyrange', prosrc => 'range_agg_transfn' },
+{ oid => '8131', descr => 'aggregate final function',
+  proname => 'range_agg_finalfn', proisstrict => 'f', prorettype => 'anymultirange',
+  proargtypes => 'internal anyrange', prosrc => 'range_agg_finalfn' },
+{ oid => '8132', descr => 'combine aggregate input into a multirange',
+  proname => 'range_agg', prokind => 'a', proisstrict => 'f',
+  prorettype => 'anymultirange', proargtypes => 'anyrange',
+  prosrc => 'aggregate_dummy' },
+{ oid => '8135', descr => 'range aggregate by intersecting',
+  proname => 'multirange_intersect_agg_transfn', prorettype => 'anymultirange',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_intersect_agg_transfn'},
+{ oid => '8136', descr => 'range aggregate by intersecting',
+  proname => 'range_intersect_agg', prokind => 'a', proisstrict => 'f',
+  prorettype => 'anymultirange', proargtypes => 'anymultirange',
+  prosrc => 'aggregate_dummy'},
+
 # date, time, timestamp constructors
 { oid => '3846', descr => 'construct date',
   proname => 'make_date', prorettype => 'date', proargtypes => 'int4 int4 int4',
@@ -10296,6 +10567,14 @@
   proname => 'binary_upgrade_set_next_array_pg_type_oid', provolatile => 'v',
   proparallel => 'r', prorettype => 'void', proargtypes => 'oid',
   prosrc => 'binary_upgrade_set_next_array_pg_type_oid' },
+{ oid => '8144', descr => 'for use by pg_upgrade',
+  proname => 'binary_upgrade_set_next_multirange_pg_type_oid', provolatile => 'v',
+  proparallel => 'r', prorettype => 'void', proargtypes => 'oid',
+  prosrc => 'binary_upgrade_set_next_multirange_pg_type_oid' },
+{ oid => '8145', descr => 'for use by pg_upgrade',
+  proname => 'binary_upgrade_set_next_multirange_array_pg_type_oid', provolatile => 'v',
+  proparallel => 'r', prorettype => 'void', proargtypes => 'oid',
+  prosrc => 'binary_upgrade_set_next_multirange_array_pg_type_oid' },
 { oid => '3585', descr => 'for use by pg_upgrade',
   proname => 'binary_upgrade_set_next_toast_pg_type_oid', provolatile => 'v',
   proparallel => 'r', prorettype => 'void', proargtypes => 'oid',
diff --git a/src/include/catalog/pg_range.dat b/src/include/catalog/pg_range.dat
index 479754c245..10060255c9 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',
+  rngmultitypid => 'int4multirange' },
 { rngtypid => 'numrange', rngsubtype => 'numeric',
   rngsubopc => 'btree/numeric_ops', rngcanonical => '-',
-  rngsubdiff => 'numrange_subdiff' },
+  rngsubdiff => 'numrange_subdiff', rngmultitypid => 'nummultirange' },
 { rngtypid => 'tsrange', rngsubtype => 'timestamp',
   rngsubopc => 'btree/timestamp_ops', rngcanonical => '-',
-  rngsubdiff => 'tsrange_subdiff' },
+  rngsubdiff => 'tsrange_subdiff', rngmultitypid => 'tsmultirange' },
 { rngtypid => 'tstzrange', rngsubtype => 'timestamptz',
   rngsubopc => 'btree/timestamptz_ops', rngcanonical => '-',
-  rngsubdiff => 'tstzrange_subdiff' },
+  rngsubdiff => 'tstzrange_subdiff', rngmultitypid => 'tstzmultirange' },
 { rngtypid => 'daterange', rngsubtype => 'date', rngsubopc => 'btree/date_ops',
-  rngcanonical => 'daterange_canonical', rngsubdiff => 'daterange_subdiff' },
+  rngcanonical => 'daterange_canonical', rngsubdiff => 'daterange_subdiff',
+  rngmultitypid => 'datemultirange' },
 { rngtypid => 'int8range', rngsubtype => 'int8', rngsubopc => 'btree/int8_ops',
-  rngcanonical => 'int8range_canonical', rngsubdiff => 'int8range_subdiff' },
+  rngcanonical => 'int8range_canonical', rngsubdiff => 'int8range_subdiff',
+  rngmultitypid => 'int8multirange' },
 
 ]
diff --git a/src/include/catalog/pg_range.h b/src/include/catalog/pg_range.h
index 98172bb1b6..60a8fde518 100644
--- a/src/include/catalog/pg_range.h
+++ b/src/include/catalog/pg_range.h
@@ -34,6 +34,9 @@ CATALOG(pg_range,3541,RangeRelationId)
 	/* OID of range's element type (subtype) */
 	Oid			rngsubtype BKI_LOOKUP(pg_type);
 
+	/* OID of the range's multirange type */
+	Oid			rngmultitypid BKI_LOOKUP(pg_type);
+
 	/* collation for this range type, or 0 */
 	Oid			rngcollation BKI_DEFAULT(0);
 
@@ -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 78f44af5e7..741090cfe5 100644
--- a/src/include/catalog/pg_type.dat
+++ b/src/include/catalog/pg_type.dat
@@ -500,6 +500,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 => 'R', 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 => 'R', 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 => 'R', 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 => 'R', 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 => 'R', 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 => 'R', 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
@@ -629,5 +663,16 @@
   typtype => 'p', typcategory => 'P', typinput => 'anycompatiblerange_in',
   typoutput => 'anycompatiblerange_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' },
+{ oid => '8153',
+  descr => 'pseudo-type representing a multirange over a polymorphic common type',
+  typname => 'anycompatiblemultirange', typlen => '-1', typbyval => 'f',
+  typtype => 'p', typcategory => 'P', typinput => 'anycompatiblemultirange_in',
+  typoutput => 'anycompatiblemultirange_out', typreceive => '-', typsend => '-',
+  typalign => 'd', typstorage => 'x' },
 
 ]
diff --git a/src/include/catalog/pg_type.h b/src/include/catalog/pg_type.h
index 7b37562648..63e7c940a2 100644
--- a/src/include/catalog/pg_type.h
+++ b/src/include/catalog/pg_type.h
@@ -263,6 +263,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 */
 
@@ -304,13 +305,15 @@ typedef FormData_pg_type *Form_pg_type;
 	 (typid) == ANYARRAYOID || \
 	 (typid) == ANYNONARRAYOID || \
 	 (typid) == ANYENUMOID || \
-	 (typid) == ANYRANGEOID)
+	 (typid) == ANYRANGEOID || \
+	 (typid) == ANYMULTIRANGEOID)
 
 #define IsPolymorphicTypeFamily2(typid)  \
 	((typid) == ANYCOMPATIBLEOID || \
 	 (typid) == ANYCOMPATIBLEARRAYOID || \
 	 (typid) == ANYCOMPATIBLENONARRAYOID || \
-	 (typid) == ANYCOMPATIBLERANGEOID)
+	 (typid) == ANYCOMPATIBLERANGEOID || \
+	 (typid) == ANYCOMPATIBLEMULTIRANGEOID)
 
 #endif							/* EXPOSE_TO_CLIENT_CODE */
 
@@ -369,4 +372,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/commands/typecmds.h b/src/include/commands/typecmds.h
index 23130895af..989914dfe1 100644
--- a/src/include/commands/typecmds.h
+++ b/src/include/commands/typecmds.h
@@ -29,6 +29,8 @@ extern ObjectAddress DefineRange(CreateRangeStmt *stmt);
 extern ObjectAddress AlterEnum(AlterEnumStmt *stmt);
 extern ObjectAddress DefineCompositeType(RangeVar *typevar, List *coldeflist);
 extern Oid	AssignTypeArrayOid(void);
+extern Oid	AssignTypeMultirangeOid(void);
+extern Oid	AssignTypeMultirangeArrayOid(void);
 
 extern ObjectAddress AlterDomainDefault(List *names, Node *defaultRaw);
 extern ObjectAddress AlterDomainNotNull(List *names, bool notNull);
diff --git a/src/include/utils/lsyscache.h b/src/include/utils/lsyscache.h
index c9c68e2f4f..d940d367f1 100644
--- a/src/include/utils/lsyscache.h
+++ b/src/include/utils/lsyscache.h
@@ -156,6 +156,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);
@@ -182,6 +183,8 @@ 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_collation(Oid rangeOid);
+extern Oid	get_range_multirange(Oid rangeOid);
+extern Oid	get_range_multirange_subtype(Oid multirangeOid);
 extern Oid	get_index_column_opclass(Oid index_oid, int attno);
 extern bool	get_index_isreplident(Oid index_oid);
 extern bool get_index_isvalid(Oid index_oid);
diff --git a/src/include/utils/multirangetypes.h b/src/include/utils/multirangetypes.h
new file mode 100644
index 0000000000..469444776c
--- /dev/null
+++ b/src/include/utils/multirangetypes.h
@@ -0,0 +1,103 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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
+{
+	int32		vl_len_;		/* 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);
+extern bool multirange_contains_elem_internal(TypeCacheEntry *typcache, MultirangeType *mr,
+											  Datum elem);
+extern bool multirange_contains_range_internal(TypeCacheEntry *typcache, MultirangeType *mr,
+											   RangeType *r);
+extern bool multirange_contains_multirange_internal(TypeCacheEntry *typcache,
+													MultirangeType *mr1,
+													MultirangeType *mr2);
+extern bool range_overlaps_multirange_internal(TypeCacheEntry *typcache, RangeType *r,
+											   MultirangeType *mr);
+extern bool multirange_overlaps_multirange_internal(TypeCacheEntry *typcache,
+													MultirangeType *mr1,
+													MultirangeType *mr2);
+extern bool range_before_multirange_internal(TypeCacheEntry *typcache, RangeType *r,
+											 MultirangeType *mr);
+extern bool range_after_multirange_internal(TypeCacheEntry *typcache, RangeType *r,
+											MultirangeType *mr);
+extern bool multirange_before_multirange_internal(TypeCacheEntry *typcache,
+												  MultirangeType *mr1,
+												  MultirangeType *mr2);
+extern MultirangeType *multirange_minus_internal(Oid mltrngtypoid,
+												 TypeCacheEntry *rangetyp,
+												 int32 range_count1,
+												 RangeType **ranges1,
+												 int32 range_count2,
+												 RangeType **ranges2);
+extern MultirangeType *multirange_intersect_internal(Oid mltrngtypoid,
+													 TypeCacheEntry *rangetyp,
+													 int32 range_count1,
+													 RangeType **ranges1,
+													 int32 range_count2,
+													 RangeType **ranges2);
+
+/* 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 0cbbf09033..204aee4054 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)
 
@@ -92,13 +94,21 @@ typedef struct
  * prototypes for functions defined in rangetypes.c
  */
 
-extern bool range_contains_elem_internal(TypeCacheEntry *typcache, const RangeType *r, Datum val);
-
 /* internal versions of the above */
+extern int	range_cmp_internal(TypeCacheEntry *typcache, const RangeType *r1,
+							   const RangeType *r2);
+extern uint32 hash_range_internal(TypeCacheEntry *typcache, const RangeType *r);
+extern uint64 hash_range_extended_internal(TypeCacheEntry *typcache, const RangeType *r,
+										   Datum seed);
+extern Datum range_lower_internal(TypeCacheEntry *typcache, const RangeType *r1,
+								  bool *isnull);
+extern Datum range_upper_internal(TypeCacheEntry *typcache, const RangeType *r1,
+								  bool *isnull);
 extern bool range_eq_internal(TypeCacheEntry *typcache, const RangeType *r1,
 							  const RangeType *r2);
 extern bool range_ne_internal(TypeCacheEntry *typcache, const RangeType *r1,
 							  const RangeType *r2);
+extern bool range_contains_elem_internal(TypeCacheEntry *typcache, const RangeType *r, Datum val);
 extern bool range_contains_internal(TypeCacheEntry *typcache, const RangeType *r1,
 									const RangeType *r2);
 extern bool range_contained_by_internal(TypeCacheEntry *typcache, const RangeType *r1,
@@ -115,6 +125,12 @@ extern bool range_overleft_internal(TypeCacheEntry *typcache, const RangeType *r
 									const RangeType *r2);
 extern bool range_overright_internal(TypeCacheEntry *typcache, const RangeType *r1,
 									 const RangeType *r2);
+extern RangeType *range_union_internal(TypeCacheEntry *typcache, RangeType *r1,
+									   RangeType *r2, bool strict);
+extern RangeType *range_minus_internal(TypeCacheEntry *typcache, RangeType *r1,
+									   RangeType *r2);
+extern RangeType *range_intersect_internal(TypeCacheEntry *typcache, const RangeType *r1,
+										   const RangeType *r2);
 
 /* assorted support functions */
 extern TypeCacheEntry *range_get_typcache(FunctionCallInfo fcinfo,
@@ -125,6 +141,7 @@ extern void range_deserialize(TypeCacheEntry *typcache, const RangeType *range,
 							  RangeBound *lower, RangeBound *upper,
 							  bool *empty);
 extern char range_get_flags(const RangeType *range);
+extern bool range_has_flag(const RangeType *range, char flag);
 extern void range_set_contain_empty(RangeType *range);
 extern RangeType *make_range(TypeCacheEntry *typcache, RangeBound *lower,
 							 RangeBound *upper, bool empty);
@@ -132,8 +149,12 @@ extern int range_cmp_bounds(TypeCacheEntry *typcache, const RangeBound *b1,
 							const RangeBound *b2);
 extern int range_cmp_bound_values(TypeCacheEntry *typcache, const RangeBound *b1,
 								  const RangeBound *b2);
-extern bool bounds_adjacent(TypeCacheEntry *typcache, RangeBound bound1,
-							RangeBound bound2);
+extern int range_compare(const void *key1, const void *key2, void *arg);
+extern bool bounds_adjacent(TypeCacheEntry *typcache, const RangeBound bound1,
+							const RangeBound bound2);
 extern RangeType *make_empty_range(TypeCacheEntry *typcache);
+extern bool range_split_internal(TypeCacheEntry *typcache, const RangeType *r1,
+								 const RangeType *r2, RangeType **output1,
+								 RangeType **output2);
 
 #endif							/* RANGETYPES_H */
diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h
index f27b73d76d..e8f393520a 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -79,6 +79,7 @@ enum SysCacheIdentifier
 	PUBLICATIONOID,
 	PUBLICATIONREL,
 	PUBLICATIONRELMAP,
+	RANGEMULTIRANGE,
 	RANGETYPE,
 	RELNAMENSP,
 	RELOID,
diff --git a/src/include/utils/typcache.h b/src/include/utils/typcache.h
index cdd20e56d7..33f332a141 100644
--- a/src/include/utils/typcache.h
+++ b/src/include/utils/typcache.h
@@ -101,6 +101,11 @@ typedef struct TypeCacheEntry
 	FmgrInfo	rng_subdiff_finfo;	/* difference function, if any */
 
 	/*
+	 * Fields computed when TYPCACHE_MULTIRANGE_INFO is required.
+	 */
+	struct TypeCacheEntry *rngtype;	/* multirange's range underlying type */
+
+	/*
 	 * Domain's base type and typmod if it's a domain type.  Zeroes if not
 	 * domain, or if information hasn't been requested.
 	 */
@@ -143,6 +148,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 828ff5a288..1e3ff02289 100644
--- a/src/pl/plpgsql/src/pl_comp.c
+++ b/src/pl/plpgsql/src/pl_comp.c
@@ -513,6 +513,8 @@ do_compile(FunctionCallInfo fcinfo,
 					else if (rettypeid == ANYRANGEOID ||
 							 rettypeid == ANYCOMPATIBLERANGEOID)
 						rettypeid = INT4RANGEOID;
+					else if (rettypeid == ANYMULTIRANGEOID)
+						rettypeid = INT4MULTIRANGEOID;
 					else		/* ANYELEMENT or ANYNONARRAY or ANYCOMPATIBLE */
 						rettypeid = INT4OID;
 					/* XXX what could we use for ANYENUM? */
@@ -2089,6 +2091,7 @@ build_datatype(HeapTuple typeTup, int32 typmod,
 		case TYPTYPE_BASE:
 		case TYPTYPE_ENUM:
 		case TYPTYPE_RANGE:
+		case TYPTYPE_MULTIRANGE:
 			typ->ttype = PLPGSQL_TTYPE_SCALAR;
 			break;
 		case TYPTYPE_COMPOSITE:
@@ -2507,6 +2510,9 @@ plpgsql_resolve_polymorphic_argtypes(int numargs,
 				case ANYCOMPATIBLERANGEOID:
 					argtypes[i] = INT4RANGEOID;
 					break;
+				case ANYMULTIRANGEOID:
+					argtypes[i] = INT4MULTIRANGEOID;
+					break;
 				default:
 					break;
 			}
diff --git a/src/test/regress/expected/dependency.out b/src/test/regress/expected/dependency.out
index b4a11b8aa9..8232795148 100644
--- a/src/test/regress/expected/dependency.out
+++ b/src/test/regress/expected/dependency.out
@@ -140,6 +140,7 @@ owner of sequence deptest_a_seq
 owner of table deptest
 owner of function deptest_func()
 owner of type deptest_enum
+owner of type deptest_multirange
 owner of type deptest_range
 owner of table deptest2
 owner of sequence ss1
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..f8ca946d6a
--- /dev/null
+++ b/src/test/regress/expected/multirangetypes.out
@@ -0,0 +1,2436 @@
+-- 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)
+
+--
+-- test casts, both a built-in range type and a user-defined one:
+--
+select 'empty'::int4range::int4multirange;
+ int4multirange 
+----------------
+ {}
+(1 row)
+
+select int4range(1, 3)::int4multirange;
+ int4range 
+-----------
+ {[1,3)}
+(1 row)
+
+select int4range(1, null)::int4multirange;
+ int4range 
+-----------
+ {[1,)}
+(1 row)
+
+select int4range(null, null)::int4multirange;
+ int4range 
+-----------
+ {(,)}
+(1 row)
+
+select 'empty'::textrange::textmultirange;
+ textmultirange 
+----------------
+ {}
+(1 row)
+
+select textrange('a', 'c')::textmultirange;
+ textrange 
+-----------
+ {[a,c)}
+(1 row)
+
+select textrange('a', null)::textmultirange;
+ textrange 
+-----------
+ {[a,)}
+(1 row)
+
+select textrange(null, null)::textmultirange;
+ textrange 
+-----------
+ {(,)}
+(1 row)
+
+--
+-- create some test data and test the operators
+--
+CREATE TABLE nummultirange_test (nmr NUMMULTIRANGE);
+CREATE INDEX nummultirange_test_btree ON nummultirange_test(nmr);
+INSERT INTO nummultirange_test VALUES('{}');
+INSERT INTO nummultirange_test VALUES('{[,)}');
+INSERT INTO nummultirange_test VALUES('{[3,]}');
+INSERT INTO nummultirange_test VALUES('{[,), [3,]}');
+INSERT INTO nummultirange_test VALUES('{[, 5)}');
+INSERT INTO nummultirange_test VALUES(nummultirange());
+INSERT INTO nummultirange_test VALUES(nummultirange(variadic '{}'::numrange[]));
+INSERT INTO nummultirange_test VALUES(nummultirange(numrange(1.1, 2.2)));
+INSERT INTO nummultirange_test VALUES('{empty}');
+INSERT INTO nummultirange_test VALUES(nummultirange(numrange(1.7, 1.7, '[]'), numrange(1.7, 1.9)));
+INSERT INTO nummultirange_test VALUES(nummultirange(numrange(1.7, 1.7, '[]'), numrange(1.9, 2.1)));
+SELECT nmr, isempty(nmr), lower(nmr), upper(nmr) FROM nummultirange_test ORDER BY nmr;
+          nmr          | isempty | lower | upper 
+-----------------------+---------+-------+-------
+ {}                    | t       |       |      
+ {}                    | t       |       |      
+ {}                    | t       |       |      
+ {}                    | t       |       |      
+ {(,5)}                | f       |       |     5
+ {(,)}                 | f       |       |      
+ {(,)}                 | f       |       |      
+ {[1.1,2.2)}           | f       |   1.1 |   2.2
+ {[1.7,1.7],[1.9,2.1)} | f       |   1.7 |   2.1
+ {[1.7,1.9)}           | f       |   1.7 |   1.9
+ {[3,)}                | f       |     3 |      
+(11 rows)
+
+SELECT nmr, lower_inc(nmr), lower_inf(nmr), upper_inc(nmr), upper_inf(nmr) FROM nummultirange_test ORDER BY nmr;
+          nmr          | lower_inc | lower_inf | upper_inc | upper_inf 
+-----------------------+-----------+-----------+-----------+-----------
+ {}                    | f         | f         | f         | f
+ {}                    | f         | f         | f         | f
+ {}                    | f         | f         | f         | f
+ {}                    | f         | f         | f         | f
+ {(,5)}                | f         | t         | f         | f
+ {(,)}                 | f         | t         | f         | t
+ {(,)}                 | f         | t         | f         | t
+ {[1.1,2.2)}           | t         | f         | f         | f
+ {[1.7,1.7],[1.9,2.1)} | t         | f         | f         | f
+ {[1.7,1.9)}           | t         | f         | f         | f
+ {[3,)}                | t         | f         | f         | t
+(11 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr = '{}';
+ nmr 
+-----
+ {}
+ {}
+ {}
+ {}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr = '{(,5)}';
+  nmr   
+--------
+ {(,5)}
+(1 row)
+
+SELECT * FROM nummultirange_test WHERE nmr = '{[3,)}';
+  nmr   
+--------
+ {[3,)}
+(1 row)
+
+SELECT * FROM nummultirange_test WHERE nmr = '{[1.7,1.7]}';
+ nmr 
+-----
+(0 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr = '{[1.7,1.7],[1.9,2.1)}';
+          nmr          
+-----------------------
+ {[1.7,1.7],[1.9,2.1)}
+(1 row)
+
+SELECT * FROM nummultirange_test WHERE nmr < '{}';
+ nmr 
+-----
+(0 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr < '{[-1000.0, -1000.0]}';
+  nmr   
+--------
+ {}
+ {(,)}
+ {(,)}
+ {(,5)}
+ {}
+ {}
+ {}
+(7 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr < '{[0.0, 1.0]}';
+  nmr   
+--------
+ {}
+ {(,)}
+ {(,)}
+ {(,5)}
+ {}
+ {}
+ {}
+(7 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr < '{[1000.0, 1001.0]}';
+          nmr          
+-----------------------
+ {}
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+ {}
+ {}
+ {[1.1,2.2)}
+ {}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(11 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr <= '{}';
+ nmr 
+-----
+ {}
+ {}
+ {}
+ {}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr <= '{[3,)}';
+          nmr          
+-----------------------
+ {}
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+ {}
+ {}
+ {[1.1,2.2)}
+ {}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(11 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr >= '{}';
+          nmr          
+-----------------------
+ {}
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+ {}
+ {}
+ {[1.1,2.2)}
+ {}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(11 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr >= '{[3,)}';
+  nmr   
+--------
+ {[3,)}
+(1 row)
+
+SELECT * FROM nummultirange_test WHERE nmr > '{}';
+          nmr          
+-----------------------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+ {[1.1,2.2)}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(7 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr > '{[-1000.0, -1000.0]}';
+          nmr          
+-----------------------
+ {[3,)}
+ {[1.1,2.2)}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr > '{[0.0, 1.0]}';
+          nmr          
+-----------------------
+ {[3,)}
+ {[1.1,2.2)}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr > '{[1000.0, 1001.0]}';
+ nmr 
+-----
+(0 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr <> '{}';
+          nmr          
+-----------------------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+ {[1.1,2.2)}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(7 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr <> '{(,5)}';
+          nmr          
+-----------------------
+ {}
+ {(,)}
+ {[3,)}
+ {(,)}
+ {}
+ {}
+ {[1.1,2.2)}
+ {}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(10 rows)
+
+select nummultirange(numrange(2.0, 1.0));
+ERROR:  range lower bound must be less than or equal to range upper bound
+select nummultirange(numrange(5.0, 6.0), numrange(1.0, 2.0));
+     nummultirange     
+-----------------------
+ {[1.0,2.0),[5.0,6.0)}
+(1 row)
+
+-- overlaps
+SELECT * FROM nummultirange_test WHERE range_overlaps_multirange(numrange(4.0, 4.2), nmr);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE numrange(4.0, 4.2) && nmr;
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE multirange_overlaps_range(nmr, numrange(4.0, 4.2));
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr && numrange(4.0, 4.2);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE multirange_overlaps_multirange(nmr, nummultirange(numrange(4.0, 4.2), numrange(6.0, 7.0)));
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr && nummultirange(numrange(4.0, 4.2), numrange(6.0, 7.0));
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr && nummultirange(numrange(6.0, 7.0));
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+(3 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr && nummultirange(numrange(6.0, 7.0), numrange(8.0, 9.0));
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+(3 rows)
+
+-- mr contains x
+SELECT * FROM nummultirange_test WHERE multirange_contains_elem(nmr, 4.0);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr @> 4.0;
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE multirange_contains_range(nmr, numrange(4.0, 4.2));
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr @> numrange(4.0, 4.2);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE multirange_contains_multirange(nmr, '{[4.0,4.2), [6.0, 8.0)}');
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+(3 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr @> '{[4.0,4.2), [6.0, 8.0)}'::nummultirange;
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+(3 rows)
+
+-- x is contained by mr
+SELECT * FROM nummultirange_test WHERE elem_contained_by_multirange(4.0, nmr);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE 4.0 <@ nmr;
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE range_contained_by_multirange(numrange(4.0, 4.2), nmr);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE numrange(4.0, 4.2) <@ nmr;
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE multirange_contained_by_multirange('{[4.0,4.2), [6.0, 8.0)}', nmr);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+(3 rows)
+
+SELECT * FROM nummultirange_test WHERE '{[4.0,4.2), [6.0, 8.0)}'::nummultirange <@ nmr;
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+(3 rows)
+
+-- overlaps
+SELECT 'empty'::numrange && nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT 'empty'::numrange && nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() && 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) && 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() && nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() && nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) && nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) && nummultirange(numrange(1,2), numrange(7,8));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(7,8)) && nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) && nummultirange(numrange(1,2), numrange(3.5,8));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(3.5,8)) && numrange(3,4);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(3.5,8)) && nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+-- contains
+SELECT nummultirange() @> nummultirange();
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange() @> 'empty'::numrange;
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(null,null)) @> numrange(1,2);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(null,null)) @> numrange(null,2);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(null,null)) @> numrange(2,null);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(null,5)) @> numrange(null,3);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(null,5)) @> numrange(null,8);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(5,null)) @> numrange(8,null);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(5,null)) @> numrange(3,null);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,5)) @> numrange(8,9);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,5)) @> numrange(3,9);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,5)) @> numrange(1,4);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,5)) @> numrange(1,5);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(-4,-2), numrange(1,5)) @> numrange(1,5);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,5), numrange(8,9)) @> numrange(1,5);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,5), numrange(8,9)) @> numrange(6,7);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,5), numrange(6,9)) @> numrange(6,7);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[1,5)}'::nummultirange @> '{[1,5)}';
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[-4,-2), [1,5)}'::nummultirange @> '{[1,5)}';
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[1,5), [8,9)}'::nummultirange @> '{[1,5)}';
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[1,5), [8,9)}'::nummultirange @> '{[6,7)}';
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT '{[1,5), [6,9)}'::nummultirange @> '{[6,7)}';
+ ?column? 
+----------
+ t
+(1 row)
+
+-- is contained by
+SELECT nummultirange() <@ nummultirange();
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT 'empty'::numrange <@ nummultirange();
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,2) <@ nummultirange(numrange(null,null));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(null,2) <@ nummultirange(numrange(null,null));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(2,null) <@ nummultirange(numrange(null,null));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(null,3) <@ nummultirange(numrange(null,5));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(null,8) <@ nummultirange(numrange(null,5));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(8,null) <@ nummultirange(numrange(5,null));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(3,null) <@ nummultirange(numrange(5,null));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(8,9) <@ nummultirange(numrange(1,5));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(3,9) <@ nummultirange(numrange(1,5));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(1,4) <@ nummultirange(numrange(1,5));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,5) <@ nummultirange(numrange(1,5));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,5) <@ nummultirange(numrange(-4,-2), numrange(1,5));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,5) <@ nummultirange(numrange(1,5), numrange(8,9));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(6,7) <@ nummultirange(numrange(1,5), numrange(8,9));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(6,7) <@ nummultirange(numrange(1,5), numrange(6,9));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[1,5)}' <@ '{[1,5)}'::nummultirange;
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[1,5)}' <@ '{[-4,-2), [1,5)}'::nummultirange;
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[1,5)}' <@ '{[1,5), [8,9)}'::nummultirange;
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[6,7)}' <@ '{[1,5), [8,9)}'::nummultirange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT '{[6,7)}' <@ '{[1,5), [6,9)}'::nummultirange;
+ ?column? 
+----------
+ t
+(1 row)
+
+-- overleft
+SELECT 'empty'::numrange &< nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT 'empty'::numrange &< nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() &< 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) &< 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() &< nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) &< nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() &< nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(6,7) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(1,2) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,4) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,6) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(3.5,6) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(6,7)) &< numrange(3,4);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) &< numrange(3,4);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,4)) &< numrange(3,4);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,6)) &< numrange(3,4);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3.5,6)) &< numrange(3,4);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(6,7)) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,4)) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,6)) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3.5,6)) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+-- overright
+SELECT nummultirange() &> 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) &> 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT 'empty'::numrange &> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT 'empty'::numrange &> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() &> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() &> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) &> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> numrange(6,7);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> numrange(1,2);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> numrange(1,4);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> numrange(1,6);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> numrange(3.5,6);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(3,4) &> nummultirange(numrange(6,7));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(3,4) &> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(3,4) &> nummultirange(numrange(1,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(3,4) &> nummultirange(numrange(1,6));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(3,4) &> nummultirange(numrange(3.5,6));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(6,7));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(1,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(1,6));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(3.5,6));
+ ?column? 
+----------
+ f
+(1 row)
+
+-- meets
+SELECT 'empty'::numrange -|- nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT 'empty'::numrange -|- nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() -|- 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() -|- nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() -|- nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(1,2) -|- nummultirange(numrange(2,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,2) -|- nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- numrange(2,4);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- numrange(3,4);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- nummultirange(numrange(2,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(5,6)) -|- nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(5,6)) -|- nummultirange(numrange(6,7));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(5,6)) -|- nummultirange(numrange(8,9));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- nummultirange(numrange(2,4), numrange(6,7));
+ ?column? 
+----------
+ t
+(1 row)
+
+-- strictly left
+select 'empty'::numrange << nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(1,2) << nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(1,2) << nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+select numrange(1,2) << nummultirange(numrange(0,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(1,2) << nummultirange(numrange(0,4), numrange(7,8));
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange() << 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange() << numrange(1,2);
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(3,4)) << numrange(3,6);
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(0,2)) << numrange(3,6);
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange(numrange(0,2), numrange(7,8)) << numrange(3,6);
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(-4,-2), numrange(0,2)) << numrange(3,6);
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange() << nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange() << nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(1,2)) << nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(1,2)) << nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(1,2)) << nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange(numrange(1,2)) << nummultirange(numrange(3,4), numrange(7,8));
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange(numrange(1,2), numrange(4,5)) << nummultirange(numrange(3,4), numrange(7,8));
+ ?column? 
+----------
+ f
+(1 row)
+
+-- strictly right
+select nummultirange() >> 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange() >> numrange(1,2);
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(3,4)) >> numrange(1,2);
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange(numrange(0,4)) >> numrange(1,2);
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(0,4), numrange(7,8)) >> numrange(1,2);
+ ?column? 
+----------
+ f
+(1 row)
+
+select 'empty'::numrange >> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(1,2) >> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(3,6) >> nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(3,6) >> nummultirange(numrange(0,2));
+ ?column? 
+----------
+ t
+(1 row)
+
+select numrange(3,6) >> nummultirange(numrange(0,2), numrange(7,8));
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(3,6) >> nummultirange(numrange(-4,-2), numrange(0,2));
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange() >> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(1,2)) >> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange() >> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(1,2)) >> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(3,4)) >> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange(numrange(3,4), numrange(7,8)) >> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange(numrange(3,4), numrange(7,8)) >> nummultirange(numrange(1,2), numrange(4,5));
+ ?column? 
+----------
+ f
+(1 row)
+
+-- union
+SELECT nummultirange() + nummultirange();
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange() + nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) + nummultirange();
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) + nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) + nummultirange(numrange(2,4));
+ ?column? 
+----------
+ {[1,4)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) + nummultirange(numrange(3,4));
+   ?column?    
+---------------
+ {[1,2),[3,4)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) + nummultirange(numrange(2,4));
+ ?column? 
+----------
+ {[1,5)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) + nummultirange(numrange(3,4));
+   ?column?    
+---------------
+ {[1,2),[3,5)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) + nummultirange(numrange(0,9));
+ ?column? 
+----------
+ {[0,9)}
+(1 row)
+
+-- merge
+SELECT range_merge(nummultirange());
+ range_merge 
+-------------
+ empty
+(1 row)
+
+SELECT range_merge(nummultirange(numrange(1,2)));
+ range_merge 
+-------------
+ [1,2)
+(1 row)
+
+SELECT range_merge(nummultirange(numrange(1,2), numrange(7,8)));
+ range_merge 
+-------------
+ [1,8)
+(1 row)
+
+-- minus
+SELECT nummultirange() - nummultirange();
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange() - nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) - nummultirange();
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(3,4)) - nummultirange();
+   ?column?    
+---------------
+ {[1,2),[3,4)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) - nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) - nummultirange(numrange(2,4));
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) - nummultirange(numrange(3,4));
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {[2,4)}
+(1 row)
+
+SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(2,3));
+   ?column?    
+---------------
+ {[1,2),[3,4)}
+(1 row)
+
+SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(0,8));
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(0,2));
+ ?column? 
+----------
+ {[2,4)}
+(1 row)
+
+SELECT nummultirange(numrange(1,8)) - nummultirange(numrange(0,2), numrange(3,4));
+   ?column?    
+---------------
+ {[2,3),[4,8)}
+(1 row)
+
+SELECT nummultirange(numrange(1,8)) - nummultirange(numrange(2,3), numrange(5,null));
+   ?column?    
+---------------
+ {[1,2),[3,5)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(-2,0));
+   ?column?    
+---------------
+ {[1,2),[4,5)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(2,4));
+   ?column?    
+---------------
+ {[1,2),[4,5)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(3,5));
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(0,9));
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange(numrange(1,3), numrange(4,5)) - nummultirange(numrange(2,9));
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(8,9));
+   ?column?    
+---------------
+ {[1,2),[4,5)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(-2,0), numrange(8,9));
+   ?column?    
+---------------
+ {[1,2),[4,5)}
+(1 row)
+
+-- intersection
+SELECT nummultirange() * nummultirange();
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange() * nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) * nummultirange();
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT '{[1,3)}'::nummultirange * '{[1,5)}'::nummultirange;
+ ?column? 
+----------
+ {[1,3)}
+(1 row)
+
+SELECT '{[1,3)}'::nummultirange * '{[0,5)}'::nummultirange;
+ ?column? 
+----------
+ {[1,3)}
+(1 row)
+
+SELECT '{[1,3)}'::nummultirange * '{[0,2)}'::nummultirange;
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT '{[1,3)}'::nummultirange * '{[2,5)}'::nummultirange;
+ ?column? 
+----------
+ {[2,3)}
+(1 row)
+
+SELECT '{[1,4)}'::nummultirange * '{[2,3)}'::nummultirange;
+ ?column? 
+----------
+ {[2,3)}
+(1 row)
+
+SELECT '{[1,4)}'::nummultirange * '{[0,2), [3,5)}'::nummultirange;
+   ?column?    
+---------------
+ {[1,2),[3,4)}
+(1 row)
+
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[0,8), [9,12)}'::nummultirange;
+       ?column?       
+----------------------
+ {[1,4),[7,8),[9,10)}
+(1 row)
+
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[9,12)}'::nummultirange;
+ ?column? 
+----------
+ {[9,10)}
+(1 row)
+
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[-5,-4), [5,6), [9,12)}'::nummultirange;
+ ?column? 
+----------
+ {[9,10)}
+(1 row)
+
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[0,2), [3,8), [9,12)}'::nummultirange;
+          ?column?          
+----------------------------
+ {[1,2),[3,4),[7,8),[9,10)}
+(1 row)
+
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[0,2), [3,8), [9,12)}'::nummultirange;
+          ?column?          
+----------------------------
+ {[1,2),[3,4),[7,8),[9,10)}
+(1 row)
+
+--
+-- range_agg function
+--
+create table reservations ( room_id integer not null, booked_during daterange );
+insert into reservations values
+-- 1: has a meets and a gap
+(1, daterange('2018-07-01', '2018-07-07')),
+(1, daterange('2018-07-07', '2018-07-14')),
+(1, daterange('2018-07-20', '2018-07-22')),
+-- 2: just a single row
+(2, daterange('2018-07-01', '2018-07-03')),
+-- 3: one null range
+(3, NULL),
+-- 4: two null ranges
+(4, NULL),
+(4, NULL),
+-- 5: a null range and a non-null range
+(5, NULL),
+(5, daterange('2018-07-01', '2018-07-03')),
+-- 6: has overlap
+(6, daterange('2018-07-01', '2018-07-07')),
+(6, daterange('2018-07-05', '2018-07-10')),
+-- 7: two ranges that meet: no gap or overlap
+(7, daterange('2018-07-01', '2018-07-07')),
+(7, daterange('2018-07-07', '2018-07-14')),
+-- 8: an empty range
+(8, 'empty'::daterange)
+;
+SELECT   room_id, range_agg(booked_during)
+FROM     reservations
+GROUP BY room_id
+ORDER BY room_id;
+ room_id |                     range_agg                     
+---------+---------------------------------------------------
+       1 | {[07-01-2018,07-14-2018),[07-20-2018,07-22-2018)}
+       2 | {[07-01-2018,07-03-2018)}
+       3 | 
+       4 | 
+       5 | {[07-01-2018,07-03-2018)}
+       6 | {[07-01-2018,07-10-2018)}
+       7 | {[07-01-2018,07-14-2018)}
+       8 | {}
+(8 rows)
+
+-- range_agg on a custom range type too
+SELECT  range_agg(r)
+FROM    (VALUES
+          ('[a,c]'::textrange),
+          ('[b,b]'::textrange),
+          ('[c,f]'::textrange),
+          ('[g,h)'::textrange),
+          ('[h,j)'::textrange)
+        ) t(r);
+   range_agg   
+---------------
+ {[a,f],[g,j)}
+(1 row)
+
+select range_intersect_agg(nmr) from nummultirange_test;
+ range_intersect_agg 
+---------------------
+ {}
+(1 row)
+
+select range_intersect_agg(nmr) from nummultirange_test where false;
+ range_intersect_agg 
+---------------------
+ 
+(1 row)
+
+-- test with just one input:
+select range_intersect_agg(nmr) from (values ('{[1,2]}'::nummultirange)) t(nmr);
+ range_intersect_agg 
+---------------------
+ {[1,2]}
+(1 row)
+
+select range_intersect_agg(nmr) from nummultirange_test where nmr @> 4.0;
+ range_intersect_agg 
+---------------------
+ {[3,5)}
+(1 row)
+
+create table nummultirange_test2(nmr nummultirange);
+create index nummultirange_test2_hash_idx on nummultirange_test2 using hash (nmr);
+INSERT INTO nummultirange_test2 VALUES('{[, 5)}');
+INSERT INTO nummultirange_test2 VALUES(nummultirange(numrange(1.1, 2.2)));
+INSERT INTO nummultirange_test2 VALUES(nummultirange(numrange(1.1, 2.2)));
+INSERT INTO nummultirange_test2 VALUES(nummultirange(numrange(1.1, 2.2,'()')));
+INSERT INTO nummultirange_test2 VALUES('{}');
+select * from nummultirange_test2 where nmr = '{}';
+ nmr 
+-----
+ {}
+(1 row)
+
+select * from nummultirange_test2 where nmr = nummultirange(numrange(1.1, 2.2));
+     nmr     
+-------------
+ {[1.1,2.2)}
+ {[1.1,2.2)}
+(2 rows)
+
+select * from nummultirange_test2 where nmr = nummultirange(numrange(1.1, 2.3));
+ nmr 
+-----
+(0 rows)
+
+set enable_nestloop=t;
+set enable_hashjoin=f;
+set enable_mergejoin=f;
+select * from nummultirange_test natural join nummultirange_test2 order by nmr;
+     nmr     
+-------------
+ {}
+ {}
+ {}
+ {}
+ {(,5)}
+ {[1.1,2.2)}
+ {[1.1,2.2)}
+(7 rows)
+
+set enable_nestloop=f;
+set enable_hashjoin=t;
+set enable_mergejoin=f;
+select * from nummultirange_test natural join nummultirange_test2 order by nmr;
+     nmr     
+-------------
+ {}
+ {}
+ {}
+ {}
+ {(,5)}
+ {[1.1,2.2)}
+ {[1.1,2.2)}
+(7 rows)
+
+set enable_nestloop=f;
+set enable_hashjoin=f;
+set enable_mergejoin=t;
+select * from nummultirange_test natural join nummultirange_test2 order by nmr;
+     nmr     
+-------------
+ {}
+ {}
+ {}
+ {}
+ {(,5)}
+ {[1.1,2.2)}
+ {[1.1,2.2)}
+(7 rows)
+
+set enable_nestloop to default;
+set enable_hashjoin to default;
+set enable_mergejoin to default;
+DROP TABLE nummultirange_test2;
+--
+-- Test user-defined multirange of floats
+--
+select '{[123.001, 5.e9)}'::float8multirange @> 888.882::float8;
+ ?column? 
+----------
+ t
+(1 row)
+
+create table float8multirange_test(f8mr float8multirange, i int);
+insert into float8multirange_test values(float8multirange(float8range(-100.00007, '1.111113e9')), 42);
+select * from float8multirange_test;
+           f8mr            | i  
+---------------------------+----
+ {[-100.00007,1111113000)} | 42
+(1 row)
+
+drop table float8multirange_test;
+--
+-- Test multirange types over domains
+--
+create domain mydomain as int4;
+create type mydomainrange as range(subtype=mydomain);
+select '{[4,50)}'::mydomainmultirange @> 7::mydomain;
+ ?column? 
+----------
+ t
+(1 row)
+
+drop domain mydomain cascade;
+NOTICE:  drop cascades to type mydomainrange
+--
+-- Test domains over multirange types
+--
+create domain restrictedmultirange as int4multirange check (upper(value) < 10);
+select '{[4,5)}'::restrictedmultirange @> 7;
+ ?column? 
+----------
+ f
+(1 row)
+
+select '{[4,50)}'::restrictedmultirange @> 7; -- should fail
+ERROR:  value for domain restrictedmultirange violates check constraint "restrictedmultirange_check"
+drop domain restrictedmultirange;
+--
+-- Test multiple multirange types over the same subtype
+--
+create type textrange1 as range(subtype=text, collation="C");
+create type textrange2 as range(subtype=text, collation="C");
+select textmultirange1(textrange2('a','Z'));  -- should fail
+ERROR:  function textmultirange1(textrange2) does not exist
+LINE 1: select textmultirange1(textrange2('a','Z'));
+               ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+select textmultirange1(textrange1('a','Z')) @> 'b'::text;
+ERROR:  range lower bound must be less than or equal to range upper bound
+select textmultirange2(textrange2('a','z')) @> 'b'::text;
+ ?column? 
+----------
+ t
+(1 row)
+
+drop type textrange1;
+drop type textrange2;
+--
+-- Test polymorphic type system
+--
+create function anyarray_anymultirange_func(a anyarray, r anymultirange)
+  returns anyelement as 'select $1[1] + lower($2);' language sql;
+select anyarray_anymultirange_func(ARRAY[1,2], int4multirange(int4range(10,20)));
+ anyarray_anymultirange_func 
+-----------------------------
+                          11
+(1 row)
+
+-- should fail
+select anyarray_anymultirange_func(ARRAY[1,2], nummultirange(numrange(10,20)));
+ERROR:  function anyarray_anymultirange_func(integer[], nummultirange) does not exist
+LINE 1: select anyarray_anymultirange_func(ARRAY[1,2], nummultirange...
+               ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+drop function anyarray_anymultirange_func(anyarray, anymultirange);
+-- should fail
+create function bogus_func(anyelement)
+  returns anymultirange as 'select int4multirange(int4range(1,10))' language sql;
+ERROR:  cannot determine result data type
+DETAIL:  A result of type anymultirange requires at least one input of type anyrange or anymultirange.
+-- should fail
+create function bogus_func(int)
+  returns anymultirange as 'select int4multirange(int4range(1,10))' language sql;
+ERROR:  cannot determine result data type
+DETAIL:  A result of type anymultirange requires at least one input of type anyrange or anymultirange.
+create function range_add_bounds(anymultirange)
+  returns anyelement as 'select lower($1) + upper($1)' language sql;
+select range_add_bounds(int4multirange(int4range(1, 17)));
+ range_add_bounds 
+------------------
+               18
+(1 row)
+
+select range_add_bounds(nummultirange(numrange(1.0001, 123.123)));
+ range_add_bounds 
+------------------
+         124.1231
+(1 row)
+
+create function multirangetypes_sql(q anymultirange, b anyarray, out c anyelement)
+  as $$ select upper($1) + $2[1] $$
+  language sql;
+select multirangetypes_sql(int4multirange(int4range(1,10)), ARRAY[2,20]);
+ multirangetypes_sql 
+---------------------
+                  12
+(1 row)
+
+select multirangetypes_sql(nummultirange(numrange(1,10)), ARRAY[2,20]);  -- match failure
+ERROR:  function multirangetypes_sql(nummultirange, integer[]) does not exist
+LINE 1: select multirangetypes_sql(nummultirange(numrange(1,10)), AR...
+               ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+create function anycompatiblearray_anycompatiblemultirange_func(a anycompatiblearray, mr anycompatiblemultirange)
+  returns anycompatible as 'select $1[1] + lower($2);' language sql;
+select anycompatiblearray_anycompatiblemultirange_func(ARRAY[1,2], multirange(int4range(10,20)));
+ anycompatiblearray_anycompatiblemultirange_func 
+-------------------------------------------------
+                                              11
+(1 row)
+
+select anycompatiblearray_anycompatiblemultirange_func(ARRAY[1,2], multirange(numrange(10,20)));
+ anycompatiblearray_anycompatiblemultirange_func 
+-------------------------------------------------
+                                              11
+(1 row)
+
+-- should fail
+select anycompatiblearray_anycompatiblemultirange_func(ARRAY[1.1,2], multirange(int4range(10,20)));
+ERROR:  function anycompatiblearray_anycompatiblemultirange_func(numeric[], int4multirange) does not exist
+LINE 1: select anycompatiblearray_anycompatiblemultirange_func(ARRAY...
+               ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+drop function anycompatiblearray_anycompatiblemultirange_func(anycompatiblearray, anycompatiblemultirange);
+create function anycompatiblerange_anycompatiblemultirange_func(r anycompatiblerange, mr anycompatiblemultirange)
+  returns anycompatible as 'select lower($1) + lower($2);' language sql;
+select anycompatiblerange_anycompatiblemultirange_func(int4range(1,2), multirange(int4range(10,20)));
+ anycompatiblerange_anycompatiblemultirange_func 
+-------------------------------------------------
+                                              11
+(1 row)
+
+-- should fail
+select anycompatiblerange_anycompatiblemultirange_func(numrange(1,2), multirange(int4range(10,20)));
+ERROR:  function anycompatiblerange_anycompatiblemultirange_func(numrange, int4multirange) does not exist
+LINE 1: select anycompatiblerange_anycompatiblemultirange_func(numra...
+               ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+drop function anycompatiblerange_anycompatiblemultirange_func(anycompatiblerange, anycompatiblemultirange);
+-- should fail
+create function bogus_func(anycompatible)
+  returns anycompatiblerange as 'select int4range(1,10)' language sql;
+ERROR:  cannot determine result data type
+DETAIL:  A result of type anycompatiblerange requires at least one input of type anycompatiblerange or anycompatiblemultirange.
+--
+-- Arrays of multiranges
+--
+select ARRAY[nummultirange(numrange(1.1, 1.2)), nummultirange(numrange(12.3, 155.5))];
+              array               
+----------------------------------
+ {"{[1.1,1.2)}","{[12.3,155.5)}"}
+(1 row)
+
+create table i8mr_array (f1 int, f2 int8multirange[]);
+insert into i8mr_array values (42, array[int8multirange(int8range(1,10)), int8multirange(int8range(2,20))]);
+select * from i8mr_array;
+ f1 |           f2            
+----+-------------------------
+ 42 | {"{[1,10)}","{[2,20)}"}
+(1 row)
+
+drop table i8mr_array;
+--
+-- Multiranges of arrays
+--
+select arraymultirange(arrayrange(ARRAY[1,2], ARRAY[2,1]));
+   arraymultirange   
+---------------------
+ {["{1,2}","{2,1}")}
+(1 row)
+
+select arraymultirange(arrayrange(ARRAY[2,1], ARRAY[1,2]));  -- fail
+ERROR:  range lower bound must be less than or equal to range upper bound
+select array[1,1] <@ arraymultirange(arrayrange(array[1,2], array[2,1]));
+ ?column? 
+----------
+ f
+(1 row)
+
+select array[1,3] <@ arraymultirange(arrayrange(array[1,2], array[2,1]));
+ ?column? 
+----------
+ t
+(1 row)
+
+--
+-- Ranges of composites
+--
+create type two_ints as (a int, b int);
+create type two_ints_range as range (subtype = two_ints);
+-- with force_parallel_mode on, this exercises tqueue.c's range remapping
+select *, row_to_json(upper(t)) as u from
+  (values (two_ints_multirange(two_ints_range(row(1,2), row(3,4)))),
+          (two_ints_multirange(two_ints_range(row(5,6), row(7,8))))) v(t);
+          t          |       u       
+---------------------+---------------
+ {["(1,2)","(3,4)")} | {"a":3,"b":4}
+ {["(5,6)","(7,8)")} | {"a":7,"b":8}
+(2 rows)
+
+drop type two_ints cascade;
+NOTICE:  drop cascades to type two_ints_range
+--
+-- Check behavior when subtype lacks a hash function
+--
+set enable_sort = off;  -- try to make it pick a hash setop implementation
+select '{(2,5)}'::cashmultirange except select '{(5,6)}'::cashmultirange;
+ cashmultirange  
+-----------------
+ {($2.00,$5.00)}
+(1 row)
+
+reset enable_sort;
+--
+-- OUT/INOUT/TABLE functions
+--
+-- infer anymultirange from anymultirange
+create function mr_outparam_succeed(i anymultirange, out r anymultirange, out t text)
+  as $$ select $1, 'foo'::text $$ language sql;
+select * from mr_outparam_succeed(int4multirange(int4range(1,2)));
+    r    |  t  
+---------+-----
+ {[1,2)} | foo
+(1 row)
+
+-- infer anyarray from anymultirange
+create function mr_outparam_succeed2(i anymultirange, out r anyarray, out t text)
+  as $$ select ARRAY[upper($1)], 'foo'::text $$ language sql;
+select * from mr_outparam_succeed2(int4multirange(int4range(1,2)));
+  r  |  t  
+-----+-----
+ {2} | foo
+(1 row)
+
+-- infer anyrange from anymultirange
+create function mr_outparam_succeed3(i anymultirange, out r anyrange, out t text)
+  as $$ select range_merge($1), 'foo'::text $$ language sql;
+select * from mr_outparam_succeed3(int4multirange(int4range(1,2)));
+   r   |  t  
+-------+-----
+ [1,2) | foo
+(1 row)
+
+-- infer anymultirange from anyrange
+create function mr_outparam_succeed4(i anyrange, out r anymultirange, out t text)
+  as $$ select multirange($1), 'foo'::text $$ language sql;
+select * from mr_outparam_succeed4(int4range(1,2));
+    r    |  t  
+---------+-----
+ {[1,2)} | foo
+(1 row)
+
+-- infer anyelement from anymultirange
+create function mr_inoutparam_succeed(out i anyelement, inout r anymultirange)
+  as $$ select upper($1), $1 $$ language sql;
+select * from mr_inoutparam_succeed(int4multirange(int4range(1,2)));
+ i |    r    
+---+---------
+ 2 | {[1,2)}
+(1 row)
+
+-- infer anyelement+anymultirange from anyelement+anymultirange
+create function mr_table_succeed(i anyelement, r anymultirange) returns table(i anyelement, r anymultirange)
+  as $$ select $1, $2 $$ language sql;
+select * from mr_table_succeed(123, int4multirange(int4range(1,11)));
+  i  |    r     
+-----+----------
+ 123 | {[1,11)}
+(1 row)
+
+-- use anymultirange in plpgsql
+create function mr_polymorphic(i anyrange) returns anymultirange
+  as $$ begin return multirange($1); end; $$ language plpgsql;
+select mr_polymorphic(int4range(1, 4));
+ mr_polymorphic 
+----------------
+ {[1,4)}
+(1 row)
+
+-- should fail
+create function mr_outparam_fail(i anyelement, out r anymultirange, out t text)
+  as $$ select '[1,10]', 'foo' $$ language sql;
+ERROR:  cannot determine result data type
+DETAIL:  A result of type anymultirange requires at least one input of type anyrange or anymultirange.
+--should fail
+create function mr_inoutparam_fail(inout i anyelement, out r anymultirange)
+  as $$ select $1, '[1,10]' $$ language sql;
+ERROR:  cannot determine result data type
+DETAIL:  A result of type anymultirange requires at least one input of type anyrange or anymultirange.
+--should fail
+create function mr_table_fail(i anyelement) returns table(i anyelement, r anymultirange)
+  as $$ select $1, '[1,10]' $$ language sql;
+ERROR:  cannot determine result data type
+DETAIL:  A result of type anymultirange requires at least one input of type anyrange or anymultirange.
diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out
index 8d350ab61b..044ce8835e 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 
@@ -208,6 +215,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 
@@ -228,6 +237,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 
@@ -340,7 +351,8 @@ WHERE p1.prorettype IN
      '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     
 ------+----------------
@@ -355,20 +367,25 @@ ORDER BY 2;
  3532 | enum_recv
 (9 rows)
 
--- anyrange is tighter than the rest, can only resolve from anyrange input
+-- anyrange and anymultirange are tighter than the rest, can only resolve
+-- from each other
 SELECT p1.oid, p1.proname
 FROM pg_proc as p1
-WHERE p1.prorettype = 'anyrange'::regtype
+WHERE p1.prorettype IN ('anyrange'::regtype, 'anymultirange'::regtype)
   AND NOT
-     'anyrange'::regtype = ANY (p1.proargtypes)
+    ('anyrange'::regtype = ANY (p1.proargtypes) OR
+      'anymultirange'::regtype = ANY (p1.proargtypes))
 ORDER BY 2;
  oid  |     proname      
 ------+------------------
+ 8002 | anymultirange_in
  3832 | anyrange_in
+ 8004 | multirange_in
+ 8006 | multirange_recv
  3876 | range_gist_union
  3834 | range_in
  3836 | range_recv
-(4 rows)
+(7 rows)
 
 -- similarly for the anycompatible family
 SELECT p1.oid, p1.proname
@@ -2193,13 +2210,14 @@ ORDER BY 1, 2, 3;
                     | float_ops        | float4_ops       | real
                     | float_ops        | float8_ops       | double precision
                     | jsonb_ops        | jsonb_ops        | jsonb
+                    | multirange_ops   | multirange_ops   | anymultirange
                     | numeric_ops      | numeric_ops      | numeric
                     | range_ops        | range_ops        | anyrange
                     | record_image_ops | record_image_ops | record
                     | record_ops       | record_ops       | record
                     | tsquery_ops      | tsquery_ops      | tsquery
                     | tsvector_ops     | tsvector_ops     | tsvector
-(14 rows)
+(15 rows)
 
 -- **************** pg_index ****************
 -- Look for illegal values in pg_index fields.
diff --git a/src/test/regress/expected/plpgsql.out b/src/test/regress/expected/plpgsql.out
index d0a6b630b8..d55006d8c9 100644
--- a/src/test/regress/expected/plpgsql.out
+++ b/src/test/regress/expected/plpgsql.out
@@ -1811,7 +1811,7 @@ begin
   return array[x + 1, x + 2];
 end$$ language plpgsql;
 ERROR:  cannot determine result data type
-DETAIL:  A result of type anyrange requires at least one input of type anyrange.
+DETAIL:  A result of type anyrange requires at least one input of type anyrange or anymultirange.
 create function f1(x anyrange) returns anyarray as $$
 begin
   return array[lower(x), upper(x)];
@@ -1856,7 +1856,7 @@ begin
   return array[x + 1, x + 2];
 end$$ language plpgsql;
 ERROR:  cannot determine result data type
-DETAIL:  A result of type anycompatiblerange requires at least one input of type anycompatiblerange.
+DETAIL:  A result of type anycompatiblerange requires at least one input of type anycompatiblerange or anycompatiblemultirange.
 create function f1(x anycompatiblerange, y anycompatiblearray) returns anycompatiblerange as $$
 begin
   return x;
diff --git a/src/test/regress/expected/polymorphism.out b/src/test/regress/expected/polymorphism.out
index 1ff40764d9..248c869931 100644
--- a/src/test/regress/expected/polymorphism.out
+++ b/src/test/regress/expected/polymorphism.out
@@ -61,7 +61,7 @@ create function polyf(x anyelement) returns anyrange as $$
   select array[x + 1, x + 2]
 $$ language sql;
 ERROR:  cannot determine result data type
-DETAIL:  A result of type anyrange requires at least one input of type anyrange.
+DETAIL:  A result of type anyrange requires at least one input of type anyrange or anymultirange.
 create function polyf(x anyrange) returns anyarray as $$
   select array[lower(x), upper(x)]
 $$ language sql;
@@ -97,12 +97,27 @@ LINE 1: select polyf(int4range(42, 49), 11, 4.5) as fail;
                ^
 HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
 drop function polyf(x anycompatiblerange, y anycompatible, z anycompatible);
+create function polyf(x anycompatiblemultirange, y anycompatible, z anycompatible) returns anycompatiblearray as $$
+  select array[lower(x), upper(x), y, z]
+$$ language sql;
+select polyf(multirange(int4range(42, 49)), 11, 2::smallint) as int, polyf(multirange(float8range(4.5, 7.8)), 7.8, 11::real) as num;
+     int      |       num        
+--------------+------------------
+ {42,49,11,2} | {4.5,7.8,7.8,11}
+(1 row)
+
+select polyf(multirange(int4range(42, 49)), 11, 4.5) as fail;  -- range type doesn't fit
+ERROR:  function polyf(int4multirange, integer, numeric) does not exist
+LINE 1: select polyf(multirange(int4range(42, 49)), 11, 4.5) as fail...
+               ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+drop function polyf(x anycompatiblemultirange, y anycompatible, z anycompatible);
 -- fail, can't infer type:
 create function polyf(x anycompatible) returns anycompatiblerange as $$
   select array[x + 1, x + 2]
 $$ language sql;
 ERROR:  cannot determine result data type
-DETAIL:  A result of type anycompatiblerange requires at least one input of type anycompatiblerange.
+DETAIL:  A result of type anycompatiblerange requires at least one input of type anycompatiblerange or anycompatiblemultirange.
 create function polyf(x anycompatiblerange, y anycompatiblearray) returns anycompatiblerange as $$
   select x
 $$ language sql;
@@ -113,6 +128,22 @@ select polyf(int4range(42, 49), array[11]) as int, polyf(float8range(4.5, 7.8),
 (1 row)
 
 drop function polyf(x anycompatiblerange, y anycompatiblearray);
+-- fail, can't infer type:
+create function polyf(x anycompatible) returns anycompatiblemultirange as $$
+  select array[x + 1, x + 2]
+$$ language sql;
+ERROR:  cannot determine result data type
+DETAIL:  A result of type anycompatiblemultirange requires at least one input of type anycompatiblerange or anycompatiblemultirange.
+create function polyf(x anycompatiblemultirange, y anycompatiblearray) returns anycompatiblemultirange as $$
+  select x
+$$ language sql;
+select polyf(multirange(int4range(42, 49)), array[11]) as int, polyf(multirange(float8range(4.5, 7.8)), array[7]) as num;
+    int    |     num     
+-----------+-------------
+ {[42,49)} | {[4.5,7.8)}
+(1 row)
+
+drop function polyf(x anycompatiblemultirange, y anycompatiblearray);
 create function polyf(a anyelement, b anyarray,
                       c anycompatible, d anycompatible,
                       OUT x anyarray, OUT y anycompatiblearray)
@@ -231,7 +262,7 @@ CREATE AGGREGATE myaggp01a(*) (SFUNC = stfnp, STYPE = int4[],
 CREATE AGGREGATE myaggp02a(*) (SFUNC = stfnp, STYPE = anyarray,
   FINALFUNC = ffp, INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 --     N    P
 -- should CREATE
 CREATE AGGREGATE myaggp03a(*) (SFUNC = stfp, STYPE = int4[],
@@ -243,11 +274,11 @@ CREATE AGGREGATE myaggp03b(*) (SFUNC = stfp, STYPE = int4[],
 CREATE AGGREGATE myaggp04a(*) (SFUNC = stfp, STYPE = anyarray,
   FINALFUNC = ffp, INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 CREATE AGGREGATE myaggp04b(*) (SFUNC = stfp, STYPE = anyarray,
   INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 --    Case2 (R = P) && ((B = P) || (B = N))
 --    -------------------------------------
 --    S    tf1      B    tf2
@@ -302,13 +333,13 @@ ERROR:  function tfp(integer[], anyelement) does not exist
 CREATE AGGREGATE myaggp13a(BASETYPE = int, SFUNC = tfnp, STYPE = anyarray,
   FINALFUNC = ffp, INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 --    P    N        N    P
 -- should ERROR: tf2p(anyarray, int) not matched by tf2p(int[],anyelement)
 CREATE AGGREGATE myaggp14a(BASETYPE = int, SFUNC = tf2p, STYPE = anyarray,
   FINALFUNC = ffp, INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 --    P    N        P    N
 -- should ERROR: tfnp(anyarray, anyelement) not matched by tfnp(int[],int)
 CREATE AGGREGATE myaggp15a(BASETYPE = anyelement, SFUNC = tfnp,
@@ -324,21 +355,21 @@ ERROR:  function tf2p(anyarray, anyelement) does not exist
 CREATE AGGREGATE myaggp17a(BASETYPE = int, SFUNC = tf1p, STYPE = anyarray,
   FINALFUNC = ffp, INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 CREATE AGGREGATE myaggp17b(BASETYPE = int, SFUNC = tf1p, STYPE = anyarray,
   INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 --    P    P        N    P
 -- should ERROR: tfp(anyarray, int) not matched by tfp(anyarray, anyelement)
 CREATE AGGREGATE myaggp18a(BASETYPE = int, SFUNC = tfp, STYPE = anyarray,
   FINALFUNC = ffp, INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 CREATE AGGREGATE myaggp18b(BASETYPE = int, SFUNC = tfp, STYPE = anyarray,
   INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 --    P    P        P    N
 -- should ERROR: tf1p(anyarray, anyelement) not matched by tf1p(anyarray, int)
 CREATE AGGREGATE myaggp19a(BASETYPE = anyelement, SFUNC = tf1p,
@@ -368,11 +399,11 @@ CREATE AGGREGATE myaggn01b(*) (SFUNC = stfnp, STYPE = int4[],
 CREATE AGGREGATE myaggn02a(*) (SFUNC = stfnp, STYPE = anyarray,
   FINALFUNC = ffnp, INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 CREATE AGGREGATE myaggn02b(*) (SFUNC = stfnp, STYPE = anyarray,
   INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 --     N    P
 -- should CREATE
 CREATE AGGREGATE myaggn03a(*) (SFUNC = stfp, STYPE = int4[],
@@ -382,7 +413,7 @@ CREATE AGGREGATE myaggn03a(*) (SFUNC = stfp, STYPE = int4[],
 CREATE AGGREGATE myaggn04a(*) (SFUNC = stfp, STYPE = anyarray,
   FINALFUNC = ffnp, INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 --    Case4 (R = N) && ((B = P) || (B = N))
 --    -------------------------------------
 --    S    tf1      B    tf2
@@ -436,21 +467,21 @@ ERROR:  function tfp(integer[], anyelement) does not exist
 CREATE AGGREGATE myaggn13a(BASETYPE = int, SFUNC = tfnp, STYPE = anyarray,
   FINALFUNC = ffnp, INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 CREATE AGGREGATE myaggn13b(BASETYPE = int, SFUNC = tfnp, STYPE = anyarray,
   INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 --    P    N        N    P
 -- should ERROR: tf2p(anyarray, int) not matched by tf2p(int[],anyelement)
 CREATE AGGREGATE myaggn14a(BASETYPE = int, SFUNC = tf2p, STYPE = anyarray,
   FINALFUNC = ffnp, INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 CREATE AGGREGATE myaggn14b(BASETYPE = int, SFUNC = tf2p, STYPE = anyarray,
   INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 --    P    N        P    N
 -- should ERROR: tfnp(anyarray, anyelement) not matched by tfnp(int[],int)
 CREATE AGGREGATE myaggn15a(BASETYPE = anyelement, SFUNC = tfnp,
@@ -472,13 +503,13 @@ ERROR:  function tf2p(anyarray, anyelement) does not exist
 CREATE AGGREGATE myaggn17a(BASETYPE = int, SFUNC = tf1p, STYPE = anyarray,
   FINALFUNC = ffnp, INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 --    P    P        N    P
 -- should ERROR: tfp(anyarray, int) not matched by tfp(anyarray, anyelement)
 CREATE AGGREGATE myaggn18a(BASETYPE = int, SFUNC = tfp, STYPE = anyarray,
   FINALFUNC = ffnp, INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 --    P    P        P    N
 -- should ERROR: tf1p(anyarray, anyelement) not matched by tf1p(anyarray, int)
 CREATE AGGREGATE myaggn19a(BASETYPE = anyelement, SFUNC = tf1p,
@@ -1853,7 +1884,59 @@ returns anycompatiblerange as $$
   select $1
 $$ language sql;
 ERROR:  cannot determine result data type
-DETAIL:  A result of type anycompatiblerange requires at least one input of type anycompatiblerange.
+DETAIL:  A result of type anycompatiblerange requires at least one input of type anycompatiblerange or anycompatiblemultirange.
+create function anyctest(anycompatible, anycompatiblemultirange)
+returns anycompatiblemultirange as $$
+  select $2
+$$ language sql;
+select x, pg_typeof(x) from anyctest(11, multirange(int4range(4,7))) x;
+    x    |   pg_typeof    
+---------+----------------
+ {[4,7)} | int4multirange
+(1 row)
+
+select x, pg_typeof(x) from anyctest(11, multirange(numrange(4,7))) x;
+    x    |   pg_typeof   
+---------+---------------
+ {[4,7)} | nummultirange
+(1 row)
+
+select x, pg_typeof(x) from anyctest(11, 12) x;  -- fail
+ERROR:  function anyctest(integer, integer) does not exist
+LINE 1: select x, pg_typeof(x) from anyctest(11, 12) x;
+                                    ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+select x, pg_typeof(x) from anyctest(11.2, multirange(int4range(4,7))) x;  -- fail
+ERROR:  function anyctest(numeric, int4multirange) does not exist
+LINE 1: select x, pg_typeof(x) from anyctest(11.2, multirange(int4ra...
+                                    ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+select x, pg_typeof(x) from anyctest(11.2, '{[4,7)}') x;  -- fail
+ERROR:  could not identify anycompatiblemultirange type
+drop function anyctest(anycompatible, anycompatiblemultirange);
+create function anyctest(anycompatiblemultirange, anycompatiblemultirange)
+returns anycompatible as $$
+  select lower($1) + upper($2)
+$$ language sql;
+select x, pg_typeof(x) from anyctest(multirange(int4range(11,12)), multirange(int4range(4,7))) x;
+ x  | pg_typeof 
+----+-----------
+ 18 | integer
+(1 row)
+
+select x, pg_typeof(x) from anyctest(multirange(int4range(11,12)), multirange(numrange(4,7))) x; -- fail
+ERROR:  function anyctest(int4multirange, nummultirange) does not exist
+LINE 1: select x, pg_typeof(x) from anyctest(multirange(int4range(11...
+                                    ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+drop function anyctest(anycompatiblemultirange, anycompatiblemultirange);
+-- fail, can't infer result type:
+create function anyctest(anycompatible)
+returns anycompatiblemultirange as $$
+  select $1
+$$ language sql;
+ERROR:  cannot determine result data type
+DETAIL:  A result of type anycompatiblemultirange requires at least one input of type anycompatiblerange or anycompatiblemultirange.
 create function anyctest(anycompatiblenonarray, anycompatiblenonarray)
 returns anycompatiblearray as $$
   select array[$1, $2]
diff --git a/src/test/regress/expected/rangefuncs.out b/src/test/regress/expected/rangefuncs.out
index 7eced28452..71e2769f44 100644
--- a/src/test/regress/expected/rangefuncs.out
+++ b/src/test/regress/expected/rangefuncs.out
@@ -1556,7 +1556,7 @@ DROP FUNCTION dup(anyelement);
 CREATE FUNCTION bad (f1 int, out f2 anyelement, out f3 anyarray)
 AS 'select $1, array[$1,$1]' LANGUAGE sql;
 ERROR:  cannot determine result data type
-DETAIL:  A result of type anyelement requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyelement requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 CREATE FUNCTION dup (f1 anycompatible, f2 anycompatiblearray, f3 out anycompatible, f4 out anycompatiblearray)
 AS 'select $1, $2' LANGUAGE sql;
 SELECT dup(22, array[44]);
diff --git a/src/test/regress/expected/rangetypes.out b/src/test/regress/expected/rangetypes.out
index c421f5394f..e9f9e1a11b 100644
--- a/src/test/regress/expected/rangetypes.out
+++ b/src/test/regress/expected/rangetypes.out
@@ -518,6 +518,24 @@ select numrange(1.0, 2.0) * numrange(2.5, 3.0);
  empty
 (1 row)
 
+select range_intersect_agg(nr) from numrange_test;
+ range_intersect_agg 
+---------------------
+ empty
+(1 row)
+
+select range_intersect_agg(nr) from numrange_test where false;
+ range_intersect_agg 
+---------------------
+ 
+(1 row)
+
+select range_intersect_agg(nr) from numrange_test where nr @> 4.0;
+ range_intersect_agg 
+---------------------
+ [3,5)
+(1 row)
+
 create table numrange_test2(nr numrange);
 create index numrange_test2_hash_idx on numrange_test2 using hash (nr);
 INSERT INTO numrange_test2 VALUES('[, 5)');
@@ -1371,12 +1389,12 @@ drop function anyarray_anyrange_func(anyarray, anyrange);
 create function bogus_func(anyelement)
   returns anyrange as 'select int4range(1,10)' language sql;
 ERROR:  cannot determine result data type
-DETAIL:  A result of type anyrange requires at least one input of type anyrange.
+DETAIL:  A result of type anyrange requires at least one input of type anyrange or anymultirange.
 -- should fail
 create function bogus_func(int)
   returns anyrange as 'select int4range(1,10)' language sql;
 ERROR:  cannot determine result data type
-DETAIL:  A result of type anyrange requires at least one input of type anyrange.
+DETAIL:  A result of type anyrange requires at least one input of type anyrange or anymultirange.
 create function range_add_bounds(anyrange)
   returns anyelement as 'select lower($1) + upper($1)' language sql;
 select range_add_bounds(int4range(1, 17));
@@ -1430,7 +1448,7 @@ drop function anycompatiblearray_anycompatiblerange_func(anycompatiblearray, any
 create function bogus_func(anycompatible)
   returns anycompatiblerange as 'select int4range(1,10)' language sql;
 ERROR:  cannot determine result data type
-DETAIL:  A result of type anycompatiblerange requires at least one input of type anycompatiblerange.
+DETAIL:  A result of type anycompatiblerange requires at least one input of type anycompatiblerange or anycompatiblemultirange.
 --
 -- Arrays of ranges
 --
@@ -1508,6 +1526,7 @@ reset enable_sort;
 --
 -- OUT/INOUT/TABLE functions
 --
+-- infer anyrange from anyrange
 create function outparam_succeed(i anyrange, out r anyrange, out t text)
   as $$ select $1, 'foo'::text $$ language sql;
 select * from outparam_succeed(int4range(1,2));
@@ -1525,6 +1544,16 @@ select * from outparam2_succeed(int4range(1,11));
  {1,11} | {11,1}
 (1 row)
 
+-- infer anyarray from anyrange
+create function outparam_succeed2(i anyrange, out r anyarray, out t text)
+  as $$ select ARRAY[upper($1)], 'foo'::text $$ language sql;
+select * from outparam_succeed2(int4range(int4range(1,2)));
+  r  |  t  
+-----+-----
+ {2} | foo
+(1 row)
+
+-- infer anyelement from anyrange
 create function inoutparam_succeed(out i anyelement, inout r anyrange)
   as $$ select upper($1), $1 $$ language sql;
 select * from inoutparam_succeed(int4range(1,2));
@@ -1547,14 +1576,14 @@ select * from table_succeed(int4range(1,11));
 create function outparam_fail(i anyelement, out r anyrange, out t text)
   as $$ select '[1,10]', 'foo' $$ language sql;
 ERROR:  cannot determine result data type
-DETAIL:  A result of type anyrange requires at least one input of type anyrange.
+DETAIL:  A result of type anyrange requires at least one input of type anyrange or anymultirange.
 --should fail
 create function inoutparam_fail(inout i anyelement, out r anyrange)
   as $$ select $1, '[1,10]' $$ language sql;
 ERROR:  cannot determine result data type
-DETAIL:  A result of type anyrange requires at least one input of type anyrange.
+DETAIL:  A result of type anyrange requires at least one input of type anyrange or anymultirange.
 --should fail
 create function table_fail(i anyelement) returns table(i anyelement, r anyrange)
   as $$ select $1, '[1,10]' $$ language sql;
 ERROR:  cannot determine result data type
-DETAIL:  A result of type anyrange requires at least one input of type anyrange.
+DETAIL:  A result of type anyrange requires at least one input of type anyrange or anymultirange.
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index 192445878d..d9ce961be2 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -92,6 +92,7 @@ num_exp_sqrt|t
 num_exp_sub|t
 num_input_test|f
 num_result|f
+nummultirange_test|t
 numrange_test|t
 onek|t
 onek2|t
@@ -172,6 +173,7 @@ quad_poly_tbl|t
 radix_text_tbl|t
 ramp|f
 real_city|f
+reservations|f
 road|t
 shighway|t
 slow_emp4000|f
diff --git a/src/test/regress/expected/type_sanity.out b/src/test/regress/expected/type_sanity.out
index 274130e706..8df1ae47e4 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'));
@@ -195,18 +195,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
@@ -240,17 +241,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
@@ -318,18 +320,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
@@ -363,17 +366,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
@@ -631,3 +635,11 @@ WHERE pronargs != 2
 ----------+------------+---------
 (0 rows)
 
+-- every range should have a valid multirange
+SELECT p1.rngtypid, p1.rngsubtype, p1.rngmultitypid
+FROM pg_range p1
+WHERE p1.rngmultitypid IS NULL OR p1.rngmultitypid = 0;
+ rngtypid | rngsubtype | rngmultitypid 
+----------+------------+---------------
+(0 rows)
+
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 95f1925072..9575275179 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 xid
+test: strings numerology point lseg line box path polygon circle date time timetz timestamp timestamptz interval inet macaddr macaddr8 tstypes xid multirangetypes
 
 # ----------
 # Another group of parallel tests
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index 8ba4136220..4a2a841c22 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -20,6 +20,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..842a7d091a
--- /dev/null
+++ b/src/test/regress/sql/multirangetypes.sql
@@ -0,0 +1,656 @@
+-- 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'));
+
+--
+-- test casts, both a built-in range type and a user-defined one:
+--
+select 'empty'::int4range::int4multirange;
+select int4range(1, 3)::int4multirange;
+select int4range(1, null)::int4multirange;
+select int4range(null, null)::int4multirange;
+select 'empty'::textrange::textmultirange;
+select textrange('a', 'c')::textmultirange;
+select textrange('a', null)::textmultirange;
+select textrange(null, null)::textmultirange;
+
+--
+-- create some test data and test the operators
+--
+
+CREATE TABLE nummultirange_test (nmr NUMMULTIRANGE);
+CREATE INDEX nummultirange_test_btree ON nummultirange_test(nmr);
+
+INSERT INTO nummultirange_test VALUES('{}');
+INSERT INTO nummultirange_test VALUES('{[,)}');
+INSERT INTO nummultirange_test VALUES('{[3,]}');
+INSERT INTO nummultirange_test VALUES('{[,), [3,]}');
+INSERT INTO nummultirange_test VALUES('{[, 5)}');
+INSERT INTO nummultirange_test VALUES(nummultirange());
+INSERT INTO nummultirange_test VALUES(nummultirange(variadic '{}'::numrange[]));
+INSERT INTO nummultirange_test VALUES(nummultirange(numrange(1.1, 2.2)));
+INSERT INTO nummultirange_test VALUES('{empty}');
+INSERT INTO nummultirange_test VALUES(nummultirange(numrange(1.7, 1.7, '[]'), numrange(1.7, 1.9)));
+INSERT INTO nummultirange_test VALUES(nummultirange(numrange(1.7, 1.7, '[]'), numrange(1.9, 2.1)));
+
+SELECT nmr, isempty(nmr), lower(nmr), upper(nmr) FROM nummultirange_test ORDER BY nmr;
+SELECT nmr, lower_inc(nmr), lower_inf(nmr), upper_inc(nmr), upper_inf(nmr) FROM nummultirange_test ORDER BY nmr;
+
+SELECT * FROM nummultirange_test WHERE nmr = '{}';
+SELECT * FROM nummultirange_test WHERE nmr = '{(,5)}';
+SELECT * FROM nummultirange_test WHERE nmr = '{[3,)}';
+SELECT * FROM nummultirange_test WHERE nmr = '{[1.7,1.7]}';
+SELECT * FROM nummultirange_test WHERE nmr = '{[1.7,1.7],[1.9,2.1)}';
+SELECT * FROM nummultirange_test WHERE nmr < '{}';
+SELECT * FROM nummultirange_test WHERE nmr < '{[-1000.0, -1000.0]}';
+SELECT * FROM nummultirange_test WHERE nmr < '{[0.0, 1.0]}';
+SELECT * FROM nummultirange_test WHERE nmr < '{[1000.0, 1001.0]}';
+SELECT * FROM nummultirange_test WHERE nmr <= '{}';
+SELECT * FROM nummultirange_test WHERE nmr <= '{[3,)}';
+SELECT * FROM nummultirange_test WHERE nmr >= '{}';
+SELECT * FROM nummultirange_test WHERE nmr >= '{[3,)}';
+SELECT * FROM nummultirange_test WHERE nmr > '{}';
+SELECT * FROM nummultirange_test WHERE nmr > '{[-1000.0, -1000.0]}';
+SELECT * FROM nummultirange_test WHERE nmr > '{[0.0, 1.0]}';
+SELECT * FROM nummultirange_test WHERE nmr > '{[1000.0, 1001.0]}';
+SELECT * FROM nummultirange_test WHERE nmr <> '{}';
+SELECT * FROM nummultirange_test WHERE nmr <> '{(,5)}';
+
+select nummultirange(numrange(2.0, 1.0));
+select nummultirange(numrange(5.0, 6.0), numrange(1.0, 2.0));
+
+-- overlaps
+SELECT * FROM nummultirange_test WHERE range_overlaps_multirange(numrange(4.0, 4.2), nmr);
+SELECT * FROM nummultirange_test WHERE numrange(4.0, 4.2) && nmr;
+SELECT * FROM nummultirange_test WHERE multirange_overlaps_range(nmr, numrange(4.0, 4.2));
+SELECT * FROM nummultirange_test WHERE nmr && numrange(4.0, 4.2);
+SELECT * FROM nummultirange_test WHERE multirange_overlaps_multirange(nmr, nummultirange(numrange(4.0, 4.2), numrange(6.0, 7.0)));
+SELECT * FROM nummultirange_test WHERE nmr && nummultirange(numrange(4.0, 4.2), numrange(6.0, 7.0));
+SELECT * FROM nummultirange_test WHERE nmr && nummultirange(numrange(6.0, 7.0));
+SELECT * FROM nummultirange_test WHERE nmr && nummultirange(numrange(6.0, 7.0), numrange(8.0, 9.0));
+
+-- mr contains x
+SELECT * FROM nummultirange_test WHERE multirange_contains_elem(nmr, 4.0);
+SELECT * FROM nummultirange_test WHERE nmr @> 4.0;
+SELECT * FROM nummultirange_test WHERE multirange_contains_range(nmr, numrange(4.0, 4.2));
+SELECT * FROM nummultirange_test WHERE nmr @> numrange(4.0, 4.2);
+SELECT * FROM nummultirange_test WHERE multirange_contains_multirange(nmr, '{[4.0,4.2), [6.0, 8.0)}');
+SELECT * FROM nummultirange_test WHERE nmr @> '{[4.0,4.2), [6.0, 8.0)}'::nummultirange;
+
+-- x is contained by mr
+SELECT * FROM nummultirange_test WHERE elem_contained_by_multirange(4.0, nmr);
+SELECT * FROM nummultirange_test WHERE 4.0 <@ nmr;
+SELECT * FROM nummultirange_test WHERE range_contained_by_multirange(numrange(4.0, 4.2), nmr);
+SELECT * FROM nummultirange_test WHERE numrange(4.0, 4.2) <@ nmr;
+SELECT * FROM nummultirange_test WHERE multirange_contained_by_multirange('{[4.0,4.2), [6.0, 8.0)}', nmr);
+SELECT * FROM nummultirange_test WHERE '{[4.0,4.2), [6.0, 8.0)}'::nummultirange <@ nmr;
+
+-- overlaps
+SELECT 'empty'::numrange && nummultirange();
+SELECT 'empty'::numrange && nummultirange(numrange(1,2));
+SELECT nummultirange() && 'empty'::numrange;
+SELECT nummultirange(numrange(1,2)) && 'empty'::numrange;
+SELECT nummultirange() && nummultirange();
+SELECT nummultirange() && nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) && nummultirange();
+SELECT nummultirange(numrange(3,4)) && nummultirange(numrange(1,2), numrange(7,8));
+SELECT nummultirange(numrange(1,2), numrange(7,8)) && nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(3,4)) && nummultirange(numrange(1,2), numrange(3.5,8));
+SELECT nummultirange(numrange(1,2), numrange(3.5,8)) && numrange(3,4);
+SELECT nummultirange(numrange(1,2), numrange(3.5,8)) && nummultirange(numrange(3,4));
+
+-- contains
+SELECT nummultirange() @> nummultirange();
+SELECT nummultirange() @> 'empty'::numrange;
+SELECT nummultirange(numrange(null,null)) @> numrange(1,2);
+SELECT nummultirange(numrange(null,null)) @> numrange(null,2);
+SELECT nummultirange(numrange(null,null)) @> numrange(2,null);
+SELECT nummultirange(numrange(null,5)) @> numrange(null,3);
+SELECT nummultirange(numrange(null,5)) @> numrange(null,8);
+SELECT nummultirange(numrange(5,null)) @> numrange(8,null);
+SELECT nummultirange(numrange(5,null)) @> numrange(3,null);
+SELECT nummultirange(numrange(1,5)) @> numrange(8,9);
+SELECT nummultirange(numrange(1,5)) @> numrange(3,9);
+SELECT nummultirange(numrange(1,5)) @> numrange(1,4);
+SELECT nummultirange(numrange(1,5)) @> numrange(1,5);
+SELECT nummultirange(numrange(-4,-2), numrange(1,5)) @> numrange(1,5);
+SELECT nummultirange(numrange(1,5), numrange(8,9)) @> numrange(1,5);
+SELECT nummultirange(numrange(1,5), numrange(8,9)) @> numrange(6,7);
+SELECT nummultirange(numrange(1,5), numrange(6,9)) @> numrange(6,7);
+SELECT '{[1,5)}'::nummultirange @> '{[1,5)}';
+SELECT '{[-4,-2), [1,5)}'::nummultirange @> '{[1,5)}';
+SELECT '{[1,5), [8,9)}'::nummultirange @> '{[1,5)}';
+SELECT '{[1,5), [8,9)}'::nummultirange @> '{[6,7)}';
+SELECT '{[1,5), [6,9)}'::nummultirange @> '{[6,7)}';
+
+-- is contained by
+SELECT nummultirange() <@ nummultirange();
+SELECT 'empty'::numrange <@ nummultirange();
+SELECT numrange(1,2) <@ nummultirange(numrange(null,null));
+SELECT numrange(null,2) <@ nummultirange(numrange(null,null));
+SELECT numrange(2,null) <@ nummultirange(numrange(null,null));
+SELECT numrange(null,3) <@ nummultirange(numrange(null,5));
+SELECT numrange(null,8) <@ nummultirange(numrange(null,5));
+SELECT numrange(8,null) <@ nummultirange(numrange(5,null));
+SELECT numrange(3,null) <@ nummultirange(numrange(5,null));
+SELECT numrange(8,9) <@ nummultirange(numrange(1,5));
+SELECT numrange(3,9) <@ nummultirange(numrange(1,5));
+SELECT numrange(1,4) <@ nummultirange(numrange(1,5));
+SELECT numrange(1,5) <@ nummultirange(numrange(1,5));
+SELECT numrange(1,5) <@ nummultirange(numrange(-4,-2), numrange(1,5));
+SELECT numrange(1,5) <@ nummultirange(numrange(1,5), numrange(8,9));
+SELECT numrange(6,7) <@ nummultirange(numrange(1,5), numrange(8,9));
+SELECT numrange(6,7) <@ nummultirange(numrange(1,5), numrange(6,9));
+SELECT '{[1,5)}' <@ '{[1,5)}'::nummultirange;
+SELECT '{[1,5)}' <@ '{[-4,-2), [1,5)}'::nummultirange;
+SELECT '{[1,5)}' <@ '{[1,5), [8,9)}'::nummultirange;
+SELECT '{[6,7)}' <@ '{[1,5), [8,9)}'::nummultirange;
+SELECT '{[6,7)}' <@ '{[1,5), [6,9)}'::nummultirange;
+
+-- overleft
+SELECT 'empty'::numrange &< nummultirange();
+SELECT 'empty'::numrange &< nummultirange(numrange(1,2));
+SELECT nummultirange() &< 'empty'::numrange;
+SELECT nummultirange(numrange(1,2)) &< 'empty'::numrange;
+SELECT nummultirange() &< nummultirange();
+SELECT nummultirange(numrange(1,2)) &< nummultirange();
+SELECT nummultirange() &< nummultirange(numrange(1,2));
+SELECT numrange(6,7) &< nummultirange(numrange(3,4));
+SELECT numrange(1,2) &< nummultirange(numrange(3,4));
+SELECT numrange(1,4) &< nummultirange(numrange(3,4));
+SELECT numrange(1,6) &< nummultirange(numrange(3,4));
+SELECT numrange(3.5,6) &< nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(6,7)) &< numrange(3,4);
+SELECT nummultirange(numrange(1,2)) &< numrange(3,4);
+SELECT nummultirange(numrange(1,4)) &< numrange(3,4);
+SELECT nummultirange(numrange(1,6)) &< numrange(3,4);
+SELECT nummultirange(numrange(3.5,6)) &< numrange(3,4);
+SELECT nummultirange(numrange(6,7)) &< nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,2)) &< nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,4)) &< nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,6)) &< nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(3.5,6)) &< nummultirange(numrange(3,4));
+
+-- overright
+SELECT nummultirange() &> 'empty'::numrange;
+SELECT nummultirange(numrange(1,2)) &> 'empty'::numrange;
+SELECT 'empty'::numrange &> nummultirange();
+SELECT 'empty'::numrange &> nummultirange(numrange(1,2));
+SELECT nummultirange() &> nummultirange();
+SELECT nummultirange() &> nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) &> nummultirange();
+SELECT nummultirange(numrange(3,4)) &> numrange(6,7);
+SELECT nummultirange(numrange(3,4)) &> numrange(1,2);
+SELECT nummultirange(numrange(3,4)) &> numrange(1,4);
+SELECT nummultirange(numrange(3,4)) &> numrange(1,6);
+SELECT nummultirange(numrange(3,4)) &> numrange(3.5,6);
+SELECT numrange(3,4) &> nummultirange(numrange(6,7));
+SELECT numrange(3,4) &> nummultirange(numrange(1,2));
+SELECT numrange(3,4) &> nummultirange(numrange(1,4));
+SELECT numrange(3,4) &> nummultirange(numrange(1,6));
+SELECT numrange(3,4) &> nummultirange(numrange(3.5,6));
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(6,7));
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(1,4));
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(1,6));
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(3.5,6));
+
+-- meets
+SELECT 'empty'::numrange -|- nummultirange();
+SELECT 'empty'::numrange -|- nummultirange(numrange(1,2));
+SELECT nummultirange() -|- 'empty'::numrange;
+SELECT nummultirange(numrange(1,2)) -|- 'empty'::numrange;
+SELECT nummultirange() -|- nummultirange();
+SELECT nummultirange(numrange(1,2)) -|- nummultirange();
+SELECT nummultirange() -|- nummultirange(numrange(1,2));
+SELECT numrange(1,2) -|- nummultirange(numrange(2,4));
+SELECT numrange(1,2) -|- nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,2)) -|- numrange(2,4);
+SELECT nummultirange(numrange(1,2)) -|- numrange(3,4);
+SELECT nummultirange(numrange(1,2)) -|- nummultirange(numrange(2,4));
+SELECT nummultirange(numrange(1,2)) -|- nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,2), numrange(5,6)) -|- nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,2), numrange(5,6)) -|- nummultirange(numrange(6,7));
+SELECT nummultirange(numrange(1,2), numrange(5,6)) -|- nummultirange(numrange(8,9));
+SELECT nummultirange(numrange(1,2)) -|- nummultirange(numrange(2,4), numrange(6,7));
+
+-- strictly left
+select 'empty'::numrange << nummultirange();
+select numrange(1,2) << nummultirange();
+select numrange(1,2) << nummultirange(numrange(3,4));
+select numrange(1,2) << nummultirange(numrange(0,4));
+select numrange(1,2) << nummultirange(numrange(0,4), numrange(7,8));
+select nummultirange() << 'empty'::numrange;
+select nummultirange() << numrange(1,2);
+select nummultirange(numrange(3,4)) << numrange(3,6);
+select nummultirange(numrange(0,2)) << numrange(3,6);
+select nummultirange(numrange(0,2), numrange(7,8)) << numrange(3,6);
+select nummultirange(numrange(-4,-2), numrange(0,2)) << numrange(3,6);
+select nummultirange() << nummultirange();
+select nummultirange() << nummultirange(numrange(1,2));
+select nummultirange(numrange(1,2)) << nummultirange();
+select nummultirange(numrange(1,2)) << nummultirange(numrange(1,2));
+select nummultirange(numrange(1,2)) << nummultirange(numrange(3,4));
+select nummultirange(numrange(1,2)) << nummultirange(numrange(3,4), numrange(7,8));
+select nummultirange(numrange(1,2), numrange(4,5)) << nummultirange(numrange(3,4), numrange(7,8));
+
+-- strictly right
+select nummultirange() >> 'empty'::numrange;
+select nummultirange() >> numrange(1,2);
+select nummultirange(numrange(3,4)) >> numrange(1,2);
+select nummultirange(numrange(0,4)) >> numrange(1,2);
+select nummultirange(numrange(0,4), numrange(7,8)) >> numrange(1,2);
+select 'empty'::numrange >> nummultirange();
+select numrange(1,2) >> nummultirange();
+select numrange(3,6) >> nummultirange(numrange(3,4));
+select numrange(3,6) >> nummultirange(numrange(0,2));
+select numrange(3,6) >> nummultirange(numrange(0,2), numrange(7,8));
+select numrange(3,6) >> nummultirange(numrange(-4,-2), numrange(0,2));
+select nummultirange() >> nummultirange();
+select nummultirange(numrange(1,2)) >> nummultirange();
+select nummultirange() >> nummultirange(numrange(1,2));
+select nummultirange(numrange(1,2)) >> nummultirange(numrange(1,2));
+select nummultirange(numrange(3,4)) >> nummultirange(numrange(1,2));
+select nummultirange(numrange(3,4), numrange(7,8)) >> nummultirange(numrange(1,2));
+select nummultirange(numrange(3,4), numrange(7,8)) >> nummultirange(numrange(1,2), numrange(4,5));
+
+-- union
+SELECT nummultirange() + nummultirange();
+SELECT nummultirange() + nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) + nummultirange();
+SELECT nummultirange(numrange(1,2)) + nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) + nummultirange(numrange(2,4));
+SELECT nummultirange(numrange(1,2)) + nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) + nummultirange(numrange(2,4));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) + nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) + nummultirange(numrange(0,9));
+
+-- merge
+SELECT range_merge(nummultirange());
+SELECT range_merge(nummultirange(numrange(1,2)));
+SELECT range_merge(nummultirange(numrange(1,2), numrange(7,8)));
+
+-- minus
+SELECT nummultirange() - nummultirange();
+SELECT nummultirange() - nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) - nummultirange();
+SELECT nummultirange(numrange(1,2), numrange(3,4)) - nummultirange();
+SELECT nummultirange(numrange(1,2)) - nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) - nummultirange(numrange(2,4));
+SELECT nummultirange(numrange(1,2)) - nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(2,3));
+SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(0,8));
+SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(0,2));
+SELECT nummultirange(numrange(1,8)) - nummultirange(numrange(0,2), numrange(3,4));
+SELECT nummultirange(numrange(1,8)) - nummultirange(numrange(2,3), numrange(5,null));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(-2,0));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(2,4));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(3,5));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(0,9));
+SELECT nummultirange(numrange(1,3), numrange(4,5)) - nummultirange(numrange(2,9));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(8,9));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(-2,0), numrange(8,9));
+
+-- intersection
+SELECT nummultirange() * nummultirange();
+SELECT nummultirange() * nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) * nummultirange();
+SELECT '{[1,3)}'::nummultirange * '{[1,5)}'::nummultirange;
+SELECT '{[1,3)}'::nummultirange * '{[0,5)}'::nummultirange;
+SELECT '{[1,3)}'::nummultirange * '{[0,2)}'::nummultirange;
+SELECT '{[1,3)}'::nummultirange * '{[2,5)}'::nummultirange;
+SELECT '{[1,4)}'::nummultirange * '{[2,3)}'::nummultirange;
+SELECT '{[1,4)}'::nummultirange * '{[0,2), [3,5)}'::nummultirange;
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[0,8), [9,12)}'::nummultirange;
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[9,12)}'::nummultirange;
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[-5,-4), [5,6), [9,12)}'::nummultirange;
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[0,2), [3,8), [9,12)}'::nummultirange;
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[0,2), [3,8), [9,12)}'::nummultirange;
+
+--
+-- range_agg function
+--
+create table reservations ( room_id integer not null, booked_during daterange );
+insert into reservations values
+-- 1: has a meets and a gap
+(1, daterange('2018-07-01', '2018-07-07')),
+(1, daterange('2018-07-07', '2018-07-14')),
+(1, daterange('2018-07-20', '2018-07-22')),
+-- 2: just a single row
+(2, daterange('2018-07-01', '2018-07-03')),
+-- 3: one null range
+(3, NULL),
+-- 4: two null ranges
+(4, NULL),
+(4, NULL),
+-- 5: a null range and a non-null range
+(5, NULL),
+(5, daterange('2018-07-01', '2018-07-03')),
+-- 6: has overlap
+(6, daterange('2018-07-01', '2018-07-07')),
+(6, daterange('2018-07-05', '2018-07-10')),
+-- 7: two ranges that meet: no gap or overlap
+(7, daterange('2018-07-01', '2018-07-07')),
+(7, daterange('2018-07-07', '2018-07-14')),
+-- 8: an empty range
+(8, 'empty'::daterange)
+;
+SELECT   room_id, range_agg(booked_during)
+FROM     reservations
+GROUP BY room_id
+ORDER BY room_id;
+
+-- range_agg on a custom range type too
+SELECT  range_agg(r)
+FROM    (VALUES
+          ('[a,c]'::textrange),
+          ('[b,b]'::textrange),
+          ('[c,f]'::textrange),
+          ('[g,h)'::textrange),
+          ('[h,j)'::textrange)
+        ) t(r);
+
+select range_intersect_agg(nmr) from nummultirange_test;
+select range_intersect_agg(nmr) from nummultirange_test where false;
+-- test with just one input:
+select range_intersect_agg(nmr) from (values ('{[1,2]}'::nummultirange)) t(nmr);
+select range_intersect_agg(nmr) from nummultirange_test where nmr @> 4.0;
+
+create table nummultirange_test2(nmr nummultirange);
+create index nummultirange_test2_hash_idx on nummultirange_test2 using hash (nmr);
+
+INSERT INTO nummultirange_test2 VALUES('{[, 5)}');
+INSERT INTO nummultirange_test2 VALUES(nummultirange(numrange(1.1, 2.2)));
+INSERT INTO nummultirange_test2 VALUES(nummultirange(numrange(1.1, 2.2)));
+INSERT INTO nummultirange_test2 VALUES(nummultirange(numrange(1.1, 2.2,'()')));
+INSERT INTO nummultirange_test2 VALUES('{}');
+
+select * from nummultirange_test2 where nmr = '{}';
+select * from nummultirange_test2 where nmr = nummultirange(numrange(1.1, 2.2));
+select * from nummultirange_test2 where nmr = nummultirange(numrange(1.1, 2.3));
+
+set enable_nestloop=t;
+set enable_hashjoin=f;
+set enable_mergejoin=f;
+select * from nummultirange_test natural join nummultirange_test2 order by nmr;
+set enable_nestloop=f;
+set enable_hashjoin=t;
+set enable_mergejoin=f;
+select * from nummultirange_test natural join nummultirange_test2 order by nmr;
+set enable_nestloop=f;
+set enable_hashjoin=f;
+set enable_mergejoin=t;
+select * from nummultirange_test natural join nummultirange_test2 order by nmr;
+
+set enable_nestloop to default;
+set enable_hashjoin to default;
+set enable_mergejoin to default;
+
+DROP TABLE nummultirange_test2;
+
+--
+-- Test user-defined multirange of floats
+--
+
+select '{[123.001, 5.e9)}'::float8multirange @> 888.882::float8;
+create table float8multirange_test(f8mr float8multirange, i int);
+insert into float8multirange_test values(float8multirange(float8range(-100.00007, '1.111113e9')), 42);
+select * from float8multirange_test;
+drop table float8multirange_test;
+
+--
+-- Test multirange types over domains
+--
+
+create domain mydomain as int4;
+create type mydomainrange as range(subtype=mydomain);
+select '{[4,50)}'::mydomainmultirange @> 7::mydomain;
+drop domain mydomain cascade;
+
+--
+-- Test domains over multirange types
+--
+
+create domain restrictedmultirange as int4multirange check (upper(value) < 10);
+select '{[4,5)}'::restrictedmultirange @> 7;
+select '{[4,50)}'::restrictedmultirange @> 7; -- should fail
+drop domain restrictedmultirange;
+
+--
+-- Test multiple multirange types over the same subtype
+--
+
+create type textrange1 as range(subtype=text, collation="C");
+create type textrange2 as range(subtype=text, collation="C");
+
+select textmultirange1(textrange2('a','Z'));  -- should fail
+select textmultirange1(textrange1('a','Z')) @> 'b'::text;
+select textmultirange2(textrange2('a','z')) @> 'b'::text;
+
+drop type textrange1;
+drop type textrange2;
+
+--
+-- Test polymorphic type system
+--
+
+create function anyarray_anymultirange_func(a anyarray, r anymultirange)
+  returns anyelement as 'select $1[1] + lower($2);' language sql;
+
+select anyarray_anymultirange_func(ARRAY[1,2], int4multirange(int4range(10,20)));
+
+-- should fail
+select anyarray_anymultirange_func(ARRAY[1,2], nummultirange(numrange(10,20)));
+
+drop function anyarray_anymultirange_func(anyarray, anymultirange);
+
+-- should fail
+create function bogus_func(anyelement)
+  returns anymultirange as 'select int4multirange(int4range(1,10))' language sql;
+
+-- should fail
+create function bogus_func(int)
+  returns anymultirange as 'select int4multirange(int4range(1,10))' language sql;
+
+create function range_add_bounds(anymultirange)
+  returns anyelement as 'select lower($1) + upper($1)' language sql;
+
+select range_add_bounds(int4multirange(int4range(1, 17)));
+select range_add_bounds(nummultirange(numrange(1.0001, 123.123)));
+
+create function multirangetypes_sql(q anymultirange, b anyarray, out c anyelement)
+  as $$ select upper($1) + $2[1] $$
+  language sql;
+
+select multirangetypes_sql(int4multirange(int4range(1,10)), ARRAY[2,20]);
+select multirangetypes_sql(nummultirange(numrange(1,10)), ARRAY[2,20]);  -- match failure
+
+create function anycompatiblearray_anycompatiblemultirange_func(a anycompatiblearray, mr anycompatiblemultirange)
+  returns anycompatible as 'select $1[1] + lower($2);' language sql;
+
+select anycompatiblearray_anycompatiblemultirange_func(ARRAY[1,2], multirange(int4range(10,20)));
+
+select anycompatiblearray_anycompatiblemultirange_func(ARRAY[1,2], multirange(numrange(10,20)));
+
+-- should fail
+select anycompatiblearray_anycompatiblemultirange_func(ARRAY[1.1,2], multirange(int4range(10,20)));
+
+drop function anycompatiblearray_anycompatiblemultirange_func(anycompatiblearray, anycompatiblemultirange);
+
+create function anycompatiblerange_anycompatiblemultirange_func(r anycompatiblerange, mr anycompatiblemultirange)
+  returns anycompatible as 'select lower($1) + lower($2);' language sql;
+
+select anycompatiblerange_anycompatiblemultirange_func(int4range(1,2), multirange(int4range(10,20)));
+
+-- should fail
+select anycompatiblerange_anycompatiblemultirange_func(numrange(1,2), multirange(int4range(10,20)));
+
+drop function anycompatiblerange_anycompatiblemultirange_func(anycompatiblerange, anycompatiblemultirange);
+
+-- should fail
+create function bogus_func(anycompatible)
+  returns anycompatiblerange as 'select int4range(1,10)' language sql;
+
+--
+-- Arrays of multiranges
+--
+
+select ARRAY[nummultirange(numrange(1.1, 1.2)), nummultirange(numrange(12.3, 155.5))];
+
+create table i8mr_array (f1 int, f2 int8multirange[]);
+insert into i8mr_array values (42, array[int8multirange(int8range(1,10)), int8multirange(int8range(2,20))]);
+select * from i8mr_array;
+drop table i8mr_array;
+
+--
+-- Multiranges of arrays
+--
+
+select arraymultirange(arrayrange(ARRAY[1,2], ARRAY[2,1]));
+select arraymultirange(arrayrange(ARRAY[2,1], ARRAY[1,2]));  -- fail
+
+select array[1,1] <@ arraymultirange(arrayrange(array[1,2], array[2,1]));
+select array[1,3] <@ arraymultirange(arrayrange(array[1,2], array[2,1]));
+
+--
+-- Ranges of composites
+--
+
+create type two_ints as (a int, b int);
+create type two_ints_range as range (subtype = two_ints);
+
+-- with force_parallel_mode on, this exercises tqueue.c's range remapping
+select *, row_to_json(upper(t)) as u from
+  (values (two_ints_multirange(two_ints_range(row(1,2), row(3,4)))),
+          (two_ints_multirange(two_ints_range(row(5,6), row(7,8))))) v(t);
+
+drop type two_ints cascade;
+
+--
+-- Check behavior when subtype lacks a hash function
+--
+
+set enable_sort = off;  -- try to make it pick a hash setop implementation
+
+select '{(2,5)}'::cashmultirange except select '{(5,6)}'::cashmultirange;
+
+reset enable_sort;
+
+--
+-- OUT/INOUT/TABLE functions
+--
+
+-- infer anymultirange from anymultirange
+create function mr_outparam_succeed(i anymultirange, out r anymultirange, out t text)
+  as $$ select $1, 'foo'::text $$ language sql;
+
+select * from mr_outparam_succeed(int4multirange(int4range(1,2)));
+
+-- infer anyarray from anymultirange
+create function mr_outparam_succeed2(i anymultirange, out r anyarray, out t text)
+  as $$ select ARRAY[upper($1)], 'foo'::text $$ language sql;
+
+select * from mr_outparam_succeed2(int4multirange(int4range(1,2)));
+
+-- infer anyrange from anymultirange
+create function mr_outparam_succeed3(i anymultirange, out r anyrange, out t text)
+  as $$ select range_merge($1), 'foo'::text $$ language sql;
+select * from mr_outparam_succeed3(int4multirange(int4range(1,2)));
+
+-- infer anymultirange from anyrange
+create function mr_outparam_succeed4(i anyrange, out r anymultirange, out t text)
+  as $$ select multirange($1), 'foo'::text $$ language sql;
+
+select * from mr_outparam_succeed4(int4range(1,2));
+
+-- infer anyelement from anymultirange
+create function mr_inoutparam_succeed(out i anyelement, inout r anymultirange)
+  as $$ select upper($1), $1 $$ language sql;
+
+select * from mr_inoutparam_succeed(int4multirange(int4range(1,2)));
+
+-- infer anyelement+anymultirange from anyelement+anymultirange
+create function mr_table_succeed(i anyelement, r anymultirange) returns table(i anyelement, r anymultirange)
+  as $$ select $1, $2 $$ language sql;
+
+select * from mr_table_succeed(123, int4multirange(int4range(1,11)));
+
+-- use anymultirange in plpgsql
+create function mr_polymorphic(i anyrange) returns anymultirange
+  as $$ begin return multirange($1); end; $$ language plpgsql;
+select mr_polymorphic(int4range(1, 4));
+
+-- should fail
+create function mr_outparam_fail(i anyelement, out r anymultirange, out t text)
+  as $$ select '[1,10]', 'foo' $$ language sql;
+
+--should fail
+create function mr_inoutparam_fail(inout i anyelement, out r anymultirange)
+  as $$ select $1, '[1,10]' $$ language sql;
+
+--should fail
+create function mr_table_fail(i anyelement) returns table(i anyelement, r anymultirange)
+  as $$ select $1, '[1,10]' $$ language sql;
diff --git a/src/test/regress/sql/opr_sanity.sql b/src/test/regress/sql/opr_sanity.sql
index 9a1ea3d999..69c4859102 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;
 
@@ -279,16 +290,19 @@ WHERE p1.prorettype IN
      '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;
 
--- anyrange is tighter than the rest, can only resolve from anyrange input
+-- anyrange and anymultirange are tighter than the rest, can only resolve
+-- from each other
 
 SELECT p1.oid, p1.proname
 FROM pg_proc as p1
-WHERE p1.prorettype = 'anyrange'::regtype
+WHERE p1.prorettype IN ('anyrange'::regtype, 'anymultirange'::regtype)
   AND NOT
-     'anyrange'::regtype = ANY (p1.proargtypes)
+    ('anyrange'::regtype = ANY (p1.proargtypes) OR
+      'anymultirange'::regtype = ANY (p1.proargtypes))
 ORDER BY 2;
 
 -- similarly for the anycompatible family
diff --git a/src/test/regress/sql/polymorphism.sql b/src/test/regress/sql/polymorphism.sql
index e5222f1f81..ac52c387bb 100644
--- a/src/test/regress/sql/polymorphism.sql
+++ b/src/test/regress/sql/polymorphism.sql
@@ -71,6 +71,16 @@ select polyf(int4range(42, 49), 11, 4.5) as fail;  -- range type doesn't fit
 
 drop function polyf(x anycompatiblerange, y anycompatible, z anycompatible);
 
+create function polyf(x anycompatiblemultirange, y anycompatible, z anycompatible) returns anycompatiblearray as $$
+  select array[lower(x), upper(x), y, z]
+$$ language sql;
+
+select polyf(multirange(int4range(42, 49)), 11, 2::smallint) as int, polyf(multirange(float8range(4.5, 7.8)), 7.8, 11::real) as num;
+
+select polyf(multirange(int4range(42, 49)), 11, 4.5) as fail;  -- range type doesn't fit
+
+drop function polyf(x anycompatiblemultirange, y anycompatible, z anycompatible);
+
 -- fail, can't infer type:
 create function polyf(x anycompatible) returns anycompatiblerange as $$
   select array[x + 1, x + 2]
@@ -84,6 +94,19 @@ select polyf(int4range(42, 49), array[11]) as int, polyf(float8range(4.5, 7.8),
 
 drop function polyf(x anycompatiblerange, y anycompatiblearray);
 
+-- fail, can't infer type:
+create function polyf(x anycompatible) returns anycompatiblemultirange as $$
+  select array[x + 1, x + 2]
+$$ language sql;
+
+create function polyf(x anycompatiblemultirange, y anycompatiblearray) returns anycompatiblemultirange as $$
+  select x
+$$ language sql;
+
+select polyf(multirange(int4range(42, 49)), array[11]) as int, polyf(multirange(float8range(4.5, 7.8)), array[7]) as num;
+
+drop function polyf(x anycompatiblemultirange, y anycompatiblearray);
+
 create function polyf(a anyelement, b anyarray,
                       c anycompatible, d anycompatible,
                       OUT x anyarray, OUT y anycompatiblearray)
@@ -994,6 +1017,35 @@ returns anycompatiblerange as $$
   select $1
 $$ language sql;
 
+create function anyctest(anycompatible, anycompatiblemultirange)
+returns anycompatiblemultirange as $$
+  select $2
+$$ language sql;
+
+select x, pg_typeof(x) from anyctest(11, multirange(int4range(4,7))) x;
+select x, pg_typeof(x) from anyctest(11, multirange(numrange(4,7))) x;
+select x, pg_typeof(x) from anyctest(11, 12) x;  -- fail
+select x, pg_typeof(x) from anyctest(11.2, multirange(int4range(4,7))) x;  -- fail
+select x, pg_typeof(x) from anyctest(11.2, '{[4,7)}') x;  -- fail
+
+drop function anyctest(anycompatible, anycompatiblemultirange);
+
+create function anyctest(anycompatiblemultirange, anycompatiblemultirange)
+returns anycompatible as $$
+  select lower($1) + upper($2)
+$$ language sql;
+
+select x, pg_typeof(x) from anyctest(multirange(int4range(11,12)), multirange(int4range(4,7))) x;
+select x, pg_typeof(x) from anyctest(multirange(int4range(11,12)), multirange(numrange(4,7))) x; -- fail
+
+drop function anyctest(anycompatiblemultirange, anycompatiblemultirange);
+
+-- fail, can't infer result type:
+create function anyctest(anycompatible)
+returns anycompatiblemultirange as $$
+  select $1
+$$ language sql;
+
 create function anyctest(anycompatiblenonarray, anycompatiblenonarray)
 returns anycompatiblearray as $$
   select array[$1, $2]
diff --git a/src/test/regress/sql/rangetypes.sql b/src/test/regress/sql/rangetypes.sql
index 4048b1d185..0276d19136 100644
--- a/src/test/regress/sql/rangetypes.sql
+++ b/src/test/regress/sql/rangetypes.sql
@@ -118,6 +118,10 @@ select numrange(1.0, 2.0) * numrange(2.0, 3.0);
 select numrange(1.0, 2.0) * numrange(1.5, 3.0);
 select numrange(1.0, 2.0) * numrange(2.5, 3.0);
 
+select range_intersect_agg(nr) from numrange_test;
+select range_intersect_agg(nr) from numrange_test where false;
+select range_intersect_agg(nr) from numrange_test where nr @> 4.0;
+
 create table numrange_test2(nr numrange);
 create index numrange_test2_hash_idx on numrange_test2 using hash (nr);
 
@@ -528,6 +532,7 @@ reset enable_sort;
 -- OUT/INOUT/TABLE functions
 --
 
+-- infer anyrange from anyrange
 create function outparam_succeed(i anyrange, out r anyrange, out t text)
   as $$ select $1, 'foo'::text $$ language sql;
 
@@ -539,6 +544,13 @@ create function outparam2_succeed(r anyrange, out lu anyarray, out ul anyarray)
 
 select * from outparam2_succeed(int4range(1,11));
 
+-- infer anyarray from anyrange
+create function outparam_succeed2(i anyrange, out r anyarray, out t text)
+  as $$ select ARRAY[upper($1)], 'foo'::text $$ language sql;
+
+select * from outparam_succeed2(int4range(int4range(1,2)));
+
+-- infer anyelement from anyrange
 create function inoutparam_succeed(out i anyelement, inout r anyrange)
   as $$ select upper($1), $1 $$ language sql;
 
diff --git a/src/test/regress/sql/type_sanity.sql b/src/test/regress/sql/type_sanity.sql
index 4b492ce062..07879d9a57 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'));
@@ -153,7 +153,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')
@@ -182,7 +182,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')
@@ -234,7 +234,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')
@@ -263,7 +263,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')
@@ -467,3 +467,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.rngmultitypid
+FROM pg_range p1
+WHERE p1.rngmultitypid IS NULL OR p1.rngmultitypid = 0;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 525d58e7f0..0b6a0717e9 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1362,6 +1362,9 @@ MultiXactMember
 MultiXactOffset
 MultiXactStateData
 MultiXactStatus
+MultirangeIOData
+MultirangeParseState
+MultirangeType
 MyData
 NDBOX
 NODE
#122Paul A Jungwirth
pj@illuminatedcomputing.com
In reply to: Paul A Jungwirth (#121)
1 attachment(s)
Re: range_agg

On Fri, Apr 10, 2020 at 8:44 PM Paul A Jungwirth
<pj@illuminatedcomputing.com> wrote:

Thanks, and thanks for your v17 also. Here is a patch building on that
and adding support for anycompatiblemultirange.

Here is a v19 that just moved the multirange tests to a new parallel
group to avoid a max-20-tests error. Sorry about that!

Btw I'm working on typanalyze + selectivity, and it seems like the
test suite doesn't run those things? At least I can't seem to get it
to call the existing range typanalyze functions. Those would run from
the vacuum process, not an ordinary backend, right? Is there a
separate test suite for that I'm overlooking? I'm sure I can figure it
out, but since I'm uploading a new patch file I thought I'd ask. . . .

Thanks,
Paul

Attachments:

v19-multirange.patchapplication/octet-stream; name=v19-multirange.patchDownload
diff --git a/doc/src/sgml/datatype.sgml b/doc/src/sgml/datatype.sgml
index c2e42f31c0..22404d18cd 100644
--- a/doc/src/sgml/datatype.sgml
+++ b/doc/src/sgml/datatype.sgml
@@ -4812,6 +4812,10 @@ SELECT * FROM pg_attribute
    </indexterm>
 
    <indexterm zone="datatype-pseudo">
+    <primary>anymultirange</primary>
+   </indexterm>
+
+   <indexterm zone="datatype-pseudo">
     <primary>anycompatible</primary>
    </indexterm>
 
@@ -4828,6 +4832,10 @@ SELECT * FROM pg_attribute
    </indexterm>
 
    <indexterm zone="datatype-pseudo">
+    <primary>anycompatiblemultirange</primary>
+   </indexterm>
+
+   <indexterm zone="datatype-pseudo">
     <primary>void</primary>
    </indexterm>
 
@@ -4937,6 +4945,13 @@ SELECT * FROM pg_attribute
        </row>
 
        <row>
+        <entry><type>anymultirange</type></entry>
+        <entry>Indicates that a function accepts any multirange data type
+        (see <xref linkend="extend-types-polymorphic"/> and
+        <xref linkend="rangetypes"/>).</entry>
+       </row>
+
+       <row>
         <entry><type>anycompatible</type></entry>
         <entry>Indicates that a function accepts any data type,
         with automatic promotion of multiple arguments to a common data type
@@ -4966,6 +4981,14 @@ SELECT * FROM pg_attribute
        </row>
 
        <row>
+        <entry><type>anycompatiblemultirange</type></entry>
+        <entry>Indicates that a function accepts any multirange data type,
+        with automatic promotion of multiple arguments to a common data type
+        (see <xref linkend="extend-types-polymorphic"/> and
+        <xref linkend="rangetypes"/>).</entry>
+       </row>
+
+       <row>
         <entry><type>cstring</type></entry>
         <entry>Indicates that a function accepts or returns a null-terminated C string.</entry>
        </row>
diff --git a/doc/src/sgml/extend.sgml b/doc/src/sgml/extend.sgml
index 930aeb767c..3f62c06daf 100644
--- a/doc/src/sgml/extend.sgml
+++ b/doc/src/sgml/extend.sgml
@@ -286,6 +286,14 @@
        </row>
 
        <row>
+        <entry><type>anymultirange</type></entry>
+        <entry>Simple</entry>
+        <entry>Indicates that a function accepts any multirange data type
+        (see <xref linkend="rangetypes"/>)
+        </entry>
+       </row>
+
+       <row>
         <entry><type>anycompatible</type></entry>
         <entry>Common</entry>
         <entry>Indicates that a function accepts any data type,
@@ -316,6 +324,14 @@
         with automatic promotion of multiple arguments to a common data type
         </entry>
        </row>
+
+       <row>
+        <entry><type>anycompatiblemultirange</type></entry>
+        <entry>Common</entry>
+        <entry>Indicates that a function accepts any multirange data type,
+        with automatic promotion of multiple arguments to a common data type
+        </entry>
+       </row>
       </tbody>
      </tgroup>
     </table>
@@ -343,17 +359,15 @@
      position declared as <type>anyarray</type> can have any array data type,
      but similarly they must all be the same type.  And similarly,
      positions declared as <type>anyrange</type> must all be the same range
-     type.  Furthermore, if there are
+     type.  Likewise for <type>anymultirange</type>.
+    </para>
+
+    <para>
+     Furthermore, if there are
      positions declared <type>anyarray</type> and others declared
      <type>anyelement</type>, the actual array type in the
      <type>anyarray</type> positions must be an array whose elements are
      the same type appearing in the <type>anyelement</type> positions.
-     Similarly, if there are positions declared <type>anyrange</type>
-     and others declared <type>anyelement</type> or <type>anyarray</type>,
-     the actual range type in the <type>anyrange</type> positions must be a
-     range whose subtype is the same type appearing in
-     the <type>anyelement</type> positions and the same as the element type
-     of the <type>anyarray</type> positions.
      <type>anynonarray</type> is treated exactly the same as <type>anyelement</type>,
      but adds the additional constraint that the actual type must not be
      an array type.
@@ -363,6 +377,19 @@
     </para>
 
     <para>
+     Similarly, if there are positions declared <type>anyrange</type>
+     and others declared <type>anyelement</type> or <type>anyarray</type>,
+     the actual range type in the <type>anyrange</type> positions must be a
+     range whose subtype is the same type appearing in
+     the <type>anyelement</type> positions and the same as the element type
+     of the <type>anyarray</type> positions.
+     If there are positions declared <type>anymultirange</type>,
+     their actual multirange type must contain ranges matching parameters declared
+     <type>anyrange</type> and base elements matching parameters declared
+     <type>anyelement</type> and <type>anyarray</type>.
+    </para>
+
+    <para>
      Thus, when more than one argument position is declared with a polymorphic
      type, the net effect is that only certain combinations of actual argument
      types are allowed.  For example, a function declared as
@@ -417,7 +444,8 @@
      Selection of the common type considers the actual types
      of <type>anycompatible</type> and <type>anycompatiblenonarray</type>
      inputs, the array element types of <type>anycompatiblearray</type>
-     inputs, and the range subtypes of <type>anycompatiblerange</type>
+     inputs, the range subtypes of <type>anycompatiblerange</type> inputs,
+     and the multirange subtypes of <type>anycompatiablemultirange</type>
      inputs.  If <type>anycompatiblenonarray</type> is present then the
      common type is required to be a non-array type.  Once a common type is
      identified, arguments in <type>anycompatible</type>
@@ -428,12 +456,15 @@
 
     <para>
      Since there is no way to select a range type knowing only its subtype,
-     use of <type>anycompatiblerange</type> requires that all arguments
-     declared with that type have the same actual range type, and that that
-     type's subtype agree with the selected common type, so that no casting
-     of the range values is required.  As with <type>anyrange</type>, use
-     of <type>anycompatiblerange</type> as a function result type requires
-     that there be an <type>anycompatiblerange</type> argument.
+     use of <type>anycompatiblerange</type> and/or
+     <type>anycompatiblemultirange</type> requires that all arguments declared
+     with that type have the same actual range and/or multirange type, and that
+     that type's subtype agree with the selected common type, so that no casting
+     of the range values is required.  As with <type>anyrange</type> and
+     <type>anymultirange</type>, use of <type>anycompatiblerange</type> and
+     <type>anymultirange</type> as a function result type requires that there be
+     an <type>anycompatiblerange</type> or <type>anycompatiblemultirange</type>
+     argument.
     </para>
 
     <para>
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 12d75b476f..2e7c62f1a0 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -13609,7 +13609,7 @@ SELECT NULLIF(value, '(none)') ...
        <row>
         <entry>Operator</entry>
         <entry>Description</entry>
-        <entry>Example</entry>
+        <entry>Examples</entry>
         <entry>Result</entry>
        </row>
       </thead>
@@ -14080,12 +14080,14 @@ NULL baz</literallayout>(3 rows)</entry>
   <title>Range Functions and Operators</title>
 
   <para>
-   See <xref linkend="rangetypes"/> for an overview of range types.
+   See <xref linkend="rangetypes"/> for an overview of range and multirange types.
   </para>
 
   <para>
    <xref linkend="range-operators-table"/> shows the operators
-   available for range types.
+   available for range and multirange types.
+   Many of these operators will accept either a range or multirange
+   on either side.
   </para>
 
     <table id="range-operators-table">
@@ -14103,134 +14105,215 @@ NULL baz</literallayout>(3 rows)</entry>
        <row>
         <entry> <literal>=</literal> </entry>
         <entry>equal</entry>
-        <entry><literal>int4range(1,5) = '[1,4]'::int4range</literal></entry>
-        <entry><literal>t</literal></entry>
+        <entry><literallayout class="monospaced">int4range(1,5) = '[1,4]'::int4range
+'{[1,5)}'::int4multirange = '{[1,4]}'::int4multirange</literallayout></entry>
+        <entry><literallayout class="monospaced">t</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>&lt;&gt;</literal> </entry>
         <entry>not equal</entry>
-        <entry><literal>numrange(1.1,2.2) &lt;&gt; numrange(1.1,2.3)</literal></entry>
-        <entry><literal>t</literal></entry>
+        <entry><literallayout class="monospaced">numrange(1.1,2.2) &lt;&gt; numrange(1.1,2.3)
+'{[1.1,2.2)}'::nummultirange &lt;&gt; '{[1.1,2.3)}'::nummultirange</literallayout></entry>
+        <entry><literallayout class="monospaced">t</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>&lt;</literal> </entry>
         <entry>less than</entry>
-        <entry><literal>int4range(1,10) &lt; int4range(2,3)</literal></entry>
-        <entry><literal>t</literal></entry>
+        <entry><literallayout class="monospaced">int4range(1,10) &lt; int4range(2,3)
+'{[1,10)}'::int4multirange &lt; '{[2,3)}'::int4multirange</literallayout>
+        </entry>
+        <entry><literallayout class="monospaced">t</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>&gt;</literal> </entry>
         <entry>greater than</entry>
-        <entry><literal>int4range(1,10) &gt; int4range(1,5)</literal></entry>
-        <entry><literal>t</literal></entry>
+        <entry>
+          <literallayout class="monospaced">int4range(1,10) &gt; int4range(1,5)
+'{[1,10)}'::int4multirange &gt; '{[1,5)}'::int4multirange</literallayout>
+        </entry>
+        <entry><literallayout class="monospaced">t</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>&lt;=</literal> </entry>
         <entry>less than or equal</entry>
-        <entry><literal>numrange(1.1,2.2) &lt;= numrange(1.1,2.2)</literal></entry>
-        <entry><literal>t</literal></entry>
+        <entry>
+          <literallayout class="monospaced">numrange(1.1,2.2) &lt;= numrange(1.1,2.2)
+'{[1.1,2.2)}'::nummultirange &lt;= '{[1.1,2.2)}'::nummultirange</literallayout>
+        </entry>
+        <entry><literallayout class="monospaced">t</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>&gt;=</literal> </entry>
         <entry>greater than or equal</entry>
-        <entry><literal>numrange(1.1,2.2) &gt;= numrange(1.1,2.0)</literal></entry>
-        <entry><literal>t</literal></entry>
+        <entry>
+          <literallayout class="monospaced">numrange(1.1,2.2) &gt;= numrange(1.1,2.0)
+'{[1.1,2.2)}'::nummultirange &gt;= '{[1.1,2.0)}'::nummultirange</literallayout>
+        </entry>
+        <entry><literallayout class="monospaced">t</literallayout></entry>
+       </row>
+
+       <row>
+        <entry> <literal>@&gt;</literal> </entry>
+        <entry>contains multirange</entry>
+        <entry>
+          <literallayout class="monospaced">int4range(2,4) @&gt; '{[2,3)}'::int4multirange
+'{[2,4)}'::int4multirange @&gt; '{[2,3)}'::int4multirange</literallayout>
+        </entry>
+        <entry><literallayout class="monospaced">t</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>@&gt;</literal> </entry>
         <entry>contains range</entry>
-        <entry><literal>int4range(2,4) @&gt; int4range(2,3)</literal></entry>
-        <entry><literal>t</literal></entry>
+        <entry>
+          <literallayout class="monospaced">int4range(2,4) @&gt; int4range(2,3)
+'{[2,4)}'::int4multirange @&gt; int4range(2,3)</literallayout>
+        </entry>
+        <entry><literallayout class="monospaced">t</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>@&gt;</literal> </entry>
         <entry>contains element</entry>
-        <entry><literal>'[2011-01-01,2011-03-01)'::tsrange @&gt; '2011-01-10'::timestamp</literal></entry>
-        <entry><literal>t</literal></entry>
+        <entry>
+          <literallayout class="monospaced">'[2011-01-01,2011-03-01)'::tsrange @&gt; '2011-01-10'::timestamp
+'{[2011-01-01,2011-03-01)}'::tsmultirange @&gt; '2011-01-10'::timestamp</literallayout>
+        </entry>
+        <entry><literallayout class="monospaced">t</literallayout></entry>
+       </row>
+
+       <row>
+        <entry> <literal>&lt;@</literal> </entry>
+        <entry>multirange is contained by</entry>
+        <entry>
+          <literallayout class="monospaced">'{[2,4)}'::int4multirange &lt;@ int4range(1,7)
+'{[2,4)}'::int4multirange &lt;@ '{[1,7)}'::int4multirange</literallayout>
+        </entry>
+        <entry><literallayout class="monospaced">t</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>&lt;@</literal> </entry>
         <entry>range is contained by</entry>
-        <entry><literal>int4range(2,4) &lt;@ int4range(1,7)</literal></entry>
-        <entry><literal>t</literal></entry>
+        <entry>
+          <literallayout class="monospaced">int4range(2,4) &lt;@ int4range(1,7)
+int4range(2,4) &lt;@ '{[1,7)}'::int4multirange</literallayout>
+        </entry>
+        <entry><literallayout class="monospaced">t</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>&lt;@</literal> </entry>
         <entry>element is contained by</entry>
-        <entry><literal>42 &lt;@ int4range(1,7)</literal></entry>
-        <entry><literal>f</literal></entry>
+        <entry>
+          <literallayout class="monospaced">42 &lt;@ int4range(1,7)
+42 &lt;@ '{[1,7)}'::int4multirange</literallayout>
+        </entry>
+        <entry><literallayout class="monospaced">f</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>&amp;&amp;</literal> </entry>
         <entry>overlap (have points in common)</entry>
-        <entry><literal>int8range(3,7) &amp;&amp; int8range(4,12)</literal></entry>
-        <entry><literal>t</literal></entry>
+        <entry>
+          <literallayout class="monospaced">int8range(3,7) &amp;&amp; int8range(4,12)
+int8range(3,7) &amp;&amp; '{[4,12)}'::int8multirange
+'{[3,7)}'::int8multirange &amp;&amp; int8range(4,12)
+'{[3,7)}'::int8multirange &amp;&amp; '{[4,12)}'::int8multirange</literallayout>
+        </entry>
+        <entry><literallayout class="monospaced">t</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>&lt;&lt;</literal> </entry>
         <entry>strictly left of</entry>
-        <entry><literal>int8range(1,10) &lt;&lt; int8range(100,110)</literal></entry>
-        <entry><literal>t</literal></entry>
+        <entry>
+          <literallayout class="monospaced">int8range(1,10) &lt;&lt; int8range(100,110)
+int8range(1,10) &lt;&lt; '{[100,110)}'::int8multirange
+'{[1,10)}'::int8multirange &lt;&lt; int8range(100,110)
+'{[1,10)}'::int8multirange &lt;&lt; '{[100,110)}'::int8multirange</literallayout>
+        </entry>
+        <entry><literallayout class="monospaced">t</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>&gt;&gt;</literal> </entry>
         <entry>strictly right of</entry>
-        <entry><literal>int8range(50,60) &gt;&gt; int8range(20,30)</literal></entry>
-        <entry><literal>t</literal></entry>
+        <entry>
+          <literallayout class="monospaced">int8range(50,60) &gt;&gt; int8range(20,30)
+int8range(50,60) &gt;&gt; '{[20,30)}'::int8multirange
+'{[50,60)}'::int8multirange &gt;&gt; int8range(20,30)
+'{[50,60)}'::int8multirange &gt;&gt; '{[20,30)}'::int8multirange</literallayout>
+        </entry>
+        <entry><literallayout class="monospaced">t</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>&amp;&lt;</literal> </entry>
         <entry>does not extend to the right of</entry>
-        <entry><literal>int8range(1,20) &amp;&lt; int8range(18,20)</literal></entry>
-        <entry><literal>t</literal></entry>
+        <entry>
+          <literallayout class="monospaced">int8range(1,20) &amp;&lt; int8range(18,20)
+int8range(1,20) &amp;&lt; '{[18,20)}'::int8multirange
+'{[1,20)}'::int8multirange &amp;&lt; int8range(18,20)
+'{[1,20)}'::int8multirange &amp;&lt; '{[18,20)}'::int8multirange</literallayout>
+        </entry>
+        <entry><literallayout class="monospaced">t</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>&amp;&gt;</literal> </entry>
         <entry>does not extend to the left of</entry>
-        <entry><literal>int8range(7,20) &amp;&gt; int8range(5,10)</literal></entry>
-        <entry><literal>t</literal></entry>
+        <entry>
+          <literallayout class="monospaced">int8range(7,20) &amp;&gt; int8range(5,10)
+int8range(7,20) &amp;&gt; '{[5,10)}'::int8multirange
+'{[7,20)}'::int8multirange &amp;&gt; int8range(5,10)
+'{[7,20)}'::int8multirange &amp;&gt; '{[5,10)}'::int8multirange</literallayout>
+        </entry>
+        <entry><literallayout class="monospaced">t</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>-|-</literal> </entry>
         <entry>is adjacent to</entry>
-        <entry><literal>numrange(1.1,2.2) -|- numrange(2.2,3.3)</literal></entry>
-        <entry><literal>t</literal></entry>
+        <entry>
+          <literallayout class="monospaced">numrange(1.1,2.2) -|- numrange(2.2,3.3)
+numrange(1.1,2.2) -|- '{[2.2,3.3)}'::nummultirange
+'{[1.1,2.2)}'::nummultirange -|- numrange(2.2,3.3)
+'{[1.1,2.2)}'::nummultirange -|- '{[2.2,3.3)}'::nummultirange</literallayout>
+        </entry>
+        <entry><literallayout class="monospaced">t</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>+</literal> </entry>
         <entry>union</entry>
-        <entry><literal>numrange(5,15) + numrange(10,20)</literal></entry>
-        <entry><literal>[5,20)</literal></entry>
+        <entry><literallayout class="monospaced">numrange(5,15) + numrange(10,20)
+'{[5,10)}'::nummultirange + '{[15,20)}'::nummultirange</literallayout></entry>
+        <entry><literallayout class="monospaced">[5,20)
+{[5,10), [15,20)}</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>*</literal> </entry>
         <entry>intersection</entry>
-        <entry><literal>int8range(5,15) * int8range(10,20)</literal></entry>
-        <entry><literal>[10,15)</literal></entry>
+        <entry><literallayout class="monospaced">int8range(5,15) * int8range(10,20)
+'{[5,15)}'::int8multirange * '{[10,20)}'::int8multirange</literallayout></entry>
+        <entry><literallayout class="monospaced">[10,15)
+{[10,15)}</literallayout></entry>
        </row>
 
        <row>
         <entry> <literal>-</literal> </entry>
         <entry>difference</entry>
-        <entry><literal>int8range(5,15) - int8range(10,20)</literal></entry>
-        <entry><literal>[5,10)</literal></entry>
+        <entry><literallayout class="monospaced">int8range(5,15) - int8range(10,20)
+'{[5,20)}'::int8multirange - '{[10,15)}'::int8multirange</literallayout></entry>
+        <entry><literallayout class="monospaced">[5,10)
+{[5,10), [15,20)}</literallayout></entry>
        </row>
 
       </tbody>
@@ -14248,19 +14331,31 @@ NULL baz</literallayout>(3 rows)</entry>
 
   <para>
    The left-of/right-of/adjacent operators always return false when an empty
-   range is involved; that is, an empty range is not considered to be either
-   before or after any other range.
+   range or multirange is involved; that is, an empty range is not considered to
+   be either before or after any other range.
+  </para>
+
+  <para>
+   Elsewhere empty ranges and multiranges are treated as the additive identity:
+   anything unioned with an empty value is itself. Anything minus an empty
+   value is itself. An empty multirange has exactly the same points as an empty
+   range. Every range contains the empty range. Every multirange contains as many
+   empty ranges as you like.
   </para>
 
   <para>
    The union and difference operators will fail if the resulting range would
    need to contain two disjoint sub-ranges, as such a range cannot be
-   represented.
+   represented. There are separate operators for union and difference that take
+   multirange parameters and return a multirange, and they do not fail even if
+   their arguments are disjoint. So if you need a union or difference operation
+   for ranges that may be disjoint, you can avoid errors by first casting your
+   ranges to multiranges.
   </para>
 
   <para>
    <xref linkend="range-functions-table"/> shows the functions
-   available for use with range types.
+   available for use with range and multirange types.
   </para>
 
   <indexterm>
@@ -14312,6 +14407,17 @@ NULL baz</literallayout>(3 rows)</entry>
        <row>
         <entry>
          <literal>
+          <function>lower</function>(<type>anymultirange</type>)
+         </literal>
+        </entry>
+        <entry>multirange's element type</entry>
+        <entry>lower bound of multirange</entry>
+        <entry><literal>lower('{[1.1,2.2)}'::nummultirange)</literal></entry>
+        <entry><literal>1.1</literal></entry>
+       </row>
+       <row>
+        <entry>
+         <literal>
           <function>upper</function>(<type>anyrange</type>)
          </literal>
         </entry>
@@ -14323,6 +14429,17 @@ NULL baz</literallayout>(3 rows)</entry>
        <row>
         <entry>
          <literal>
+          <function>upper</function>(<type>anymultirange</type>)
+         </literal>
+        </entry>
+        <entry>multirange's element type</entry>
+        <entry>upper bound of multirange</entry>
+        <entry><literal>upper('{[1.1,2.2)}'::nummultirange)</literal></entry>
+        <entry><literal>2.2</literal></entry>
+       </row>
+       <row>
+        <entry>
+         <literal>
           <function>isempty</function>(<type>anyrange</type>)
          </literal>
         </entry>
@@ -14334,6 +14451,17 @@ NULL baz</literallayout>(3 rows)</entry>
        <row>
         <entry>
          <literal>
+          <function>isempty</function>(<type>anymultirange</type>)
+         </literal>
+        </entry>
+        <entry><type>boolean</type></entry>
+        <entry>is the multirange empty?</entry>
+        <entry><literal>isempty('{[1.1,2.2)}'::nummultirange)</literal></entry>
+        <entry><literal>false</literal></entry>
+       </row>
+       <row>
+        <entry>
+         <literal>
           <function>lower_inc</function>(<type>anyrange</type>)
          </literal>
         </entry>
@@ -14345,6 +14473,17 @@ NULL baz</literallayout>(3 rows)</entry>
        <row>
         <entry>
          <literal>
+          <function>lower_inc</function>(<type>anymultirange</type>)
+         </literal>
+        </entry>
+        <entry><type>boolean</type></entry>
+        <entry>is the lower bound inclusive?</entry>
+        <entry><literal>lower_inc('{[1.1,2.2)}'::nummultirange)</literal></entry>
+        <entry><literal>true</literal></entry>
+       </row>
+       <row>
+        <entry>
+         <literal>
           <function>upper_inc</function>(<type>anyrange</type>)
          </literal>
         </entry>
@@ -14356,6 +14495,17 @@ NULL baz</literallayout>(3 rows)</entry>
        <row>
         <entry>
          <literal>
+          <function>upper_inc</function>(<type>anymultirange</type>)
+         </literal>
+        </entry>
+        <entry><type>boolean</type></entry>
+        <entry>is the upper bound inclusive?</entry>
+        <entry><literal>upper_inc('{[1.1,2.2)}'::nummultirange)</literal></entry>
+        <entry><literal>false</literal></entry>
+       </row>
+       <row>
+        <entry>
+         <literal>
           <function>lower_inf</function>(<type>anyrange</type>)
          </literal>
         </entry>
@@ -14367,6 +14517,17 @@ NULL baz</literallayout>(3 rows)</entry>
        <row>
         <entry>
          <literal>
+          <function>lower_inf</function>(<type>anymultirange</type>)
+         </literal>
+        </entry>
+        <entry><type>boolean</type></entry>
+        <entry>is the lower bound infinite?</entry>
+        <entry><literal>lower_inf('{(,)}'::datemultirange)</literal></entry>
+        <entry><literal>true</literal></entry>
+       </row>
+       <row>
+        <entry>
+         <literal>
           <function>upper_inf</function>(<type>anyrange</type>)
          </literal>
         </entry>
@@ -14378,6 +14539,17 @@ NULL baz</literallayout>(3 rows)</entry>
        <row>
         <entry>
          <literal>
+          <function>upper_inf</function>(<type>anymultirange</type>)
+         </literal>
+        </entry>
+        <entry><type>boolean</type></entry>
+        <entry>is the upper bound infinite?</entry>
+        <entry><literal>upper_inf('{(,)}'::datemultirange)</literal></entry>
+        <entry><literal>true</literal></entry>
+       </row>
+       <row>
+        <entry>
+         <literal>
           <function>range_merge</function>(<type>anyrange</type>, <type>anyrange</type>)
          </literal>
         </entry>
@@ -14386,16 +14558,38 @@ NULL baz</literallayout>(3 rows)</entry>
         <entry><literal>range_merge('[1,2)'::int4range, '[3,4)'::int4range)</literal></entry>
         <entry><literal>[1,4)</literal></entry>
        </row>
+       <row>
+        <entry>
+         <literal>
+          <function>range_merge</function>(<type>anymultirange</type>)
+         </literal>
+        </entry>
+        <entry><type>anyrange</type></entry>
+        <entry>the smallest range which includes the entire multirange</entry>
+        <entry><literal>range_merge('{[1,2), [3,4)}'::int4multirange)</literal></entry>
+        <entry><literal>[1,4)</literal></entry>
+       </row>
+       <row>
+        <entry>
+         <literal>
+          <function>multirange</function>(<type>anyrange</type>)
+         </literal>
+        </entry>
+        <entry><type>anymultirange</type></entry>
+        <entry>a multirange containing just the given range</entry>
+        <entry><literal>multirange('[1,2)'::int4range)</literal></entry>
+        <entry><literal>{[1,2)}</literal></entry>
+       </row>
       </tbody>
      </tgroup>
     </table>
 
   <para>
    The <function>lower</function> and  <function>upper</function> functions return null
-   if the range is empty or the requested bound is infinite.
+   if the input range/multirange is empty or the requested bound is infinite.
    The <function>lower_inc</function>, <function>upper_inc</function>,
    <function>lower_inf</function>, and <function>upper_inf</function>
-   functions all return false for an empty range.
+   functions all return false for an empty range or multirange.
   </para>
   </sect1>
 
@@ -14717,6 +14911,44 @@ NULL baz</literallayout>(3 rows)</entry>
      <row>
       <entry>
        <indexterm>
+        <primary>range_agg</primary>
+       </indexterm>
+       <function>
+         range_agg(<replaceable class="parameter">expression</replaceable>)
+       </function>
+      </entry>
+      <entry>
+       <type>anyrange</type>
+      </entry>
+      <entry>
+       <type>multirange</type> of the argument range type
+      </entry>
+      <entry>Yes</entry>
+      <entry>union of the non-null input values</entry>
+     </row>
+
+     <row>
+      <entry>
+       <indexterm>
+        <primary>range_intersect_agg</primary>
+       </indexterm>
+       <function>
+         range_intersect_agg(<replaceable class="parameter">expression</replaceable>)
+       </function>
+      </entry>
+      <entry>
+       <type>anyrange</type>
+      </entry>
+      <entry>
+       <type>multirange</type> of the argument range type
+      </entry>
+      <entry>No</entry>
+      <entry>intersection of the non-null input values</entry>
+     </row>
+
+     <row>
+      <entry>
+       <indexterm>
         <primary>string_agg</primary>
        </indexterm>
        <function>
diff --git a/doc/src/sgml/rangetypes.sgml b/doc/src/sgml/rangetypes.sgml
index b75fb3a392..c8784bdce3 100644
--- a/doc/src/sgml/rangetypes.sgml
+++ b/doc/src/sgml/rangetypes.sgml
@@ -27,6 +27,13 @@
   ranges from an instrument, and so forth can also be useful.
  </para>
 
+ <para>
+  Every range type has a corresponding multirange type. A multirange is
+  an ordered list of non-continguous, non-empty, non-null ranges. Most
+  range operators also work on multiranges, and they have a few functions
+  of their own.
+ </para>
+
  <sect2 id="rangetypes-builtin">
   <title>Built-in Range Types</title>
 
@@ -232,10 +239,30 @@ SELECT '[4,4]'::int4range;
 SELECT '[4,4)'::int4range;
 </programlisting>
   </para>
+
+  <para>
+   The input for a multirange is curly brackets (<literal>{</literal> and
+   <literal>}</literal>) containing zero or more valid ranges,
+   separated by commas. Whitespace is permitted around the brackets and
+   commas. This is intended to be reminiscent of array syntax, although
+   multiranges are much simpler: they have just one dimension and there is
+   no need to quote their contents. (The bounds of their ranges may be
+   quoted as above however.)
+  </para>
+
+  <para>
+  Examples:
+<programlisting>
+SELECT '{}'::int4multirange;
+SELECT '{[3,7)}'::int4multirange;
+SELECT '{[3,7), [8,9)}'::int4multirange;
+</programlisting>
+  </para>
+
  </sect2>
 
  <sect2 id="rangetypes-construct">
-  <title>Constructing Ranges</title>
+  <title>Constructing Ranges and Multiranges</title>
 
   <para>
    Each range type has a constructor function with the same name as the range
@@ -269,6 +296,19 @@ SELECT int8range(1, 14, '(]');
 SELECT numrange(NULL, 2.2);
 </programlisting>
   </para>
+
+  <para>
+   Each range type also has a multirange constructor with the same name as the
+   multirange type.  The constructor function takes zero or more arguments
+   which are all ranges of the appropriate type.
+   For example:
+
+<programlisting>
+SELECT nummultirange();
+SELECT nummultirange(numrange(1.0, 14.0));
+SELECT nummultirange(numrange(1.0, 14.0), numrange(20.0, 25.0));
+</programlisting>
+  </para>
  </sect2>
 
  <sect2 id="rangetypes-discrete">
@@ -342,6 +382,11 @@ SELECT '[1.234, 5.678]'::floatrange;
   </para>
 
   <para>
+   When you define your own range you automatically get a corresponding
+   multirange type.
+  </para>
+
+  <para>
    Defining your own range type also allows you to specify a different
    subtype B-tree operator class or collation to use, so as to change the sort
    ordering that determines which values fall into a given range.
diff --git a/src/backend/catalog/pg_range.c b/src/backend/catalog/pg_range.c
index b5bc36c2bd..1217a6ddd0 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_rngmultitypid - 1] = ObjectIdGetDatum(multirangeTypeOid);
 
 	tup = heap_form_tuple(RelationGetDescr(pg_range), values, nulls);
 
@@ -100,6 +102,12 @@ 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 cd56714968..c359f27c74 100644
--- a/src/backend/catalog/pg_type.c
+++ b/src/backend/catalog/pg_type.c
@@ -27,6 +27,7 @@
 #include "catalog/pg_proc.h"
 #include "catalog/pg_type.h"
 #include "commands/typecmds.h"
+#include "mb/pg_wchar.h"
 #include "miscadmin.h"
 #include "parser/scansup.h"
 #include "utils/acl.h"
@@ -36,6 +37,9 @@
 #include "utils/rel.h"
 #include "utils/syscache.h"
 
+static char *makeUniqueTypeName(const char *typeName, Oid typeNamespace,
+								bool tryOriginal);
+
 /* Potentially set by pg_upgrade_support functions */
 Oid			binary_upgrade_next_pg_type_oid = InvalidOid;
 
@@ -809,31 +813,10 @@ RenameTypeInternal(Oid typeOid, const char *newTypeName, Oid typeNamespace)
 char *
 makeArrayTypeName(const char *typeName, Oid typeNamespace)
 {
-	char	   *arr = (char *) palloc(NAMEDATALEN);
-	int			namelen = strlen(typeName);
-	int			i;
-
-	/*
-	 * 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;
-	}
+	char	   *arr;
 
-	if (i >= NAMEDATALEN - 1)
+	arr = makeUniqueTypeName(typeName, typeNamespace, false);
+	if (arr == NULL)
 		ereport(ERROR,
 				(errcode(ERRCODE_DUPLICATE_OBJECT),
 				 errmsg("could not form array type name for type \"%s\"",
@@ -904,3 +887,91 @@ moveArrayTypeName(Oid typeOid, const char *typeName, Oid typeNamespace)
 
 	return true;
 }
+
+
+/*
+ * makeMultirangeTypeName
+ *	  - given a range type name, make a multirange type name for it
+ *
+ * caller is responsible for pfreeing the result
+ */
+char *
+makeMultirangeTypeName(const char *rangeTypeName, Oid typeNamespace)
+{
+	char	   *buf;
+	char	   *mrname;
+	char	   *rangestr;
+
+	/*
+	 * 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...
+	 */
+	rangestr = strstr(rangeTypeName, "range");
+	if (rangestr)
+	{
+		char	*prefix = pnstrdup(rangeTypeName, rangestr - rangeTypeName);
+
+		buf = psprintf("%s%s%s", prefix, "multi", rangestr);
+	}
+	else
+		buf = psprintf("%s_multirange", pnstrdup(rangeTypeName, NAMEDATALEN - 12));
+
+	/* clip it at NAMEDATALEN-1 bytes */
+	buf[pg_mbcliplen(buf, strlen(buf), NAMEDATALEN - 1)] = '\0';
+
+	mrname = makeUniqueTypeName(buf, typeNamespace, true);
+	if (mrname == NULL)
+		ereport(ERROR,
+				(errcode(ERRCODE_DUPLICATE_OBJECT),
+				 errmsg("could not form multirange type name for type \"%s\"",
+						rangeTypeName)));
+
+	return mrname;
+}
+
+/*
+ * makeUniqueTypeName
+ *		Generate a unique name for a prospective new type
+ *
+ * Given a typeName, return a new palloc'ed name by preprending underscores
+ * until a non-conflicting name results.
+ *
+ * If tryOriginal, first try with zero underscores.
+ */
+static char *
+makeUniqueTypeName(const char *typeName, Oid typeNamespace, bool tryOriginal)
+{
+	int			i;
+	int			namelen;
+	char		dest[NAMEDATALEN];
+
+	Assert(strlen(typeName) <= NAMEDATALEN - 1);
+
+	if (tryOriginal &&
+		!SearchSysCacheExists2(TYPENAMENSP,
+							   CStringGetDatum(typeName),
+							   ObjectIdGetDatum(typeNamespace)))
+		return pstrdup(typeName);
+
+	/*
+	 * The idea is to prepend underscores as needed until we make a name that
+	 * doesn't collide with anything ...
+	 */
+	namelen = strlen(typeName);
+	for (i = 1; i < NAMEDATALEN - 1; i++)
+	{
+		dest[i - 1] = '_';
+		strlcpy(dest + i, typeName, NAMEDATALEN - i);
+		if (namelen + i >= NAMEDATALEN)
+			truncate_identifier(dest, NAMEDATALEN, false);
+
+		if (!SearchSysCacheExists2(TYPENAMENSP,
+								   CStringGetDatum(dest),
+								   ObjectIdGetDatum(typeNamespace)))
+			return pstrdup(dest);
+	}
+
+	return NULL;
+}
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index 8891b1d564..dfb2edd3d0 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -105,9 +105,14 @@ typedef struct
 
 /* Potentially set by pg_upgrade_support functions */
 Oid			binary_upgrade_next_array_pg_type_oid = InvalidOid;
+Oid			binary_upgrade_next_mrng_pg_type_oid = InvalidOid;
+Oid			binary_upgrade_next_mrng_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 rangeOid,
+									   Oid rangeArrayOid, Oid *castFuncOid);
 static Oid	findTypeInputFunction(List *procname, Oid typeOid);
 static Oid	findTypeOutputFunction(List *procname, Oid typeOid);
 static Oid	findTypeReceiveFunction(List *procname, Oid typeOid);
@@ -738,7 +743,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",
@@ -1279,6 +1285,11 @@ checkEnumOwner(HeapTuple tup)
 /*
  * DefineRange
  *		Registers a new range type.
+ *
+ * Perhaps it might be worthwhile to set pg_type.typelem to the base type,
+ * and likewise on multiranges to set it to the range type. But having a
+ * non-zero typelem is treated elsewhere as a synonym for being an array,
+ * and users might have queries with that same assumption.
  */
 ObjectAddress
 DefineRange(CreateRangeStmt *stmt)
@@ -1287,7 +1298,11 @@ DefineRange(CreateRangeStmt *stmt)
 	Oid			typeNamespace;
 	Oid			typoid;
 	char	   *rangeArrayName;
+	char	   *multirangeTypeName;
+	char	   *multirangeArrayName;
 	Oid			rangeArrayOid;
+	Oid			multirangeOid;
+	Oid			multirangeArrayOid;
 	Oid			rangeSubtype = InvalidOid;
 	List	   *rangeSubOpclassName = NIL;
 	List	   *rangeCollationName = NIL;
@@ -1304,6 +1319,8 @@ DefineRange(CreateRangeStmt *stmt)
 	AclResult	aclresult;
 	ListCell   *lc;
 	ObjectAddress address;
+	ObjectAddress mltrngaddress;
+	Oid			castFuncOid;
 
 	/* Convert list of names to a name and namespace */
 	typeNamespace = QualifiedNameGetCreationNamespace(stmt->typeName,
@@ -1452,8 +1469,10 @@ DefineRange(CreateRangeStmt *stmt)
 	/* alignment must be TYPALIGN_INT or TYPALIGN_DOUBLE for ranges */
 	alignment = (subtypalign == TYPALIGN_DOUBLE) ? TYPALIGN_DOUBLE : TYPALIGN_INT;
 
-	/* Allocate OID for array type */
+	/* Allocate OID for array type, its multirange, and its multirange array */
 	rangeArrayOid = AssignTypeArrayOid();
+	multirangeOid = AssignTypeMultirangeOid();
+	multirangeArrayOid = AssignTypeMultirangeArrayOid();
 
 	/* Create the pg_type entry */
 	address =
@@ -1491,9 +1510,46 @@ DefineRange(CreateRangeStmt *stmt)
 	Assert(typoid == InvalidOid || typoid == address.objectId);
 	typoid = address.objectId;
 
+	/* Create the multirange that goes with it */
+	multirangeTypeName = makeMultirangeTypeName(typeName, typeNamespace);
+
+	mltrngaddress =
+		TypeCreate(multirangeOid,	/* force assignment of this 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_RANGE,	/* type-category (range 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) */
+	Assert(multirangeOid == mltrngaddress.objectId);
+
 	/* Create the entry in pg_range */
 	RangeCreate(typoid, rangeSubtype, rangeCollation, rangeSubOpclass,
-				rangeCanonical, rangeSubtypeDiff);
+				rangeCanonical, rangeSubtypeDiff, multirangeOid);
 
 	/*
 	 * Create the array type that goes with it.
@@ -1534,8 +1590,53 @@ 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 */
+			   multirangeOid,	/* 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,
+							   multirangeOid, typoid, rangeArrayOid,
+							   &castFuncOid);
+
+	/* Create cast from the range type to its multirange type */
+	CastCreate(typoid, multirangeOid, castFuncOid, 'e', 'f', DEPENDENCY_INTERNAL);
+
+	pfree(multirangeTypeName);
+	pfree(multirangeArrayName);
 
 	return address;
 }
@@ -1613,6 +1714,147 @@ 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.
+ *
+ * Sets castFuncOid to the oid of the new constructor that can be used
+ * to cast from a range to a multirange.
+ */
+static void
+makeMultirangeConstructors(const char *name, Oid namespace,
+						   Oid multirangeOid, Oid rangeOid, Oid rangeArrayOid,
+						   Oid *castFuncOid)
+{
+	ObjectAddress myself,
+				referenced;
+	oidvector  *argtypes;
+	Datum		allParamTypes;
+	ArrayType  *allParameterTypes;
+	Datum		paramModes;
+	ArrayType  *parameterModes;
+
+	referenced.classId = TypeRelationId;
+	referenced.objectId = multirangeOid;
+	referenced.objectSubId = 0;
+
+	/* 0-arg constructor - for empty multiranges */
+	argtypes = buildoidvector(NULL, 0);
+	myself = ProcedureCreate(name,	/* name: same as multirange type */
+							 namespace,
+							 false,	/* replace */
+							 false,	/* returns set */
+							 multirangeOid,	/* return type */
+							 BOOTSTRAP_SUPERUSERID,	/* proowner */
+							 INTERNALlanguageId,	/* language */
+							 F_FMGR_INTERNAL_VALIDATOR,
+							 "multirange_constructor0",	/* prosrc */
+							 NULL,	/* probin */
+							 PROKIND_FUNCTION,
+							 false,	/* security_definer */
+							 false,	/* leakproof */
+							 false, /* isStrict */
+							 PROVOLATILE_IMMUTABLE,	/* volatility */
+							 PROPARALLEL_SAFE,	/* parallel safety */
+							 argtypes,	/* parameterTypes */
+							 PointerGetDatum(NULL),	/* allParameterTypes */
+							 PointerGetDatum(NULL),	/* parameterModes */
+							 PointerGetDatum(NULL),	/* parameterNames */
+							 NIL,	/* parameterDefaults */
+							 PointerGetDatum(NULL),	/* trftypes */
+							 PointerGetDatum(NULL),	/* proconfig */
+							 InvalidOid,	/* prosupport */
+							 1.0,	/* procost */
+							 0.0);	/* prorows */
+	/*
+	 * Make the constructor 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);
+	pfree(argtypes);
+
+	/*
+	 * 1-arg constructor - for casts
+	 *
+	 * In theory we shouldn't need both this and the vararg (n-arg) constructor,
+	 * but having a separate 1-arg function lets us define casts against it.
+	 */
+	argtypes = buildoidvector(&rangeOid, 1);
+	myself = ProcedureCreate(name,	/* name: same as multirange type */
+							 namespace,
+							 false,	/* replace */
+							 false,	/* returns set */
+							 multirangeOid,	/* return type */
+							 BOOTSTRAP_SUPERUSERID,	/* proowner */
+							 INTERNALlanguageId,	/* language */
+							 F_FMGR_INTERNAL_VALIDATOR,
+							 "multirange_constructor1",	/* prosrc */
+							 NULL,	/* probin */
+							 PROKIND_FUNCTION,
+							 false,	/* security_definer */
+							 false,	/* leakproof */
+							 true, /* isStrict */
+							 PROVOLATILE_IMMUTABLE,	/* volatility */
+							 PROPARALLEL_SAFE,	/* parallel safety */
+							 argtypes,	/* parameterTypes */
+							 PointerGetDatum(NULL),	/* allParameterTypes */
+							 PointerGetDatum(NULL),	/* parameterModes */
+							 PointerGetDatum(NULL),	/* parameterNames */
+							 NIL,	/* parameterDefaults */
+							 PointerGetDatum(NULL),	/* trftypes */
+							 PointerGetDatum(NULL),	/* proconfig */
+							 InvalidOid,	/* prosupport */
+							 1.0,	/* procost */
+							 0.0);	/* prorows */
+	/* ditto */
+	recordDependencyOn(&myself, &referenced, DEPENDENCY_INTERNAL);
+	pfree(argtypes);
+	*castFuncOid = myself.objectId;
+
+	/* n-arg constructor - vararg */
+	argtypes = buildoidvector(&rangeArrayOid, 1);
+	allParamTypes = ObjectIdGetDatum(rangeArrayOid);
+	allParameterTypes = construct_array(&allParamTypes,
+										1, OIDOID,
+										sizeof(Oid), true, 'i');
+	paramModes = CharGetDatum(FUNC_PARAM_VARIADIC);
+	parameterModes = construct_array(&paramModes, 1, CHAROID,
+									 1, true, 'c');
+	myself = ProcedureCreate(name,	/* name: same as multirange type */
+							 namespace,
+							 false,	/* replace */
+							 false,	/* returns set */
+							 multirangeOid,	/* return type */
+							 BOOTSTRAP_SUPERUSERID,	/* proowner */
+							 INTERNALlanguageId,	/* language */
+							 F_FMGR_INTERNAL_VALIDATOR,
+							 "multirange_constructor2",	/* prosrc */
+							 NULL,	/* probin */
+							 PROKIND_FUNCTION,
+							 false,	/* security_definer */
+							 false,	/* leakproof */
+							 false, /* isStrict */
+							 PROVOLATILE_IMMUTABLE,	/* volatility */
+							 PROPARALLEL_SAFE,	/* parallel safety */
+							 argtypes,	/* parameterTypes */
+							 PointerGetDatum(allParameterTypes),	/* allParameterTypes */
+							 PointerGetDatum(parameterModes),	/* parameterModes */
+							 PointerGetDatum(NULL),	/* parameterNames */
+							 NIL,	/* parameterDefaults */
+							 PointerGetDatum(NULL),	/* trftypes */
+							 PointerGetDatum(NULL),	/* proconfig */
+							 InvalidOid,	/* prosupport */
+							 1.0,	/* procost */
+							 0.0);	/* prorows */
+	/* ditto */
+	recordDependencyOn(&myself, &referenced, DEPENDENCY_INTERNAL);
+	pfree(argtypes);
+	pfree(allParameterTypes);
+	pfree(parameterModes);
+}
 
 /*
  * Find suitable I/O functions for a type.
@@ -2045,6 +2287,72 @@ AssignTypeArrayOid(void)
 	return type_array_oid;
 }
 
+/*
+ *	AssignTypeMultirangeOid
+ *
+ *	Pre-assign the range type's multirange OID for use in pg_type.oid
+ */
+Oid
+AssignTypeMultirangeOid(void)
+{
+	Oid			type_multirange_oid;
+
+	/* Use binary-upgrade override for pg_type.oid? */
+	if (IsBinaryUpgrade)
+	{
+		if (!OidIsValid(binary_upgrade_next_mrng_pg_type_oid))
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+					 errmsg("pg_type multirange OID value not set when in binary upgrade mode")));
+
+		type_multirange_oid = binary_upgrade_next_mrng_pg_type_oid;
+		binary_upgrade_next_mrng_pg_type_oid = InvalidOid;
+	}
+	else
+	{
+		Relation	pg_type = table_open(TypeRelationId, AccessShareLock);
+
+		type_multirange_oid = GetNewOidWithIndex(pg_type, TypeOidIndexId,
+												 Anum_pg_type_oid);
+		table_close(pg_type, AccessShareLock);
+	}
+
+	return type_multirange_oid;
+}
+
+/*
+ *	AssignTypeMultirangeArrayOid
+ *
+ *	Pre-assign the range type's multirange array OID for use in pg_type.typarray
+ */
+Oid
+AssignTypeMultirangeArrayOid(void)
+{
+	Oid			type_multirange_array_oid;
+
+	/* Use binary-upgrade override for pg_type.oid? */
+	if (IsBinaryUpgrade)
+	{
+		if (!OidIsValid(binary_upgrade_next_mrng_array_pg_type_oid))
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+					 errmsg("pg_type multirange array OID value not set when in binary upgrade mode")));
+
+		type_multirange_array_oid = binary_upgrade_next_mrng_array_pg_type_oid;
+		binary_upgrade_next_mrng_array_pg_type_oid = InvalidOid;
+	}
+	else
+	{
+		Relation	pg_type = table_open(TypeRelationId, AccessShareLock);
+
+		type_multirange_array_oid = GetNewOidWithIndex(pg_type, TypeOidIndexId,
+													   Anum_pg_type_oid);
+		table_close(pg_type, AccessShareLock);
+	}
+
+	return type_multirange_array_oid;
+}
+
 
 /*-------------------------------------------------------------------
  * DefineCompositeType
diff --git a/src/backend/executor/functions.c b/src/backend/executor/functions.c
index 1c387a952e..e26822361c 100644
--- a/src/backend/executor/functions.c
+++ b/src/backend/executor/functions.c
@@ -1693,7 +1693,8 @@ check_sql_fn_retval(List *queryTreeList,
 	if (fn_typtype == TYPTYPE_BASE ||
 		fn_typtype == TYPTYPE_DOMAIN ||
 		fn_typtype == TYPTYPE_ENUM ||
-		fn_typtype == TYPTYPE_RANGE)
+		fn_typtype == TYPTYPE_RANGE ||
+		fn_typtype == TYPTYPE_MULTIRANGE)
 	{
 		/*
 		 * For scalar-type returns, the target list must have exactly one
diff --git a/src/backend/parser/parse_coerce.c b/src/backend/parser/parse_coerce.c
index 1b11cf731c..04cbffd7c2 100644
--- a/src/backend/parser/parse_coerce.c
+++ b/src/backend/parser/parse_coerce.c
@@ -189,19 +189,21 @@ coerce_type(ParseState *pstate, Node *node,
 	if (targetTypeId == ANYARRAYOID ||
 		targetTypeId == ANYENUMOID ||
 		targetTypeId == ANYRANGEOID ||
+		targetTypeId == ANYMULTIRANGEOID ||
 		targetTypeId == ANYCOMPATIBLEARRAYOID ||
-		targetTypeId == ANYCOMPATIBLERANGEOID)
+		targetTypeId == ANYCOMPATIBLERANGEOID ||
+		targetTypeId == ANYCOMPATIBLEMULTIRANGEOID)
 	{
 		/*
 		 * 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 the pseudotype's input
-		 * function, 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
+		 * the pseudotype's input function, 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
@@ -1532,8 +1534,8 @@ coerce_to_common_type(ParseState *pstate, Node *node,
  * 1) All arguments declared ANYELEMENT must have the same datatype.
  * 2) All arguments declared ANYARRAY must have the same datatype,
  *	  which must be a varlena array type.
- * 3) All arguments declared ANYRANGE must have the same datatype,
- *	  which must be a range type.
+ * 3) All arguments declared ANYRANGE or ANYMULTIRANGE must be a range or
+ *	  multirange type, all derived from the same base datatype.
  * 4) If there are arguments of more than one of these polymorphic types,
  *	  the array element type and/or range subtype must be the same as each
  *	  other and the same as the ANYELEMENT type.
@@ -1548,8 +1550,8 @@ coerce_to_common_type(ParseState *pstate, Node *node,
  *	  to a common supertype (chosen as per select_common_type's rules).
  *	  ANYCOMPATIBLENONARRAY works like ANYCOMPATIBLE but also requires the
  *	  common supertype to not be an array.  If there are ANYCOMPATIBLEARRAY
- *	  or ANYCOMPATIBLERANGE arguments, their element types or subtypes are
- *	  included while making the choice of common supertype.
+ *	  or ANYCOMPATIBLERANGE or ANYCOMPATIBLEMULTIRANGE arguments, their element
+ *	  types or subtypes are included while making the choice of common supertype.
  * 8) The resolved type of ANYCOMPATIBLEARRAY arguments will be the array
  *	  type over the common supertype (which might not be the same array type
  *	  as any of the original arrays).
@@ -1557,6 +1559,10 @@ coerce_to_common_type(ParseState *pstate, Node *node,
  *	  (after domain flattening), since we have no preference rule that would
  *	  let us choose one over another.  Furthermore, that range's subtype
  *	  must exactly match the common supertype chosen by rule 7.
+ * 10) All ANYCOMPATIBLEMULTIRANGE arguments must be the exact same multirange
+ *	  type (after domain flattening), since we have no preference rule that would
+ *	  let us choose one over another.  Furthermore, that multirange's range's
+ *	  subtype must exactly match the common supertype chosen by rule 7.
  *
  * Domains over arrays match ANYARRAY, and are immediately flattened to their
  * base type.  (Thus, for example, we will consider it a match if one ANYARRAY
@@ -1565,7 +1571,9 @@ coerce_to_common_type(ParseState *pstate, Node *node,
  * for ANYCOMPATIBLEARRAY and ANYCOMPATIBLENONARRAY.
  *
  * Similarly, domains over ranges match ANYRANGE or ANYCOMPATIBLERANGE,
- * and are immediately flattened to their base type.
+ * and are immediately flattened to their base type, and domains over
+ * multiranges match ANYMULTIRANGE or ANYCOMPATIBLEMULTIRANGE and are immediately
+ * flattened to their base type.
  *
  * Note that domains aren't currently considered to match ANYENUM,
  * even if their base type would match.
@@ -1583,8 +1591,12 @@ check_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			anycompatible_range_typeid = InvalidOid;
 	Oid			anycompatible_range_typelem = InvalidOid;
+	Oid			anycompatible_multirange_typeid = InvalidOid;
+	Oid			anycompatible_multirange_typelem = InvalidOid;
+	Oid			range_typelem = InvalidOid;
 	bool		have_anynonarray = false;
 	bool		have_anyenum = false;
 	bool		have_anycompatible_nonarray = false;
@@ -1633,6 +1645,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;
+		}
 		else if (decl_type == ANYCOMPATIBLEOID ||
 				 decl_type == ANYCOMPATIBLENONARRAYOID)
 		{
@@ -1677,6 +1698,42 @@ check_generic_type_consistency(const Oid *actual_arg_types,
 				anycompatible_actual_types[n_anycompatible_args++] = anycompatible_range_typelem;
 			}
 		}
+		else if (decl_type == ANYCOMPATIBLEMULTIRANGEOID)
+		{
+			if (actual_type == UNKNOWNOID)
+				continue;
+			actual_type = getBaseType(actual_type); /* flatten domains */
+			if (OidIsValid(anycompatible_multirange_typeid))
+			{
+				/* All ANYCOMPATIBLEMULTIRANGE arguments must be the same type */
+				if (anycompatible_multirange_typeid != actual_type)
+					return false;
+			}
+			else
+			{
+				anycompatible_multirange_typeid = actual_type;
+				anycompatible_multirange_typelem = get_range_multirange_subtype(actual_type);
+				if (!OidIsValid(anycompatible_multirange_typelem))
+					return false;	/* not a multirange type */
+
+				if (OidIsValid(anycompatible_range_typeid))
+				{
+					/* ANYCOMPATIBLEMULTIRANGE and ANYCOMPATIBLERANGE arguments must match */
+					if (anycompatible_range_typeid != anycompatible_multirange_typelem)
+						return false;
+				}
+				else
+				{
+					anycompatible_range_typeid = anycompatible_multirange_typelem;
+					anycompatible_range_typelem = get_range_subtype(anycompatible_range_typeid);
+					if (!OidIsValid(anycompatible_range_typelem))
+						return false;	/* not a range type */
+				}
+				/* collect the subtype for common-supertype choice */
+				anycompatible_actual_types[n_anycompatible_args++] =
+					anycompatible_range_typelem;
+			}
+		}
 	}
 
 	/* Get the element type based on the array type, if we have one */
@@ -1723,8 +1780,6 @@ check_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))
 	{
-		Oid			range_typelem;
-
 		range_typelem = get_range_subtype(range_typeid);
 		if (!OidIsValid(range_typelem))
 			return false;		/* should be a range, but isn't */
@@ -1743,6 +1798,45 @@ 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))
+	{
+		Oid			multirange_typelem;
+
+		multirange_typelem = get_range_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 */
+		}
+		else if (multirange_typelem != range_typeid)
+		{
+			/* otherwise, they better match */
+			return false;
+		}
+
+		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 */
@@ -1781,8 +1875,10 @@ check_generic_type_consistency(const Oid *actual_arg_types,
 		}
 
 		/*
-		 * the anycompatible type must exactly match the range element type,
-		 * if we were able to identify one
+		 * The anycompatible type must exactly match the range element type,
+		 * if we were able to identify one. This checks compatibility for
+		 * anycompatiblemultirange too since that also sets
+		 * anycompatible_range_typelem above.
 		 */
 		if (OidIsValid(anycompatible_range_typelem) &&
 			anycompatible_range_typelem != anycompatible_typeid)
@@ -1821,21 +1917,27 @@ check_generic_type_consistency(const Oid *actual_arg_types,
  *	  argument's actual type as the function's return type.
  * 2) If return type is ANYARRAY, and any argument is ANYARRAY, use the
  *	  argument's actual type as the function's return type.
- * 3) Similarly, if return type is ANYRANGE, and any argument is ANYRANGE,
- *	  use the argument's actual type as the function's return type.
- * 4) Otherwise, if return type is ANYELEMENT or ANYARRAY, and there is
+ * 3) Similarly, if return type is ANYRANGE or ANYMULTIRANGE, and any
+ *	  argument is ANYRANGE or ANYMULTIRANGE, use that argument's
+ *	  actual type, range type or multirange type as the function's return
+ *	  type.
+ * 4) Otherwise, if return type is ANYMULTIRANGE, and any argument is
+ *	  ANYMULTIRANGE, use the argument's actual type as the function's return
+ *	  type. Or if any argument is ANYRANGE, use its multirange type as the
+ *	  function's return type.
+ * 5) Otherwise, if return type is ANYELEMENT or ANYARRAY, and there is
  *	  at least one ANYELEMENT, ANYARRAY, or ANYRANGE input, deduce the
  *	  return type from those inputs, or throw error if we can't.
- * 5) Otherwise, if return type is ANYRANGE, throw error.  (We have no way to
- *	  select a specific range type if the arguments don't include ANYRANGE.)
- * 6) ANYENUM is treated the same as ANYELEMENT except that if it is used
+ * 6) Otherwise, if return type is ANYRANGE or ANYMULTIRANGE, throw error.
+ *	  (We have no way to select a specific range type if the arguments don't
+ *	  include ANYRANGE.)
  *	  (alone or in combination with plain ANYELEMENT), we add the extra
  *	  condition that the ANYELEMENT type must be an enum.
- * 7) ANYNONARRAY is treated the same as ANYELEMENT except that if it is used,
+ * 8) ANYNONARRAY is treated the same as ANYELEMENT except that if it is used,
  *	  we add the extra condition that the ANYELEMENT type must not be an array.
  *	  (This is a no-op if used in combination with ANYARRAY or ANYENUM, but
  *	  is an extra restriction if not.)
- * 8) ANYCOMPATIBLE, ANYCOMPATIBLEARRAY, ANYCOMPATIBLENONARRAY, and
+ * 9) ANYCOMPATIBLE, ANYCOMPATIBLEARRAY, ANYCOMPATIBLENONARRAY, and
  *	  ANYCOMPATIBLERANGE are handled by resolving the common supertype
  *	  of those arguments (or their element types/subtypes, for array and range
  *	  inputs), and then coercing all those arguments to the common supertype,
@@ -1889,15 +1991,21 @@ 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			anycompatible_typeid = InvalidOid;
 	Oid			anycompatible_array_typeid = InvalidOid;
 	Oid			anycompatible_range_typeid = InvalidOid;
 	Oid			anycompatible_range_typelem = InvalidOid;
+	Oid			anycompatible_multirange_typeid = InvalidOid;
+	Oid			anycompatible_multirange_typelem = InvalidOid;
+	Oid			range_typelem;
+	Oid			multirange_typelem;
 	bool		have_anynonarray = (rettype == ANYNONARRAYOID);
 	bool		have_anyenum = (rettype == ANYENUMOID);
 	bool		have_anycompatible_nonarray = (rettype == ANYCOMPATIBLENONARRAYOID);
 	bool		have_anycompatible_array = (rettype == ANYCOMPATIBLEARRAYOID);
 	bool		have_anycompatible_range = (rettype == ANYCOMPATIBLERANGEOID);
+	bool		have_anycompatible_multirange = (rettype == ANYCOMPATIBLEMULTIRANGEOID);
 	int			n_poly_args = 0;	/* this counts all family-1 arguments */
 	int			n_anycompatible_args = 0;	/* this counts only non-unknowns */
 	Oid			anycompatible_actual_types[FUNC_MAX_ARGS];
@@ -1977,6 +2085,26 @@ enforce_generic_type_consistency(const Oid *actual_arg_types,
 								   format_type_be(actual_type))));
 			range_typeid = actual_type;
 		}
+		else if (decl_type == ANYMULTIRANGEOID)
+		{
+			n_poly_args++;
+			if (actual_type == UNKNOWNOID)
+			{
+				have_poly_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;
+		}
 		else if (decl_type == ANYCOMPATIBLEOID ||
 				 decl_type == ANYCOMPATIBLENONARRAYOID)
 		{
@@ -2045,6 +2173,41 @@ enforce_generic_type_consistency(const Oid *actual_arg_types,
 				anycompatible_actual_types[n_anycompatible_args++] = anycompatible_range_typelem;
 			}
 		}
+		else if (decl_type == ANYCOMPATIBLEMULTIRANGEOID)
+		{
+			have_poly_anycompatible = true;
+			have_anycompatible_multirange = true;
+			if (actual_type == UNKNOWNOID)
+				continue;
+			if (allow_poly && decl_type == actual_type)
+				continue;		/* no new information here */
+			actual_type = getBaseType(actual_type); /* flatten domains */
+			if (OidIsValid(anycompatible_multirange_typeid))
+			{
+				/* All ANYCOMPATIBLEMULTIRANGE arguments must be the same type */
+				if (anycompatible_multirange_typeid != actual_type)
+					ereport(ERROR,
+							(errcode(ERRCODE_DATATYPE_MISMATCH),
+							 errmsg("arguments declared \"anycompatiblemultirange\" are not all alike"),
+							 errdetail("%s versus %s",
+									   format_type_be(anycompatible_multirange_typeid),
+									   format_type_be(actual_type))));
+			}
+			else
+			{
+				anycompatible_multirange_typeid = actual_type;
+				anycompatible_multirange_typelem = get_range_multirange_subtype(actual_type);
+				anycompatible_range_typelem = get_range_subtype(anycompatible_multirange_typelem);
+				if (!OidIsValid(anycompatible_multirange_typelem))
+					ereport(ERROR,
+							(errcode(ERRCODE_DATATYPE_MISMATCH),
+							 errmsg("argument declared %s is not a multirange type but type %s",
+									"anycompatiblemultirange",
+									format_type_be(actual_type))));
+				/* collect the subtype for common-supertype choice */
+				anycompatible_actual_types[n_anycompatible_args++] = anycompatible_range_typelem;
+			}
+		}
 	}
 
 	/*
@@ -2113,8 +2276,6 @@ 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))
 		{
-			Oid			range_typelem;
-
 			range_typelem = get_range_subtype(range_typeid);
 			if (!OidIsValid(range_typelem))
 				ereport(ERROR,
@@ -2143,6 +2304,61 @@ enforce_generic_type_consistency(const Oid *actual_arg_types,
 								   format_type_be(elem_typeid))));
 			}
 		}
+		else
+			range_typelem = InvalidOid;
+
+		/* Get the element type based on the multirange type, if we have one */
+		if (OidIsValid(multirange_typeid))
+		{
+			multirange_typelem = get_range_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))));
+			}
+
+			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 */
+				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))));
+			}
+		}
+		else
+			multirange_typelem = InvalidOid;
 
 		if (!OidIsValid(elem_typeid))
 		{
@@ -2151,6 +2367,7 @@ enforce_generic_type_consistency(const Oid *actual_arg_types,
 				elem_typeid = ANYELEMENTOID;
 				array_typeid = ANYARRAYOID;
 				range_typeid = ANYRANGEOID;
+				multirange_typeid = ANYMULTIRANGEOID;
 			}
 			else
 			{
@@ -2250,6 +2467,7 @@ enforce_generic_type_consistency(const Oid *actual_arg_types,
 				anycompatible_typeid = ANYCOMPATIBLEOID;
 				anycompatible_array_typeid = ANYCOMPATIBLEARRAYOID;
 				anycompatible_range_typeid = ANYCOMPATIBLERANGEOID;
+				anycompatible_multirange_typeid = ANYCOMPATIBLEMULTIRANGEOID;
 			}
 			else
 			{
@@ -2281,6 +2499,8 @@ enforce_generic_type_consistency(const Oid *actual_arg_types,
 				declared_arg_types[j] = anycompatible_array_typeid;
 			else if (decl_type == ANYCOMPATIBLERANGEOID)
 				declared_arg_types[j] = anycompatible_range_typeid;
+			else if (decl_type == ANYCOMPATIBLEMULTIRANGEOID)
+				declared_arg_types[j] = anycompatible_multirange_typeid;
 		}
 	}
 
@@ -2331,6 +2551,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;
+			}
 		}
 	}
 
@@ -2367,6 +2598,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 ANYCOMPATIBLE use the appropriate type */
 	if (rettype == ANYCOMPATIBLEOID ||
 		rettype == ANYCOMPATIBLENONARRAYOID)
@@ -2401,6 +2648,17 @@ enforce_generic_type_consistency(const Oid *actual_arg_types,
 		return anycompatible_range_typeid;
 	}
 
+	/* if we return ANYCOMPATIBLEMULTIRANGE use the appropriate argument type */
+	if (rettype == ANYCOMPATIBLEMULTIRANGEOID)
+	{
+		/* this error is unreachable if the function signature is valid: */
+		if (!OidIsValid(anycompatible_multirange_typeid))
+			ereport(ERROR,
+					(errcode(ERRCODE_DATATYPE_MISMATCH),
+					 errmsg_internal("could not identify anycompatiblemultirange type")));
+		return anycompatible_multirange_typeid;
+	}
+
 	/* we don't return a generic type; send back the original return type */
 	return rettype;
 }
@@ -2418,20 +2676,37 @@ check_valid_polymorphic_signature(Oid ret_type,
 								  const Oid *declared_arg_types,
 								  int nargs)
 {
-	if (ret_type == ANYRANGEOID || ret_type == ANYCOMPATIBLERANGEOID)
+	if (ret_type == ANYRANGEOID || ret_type == ANYMULTIRANGEOID)
 	{
 		/*
-		 * ANYRANGE requires an ANYRANGE input, else we can't tell which of
-		 * several range types with the same element type to use.  Likewise
-		 * for ANYCOMPATIBLERANGE.
+		 * ANYRANGE and ANYMULTIRANGE require an ANYRANGE or ANYMULTIRANGE input,
+		 * else we can't tell which of several range types with the same element
+		 * type to use.
 		 */
 		for (int i = 0; i < nargs; i++)
 		{
-			if (declared_arg_types[i] == ret_type)
+			if (declared_arg_types[i] == ANYRANGEOID ||
+				declared_arg_types[i] == ANYMULTIRANGEOID)
 				return NULL;	/* OK */
 		}
-		return psprintf(_("A result of type %s requires at least one input of type %s."),
-						format_type_be(ret_type), format_type_be(ret_type));
+		return psprintf(_("A result of type %s requires at least one input of type anyrange or anymultirange."),
+						format_type_be(ret_type));
+	}
+	else if (ret_type == ANYCOMPATIBLERANGEOID || ret_type == ANYCOMPATIBLEMULTIRANGEOID)
+	{
+		/*
+		 * ANYCOMPATIBLERANGE and ANYCOMPATIBLEMULTIRANGE require an ANYCOMPATIBLERANGE or
+		 * ANYCOMPATIBLEMULTIRANGE input, else we can't tell which of several range types
+		 * with the same element type to use.
+		 */
+		for (int i = 0; i < nargs; i++)
+		{
+			if (declared_arg_types[i] == ANYCOMPATIBLERANGEOID ||
+				declared_arg_types[i] == ANYCOMPATIBLEMULTIRANGEOID)
+				return NULL;	/* OK */
+		}
+		return psprintf(_("A result of type %s requires at least one input of type anycompatiblerange or anycompatiblemultirange."),
+						format_type_be(ret_type));
 	}
 	else if (IsPolymorphicTypeFamily1(ret_type))
 	{
@@ -2442,7 +2717,7 @@ check_valid_polymorphic_signature(Oid ret_type,
 				return NULL;	/* OK */
 		}
 		/* Keep this list in sync with IsPolymorphicTypeFamily1! */
-		return psprintf(_("A result of type %s requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange."),
+		return psprintf(_("A result of type %s requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange."),
 						format_type_be(ret_type));
 	}
 	else if (IsPolymorphicTypeFamily2(ret_type))
@@ -2594,6 +2869,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 || targettype == ANYCOMPATIBLEMULTIRANGEOID)
+		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 5d2aca8cfe..1a08218de9 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..bd3cf3dc93
--- /dev/null
+++ b/src/backend/utils/adt/multirangetypes.c
@@ -0,0 +1,2186 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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/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 */
+	FmgrInfo	typioproc;		/* range type's I/O proc */
+	Oid			typioparam;		/* range type's I/O parameter */
+} 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 mltrngtypid,
+												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;
+	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 = NULL;
+	int32		range_str_len;
+	char	   *range_str_copy;
+
+	cache = get_multirange_io_data(fcinfo, mltrngtypoid, IOFunc_input);
+	rangetyp = cache->typcache->rngtype;
+
+	/* 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->typioproc,
+																 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->typioproc, 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->typioproc,
+														   &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;
+
+		range = RangeTypePGetDatum(ranges[i]);
+		range = PointerGetDatum(SendFunctionCall(&cache->typioproc, range));
+
+		pq_sendint32(buf, VARSIZE(range) - VARHDRSZ);
+		pq_sendbytes(buf, VARDATA(range), VARSIZE(range) - VARHDRSZ);
+	}
+
+	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)
+	{
+		Oid			typiofunc;
+		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,
+						 &typiofunc);
+
+		if (!OidIsValid(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(typiofunc, &cache->typioproc,
+					  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;
+	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++)
+		bytelen += MAXALIGN(VARSIZE(ranges[i]));
+
+	/* 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++)
+	{
+		memcpy(ptr, ranges[i], VARSIZE(ranges[i]));
+		ptr += MAXALIGN(VARSIZE(ranges[i]));
+	}
+
+	return multirange;
+}
+
+/*
+ * Converts a list of arbitrary ranges 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_constructor2(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,
+				(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+				 errmsg("multirange values cannot contain NULL members")));
+
+	rangeArray = PG_GETARG_ARRAYTYPE_P(0);
+
+	dims = ARR_NDIM(rangeArray);
+	if (dims > 1)
+		ereport(ERROR,
+				(errcode(ERRCODE_CARDINALITY_VIOLATION),
+				 errmsg("multiranges cannot be constructed from multi-dimensional arrays")));
+
+	rngtypid = ARR_ELEMTYPE(rangeArray);
+	if (rngtypid != rangetyp->type_id)
+		ereport(ERROR,
+				(errcode(ERRCODE_DATATYPE_MISMATCH),
+				 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,
+						(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+						 errmsg("multirange values cannot contain NULL members")));
+
+			/* make_multirange will do its own copy */
+			ranges[i] = DatumGetRangeTypeP(elements[i]);
+		}
+	}
+
+	PG_RETURN_MULTIRANGE_P(make_multirange(mltrngtypid, rangetyp, range_count, ranges));
+}
+
+/*
+ * Construct multirange value from a single range.
+ * It'd be nice if we could just use multirange_constructor2
+ * for this case, but we need a non-variadic single-arg function
+ * to let us define a CAST from a range to its multirange.
+ */
+Datum
+multirange_constructor1(PG_FUNCTION_ARGS)
+{
+	Oid			mltrngtypid = get_fn_expr_rettype(fcinfo->flinfo);
+	Oid			rngtypid;
+	TypeCacheEntry *typcache;
+	TypeCacheEntry *rangetyp;
+	RangeType  *range;
+
+	typcache = multirange_get_typcache(fcinfo, mltrngtypid);
+	rangetyp = typcache->rngtype;
+
+	/*
+	 * These checks should be guaranteed by our signature, but let's do them
+	 * just in case.
+	 */
+
+	if (PG_ARGISNULL(0))
+				ereport(ERROR,
+						(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+						 errmsg("multirange values cannot contain NULL members")));
+
+	range = PG_GETARG_RANGE_P(0);
+
+	/* Make sure the range type matches. */
+	rngtypid = RangeTypeGetOid(range);
+	if (rngtypid != rangetyp->type_id)
+		ereport(ERROR,
+				(errcode(ERRCODE_DATATYPE_MISMATCH),
+				 errmsg("type %u does not match constructor type", rngtypid)));
+
+	PG_RETURN_MULTIRANGE_P(make_multirange(mltrngtypid, rangetyp, 1, &range));
+}
+
+/*
+ * 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
+		elog(ERROR,		/* can't happen */
+			 "niladic multirange constructor must not receive arguments");
+}
+
+
+/* multirange, multirange -> multirange type functions */
+
+/* multirange union */
+Datum
+multirange_union(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
+	MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count1;
+	int32		range_count2;
+	int32		range_count3;
+	RangeType **ranges1;
+	RangeType **ranges2;
+	RangeType **ranges3;
+
+	if (MultirangeIsEmpty(mr1))
+		PG_RETURN_MULTIRANGE_P(mr2);
+	if (MultirangeIsEmpty(mr2))
+		PG_RETURN_MULTIRANGE_P(mr1);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr1));
+
+	multirange_deserialize(mr1, &range_count1, &ranges1);
+	multirange_deserialize(mr2, &range_count2, &ranges2);
+
+	range_count3 = range_count1 + range_count2;
+	ranges3 = palloc0(range_count3 * sizeof(RangeType *));
+	memcpy(ranges3, ranges1, range_count1 * sizeof(RangeType *));
+	memcpy(ranges3 + range_count1, ranges2, range_count2 * sizeof(RangeType *));
+	PG_RETURN_MULTIRANGE_P(make_multirange(typcache->type_id, typcache->rngtype,
+										   range_count3, ranges3));
+}
+
+/* multirange minus */
+Datum
+multirange_minus(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
+	MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
+	Oid			mltrngtypoid = MultirangeTypeGetOid(mr1);
+	TypeCacheEntry *typcache;
+	TypeCacheEntry *rangetyp;
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+
+	typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
+	rangetyp = typcache->rngtype;
+
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		PG_RETURN_MULTIRANGE_P(mr1);
+
+	multirange_deserialize(mr1, &range_count1, &ranges1);
+	multirange_deserialize(mr2, &range_count2, &ranges2);
+
+	PG_RETURN_MULTIRANGE_P(multirange_minus_internal(mltrngtypoid,
+													 rangetyp,
+													 range_count1,
+													 ranges1,
+													 range_count2,
+													 ranges2));
+}
+
+MultirangeType *
+multirange_minus_internal(Oid mltrngtypoid, TypeCacheEntry *rangetyp,
+						  int32 range_count1, RangeType **ranges1,
+						  int32 range_count2, RangeType **ranges2)
+{
+	RangeType  *r1;
+	RangeType  *r2;
+	RangeType **ranges3;
+	int32		range_count3;
+	int32		i1;
+	int32		i2;
+
+	/*
+	 * Worst case: every range in ranges1 makes a different cut to some range
+	 * in ranges2.
+	 */
+	ranges3 = palloc0((range_count1 + range_count2) * sizeof(RangeType *));
+	range_count3 = 0;
+
+	/*
+	 * For each range in mr1, keep subtracting until it's gone or the ranges
+	 * in mr2 have passed it. After a subtraction we assign what's left back
+	 * to r1. The parallel progress through mr1 and mr2 is similar to
+	 * multirange_overlaps_multirange_internal.
+	 */
+	r2 = ranges2[0];
+	for (i1 = 0, i2 = 0; i1 < range_count1; i1++)
+	{
+		r1 = ranges1[i1];
+
+		/* Discard r2s while r2 << r1 */
+		while (r2 != NULL && range_before_internal(rangetyp, r2, r1))
+		{
+			r2 = ++i2 >= range_count2 ? NULL : ranges2[i2];
+		}
+
+		while (r2 != NULL)
+		{
+			if (range_split_internal(rangetyp, r1, r2, &ranges3[range_count3], &r1))
+			{
+				/*
+				 * If r2 takes a bite out of the middle of r1, we need two
+				 * outputs
+				 */
+				range_count3++;
+				r2 = ++i2 >= range_count2 ? NULL : ranges2[i2];
+
+			}
+			else if (range_overlaps_internal(rangetyp, r1, r2))
+			{
+				/*
+				 * If r2 overlaps r1, replace r1 with r1 - r2.
+				 */
+				r1 = range_minus_internal(rangetyp, r1, r2);
+
+				/*
+				 * If r2 goes past r1, then we need to stay with it, in case
+				 * it hits future r1s. Otherwise we need to keep r1, in case
+				 * future r2s hit it. Since we already subtracted, there's no
+				 * point in using the overright/overleft calls.
+				 */
+				if (RangeIsEmpty(r1) || range_before_internal(rangetyp, r1, r2))
+					break;
+				else
+					r2 = ++i2 >= range_count2 ? NULL : ranges2[i2];
+
+			}
+			else
+			{
+				/*
+				 * This and all future r2s are past r1, so keep them. Also
+				 * assign whatever is left of r1 to the result.
+				 */
+				break;
+			}
+		}
+
+		/*
+		 * Nothing else can remove anything from r1, so keep it. Even if r1 is
+		 * empty here, make_multirange will remove it.
+		 */
+		ranges3[range_count3++] = r1;
+	}
+
+	return make_multirange(mltrngtypoid, rangetyp, range_count3, ranges3);
+}
+
+/* multirange intersection */
+Datum
+multirange_intersect(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
+	MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
+	Oid			mltrngtypoid = MultirangeTypeGetOid(mr1);
+	TypeCacheEntry *typcache;
+	TypeCacheEntry *rangetyp;
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+
+	typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
+	rangetyp = typcache->rngtype;
+
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		PG_RETURN_MULTIRANGE_P(make_empty_multirange(mltrngtypoid, rangetyp));
+
+	multirange_deserialize(mr1, &range_count1, &ranges1);
+	multirange_deserialize(mr2, &range_count2, &ranges2);
+
+	PG_RETURN_MULTIRANGE_P(multirange_intersect_internal(mltrngtypoid,
+														 rangetyp,
+														 range_count1,
+														 ranges1,
+														 range_count2,
+														 ranges2));
+}
+
+MultirangeType *
+multirange_intersect_internal(Oid mltrngtypoid, TypeCacheEntry *rangetyp,
+							  int32 range_count1, RangeType **ranges1,
+							  int32 range_count2, RangeType **ranges2)
+{
+	RangeType  *r1;
+	RangeType  *r2;
+	RangeType **ranges3;
+	int32		range_count3;
+	int32		i1;
+	int32		i2;
+
+	if (range_count1 == 0 || range_count2 == 0)
+		return make_multirange(mltrngtypoid, rangetyp, 0, NULL);
+
+	/*-----------------------------------------------
+	 * Worst case is a stitching pattern like this:
+	 *
+	 * mr1: --- --- --- ---
+	 * mr2:   --- --- ---
+	 * mr3:   - - - - - -
+	 *
+	 * That seems to be range_count1 + range_count2 - 1,
+	 * but one extra won't hurt.
+	 *-----------------------------------------------
+	 */
+	ranges3 = palloc0((range_count1 + range_count2) * sizeof(RangeType *));
+	range_count3 = 0;
+
+	/*
+	 * For each range in mr1, keep intersecting until the ranges in mr2 have
+	 * passed it. The parallel progress through mr1 and mr2 is similar to
+	 * multirange_minus_multirange_internal, but we don't have to assign back
+	 * to r1.
+	 */
+	r2 = ranges2[0];
+	for (i1 = 0, i2 = 0; i1 < range_count1; i1++)
+	{
+		r1 = ranges1[i1];
+
+		/* Discard r2s while r2 << r1 */
+		while (r2 != NULL && range_before_internal(rangetyp, r2, r1))
+		{
+			r2 = ++i2 >= range_count2 ? NULL : ranges2[i2];
+		}
+
+		while (r2 != NULL)
+		{
+			if (range_overlaps_internal(rangetyp, r1, r2))
+			{
+				/* Keep the overlapping part */
+				ranges3[range_count3++] = range_intersect_internal(rangetyp, r1, r2);
+
+				/* If we "used up" all of r2, go to the next one... */
+				if (range_overleft_internal(rangetyp, r2, r1))
+					r2 = ++i2 >= range_count2 ? NULL : ranges2[i2];
+
+				/* ...otherwise go to the next r1 */
+				else
+					break;
+			}
+			else
+				/* We're past r1, so move to the next one */
+				break;
+		}
+
+		/* If we're out of r2s, there can be no more intersections */
+		if (r2 == NULL)
+			break;
+	}
+
+	return make_multirange(mltrngtypoid, rangetyp, range_count3, ranges3);
+}
+
+/*
+ * range_agg_transfn: combine adjacent/overlapping ranges.
+ *
+ * All we do here is gather the input ranges into an array
+ * so that the finalfn can sort and combine them.
+ */
+Datum
+range_agg_transfn(PG_FUNCTION_ARGS)
+{
+	MemoryContext aggContext;
+	Oid			rngtypoid;
+	ArrayBuildState *state;
+
+	if (!AggCheckCallContext(fcinfo, &aggContext))
+		elog(ERROR, "range_agg_transfn called in non-aggregate context");
+
+	rngtypoid = get_fn_expr_argtype(fcinfo->flinfo, 1);
+	if (!type_is_range(rngtypoid))
+		ereport(ERROR,
+				(errcode(ERRCODE_DATATYPE_MISMATCH),
+				 errmsg("range_agg must be called with a range")));
+
+	if (PG_ARGISNULL(0))
+		state = initArrayResult(rngtypoid, aggContext, false);
+	else
+		state = (ArrayBuildState *) PG_GETARG_POINTER(0);
+
+	/* skip NULLs */
+	if (!PG_ARGISNULL(1))
+		accumArrayResult(state, PG_GETARG_DATUM(1), false, rngtypoid, aggContext);
+
+	PG_RETURN_POINTER(state);
+}
+
+/*
+ * range_agg_finalfn: use our internal array to merge touching ranges.
+ */
+Datum
+range_agg_finalfn(PG_FUNCTION_ARGS)
+{
+	MemoryContext aggContext;
+	Oid			mltrngtypoid;
+	TypeCacheEntry *typcache;
+	ArrayBuildState *state;
+	int32		range_count;
+	RangeType **ranges;
+	int			i;
+
+	if (!AggCheckCallContext(fcinfo, &aggContext))
+		elog(ERROR, "range_agg_finalfn called in non-aggregate context");
+
+	state = PG_ARGISNULL(0) ? NULL : (ArrayBuildState *) PG_GETARG_POINTER(0);
+	if (state == NULL)
+		/* This shouldn't be possible, but just in case.... */
+		PG_RETURN_NULL();
+
+	/* Also return NULL if we had zero inputs, like other aggregates */
+	range_count = state->nelems;
+	if (range_count == 0)
+		PG_RETURN_NULL();
+
+	mltrngtypoid = get_fn_expr_rettype(fcinfo->flinfo);
+	typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
+
+	ranges = palloc0(range_count * sizeof(RangeType *));
+	for (i = 0; i < range_count; i++)
+		ranges[i] = DatumGetRangeTypeP(state->dvalues[i]);
+
+	PG_RETURN_MULTIRANGE_P(make_multirange(mltrngtypoid, typcache->rngtype, range_count, ranges));
+}
+
+Datum
+multirange_intersect_agg_transfn(PG_FUNCTION_ARGS)
+{
+	MemoryContext aggContext;
+	Oid			mltrngtypoid;
+	TypeCacheEntry *typcache;
+	MultirangeType *result;
+	MultirangeType *current;
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+
+	if (!AggCheckCallContext(fcinfo, &aggContext))
+		elog(ERROR, "multirange_intersect_agg_transfn called in non-aggregate context");
+
+	mltrngtypoid = get_fn_expr_argtype(fcinfo->flinfo, 1);
+	if (!type_is_multirange(mltrngtypoid))
+		ereport(ERROR,
+				(errcode(ERRCODE_DATATYPE_MISMATCH),
+				 errmsg("range_intersect_agg must be called with a multirange")));
+
+	typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
+
+	/* strictness ensures these are non-null */
+	result = PG_GETARG_MULTIRANGE_P(0);
+	current = PG_GETARG_MULTIRANGE_P(1);
+
+	multirange_deserialize(result, &range_count1, &ranges1);
+	multirange_deserialize(current, &range_count2, &ranges2);
+
+	result = multirange_intersect_internal(mltrngtypoid,
+										   typcache->rngtype,
+										   range_count1,
+										   ranges1,
+										   range_count2,
+										   ranges2);
+	PG_RETURN_RANGE_P(result);
+}
+
+
+/* multirange -> element type functions */
+
+/* extract lower bound value */
+Datum
+multirange_lower(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+	bool		isnull;
+	Datum		result;
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_NULL();
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	result = range_lower_internal(typcache->rngtype, ranges[0], &isnull);
+
+	if (isnull)
+		PG_RETURN_NULL();
+	else
+		PG_RETURN_DATUM(result);
+}
+
+/* extract upper bound value */
+Datum
+multirange_upper(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+	bool		isnull;
+	Datum		result;
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_NULL();
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	result = range_upper_internal(typcache->rngtype, ranges[range_count - 1], &isnull);
+
+	if (isnull)
+		PG_RETURN_NULL();
+	else
+		PG_RETURN_DATUM(result);
+}
+
+
+/* multirange -> bool functions */
+
+/* is multirange empty? */
+Datum
+multirange_empty(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+
+	PG_RETURN_BOOL(mr->rangeCount == 0);
+}
+
+/* is lower bound inclusive? */
+Datum
+multirange_lower_inc(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	int32		range_count;
+	RangeType **ranges;
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_has_flag(ranges[0], RANGE_LB_INC));
+}
+
+/* is upper bound inclusive? */
+Datum
+multirange_upper_inc(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	int32		range_count;
+	RangeType **ranges;
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_has_flag(ranges[range_count - 1], RANGE_UB_INC));
+}
+
+/* is lower bound infinite? */
+Datum
+multirange_lower_inf(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	int32		range_count;
+	RangeType **ranges;
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_has_flag(ranges[0], RANGE_LB_INF));
+}
+
+/* is upper bound infinite? */
+Datum
+multirange_upper_inf(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	int32		range_count;
+	RangeType **ranges;
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_has_flag(ranges[range_count - 1], RANGE_UB_INF));
+}
+
+
+
+/* multirange, element -> bool functions */
+
+/* contains? */
+Datum
+multirange_contains_elem(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	Datum		val = PG_GETARG_DATUM(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(multirange_contains_elem_internal(typcache, mr, val));
+}
+
+/* contained by? */
+Datum
+elem_contained_by_multirange(PG_FUNCTION_ARGS)
+{
+	Datum		val = PG_GETARG_DATUM(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(multirange_contains_elem_internal(typcache, mr, val));
+}
+
+/*
+ * Test whether multirange mr contains a specific element value.
+ */
+bool
+multirange_contains_elem_internal(TypeCacheEntry *typcache, MultirangeType *mr, Datum val)
+{
+	TypeCacheEntry *rangetyp;
+	int32		range_count;
+	RangeType **ranges;
+	RangeType  *r;
+	int			i;
+
+	rangetyp = typcache->rngtype;
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	for (i = 0; i < range_count; i++)
+	{
+		r = ranges[i];
+		if (range_contains_elem_internal(rangetyp, r, val))
+			return true;
+	}
+
+	return false;
+}
+
+/* multirange, range -> bool functions */
+
+/* contains? */
+Datum
+multirange_contains_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(multirange_contains_range_internal(typcache, mr, r));
+}
+
+/* contained by? */
+Datum
+range_contained_by_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(multirange_contains_range_internal(typcache, mr, r));
+}
+
+/*
+ * Test whether multirange mr contains a specific range r.
+ */
+bool
+multirange_contains_range_internal(TypeCacheEntry *typcache, MultirangeType *mr, RangeType *r)
+{
+	TypeCacheEntry *rangetyp;
+	int32		range_count;
+	RangeType **ranges;
+	RangeType  *mrr;
+	int			i;
+
+	rangetyp = typcache->rngtype;
+
+	/*
+	 * Every multirange contains an infinite number of empty ranges, even an
+	 * empty one.
+	 */
+	if (RangeIsEmpty(r))
+		return true;
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	for (i = 0; i < range_count; i++)
+	{
+		mrr = ranges[i];
+		if (range_contains_internal(rangetyp, mrr, r))
+			return true;
+	}
+
+	return false;
+}
+
+
+/* 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));
+}
+
+/* overlaps? */
+Datum
+range_overlaps_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(range_overlaps_multirange_internal(typcache, r, mr));
+}
+
+Datum
+multirange_overlaps_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(range_overlaps_multirange_internal(typcache, r, mr));
+}
+
+Datum
+multirange_overlaps_multirange(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_overlaps_multirange_internal(typcache, mr1, mr2));
+}
+
+bool
+range_overlaps_multirange_internal(TypeCacheEntry *typcache, RangeType *r, MultirangeType *mr)
+{
+	TypeCacheEntry *rangetyp;
+	int32		range_count;
+	int32		i;
+	RangeType **ranges;
+	RangeType  *mrr;
+
+	/*
+	 * Empties never overlap, even with empties. (This seems strange since
+	 * they *do* contain each other, but we want to follow how ranges work.)
+	 */
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		return false;
+
+	rangetyp = typcache->rngtype;
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	/* Scan through mrr and once it gets to r they either overlap or not */
+	mrr = ranges[0];
+
+	/* Discard mrrs while mrr << r */
+	i = 0;
+	while (range_before_internal(rangetyp, mrr, r))
+	{
+		if (++i >= range_count)
+			return false;
+		mrr = ranges[i];
+	}
+
+	/* Now either we overlap or we passed r */
+	return range_overlaps_internal(rangetyp, mrr, r);
+}
+
+bool
+multirange_overlaps_multirange_internal(TypeCacheEntry *typcache, MultirangeType *mr1,
+										MultirangeType *mr2)
+{
+	TypeCacheEntry *rangetyp;
+	int32		range_count1;
+	int32		range_count2;
+	int32		i1;
+	int32		i2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+	RangeType  *r1;
+	RangeType  *r2;
+
+	/*
+	 * Empties never overlap, even with empties. (This seems strange since
+	 * they *do* contain each other, but we want to follow how ranges work.)
+	 */
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		return false;
+
+	rangetyp = typcache->rngtype;
+
+	multirange_deserialize(mr1, &range_count1, &ranges1);
+	multirange_deserialize(mr2, &range_count2, &ranges2);
+
+	/*
+	 * Every range in mr1 gets a chance to overlap with the ranges in mr2, but
+	 * we can use their ordering to avoid O(n^2). This is similar to
+	 * range_overlaps_multirange where r1 : r2 :: mrr : r, but there if we
+	 * don't find an overlap with r we're done, and here if we don't find an
+	 * overlap with r2 we try the next r2.
+	 */
+	r1 = ranges1[0];
+	for (i1 = 0, i2 = 0; i2 < range_count2; i2++)
+	{
+		r2 = ranges2[i2];
+
+		/* Discard r1s while r1 << r2 */
+		while (range_before_internal(rangetyp, r1, r2))
+		{
+			if (++i1 >= range_count1)
+				return false;
+			r1 = ranges1[i1];
+		}
+
+		/*
+		 * If r1 && r2, we're done, otherwise we failed to find an overlap for
+		 * r2, so go to the next one.
+		 */
+		if (range_overlaps_internal(rangetyp, r1, r2))
+			return true;
+	}
+
+	/* We looked through all of mr2 without finding an overlap */
+	return false;
+}
+
+/* does not extend to right of? */
+Datum
+range_overleft_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_overleft_internal(typcache->rngtype, r, ranges[range_count - 1]));
+}
+
+Datum
+multirange_overleft_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_overleft_internal(typcache->rngtype,
+										   ranges[range_count - 1], r));
+}
+
+Datum
+multirange_overleft_multirange(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
+	MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr1));
+
+	multirange_deserialize(mr1, &range_count1, &ranges1);
+	multirange_deserialize(mr2, &range_count2, &ranges2);
+
+	PG_RETURN_BOOL(range_overleft_internal(typcache->rngtype,
+										   ranges1[range_count1 - 1],
+										   ranges2[range_count2 - 1]));
+}
+
+/* does not extend to left of? */
+Datum
+range_overright_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_overright_internal(typcache->rngtype, r, ranges[0]));
+}
+
+Datum
+multirange_overright_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_overright_internal(typcache->rngtype, ranges[0], r));
+}
+
+Datum
+multirange_overright_multirange(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
+	MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr1));
+
+	multirange_deserialize(mr1, &range_count1, &ranges1);
+	multirange_deserialize(mr2, &range_count2, &ranges2);
+
+	PG_RETURN_BOOL(range_overright_internal(typcache->rngtype,
+											ranges1[0],
+											ranges2[0]));
+}
+
+/* contains? */
+Datum
+multirange_contains_multirange(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_contains_multirange_internal(typcache, mr1, mr2));
+}
+
+/* contained by? */
+Datum
+multirange_contained_by_multirange(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_contains_multirange_internal(typcache, mr2, mr1));
+}
+
+/*
+ * Test whether multirange mr1 contains every range from another multirange mr2.
+ */
+bool
+multirange_contains_multirange_internal(TypeCacheEntry *typcache,
+										MultirangeType *mr1, MultirangeType *mr2)
+{
+	TypeCacheEntry *rangetyp;
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+	RangeType  *r1;
+	RangeType  *r2;
+	int			i1,
+				i2;
+
+	rangetyp = typcache->rngtype;
+
+	multirange_deserialize(mr1, &range_count1, &ranges1);
+	multirange_deserialize(mr2, &range_count2, &ranges2);
+
+	/*
+	 * We follow the same logic for empties as ranges: - an empty multirange
+	 * contains an empty range/multirange. - an empty multirange can't contain
+	 * any other range/multirange. - an empty multirange is contained by any
+	 * other range/multirange.
+	 */
+
+	if (range_count2 == 0)
+		return true;
+	if (range_count1 == 0)
+		return false;
+
+	/*
+	 * Every range in mr2 must be contained by some range in mr1. To avoid
+	 * O(n^2) we walk through both ranges in tandem.
+	 */
+	r1 = ranges1[0];
+	for (i1 = 0, i2 = 0; i2 < range_count2; i2++)
+	{
+		r2 = ranges2[i2];
+
+		/* Discard r1s while r1 << r2 */
+		while (range_before_internal(rangetyp, r1, r2))
+		{
+			if (++i1 >= range_count1)
+				return false;
+			r1 = ranges1[i1];
+		}
+
+		/*
+		 * If r1 @> r2, go to the next r2, otherwise return false (since every
+		 * r1[n] and r1[n+1] must have a gap). Note this will give weird
+		 * answers if you don't canonicalize, e.g. with a custom
+		 * int2multirange {[1,1], [2,2]} there is a "gap". But that is
+		 * consistent with other range operators, e.g. '[1,1]'::int2range -|-
+		 * '[2,2]'::int2range is false.
+		 */
+		if (!range_contains_internal(rangetyp, r1, r2))
+			return false;
+	}
+
+	/* All ranges in mr2 are satisfied */
+	return true;
+}
+
+/* strictly left of? */
+Datum
+range_before_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(range_before_multirange_internal(typcache, r, mr));
+}
+
+Datum
+multirange_before_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(range_after_multirange_internal(typcache, r, mr));
+}
+
+Datum
+multirange_before_multirange(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_before_multirange_internal(typcache, mr1, mr2));
+}
+
+/* strictly right of? */
+Datum
+range_after_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(range_after_multirange_internal(typcache, r, mr));
+}
+
+Datum
+multirange_after_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(range_before_multirange_internal(typcache, r, mr));
+}
+
+Datum
+multirange_after_multirange(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_before_multirange_internal(typcache, mr2, mr1));
+}
+
+/* strictly left of? (internal version) */
+bool
+range_before_multirange_internal(TypeCacheEntry *typcache, RangeType *r,
+								 MultirangeType *mr)
+{
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		return false;
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	return range_before_internal(typcache->rngtype, r, ranges[0]);
+}
+
+bool
+multirange_before_multirange_internal(TypeCacheEntry *typcache, MultirangeType *mr1,
+									  MultirangeType *mr2)
+{
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		return false;
+
+	multirange_deserialize(mr1, &range_count1, &ranges1);
+	multirange_deserialize(mr2, &range_count2, &ranges2);
+
+	return range_before_internal(typcache->rngtype, ranges1[range_count1 - 1],
+								 ranges2[0]);
+}
+
+/* strictly right of? (internal version) */
+bool
+range_after_multirange_internal(TypeCacheEntry *typcache, RangeType *r,
+								MultirangeType *mr)
+{
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		return false;
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	return range_after_internal(typcache->rngtype, r, ranges[range_count - 1]);
+}
+
+/* adjacent to? */
+Datum
+range_adjacent_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		return false;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_adjacent_internal(typcache->rngtype, r, ranges[0]));
+}
+
+Datum
+multirange_adjacent_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		return false;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_adjacent_internal(typcache->rngtype, ranges[range_count - 1], r));
+}
+
+Datum
+multirange_adjacent_multirange(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
+	MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		return false;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr1));
+
+	multirange_deserialize(mr1, &range_count1, &ranges1);
+	multirange_deserialize(mr2, &range_count2, &ranges2);
+
+	PG_RETURN_BOOL(range_adjacent_internal(typcache->rngtype, ranges1[range_count1 - 1], ranges2[0]));
+}
+
+/* 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);
+}
+
+/* multirange -> range functions */
+
+/* Find the smallest range that includes everything in the multirange */
+Datum
+range_merge_from_multirange(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	Oid			mltrngtypoid = MultirangeTypeGetOid(mr);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_RANGE_P(make_empty_range(typcache->rngtype));
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_RANGE_P(range_union_internal(typcache->rngtype, ranges[0],
+										   ranges[range_count - 1], false));
+}
+
+/* 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/pg_upgrade_support.c b/src/backend/utils/adt/pg_upgrade_support.c
index 18f2ee8226..248ea7f44a 100644
--- a/src/backend/utils/adt/pg_upgrade_support.c
+++ b/src/backend/utils/adt/pg_upgrade_support.c
@@ -52,6 +52,28 @@ binary_upgrade_set_next_array_pg_type_oid(PG_FUNCTION_ARGS)
 }
 
 Datum
+binary_upgrade_set_next_multirange_pg_type_oid(PG_FUNCTION_ARGS)
+{
+	Oid			typoid = PG_GETARG_OID(0);
+
+	CHECK_IS_BINARY_UPGRADE;
+	binary_upgrade_next_mrng_pg_type_oid = typoid;
+
+	PG_RETURN_VOID();
+}
+
+Datum
+binary_upgrade_set_next_multirange_array_pg_type_oid(PG_FUNCTION_ARGS)
+{
+	Oid			typoid = PG_GETARG_OID(0);
+
+	CHECK_IS_BINARY_UPGRADE;
+	binary_upgrade_next_mrng_array_pg_type_oid = typoid;
+
+	PG_RETURN_VOID();
+}
+
+Datum
 binary_upgrade_set_next_toast_pg_type_oid(PG_FUNCTION_ARGS)
 {
 	Oid			typoid = PG_GETARG_OID(0);
diff --git a/src/backend/utils/adt/pseudotypes.c b/src/backend/utils/adt/pseudotypes.c
index 3d6b2f9093..99a93271fe 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"
 
 
 /*
@@ -228,6 +229,43 @@ anycompatiblerange_out(PG_FUNCTION_ARGS)
 }
 
 /*
+ * anycompatiblemultirange
+ *
+ * We may as well allow output, since multirange_out will in fact work.
+ */
+PSEUDOTYPE_DUMMY_INPUT_FUNC(anycompatiblemultirange);
+
+Datum
+anycompatiblemultirange_out(PG_FUNCTION_ARGS)
+{
+	return multirange_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
  *
  * We support void_in so that PL functions can return VOID without any
diff --git a/src/backend/utils/adt/rangetypes.c b/src/backend/utils/adt/rangetypes.c
index 01ad8bc240..0af3c67296 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
 {
@@ -431,19 +429,37 @@ range_lower(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
 	TypeCacheEntry *typcache;
+	bool		isnull;
+	Datum		result;
+
+	typcache = range_get_typcache(fcinfo, RangeTypeGetOid(r1));
+
+	result = range_lower_internal(typcache, r1, &isnull);
+
+	if (isnull)
+		PG_RETURN_NULL();
+	else
+		PG_RETURN_DATUM(result);
+}
+
+Datum
+range_lower_internal(TypeCacheEntry *typcache, const RangeType *r1, bool *isnull)
+{
 	RangeBound	lower;
 	RangeBound	upper;
 	bool		empty;
 
-	typcache = range_get_typcache(fcinfo, RangeTypeGetOid(r1));
-
 	range_deserialize(typcache, r1, &lower, &upper, &empty);
 
 	/* Return NULL if there's no finite lower bound */
 	if (empty || lower.infinite)
-		PG_RETURN_NULL();
+	{
+		*isnull = true;
+		return 0;
+	}
 
-	PG_RETURN_DATUM(lower.val);
+	*isnull = false;
+	return lower.val;
 }
 
 /* extract upper bound value */
@@ -452,19 +468,37 @@ range_upper(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
 	TypeCacheEntry *typcache;
+	bool		isnull;
+	Datum		result;
+
+	typcache = range_get_typcache(fcinfo, RangeTypeGetOid(r1));
+
+	result = range_upper_internal(typcache, r1, &isnull);
+
+	if (isnull)
+		PG_RETURN_NULL();
+	else
+		PG_RETURN_DATUM(result);
+}
+
+Datum
+range_upper_internal(TypeCacheEntry *typcache, const RangeType *r1, bool *isnull)
+{
 	RangeBound	lower;
 	RangeBound	upper;
 	bool		empty;
 
-	typcache = range_get_typcache(fcinfo, RangeTypeGetOid(r1));
-
 	range_deserialize(typcache, r1, &lower, &upper, &empty);
 
 	/* Return NULL if there's no finite upper bound */
 	if (empty || upper.infinite)
-		PG_RETURN_NULL();
+	{
+		*isnull = true;
+		return 0;
+	}
 
-	PG_RETURN_DATUM(upper.val);
+	*isnull = false;
+	return upper.val;
 }
 
 
@@ -475,9 +509,8 @@ Datum
 range_empty(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
-	char		flags = range_get_flags(r1);
 
-	PG_RETURN_BOOL(flags & RANGE_EMPTY);
+	PG_RETURN_BOOL(range_has_flag(r1, RANGE_EMPTY));
 }
 
 /* is lower bound inclusive? */
@@ -485,9 +518,8 @@ Datum
 range_lower_inc(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
-	char		flags = range_get_flags(r1);
 
-	PG_RETURN_BOOL(flags & RANGE_LB_INC);
+	PG_RETURN_BOOL(range_has_flag(r1, RANGE_LB_INC));
 }
 
 /* is upper bound inclusive? */
@@ -495,9 +527,8 @@ Datum
 range_upper_inc(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
-	char		flags = range_get_flags(r1);
 
-	PG_RETURN_BOOL(flags & RANGE_UB_INC);
+	PG_RETURN_BOOL(range_has_flag(r1, RANGE_UB_INC));
 }
 
 /* is lower bound infinite? */
@@ -505,9 +536,8 @@ Datum
 range_lower_inf(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
-	char		flags = range_get_flags(r1);
 
-	PG_RETURN_BOOL(flags & RANGE_LB_INF);
+	PG_RETURN_BOOL(range_has_flag(r1, RANGE_LB_INF));
 }
 
 /* is upper bound infinite? */
@@ -515,9 +545,8 @@ Datum
 range_upper_inf(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
-	char		flags = range_get_flags(r1);
 
-	PG_RETURN_BOOL(flags & RANGE_UB_INF);
+	PG_RETURN_BOOL(range_has_flag(r1, RANGE_UB_INF));
 }
 
 
@@ -957,7 +986,25 @@ range_minus(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
 	RangeType  *r2 = PG_GETARG_RANGE_P(1);
+	RangeType  *ret;
 	TypeCacheEntry *typcache;
+
+	/* 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));
+
+	ret = range_minus_internal(typcache, r1, r2);
+	if (ret)
+		PG_RETURN_RANGE_P(ret);
+	else
+		PG_RETURN_NULL();
+}
+
+RangeType *
+range_minus_internal(TypeCacheEntry *typcache, RangeType *r1, RangeType *r2)
+{
 	RangeBound	lower1,
 				lower2;
 	RangeBound	upper1,
@@ -969,18 +1016,12 @@ range_minus(PG_FUNCTION_ARGS)
 				cmp_u1l2,
 				cmp_u1u2;
 
-	/* 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));
-
 	range_deserialize(typcache, r1, &lower1, &upper1, &empty1);
 	range_deserialize(typcache, r2, &lower2, &upper2, &empty2);
 
 	/* if either is empty, r1 is the correct answer */
 	if (empty1 || empty2)
-		PG_RETURN_RANGE_P(r1);
+		return r1;
 
 	cmp_l1l2 = range_cmp_bounds(typcache, &lower1, &lower2);
 	cmp_l1u2 = range_cmp_bounds(typcache, &lower1, &upper2);
@@ -993,34 +1034,34 @@ range_minus(PG_FUNCTION_ARGS)
 				 errmsg("result of range difference would not be contiguous")));
 
 	if (cmp_l1u2 > 0 || cmp_u1l2 < 0)
-		PG_RETURN_RANGE_P(r1);
+		return r1;
 
 	if (cmp_l1l2 >= 0 && cmp_u1u2 <= 0)
-		PG_RETURN_RANGE_P(make_empty_range(typcache));
+		return make_empty_range(typcache);
 
 	if (cmp_l1l2 <= 0 && cmp_u1l2 >= 0 && cmp_u1u2 <= 0)
 	{
 		lower2.inclusive = !lower2.inclusive;
 		lower2.lower = false;	/* it will become the upper bound */
-		PG_RETURN_RANGE_P(make_range(typcache, &lower1, &lower2, false));
+		return make_range(typcache, &lower1, &lower2, false);
 	}
 
 	if (cmp_l1l2 >= 0 && cmp_u1u2 >= 0 && cmp_l1u2 <= 0)
 	{
 		upper2.inclusive = !upper2.inclusive;
 		upper2.lower = true;	/* it will become the lower bound */
-		PG_RETURN_RANGE_P(make_range(typcache, &upper2, &upper1, false));
+		return make_range(typcache, &upper2, &upper1, false);
 	}
 
 	elog(ERROR, "unexpected case in range_minus");
-	PG_RETURN_NULL();
+	return NULL;
 }
 
 /*
  * Set union.  If strict is true, it is an error that the two input ranges
  * are not adjacent or overlapping.
  */
-static RangeType *
+RangeType *
 range_union_internal(TypeCacheEntry *typcache, RangeType *r1, RangeType *r2,
 					 bool strict)
 {
@@ -1101,6 +1142,19 @@ range_intersect(PG_FUNCTION_ARGS)
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
 	RangeType  *r2 = PG_GETARG_RANGE_P(1);
 	TypeCacheEntry *typcache;
+
+	/* 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));
+
+	PG_RETURN_RANGE_P(range_intersect_internal(typcache, r1, r2));
+}
+
+RangeType *
+range_intersect_internal(TypeCacheEntry *typcache, const RangeType *r1, const RangeType *r2)
+{
 	RangeBound	lower1,
 				lower2;
 	RangeBound	upper1,
@@ -1110,17 +1164,11 @@ range_intersect(PG_FUNCTION_ARGS)
 	RangeBound *result_lower;
 	RangeBound *result_upper;
 
-	/* 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));
-
 	range_deserialize(typcache, r1, &lower1, &upper1, &empty1);
 	range_deserialize(typcache, r2, &lower2, &upper2, &empty2);
 
-	if (empty1 || empty2 || !DatumGetBool(range_overlaps(fcinfo)))
-		PG_RETURN_RANGE_P(make_empty_range(typcache));
+	if (empty1 || empty2 || !range_overlaps_internal(typcache, r1, r2))
+		return make_empty_range(typcache);
 
 	if (range_cmp_bounds(typcache, &lower1, &lower2) >= 0)
 		result_lower = &lower1;
@@ -1132,9 +1180,81 @@ range_intersect(PG_FUNCTION_ARGS)
 	else
 		result_upper = &upper2;
 
-	PG_RETURN_RANGE_P(make_range(typcache, result_lower, result_upper, false));
+	return make_range(typcache, result_lower, result_upper, false);
+}
+
+/* range, range -> range, range functions */
+
+/*
+ * range_split_internal - if r2 intersects the middle of r1, leaving non-empty
+ * ranges on both sides, then return true and set output1 and output2 to the
+ * results of r1 - r2 (in order). Otherwise return false and don't set output1
+ * or output2. Neither input range should be empty.
+ */
+bool
+range_split_internal(TypeCacheEntry *typcache, const RangeType *r1, const RangeType *r2,
+					 RangeType **output1, RangeType **output2)
+{
+	RangeBound	lower1,
+				lower2;
+	RangeBound	upper1,
+				upper2;
+	bool		empty1,
+				empty2;
+
+	range_deserialize(typcache, r1, &lower1, &upper1, &empty1);
+	range_deserialize(typcache, r2, &lower2, &upper2, &empty2);
+
+	if (range_cmp_bounds(typcache, &lower1, &lower2) < 0 &&
+		range_cmp_bounds(typcache, &upper1, &upper2) > 0)
+	{
+		/*
+		 * Need to invert inclusive/exclusive for the lower2 and upper2
+		 * points. They can't be infinite though. We're allowed to overwrite
+		 * these RangeBounds since they only exist locally.
+		 */
+		lower2.inclusive = !lower2.inclusive;
+		lower2.lower = false;
+		upper2.inclusive = !upper2.inclusive;
+		upper2.lower = true;
+
+		*output1 = make_range(typcache, &lower1, &lower2, false);
+		*output2 = make_range(typcache, &upper2, &upper1, false);
+		return true;
+	}
+
+	return false;
 }
 
+/* range -> range aggregate functions */
+
+Datum
+range_intersect_agg_transfn(PG_FUNCTION_ARGS)
+{
+	MemoryContext aggContext;
+	Oid			rngtypoid;
+	TypeCacheEntry *typcache;
+	RangeType  *result;
+	RangeType  *current;
+
+	if (!AggCheckCallContext(fcinfo, &aggContext))
+		elog(ERROR, "range_intersect_agg_transfn called in non-aggregate context");
+
+	rngtypoid = get_fn_expr_argtype(fcinfo->flinfo, 1);
+	if (!type_is_range(rngtypoid))
+		ereport(ERROR, (errmsg("range_intersect_agg must be called with a range")));
+
+	typcache = range_get_typcache(fcinfo, rngtypoid);
+
+	/* strictness ensures these are non-null */
+	result = PG_GETARG_RANGE_P(0);
+	current = PG_GETARG_RANGE_P(1);
+
+	result = range_intersect_internal(typcache, result, current);
+	PG_RETURN_RANGE_P(result);
+}
+
+
 /* Btree support */
 
 /* btree comparator */
@@ -1144,12 +1264,6 @@ range_cmp(PG_FUNCTION_ARGS)
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
 	RangeType  *r2 = PG_GETARG_RANGE_P(1);
 	TypeCacheEntry *typcache;
-	RangeBound	lower1,
-				lower2;
-	RangeBound	upper1,
-				upper2;
-	bool		empty1,
-				empty2;
 	int			cmp;
 
 	check_stack_depth();		/* recurses when subtype is a range type */
@@ -1160,6 +1274,28 @@ range_cmp(PG_FUNCTION_ARGS)
 
 	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, const RangeType *r1, const 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);
 
@@ -1177,10 +1313,7 @@ range_cmp(PG_FUNCTION_ARGS)
 			cmp = range_cmp_bounds(typcache, &upper1, &upper2);
 	}
 
-	PG_FREE_IF_COPY(r1, 0);
-	PG_FREE_IF_COPY(r2, 1);
-
-	PG_RETURN_INT32(cmp);
+	return cmp;
 }
 
 /* inequality operators using the range_cmp function */
@@ -1218,13 +1351,10 @@ range_gt(PG_FUNCTION_ARGS)
 
 /* Hash support */
 
-/* hash a range value */
-Datum
-hash_range(PG_FUNCTION_ARGS)
+uint32
+hash_range_internal(TypeCacheEntry *typcache, const RangeType *r)
 {
-	RangeType  *r = PG_GETARG_RANGE_P(0);
 	uint32		result;
-	TypeCacheEntry *typcache;
 	TypeCacheEntry *scache;
 	RangeBound	lower;
 	RangeBound	upper;
@@ -1233,10 +1363,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 +1404,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, const RangeType *r, Datum seed)
+{
+	uint64		result;
 	TypeCacheEntry *scache;
 	RangeBound	lower;
 	RangeBound	upper;
@@ -1300,10 +1433,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 +1471,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));
 }
 
 /*
@@ -1770,6 +1917,21 @@ range_get_flags(const RangeType *range)
 }
 
 /*
+ * range_has_flag: set whether a range has a specific flag.
+ *
+ * This lets expose some of our functions that just check flags
+ * to the rest of the code base (like to multiranges)
+ * without writing full-fledged *_internal versions.
+ */
+bool
+range_has_flag(const RangeType *r1, char flag)
+{
+	char		flags = range_get_flags(r1);
+
+	return flags & flag;
+}
+
+/*
  * range_set_contain_empty: set the RANGE_CONTAIN_EMPTY bit in the value.
  *
  * This is only needed in GiST operations, so we don't include a provision
@@ -1938,6 +2100,46 @@ range_cmp_bound_values(TypeCacheEntry *typcache, const RangeBound *b1,
 }
 
 /*
+ * qsort callback for sorting ranges.
+ *
+ * Two empty ranges compare equal; an empty range sorts to the left of any
+ * non-empty range.  Two non-empty ranges are sorted by lower bound first
+ * and by upper bound next.
+ */
+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.
  */
 RangeType *
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index a7d63f107f..b137461de7 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -2530,6 +2530,16 @@ type_is_range(Oid typid)
 }
 
 /*
+ * 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
  *
  *		Given the type OID, fetch its category and preferred-type status.
@@ -3184,7 +3194,7 @@ get_namespace_name_or_temp(Oid nspid)
 		return get_namespace_name(nspid);
 }
 
-/*				---------- PG_RANGE CACHE ----------				 */
+/*				---------- PG_RANGE CACHES ----------				 */
 
 /*
  * get_range_subtype
@@ -3237,6 +3247,56 @@ get_range_collation(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->rngmultitypid;
+		ReleaseSysCache(tp);
+		return result;
+	}
+	else
+		return InvalidOid;
+}
+
+/*
+ * get_range_multirange_subtype
+ *		Returns the subtype of a given multirange type
+ *
+ * Returns InvalidOid if the type is not a multirange type.
+ */
+Oid
+get_range_multirange_subtype(Oid multirangeOid)
+{
+	HeapTuple	tp;
+
+	tp = SearchSysCache1(RANGEMULTIRANGE, 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 53d9ddf159..7654331a59 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -651,6 +651,18 @@ static const struct cachedesc cacheinfo[] = {
 		},
 		64
 	},
+	{RangeRelationId,			/* RANGEMULTIRANGE */
+		RangeMultirangeTypidIndexId,
+		1,
+		{
+			Anum_pg_range_rngmultitypid,
+			0,
+			0,
+			0
+		},
+		4
+	},
+
 	{RangeRelationId,			/* RANGETYPE */
 		RangeTypidIndexId,
 		1,
diff --git a/src/backend/utils/cache/typcache.c b/src/backend/utils/cache/typcache.c
index 854f133f9b..160846cf10 100644
--- a/src/backend/utils/cache/typcache.c
+++ b/src/backend/utils/cache/typcache.c
@@ -286,6 +286,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);
@@ -302,6 +303,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 TypeCacheTypCallback(Datum arg, int cacheid, uint32 hashvalue);
 static void TypeCacheOpcCallback(Datum arg, int cacheid, uint32 hashvalue);
@@ -553,8 +557,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))
@@ -591,7 +595,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))
@@ -616,7 +620,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))
@@ -641,7 +645,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))
@@ -693,6 +697,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;
@@ -737,6 +748,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;
@@ -817,6 +835,16 @@ lookup_type_cache(Oid type_id, int flags)
 	}
 
 	/*
+	 * 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
 	 */
 	if ((flags & TYPECACHE_DOMAIN_BASE_INFO) &&
@@ -927,6 +955,22 @@ load_rangetype_info(TypeCacheEntry *typentry)
 	typentry->rngelemtype = lookup_type_cache(subtypeOid, 0);
 }
 
+/*
+ * load_multirangetype_info --- helper routine to set up multirange type
+ * information
+ */
+static void
+load_multirangetype_info(TypeCacheEntry *typentry)
+{
+	Oid			rangetypeOid;
+
+	rangetypeOid = get_range_multirange_subtype(typentry->type_id);
+	if (!OidIsValid(rangetypeOid))
+		elog(ERROR, "cache lookup failed for multirange type %u",
+			 typentry->type_id);
+
+	typentry->rngtype = lookup_type_cache(rangetypeOid, TYPECACHE_RANGE_INFO);
+}
 
 /*
  * load_domaintype_info --- helper routine to set up domain constraint info
@@ -1527,11 +1571,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
@@ -1574,6 +1618,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 78ed857203..8a5bd9bf19 100644
--- a/src/backend/utils/fmgr/funcapi.c
+++ b/src/backend/utils/fmgr/funcapi.c
@@ -35,6 +35,7 @@ typedef struct polymorphic_actuals
 	Oid			anyelement_type;	/* anyelement mapping, if known */
 	Oid			anyarray_type;	/* anyarray mapping, if known */
 	Oid			anyrange_type;	/* anyrange mapping, if known */
+	Oid			anymultirange_type;	/* anymultirange mapping, if known */
 } polymorphic_actuals;
 
 static void shutdown_MultiFuncCall(Datum arg);
@@ -46,6 +47,7 @@ static TypeFuncClass internal_get_result_type(Oid funcid,
 static void resolve_anyelement_from_others(polymorphic_actuals *actuals);
 static void resolve_anyarray_from_others(polymorphic_actuals *actuals);
 static void resolve_anyrange_from_others(polymorphic_actuals *actuals);
+static void resolve_anymultirange_from_others(polymorphic_actuals *actuals);
 static bool resolve_polymorphic_tupdesc(TupleDesc tupdesc,
 										oidvector *declared_args,
 										Node *call_expr);
@@ -503,6 +505,31 @@ resolve_anyelement_from_others(polymorphic_actuals *actuals)
 							format_type_be(range_base_type))));
 		actuals->anyelement_type = range_typelem;
 	}
+	else if (OidIsValid(actuals->anymultirange_type))
+	{
+		/* Use the element type based on the multirange type */
+		Oid			multirange_base_type = getBaseType(actuals->anymultirange_type);
+		Oid			multirange_typelem =
+			get_range_multirange_subtype(multirange_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(multirange_base_type))));
+
+		Oid			range_base_type = getBaseType(multirange_typelem);
+		Oid			range_typelem = get_range_subtype(range_base_type);
+
+		if (!OidIsValid(range_typelem))
+			ereport(ERROR,
+					(errcode(ERRCODE_DATATYPE_MISMATCH),
+					 errmsg("argument declared %s does not contain a range type but type %s",
+							"anymultirange",
+							format_type_be(range_base_type))));
+		actuals->anyelement_type = range_typelem;
+	}
 	else
 		elog(ERROR, "could not determine polymorphic type");
 }
@@ -540,10 +567,54 @@ static void
 resolve_anyrange_from_others(polymorphic_actuals *actuals)
 {
 	/*
-	 * We can't deduce a range type from other polymorphic inputs, because
-	 * there may be multiple range types with the same subtype.
+	 * We can't deduce a range type from other polymorphic array or base types,
+	 * because there may be multiple range types with the same subtype, but we
+	 * can deduce it from a polymorphic multirange type.
 	 */
-	elog(ERROR, "could not determine polymorphic type");
+	if (OidIsValid(actuals->anymultirange_type))
+	{
+		/* Use the element type based on the multirange type */
+		Oid			multirange_base_type = getBaseType(actuals->anymultirange_type);
+		Oid			multirange_typelem =
+			get_range_multirange_subtype(multirange_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(multirange_base_type))));
+		actuals->anyrange_type = multirange_typelem;
+	}
+	else
+		elog(ERROR, "could not determine polymorphic type");
+}
+
+/*
+ * Resolve actual type of ANYMULTIRANGE from other polymorphic inputs
+ */
+static void
+resolve_anymultirange_from_others(polymorphic_actuals *actuals)
+{
+	/*
+	 * We can't deduce a multirange type from polymorphic array or base types,
+	 * because there may be multiple range types with the same subtype, but we
+	 * can deduce it from a polymorphic range type.
+	 */
+	if (OidIsValid(actuals->anyrange_type))
+	{
+		Oid			range_base_type = getBaseType(actuals->anyrange_type);
+		Oid			multirange_typeid = get_range_multirange(range_base_type);
+
+		if (!OidIsValid(multirange_typeid))
+			ereport(ERROR,
+					(errcode(ERRCODE_UNDEFINED_OBJECT),
+					 errmsg("could not find multirange type for data type %s",
+							format_type_be(actuals->anyrange_type))));
+		actuals->anymultirange_type = multirange_typeid;
+	}
+	else
+		elog(ERROR, "could not determine polymorphic type");
 }
 
 /*
@@ -566,9 +637,11 @@ 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_anycompatible_result = false;
 	bool		have_anycompatible_array_result = false;
 	bool		have_anycompatible_range_result = false;
+	bool		have_anycompatible_multirange_result = false;
 	polymorphic_actuals poly_actuals;
 	polymorphic_actuals anyc_actuals;
 	Oid			anycollation = InvalidOid;
@@ -594,6 +667,10 @@ resolve_polymorphic_tupdesc(TupleDesc tupdesc, oidvector *declared_args,
 				have_polymorphic_result = true;
 				have_anyrange_result = true;
 				break;
+			case ANYMULTIRANGEOID:
+				have_polymorphic_result = true;
+				have_anymultirange_result = true;
+				break;
 			case ANYCOMPATIBLEOID:
 			case ANYCOMPATIBLENONARRAYOID:
 				have_polymorphic_result = true;
@@ -607,6 +684,10 @@ resolve_polymorphic_tupdesc(TupleDesc tupdesc, oidvector *declared_args,
 				have_polymorphic_result = true;
 				have_anycompatible_range_result = true;
 				break;
+			case ANYCOMPATIBLEMULTIRANGEOID:
+				have_polymorphic_result = true;
+				have_anycompatible_multirange_result = true;
+				break;
 			default:
 				break;
 		}
@@ -660,6 +741,15 @@ resolve_polymorphic_tupdesc(TupleDesc tupdesc, oidvector *declared_args,
 						return false;
 				}
 				break;
+			case ANYMULTIRANGEOID:
+				if (!OidIsValid(poly_actuals.anymultirange_type))
+				{
+					poly_actuals.anymultirange_type =
+						get_call_expr_argtype(call_expr, i);
+					if (!OidIsValid(poly_actuals.anymultirange_type))
+						return false;
+				}
+				break;
 			case ANYCOMPATIBLEOID:
 			case ANYCOMPATIBLENONARRAYOID:
 				if (!OidIsValid(anyc_actuals.anyelement_type))
@@ -688,6 +778,15 @@ resolve_polymorphic_tupdesc(TupleDesc tupdesc, oidvector *declared_args,
 						return false;
 				}
 				break;
+			case ANYCOMPATIBLEMULTIRANGEOID:
+				if (!OidIsValid(anyc_actuals.anymultirange_type))
+				{
+					anyc_actuals.anymultirange_type =
+						get_call_expr_argtype(call_expr, i);
+					if (!OidIsValid(anyc_actuals.anymultirange_type))
+						return false;
+				}
+				break;
 			default:
 				break;
 		}
@@ -703,6 +802,9 @@ resolve_polymorphic_tupdesc(TupleDesc tupdesc, oidvector *declared_args,
 	if (have_anyrange_result && !OidIsValid(poly_actuals.anyrange_type))
 		resolve_anyrange_from_others(&poly_actuals);
 
+	if (have_anymultirange_result && !OidIsValid(poly_actuals.anymultirange_type))
+		resolve_anymultirange_from_others(&poly_actuals);
+
 	if (have_anycompatible_result && !OidIsValid(anyc_actuals.anyelement_type))
 		resolve_anyelement_from_others(&anyc_actuals);
 
@@ -712,6 +814,9 @@ resolve_polymorphic_tupdesc(TupleDesc tupdesc, oidvector *declared_args,
 	if (have_anycompatible_range_result && !OidIsValid(anyc_actuals.anyrange_type))
 		resolve_anyrange_from_others(&anyc_actuals);
 
+	if (have_anycompatible_multirange_result && !OidIsValid(anyc_actuals.anymultirange_type))
+		resolve_anymultirange_from_others(&anyc_actuals);
+
 	/*
 	 * Identify the collation to use for polymorphic OUT parameters. (It'll
 	 * necessarily be the same for both anyelement and anyarray, likewise for
@@ -780,6 +885,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),
+								   poly_actuals.anymultirange_type,
+								   -1,
+								   0);
+				/* no collation should be attached to a multirange type */
+				break;
 			case ANYCOMPATIBLEOID:
 			case ANYCOMPATIBLENONARRAYOID:
 				TupleDescInitEntry(tupdesc, i + 1,
@@ -805,6 +918,14 @@ resolve_polymorphic_tupdesc(TupleDesc tupdesc, oidvector *declared_args,
 								   0);
 				/* no collation should be attached to a range type */
 				break;
+			case ANYCOMPATIBLEMULTIRANGEOID:
+				TupleDescInitEntry(tupdesc, i + 1,
+								   NameStr(att->attname),
+								   anyc_actuals.anymultirange_type,
+								   -1,
+								   0);
+				/* no collation should be attached to a multirange type */
+				break;
 			default:
 				break;
 		}
@@ -834,9 +955,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;
 	bool		have_anycompatible_result = false;
 	bool		have_anycompatible_array_result = false;
 	bool		have_anycompatible_range_result = false;
+	bool		have_anycompatible_multirange_result = false;
 	polymorphic_actuals poly_actuals;
 	polymorphic_actuals anyc_actuals;
 	int			inargno;
@@ -912,6 +1035,24 @@ resolve_polymorphic_argtypes(int numargs, Oid *argtypes, char *argmodes,
 					argtypes[i] = poly_actuals.anyrange_type;
 				}
 				break;
+			case ANYMULTIRANGEOID:
+				if (argmode == PROARGMODE_OUT || argmode == PROARGMODE_TABLE)
+				{
+					have_polymorphic_result = true;
+					have_anymultirange_result = true;
+				}
+				else
+				{
+					if (!OidIsValid(poly_actuals.anymultirange_type))
+					{
+						poly_actuals.anymultirange_type =
+							get_call_expr_argtype(call_expr, inargno);
+						if (!OidIsValid(poly_actuals.anymultirange_type))
+							return false;
+					}
+					argtypes[i] = poly_actuals.anymultirange_type;
+				}
+				break;
 			case ANYCOMPATIBLEOID:
 			case ANYCOMPATIBLENONARRAYOID:
 				if (argmode == PROARGMODE_OUT || argmode == PROARGMODE_TABLE)
@@ -967,6 +1108,24 @@ resolve_polymorphic_argtypes(int numargs, Oid *argtypes, char *argmodes,
 					argtypes[i] = anyc_actuals.anyrange_type;
 				}
 				break;
+			case ANYCOMPATIBLEMULTIRANGEOID:
+				if (argmode == PROARGMODE_OUT || argmode == PROARGMODE_TABLE)
+				{
+					have_polymorphic_result = true;
+					have_anycompatible_multirange_result = true;
+				}
+				else
+				{
+					if (!OidIsValid(anyc_actuals.anymultirange_type))
+					{
+						anyc_actuals.anymultirange_type =
+							get_call_expr_argtype(call_expr, inargno);
+						if (!OidIsValid(anyc_actuals.anymultirange_type))
+							return false;
+					}
+					argtypes[i] = anyc_actuals.anymultirange_type;
+				}
+				break;
 			default:
 				break;
 		}
@@ -988,6 +1147,9 @@ resolve_polymorphic_argtypes(int numargs, Oid *argtypes, char *argmodes,
 	if (have_anyrange_result && !OidIsValid(poly_actuals.anyrange_type))
 		resolve_anyrange_from_others(&poly_actuals);
 
+	if (have_anymultirange_result && !OidIsValid(poly_actuals.anymultirange_type))
+		resolve_anymultirange_from_others(&poly_actuals);
+
 	if (have_anycompatible_result && !OidIsValid(anyc_actuals.anyelement_type))
 		resolve_anyelement_from_others(&anyc_actuals);
 
@@ -997,6 +1159,9 @@ resolve_polymorphic_argtypes(int numargs, Oid *argtypes, char *argmodes,
 	if (have_anycompatible_range_result && !OidIsValid(anyc_actuals.anyrange_type))
 		resolve_anyrange_from_others(&anyc_actuals);
 
+	if (have_anycompatible_multirange_result && !OidIsValid(anyc_actuals.anymultirange_type))
+		resolve_anymultirange_from_others(&anyc_actuals);
+
 	/* And finally replace the output column types as needed */
 	for (i = 0; i < numargs; i++)
 	{
@@ -1013,6 +1178,9 @@ resolve_polymorphic_argtypes(int numargs, Oid *argtypes, char *argmodes,
 			case ANYRANGEOID:
 				argtypes[i] = poly_actuals.anyrange_type;
 				break;
+			case ANYMULTIRANGEOID:
+				argtypes[i] = poly_actuals.anymultirange_type;
+				break;
 			case ANYCOMPATIBLEOID:
 			case ANYCOMPATIBLENONARRAYOID:
 				argtypes[i] = anyc_actuals.anyelement_type;
@@ -1023,6 +1191,9 @@ resolve_polymorphic_argtypes(int numargs, Oid *argtypes, char *argmodes,
 			case ANYCOMPATIBLERANGEOID:
 				argtypes[i] = anyc_actuals.anyrange_type;
 				break;
+			case ANYCOMPATIBLEMULTIRANGEOID:
+				argtypes[i] = anyc_actuals.anymultirange_type;
+				break;
 			default:
 				break;
 		}
@@ -1052,6 +1223,7 @@ get_type_func_class(Oid typid, Oid *base_typeid)
 		case TYPTYPE_BASE:
 		case TYPTYPE_ENUM:
 		case TYPTYPE_RANGE:
+		case TYPTYPE_MULTIRANGE:
 			return TYPEFUNC_SCALAR;
 		case TYPTYPE_DOMAIN:
 			*base_typeid = typid = getBaseType(typid);
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index c579227b19..5d09aa3d1e 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -276,7 +276,8 @@ static void dumpSearchPath(Archive *AH);
 static void binary_upgrade_set_type_oids_by_type_oid(Archive *fout,
 													 PQExpBuffer upgrade_buffer,
 													 Oid pg_type_oid,
-													 bool force_array_type);
+													 bool force_array_type,
+													 bool include_multirange_type);
 static bool binary_upgrade_set_type_oids_by_rel_oid(Archive *fout,
 													PQExpBuffer upgrade_buffer, Oid pg_rel_oid);
 static void binary_upgrade_set_pg_class_oids(Archive *fout,
@@ -1648,7 +1649,7 @@ selectDumpableType(TypeInfo *tyinfo, Archive *fout)
 	}
 
 	/* skip auto-generated array types */
-	if (tyinfo->isArray)
+	if (tyinfo->isArray || tyinfo->isMultirange)
 	{
 		tyinfo->dobj.objType = DO_DUMMY_TYPE;
 
@@ -4432,16 +4433,49 @@ append_depends_on_extension(Archive *fout,
 	}
 }
 
+static Oid
+get_next_possible_free_pg_type_oid(Archive *fout, PQExpBuffer upgrade_query)
+{
+	/*
+	 * If the old version didn't assign an array type, but the new version
+	 * does, we must select an unused type OID to assign.  This currently only
+	 * happens for domains, when upgrading pre-v11 to v11 and up.
+	 *
+	 * Note: local state here is kind of ugly, but we must have some, since we
+	 * mustn't choose the same unused OID more than once.
+	 */
+	static Oid	next_possible_free_oid = FirstNormalObjectId;
+	PGresult   *res;
+	bool		is_dup;
+
+	do
+	{
+		++next_possible_free_oid;
+		printfPQExpBuffer(upgrade_query,
+						  "SELECT EXISTS(SELECT 1 "
+						  "FROM pg_catalog.pg_type "
+						  "WHERE oid = '%u'::pg_catalog.oid);",
+						  next_possible_free_oid);
+		res = ExecuteSqlQueryForSingleRow(fout, upgrade_query->data);
+		is_dup = (PQgetvalue(res, 0, 0)[0] == 't');
+		PQclear(res);
+	} while (is_dup);
+
+	return next_possible_free_oid;
+}
 
 static void
 binary_upgrade_set_type_oids_by_type_oid(Archive *fout,
 										 PQExpBuffer upgrade_buffer,
 										 Oid pg_type_oid,
-										 bool force_array_type)
+										 bool force_array_type,
+										 bool include_multirange_type)
 {
 	PQExpBuffer upgrade_query = createPQExpBuffer();
 	PGresult   *res;
 	Oid			pg_type_array_oid;
+	Oid			pg_type_multirange_oid;
+	Oid			pg_type_multirange_array_oid;
 
 	appendPQExpBufferStr(upgrade_buffer, "\n-- For binary upgrade, must preserve pg_type oid\n");
 	appendPQExpBuffer(upgrade_buffer,
@@ -4462,33 +4496,7 @@ binary_upgrade_set_type_oids_by_type_oid(Archive *fout,
 	PQclear(res);
 
 	if (!OidIsValid(pg_type_array_oid) && force_array_type)
-	{
-		/*
-		 * If the old version didn't assign an array type, but the new version
-		 * does, we must select an unused type OID to assign.  This currently
-		 * only happens for domains, when upgrading pre-v11 to v11 and up.
-		 *
-		 * Note: local state here is kind of ugly, but we must have some,
-		 * since we mustn't choose the same unused OID more than once.
-		 */
-		static Oid	next_possible_free_oid = FirstNormalObjectId;
-		bool		is_dup;
-
-		do
-		{
-			++next_possible_free_oid;
-			printfPQExpBuffer(upgrade_query,
-							  "SELECT EXISTS(SELECT 1 "
-							  "FROM pg_catalog.pg_type "
-							  "WHERE oid = '%u'::pg_catalog.oid);",
-							  next_possible_free_oid);
-			res = ExecuteSqlQueryForSingleRow(fout, upgrade_query->data);
-			is_dup = (PQgetvalue(res, 0, 0)[0] == 't');
-			PQclear(res);
-		} while (is_dup);
-
-		pg_type_array_oid = next_possible_free_oid;
-	}
+		pg_type_array_oid = get_next_possible_free_pg_type_oid(fout, upgrade_query);
 
 	if (OidIsValid(pg_type_array_oid))
 	{
@@ -4499,6 +4507,46 @@ binary_upgrade_set_type_oids_by_type_oid(Archive *fout,
 						  pg_type_array_oid);
 	}
 
+	/*
+	 * Pre-set the multirange type oid and its own array type oid.
+	 */
+	if (include_multirange_type)
+	{
+		if (fout->remoteVersion >= 130000)
+		{
+			appendPQExpBuffer(upgrade_query,
+							  "SELECT t.oid, t.typarray "
+							  "FROM pg_catalog.pg_type t "
+							  "JOIN pg_catalog.pg_range r "
+							  "ON t.oid = r.rngmultitypid "
+							  "WHERE r.rngtypid = '%u'::pg_catalog.oid;",
+							  pg_type_oid);
+
+			res = ExecuteSqlQueryForSingleRow(fout, upgrade_query->data);
+
+			pg_type_multirange_oid = atooid(PQgetvalue(res, 0, PQfnumber(res, "oid")));
+			pg_type_multirange_array_oid = atooid(PQgetvalue(res, 0, PQfnumber(res, "typarray")));
+
+			PQclear(res);
+		}
+		else
+		{
+			pg_type_multirange_oid = get_next_possible_free_pg_type_oid(fout, upgrade_query);
+			pg_type_multirange_array_oid = get_next_possible_free_pg_type_oid(fout, upgrade_query);
+		}
+
+		appendPQExpBufferStr(upgrade_buffer,
+							 "\n-- For binary upgrade, must preserve multirange pg_type oid\n");
+		appendPQExpBuffer(upgrade_buffer,
+						  "SELECT pg_catalog.binary_upgrade_set_next_multirange_pg_type_oid('%u'::pg_catalog.oid);\n\n",
+						  pg_type_multirange_oid);
+		appendPQExpBufferStr(upgrade_buffer,
+							 "\n-- For binary upgrade, must preserve multirange pg_type array oid\n");
+		appendPQExpBuffer(upgrade_buffer,
+						  "SELECT pg_catalog.binary_upgrade_set_next_multirange_array_pg_type_oid('%u'::pg_catalog.oid);\n\n",
+						  pg_type_multirange_array_oid);
+	}
+
 	destroyPQExpBuffer(upgrade_query);
 }
 
@@ -4532,7 +4580,7 @@ binary_upgrade_set_type_oids_by_rel_oid(Archive *fout,
 	pg_type_oid = atooid(PQgetvalue(upgrade_res, 0, PQfnumber(upgrade_res, "crel")));
 
 	binary_upgrade_set_type_oids_by_type_oid(fout, upgrade_buffer,
-											 pg_type_oid, false);
+											 pg_type_oid, false, false);
 
 	if (!PQgetisnull(upgrade_res, 0, PQfnumber(upgrade_res, "trel")))
 	{
@@ -5081,6 +5129,11 @@ getTypes(Archive *fout, int *numTypes)
 		else
 			tyinfo[i].isArray = false;
 
+		if (tyinfo[i].typtype == 'm')
+			tyinfo[i].isMultirange = true;
+		else
+			tyinfo[i].isMultirange = false;
+
 		/* Decide whether we want to dump it */
 		selectDumpableType(&tyinfo[i], fout);
 
@@ -8219,9 +8272,12 @@ getProcLangs(Archive *fout, int *numProcLangs)
 
 /*
  * getCasts
- *	  get basic information about every cast in the system
+ *	  get basic information about most casts in the system
  *
  * numCasts is set to the number of casts read in
+ *
+ * Skip casts from a range to its multirange, since we'll create those
+ * automatically.
  */
 CastInfo *
 getCasts(Archive *fout, int *numCasts)
@@ -8239,7 +8295,20 @@ getCasts(Archive *fout, int *numCasts)
 	int			i_castcontext;
 	int			i_castmethod;
 
-	if (fout->remoteVersion >= 80400)
+	if (fout->remoteVersion >= 130000)
+	{
+		appendPQExpBufferStr(query, "SELECT tableoid, oid, "
+							 "castsource, casttarget, castfunc, castcontext, "
+							 "castmethod "
+							 "FROM pg_cast c "
+							 "WHERE NOT EXISTS ( "
+							 "SELECT 1 FROM pg_range r "
+							 "WHERE c.castsource = r.rngtypid "
+							 "AND c.casttarget = r.rngmultitypid "
+							 ") "
+							 "ORDER BY 3,4");
+	}
+	else if (fout->remoteVersion >= 80400)
 	{
 		appendPQExpBufferStr(query, "SELECT tableoid, oid, "
 							 "castsource, casttarget, castfunc, castcontext, "
@@ -10427,7 +10496,7 @@ dumpEnumType(Archive *fout, TypeInfo *tyinfo)
 	if (dopt->binary_upgrade)
 		binary_upgrade_set_type_oids_by_type_oid(fout, q,
 												 tyinfo->dobj.catId.oid,
-												 false);
+												 false, false);
 
 	appendPQExpBuffer(q, "CREATE TYPE %s AS ENUM (",
 					  qualtypname);
@@ -10553,7 +10622,7 @@ dumpRangeType(Archive *fout, TypeInfo *tyinfo)
 	if (dopt->binary_upgrade)
 		binary_upgrade_set_type_oids_by_type_oid(fout, q,
 												 tyinfo->dobj.catId.oid,
-												 false);
+												 false, true);
 
 	appendPQExpBuffer(q, "CREATE TYPE %s AS RANGE (",
 					  qualtypname);
@@ -10659,7 +10728,7 @@ dumpUndefinedType(Archive *fout, TypeInfo *tyinfo)
 	if (dopt->binary_upgrade)
 		binary_upgrade_set_type_oids_by_type_oid(fout, q,
 												 tyinfo->dobj.catId.oid,
-												 false);
+												 false, false);
 
 	appendPQExpBuffer(q, "CREATE TYPE %s;\n",
 					  qualtypname);
@@ -10864,7 +10933,7 @@ dumpBaseType(Archive *fout, TypeInfo *tyinfo)
 	if (dopt->binary_upgrade)
 		binary_upgrade_set_type_oids_by_type_oid(fout, q,
 												 tyinfo->dobj.catId.oid,
-												 false);
+												 false, false);
 
 	appendPQExpBuffer(q,
 					  "CREATE TYPE %s (\n"
@@ -11051,7 +11120,8 @@ dumpDomain(Archive *fout, TypeInfo *tyinfo)
 	if (dopt->binary_upgrade)
 		binary_upgrade_set_type_oids_by_type_oid(fout, q,
 												 tyinfo->dobj.catId.oid,
-												 true); /* force array type */
+												 true,		/* force array type */
+												 false);	/* force multirange type */
 
 	qtypname = pg_strdup(fmtId(tyinfo->dobj.name));
 	qualtypname = pg_strdup(fmtQualifiedDumpable(tyinfo));
@@ -11239,7 +11309,7 @@ dumpCompositeType(Archive *fout, TypeInfo *tyinfo)
 	{
 		binary_upgrade_set_type_oids_by_type_oid(fout, q,
 												 tyinfo->dobj.catId.oid,
-												 false);
+												 false, false);
 		binary_upgrade_set_pg_class_oids(fout, q, tyinfo->typrelid, false);
 	}
 
@@ -11513,7 +11583,7 @@ dumpShellType(Archive *fout, ShellTypeInfo *stinfo)
 	if (dopt->binary_upgrade)
 		binary_upgrade_set_type_oids_by_type_oid(fout, q,
 												 stinfo->baseType->dobj.catId.oid,
-												 false);
+												 false, false);
 
 	appendPQExpBuffer(q, "CREATE TYPE %s;\n",
 					  fmtQualifiedDumpable(stinfo));
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 61c909e06d..36178db881 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -176,6 +176,7 @@ typedef struct _typeInfo
 	char		typrelkind;		/* 'r', 'v', 'c', etc */
 	char		typtype;		/* 'b', 'c', etc */
 	bool		isArray;		/* true if auto-generated array type */
+	bool		isMultirange;	/* true if auto-generated multirange type */
 	bool		isDefined;		/* true if typisdefined */
 	/* If needed, we'll create a "shell type" entry for it; link that here: */
 	struct _shellTypeInfo *shellType;	/* shell-type entry, or NULL */
diff --git a/src/include/access/tupmacs.h b/src/include/access/tupmacs.h
index 70157cf90a..c262265b95 100644
--- a/src/include/access/tupmacs.h
+++ b/src/include/access/tupmacs.h
@@ -139,8 +139,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/binary_upgrade.h b/src/include/catalog/binary_upgrade.h
index 12d94fe1b3..01d95117cd 100644
--- a/src/include/catalog/binary_upgrade.h
+++ b/src/include/catalog/binary_upgrade.h
@@ -16,6 +16,8 @@
 
 extern PGDLLIMPORT Oid binary_upgrade_next_pg_type_oid;
 extern PGDLLIMPORT Oid binary_upgrade_next_array_pg_type_oid;
+extern PGDLLIMPORT Oid binary_upgrade_next_mrng_pg_type_oid;
+extern PGDLLIMPORT Oid binary_upgrade_next_mrng_array_pg_type_oid;
 extern PGDLLIMPORT Oid binary_upgrade_next_toast_pg_type_oid;
 
 extern PGDLLIMPORT Oid binary_upgrade_next_heap_pg_class_oid;
diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h
index 8be303870f..566f9364c5 100644
--- a/src/include/catalog/indexing.h
+++ b/src/include/catalog/indexing.h
@@ -327,6 +327,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_rngmultitypid_index, 8001, on pg_range using btree(rngmultitypid 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_aggregate.dat b/src/include/catalog/pg_aggregate.dat
index ffabe275c0..03bab74253 100644
--- a/src/include/catalog/pg_aggregate.dat
+++ b/src/include/catalog/pg_aggregate.dat
@@ -538,6 +538,17 @@
   aggtransfn => 'bytea_string_agg_transfn',
   aggfinalfn => 'bytea_string_agg_finalfn', aggtranstype => 'internal' },
 
+# range
+{ aggfnoid => 'range_intersect_agg(anyrange)',
+  aggtransfn => 'range_intersect_agg_transfn',
+  aggcombinefn => 'range_intersect_agg_transfn', aggtranstype => 'anyrange' },
+{ aggfnoid => 'range_intersect_agg(anymultirange)',
+  aggtransfn => 'multirange_intersect_agg_transfn',
+  aggcombinefn => 'multirange_intersect_agg_transfn', aggtranstype => 'anymultirange' },
+{ aggfnoid => 'range_agg(anyrange)', aggtransfn => 'range_agg_transfn',
+  aggfinalfn => 'range_agg_finalfn', aggfinalextra => 't',
+  aggtranstype => 'internal' },
+
 # json
 { aggfnoid => 'json_agg', aggtransfn => 'json_agg_transfn',
   aggfinalfn => 'json_agg_finalfn', aggtranstype => 'internal' },
diff --git a/src/include/catalog/pg_amop.dat b/src/include/catalog/pg_amop.dat
index b5dfaad9ec..604286e2c2 100644
--- a/src/include/catalog/pg_amop.dat
+++ b/src/include/catalog/pg_amop.dat
@@ -1378,6 +1378,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 37b580883f..9842e80acf 100644
--- a/src/include/catalog/pg_amproc.dat
+++ b/src/include/catalog/pg_amproc.dat
@@ -285,6 +285,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' },
 { amprocfamily => 'btree/xid8_ops', amproclefttype => 'xid8',
@@ -453,6 +455,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',
@@ -1274,12 +1281,13 @@
   amprocrighttype => 'anyrange', amprocnum => '4',
   amproc => 'brin_inclusion_union' },
 { amprocfamily => 'brin/range_inclusion_ops', amproclefttype => 'anyrange',
-  amprocrighttype => 'anyrange', amprocnum => '11', amproc => 'range_merge' },
+  amprocrighttype => 'anyrange', amprocnum => '11',
+  amproc => 'range_merge(anyrange,anyrange)' },
 { amprocfamily => 'brin/range_inclusion_ops', amproclefttype => 'anyrange',
   amprocrighttype => 'anyrange', amprocnum => '13',
   amproc => 'range_contains' },
 { amprocfamily => 'brin/range_inclusion_ops', amproclefttype => 'anyrange',
-  amprocrighttype => 'anyrange', amprocnum => '14', amproc => 'isempty' },
+  amprocrighttype => 'anyrange', amprocnum => '14', amproc => 'isempty(anyrange)' },
 
 # minmax pg_lsn
 { amprocfamily => 'brin/pg_lsn_minmax_ops', amproclefttype => 'pg_lsn',
diff --git a/src/include/catalog/pg_cast.dat b/src/include/catalog/pg_cast.dat
index 5a58f50fbb..d6ca624add 100644
--- a/src/include/catalog/pg_cast.dat
+++ b/src/include/catalog/pg_cast.dat
@@ -530,4 +530,17 @@
 { castsource => 'jsonb', casttarget => 'float8', castfunc => 'float8(jsonb)',
   castcontext => 'e', castmethod => 'f' },
 
+# range to multirange
+{ castsource => 'int4range', casttarget => 'int4multirange', castfunc => 'int4multirange(int4range)',
+  castcontext => 'e', castmethod => 'f' },
+{ castsource => 'int8range', casttarget => 'int8multirange', castfunc => 'int8multirange(int8range)',
+  castcontext => 'e', castmethod => 'f' },
+{ castsource => 'numrange', casttarget => 'nummultirange', castfunc => 'nummultirange(numrange)',
+  castcontext => 'e', castmethod => 'f' },
+{ castsource => 'daterange', casttarget => 'datemultirange', castfunc => 'datemultirange(daterange)',
+  castcontext => 'e', castmethod => 'f' },
+{ castsource => 'tsrange', casttarget => 'tsmultirange', castfunc => 'tsmultirange(tsrange)',
+  castcontext => 'e', castmethod => 'f' },
+{ castsource => 'tstzrange', casttarget => 'tstzmultirange', castfunc => 'tstzmultirange(tstzrange)',
+  castcontext => 'e', castmethod => 'f' },
 ]
diff --git a/src/include/catalog/pg_opclass.dat b/src/include/catalog/pg_opclass.dat
index f2342bb328..9b0609defa 100644
--- a/src/include/catalog/pg_opclass.dat
+++ b/src/include/catalog/pg_opclass.dat
@@ -230,6 +230,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 00ada7e48f..071352539f 100644
--- a/src/include/catalog/pg_operator.dat
+++ b/src/include/catalog/pg_operator.dat
@@ -3327,5 +3327,174 @@
   oprname => '@@', oprleft => 'jsonb', oprright => 'jsonpath',
   oprresult => 'bool', oprcode => 'jsonb_path_match_opr(jsonb,jsonpath)',
   oprrest => 'matchingsel', oprjoin => 'matchingjoinsel' },
+{ 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' },
+{ oid => '8073', oid_symbol => 'OID_RANGE_OVERLAPS_MULTIRANGE_OP',
+  descr => 'overlaps',
+  oprname => '&&', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '&&(anymultirange,anyrange)',
+  oprcode => 'range_overlaps_multirange', oprrest => 'areasel',
+  oprjoin => 'areajoinsel' },
+{ oid => '8074', oid_symbol => 'OID_MULTIRANGE_OVERLAPS_RANGE_OP', descr => 'contains',
+  oprname => '&&', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcom => '&&(anyrange,anymultirange)',
+  oprcode => 'multirange_overlaps_range', oprrest => 'areasel',
+  oprjoin => 'areajoinsel' },
+{ oid => '8075', oid_symbol => 'OID_MULTIRANGE_OVERLAPS_MULTIRANGE_OP', descr => 'contains',
+  oprname => '&&', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '&&(anymultirange,anymultirange)',
+  oprcode => 'multirange_overlaps_multirange', oprrest => 'areasel',
+  oprjoin => 'areajoinsel' },
+{ oid => '8064', oid_symbol => 'OID_MULTIRANGE_CONTAINS_ELEM_OP',
+  descr => 'contains',
+  oprname => '@>', oprleft => 'anymultirange', oprright => 'anyelement',
+  oprresult => 'bool', oprcom => '<@(anyelement,anymultirange)',
+  oprcode => 'multirange_contains_elem', oprrest => 'contsel',
+  oprjoin => 'contjoinsel' },
+{ oid => '8065', oid_symbol => 'OID_MULTIRANGE_CONTAINS_RANGE_OP', descr => 'contains',
+  oprname => '@>', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcom => '<@(anyrange,anymultirange)',
+  oprcode => 'multirange_contains_range', oprrest => 'contsel',
+  oprjoin => 'contjoinsel' },
+{ oid => '8066', oid_symbol => 'OID_MULTIRANGE_CONTAINS_MULTIRANGE_OP', descr => 'contains',
+  oprname => '@>', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '<@(anymultirange,anymultirange)',
+  oprcode => 'multirange_contains_multirange', oprrest => 'contsel',
+  oprjoin => 'contjoinsel' },
+{ oid => '8067', oid_symbol => 'OID_MULTIRANGE_ELEM_CONTAINED_OP',
+  descr => 'is contained by',
+  oprname => '<@', oprleft => 'anyelement', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '@>(anymultirange,anyelement)',
+  oprcode => 'elem_contained_by_multirange', oprrest => 'contsel',
+  oprjoin => 'contjoinsel' },
+{ oid => '8068', oid_symbol => 'OID_MULTIRANGE_RANGE_CONTAINED_OP',
+  descr => 'is contained by',
+  oprname => '<@', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '@>(anymultirange,anyrange)',
+  oprcode => 'range_contained_by_multirange', oprrest => 'contsel',
+  oprjoin => 'contjoinsel' },
+{ oid => '8069', oid_symbol => 'OID_MULTIRANGE_MULTIRANGE_CONTAINED_OP',
+  descr => 'is contained by',
+  oprname => '<@', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '@>(anymultirange,anymultirange)',
+  oprcode => 'multirange_contained_by_multirange', oprrest => 'contsel',
+  oprjoin => 'contjoinsel' },
+{ oid => '8106', oid_symbol => 'OID_RANGE_OVERLAPS_LEFT_MULTIRANGE_OP',
+  descr => 'overlaps or is left of',
+  oprname => '&<', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcode => 'range_overleft_multirange', oprrest => 'scalarltsel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8107', oid_symbol => 'OID_MULTIRANGE_OVERLAPS_LEFT_RANGE_OP',
+  descr => 'overlaps or is left of',
+  oprname => '&<', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcode => 'multirange_overleft_range', oprrest => 'scalarltsel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8108', oid_symbol => 'OID_MULTIRANGE_OVERLAPS_LEFT_MULTIRANGE_OP',
+  descr => 'overlaps or is left of',
+  oprname => '&<', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcode => 'multirange_overleft_multirange', oprrest => 'scalarltsel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8109', oid_symbol => 'OID_RANGE_OVERLAPS_RIGHT_MULTIRANGE_OP',
+  descr => 'overlaps or is right of',
+  oprname => '&>', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcode => 'range_overright_multirange', oprrest => 'scalargtsel',
+  oprjoin => 'scalargtjoinsel' },
+{ oid => '8110', oid_symbol => 'OID_MULTIRANGE_OVERLAPS_RIGHT_RANGE_OP',
+  descr => 'overlaps or is right of',
+  oprname => '&>', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcode => 'multirange_overright_range', oprrest => 'scalargtsel',
+  oprjoin => 'scalargtjoinsel' },
+{ oid => '8111', oid_symbol => 'OID_MULTIRANGE_OVERLAPS_RIGHT_MULTIRANGE_OP',
+  descr => 'overlaps or is right of',
+  oprname => '&>', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcode => 'multirange_overright_multirange', oprrest => 'scalargtsel',
+  oprjoin => 'scalargtjoinsel' },
+{ oid => '8091', oid_symbol => 'OID_RANGE_ADJACENT_MULTIRANGE_OP', descr => 'is adjacent to',
+  oprname => '-|-', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '-|-(anymultirange,anyrange)',
+  oprcode => 'range_adjacent_multirange', oprrest => 'scalarltsel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8092', oid_symbol => 'OID_MULTIRANGE_ADJACENT_RANGE_OP', descr => 'is adjacent to',
+  oprname => '-|-', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcom => '-|-(anyrange,anymultirange)',
+  oprcode => 'multirange_adjacent_range', oprrest => 'scalarltsel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8093', oid_symbol => 'OID_MULTIRANGE_ADJACENT_MULTIRANGE_OP', descr => 'is adjacent to',
+  oprname => '-|-', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '-|-(anymultirange,anymultirange)',
+  oprcode => 'multirange_adjacent_multirange', oprrest => 'scalarltsel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8117', descr => 'multirange union',
+  oprname => '+', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'anymultirange', oprcom => '+(anymultirange,anymultirange)',
+  oprcode => 'multirange_union' },
+{ oid => '8123', descr => 'multirange minus',
+  oprname => '-', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'anymultirange', oprcode => 'multirange_minus' },
+{ oid => '8129', descr => 'multirange intersect',
+  oprname => '*', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'anymultirange', oprcom => '*(anymultirange,anymultirange)',
+  oprcode => 'multirange_intersect' },
+{ oid => '8082', oid_symbol => 'OID_RANGE_BEFORE_MULTIRANGE_OP', descr => 'is left of',
+  oprname => '<<', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '>>(anymultirange,anyrange)',
+  oprcode => 'range_before_multirange', oprrest => 'scalarltsel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8083', oid_symbol => 'OID_MULTIRANGE_BEFORE_RANGE_OP', descr => 'is left of',
+  oprname => '<<', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcom => '>>(anyrange,anymultirange)',
+  oprcode => 'multirange_before_range', oprrest => 'scalarltsel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8084', oid_symbol => 'OID_MULTIRANGE_BEFORE_MULTIRANGE_OP', descr => 'is left of',
+  oprname => '<<', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '>>(anymultirange,anymultirange)',
+  oprcode => 'multirange_before_multirange', oprrest => 'scalarltsel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8085', oid_symbol => 'OID_RANGE_AFTER_MULTIRANGE_OP', descr => 'is right of',
+  oprname => '>>', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '<<(anymultirange,anyrange)',
+  oprcode => 'range_after_multirange', oprrest => 'scalargtsel',
+  oprjoin => 'scalargtjoinsel' },
+{ oid => '8086', oid_symbol => 'OID_MULTIRANGE_AFTER_RANGE_OP', descr => 'is right of',
+  oprname => '>>', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcom => '<<(anyrange,anymultirange)',
+  oprcode => 'multirange_after_range', oprrest => 'scalargtsel',
+  oprjoin => 'scalargtjoinsel' },
+{ oid => '8087', oid_symbol => 'OID_MULTIRANGE_AFTER_MULTIRANGE_OP', descr => 'is right of',
+  oprname => '>>', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '<<(anymultirange,anymultirange)',
+  oprcode => 'multirange_after_multirange', oprrest => 'scalargtsel',
+  oprjoin => 'scalargtjoinsel' },
 
 ]
diff --git a/src/include/catalog/pg_opfamily.dat b/src/include/catalog/pg_opfamily.dat
index 4004138d77..dfc13fd6e5 100644
--- a/src/include/catalog/pg_opfamily.dat
+++ b/src/include/catalog/pg_opfamily.dat
@@ -230,5 +230,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 4bce3ad8de..5eb505fdda 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -7208,6 +7208,14 @@
   proname => 'anycompatiblerange_out', provolatile => 's',
   prorettype => 'cstring', proargtypes => 'anycompatiblerange',
   prosrc => 'anycompatiblerange_out' },
+{ oid => '8154', descr => 'I/O',
+  proname => 'anycompatiblemultirange_in', provolatile => 's',
+  prorettype => 'anycompatiblemultirange', proargtypes => 'cstring oid int4',
+  prosrc => 'anycompatiblemultirange_in' },
+{ oid => '8155', descr => 'I/O',
+  proname => 'anycompatiblemultirange_out', provolatile => 's',
+  prorettype => 'cstring', proargtypes => 'anycompatiblemultirange',
+  prosrc => 'anycompatiblemultirange_out' },
 
 # tablesample method handlers
 { oid => '3313', descr => 'BERNOULLI tablesample method handler',
@@ -9797,6 +9805,10 @@
   descr => 'the smallest range which includes both of the given ranges',
   proname => 'range_merge', prorettype => 'anyrange',
   proargtypes => 'anyrange anyrange', prosrc => 'range_merge' },
+{ oid => '8143',
+  descr => 'the smallest range which includes the whole multirange',
+  proname => 'range_merge', prorettype => 'anyrange',
+  proargtypes => 'anymultirange', prosrc => 'range_merge_from_multirange' },
 { oid => '3868',
   proname => 'range_intersect', prorettype => 'anyrange',
   proargtypes => 'anyrange anyrange', prosrc => 'range_intersect' },
@@ -9846,6 +9858,13 @@
 { oid => '3169', descr => 'restriction selectivity for range operators',
   proname => 'rangesel', provolatile => 's', prorettype => 'float8',
   proargtypes => 'internal oid internal int4', prosrc => 'rangesel' },
+{ oid => '8133', descr => 'range aggregate by intersecting',
+  proname => 'range_intersect_agg_transfn', prorettype => 'anyrange',
+  proargtypes => 'anyrange anyrange', prosrc => 'range_intersect_agg_transfn'},
+{ oid => '8134', descr => 'range aggregate by intersecting',
+  proname => 'range_intersect_agg', prokind => 'a', proisstrict => 'f',
+  prorettype => 'anyrange', proargtypes => 'anyrange',
+  prosrc => 'aggregate_dummy'},
 
 { oid => '3914', descr => 'convert an int4 range to canonical form',
   proname => 'int4range_canonical', prorettype => 'int4range',
@@ -9914,6 +9933,258 @@
   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 => '8094', descr => 'lower bound of multirange',
+  proname => 'lower', prorettype => 'anyelement', proargtypes => 'anymultirange',
+  prosrc => 'multirange_lower' },
+{ oid => '8095', descr => 'upper bound of multirange',
+  proname => 'upper', prorettype => 'anyelement', proargtypes => 'anymultirange',
+  prosrc => 'multirange_upper' },
+{ oid => '8057', descr => 'is the multirange empty?',
+  proname => 'isempty', prorettype => 'bool',
+  proargtypes => 'anymultirange', prosrc => 'multirange_empty' },
+{ oid => '8096', descr => 'is the multirange\'s lower bound inclusive?',
+  proname => 'lower_inc', prorettype => 'bool', proargtypes => 'anymultirange',
+  prosrc => 'multirange_lower_inc' },
+{ oid => '8097', descr => 'is the multirange\'s upper bound inclusive?',
+  proname => 'upper_inc', prorettype => 'bool', proargtypes => 'anymultirange',
+  prosrc => 'multirange_upper_inc' },
+{ oid => '8098', descr => 'is the multirange\'s lower bound infinite?',
+  proname => 'lower_inf', prorettype => 'bool', proargtypes => 'anymultirange',
+  prosrc => 'multirange_lower_inf' },
+{ oid => '8099', descr => 'is the multirange\'s upper bound infinite?',
+  proname => 'upper_inf', prorettype => 'bool', proargtypes => 'anymultirange',
+  prosrc => 'multirange_upper_inf' },
+{ oid => '8008', descr => 'multirange typanalyze',
+  proname => 'multirange_typanalyze', provolatile => 's', prorettype => 'bool',
+  proargtypes => 'internal', prosrc => 'multirange_typanalyze' },
+{ oid => '8033',
+  proname => 'multirange_eq', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_eq' },
+{ oid => '8034',
+  proname => 'multirange_ne', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_ne' },
+{ oid => '8070',
+  proname => 'range_overlaps_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_overlaps_multirange' },
+{ oid => '8071',
+  proname => 'multirange_overlaps_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_overlaps_range' },
+{ oid => '8072',
+  proname => 'multirange_overlaps_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_overlaps_multirange' },
+{ oid => '8058',
+  proname => 'multirange_contains_elem', prorettype => 'bool',
+  proargtypes => 'anymultirange anyelement', prosrc => 'multirange_contains_elem' },
+{ oid => '8059',
+  proname => 'multirange_contains_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_contains_range' },
+{ oid => '8060',
+  proname => 'multirange_contains_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_contains_multirange' },
+{ oid => '8061',
+  proname => 'elem_contained_by_multirange', prorettype => 'bool',
+  proargtypes => 'anyelement anymultirange', prosrc => 'elem_contained_by_multirange' },
+{ oid => '8062',
+  proname => 'range_contained_by_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_contained_by_multirange' },
+{ oid => '8063',
+  proname => 'multirange_contained_by_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_contained_by_multirange' },
+{ oid => '8088',
+  proname => 'range_adjacent_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_adjacent_multirange' },
+{ oid => '8089',
+  proname => 'multirange_adjacent_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_adjacent_multirange' },
+{ oid => '8090',
+  proname => 'multirange_adjacent_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_adjacent_range' },
+{ oid => '8076',
+  proname => 'range_before_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_before_multirange' },
+{ oid => '8077',
+  proname => 'multirange_before_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_before_range' },
+{ oid => '8078',
+  proname => 'multirange_before_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_before_multirange' },
+{ oid => '8079',
+  proname => 'range_after_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_after_multirange' },
+{ oid => '8080',
+  proname => 'multirange_after_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_after_range' },
+{ oid => '8081',
+  proname => 'multirange_after_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_after_multirange' },
+{ oid => '8100',
+  proname => 'range_overleft_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_overleft_multirange' },
+{ oid => '8101',
+  proname => 'multirange_overleft_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_overleft_range' },
+{ oid => '8102',
+  proname => 'multirange_overleft_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_overleft_multirange' },
+{ oid => '8103',
+  proname => 'range_overright_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_overright_multirange' },
+{ oid => '8104',
+  proname => 'multirange_overright_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_overright_range' },
+{ oid => '8105',
+  proname => 'multirange_overright_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_overright_multirange' },
+{ oid => '8114',
+  proname => 'multirange_union', prorettype => 'anymultirange',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_union' },
+{ oid => '8120',
+  proname => 'multirange_minus', prorettype => 'anymultirange',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_minus' },
+{ oid => '8126',
+  proname => 'multirange_intersect', prorettype => 'anymultirange',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_intersect' },
+{ oid => '8022', descr => 'less-equal-greater',
+  proname => 'multirange_cmp', prorettype => 'int4',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_cmp' },
+{ oid => '8023',
+  proname => 'multirange_lt', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_lt' },
+{ oid => '8024',
+  proname => 'multirange_le', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_le' },
+{ oid => '8025',
+  proname => 'multirange_ge', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_ge' },
+{ oid => '8026',
+  proname => 'multirange_gt', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_gt' },
+{ oid => '8038', descr => 'hash a multirange',
+  proname => 'hash_multirange', prorettype => 'int4', proargtypes => 'anymultirange',
+  prosrc => 'hash_multirange' },
+{ oid => '8039', descr => 'hash a multirange',
+  proname => 'hash_multirange_extended', prorettype => 'int8',
+  proargtypes => 'anymultirange int8', prosrc => 'hash_multirange_extended' },
+
+{ oid => '8045', descr => 'int4multirange constructor',
+  proname => 'int4multirange', proisstrict => 'f',
+  prorettype => 'int4multirange', proargtypes => '',
+  prosrc => 'multirange_constructor0' },
+{ oid => '8146', descr => 'int4multirange constructor',
+  proname => 'int4multirange', proisstrict => 't',
+  prorettype => 'int4multirange', proargtypes => 'int4range',
+  prosrc => 'multirange_constructor1' },
+{ oid => '8046', descr => 'int4multirange constructor',
+  proname => 'int4multirange', provariadic => 'int4range', proisstrict => 'f',
+  prorettype => 'int4multirange', proargtypes => '_int4range',
+  proallargtypes => '{_int4range}', proargmodes => '{v}',
+  prosrc => 'multirange_constructor2' },
+{ oid => '8047', descr => 'nummultirange constructor',
+  proname => 'nummultirange', proisstrict => 'f',
+  prorettype => 'nummultirange', proargtypes => '',
+  prosrc => 'multirange_constructor0' },
+{ oid => '8147', descr => 'nummultirange constructor',
+  proname => 'nummultirange', proisstrict => 't',
+  prorettype => 'nummultirange', proargtypes => 'numrange',
+  prosrc => 'multirange_constructor1' },
+{ oid => '8048', descr => 'nummultirange constructor',
+  proname => 'nummultirange', provariadic => 'numrange', proisstrict => 'f',
+  prorettype => 'nummultirange', proargtypes => '_numrange',
+  proallargtypes => '{_numrange}', proargmodes => '{v}',
+  prosrc => 'multirange_constructor2' },
+{ oid => '8049', descr => 'tsmultirange constructor',
+  proname => 'tsmultirange', proisstrict => 'f',
+  prorettype => 'tsmultirange', proargtypes => '',
+  prosrc => 'multirange_constructor0' },
+{ oid => '8148', descr => 'tsmultirange constructor',
+  proname => 'tsmultirange', proisstrict => 't',
+  prorettype => 'tsmultirange', proargtypes => 'tsrange',
+  prosrc => 'multirange_constructor1' },
+{ oid => '8050', descr => 'tsmultirange constructor',
+  proname => 'tsmultirange', provariadic => 'tsrange', proisstrict => 'f',
+  prorettype => 'tsmultirange', proargtypes => '_tsrange',
+  proallargtypes => '{_tsrange}', proargmodes => '{v}',
+  prosrc => 'multirange_constructor2' },
+{ oid => '8051', descr => 'tstzmultirange constructor',
+  proname => 'tstzmultirange', proisstrict => 'f',
+  prorettype => 'tstzmultirange', proargtypes => '',
+  prosrc => 'multirange_constructor0' },
+{ oid => '8149', descr => 'tstzmultirange constructor',
+  proname => 'tstzmultirange', proisstrict => 't',
+  prorettype => 'tstzmultirange', proargtypes => 'tstzrange',
+  prosrc => 'multirange_constructor1' },
+{ oid => '8052', descr => 'tstzmultirange constructor',
+  proname => 'tstzmultirange', provariadic => 'tstzrange', proisstrict => 'f',
+  prorettype => 'tstzmultirange', proargtypes => '_tstzrange',
+  proallargtypes => '{_tstzrange}', proargmodes => '{v}',
+  prosrc => 'multirange_constructor2' },
+{ oid => '8053', descr => 'datemultirange constructor',
+  proname => 'datemultirange', proisstrict => 'f',
+  prorettype => 'datemultirange', proargtypes => '',
+  prosrc => 'multirange_constructor0' },
+{ oid => '8150', descr => 'datemultirange constructor',
+  proname => 'datemultirange', proisstrict => 't',
+  prorettype => 'datemultirange', proargtypes => 'daterange',
+  prosrc => 'multirange_constructor1' },
+{ oid => '8054', descr => 'datemultirange constructor',
+  proname => 'datemultirange', provariadic => 'daterange', proisstrict => 'f',
+  prorettype => 'datemultirange', proargtypes => '_daterange',
+  proallargtypes => '{_daterange}', proargmodes => '{v}',
+  prosrc => 'multirange_constructor2' },
+{ oid => '8055', descr => 'int8multirange constructor',
+  proname => 'int8multirange', proisstrict => 'f',
+  prorettype => 'int8multirange', proargtypes => '',
+  prosrc => 'multirange_constructor0' },
+{ oid => '8151', descr => 'int8multirange constructor',
+  proname => 'int8multirange', proisstrict => 't',
+  prorettype => 'int8multirange', proargtypes => 'int8range',
+  prosrc => 'multirange_constructor1' },
+{ oid => '8056', descr => 'int8multirange constructor',
+  proname => 'int8multirange', provariadic => 'int8range', proisstrict => 'f',
+  prorettype => 'int8multirange', proargtypes => '_int8range',
+  proallargtypes => '{_int8range}', proargmodes => '{v}',
+  prosrc => 'multirange_constructor2' },
+{ oid => '8152', descr => 'anymultirange cast',
+  proname => 'multirange', proisstrict => 't',
+  prorettype => 'anymultirange', proargtypes => 'anyrange',
+  prosrc => 'multirange_constructor1' },
+{ oid => '8130', descr => 'aggregate transition function',
+  proname => 'range_agg_transfn', proisstrict => 'f', prorettype => 'internal',
+  proargtypes => 'internal anyrange', prosrc => 'range_agg_transfn' },
+{ oid => '8131', descr => 'aggregate final function',
+  proname => 'range_agg_finalfn', proisstrict => 'f', prorettype => 'anymultirange',
+  proargtypes => 'internal anyrange', prosrc => 'range_agg_finalfn' },
+{ oid => '8132', descr => 'combine aggregate input into a multirange',
+  proname => 'range_agg', prokind => 'a', proisstrict => 'f',
+  prorettype => 'anymultirange', proargtypes => 'anyrange',
+  prosrc => 'aggregate_dummy' },
+{ oid => '8135', descr => 'range aggregate by intersecting',
+  proname => 'multirange_intersect_agg_transfn', prorettype => 'anymultirange',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_intersect_agg_transfn'},
+{ oid => '8136', descr => 'range aggregate by intersecting',
+  proname => 'range_intersect_agg', prokind => 'a', proisstrict => 'f',
+  prorettype => 'anymultirange', proargtypes => 'anymultirange',
+  prosrc => 'aggregate_dummy'},
+
 # date, time, timestamp constructors
 { oid => '3846', descr => 'construct date',
   proname => 'make_date', prorettype => 'date', proargtypes => 'int4 int4 int4',
@@ -10296,6 +10567,14 @@
   proname => 'binary_upgrade_set_next_array_pg_type_oid', provolatile => 'v',
   proparallel => 'r', prorettype => 'void', proargtypes => 'oid',
   prosrc => 'binary_upgrade_set_next_array_pg_type_oid' },
+{ oid => '8144', descr => 'for use by pg_upgrade',
+  proname => 'binary_upgrade_set_next_multirange_pg_type_oid', provolatile => 'v',
+  proparallel => 'r', prorettype => 'void', proargtypes => 'oid',
+  prosrc => 'binary_upgrade_set_next_multirange_pg_type_oid' },
+{ oid => '8145', descr => 'for use by pg_upgrade',
+  proname => 'binary_upgrade_set_next_multirange_array_pg_type_oid', provolatile => 'v',
+  proparallel => 'r', prorettype => 'void', proargtypes => 'oid',
+  prosrc => 'binary_upgrade_set_next_multirange_array_pg_type_oid' },
 { oid => '3585', descr => 'for use by pg_upgrade',
   proname => 'binary_upgrade_set_next_toast_pg_type_oid', provolatile => 'v',
   proparallel => 'r', prorettype => 'void', proargtypes => 'oid',
diff --git a/src/include/catalog/pg_range.dat b/src/include/catalog/pg_range.dat
index 479754c245..10060255c9 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',
+  rngmultitypid => 'int4multirange' },
 { rngtypid => 'numrange', rngsubtype => 'numeric',
   rngsubopc => 'btree/numeric_ops', rngcanonical => '-',
-  rngsubdiff => 'numrange_subdiff' },
+  rngsubdiff => 'numrange_subdiff', rngmultitypid => 'nummultirange' },
 { rngtypid => 'tsrange', rngsubtype => 'timestamp',
   rngsubopc => 'btree/timestamp_ops', rngcanonical => '-',
-  rngsubdiff => 'tsrange_subdiff' },
+  rngsubdiff => 'tsrange_subdiff', rngmultitypid => 'tsmultirange' },
 { rngtypid => 'tstzrange', rngsubtype => 'timestamptz',
   rngsubopc => 'btree/timestamptz_ops', rngcanonical => '-',
-  rngsubdiff => 'tstzrange_subdiff' },
+  rngsubdiff => 'tstzrange_subdiff', rngmultitypid => 'tstzmultirange' },
 { rngtypid => 'daterange', rngsubtype => 'date', rngsubopc => 'btree/date_ops',
-  rngcanonical => 'daterange_canonical', rngsubdiff => 'daterange_subdiff' },
+  rngcanonical => 'daterange_canonical', rngsubdiff => 'daterange_subdiff',
+  rngmultitypid => 'datemultirange' },
 { rngtypid => 'int8range', rngsubtype => 'int8', rngsubopc => 'btree/int8_ops',
-  rngcanonical => 'int8range_canonical', rngsubdiff => 'int8range_subdiff' },
+  rngcanonical => 'int8range_canonical', rngsubdiff => 'int8range_subdiff',
+  rngmultitypid => 'int8multirange' },
 
 ]
diff --git a/src/include/catalog/pg_range.h b/src/include/catalog/pg_range.h
index 98172bb1b6..60a8fde518 100644
--- a/src/include/catalog/pg_range.h
+++ b/src/include/catalog/pg_range.h
@@ -34,6 +34,9 @@ CATALOG(pg_range,3541,RangeRelationId)
 	/* OID of range's element type (subtype) */
 	Oid			rngsubtype BKI_LOOKUP(pg_type);
 
+	/* OID of the range's multirange type */
+	Oid			rngmultitypid BKI_LOOKUP(pg_type);
+
 	/* collation for this range type, or 0 */
 	Oid			rngcollation BKI_DEFAULT(0);
 
@@ -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 78f44af5e7..741090cfe5 100644
--- a/src/include/catalog/pg_type.dat
+++ b/src/include/catalog/pg_type.dat
@@ -500,6 +500,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 => 'R', 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 => 'R', 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 => 'R', 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 => 'R', 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 => 'R', 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 => 'R', 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
@@ -629,5 +663,16 @@
   typtype => 'p', typcategory => 'P', typinput => 'anycompatiblerange_in',
   typoutput => 'anycompatiblerange_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' },
+{ oid => '8153',
+  descr => 'pseudo-type representing a multirange over a polymorphic common type',
+  typname => 'anycompatiblemultirange', typlen => '-1', typbyval => 'f',
+  typtype => 'p', typcategory => 'P', typinput => 'anycompatiblemultirange_in',
+  typoutput => 'anycompatiblemultirange_out', typreceive => '-', typsend => '-',
+  typalign => 'd', typstorage => 'x' },
 
 ]
diff --git a/src/include/catalog/pg_type.h b/src/include/catalog/pg_type.h
index 7b37562648..63e7c940a2 100644
--- a/src/include/catalog/pg_type.h
+++ b/src/include/catalog/pg_type.h
@@ -263,6 +263,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 */
 
@@ -304,13 +305,15 @@ typedef FormData_pg_type *Form_pg_type;
 	 (typid) == ANYARRAYOID || \
 	 (typid) == ANYNONARRAYOID || \
 	 (typid) == ANYENUMOID || \
-	 (typid) == ANYRANGEOID)
+	 (typid) == ANYRANGEOID || \
+	 (typid) == ANYMULTIRANGEOID)
 
 #define IsPolymorphicTypeFamily2(typid)  \
 	((typid) == ANYCOMPATIBLEOID || \
 	 (typid) == ANYCOMPATIBLEARRAYOID || \
 	 (typid) == ANYCOMPATIBLENONARRAYOID || \
-	 (typid) == ANYCOMPATIBLERANGEOID)
+	 (typid) == ANYCOMPATIBLERANGEOID || \
+	 (typid) == ANYCOMPATIBLEMULTIRANGEOID)
 
 #endif							/* EXPOSE_TO_CLIENT_CODE */
 
@@ -369,4 +372,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/commands/typecmds.h b/src/include/commands/typecmds.h
index 23130895af..989914dfe1 100644
--- a/src/include/commands/typecmds.h
+++ b/src/include/commands/typecmds.h
@@ -29,6 +29,8 @@ extern ObjectAddress DefineRange(CreateRangeStmt *stmt);
 extern ObjectAddress AlterEnum(AlterEnumStmt *stmt);
 extern ObjectAddress DefineCompositeType(RangeVar *typevar, List *coldeflist);
 extern Oid	AssignTypeArrayOid(void);
+extern Oid	AssignTypeMultirangeOid(void);
+extern Oid	AssignTypeMultirangeArrayOid(void);
 
 extern ObjectAddress AlterDomainDefault(List *names, Node *defaultRaw);
 extern ObjectAddress AlterDomainNotNull(List *names, bool notNull);
diff --git a/src/include/utils/lsyscache.h b/src/include/utils/lsyscache.h
index c9c68e2f4f..d940d367f1 100644
--- a/src/include/utils/lsyscache.h
+++ b/src/include/utils/lsyscache.h
@@ -156,6 +156,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);
@@ -182,6 +183,8 @@ 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_collation(Oid rangeOid);
+extern Oid	get_range_multirange(Oid rangeOid);
+extern Oid	get_range_multirange_subtype(Oid multirangeOid);
 extern Oid	get_index_column_opclass(Oid index_oid, int attno);
 extern bool	get_index_isreplident(Oid index_oid);
 extern bool get_index_isvalid(Oid index_oid);
diff --git a/src/include/utils/multirangetypes.h b/src/include/utils/multirangetypes.h
new file mode 100644
index 0000000000..469444776c
--- /dev/null
+++ b/src/include/utils/multirangetypes.h
@@ -0,0 +1,103 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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
+{
+	int32		vl_len_;		/* 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);
+extern bool multirange_contains_elem_internal(TypeCacheEntry *typcache, MultirangeType *mr,
+											  Datum elem);
+extern bool multirange_contains_range_internal(TypeCacheEntry *typcache, MultirangeType *mr,
+											   RangeType *r);
+extern bool multirange_contains_multirange_internal(TypeCacheEntry *typcache,
+													MultirangeType *mr1,
+													MultirangeType *mr2);
+extern bool range_overlaps_multirange_internal(TypeCacheEntry *typcache, RangeType *r,
+											   MultirangeType *mr);
+extern bool multirange_overlaps_multirange_internal(TypeCacheEntry *typcache,
+													MultirangeType *mr1,
+													MultirangeType *mr2);
+extern bool range_before_multirange_internal(TypeCacheEntry *typcache, RangeType *r,
+											 MultirangeType *mr);
+extern bool range_after_multirange_internal(TypeCacheEntry *typcache, RangeType *r,
+											MultirangeType *mr);
+extern bool multirange_before_multirange_internal(TypeCacheEntry *typcache,
+												  MultirangeType *mr1,
+												  MultirangeType *mr2);
+extern MultirangeType *multirange_minus_internal(Oid mltrngtypoid,
+												 TypeCacheEntry *rangetyp,
+												 int32 range_count1,
+												 RangeType **ranges1,
+												 int32 range_count2,
+												 RangeType **ranges2);
+extern MultirangeType *multirange_intersect_internal(Oid mltrngtypoid,
+													 TypeCacheEntry *rangetyp,
+													 int32 range_count1,
+													 RangeType **ranges1,
+													 int32 range_count2,
+													 RangeType **ranges2);
+
+/* 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 0cbbf09033..204aee4054 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)
 
@@ -92,13 +94,21 @@ typedef struct
  * prototypes for functions defined in rangetypes.c
  */
 
-extern bool range_contains_elem_internal(TypeCacheEntry *typcache, const RangeType *r, Datum val);
-
 /* internal versions of the above */
+extern int	range_cmp_internal(TypeCacheEntry *typcache, const RangeType *r1,
+							   const RangeType *r2);
+extern uint32 hash_range_internal(TypeCacheEntry *typcache, const RangeType *r);
+extern uint64 hash_range_extended_internal(TypeCacheEntry *typcache, const RangeType *r,
+										   Datum seed);
+extern Datum range_lower_internal(TypeCacheEntry *typcache, const RangeType *r1,
+								  bool *isnull);
+extern Datum range_upper_internal(TypeCacheEntry *typcache, const RangeType *r1,
+								  bool *isnull);
 extern bool range_eq_internal(TypeCacheEntry *typcache, const RangeType *r1,
 							  const RangeType *r2);
 extern bool range_ne_internal(TypeCacheEntry *typcache, const RangeType *r1,
 							  const RangeType *r2);
+extern bool range_contains_elem_internal(TypeCacheEntry *typcache, const RangeType *r, Datum val);
 extern bool range_contains_internal(TypeCacheEntry *typcache, const RangeType *r1,
 									const RangeType *r2);
 extern bool range_contained_by_internal(TypeCacheEntry *typcache, const RangeType *r1,
@@ -115,6 +125,12 @@ extern bool range_overleft_internal(TypeCacheEntry *typcache, const RangeType *r
 									const RangeType *r2);
 extern bool range_overright_internal(TypeCacheEntry *typcache, const RangeType *r1,
 									 const RangeType *r2);
+extern RangeType *range_union_internal(TypeCacheEntry *typcache, RangeType *r1,
+									   RangeType *r2, bool strict);
+extern RangeType *range_minus_internal(TypeCacheEntry *typcache, RangeType *r1,
+									   RangeType *r2);
+extern RangeType *range_intersect_internal(TypeCacheEntry *typcache, const RangeType *r1,
+										   const RangeType *r2);
 
 /* assorted support functions */
 extern TypeCacheEntry *range_get_typcache(FunctionCallInfo fcinfo,
@@ -125,6 +141,7 @@ extern void range_deserialize(TypeCacheEntry *typcache, const RangeType *range,
 							  RangeBound *lower, RangeBound *upper,
 							  bool *empty);
 extern char range_get_flags(const RangeType *range);
+extern bool range_has_flag(const RangeType *range, char flag);
 extern void range_set_contain_empty(RangeType *range);
 extern RangeType *make_range(TypeCacheEntry *typcache, RangeBound *lower,
 							 RangeBound *upper, bool empty);
@@ -132,8 +149,12 @@ extern int range_cmp_bounds(TypeCacheEntry *typcache, const RangeBound *b1,
 							const RangeBound *b2);
 extern int range_cmp_bound_values(TypeCacheEntry *typcache, const RangeBound *b1,
 								  const RangeBound *b2);
-extern bool bounds_adjacent(TypeCacheEntry *typcache, RangeBound bound1,
-							RangeBound bound2);
+extern int range_compare(const void *key1, const void *key2, void *arg);
+extern bool bounds_adjacent(TypeCacheEntry *typcache, const RangeBound bound1,
+							const RangeBound bound2);
 extern RangeType *make_empty_range(TypeCacheEntry *typcache);
+extern bool range_split_internal(TypeCacheEntry *typcache, const RangeType *r1,
+								 const RangeType *r2, RangeType **output1,
+								 RangeType **output2);
 
 #endif							/* RANGETYPES_H */
diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h
index f27b73d76d..e8f393520a 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -79,6 +79,7 @@ enum SysCacheIdentifier
 	PUBLICATIONOID,
 	PUBLICATIONREL,
 	PUBLICATIONRELMAP,
+	RANGEMULTIRANGE,
 	RANGETYPE,
 	RELNAMENSP,
 	RELOID,
diff --git a/src/include/utils/typcache.h b/src/include/utils/typcache.h
index cdd20e56d7..33f332a141 100644
--- a/src/include/utils/typcache.h
+++ b/src/include/utils/typcache.h
@@ -101,6 +101,11 @@ typedef struct TypeCacheEntry
 	FmgrInfo	rng_subdiff_finfo;	/* difference function, if any */
 
 	/*
+	 * Fields computed when TYPCACHE_MULTIRANGE_INFO is required.
+	 */
+	struct TypeCacheEntry *rngtype;	/* multirange's range underlying type */
+
+	/*
 	 * Domain's base type and typmod if it's a domain type.  Zeroes if not
 	 * domain, or if information hasn't been requested.
 	 */
@@ -143,6 +148,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 828ff5a288..1e3ff02289 100644
--- a/src/pl/plpgsql/src/pl_comp.c
+++ b/src/pl/plpgsql/src/pl_comp.c
@@ -513,6 +513,8 @@ do_compile(FunctionCallInfo fcinfo,
 					else if (rettypeid == ANYRANGEOID ||
 							 rettypeid == ANYCOMPATIBLERANGEOID)
 						rettypeid = INT4RANGEOID;
+					else if (rettypeid == ANYMULTIRANGEOID)
+						rettypeid = INT4MULTIRANGEOID;
 					else		/* ANYELEMENT or ANYNONARRAY or ANYCOMPATIBLE */
 						rettypeid = INT4OID;
 					/* XXX what could we use for ANYENUM? */
@@ -2089,6 +2091,7 @@ build_datatype(HeapTuple typeTup, int32 typmod,
 		case TYPTYPE_BASE:
 		case TYPTYPE_ENUM:
 		case TYPTYPE_RANGE:
+		case TYPTYPE_MULTIRANGE:
 			typ->ttype = PLPGSQL_TTYPE_SCALAR;
 			break;
 		case TYPTYPE_COMPOSITE:
@@ -2507,6 +2510,9 @@ plpgsql_resolve_polymorphic_argtypes(int numargs,
 				case ANYCOMPATIBLERANGEOID:
 					argtypes[i] = INT4RANGEOID;
 					break;
+				case ANYMULTIRANGEOID:
+					argtypes[i] = INT4MULTIRANGEOID;
+					break;
 				default:
 					break;
 			}
diff --git a/src/test/regress/expected/dependency.out b/src/test/regress/expected/dependency.out
index b4a11b8aa9..8232795148 100644
--- a/src/test/regress/expected/dependency.out
+++ b/src/test/regress/expected/dependency.out
@@ -140,6 +140,7 @@ owner of sequence deptest_a_seq
 owner of table deptest
 owner of function deptest_func()
 owner of type deptest_enum
+owner of type deptest_multirange
 owner of type deptest_range
 owner of table deptest2
 owner of sequence ss1
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..f8ca946d6a
--- /dev/null
+++ b/src/test/regress/expected/multirangetypes.out
@@ -0,0 +1,2436 @@
+-- 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)
+
+--
+-- test casts, both a built-in range type and a user-defined one:
+--
+select 'empty'::int4range::int4multirange;
+ int4multirange 
+----------------
+ {}
+(1 row)
+
+select int4range(1, 3)::int4multirange;
+ int4range 
+-----------
+ {[1,3)}
+(1 row)
+
+select int4range(1, null)::int4multirange;
+ int4range 
+-----------
+ {[1,)}
+(1 row)
+
+select int4range(null, null)::int4multirange;
+ int4range 
+-----------
+ {(,)}
+(1 row)
+
+select 'empty'::textrange::textmultirange;
+ textmultirange 
+----------------
+ {}
+(1 row)
+
+select textrange('a', 'c')::textmultirange;
+ textrange 
+-----------
+ {[a,c)}
+(1 row)
+
+select textrange('a', null)::textmultirange;
+ textrange 
+-----------
+ {[a,)}
+(1 row)
+
+select textrange(null, null)::textmultirange;
+ textrange 
+-----------
+ {(,)}
+(1 row)
+
+--
+-- create some test data and test the operators
+--
+CREATE TABLE nummultirange_test (nmr NUMMULTIRANGE);
+CREATE INDEX nummultirange_test_btree ON nummultirange_test(nmr);
+INSERT INTO nummultirange_test VALUES('{}');
+INSERT INTO nummultirange_test VALUES('{[,)}');
+INSERT INTO nummultirange_test VALUES('{[3,]}');
+INSERT INTO nummultirange_test VALUES('{[,), [3,]}');
+INSERT INTO nummultirange_test VALUES('{[, 5)}');
+INSERT INTO nummultirange_test VALUES(nummultirange());
+INSERT INTO nummultirange_test VALUES(nummultirange(variadic '{}'::numrange[]));
+INSERT INTO nummultirange_test VALUES(nummultirange(numrange(1.1, 2.2)));
+INSERT INTO nummultirange_test VALUES('{empty}');
+INSERT INTO nummultirange_test VALUES(nummultirange(numrange(1.7, 1.7, '[]'), numrange(1.7, 1.9)));
+INSERT INTO nummultirange_test VALUES(nummultirange(numrange(1.7, 1.7, '[]'), numrange(1.9, 2.1)));
+SELECT nmr, isempty(nmr), lower(nmr), upper(nmr) FROM nummultirange_test ORDER BY nmr;
+          nmr          | isempty | lower | upper 
+-----------------------+---------+-------+-------
+ {}                    | t       |       |      
+ {}                    | t       |       |      
+ {}                    | t       |       |      
+ {}                    | t       |       |      
+ {(,5)}                | f       |       |     5
+ {(,)}                 | f       |       |      
+ {(,)}                 | f       |       |      
+ {[1.1,2.2)}           | f       |   1.1 |   2.2
+ {[1.7,1.7],[1.9,2.1)} | f       |   1.7 |   2.1
+ {[1.7,1.9)}           | f       |   1.7 |   1.9
+ {[3,)}                | f       |     3 |      
+(11 rows)
+
+SELECT nmr, lower_inc(nmr), lower_inf(nmr), upper_inc(nmr), upper_inf(nmr) FROM nummultirange_test ORDER BY nmr;
+          nmr          | lower_inc | lower_inf | upper_inc | upper_inf 
+-----------------------+-----------+-----------+-----------+-----------
+ {}                    | f         | f         | f         | f
+ {}                    | f         | f         | f         | f
+ {}                    | f         | f         | f         | f
+ {}                    | f         | f         | f         | f
+ {(,5)}                | f         | t         | f         | f
+ {(,)}                 | f         | t         | f         | t
+ {(,)}                 | f         | t         | f         | t
+ {[1.1,2.2)}           | t         | f         | f         | f
+ {[1.7,1.7],[1.9,2.1)} | t         | f         | f         | f
+ {[1.7,1.9)}           | t         | f         | f         | f
+ {[3,)}                | t         | f         | f         | t
+(11 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr = '{}';
+ nmr 
+-----
+ {}
+ {}
+ {}
+ {}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr = '{(,5)}';
+  nmr   
+--------
+ {(,5)}
+(1 row)
+
+SELECT * FROM nummultirange_test WHERE nmr = '{[3,)}';
+  nmr   
+--------
+ {[3,)}
+(1 row)
+
+SELECT * FROM nummultirange_test WHERE nmr = '{[1.7,1.7]}';
+ nmr 
+-----
+(0 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr = '{[1.7,1.7],[1.9,2.1)}';
+          nmr          
+-----------------------
+ {[1.7,1.7],[1.9,2.1)}
+(1 row)
+
+SELECT * FROM nummultirange_test WHERE nmr < '{}';
+ nmr 
+-----
+(0 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr < '{[-1000.0, -1000.0]}';
+  nmr   
+--------
+ {}
+ {(,)}
+ {(,)}
+ {(,5)}
+ {}
+ {}
+ {}
+(7 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr < '{[0.0, 1.0]}';
+  nmr   
+--------
+ {}
+ {(,)}
+ {(,)}
+ {(,5)}
+ {}
+ {}
+ {}
+(7 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr < '{[1000.0, 1001.0]}';
+          nmr          
+-----------------------
+ {}
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+ {}
+ {}
+ {[1.1,2.2)}
+ {}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(11 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr <= '{}';
+ nmr 
+-----
+ {}
+ {}
+ {}
+ {}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr <= '{[3,)}';
+          nmr          
+-----------------------
+ {}
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+ {}
+ {}
+ {[1.1,2.2)}
+ {}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(11 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr >= '{}';
+          nmr          
+-----------------------
+ {}
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+ {}
+ {}
+ {[1.1,2.2)}
+ {}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(11 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr >= '{[3,)}';
+  nmr   
+--------
+ {[3,)}
+(1 row)
+
+SELECT * FROM nummultirange_test WHERE nmr > '{}';
+          nmr          
+-----------------------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+ {[1.1,2.2)}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(7 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr > '{[-1000.0, -1000.0]}';
+          nmr          
+-----------------------
+ {[3,)}
+ {[1.1,2.2)}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr > '{[0.0, 1.0]}';
+          nmr          
+-----------------------
+ {[3,)}
+ {[1.1,2.2)}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr > '{[1000.0, 1001.0]}';
+ nmr 
+-----
+(0 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr <> '{}';
+          nmr          
+-----------------------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+ {[1.1,2.2)}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(7 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr <> '{(,5)}';
+          nmr          
+-----------------------
+ {}
+ {(,)}
+ {[3,)}
+ {(,)}
+ {}
+ {}
+ {[1.1,2.2)}
+ {}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(10 rows)
+
+select nummultirange(numrange(2.0, 1.0));
+ERROR:  range lower bound must be less than or equal to range upper bound
+select nummultirange(numrange(5.0, 6.0), numrange(1.0, 2.0));
+     nummultirange     
+-----------------------
+ {[1.0,2.0),[5.0,6.0)}
+(1 row)
+
+-- overlaps
+SELECT * FROM nummultirange_test WHERE range_overlaps_multirange(numrange(4.0, 4.2), nmr);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE numrange(4.0, 4.2) && nmr;
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE multirange_overlaps_range(nmr, numrange(4.0, 4.2));
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr && numrange(4.0, 4.2);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE multirange_overlaps_multirange(nmr, nummultirange(numrange(4.0, 4.2), numrange(6.0, 7.0)));
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr && nummultirange(numrange(4.0, 4.2), numrange(6.0, 7.0));
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr && nummultirange(numrange(6.0, 7.0));
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+(3 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr && nummultirange(numrange(6.0, 7.0), numrange(8.0, 9.0));
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+(3 rows)
+
+-- mr contains x
+SELECT * FROM nummultirange_test WHERE multirange_contains_elem(nmr, 4.0);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr @> 4.0;
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE multirange_contains_range(nmr, numrange(4.0, 4.2));
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr @> numrange(4.0, 4.2);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE multirange_contains_multirange(nmr, '{[4.0,4.2), [6.0, 8.0)}');
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+(3 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr @> '{[4.0,4.2), [6.0, 8.0)}'::nummultirange;
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+(3 rows)
+
+-- x is contained by mr
+SELECT * FROM nummultirange_test WHERE elem_contained_by_multirange(4.0, nmr);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE 4.0 <@ nmr;
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE range_contained_by_multirange(numrange(4.0, 4.2), nmr);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE numrange(4.0, 4.2) <@ nmr;
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE multirange_contained_by_multirange('{[4.0,4.2), [6.0, 8.0)}', nmr);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+(3 rows)
+
+SELECT * FROM nummultirange_test WHERE '{[4.0,4.2), [6.0, 8.0)}'::nummultirange <@ nmr;
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+(3 rows)
+
+-- overlaps
+SELECT 'empty'::numrange && nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT 'empty'::numrange && nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() && 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) && 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() && nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() && nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) && nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) && nummultirange(numrange(1,2), numrange(7,8));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(7,8)) && nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) && nummultirange(numrange(1,2), numrange(3.5,8));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(3.5,8)) && numrange(3,4);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(3.5,8)) && nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+-- contains
+SELECT nummultirange() @> nummultirange();
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange() @> 'empty'::numrange;
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(null,null)) @> numrange(1,2);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(null,null)) @> numrange(null,2);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(null,null)) @> numrange(2,null);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(null,5)) @> numrange(null,3);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(null,5)) @> numrange(null,8);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(5,null)) @> numrange(8,null);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(5,null)) @> numrange(3,null);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,5)) @> numrange(8,9);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,5)) @> numrange(3,9);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,5)) @> numrange(1,4);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,5)) @> numrange(1,5);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(-4,-2), numrange(1,5)) @> numrange(1,5);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,5), numrange(8,9)) @> numrange(1,5);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,5), numrange(8,9)) @> numrange(6,7);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,5), numrange(6,9)) @> numrange(6,7);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[1,5)}'::nummultirange @> '{[1,5)}';
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[-4,-2), [1,5)}'::nummultirange @> '{[1,5)}';
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[1,5), [8,9)}'::nummultirange @> '{[1,5)}';
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[1,5), [8,9)}'::nummultirange @> '{[6,7)}';
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT '{[1,5), [6,9)}'::nummultirange @> '{[6,7)}';
+ ?column? 
+----------
+ t
+(1 row)
+
+-- is contained by
+SELECT nummultirange() <@ nummultirange();
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT 'empty'::numrange <@ nummultirange();
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,2) <@ nummultirange(numrange(null,null));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(null,2) <@ nummultirange(numrange(null,null));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(2,null) <@ nummultirange(numrange(null,null));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(null,3) <@ nummultirange(numrange(null,5));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(null,8) <@ nummultirange(numrange(null,5));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(8,null) <@ nummultirange(numrange(5,null));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(3,null) <@ nummultirange(numrange(5,null));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(8,9) <@ nummultirange(numrange(1,5));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(3,9) <@ nummultirange(numrange(1,5));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(1,4) <@ nummultirange(numrange(1,5));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,5) <@ nummultirange(numrange(1,5));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,5) <@ nummultirange(numrange(-4,-2), numrange(1,5));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,5) <@ nummultirange(numrange(1,5), numrange(8,9));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(6,7) <@ nummultirange(numrange(1,5), numrange(8,9));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(6,7) <@ nummultirange(numrange(1,5), numrange(6,9));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[1,5)}' <@ '{[1,5)}'::nummultirange;
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[1,5)}' <@ '{[-4,-2), [1,5)}'::nummultirange;
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[1,5)}' <@ '{[1,5), [8,9)}'::nummultirange;
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[6,7)}' <@ '{[1,5), [8,9)}'::nummultirange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT '{[6,7)}' <@ '{[1,5), [6,9)}'::nummultirange;
+ ?column? 
+----------
+ t
+(1 row)
+
+-- overleft
+SELECT 'empty'::numrange &< nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT 'empty'::numrange &< nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() &< 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) &< 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() &< nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) &< nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() &< nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(6,7) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(1,2) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,4) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,6) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(3.5,6) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(6,7)) &< numrange(3,4);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) &< numrange(3,4);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,4)) &< numrange(3,4);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,6)) &< numrange(3,4);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3.5,6)) &< numrange(3,4);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(6,7)) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,4)) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,6)) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3.5,6)) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+-- overright
+SELECT nummultirange() &> 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) &> 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT 'empty'::numrange &> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT 'empty'::numrange &> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() &> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() &> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) &> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> numrange(6,7);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> numrange(1,2);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> numrange(1,4);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> numrange(1,6);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> numrange(3.5,6);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(3,4) &> nummultirange(numrange(6,7));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(3,4) &> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(3,4) &> nummultirange(numrange(1,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(3,4) &> nummultirange(numrange(1,6));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(3,4) &> nummultirange(numrange(3.5,6));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(6,7));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(1,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(1,6));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(3.5,6));
+ ?column? 
+----------
+ f
+(1 row)
+
+-- meets
+SELECT 'empty'::numrange -|- nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT 'empty'::numrange -|- nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() -|- 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() -|- nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() -|- nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(1,2) -|- nummultirange(numrange(2,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,2) -|- nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- numrange(2,4);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- numrange(3,4);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- nummultirange(numrange(2,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(5,6)) -|- nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(5,6)) -|- nummultirange(numrange(6,7));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(5,6)) -|- nummultirange(numrange(8,9));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- nummultirange(numrange(2,4), numrange(6,7));
+ ?column? 
+----------
+ t
+(1 row)
+
+-- strictly left
+select 'empty'::numrange << nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(1,2) << nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(1,2) << nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+select numrange(1,2) << nummultirange(numrange(0,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(1,2) << nummultirange(numrange(0,4), numrange(7,8));
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange() << 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange() << numrange(1,2);
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(3,4)) << numrange(3,6);
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(0,2)) << numrange(3,6);
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange(numrange(0,2), numrange(7,8)) << numrange(3,6);
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(-4,-2), numrange(0,2)) << numrange(3,6);
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange() << nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange() << nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(1,2)) << nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(1,2)) << nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(1,2)) << nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange(numrange(1,2)) << nummultirange(numrange(3,4), numrange(7,8));
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange(numrange(1,2), numrange(4,5)) << nummultirange(numrange(3,4), numrange(7,8));
+ ?column? 
+----------
+ f
+(1 row)
+
+-- strictly right
+select nummultirange() >> 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange() >> numrange(1,2);
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(3,4)) >> numrange(1,2);
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange(numrange(0,4)) >> numrange(1,2);
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(0,4), numrange(7,8)) >> numrange(1,2);
+ ?column? 
+----------
+ f
+(1 row)
+
+select 'empty'::numrange >> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(1,2) >> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(3,6) >> nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(3,6) >> nummultirange(numrange(0,2));
+ ?column? 
+----------
+ t
+(1 row)
+
+select numrange(3,6) >> nummultirange(numrange(0,2), numrange(7,8));
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(3,6) >> nummultirange(numrange(-4,-2), numrange(0,2));
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange() >> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(1,2)) >> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange() >> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(1,2)) >> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(3,4)) >> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange(numrange(3,4), numrange(7,8)) >> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange(numrange(3,4), numrange(7,8)) >> nummultirange(numrange(1,2), numrange(4,5));
+ ?column? 
+----------
+ f
+(1 row)
+
+-- union
+SELECT nummultirange() + nummultirange();
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange() + nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) + nummultirange();
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) + nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) + nummultirange(numrange(2,4));
+ ?column? 
+----------
+ {[1,4)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) + nummultirange(numrange(3,4));
+   ?column?    
+---------------
+ {[1,2),[3,4)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) + nummultirange(numrange(2,4));
+ ?column? 
+----------
+ {[1,5)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) + nummultirange(numrange(3,4));
+   ?column?    
+---------------
+ {[1,2),[3,5)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) + nummultirange(numrange(0,9));
+ ?column? 
+----------
+ {[0,9)}
+(1 row)
+
+-- merge
+SELECT range_merge(nummultirange());
+ range_merge 
+-------------
+ empty
+(1 row)
+
+SELECT range_merge(nummultirange(numrange(1,2)));
+ range_merge 
+-------------
+ [1,2)
+(1 row)
+
+SELECT range_merge(nummultirange(numrange(1,2), numrange(7,8)));
+ range_merge 
+-------------
+ [1,8)
+(1 row)
+
+-- minus
+SELECT nummultirange() - nummultirange();
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange() - nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) - nummultirange();
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(3,4)) - nummultirange();
+   ?column?    
+---------------
+ {[1,2),[3,4)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) - nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) - nummultirange(numrange(2,4));
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) - nummultirange(numrange(3,4));
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {[2,4)}
+(1 row)
+
+SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(2,3));
+   ?column?    
+---------------
+ {[1,2),[3,4)}
+(1 row)
+
+SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(0,8));
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(0,2));
+ ?column? 
+----------
+ {[2,4)}
+(1 row)
+
+SELECT nummultirange(numrange(1,8)) - nummultirange(numrange(0,2), numrange(3,4));
+   ?column?    
+---------------
+ {[2,3),[4,8)}
+(1 row)
+
+SELECT nummultirange(numrange(1,8)) - nummultirange(numrange(2,3), numrange(5,null));
+   ?column?    
+---------------
+ {[1,2),[3,5)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(-2,0));
+   ?column?    
+---------------
+ {[1,2),[4,5)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(2,4));
+   ?column?    
+---------------
+ {[1,2),[4,5)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(3,5));
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(0,9));
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange(numrange(1,3), numrange(4,5)) - nummultirange(numrange(2,9));
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(8,9));
+   ?column?    
+---------------
+ {[1,2),[4,5)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(-2,0), numrange(8,9));
+   ?column?    
+---------------
+ {[1,2),[4,5)}
+(1 row)
+
+-- intersection
+SELECT nummultirange() * nummultirange();
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange() * nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) * nummultirange();
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT '{[1,3)}'::nummultirange * '{[1,5)}'::nummultirange;
+ ?column? 
+----------
+ {[1,3)}
+(1 row)
+
+SELECT '{[1,3)}'::nummultirange * '{[0,5)}'::nummultirange;
+ ?column? 
+----------
+ {[1,3)}
+(1 row)
+
+SELECT '{[1,3)}'::nummultirange * '{[0,2)}'::nummultirange;
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT '{[1,3)}'::nummultirange * '{[2,5)}'::nummultirange;
+ ?column? 
+----------
+ {[2,3)}
+(1 row)
+
+SELECT '{[1,4)}'::nummultirange * '{[2,3)}'::nummultirange;
+ ?column? 
+----------
+ {[2,3)}
+(1 row)
+
+SELECT '{[1,4)}'::nummultirange * '{[0,2), [3,5)}'::nummultirange;
+   ?column?    
+---------------
+ {[1,2),[3,4)}
+(1 row)
+
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[0,8), [9,12)}'::nummultirange;
+       ?column?       
+----------------------
+ {[1,4),[7,8),[9,10)}
+(1 row)
+
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[9,12)}'::nummultirange;
+ ?column? 
+----------
+ {[9,10)}
+(1 row)
+
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[-5,-4), [5,6), [9,12)}'::nummultirange;
+ ?column? 
+----------
+ {[9,10)}
+(1 row)
+
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[0,2), [3,8), [9,12)}'::nummultirange;
+          ?column?          
+----------------------------
+ {[1,2),[3,4),[7,8),[9,10)}
+(1 row)
+
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[0,2), [3,8), [9,12)}'::nummultirange;
+          ?column?          
+----------------------------
+ {[1,2),[3,4),[7,8),[9,10)}
+(1 row)
+
+--
+-- range_agg function
+--
+create table reservations ( room_id integer not null, booked_during daterange );
+insert into reservations values
+-- 1: has a meets and a gap
+(1, daterange('2018-07-01', '2018-07-07')),
+(1, daterange('2018-07-07', '2018-07-14')),
+(1, daterange('2018-07-20', '2018-07-22')),
+-- 2: just a single row
+(2, daterange('2018-07-01', '2018-07-03')),
+-- 3: one null range
+(3, NULL),
+-- 4: two null ranges
+(4, NULL),
+(4, NULL),
+-- 5: a null range and a non-null range
+(5, NULL),
+(5, daterange('2018-07-01', '2018-07-03')),
+-- 6: has overlap
+(6, daterange('2018-07-01', '2018-07-07')),
+(6, daterange('2018-07-05', '2018-07-10')),
+-- 7: two ranges that meet: no gap or overlap
+(7, daterange('2018-07-01', '2018-07-07')),
+(7, daterange('2018-07-07', '2018-07-14')),
+-- 8: an empty range
+(8, 'empty'::daterange)
+;
+SELECT   room_id, range_agg(booked_during)
+FROM     reservations
+GROUP BY room_id
+ORDER BY room_id;
+ room_id |                     range_agg                     
+---------+---------------------------------------------------
+       1 | {[07-01-2018,07-14-2018),[07-20-2018,07-22-2018)}
+       2 | {[07-01-2018,07-03-2018)}
+       3 | 
+       4 | 
+       5 | {[07-01-2018,07-03-2018)}
+       6 | {[07-01-2018,07-10-2018)}
+       7 | {[07-01-2018,07-14-2018)}
+       8 | {}
+(8 rows)
+
+-- range_agg on a custom range type too
+SELECT  range_agg(r)
+FROM    (VALUES
+          ('[a,c]'::textrange),
+          ('[b,b]'::textrange),
+          ('[c,f]'::textrange),
+          ('[g,h)'::textrange),
+          ('[h,j)'::textrange)
+        ) t(r);
+   range_agg   
+---------------
+ {[a,f],[g,j)}
+(1 row)
+
+select range_intersect_agg(nmr) from nummultirange_test;
+ range_intersect_agg 
+---------------------
+ {}
+(1 row)
+
+select range_intersect_agg(nmr) from nummultirange_test where false;
+ range_intersect_agg 
+---------------------
+ 
+(1 row)
+
+-- test with just one input:
+select range_intersect_agg(nmr) from (values ('{[1,2]}'::nummultirange)) t(nmr);
+ range_intersect_agg 
+---------------------
+ {[1,2]}
+(1 row)
+
+select range_intersect_agg(nmr) from nummultirange_test where nmr @> 4.0;
+ range_intersect_agg 
+---------------------
+ {[3,5)}
+(1 row)
+
+create table nummultirange_test2(nmr nummultirange);
+create index nummultirange_test2_hash_idx on nummultirange_test2 using hash (nmr);
+INSERT INTO nummultirange_test2 VALUES('{[, 5)}');
+INSERT INTO nummultirange_test2 VALUES(nummultirange(numrange(1.1, 2.2)));
+INSERT INTO nummultirange_test2 VALUES(nummultirange(numrange(1.1, 2.2)));
+INSERT INTO nummultirange_test2 VALUES(nummultirange(numrange(1.1, 2.2,'()')));
+INSERT INTO nummultirange_test2 VALUES('{}');
+select * from nummultirange_test2 where nmr = '{}';
+ nmr 
+-----
+ {}
+(1 row)
+
+select * from nummultirange_test2 where nmr = nummultirange(numrange(1.1, 2.2));
+     nmr     
+-------------
+ {[1.1,2.2)}
+ {[1.1,2.2)}
+(2 rows)
+
+select * from nummultirange_test2 where nmr = nummultirange(numrange(1.1, 2.3));
+ nmr 
+-----
+(0 rows)
+
+set enable_nestloop=t;
+set enable_hashjoin=f;
+set enable_mergejoin=f;
+select * from nummultirange_test natural join nummultirange_test2 order by nmr;
+     nmr     
+-------------
+ {}
+ {}
+ {}
+ {}
+ {(,5)}
+ {[1.1,2.2)}
+ {[1.1,2.2)}
+(7 rows)
+
+set enable_nestloop=f;
+set enable_hashjoin=t;
+set enable_mergejoin=f;
+select * from nummultirange_test natural join nummultirange_test2 order by nmr;
+     nmr     
+-------------
+ {}
+ {}
+ {}
+ {}
+ {(,5)}
+ {[1.1,2.2)}
+ {[1.1,2.2)}
+(7 rows)
+
+set enable_nestloop=f;
+set enable_hashjoin=f;
+set enable_mergejoin=t;
+select * from nummultirange_test natural join nummultirange_test2 order by nmr;
+     nmr     
+-------------
+ {}
+ {}
+ {}
+ {}
+ {(,5)}
+ {[1.1,2.2)}
+ {[1.1,2.2)}
+(7 rows)
+
+set enable_nestloop to default;
+set enable_hashjoin to default;
+set enable_mergejoin to default;
+DROP TABLE nummultirange_test2;
+--
+-- Test user-defined multirange of floats
+--
+select '{[123.001, 5.e9)}'::float8multirange @> 888.882::float8;
+ ?column? 
+----------
+ t
+(1 row)
+
+create table float8multirange_test(f8mr float8multirange, i int);
+insert into float8multirange_test values(float8multirange(float8range(-100.00007, '1.111113e9')), 42);
+select * from float8multirange_test;
+           f8mr            | i  
+---------------------------+----
+ {[-100.00007,1111113000)} | 42
+(1 row)
+
+drop table float8multirange_test;
+--
+-- Test multirange types over domains
+--
+create domain mydomain as int4;
+create type mydomainrange as range(subtype=mydomain);
+select '{[4,50)}'::mydomainmultirange @> 7::mydomain;
+ ?column? 
+----------
+ t
+(1 row)
+
+drop domain mydomain cascade;
+NOTICE:  drop cascades to type mydomainrange
+--
+-- Test domains over multirange types
+--
+create domain restrictedmultirange as int4multirange check (upper(value) < 10);
+select '{[4,5)}'::restrictedmultirange @> 7;
+ ?column? 
+----------
+ f
+(1 row)
+
+select '{[4,50)}'::restrictedmultirange @> 7; -- should fail
+ERROR:  value for domain restrictedmultirange violates check constraint "restrictedmultirange_check"
+drop domain restrictedmultirange;
+--
+-- Test multiple multirange types over the same subtype
+--
+create type textrange1 as range(subtype=text, collation="C");
+create type textrange2 as range(subtype=text, collation="C");
+select textmultirange1(textrange2('a','Z'));  -- should fail
+ERROR:  function textmultirange1(textrange2) does not exist
+LINE 1: select textmultirange1(textrange2('a','Z'));
+               ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+select textmultirange1(textrange1('a','Z')) @> 'b'::text;
+ERROR:  range lower bound must be less than or equal to range upper bound
+select textmultirange2(textrange2('a','z')) @> 'b'::text;
+ ?column? 
+----------
+ t
+(1 row)
+
+drop type textrange1;
+drop type textrange2;
+--
+-- Test polymorphic type system
+--
+create function anyarray_anymultirange_func(a anyarray, r anymultirange)
+  returns anyelement as 'select $1[1] + lower($2);' language sql;
+select anyarray_anymultirange_func(ARRAY[1,2], int4multirange(int4range(10,20)));
+ anyarray_anymultirange_func 
+-----------------------------
+                          11
+(1 row)
+
+-- should fail
+select anyarray_anymultirange_func(ARRAY[1,2], nummultirange(numrange(10,20)));
+ERROR:  function anyarray_anymultirange_func(integer[], nummultirange) does not exist
+LINE 1: select anyarray_anymultirange_func(ARRAY[1,2], nummultirange...
+               ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+drop function anyarray_anymultirange_func(anyarray, anymultirange);
+-- should fail
+create function bogus_func(anyelement)
+  returns anymultirange as 'select int4multirange(int4range(1,10))' language sql;
+ERROR:  cannot determine result data type
+DETAIL:  A result of type anymultirange requires at least one input of type anyrange or anymultirange.
+-- should fail
+create function bogus_func(int)
+  returns anymultirange as 'select int4multirange(int4range(1,10))' language sql;
+ERROR:  cannot determine result data type
+DETAIL:  A result of type anymultirange requires at least one input of type anyrange or anymultirange.
+create function range_add_bounds(anymultirange)
+  returns anyelement as 'select lower($1) + upper($1)' language sql;
+select range_add_bounds(int4multirange(int4range(1, 17)));
+ range_add_bounds 
+------------------
+               18
+(1 row)
+
+select range_add_bounds(nummultirange(numrange(1.0001, 123.123)));
+ range_add_bounds 
+------------------
+         124.1231
+(1 row)
+
+create function multirangetypes_sql(q anymultirange, b anyarray, out c anyelement)
+  as $$ select upper($1) + $2[1] $$
+  language sql;
+select multirangetypes_sql(int4multirange(int4range(1,10)), ARRAY[2,20]);
+ multirangetypes_sql 
+---------------------
+                  12
+(1 row)
+
+select multirangetypes_sql(nummultirange(numrange(1,10)), ARRAY[2,20]);  -- match failure
+ERROR:  function multirangetypes_sql(nummultirange, integer[]) does not exist
+LINE 1: select multirangetypes_sql(nummultirange(numrange(1,10)), AR...
+               ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+create function anycompatiblearray_anycompatiblemultirange_func(a anycompatiblearray, mr anycompatiblemultirange)
+  returns anycompatible as 'select $1[1] + lower($2);' language sql;
+select anycompatiblearray_anycompatiblemultirange_func(ARRAY[1,2], multirange(int4range(10,20)));
+ anycompatiblearray_anycompatiblemultirange_func 
+-------------------------------------------------
+                                              11
+(1 row)
+
+select anycompatiblearray_anycompatiblemultirange_func(ARRAY[1,2], multirange(numrange(10,20)));
+ anycompatiblearray_anycompatiblemultirange_func 
+-------------------------------------------------
+                                              11
+(1 row)
+
+-- should fail
+select anycompatiblearray_anycompatiblemultirange_func(ARRAY[1.1,2], multirange(int4range(10,20)));
+ERROR:  function anycompatiblearray_anycompatiblemultirange_func(numeric[], int4multirange) does not exist
+LINE 1: select anycompatiblearray_anycompatiblemultirange_func(ARRAY...
+               ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+drop function anycompatiblearray_anycompatiblemultirange_func(anycompatiblearray, anycompatiblemultirange);
+create function anycompatiblerange_anycompatiblemultirange_func(r anycompatiblerange, mr anycompatiblemultirange)
+  returns anycompatible as 'select lower($1) + lower($2);' language sql;
+select anycompatiblerange_anycompatiblemultirange_func(int4range(1,2), multirange(int4range(10,20)));
+ anycompatiblerange_anycompatiblemultirange_func 
+-------------------------------------------------
+                                              11
+(1 row)
+
+-- should fail
+select anycompatiblerange_anycompatiblemultirange_func(numrange(1,2), multirange(int4range(10,20)));
+ERROR:  function anycompatiblerange_anycompatiblemultirange_func(numrange, int4multirange) does not exist
+LINE 1: select anycompatiblerange_anycompatiblemultirange_func(numra...
+               ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+drop function anycompatiblerange_anycompatiblemultirange_func(anycompatiblerange, anycompatiblemultirange);
+-- should fail
+create function bogus_func(anycompatible)
+  returns anycompatiblerange as 'select int4range(1,10)' language sql;
+ERROR:  cannot determine result data type
+DETAIL:  A result of type anycompatiblerange requires at least one input of type anycompatiblerange or anycompatiblemultirange.
+--
+-- Arrays of multiranges
+--
+select ARRAY[nummultirange(numrange(1.1, 1.2)), nummultirange(numrange(12.3, 155.5))];
+              array               
+----------------------------------
+ {"{[1.1,1.2)}","{[12.3,155.5)}"}
+(1 row)
+
+create table i8mr_array (f1 int, f2 int8multirange[]);
+insert into i8mr_array values (42, array[int8multirange(int8range(1,10)), int8multirange(int8range(2,20))]);
+select * from i8mr_array;
+ f1 |           f2            
+----+-------------------------
+ 42 | {"{[1,10)}","{[2,20)}"}
+(1 row)
+
+drop table i8mr_array;
+--
+-- Multiranges of arrays
+--
+select arraymultirange(arrayrange(ARRAY[1,2], ARRAY[2,1]));
+   arraymultirange   
+---------------------
+ {["{1,2}","{2,1}")}
+(1 row)
+
+select arraymultirange(arrayrange(ARRAY[2,1], ARRAY[1,2]));  -- fail
+ERROR:  range lower bound must be less than or equal to range upper bound
+select array[1,1] <@ arraymultirange(arrayrange(array[1,2], array[2,1]));
+ ?column? 
+----------
+ f
+(1 row)
+
+select array[1,3] <@ arraymultirange(arrayrange(array[1,2], array[2,1]));
+ ?column? 
+----------
+ t
+(1 row)
+
+--
+-- Ranges of composites
+--
+create type two_ints as (a int, b int);
+create type two_ints_range as range (subtype = two_ints);
+-- with force_parallel_mode on, this exercises tqueue.c's range remapping
+select *, row_to_json(upper(t)) as u from
+  (values (two_ints_multirange(two_ints_range(row(1,2), row(3,4)))),
+          (two_ints_multirange(two_ints_range(row(5,6), row(7,8))))) v(t);
+          t          |       u       
+---------------------+---------------
+ {["(1,2)","(3,4)")} | {"a":3,"b":4}
+ {["(5,6)","(7,8)")} | {"a":7,"b":8}
+(2 rows)
+
+drop type two_ints cascade;
+NOTICE:  drop cascades to type two_ints_range
+--
+-- Check behavior when subtype lacks a hash function
+--
+set enable_sort = off;  -- try to make it pick a hash setop implementation
+select '{(2,5)}'::cashmultirange except select '{(5,6)}'::cashmultirange;
+ cashmultirange  
+-----------------
+ {($2.00,$5.00)}
+(1 row)
+
+reset enable_sort;
+--
+-- OUT/INOUT/TABLE functions
+--
+-- infer anymultirange from anymultirange
+create function mr_outparam_succeed(i anymultirange, out r anymultirange, out t text)
+  as $$ select $1, 'foo'::text $$ language sql;
+select * from mr_outparam_succeed(int4multirange(int4range(1,2)));
+    r    |  t  
+---------+-----
+ {[1,2)} | foo
+(1 row)
+
+-- infer anyarray from anymultirange
+create function mr_outparam_succeed2(i anymultirange, out r anyarray, out t text)
+  as $$ select ARRAY[upper($1)], 'foo'::text $$ language sql;
+select * from mr_outparam_succeed2(int4multirange(int4range(1,2)));
+  r  |  t  
+-----+-----
+ {2} | foo
+(1 row)
+
+-- infer anyrange from anymultirange
+create function mr_outparam_succeed3(i anymultirange, out r anyrange, out t text)
+  as $$ select range_merge($1), 'foo'::text $$ language sql;
+select * from mr_outparam_succeed3(int4multirange(int4range(1,2)));
+   r   |  t  
+-------+-----
+ [1,2) | foo
+(1 row)
+
+-- infer anymultirange from anyrange
+create function mr_outparam_succeed4(i anyrange, out r anymultirange, out t text)
+  as $$ select multirange($1), 'foo'::text $$ language sql;
+select * from mr_outparam_succeed4(int4range(1,2));
+    r    |  t  
+---------+-----
+ {[1,2)} | foo
+(1 row)
+
+-- infer anyelement from anymultirange
+create function mr_inoutparam_succeed(out i anyelement, inout r anymultirange)
+  as $$ select upper($1), $1 $$ language sql;
+select * from mr_inoutparam_succeed(int4multirange(int4range(1,2)));
+ i |    r    
+---+---------
+ 2 | {[1,2)}
+(1 row)
+
+-- infer anyelement+anymultirange from anyelement+anymultirange
+create function mr_table_succeed(i anyelement, r anymultirange) returns table(i anyelement, r anymultirange)
+  as $$ select $1, $2 $$ language sql;
+select * from mr_table_succeed(123, int4multirange(int4range(1,11)));
+  i  |    r     
+-----+----------
+ 123 | {[1,11)}
+(1 row)
+
+-- use anymultirange in plpgsql
+create function mr_polymorphic(i anyrange) returns anymultirange
+  as $$ begin return multirange($1); end; $$ language plpgsql;
+select mr_polymorphic(int4range(1, 4));
+ mr_polymorphic 
+----------------
+ {[1,4)}
+(1 row)
+
+-- should fail
+create function mr_outparam_fail(i anyelement, out r anymultirange, out t text)
+  as $$ select '[1,10]', 'foo' $$ language sql;
+ERROR:  cannot determine result data type
+DETAIL:  A result of type anymultirange requires at least one input of type anyrange or anymultirange.
+--should fail
+create function mr_inoutparam_fail(inout i anyelement, out r anymultirange)
+  as $$ select $1, '[1,10]' $$ language sql;
+ERROR:  cannot determine result data type
+DETAIL:  A result of type anymultirange requires at least one input of type anyrange or anymultirange.
+--should fail
+create function mr_table_fail(i anyelement) returns table(i anyelement, r anymultirange)
+  as $$ select $1, '[1,10]' $$ language sql;
+ERROR:  cannot determine result data type
+DETAIL:  A result of type anymultirange requires at least one input of type anyrange or anymultirange.
diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out
index 8d350ab61b..044ce8835e 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 
@@ -208,6 +215,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 
@@ -228,6 +237,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 
@@ -340,7 +351,8 @@ WHERE p1.prorettype IN
      '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     
 ------+----------------
@@ -355,20 +367,25 @@ ORDER BY 2;
  3532 | enum_recv
 (9 rows)
 
--- anyrange is tighter than the rest, can only resolve from anyrange input
+-- anyrange and anymultirange are tighter than the rest, can only resolve
+-- from each other
 SELECT p1.oid, p1.proname
 FROM pg_proc as p1
-WHERE p1.prorettype = 'anyrange'::regtype
+WHERE p1.prorettype IN ('anyrange'::regtype, 'anymultirange'::regtype)
   AND NOT
-     'anyrange'::regtype = ANY (p1.proargtypes)
+    ('anyrange'::regtype = ANY (p1.proargtypes) OR
+      'anymultirange'::regtype = ANY (p1.proargtypes))
 ORDER BY 2;
  oid  |     proname      
 ------+------------------
+ 8002 | anymultirange_in
  3832 | anyrange_in
+ 8004 | multirange_in
+ 8006 | multirange_recv
  3876 | range_gist_union
  3834 | range_in
  3836 | range_recv
-(4 rows)
+(7 rows)
 
 -- similarly for the anycompatible family
 SELECT p1.oid, p1.proname
@@ -2193,13 +2210,14 @@ ORDER BY 1, 2, 3;
                     | float_ops        | float4_ops       | real
                     | float_ops        | float8_ops       | double precision
                     | jsonb_ops        | jsonb_ops        | jsonb
+                    | multirange_ops   | multirange_ops   | anymultirange
                     | numeric_ops      | numeric_ops      | numeric
                     | range_ops        | range_ops        | anyrange
                     | record_image_ops | record_image_ops | record
                     | record_ops       | record_ops       | record
                     | tsquery_ops      | tsquery_ops      | tsquery
                     | tsvector_ops     | tsvector_ops     | tsvector
-(14 rows)
+(15 rows)
 
 -- **************** pg_index ****************
 -- Look for illegal values in pg_index fields.
diff --git a/src/test/regress/expected/plpgsql.out b/src/test/regress/expected/plpgsql.out
index d0a6b630b8..d55006d8c9 100644
--- a/src/test/regress/expected/plpgsql.out
+++ b/src/test/regress/expected/plpgsql.out
@@ -1811,7 +1811,7 @@ begin
   return array[x + 1, x + 2];
 end$$ language plpgsql;
 ERROR:  cannot determine result data type
-DETAIL:  A result of type anyrange requires at least one input of type anyrange.
+DETAIL:  A result of type anyrange requires at least one input of type anyrange or anymultirange.
 create function f1(x anyrange) returns anyarray as $$
 begin
   return array[lower(x), upper(x)];
@@ -1856,7 +1856,7 @@ begin
   return array[x + 1, x + 2];
 end$$ language plpgsql;
 ERROR:  cannot determine result data type
-DETAIL:  A result of type anycompatiblerange requires at least one input of type anycompatiblerange.
+DETAIL:  A result of type anycompatiblerange requires at least one input of type anycompatiblerange or anycompatiblemultirange.
 create function f1(x anycompatiblerange, y anycompatiblearray) returns anycompatiblerange as $$
 begin
   return x;
diff --git a/src/test/regress/expected/polymorphism.out b/src/test/regress/expected/polymorphism.out
index 1ff40764d9..248c869931 100644
--- a/src/test/regress/expected/polymorphism.out
+++ b/src/test/regress/expected/polymorphism.out
@@ -61,7 +61,7 @@ create function polyf(x anyelement) returns anyrange as $$
   select array[x + 1, x + 2]
 $$ language sql;
 ERROR:  cannot determine result data type
-DETAIL:  A result of type anyrange requires at least one input of type anyrange.
+DETAIL:  A result of type anyrange requires at least one input of type anyrange or anymultirange.
 create function polyf(x anyrange) returns anyarray as $$
   select array[lower(x), upper(x)]
 $$ language sql;
@@ -97,12 +97,27 @@ LINE 1: select polyf(int4range(42, 49), 11, 4.5) as fail;
                ^
 HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
 drop function polyf(x anycompatiblerange, y anycompatible, z anycompatible);
+create function polyf(x anycompatiblemultirange, y anycompatible, z anycompatible) returns anycompatiblearray as $$
+  select array[lower(x), upper(x), y, z]
+$$ language sql;
+select polyf(multirange(int4range(42, 49)), 11, 2::smallint) as int, polyf(multirange(float8range(4.5, 7.8)), 7.8, 11::real) as num;
+     int      |       num        
+--------------+------------------
+ {42,49,11,2} | {4.5,7.8,7.8,11}
+(1 row)
+
+select polyf(multirange(int4range(42, 49)), 11, 4.5) as fail;  -- range type doesn't fit
+ERROR:  function polyf(int4multirange, integer, numeric) does not exist
+LINE 1: select polyf(multirange(int4range(42, 49)), 11, 4.5) as fail...
+               ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+drop function polyf(x anycompatiblemultirange, y anycompatible, z anycompatible);
 -- fail, can't infer type:
 create function polyf(x anycompatible) returns anycompatiblerange as $$
   select array[x + 1, x + 2]
 $$ language sql;
 ERROR:  cannot determine result data type
-DETAIL:  A result of type anycompatiblerange requires at least one input of type anycompatiblerange.
+DETAIL:  A result of type anycompatiblerange requires at least one input of type anycompatiblerange or anycompatiblemultirange.
 create function polyf(x anycompatiblerange, y anycompatiblearray) returns anycompatiblerange as $$
   select x
 $$ language sql;
@@ -113,6 +128,22 @@ select polyf(int4range(42, 49), array[11]) as int, polyf(float8range(4.5, 7.8),
 (1 row)
 
 drop function polyf(x anycompatiblerange, y anycompatiblearray);
+-- fail, can't infer type:
+create function polyf(x anycompatible) returns anycompatiblemultirange as $$
+  select array[x + 1, x + 2]
+$$ language sql;
+ERROR:  cannot determine result data type
+DETAIL:  A result of type anycompatiblemultirange requires at least one input of type anycompatiblerange or anycompatiblemultirange.
+create function polyf(x anycompatiblemultirange, y anycompatiblearray) returns anycompatiblemultirange as $$
+  select x
+$$ language sql;
+select polyf(multirange(int4range(42, 49)), array[11]) as int, polyf(multirange(float8range(4.5, 7.8)), array[7]) as num;
+    int    |     num     
+-----------+-------------
+ {[42,49)} | {[4.5,7.8)}
+(1 row)
+
+drop function polyf(x anycompatiblemultirange, y anycompatiblearray);
 create function polyf(a anyelement, b anyarray,
                       c anycompatible, d anycompatible,
                       OUT x anyarray, OUT y anycompatiblearray)
@@ -231,7 +262,7 @@ CREATE AGGREGATE myaggp01a(*) (SFUNC = stfnp, STYPE = int4[],
 CREATE AGGREGATE myaggp02a(*) (SFUNC = stfnp, STYPE = anyarray,
   FINALFUNC = ffp, INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 --     N    P
 -- should CREATE
 CREATE AGGREGATE myaggp03a(*) (SFUNC = stfp, STYPE = int4[],
@@ -243,11 +274,11 @@ CREATE AGGREGATE myaggp03b(*) (SFUNC = stfp, STYPE = int4[],
 CREATE AGGREGATE myaggp04a(*) (SFUNC = stfp, STYPE = anyarray,
   FINALFUNC = ffp, INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 CREATE AGGREGATE myaggp04b(*) (SFUNC = stfp, STYPE = anyarray,
   INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 --    Case2 (R = P) && ((B = P) || (B = N))
 --    -------------------------------------
 --    S    tf1      B    tf2
@@ -302,13 +333,13 @@ ERROR:  function tfp(integer[], anyelement) does not exist
 CREATE AGGREGATE myaggp13a(BASETYPE = int, SFUNC = tfnp, STYPE = anyarray,
   FINALFUNC = ffp, INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 --    P    N        N    P
 -- should ERROR: tf2p(anyarray, int) not matched by tf2p(int[],anyelement)
 CREATE AGGREGATE myaggp14a(BASETYPE = int, SFUNC = tf2p, STYPE = anyarray,
   FINALFUNC = ffp, INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 --    P    N        P    N
 -- should ERROR: tfnp(anyarray, anyelement) not matched by tfnp(int[],int)
 CREATE AGGREGATE myaggp15a(BASETYPE = anyelement, SFUNC = tfnp,
@@ -324,21 +355,21 @@ ERROR:  function tf2p(anyarray, anyelement) does not exist
 CREATE AGGREGATE myaggp17a(BASETYPE = int, SFUNC = tf1p, STYPE = anyarray,
   FINALFUNC = ffp, INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 CREATE AGGREGATE myaggp17b(BASETYPE = int, SFUNC = tf1p, STYPE = anyarray,
   INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 --    P    P        N    P
 -- should ERROR: tfp(anyarray, int) not matched by tfp(anyarray, anyelement)
 CREATE AGGREGATE myaggp18a(BASETYPE = int, SFUNC = tfp, STYPE = anyarray,
   FINALFUNC = ffp, INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 CREATE AGGREGATE myaggp18b(BASETYPE = int, SFUNC = tfp, STYPE = anyarray,
   INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 --    P    P        P    N
 -- should ERROR: tf1p(anyarray, anyelement) not matched by tf1p(anyarray, int)
 CREATE AGGREGATE myaggp19a(BASETYPE = anyelement, SFUNC = tf1p,
@@ -368,11 +399,11 @@ CREATE AGGREGATE myaggn01b(*) (SFUNC = stfnp, STYPE = int4[],
 CREATE AGGREGATE myaggn02a(*) (SFUNC = stfnp, STYPE = anyarray,
   FINALFUNC = ffnp, INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 CREATE AGGREGATE myaggn02b(*) (SFUNC = stfnp, STYPE = anyarray,
   INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 --     N    P
 -- should CREATE
 CREATE AGGREGATE myaggn03a(*) (SFUNC = stfp, STYPE = int4[],
@@ -382,7 +413,7 @@ CREATE AGGREGATE myaggn03a(*) (SFUNC = stfp, STYPE = int4[],
 CREATE AGGREGATE myaggn04a(*) (SFUNC = stfp, STYPE = anyarray,
   FINALFUNC = ffnp, INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 --    Case4 (R = N) && ((B = P) || (B = N))
 --    -------------------------------------
 --    S    tf1      B    tf2
@@ -436,21 +467,21 @@ ERROR:  function tfp(integer[], anyelement) does not exist
 CREATE AGGREGATE myaggn13a(BASETYPE = int, SFUNC = tfnp, STYPE = anyarray,
   FINALFUNC = ffnp, INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 CREATE AGGREGATE myaggn13b(BASETYPE = int, SFUNC = tfnp, STYPE = anyarray,
   INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 --    P    N        N    P
 -- should ERROR: tf2p(anyarray, int) not matched by tf2p(int[],anyelement)
 CREATE AGGREGATE myaggn14a(BASETYPE = int, SFUNC = tf2p, STYPE = anyarray,
   FINALFUNC = ffnp, INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 CREATE AGGREGATE myaggn14b(BASETYPE = int, SFUNC = tf2p, STYPE = anyarray,
   INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 --    P    N        P    N
 -- should ERROR: tfnp(anyarray, anyelement) not matched by tfnp(int[],int)
 CREATE AGGREGATE myaggn15a(BASETYPE = anyelement, SFUNC = tfnp,
@@ -472,13 +503,13 @@ ERROR:  function tf2p(anyarray, anyelement) does not exist
 CREATE AGGREGATE myaggn17a(BASETYPE = int, SFUNC = tf1p, STYPE = anyarray,
   FINALFUNC = ffnp, INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 --    P    P        N    P
 -- should ERROR: tfp(anyarray, int) not matched by tfp(anyarray, anyelement)
 CREATE AGGREGATE myaggn18a(BASETYPE = int, SFUNC = tfp, STYPE = anyarray,
   FINALFUNC = ffnp, INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 --    P    P        P    N
 -- should ERROR: tf1p(anyarray, anyelement) not matched by tf1p(anyarray, int)
 CREATE AGGREGATE myaggn19a(BASETYPE = anyelement, SFUNC = tf1p,
@@ -1853,7 +1884,59 @@ returns anycompatiblerange as $$
   select $1
 $$ language sql;
 ERROR:  cannot determine result data type
-DETAIL:  A result of type anycompatiblerange requires at least one input of type anycompatiblerange.
+DETAIL:  A result of type anycompatiblerange requires at least one input of type anycompatiblerange or anycompatiblemultirange.
+create function anyctest(anycompatible, anycompatiblemultirange)
+returns anycompatiblemultirange as $$
+  select $2
+$$ language sql;
+select x, pg_typeof(x) from anyctest(11, multirange(int4range(4,7))) x;
+    x    |   pg_typeof    
+---------+----------------
+ {[4,7)} | int4multirange
+(1 row)
+
+select x, pg_typeof(x) from anyctest(11, multirange(numrange(4,7))) x;
+    x    |   pg_typeof   
+---------+---------------
+ {[4,7)} | nummultirange
+(1 row)
+
+select x, pg_typeof(x) from anyctest(11, 12) x;  -- fail
+ERROR:  function anyctest(integer, integer) does not exist
+LINE 1: select x, pg_typeof(x) from anyctest(11, 12) x;
+                                    ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+select x, pg_typeof(x) from anyctest(11.2, multirange(int4range(4,7))) x;  -- fail
+ERROR:  function anyctest(numeric, int4multirange) does not exist
+LINE 1: select x, pg_typeof(x) from anyctest(11.2, multirange(int4ra...
+                                    ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+select x, pg_typeof(x) from anyctest(11.2, '{[4,7)}') x;  -- fail
+ERROR:  could not identify anycompatiblemultirange type
+drop function anyctest(anycompatible, anycompatiblemultirange);
+create function anyctest(anycompatiblemultirange, anycompatiblemultirange)
+returns anycompatible as $$
+  select lower($1) + upper($2)
+$$ language sql;
+select x, pg_typeof(x) from anyctest(multirange(int4range(11,12)), multirange(int4range(4,7))) x;
+ x  | pg_typeof 
+----+-----------
+ 18 | integer
+(1 row)
+
+select x, pg_typeof(x) from anyctest(multirange(int4range(11,12)), multirange(numrange(4,7))) x; -- fail
+ERROR:  function anyctest(int4multirange, nummultirange) does not exist
+LINE 1: select x, pg_typeof(x) from anyctest(multirange(int4range(11...
+                                    ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+drop function anyctest(anycompatiblemultirange, anycompatiblemultirange);
+-- fail, can't infer result type:
+create function anyctest(anycompatible)
+returns anycompatiblemultirange as $$
+  select $1
+$$ language sql;
+ERROR:  cannot determine result data type
+DETAIL:  A result of type anycompatiblemultirange requires at least one input of type anycompatiblerange or anycompatiblemultirange.
 create function anyctest(anycompatiblenonarray, anycompatiblenonarray)
 returns anycompatiblearray as $$
   select array[$1, $2]
diff --git a/src/test/regress/expected/rangefuncs.out b/src/test/regress/expected/rangefuncs.out
index 7eced28452..71e2769f44 100644
--- a/src/test/regress/expected/rangefuncs.out
+++ b/src/test/regress/expected/rangefuncs.out
@@ -1556,7 +1556,7 @@ DROP FUNCTION dup(anyelement);
 CREATE FUNCTION bad (f1 int, out f2 anyelement, out f3 anyarray)
 AS 'select $1, array[$1,$1]' LANGUAGE sql;
 ERROR:  cannot determine result data type
-DETAIL:  A result of type anyelement requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyelement requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 CREATE FUNCTION dup (f1 anycompatible, f2 anycompatiblearray, f3 out anycompatible, f4 out anycompatiblearray)
 AS 'select $1, $2' LANGUAGE sql;
 SELECT dup(22, array[44]);
diff --git a/src/test/regress/expected/rangetypes.out b/src/test/regress/expected/rangetypes.out
index c421f5394f..e9f9e1a11b 100644
--- a/src/test/regress/expected/rangetypes.out
+++ b/src/test/regress/expected/rangetypes.out
@@ -518,6 +518,24 @@ select numrange(1.0, 2.0) * numrange(2.5, 3.0);
  empty
 (1 row)
 
+select range_intersect_agg(nr) from numrange_test;
+ range_intersect_agg 
+---------------------
+ empty
+(1 row)
+
+select range_intersect_agg(nr) from numrange_test where false;
+ range_intersect_agg 
+---------------------
+ 
+(1 row)
+
+select range_intersect_agg(nr) from numrange_test where nr @> 4.0;
+ range_intersect_agg 
+---------------------
+ [3,5)
+(1 row)
+
 create table numrange_test2(nr numrange);
 create index numrange_test2_hash_idx on numrange_test2 using hash (nr);
 INSERT INTO numrange_test2 VALUES('[, 5)');
@@ -1371,12 +1389,12 @@ drop function anyarray_anyrange_func(anyarray, anyrange);
 create function bogus_func(anyelement)
   returns anyrange as 'select int4range(1,10)' language sql;
 ERROR:  cannot determine result data type
-DETAIL:  A result of type anyrange requires at least one input of type anyrange.
+DETAIL:  A result of type anyrange requires at least one input of type anyrange or anymultirange.
 -- should fail
 create function bogus_func(int)
   returns anyrange as 'select int4range(1,10)' language sql;
 ERROR:  cannot determine result data type
-DETAIL:  A result of type anyrange requires at least one input of type anyrange.
+DETAIL:  A result of type anyrange requires at least one input of type anyrange or anymultirange.
 create function range_add_bounds(anyrange)
   returns anyelement as 'select lower($1) + upper($1)' language sql;
 select range_add_bounds(int4range(1, 17));
@@ -1430,7 +1448,7 @@ drop function anycompatiblearray_anycompatiblerange_func(anycompatiblearray, any
 create function bogus_func(anycompatible)
   returns anycompatiblerange as 'select int4range(1,10)' language sql;
 ERROR:  cannot determine result data type
-DETAIL:  A result of type anycompatiblerange requires at least one input of type anycompatiblerange.
+DETAIL:  A result of type anycompatiblerange requires at least one input of type anycompatiblerange or anycompatiblemultirange.
 --
 -- Arrays of ranges
 --
@@ -1508,6 +1526,7 @@ reset enable_sort;
 --
 -- OUT/INOUT/TABLE functions
 --
+-- infer anyrange from anyrange
 create function outparam_succeed(i anyrange, out r anyrange, out t text)
   as $$ select $1, 'foo'::text $$ language sql;
 select * from outparam_succeed(int4range(1,2));
@@ -1525,6 +1544,16 @@ select * from outparam2_succeed(int4range(1,11));
  {1,11} | {11,1}
 (1 row)
 
+-- infer anyarray from anyrange
+create function outparam_succeed2(i anyrange, out r anyarray, out t text)
+  as $$ select ARRAY[upper($1)], 'foo'::text $$ language sql;
+select * from outparam_succeed2(int4range(int4range(1,2)));
+  r  |  t  
+-----+-----
+ {2} | foo
+(1 row)
+
+-- infer anyelement from anyrange
 create function inoutparam_succeed(out i anyelement, inout r anyrange)
   as $$ select upper($1), $1 $$ language sql;
 select * from inoutparam_succeed(int4range(1,2));
@@ -1547,14 +1576,14 @@ select * from table_succeed(int4range(1,11));
 create function outparam_fail(i anyelement, out r anyrange, out t text)
   as $$ select '[1,10]', 'foo' $$ language sql;
 ERROR:  cannot determine result data type
-DETAIL:  A result of type anyrange requires at least one input of type anyrange.
+DETAIL:  A result of type anyrange requires at least one input of type anyrange or anymultirange.
 --should fail
 create function inoutparam_fail(inout i anyelement, out r anyrange)
   as $$ select $1, '[1,10]' $$ language sql;
 ERROR:  cannot determine result data type
-DETAIL:  A result of type anyrange requires at least one input of type anyrange.
+DETAIL:  A result of type anyrange requires at least one input of type anyrange or anymultirange.
 --should fail
 create function table_fail(i anyelement) returns table(i anyelement, r anyrange)
   as $$ select $1, '[1,10]' $$ language sql;
 ERROR:  cannot determine result data type
-DETAIL:  A result of type anyrange requires at least one input of type anyrange.
+DETAIL:  A result of type anyrange requires at least one input of type anyrange or anymultirange.
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index 192445878d..d9ce961be2 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -92,6 +92,7 @@ num_exp_sqrt|t
 num_exp_sub|t
 num_input_test|f
 num_result|f
+nummultirange_test|t
 numrange_test|t
 onek|t
 onek2|t
@@ -172,6 +173,7 @@ quad_poly_tbl|t
 radix_text_tbl|t
 ramp|f
 real_city|f
+reservations|f
 road|t
 shighway|t
 slow_emp4000|f
diff --git a/src/test/regress/expected/type_sanity.out b/src/test/regress/expected/type_sanity.out
index 274130e706..8df1ae47e4 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'));
@@ -195,18 +195,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
@@ -240,17 +241,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
@@ -318,18 +320,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
@@ -363,17 +366,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
@@ -631,3 +635,11 @@ WHERE pronargs != 2
 ----------+------------+---------
 (0 rows)
 
+-- every range should have a valid multirange
+SELECT p1.rngtypid, p1.rngsubtype, p1.rngmultitypid
+FROM pg_range p1
+WHERE p1.rngmultitypid IS NULL OR p1.rngmultitypid = 0;
+ rngtypid | rngsubtype | rngmultitypid 
+----------+------------+---------------
+(0 rows)
+
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 95f1925072..b0a02f8f71 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -19,6 +19,7 @@ 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 xid
 
@@ -27,7 +28,7 @@ test: strings numerology point lseg line box path polygon circle date time timet
 # geometry depends on point, lseg, box, path, polygon and circle
 # horology depends on interval, timetz, timestamp, timestamptz
 # ----------
-test: geometry horology regex oidjoins type_sanity opr_sanity misc_sanity comments expressions unicode
+test: geometry horology regex oidjoins type_sanity opr_sanity misc_sanity comments expressions unicode multirangetypes
 
 # ----------
 # These four each depend on the previous one
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index 8ba4136220..4a2a841c22 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -20,6 +20,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..842a7d091a
--- /dev/null
+++ b/src/test/regress/sql/multirangetypes.sql
@@ -0,0 +1,656 @@
+-- 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'));
+
+--
+-- test casts, both a built-in range type and a user-defined one:
+--
+select 'empty'::int4range::int4multirange;
+select int4range(1, 3)::int4multirange;
+select int4range(1, null)::int4multirange;
+select int4range(null, null)::int4multirange;
+select 'empty'::textrange::textmultirange;
+select textrange('a', 'c')::textmultirange;
+select textrange('a', null)::textmultirange;
+select textrange(null, null)::textmultirange;
+
+--
+-- create some test data and test the operators
+--
+
+CREATE TABLE nummultirange_test (nmr NUMMULTIRANGE);
+CREATE INDEX nummultirange_test_btree ON nummultirange_test(nmr);
+
+INSERT INTO nummultirange_test VALUES('{}');
+INSERT INTO nummultirange_test VALUES('{[,)}');
+INSERT INTO nummultirange_test VALUES('{[3,]}');
+INSERT INTO nummultirange_test VALUES('{[,), [3,]}');
+INSERT INTO nummultirange_test VALUES('{[, 5)}');
+INSERT INTO nummultirange_test VALUES(nummultirange());
+INSERT INTO nummultirange_test VALUES(nummultirange(variadic '{}'::numrange[]));
+INSERT INTO nummultirange_test VALUES(nummultirange(numrange(1.1, 2.2)));
+INSERT INTO nummultirange_test VALUES('{empty}');
+INSERT INTO nummultirange_test VALUES(nummultirange(numrange(1.7, 1.7, '[]'), numrange(1.7, 1.9)));
+INSERT INTO nummultirange_test VALUES(nummultirange(numrange(1.7, 1.7, '[]'), numrange(1.9, 2.1)));
+
+SELECT nmr, isempty(nmr), lower(nmr), upper(nmr) FROM nummultirange_test ORDER BY nmr;
+SELECT nmr, lower_inc(nmr), lower_inf(nmr), upper_inc(nmr), upper_inf(nmr) FROM nummultirange_test ORDER BY nmr;
+
+SELECT * FROM nummultirange_test WHERE nmr = '{}';
+SELECT * FROM nummultirange_test WHERE nmr = '{(,5)}';
+SELECT * FROM nummultirange_test WHERE nmr = '{[3,)}';
+SELECT * FROM nummultirange_test WHERE nmr = '{[1.7,1.7]}';
+SELECT * FROM nummultirange_test WHERE nmr = '{[1.7,1.7],[1.9,2.1)}';
+SELECT * FROM nummultirange_test WHERE nmr < '{}';
+SELECT * FROM nummultirange_test WHERE nmr < '{[-1000.0, -1000.0]}';
+SELECT * FROM nummultirange_test WHERE nmr < '{[0.0, 1.0]}';
+SELECT * FROM nummultirange_test WHERE nmr < '{[1000.0, 1001.0]}';
+SELECT * FROM nummultirange_test WHERE nmr <= '{}';
+SELECT * FROM nummultirange_test WHERE nmr <= '{[3,)}';
+SELECT * FROM nummultirange_test WHERE nmr >= '{}';
+SELECT * FROM nummultirange_test WHERE nmr >= '{[3,)}';
+SELECT * FROM nummultirange_test WHERE nmr > '{}';
+SELECT * FROM nummultirange_test WHERE nmr > '{[-1000.0, -1000.0]}';
+SELECT * FROM nummultirange_test WHERE nmr > '{[0.0, 1.0]}';
+SELECT * FROM nummultirange_test WHERE nmr > '{[1000.0, 1001.0]}';
+SELECT * FROM nummultirange_test WHERE nmr <> '{}';
+SELECT * FROM nummultirange_test WHERE nmr <> '{(,5)}';
+
+select nummultirange(numrange(2.0, 1.0));
+select nummultirange(numrange(5.0, 6.0), numrange(1.0, 2.0));
+
+-- overlaps
+SELECT * FROM nummultirange_test WHERE range_overlaps_multirange(numrange(4.0, 4.2), nmr);
+SELECT * FROM nummultirange_test WHERE numrange(4.0, 4.2) && nmr;
+SELECT * FROM nummultirange_test WHERE multirange_overlaps_range(nmr, numrange(4.0, 4.2));
+SELECT * FROM nummultirange_test WHERE nmr && numrange(4.0, 4.2);
+SELECT * FROM nummultirange_test WHERE multirange_overlaps_multirange(nmr, nummultirange(numrange(4.0, 4.2), numrange(6.0, 7.0)));
+SELECT * FROM nummultirange_test WHERE nmr && nummultirange(numrange(4.0, 4.2), numrange(6.0, 7.0));
+SELECT * FROM nummultirange_test WHERE nmr && nummultirange(numrange(6.0, 7.0));
+SELECT * FROM nummultirange_test WHERE nmr && nummultirange(numrange(6.0, 7.0), numrange(8.0, 9.0));
+
+-- mr contains x
+SELECT * FROM nummultirange_test WHERE multirange_contains_elem(nmr, 4.0);
+SELECT * FROM nummultirange_test WHERE nmr @> 4.0;
+SELECT * FROM nummultirange_test WHERE multirange_contains_range(nmr, numrange(4.0, 4.2));
+SELECT * FROM nummultirange_test WHERE nmr @> numrange(4.0, 4.2);
+SELECT * FROM nummultirange_test WHERE multirange_contains_multirange(nmr, '{[4.0,4.2), [6.0, 8.0)}');
+SELECT * FROM nummultirange_test WHERE nmr @> '{[4.0,4.2), [6.0, 8.0)}'::nummultirange;
+
+-- x is contained by mr
+SELECT * FROM nummultirange_test WHERE elem_contained_by_multirange(4.0, nmr);
+SELECT * FROM nummultirange_test WHERE 4.0 <@ nmr;
+SELECT * FROM nummultirange_test WHERE range_contained_by_multirange(numrange(4.0, 4.2), nmr);
+SELECT * FROM nummultirange_test WHERE numrange(4.0, 4.2) <@ nmr;
+SELECT * FROM nummultirange_test WHERE multirange_contained_by_multirange('{[4.0,4.2), [6.0, 8.0)}', nmr);
+SELECT * FROM nummultirange_test WHERE '{[4.0,4.2), [6.0, 8.0)}'::nummultirange <@ nmr;
+
+-- overlaps
+SELECT 'empty'::numrange && nummultirange();
+SELECT 'empty'::numrange && nummultirange(numrange(1,2));
+SELECT nummultirange() && 'empty'::numrange;
+SELECT nummultirange(numrange(1,2)) && 'empty'::numrange;
+SELECT nummultirange() && nummultirange();
+SELECT nummultirange() && nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) && nummultirange();
+SELECT nummultirange(numrange(3,4)) && nummultirange(numrange(1,2), numrange(7,8));
+SELECT nummultirange(numrange(1,2), numrange(7,8)) && nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(3,4)) && nummultirange(numrange(1,2), numrange(3.5,8));
+SELECT nummultirange(numrange(1,2), numrange(3.5,8)) && numrange(3,4);
+SELECT nummultirange(numrange(1,2), numrange(3.5,8)) && nummultirange(numrange(3,4));
+
+-- contains
+SELECT nummultirange() @> nummultirange();
+SELECT nummultirange() @> 'empty'::numrange;
+SELECT nummultirange(numrange(null,null)) @> numrange(1,2);
+SELECT nummultirange(numrange(null,null)) @> numrange(null,2);
+SELECT nummultirange(numrange(null,null)) @> numrange(2,null);
+SELECT nummultirange(numrange(null,5)) @> numrange(null,3);
+SELECT nummultirange(numrange(null,5)) @> numrange(null,8);
+SELECT nummultirange(numrange(5,null)) @> numrange(8,null);
+SELECT nummultirange(numrange(5,null)) @> numrange(3,null);
+SELECT nummultirange(numrange(1,5)) @> numrange(8,9);
+SELECT nummultirange(numrange(1,5)) @> numrange(3,9);
+SELECT nummultirange(numrange(1,5)) @> numrange(1,4);
+SELECT nummultirange(numrange(1,5)) @> numrange(1,5);
+SELECT nummultirange(numrange(-4,-2), numrange(1,5)) @> numrange(1,5);
+SELECT nummultirange(numrange(1,5), numrange(8,9)) @> numrange(1,5);
+SELECT nummultirange(numrange(1,5), numrange(8,9)) @> numrange(6,7);
+SELECT nummultirange(numrange(1,5), numrange(6,9)) @> numrange(6,7);
+SELECT '{[1,5)}'::nummultirange @> '{[1,5)}';
+SELECT '{[-4,-2), [1,5)}'::nummultirange @> '{[1,5)}';
+SELECT '{[1,5), [8,9)}'::nummultirange @> '{[1,5)}';
+SELECT '{[1,5), [8,9)}'::nummultirange @> '{[6,7)}';
+SELECT '{[1,5), [6,9)}'::nummultirange @> '{[6,7)}';
+
+-- is contained by
+SELECT nummultirange() <@ nummultirange();
+SELECT 'empty'::numrange <@ nummultirange();
+SELECT numrange(1,2) <@ nummultirange(numrange(null,null));
+SELECT numrange(null,2) <@ nummultirange(numrange(null,null));
+SELECT numrange(2,null) <@ nummultirange(numrange(null,null));
+SELECT numrange(null,3) <@ nummultirange(numrange(null,5));
+SELECT numrange(null,8) <@ nummultirange(numrange(null,5));
+SELECT numrange(8,null) <@ nummultirange(numrange(5,null));
+SELECT numrange(3,null) <@ nummultirange(numrange(5,null));
+SELECT numrange(8,9) <@ nummultirange(numrange(1,5));
+SELECT numrange(3,9) <@ nummultirange(numrange(1,5));
+SELECT numrange(1,4) <@ nummultirange(numrange(1,5));
+SELECT numrange(1,5) <@ nummultirange(numrange(1,5));
+SELECT numrange(1,5) <@ nummultirange(numrange(-4,-2), numrange(1,5));
+SELECT numrange(1,5) <@ nummultirange(numrange(1,5), numrange(8,9));
+SELECT numrange(6,7) <@ nummultirange(numrange(1,5), numrange(8,9));
+SELECT numrange(6,7) <@ nummultirange(numrange(1,5), numrange(6,9));
+SELECT '{[1,5)}' <@ '{[1,5)}'::nummultirange;
+SELECT '{[1,5)}' <@ '{[-4,-2), [1,5)}'::nummultirange;
+SELECT '{[1,5)}' <@ '{[1,5), [8,9)}'::nummultirange;
+SELECT '{[6,7)}' <@ '{[1,5), [8,9)}'::nummultirange;
+SELECT '{[6,7)}' <@ '{[1,5), [6,9)}'::nummultirange;
+
+-- overleft
+SELECT 'empty'::numrange &< nummultirange();
+SELECT 'empty'::numrange &< nummultirange(numrange(1,2));
+SELECT nummultirange() &< 'empty'::numrange;
+SELECT nummultirange(numrange(1,2)) &< 'empty'::numrange;
+SELECT nummultirange() &< nummultirange();
+SELECT nummultirange(numrange(1,2)) &< nummultirange();
+SELECT nummultirange() &< nummultirange(numrange(1,2));
+SELECT numrange(6,7) &< nummultirange(numrange(3,4));
+SELECT numrange(1,2) &< nummultirange(numrange(3,4));
+SELECT numrange(1,4) &< nummultirange(numrange(3,4));
+SELECT numrange(1,6) &< nummultirange(numrange(3,4));
+SELECT numrange(3.5,6) &< nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(6,7)) &< numrange(3,4);
+SELECT nummultirange(numrange(1,2)) &< numrange(3,4);
+SELECT nummultirange(numrange(1,4)) &< numrange(3,4);
+SELECT nummultirange(numrange(1,6)) &< numrange(3,4);
+SELECT nummultirange(numrange(3.5,6)) &< numrange(3,4);
+SELECT nummultirange(numrange(6,7)) &< nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,2)) &< nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,4)) &< nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,6)) &< nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(3.5,6)) &< nummultirange(numrange(3,4));
+
+-- overright
+SELECT nummultirange() &> 'empty'::numrange;
+SELECT nummultirange(numrange(1,2)) &> 'empty'::numrange;
+SELECT 'empty'::numrange &> nummultirange();
+SELECT 'empty'::numrange &> nummultirange(numrange(1,2));
+SELECT nummultirange() &> nummultirange();
+SELECT nummultirange() &> nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) &> nummultirange();
+SELECT nummultirange(numrange(3,4)) &> numrange(6,7);
+SELECT nummultirange(numrange(3,4)) &> numrange(1,2);
+SELECT nummultirange(numrange(3,4)) &> numrange(1,4);
+SELECT nummultirange(numrange(3,4)) &> numrange(1,6);
+SELECT nummultirange(numrange(3,4)) &> numrange(3.5,6);
+SELECT numrange(3,4) &> nummultirange(numrange(6,7));
+SELECT numrange(3,4) &> nummultirange(numrange(1,2));
+SELECT numrange(3,4) &> nummultirange(numrange(1,4));
+SELECT numrange(3,4) &> nummultirange(numrange(1,6));
+SELECT numrange(3,4) &> nummultirange(numrange(3.5,6));
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(6,7));
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(1,4));
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(1,6));
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(3.5,6));
+
+-- meets
+SELECT 'empty'::numrange -|- nummultirange();
+SELECT 'empty'::numrange -|- nummultirange(numrange(1,2));
+SELECT nummultirange() -|- 'empty'::numrange;
+SELECT nummultirange(numrange(1,2)) -|- 'empty'::numrange;
+SELECT nummultirange() -|- nummultirange();
+SELECT nummultirange(numrange(1,2)) -|- nummultirange();
+SELECT nummultirange() -|- nummultirange(numrange(1,2));
+SELECT numrange(1,2) -|- nummultirange(numrange(2,4));
+SELECT numrange(1,2) -|- nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,2)) -|- numrange(2,4);
+SELECT nummultirange(numrange(1,2)) -|- numrange(3,4);
+SELECT nummultirange(numrange(1,2)) -|- nummultirange(numrange(2,4));
+SELECT nummultirange(numrange(1,2)) -|- nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,2), numrange(5,6)) -|- nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,2), numrange(5,6)) -|- nummultirange(numrange(6,7));
+SELECT nummultirange(numrange(1,2), numrange(5,6)) -|- nummultirange(numrange(8,9));
+SELECT nummultirange(numrange(1,2)) -|- nummultirange(numrange(2,4), numrange(6,7));
+
+-- strictly left
+select 'empty'::numrange << nummultirange();
+select numrange(1,2) << nummultirange();
+select numrange(1,2) << nummultirange(numrange(3,4));
+select numrange(1,2) << nummultirange(numrange(0,4));
+select numrange(1,2) << nummultirange(numrange(0,4), numrange(7,8));
+select nummultirange() << 'empty'::numrange;
+select nummultirange() << numrange(1,2);
+select nummultirange(numrange(3,4)) << numrange(3,6);
+select nummultirange(numrange(0,2)) << numrange(3,6);
+select nummultirange(numrange(0,2), numrange(7,8)) << numrange(3,6);
+select nummultirange(numrange(-4,-2), numrange(0,2)) << numrange(3,6);
+select nummultirange() << nummultirange();
+select nummultirange() << nummultirange(numrange(1,2));
+select nummultirange(numrange(1,2)) << nummultirange();
+select nummultirange(numrange(1,2)) << nummultirange(numrange(1,2));
+select nummultirange(numrange(1,2)) << nummultirange(numrange(3,4));
+select nummultirange(numrange(1,2)) << nummultirange(numrange(3,4), numrange(7,8));
+select nummultirange(numrange(1,2), numrange(4,5)) << nummultirange(numrange(3,4), numrange(7,8));
+
+-- strictly right
+select nummultirange() >> 'empty'::numrange;
+select nummultirange() >> numrange(1,2);
+select nummultirange(numrange(3,4)) >> numrange(1,2);
+select nummultirange(numrange(0,4)) >> numrange(1,2);
+select nummultirange(numrange(0,4), numrange(7,8)) >> numrange(1,2);
+select 'empty'::numrange >> nummultirange();
+select numrange(1,2) >> nummultirange();
+select numrange(3,6) >> nummultirange(numrange(3,4));
+select numrange(3,6) >> nummultirange(numrange(0,2));
+select numrange(3,6) >> nummultirange(numrange(0,2), numrange(7,8));
+select numrange(3,6) >> nummultirange(numrange(-4,-2), numrange(0,2));
+select nummultirange() >> nummultirange();
+select nummultirange(numrange(1,2)) >> nummultirange();
+select nummultirange() >> nummultirange(numrange(1,2));
+select nummultirange(numrange(1,2)) >> nummultirange(numrange(1,2));
+select nummultirange(numrange(3,4)) >> nummultirange(numrange(1,2));
+select nummultirange(numrange(3,4), numrange(7,8)) >> nummultirange(numrange(1,2));
+select nummultirange(numrange(3,4), numrange(7,8)) >> nummultirange(numrange(1,2), numrange(4,5));
+
+-- union
+SELECT nummultirange() + nummultirange();
+SELECT nummultirange() + nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) + nummultirange();
+SELECT nummultirange(numrange(1,2)) + nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) + nummultirange(numrange(2,4));
+SELECT nummultirange(numrange(1,2)) + nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) + nummultirange(numrange(2,4));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) + nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) + nummultirange(numrange(0,9));
+
+-- merge
+SELECT range_merge(nummultirange());
+SELECT range_merge(nummultirange(numrange(1,2)));
+SELECT range_merge(nummultirange(numrange(1,2), numrange(7,8)));
+
+-- minus
+SELECT nummultirange() - nummultirange();
+SELECT nummultirange() - nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) - nummultirange();
+SELECT nummultirange(numrange(1,2), numrange(3,4)) - nummultirange();
+SELECT nummultirange(numrange(1,2)) - nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) - nummultirange(numrange(2,4));
+SELECT nummultirange(numrange(1,2)) - nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(2,3));
+SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(0,8));
+SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(0,2));
+SELECT nummultirange(numrange(1,8)) - nummultirange(numrange(0,2), numrange(3,4));
+SELECT nummultirange(numrange(1,8)) - nummultirange(numrange(2,3), numrange(5,null));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(-2,0));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(2,4));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(3,5));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(0,9));
+SELECT nummultirange(numrange(1,3), numrange(4,5)) - nummultirange(numrange(2,9));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(8,9));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(-2,0), numrange(8,9));
+
+-- intersection
+SELECT nummultirange() * nummultirange();
+SELECT nummultirange() * nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) * nummultirange();
+SELECT '{[1,3)}'::nummultirange * '{[1,5)}'::nummultirange;
+SELECT '{[1,3)}'::nummultirange * '{[0,5)}'::nummultirange;
+SELECT '{[1,3)}'::nummultirange * '{[0,2)}'::nummultirange;
+SELECT '{[1,3)}'::nummultirange * '{[2,5)}'::nummultirange;
+SELECT '{[1,4)}'::nummultirange * '{[2,3)}'::nummultirange;
+SELECT '{[1,4)}'::nummultirange * '{[0,2), [3,5)}'::nummultirange;
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[0,8), [9,12)}'::nummultirange;
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[9,12)}'::nummultirange;
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[-5,-4), [5,6), [9,12)}'::nummultirange;
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[0,2), [3,8), [9,12)}'::nummultirange;
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[0,2), [3,8), [9,12)}'::nummultirange;
+
+--
+-- range_agg function
+--
+create table reservations ( room_id integer not null, booked_during daterange );
+insert into reservations values
+-- 1: has a meets and a gap
+(1, daterange('2018-07-01', '2018-07-07')),
+(1, daterange('2018-07-07', '2018-07-14')),
+(1, daterange('2018-07-20', '2018-07-22')),
+-- 2: just a single row
+(2, daterange('2018-07-01', '2018-07-03')),
+-- 3: one null range
+(3, NULL),
+-- 4: two null ranges
+(4, NULL),
+(4, NULL),
+-- 5: a null range and a non-null range
+(5, NULL),
+(5, daterange('2018-07-01', '2018-07-03')),
+-- 6: has overlap
+(6, daterange('2018-07-01', '2018-07-07')),
+(6, daterange('2018-07-05', '2018-07-10')),
+-- 7: two ranges that meet: no gap or overlap
+(7, daterange('2018-07-01', '2018-07-07')),
+(7, daterange('2018-07-07', '2018-07-14')),
+-- 8: an empty range
+(8, 'empty'::daterange)
+;
+SELECT   room_id, range_agg(booked_during)
+FROM     reservations
+GROUP BY room_id
+ORDER BY room_id;
+
+-- range_agg on a custom range type too
+SELECT  range_agg(r)
+FROM    (VALUES
+          ('[a,c]'::textrange),
+          ('[b,b]'::textrange),
+          ('[c,f]'::textrange),
+          ('[g,h)'::textrange),
+          ('[h,j)'::textrange)
+        ) t(r);
+
+select range_intersect_agg(nmr) from nummultirange_test;
+select range_intersect_agg(nmr) from nummultirange_test where false;
+-- test with just one input:
+select range_intersect_agg(nmr) from (values ('{[1,2]}'::nummultirange)) t(nmr);
+select range_intersect_agg(nmr) from nummultirange_test where nmr @> 4.0;
+
+create table nummultirange_test2(nmr nummultirange);
+create index nummultirange_test2_hash_idx on nummultirange_test2 using hash (nmr);
+
+INSERT INTO nummultirange_test2 VALUES('{[, 5)}');
+INSERT INTO nummultirange_test2 VALUES(nummultirange(numrange(1.1, 2.2)));
+INSERT INTO nummultirange_test2 VALUES(nummultirange(numrange(1.1, 2.2)));
+INSERT INTO nummultirange_test2 VALUES(nummultirange(numrange(1.1, 2.2,'()')));
+INSERT INTO nummultirange_test2 VALUES('{}');
+
+select * from nummultirange_test2 where nmr = '{}';
+select * from nummultirange_test2 where nmr = nummultirange(numrange(1.1, 2.2));
+select * from nummultirange_test2 where nmr = nummultirange(numrange(1.1, 2.3));
+
+set enable_nestloop=t;
+set enable_hashjoin=f;
+set enable_mergejoin=f;
+select * from nummultirange_test natural join nummultirange_test2 order by nmr;
+set enable_nestloop=f;
+set enable_hashjoin=t;
+set enable_mergejoin=f;
+select * from nummultirange_test natural join nummultirange_test2 order by nmr;
+set enable_nestloop=f;
+set enable_hashjoin=f;
+set enable_mergejoin=t;
+select * from nummultirange_test natural join nummultirange_test2 order by nmr;
+
+set enable_nestloop to default;
+set enable_hashjoin to default;
+set enable_mergejoin to default;
+
+DROP TABLE nummultirange_test2;
+
+--
+-- Test user-defined multirange of floats
+--
+
+select '{[123.001, 5.e9)}'::float8multirange @> 888.882::float8;
+create table float8multirange_test(f8mr float8multirange, i int);
+insert into float8multirange_test values(float8multirange(float8range(-100.00007, '1.111113e9')), 42);
+select * from float8multirange_test;
+drop table float8multirange_test;
+
+--
+-- Test multirange types over domains
+--
+
+create domain mydomain as int4;
+create type mydomainrange as range(subtype=mydomain);
+select '{[4,50)}'::mydomainmultirange @> 7::mydomain;
+drop domain mydomain cascade;
+
+--
+-- Test domains over multirange types
+--
+
+create domain restrictedmultirange as int4multirange check (upper(value) < 10);
+select '{[4,5)}'::restrictedmultirange @> 7;
+select '{[4,50)}'::restrictedmultirange @> 7; -- should fail
+drop domain restrictedmultirange;
+
+--
+-- Test multiple multirange types over the same subtype
+--
+
+create type textrange1 as range(subtype=text, collation="C");
+create type textrange2 as range(subtype=text, collation="C");
+
+select textmultirange1(textrange2('a','Z'));  -- should fail
+select textmultirange1(textrange1('a','Z')) @> 'b'::text;
+select textmultirange2(textrange2('a','z')) @> 'b'::text;
+
+drop type textrange1;
+drop type textrange2;
+
+--
+-- Test polymorphic type system
+--
+
+create function anyarray_anymultirange_func(a anyarray, r anymultirange)
+  returns anyelement as 'select $1[1] + lower($2);' language sql;
+
+select anyarray_anymultirange_func(ARRAY[1,2], int4multirange(int4range(10,20)));
+
+-- should fail
+select anyarray_anymultirange_func(ARRAY[1,2], nummultirange(numrange(10,20)));
+
+drop function anyarray_anymultirange_func(anyarray, anymultirange);
+
+-- should fail
+create function bogus_func(anyelement)
+  returns anymultirange as 'select int4multirange(int4range(1,10))' language sql;
+
+-- should fail
+create function bogus_func(int)
+  returns anymultirange as 'select int4multirange(int4range(1,10))' language sql;
+
+create function range_add_bounds(anymultirange)
+  returns anyelement as 'select lower($1) + upper($1)' language sql;
+
+select range_add_bounds(int4multirange(int4range(1, 17)));
+select range_add_bounds(nummultirange(numrange(1.0001, 123.123)));
+
+create function multirangetypes_sql(q anymultirange, b anyarray, out c anyelement)
+  as $$ select upper($1) + $2[1] $$
+  language sql;
+
+select multirangetypes_sql(int4multirange(int4range(1,10)), ARRAY[2,20]);
+select multirangetypes_sql(nummultirange(numrange(1,10)), ARRAY[2,20]);  -- match failure
+
+create function anycompatiblearray_anycompatiblemultirange_func(a anycompatiblearray, mr anycompatiblemultirange)
+  returns anycompatible as 'select $1[1] + lower($2);' language sql;
+
+select anycompatiblearray_anycompatiblemultirange_func(ARRAY[1,2], multirange(int4range(10,20)));
+
+select anycompatiblearray_anycompatiblemultirange_func(ARRAY[1,2], multirange(numrange(10,20)));
+
+-- should fail
+select anycompatiblearray_anycompatiblemultirange_func(ARRAY[1.1,2], multirange(int4range(10,20)));
+
+drop function anycompatiblearray_anycompatiblemultirange_func(anycompatiblearray, anycompatiblemultirange);
+
+create function anycompatiblerange_anycompatiblemultirange_func(r anycompatiblerange, mr anycompatiblemultirange)
+  returns anycompatible as 'select lower($1) + lower($2);' language sql;
+
+select anycompatiblerange_anycompatiblemultirange_func(int4range(1,2), multirange(int4range(10,20)));
+
+-- should fail
+select anycompatiblerange_anycompatiblemultirange_func(numrange(1,2), multirange(int4range(10,20)));
+
+drop function anycompatiblerange_anycompatiblemultirange_func(anycompatiblerange, anycompatiblemultirange);
+
+-- should fail
+create function bogus_func(anycompatible)
+  returns anycompatiblerange as 'select int4range(1,10)' language sql;
+
+--
+-- Arrays of multiranges
+--
+
+select ARRAY[nummultirange(numrange(1.1, 1.2)), nummultirange(numrange(12.3, 155.5))];
+
+create table i8mr_array (f1 int, f2 int8multirange[]);
+insert into i8mr_array values (42, array[int8multirange(int8range(1,10)), int8multirange(int8range(2,20))]);
+select * from i8mr_array;
+drop table i8mr_array;
+
+--
+-- Multiranges of arrays
+--
+
+select arraymultirange(arrayrange(ARRAY[1,2], ARRAY[2,1]));
+select arraymultirange(arrayrange(ARRAY[2,1], ARRAY[1,2]));  -- fail
+
+select array[1,1] <@ arraymultirange(arrayrange(array[1,2], array[2,1]));
+select array[1,3] <@ arraymultirange(arrayrange(array[1,2], array[2,1]));
+
+--
+-- Ranges of composites
+--
+
+create type two_ints as (a int, b int);
+create type two_ints_range as range (subtype = two_ints);
+
+-- with force_parallel_mode on, this exercises tqueue.c's range remapping
+select *, row_to_json(upper(t)) as u from
+  (values (two_ints_multirange(two_ints_range(row(1,2), row(3,4)))),
+          (two_ints_multirange(two_ints_range(row(5,6), row(7,8))))) v(t);
+
+drop type two_ints cascade;
+
+--
+-- Check behavior when subtype lacks a hash function
+--
+
+set enable_sort = off;  -- try to make it pick a hash setop implementation
+
+select '{(2,5)}'::cashmultirange except select '{(5,6)}'::cashmultirange;
+
+reset enable_sort;
+
+--
+-- OUT/INOUT/TABLE functions
+--
+
+-- infer anymultirange from anymultirange
+create function mr_outparam_succeed(i anymultirange, out r anymultirange, out t text)
+  as $$ select $1, 'foo'::text $$ language sql;
+
+select * from mr_outparam_succeed(int4multirange(int4range(1,2)));
+
+-- infer anyarray from anymultirange
+create function mr_outparam_succeed2(i anymultirange, out r anyarray, out t text)
+  as $$ select ARRAY[upper($1)], 'foo'::text $$ language sql;
+
+select * from mr_outparam_succeed2(int4multirange(int4range(1,2)));
+
+-- infer anyrange from anymultirange
+create function mr_outparam_succeed3(i anymultirange, out r anyrange, out t text)
+  as $$ select range_merge($1), 'foo'::text $$ language sql;
+select * from mr_outparam_succeed3(int4multirange(int4range(1,2)));
+
+-- infer anymultirange from anyrange
+create function mr_outparam_succeed4(i anyrange, out r anymultirange, out t text)
+  as $$ select multirange($1), 'foo'::text $$ language sql;
+
+select * from mr_outparam_succeed4(int4range(1,2));
+
+-- infer anyelement from anymultirange
+create function mr_inoutparam_succeed(out i anyelement, inout r anymultirange)
+  as $$ select upper($1), $1 $$ language sql;
+
+select * from mr_inoutparam_succeed(int4multirange(int4range(1,2)));
+
+-- infer anyelement+anymultirange from anyelement+anymultirange
+create function mr_table_succeed(i anyelement, r anymultirange) returns table(i anyelement, r anymultirange)
+  as $$ select $1, $2 $$ language sql;
+
+select * from mr_table_succeed(123, int4multirange(int4range(1,11)));
+
+-- use anymultirange in plpgsql
+create function mr_polymorphic(i anyrange) returns anymultirange
+  as $$ begin return multirange($1); end; $$ language plpgsql;
+select mr_polymorphic(int4range(1, 4));
+
+-- should fail
+create function mr_outparam_fail(i anyelement, out r anymultirange, out t text)
+  as $$ select '[1,10]', 'foo' $$ language sql;
+
+--should fail
+create function mr_inoutparam_fail(inout i anyelement, out r anymultirange)
+  as $$ select $1, '[1,10]' $$ language sql;
+
+--should fail
+create function mr_table_fail(i anyelement) returns table(i anyelement, r anymultirange)
+  as $$ select $1, '[1,10]' $$ language sql;
diff --git a/src/test/regress/sql/opr_sanity.sql b/src/test/regress/sql/opr_sanity.sql
index 9a1ea3d999..69c4859102 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;
 
@@ -279,16 +290,19 @@ WHERE p1.prorettype IN
      '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;
 
--- anyrange is tighter than the rest, can only resolve from anyrange input
+-- anyrange and anymultirange are tighter than the rest, can only resolve
+-- from each other
 
 SELECT p1.oid, p1.proname
 FROM pg_proc as p1
-WHERE p1.prorettype = 'anyrange'::regtype
+WHERE p1.prorettype IN ('anyrange'::regtype, 'anymultirange'::regtype)
   AND NOT
-     'anyrange'::regtype = ANY (p1.proargtypes)
+    ('anyrange'::regtype = ANY (p1.proargtypes) OR
+      'anymultirange'::regtype = ANY (p1.proargtypes))
 ORDER BY 2;
 
 -- similarly for the anycompatible family
diff --git a/src/test/regress/sql/polymorphism.sql b/src/test/regress/sql/polymorphism.sql
index e5222f1f81..ac52c387bb 100644
--- a/src/test/regress/sql/polymorphism.sql
+++ b/src/test/regress/sql/polymorphism.sql
@@ -71,6 +71,16 @@ select polyf(int4range(42, 49), 11, 4.5) as fail;  -- range type doesn't fit
 
 drop function polyf(x anycompatiblerange, y anycompatible, z anycompatible);
 
+create function polyf(x anycompatiblemultirange, y anycompatible, z anycompatible) returns anycompatiblearray as $$
+  select array[lower(x), upper(x), y, z]
+$$ language sql;
+
+select polyf(multirange(int4range(42, 49)), 11, 2::smallint) as int, polyf(multirange(float8range(4.5, 7.8)), 7.8, 11::real) as num;
+
+select polyf(multirange(int4range(42, 49)), 11, 4.5) as fail;  -- range type doesn't fit
+
+drop function polyf(x anycompatiblemultirange, y anycompatible, z anycompatible);
+
 -- fail, can't infer type:
 create function polyf(x anycompatible) returns anycompatiblerange as $$
   select array[x + 1, x + 2]
@@ -84,6 +94,19 @@ select polyf(int4range(42, 49), array[11]) as int, polyf(float8range(4.5, 7.8),
 
 drop function polyf(x anycompatiblerange, y anycompatiblearray);
 
+-- fail, can't infer type:
+create function polyf(x anycompatible) returns anycompatiblemultirange as $$
+  select array[x + 1, x + 2]
+$$ language sql;
+
+create function polyf(x anycompatiblemultirange, y anycompatiblearray) returns anycompatiblemultirange as $$
+  select x
+$$ language sql;
+
+select polyf(multirange(int4range(42, 49)), array[11]) as int, polyf(multirange(float8range(4.5, 7.8)), array[7]) as num;
+
+drop function polyf(x anycompatiblemultirange, y anycompatiblearray);
+
 create function polyf(a anyelement, b anyarray,
                       c anycompatible, d anycompatible,
                       OUT x anyarray, OUT y anycompatiblearray)
@@ -994,6 +1017,35 @@ returns anycompatiblerange as $$
   select $1
 $$ language sql;
 
+create function anyctest(anycompatible, anycompatiblemultirange)
+returns anycompatiblemultirange as $$
+  select $2
+$$ language sql;
+
+select x, pg_typeof(x) from anyctest(11, multirange(int4range(4,7))) x;
+select x, pg_typeof(x) from anyctest(11, multirange(numrange(4,7))) x;
+select x, pg_typeof(x) from anyctest(11, 12) x;  -- fail
+select x, pg_typeof(x) from anyctest(11.2, multirange(int4range(4,7))) x;  -- fail
+select x, pg_typeof(x) from anyctest(11.2, '{[4,7)}') x;  -- fail
+
+drop function anyctest(anycompatible, anycompatiblemultirange);
+
+create function anyctest(anycompatiblemultirange, anycompatiblemultirange)
+returns anycompatible as $$
+  select lower($1) + upper($2)
+$$ language sql;
+
+select x, pg_typeof(x) from anyctest(multirange(int4range(11,12)), multirange(int4range(4,7))) x;
+select x, pg_typeof(x) from anyctest(multirange(int4range(11,12)), multirange(numrange(4,7))) x; -- fail
+
+drop function anyctest(anycompatiblemultirange, anycompatiblemultirange);
+
+-- fail, can't infer result type:
+create function anyctest(anycompatible)
+returns anycompatiblemultirange as $$
+  select $1
+$$ language sql;
+
 create function anyctest(anycompatiblenonarray, anycompatiblenonarray)
 returns anycompatiblearray as $$
   select array[$1, $2]
diff --git a/src/test/regress/sql/rangetypes.sql b/src/test/regress/sql/rangetypes.sql
index 4048b1d185..0276d19136 100644
--- a/src/test/regress/sql/rangetypes.sql
+++ b/src/test/regress/sql/rangetypes.sql
@@ -118,6 +118,10 @@ select numrange(1.0, 2.0) * numrange(2.0, 3.0);
 select numrange(1.0, 2.0) * numrange(1.5, 3.0);
 select numrange(1.0, 2.0) * numrange(2.5, 3.0);
 
+select range_intersect_agg(nr) from numrange_test;
+select range_intersect_agg(nr) from numrange_test where false;
+select range_intersect_agg(nr) from numrange_test where nr @> 4.0;
+
 create table numrange_test2(nr numrange);
 create index numrange_test2_hash_idx on numrange_test2 using hash (nr);
 
@@ -528,6 +532,7 @@ reset enable_sort;
 -- OUT/INOUT/TABLE functions
 --
 
+-- infer anyrange from anyrange
 create function outparam_succeed(i anyrange, out r anyrange, out t text)
   as $$ select $1, 'foo'::text $$ language sql;
 
@@ -539,6 +544,13 @@ create function outparam2_succeed(r anyrange, out lu anyarray, out ul anyarray)
 
 select * from outparam2_succeed(int4range(1,11));
 
+-- infer anyarray from anyrange
+create function outparam_succeed2(i anyrange, out r anyarray, out t text)
+  as $$ select ARRAY[upper($1)], 'foo'::text $$ language sql;
+
+select * from outparam_succeed2(int4range(int4range(1,2)));
+
+-- infer anyelement from anyrange
 create function inoutparam_succeed(out i anyelement, inout r anyrange)
   as $$ select upper($1), $1 $$ language sql;
 
diff --git a/src/test/regress/sql/type_sanity.sql b/src/test/regress/sql/type_sanity.sql
index 4b492ce062..07879d9a57 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'));
@@ -153,7 +153,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')
@@ -182,7 +182,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')
@@ -234,7 +234,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')
@@ -263,7 +263,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')
@@ -467,3 +467,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.rngmultitypid
+FROM pg_range p1
+WHERE p1.rngmultitypid IS NULL OR p1.rngmultitypid = 0;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 525d58e7f0..0b6a0717e9 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1362,6 +1362,9 @@ MultiXactMember
 MultiXactOffset
 MultiXactStateData
 MultiXactStatus
+MultirangeIOData
+MultirangeParseState
+MultirangeType
 MyData
 NDBOX
 NODE
#123Paul A Jungwirth
pj@illuminatedcomputing.com
In reply to: Paul A Jungwirth (#122)
Re: range_agg

On Sat, Apr 11, 2020 at 9:36 AM Paul A Jungwirth
<pj@illuminatedcomputing.com> wrote:

Btw I'm working on typanalyze + selectivity, and it seems like the
test suite doesn't run those things?

Nevermind, I just had to add `analyze numrange_test` to
src/test/regress/sql/rangetypes.sql. :-) Do you want a separate patch
for that? Or maybe it should go in sql/vacuum.sql?

Regards,
Paul

#124Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Paul A Jungwirth (#123)
Re: range_agg

On 2020-Apr-11, Paul A Jungwirth wrote:

On Sat, Apr 11, 2020 at 9:36 AM Paul A Jungwirth
<pj@illuminatedcomputing.com> wrote:

Btw I'm working on typanalyze + selectivity, and it seems like the
test suite doesn't run those things?

Nevermind, I just had to add `analyze numrange_test` to
src/test/regress/sql/rangetypes.sql. :-) Do you want a separate patch
for that? Or maybe it should go in sql/vacuum.sql?

Dunno, it seems fine in rangetypes.sql.

--
�lvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

#125Justin Pryzby
pryzby@telsasoft.com
In reply to: Paul A Jungwirth (#122)
Re: range_agg

On Sat, Apr 11, 2020 at 09:36:37AM -0700, Paul A Jungwirth wrote:

On Fri, Apr 10, 2020 at 8:44 PM Paul A Jungwirth <pj@illuminatedcomputing.com> wrote:

Thanks, and thanks for your v17 also. Here is a patch building on that
and adding support for anycompatiblemultirange.

Here is a v19 that just moved the multirange tests to a new parallel
group to avoid a max-20-tests error. Sorry about that!

This needs to be rebased ; set cfbot to "waiting".

--
Justin

#126Paul A Jungwirth
pj@illuminatedcomputing.com
In reply to: Justin Pryzby (#125)
1 attachment(s)
Re: range_agg

On Sun, Jul 5, 2020 at 10:20 AM Justin Pryzby <pryzby@telsasoft.com> wrote:

This needs to be rebased ; set cfbot to "waiting".

Here is a patch that is rebased onto current master. It also includes
the analyze/selectivity additions.

I haven't made much progress storing on-disk multiranges without the
range type oids. Peter Geoghegan suggested I look at how we handle
arrays in heap_deform_tuple, but I don't see anything there to help me
(probably I misunderstood him though). Just knowing that arrays are
something we do this for is enough to hunt for clues, but if anyone
can point me more directly to code that will help me do it for
multiranges, I'd be appreciative.

Yours,
Paul

Attachments:

v20-multirange.patchapplication/x-patch; name=v20-multirange.patchDownload
diff --git a/doc/src/sgml/datatype.sgml b/doc/src/sgml/datatype.sgml
index 7027758d28..d433baa904 100644
--- a/doc/src/sgml/datatype.sgml
+++ b/doc/src/sgml/datatype.sgml
@@ -4842,6 +4842,10 @@ SELECT * FROM pg_attribute
     <primary>anyrange</primary>
    </indexterm>
 
+   <indexterm zone="datatype-pseudo">
+    <primary>anymultirange</primary>
+   </indexterm>
+
    <indexterm zone="datatype-pseudo">
     <primary>anycompatible</primary>
    </indexterm>
@@ -4858,6 +4862,10 @@ SELECT * FROM pg_attribute
     <primary>anycompatiblerange</primary>
    </indexterm>
 
+   <indexterm zone="datatype-pseudo">
+    <primary>anycompatiblemultirange</primary>
+   </indexterm>
+
    <indexterm zone="datatype-pseudo">
     <primary>void</primary>
    </indexterm>
@@ -4969,6 +4977,13 @@ SELECT * FROM pg_attribute
         <xref linkend="rangetypes"/>).</entry>
        </row>
 
+       <row>
+        <entry><type>anymultirange</type></entry>
+        <entry>Indicates that a function accepts any multirange data type
+        (see <xref linkend="extend-types-polymorphic"/> and
+        <xref linkend="rangetypes"/>).</entry>
+       </row>
+
        <row>
         <entry><type>anycompatible</type></entry>
         <entry>Indicates that a function accepts any data type,
@@ -4998,6 +5013,14 @@ SELECT * FROM pg_attribute
         <xref linkend="rangetypes"/>).</entry>
        </row>
 
+       <row>
+        <entry><type>anycompatiblemultirange</type></entry>
+        <entry>Indicates that a function accepts any multirange data type,
+        with automatic promotion of multiple arguments to a common data type
+        (see <xref linkend="extend-types-polymorphic"/> and
+        <xref linkend="rangetypes"/>).</entry>
+       </row>
+
        <row>
         <entry><type>cstring</type></entry>
         <entry>Indicates that a function accepts or returns a null-terminated C string.</entry>
diff --git a/doc/src/sgml/extend.sgml b/doc/src/sgml/extend.sgml
index c1ffb14571..5b5f1bf68f 100644
--- a/doc/src/sgml/extend.sgml
+++ b/doc/src/sgml/extend.sgml
@@ -288,6 +288,14 @@
         </entry>
        </row>
 
+       <row>
+        <entry><type>anymultirange</type></entry>
+        <entry>Simple</entry>
+        <entry>Indicates that a function accepts any multirange data type
+        (see <xref linkend="rangetypes"/>)
+        </entry>
+       </row>
+
        <row>
         <entry><type>anycompatible</type></entry>
         <entry>Common</entry>
@@ -319,6 +327,14 @@
         with automatic promotion of multiple arguments to a common data type
         </entry>
        </row>
+
+       <row>
+        <entry><type>anycompatiblemultirange</type></entry>
+        <entry>Common</entry>
+        <entry>Indicates that a function accepts any multirange data type,
+        with automatic promotion of multiple arguments to a common data type
+        </entry>
+       </row>
       </tbody>
      </tgroup>
     </table>
@@ -346,17 +362,15 @@
      position declared as <type>anyarray</type> can have any array data type,
      but similarly they must all be the same type.  And similarly,
      positions declared as <type>anyrange</type> must all be the same range
-     type.  Furthermore, if there are
+     type.  Likewise for <type>anymultirange</type>.
+    </para>
+
+    <para>
+     Furthermore, if there are
      positions declared <type>anyarray</type> and others declared
      <type>anyelement</type>, the actual array type in the
      <type>anyarray</type> positions must be an array whose elements are
      the same type appearing in the <type>anyelement</type> positions.
-     Similarly, if there are positions declared <type>anyrange</type>
-     and others declared <type>anyelement</type> or <type>anyarray</type>,
-     the actual range type in the <type>anyrange</type> positions must be a
-     range whose subtype is the same type appearing in
-     the <type>anyelement</type> positions and the same as the element type
-     of the <type>anyarray</type> positions.
      <type>anynonarray</type> is treated exactly the same as <type>anyelement</type>,
      but adds the additional constraint that the actual type must not be
      an array type.
@@ -365,6 +379,19 @@
      be an enum type.
     </para>
 
+    <para>
+     Similarly, if there are positions declared <type>anyrange</type>
+     and others declared <type>anyelement</type> or <type>anyarray</type>,
+     the actual range type in the <type>anyrange</type> positions must be a
+     range whose subtype is the same type appearing in
+     the <type>anyelement</type> positions and the same as the element type
+     of the <type>anyarray</type> positions.
+     If there are positions declared <type>anymultirange</type>,
+     their actual multirange type must contain ranges matching parameters declared
+     <type>anyrange</type> and base elements matching parameters declared
+     <type>anyelement</type> and <type>anyarray</type>.
+    </para>
+
     <para>
      Thus, when more than one argument position is declared with a polymorphic
      type, the net effect is that only certain combinations of actual argument
@@ -420,7 +447,8 @@
      Selection of the common type considers the actual types
      of <type>anycompatible</type> and <type>anycompatiblenonarray</type>
      inputs, the array element types of <type>anycompatiblearray</type>
-     inputs, and the range subtypes of <type>anycompatiblerange</type>
+     inputs, the range subtypes of <type>anycompatiblerange</type> inputs,
+     and the multirange subtypes of <type>anycompatiablemultirange</type>
      inputs.  If <type>anycompatiblenonarray</type> is present then the
      common type is required to be a non-array type.  Once a common type is
      identified, arguments in <type>anycompatible</type>
@@ -431,12 +459,15 @@
 
     <para>
      Since there is no way to select a range type knowing only its subtype,
-     use of <type>anycompatiblerange</type> requires that all arguments
-     declared with that type have the same actual range type, and that that
-     type's subtype agree with the selected common type, so that no casting
-     of the range values is required.  As with <type>anyrange</type>, use
-     of <type>anycompatiblerange</type> as a function result type requires
-     that there be an <type>anycompatiblerange</type> argument.
+     use of <type>anycompatiblerange</type> and/or
+     <type>anycompatiblemultirange</type> requires that all arguments declared
+     with that type have the same actual range and/or multirange type, and that
+     that type's subtype agree with the selected common type, so that no casting
+     of the range values is required.  As with <type>anyrange</type> and
+     <type>anymultirange</type>, use of <type>anycompatiblerange</type> and
+     <type>anymultirange</type> as a function result type requires that there be
+     an <type>anycompatiblerange</type> or <type>anycompatiblemultirange</type>
+     argument.
     </para>
 
     <para>
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index f065856535..46d9836de7 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -17909,12 +17909,15 @@ SELECT NULLIF(value, '(none)') ...
   <para>
    <xref linkend="range-operators-table"/> shows the specialized operators
    available for range types.
+   <xref linkend="multirange-operators-table"/> shows the specialized operators
+   available for multirange types.
    In addition to those, the usual comparison operators shown in
    <xref linkend="functions-comparison-op-table"/> are available for range
-   types.  The comparison operators order first by the range lower bounds, and
-   only if those are equal do they compare the upper bounds.  This does not
-   usually result in a useful overall ordering, but the operators are provided
-   to allow unique indexes to be constructed on ranges.
+   and multirange types.  The comparison operators order first by the range lower
+   bounds, and only if those are equal do they compare the upper bounds.  The
+   multirange operators compare each range until one is unequal. This
+   does not usually result in a useful overall ordering, but the operators are
+   provided to allow unique indexes to be constructed on ranges.
   </para>
 
    <table id="range-operators-table">
@@ -18124,15 +18127,449 @@ SELECT NULLIF(value, '(none)') ...
     </tgroup>
    </table>
 
+   <table id="multirange-operators-table">
+    <title>Multirange Operators</title>
+    <tgroup cols="1">
+     <thead>
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        Operator
+       </para>
+       <para>
+        Description
+       </para>
+       <para>
+        Example(s)
+       </para></entry>
+      </row>
+     </thead>
+
+     <tbody>
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>@&gt;</literal> <type>anymultirange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Does the first multirange contain the second?
+       </para>
+       <para>
+        <literal>'{[2,4)}'::int4multirange @&gt; '{[2,3)}'::int4multirange</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>@&gt;</literal> <type>anyrange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Does the multirange contain the range?
+       </para>
+       <para>
+        <literal>'{[2,4)}'::int4multirange @&gt; int4range(2,3)</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>@&gt;</literal> <type>anyelement</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Does the multirange contain the element?
+       </para>
+       <para>
+        <literal>'{[2011-01-01,2011-03-01)}'::tsmultirange @&gt; '2011-01-10'::timestamp</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>&lt;@</literal> <type>anymultirange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the first multirange contained by the second?
+       </para>
+       <para>
+        <literal>'{[2,4)}'::int4multirange &lt;@ '{[1,7)}'::int4multirange</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>&lt;@</literal> <type>anyrange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the multirange contained by the range?
+       </para>
+       <para>
+        <literal>'{[2,4)}'::int4multirange &lt;@ int4range(1,7)</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anyrange</type> <literal>&lt;@</literal> <type>anymultirange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the range contained by the multirange?
+       </para>
+       <para>
+        <literal>int4range(2,4) &lt;@ '{[1,7)}'::int4multirange</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anyelement</type> <literal>&lt;@</literal> <type>anymultirange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the element contained by the multirange?
+       </para>
+       <para>
+        <literal>42 &lt;@ '{[1,7)}'::int4multirange</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>&amp;&amp;</literal> <type>anymultirange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Do the multiranges overlap, that is, have any elements in common?
+       </para>
+       <para>
+        <literal>'{[3,7)}'::int8multirange &amp;&amp; '{[4,12)}'::int8multirange</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>&amp;&amp;</literal> <type>anyrange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Does the multirange overlap the range?
+       </para>
+       <para>
+        <literal>'{[3,7)}'::int8multirange &amp;&amp; int8range(4,12)</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anyrange</type> <literal>&amp;&amp;</literal> <type>anymultirange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Does the range overlap the multirange?
+       </para>
+       <para>
+        <literal>int8range(3,7) &amp;&amp; '{[4,12)}'::int8multirange</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>&lt;&lt;</literal> <type>anymultirange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the first multirange strictly left of the second?
+       </para>
+       <para>
+        <literal>'{[1,10)}'::int8multirange &lt;&lt; '{[100,110)}'::int8multirange</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>&lt;&lt;</literal> <type>anyrange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the multirange strictly left of the range?
+       </para>
+       <para>
+        <literal>'{[1,10)}'::int8multirange &lt;&lt; int8range(100,110)</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anyrange</type> <literal>&lt;&lt;</literal> <type>anymultirange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the range strictly left of the multirange?
+       </para>
+       <para>
+        <literal>int8range(1,10) &lt;&lt; '{[100,110)}'::int8multirange</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>&gt;&gt;</literal> <type>anymultirange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the first multirange strictly right of the second?
+       </para>
+       <para>
+        <literal>'{[50,60)}'::int8multirange &gt;&gt; '{[20,30)}'::int8multirange</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>&gt;&gt;</literal> <type>anyrange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the multirange strictly right of the range?
+       </para>
+       <para>
+        <literal>'{[50,60)}'::int8multirange &gt;&gt; int8range(20,30)</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anyrange</type> <literal>&gt;&gt;</literal> <type>anymultirange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the range strictly right of the multirange?
+       </para>
+       <para>
+        <literal>int8range(50,60) &gt;&gt; '{[20,30)}'::int8multirange</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>&amp;&lt;</literal> <type>anymultirange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Does the first multirange not extend to the right of the second?
+       </para>
+       <para>
+        <literal>'{[1,20)}'::int8multirange &amp;&lt; '{[18,20)}'::int8multirange</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>&amp;&lt;</literal> <type>anyrange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Does the multirange not extend to the right of the range?
+       </para>
+       <para>
+        <literal>'{[1,20)}'::int8multirange &amp;&lt; int8range(18,20)</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anyrange</type> <literal>&amp;&lt;</literal> <type>anymultirange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Does the range not extend to the right of the multirange?
+       </para>
+       <para>
+        <literal>int8range(1,20) &amp;&lt; '{[18,20)}'::int8multirange</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>&amp;&gt;</literal> <type>anymultirange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Does the first multirange not extend to the left of the second?
+       </para>
+       <para>
+        <literal>'{[7,20)}'::int8multirange &amp;&gt; '{[5,10)}'::int8multirange</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>&amp;&gt;</literal> <type>anyrange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Does the multirange not extend to the left of the range?
+       </para>
+       <para>
+        <literal>'{[7,20)}'::int8multirange &amp;&gt; int8range(5,10)</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anyrange</type> <literal>&amp;&gt;</literal> <type>anymultirange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Does the range not extend to the left of the multirange?
+       </para>
+       <para>
+        <literal>int8range(7,20) &amp;&gt; '{[5,10)}'::int8multirange</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>-|-</literal> <type>anymultirange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Are the multiranges adjacent?
+       </para>
+       <para>
+        <literal>'{[1.1,2.2)}'::nummultirange -|- '{[2.2,3.3)}'::nummultirange</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>-|-</literal> <type>anyrange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the multirange adjacent to the range?
+       </para>
+       <para>
+        <literal>'{[1.1,2.2)}'::nummultirange -|- numrange(2.2,3.3)</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anyrange</type> <literal>-|-</literal> <type>anymultirange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the range adjacent to the multirange?
+       </para>
+       <para>
+        <literal>numrange(1.1,2.2) -|- '{[2.2,3.3)}'::nummultirange</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>+</literal> <type>anymultirange</type>
+        <returnvalue>anymultirange</returnvalue>
+       </para>
+       <para>
+        Computes the union of the multiranges.  The multiranges need not overlap
+        or be adjacent.
+       </para>
+       <para>
+        <literal>'{[5,10)}'::nummultirange + '{[15,20)}'::nummultirange</literal>
+        <returnvalue>{[5,10), [15,20)}</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>*</literal> <type>anymultirange</type>
+        <returnvalue>anymultirange</returnvalue>
+       </para>
+       <para>
+        Computes the intersection of the multiranges.
+       </para>
+       <para>
+        <literal>'{[5,15)}'::int8multirange * '{[10,20)}'::int8multirange</literal>
+        <returnvalue>{[10,15)}</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>-</literal> <type>anymultirange</type>
+        <returnvalue>anymultirange</returnvalue>
+       </para>
+       <para>
+        Computes the difference of the multiranges.
+       </para>
+       <para>
+        <literal>'{[5,20)}'::int8multirange - '{[10,15)}'::int8multirange</literal>
+        <returnvalue>{[5,10), [15,20)}</returnvalue>
+       </para></entry>
+      </row>
+     </tbody>
+    </tgroup>
+   </table>
+
   <para>
    The left-of/right-of/adjacent operators always return false when an empty
-   range is involved; that is, an empty range is not considered to be either
-   before or after any other range.
+   range or multirange is involved; that is, an empty range is not considered to
+   be either before or after any other range.
+  </para>
+
+  <para>
+   Elsewhere empty ranges and multiranges are treated as the additive identity:
+   anything unioned with an empty value is itself. Anything minus an empty
+   value is itself. An empty multirange has exactly the same points as an empty
+   range. Every range contains the empty range. Every multirange contains as many
+   empty ranges as you like.
+  </para>
+
+  <para>
+   The range union and difference operators will fail if the resulting range would
+   need to contain two disjoint sub-ranges, as such a range cannot be
+   represented. There are separate operators for union and difference that take
+   multirange parameters and return a multirange, and they do not fail even if
+   their arguments are disjoint. So if you need a union or difference operation
+   for ranges that may be disjoint, you can avoid errors by first casting your
+   ranges to multiranges.
   </para>
 
   <para>
    <xref linkend="range-functions-table"/> shows the functions
    available for use with range types.
+   <xref linkend="multirange-functions-table"/> shows the functions
+   available for use with multirange types.
   </para>
 
    <table id="range-functions-table">
@@ -18294,10 +18731,185 @@ SELECT NULLIF(value, '(none)') ...
     </tgroup>
    </table>
 
+   <table id="multirange-functions-table">
+    <title>Multirange Functions</title>
+    <tgroup cols="1">
+     <thead>
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        Function
+       </para>
+       <para>
+        Description
+       </para>
+       <para>
+        Example(s)
+       </para></entry>
+      </row>
+     </thead>
+     <tbody>
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>lower</primary>
+        </indexterm>
+        <function>lower</function> ( <type>anymultirange</type> )
+        <returnvalue>anyelement</returnvalue>
+       </para>
+       <para>
+        Extracts the lower bound of the multirange (<literal>NULL</literal> if the
+        multirange is empty or the lower bound is infinite).
+       </para>
+       <para>
+        <literal>lower('{[1.1,2.2)}'::nummultirange)</literal>
+        <returnvalue>1.1</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>upper</primary>
+        </indexterm>
+        <function>upper</function> ( <type>anymultirange</type> )
+        <returnvalue>anyelement</returnvalue>
+       </para>
+       <para>
+        Extracts the upper bound of the multirange (<literal>NULL</literal> if the
+        multirange is empty or the upper bound is infinite).
+       </para>
+       <para>
+        <literal>upper('{[1.1,2.2)}'::nummultirange)</literal>
+        <returnvalue>2.2</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>isempty</primary>
+        </indexterm>
+        <function>isempty</function> ( <type>anymultirange</type> )
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the multirange empty?
+       </para>
+       <para>
+        <literal>isempty('{[1.1,2.2)}'::nummultirange)</literal>
+        <returnvalue>f</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>lower_inc</primary>
+        </indexterm>
+        <function>lower_inc</function> ( <type>anymultirange</type> )
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the multirange's lower bound inclusive?
+       </para>
+       <para>
+        <literal>lower_inc('{[1.1,2.2)}'::nummultirange)</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>upper_inc</primary>
+        </indexterm>
+        <function>upper_inc</function> ( <type>anymultirange</type> )
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the multirange's upper bound inclusive?
+       </para>
+       <para>
+        <literal>upper_inc('{[1.1,2.2)}'::nummultirange)</literal>
+        <returnvalue>f</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>lower_inf</primary>
+        </indexterm>
+        <function>lower_inf</function> ( <type>anymultirange</type> )
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the multirange's lower bound infinite?
+       </para>
+       <para>
+        <literal>lower_inf('{(,)}'::datemultirange)</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>upper_inf</primary>
+        </indexterm>
+        <function>upper_inf</function> ( <type>anymultirange</type> )
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the multirange's upper bound infinite?
+       </para>
+       <para>
+        <literal>upper_inf('{(,)}'::datemultirange)</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>range_merge</primary>
+        </indexterm>
+        <function>range_merge</function> ( <type>anymultirange</type> )
+        <returnvalue>anyrange</returnvalue>
+       </para>
+       <para>
+        Computes the smallest range that includes the entire multirange.
+       </para>
+       <para>
+        <literal>range_merge('{[1,2), [3,4)}'::int4multirange)</literal>
+        <returnvalue>[1,4)</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>multirange</primary>
+        </indexterm>
+        <function>multirange</function> ( <type>anyrange</type> )
+        <returnvalue>anymultirange</returnvalue>
+       </para>
+       <para>
+        Returns a multirange containing just the given range.
+       </para>
+       <para>
+        <literal>multirange('[1,2)'::int4range)</literal>
+        <returnvalue>{[1,2)}</returnvalue>
+       </para></entry>
+      </row>
+     </tbody>
+    </tgroup>
+   </table>
+
   <para>
    The <function>lower_inc</function>, <function>upper_inc</function>,
    <function>lower_inf</function>, and <function>upper_inf</function>
-   functions all return false for an empty range.
+   functions all return false for an empty range or multirange.
   </para>
   </sect1>
 
@@ -18629,6 +19241,36 @@ SELECT NULLIF(value, '(none)') ...
        <entry>Yes</entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>range_agg</primary>
+        </indexterm>
+        <function>range_agg</function> ( <parameter>value</parameter>
+         <type>anyrange</type> )
+        <returnvalue>anymultirange</returnvalue>
+       </para>
+       <para>
+        Computes the union of the non-null input values.
+       </para></entry>
+       <entry>No</entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>range_intersect_agg</primary>
+        </indexterm>
+        <function>range_intersect_agg</function> ( <parameter>value</parameter>
+         <type>anyrange</type> )
+        <returnvalue>anymultirange</returnvalue>
+       </para>
+       <para>
+        Computes the intersection of the non-null input values.
+       </para></entry>
+       <entry>No</entry>
+      </row>
+
       <row>
        <entry role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/doc/src/sgml/rangetypes.sgml b/doc/src/sgml/rangetypes.sgml
index b75fb3a392..c8784bdce3 100644
--- a/doc/src/sgml/rangetypes.sgml
+++ b/doc/src/sgml/rangetypes.sgml
@@ -27,6 +27,13 @@
   ranges from an instrument, and so forth can also be useful.
  </para>
 
+ <para>
+  Every range type has a corresponding multirange type. A multirange is
+  an ordered list of non-continguous, non-empty, non-null ranges. Most
+  range operators also work on multiranges, and they have a few functions
+  of their own.
+ </para>
+
  <sect2 id="rangetypes-builtin">
   <title>Built-in Range Types</title>
 
@@ -232,10 +239,30 @@ SELECT '[4,4]'::int4range;
 SELECT '[4,4)'::int4range;
 </programlisting>
   </para>
+
+  <para>
+   The input for a multirange is curly brackets (<literal>{</literal> and
+   <literal>}</literal>) containing zero or more valid ranges,
+   separated by commas. Whitespace is permitted around the brackets and
+   commas. This is intended to be reminiscent of array syntax, although
+   multiranges are much simpler: they have just one dimension and there is
+   no need to quote their contents. (The bounds of their ranges may be
+   quoted as above however.)
+  </para>
+
+  <para>
+  Examples:
+<programlisting>
+SELECT '{}'::int4multirange;
+SELECT '{[3,7)}'::int4multirange;
+SELECT '{[3,7), [8,9)}'::int4multirange;
+</programlisting>
+  </para>
+
  </sect2>
 
  <sect2 id="rangetypes-construct">
-  <title>Constructing Ranges</title>
+  <title>Constructing Ranges and Multiranges</title>
 
   <para>
    Each range type has a constructor function with the same name as the range
@@ -267,6 +294,19 @@ SELECT int8range(1, 14, '(]');
 
 -- Using NULL for either bound causes the range to be unbounded on that side.
 SELECT numrange(NULL, 2.2);
+</programlisting>
+  </para>
+
+  <para>
+   Each range type also has a multirange constructor with the same name as the
+   multirange type.  The constructor function takes zero or more arguments
+   which are all ranges of the appropriate type.
+   For example:
+
+<programlisting>
+SELECT nummultirange();
+SELECT nummultirange(numrange(1.0, 14.0));
+SELECT nummultirange(numrange(1.0, 14.0), numrange(20.0, 25.0));
 </programlisting>
   </para>
  </sect2>
@@ -341,6 +381,11 @@ SELECT '[1.234, 5.678]'::floatrange;
    function in this example.
   </para>
 
+  <para>
+   When you define your own range you automatically get a corresponding
+   multirange type.
+  </para>
+
   <para>
    Defining your own range type also allows you to specify a different
    subtype B-tree operator class or collation to use, so as to change the sort
diff --git a/src/backend/catalog/pg_range.c b/src/backend/catalog/pg_range.c
index b5bc36c2bd..1217a6ddd0 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_rngmultitypid - 1] = ObjectIdGetDatum(multirangeTypeOid);
 
 	tup = heap_form_tuple(RelationGetDescr(pg_range), values, nulls);
 
@@ -100,6 +102,12 @@ 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 79ffe317dd..423b440d5f 100644
--- a/src/backend/catalog/pg_type.c
+++ b/src/backend/catalog/pg_type.c
@@ -27,6 +27,7 @@
 #include "catalog/pg_proc.h"
 #include "catalog/pg_type.h"
 #include "commands/typecmds.h"
+#include "mb/pg_wchar.h"
 #include "miscadmin.h"
 #include "parser/scansup.h"
 #include "utils/acl.h"
@@ -36,6 +37,9 @@
 #include "utils/rel.h"
 #include "utils/syscache.h"
 
+static char *makeUniqueTypeName(const char *typeName, Oid typeNamespace,
+								bool tryOriginal);
+
 /* Potentially set by pg_upgrade_support functions */
 Oid			binary_upgrade_next_pg_type_oid = InvalidOid;
 
@@ -784,31 +788,10 @@ RenameTypeInternal(Oid typeOid, const char *newTypeName, Oid typeNamespace)
 char *
 makeArrayTypeName(const char *typeName, Oid typeNamespace)
 {
-	char	   *arr = (char *) palloc(NAMEDATALEN);
-	int			namelen = strlen(typeName);
-	int			i;
-
-	/*
-	 * 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;
-	}
+	char	   *arr;
 
-	if (i >= NAMEDATALEN - 1)
+	arr = makeUniqueTypeName(typeName, typeNamespace, false);
+	if (arr == NULL)
 		ereport(ERROR,
 				(errcode(ERRCODE_DUPLICATE_OBJECT),
 				 errmsg("could not form array type name for type \"%s\"",
@@ -879,3 +862,91 @@ moveArrayTypeName(Oid typeOid, const char *typeName, Oid typeNamespace)
 
 	return true;
 }
+
+
+/*
+ * makeMultirangeTypeName
+ *	  - given a range type name, make a multirange type name for it
+ *
+ * caller is responsible for pfreeing the result
+ */
+char *
+makeMultirangeTypeName(const char *rangeTypeName, Oid typeNamespace)
+{
+	char	   *buf;
+	char	   *mrname;
+	char	   *rangestr;
+
+	/*
+	 * 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...
+	 */
+	rangestr = strstr(rangeTypeName, "range");
+	if (rangestr)
+	{
+		char	*prefix = pnstrdup(rangeTypeName, rangestr - rangeTypeName);
+
+		buf = psprintf("%s%s%s", prefix, "multi", rangestr);
+	}
+	else
+		buf = psprintf("%s_multirange", pnstrdup(rangeTypeName, NAMEDATALEN - 12));
+
+	/* clip it at NAMEDATALEN-1 bytes */
+	buf[pg_mbcliplen(buf, strlen(buf), NAMEDATALEN - 1)] = '\0';
+
+	mrname = makeUniqueTypeName(buf, typeNamespace, true);
+	if (mrname == NULL)
+		ereport(ERROR,
+				(errcode(ERRCODE_DUPLICATE_OBJECT),
+				 errmsg("could not form multirange type name for type \"%s\"",
+						rangeTypeName)));
+
+	return mrname;
+}
+
+/*
+ * makeUniqueTypeName
+ *		Generate a unique name for a prospective new type
+ *
+ * Given a typeName, return a new palloc'ed name by preprending underscores
+ * until a non-conflicting name results.
+ *
+ * If tryOriginal, first try with zero underscores.
+ */
+static char *
+makeUniqueTypeName(const char *typeName, Oid typeNamespace, bool tryOriginal)
+{
+	int			i;
+	int			namelen;
+	char		dest[NAMEDATALEN];
+
+	Assert(strlen(typeName) <= NAMEDATALEN - 1);
+
+	if (tryOriginal &&
+		!SearchSysCacheExists2(TYPENAMENSP,
+							   CStringGetDatum(typeName),
+							   ObjectIdGetDatum(typeNamespace)))
+		return pstrdup(typeName);
+
+	/*
+	 * The idea is to prepend underscores as needed until we make a name that
+	 * doesn't collide with anything ...
+	 */
+	namelen = strlen(typeName);
+	for (i = 1; i < NAMEDATALEN - 1; i++)
+	{
+		dest[i - 1] = '_';
+		strlcpy(dest + i, typeName, NAMEDATALEN - i);
+		if (namelen + i >= NAMEDATALEN)
+			truncate_identifier(dest, NAMEDATALEN, false);
+
+		if (!SearchSysCacheExists2(TYPENAMENSP,
+								   CStringGetDatum(dest),
+								   ObjectIdGetDatum(typeNamespace)))
+			return pstrdup(dest);
+	}
+
+	return NULL;
+}
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index 9e5938b10e..cef3507be6 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -105,9 +105,14 @@ typedef struct
 
 /* Potentially set by pg_upgrade_support functions */
 Oid			binary_upgrade_next_array_pg_type_oid = InvalidOid;
+Oid			binary_upgrade_next_mrng_pg_type_oid = InvalidOid;
+Oid			binary_upgrade_next_mrng_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 rangeOid,
+									   Oid rangeArrayOid, Oid *castFuncOid);
 static Oid	findTypeInputFunction(List *procname, Oid typeOid);
 static Oid	findTypeOutputFunction(List *procname, Oid typeOid);
 static Oid	findTypeReceiveFunction(List *procname, Oid typeOid);
@@ -738,7 +743,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",
@@ -1279,6 +1285,11 @@ checkEnumOwner(HeapTuple tup)
 /*
  * DefineRange
  *		Registers a new range type.
+ *
+ * Perhaps it might be worthwhile to set pg_type.typelem to the base type,
+ * and likewise on multiranges to set it to the range type. But having a
+ * non-zero typelem is treated elsewhere as a synonym for being an array,
+ * and users might have queries with that same assumption.
  */
 ObjectAddress
 DefineRange(CreateRangeStmt *stmt)
@@ -1287,7 +1298,11 @@ DefineRange(CreateRangeStmt *stmt)
 	Oid			typeNamespace;
 	Oid			typoid;
 	char	   *rangeArrayName;
+	char	   *multirangeTypeName;
+	char	   *multirangeArrayName;
 	Oid			rangeArrayOid;
+	Oid			multirangeOid;
+	Oid			multirangeArrayOid;
 	Oid			rangeSubtype = InvalidOid;
 	List	   *rangeSubOpclassName = NIL;
 	List	   *rangeCollationName = NIL;
@@ -1304,6 +1319,8 @@ DefineRange(CreateRangeStmt *stmt)
 	AclResult	aclresult;
 	ListCell   *lc;
 	ObjectAddress address;
+	ObjectAddress mltrngaddress;
+	Oid			castFuncOid;
 
 	/* Convert list of names to a name and namespace */
 	typeNamespace = QualifiedNameGetCreationNamespace(stmt->typeName,
@@ -1452,8 +1469,10 @@ DefineRange(CreateRangeStmt *stmt)
 	/* alignment must be TYPALIGN_INT or TYPALIGN_DOUBLE for ranges */
 	alignment = (subtypalign == TYPALIGN_DOUBLE) ? TYPALIGN_DOUBLE : TYPALIGN_INT;
 
-	/* Allocate OID for array type */
+	/* Allocate OID for array type, its multirange, and its multirange array */
 	rangeArrayOid = AssignTypeArrayOid();
+	multirangeOid = AssignTypeMultirangeOid();
+	multirangeArrayOid = AssignTypeMultirangeArrayOid();
 
 	/* Create the pg_type entry */
 	address =
@@ -1491,9 +1510,46 @@ DefineRange(CreateRangeStmt *stmt)
 	Assert(typoid == InvalidOid || typoid == address.objectId);
 	typoid = address.objectId;
 
+	/* Create the multirange that goes with it */
+	multirangeTypeName = makeMultirangeTypeName(typeName, typeNamespace);
+
+	mltrngaddress =
+		TypeCreate(multirangeOid,	/* force assignment of this 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_RANGE,	/* type-category (range 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) */
+	Assert(multirangeOid == mltrngaddress.objectId);
+
 	/* Create the entry in pg_range */
 	RangeCreate(typoid, rangeSubtype, rangeCollation, rangeSubOpclass,
-				rangeCanonical, rangeSubtypeDiff);
+				rangeCanonical, rangeSubtypeDiff, multirangeOid);
 
 	/*
 	 * Create the array type that goes with it.
@@ -1534,8 +1590,53 @@ 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 */
+			   multirangeOid,	/* 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,
+							   multirangeOid, typoid, rangeArrayOid,
+							   &castFuncOid);
+
+	/* Create cast from the range type to its multirange type */
+	CastCreate(typoid, multirangeOid, castFuncOid, 'e', 'f', DEPENDENCY_INTERNAL);
+
+	pfree(multirangeTypeName);
+	pfree(multirangeArrayName);
 
 	return address;
 }
@@ -1613,6 +1714,147 @@ 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.
+ *
+ * Sets castFuncOid to the oid of the new constructor that can be used
+ * to cast from a range to a multirange.
+ */
+static void
+makeMultirangeConstructors(const char *name, Oid namespace,
+						   Oid multirangeOid, Oid rangeOid, Oid rangeArrayOid,
+						   Oid *castFuncOid)
+{
+	ObjectAddress myself,
+				referenced;
+	oidvector  *argtypes;
+	Datum		allParamTypes;
+	ArrayType  *allParameterTypes;
+	Datum		paramModes;
+	ArrayType  *parameterModes;
+
+	referenced.classId = TypeRelationId;
+	referenced.objectId = multirangeOid;
+	referenced.objectSubId = 0;
+
+	/* 0-arg constructor - for empty multiranges */
+	argtypes = buildoidvector(NULL, 0);
+	myself = ProcedureCreate(name,	/* name: same as multirange type */
+							 namespace,
+							 false,	/* replace */
+							 false,	/* returns set */
+							 multirangeOid,	/* return type */
+							 BOOTSTRAP_SUPERUSERID,	/* proowner */
+							 INTERNALlanguageId,	/* language */
+							 F_FMGR_INTERNAL_VALIDATOR,
+							 "multirange_constructor0",	/* prosrc */
+							 NULL,	/* probin */
+							 PROKIND_FUNCTION,
+							 false,	/* security_definer */
+							 false,	/* leakproof */
+							 false, /* isStrict */
+							 PROVOLATILE_IMMUTABLE,	/* volatility */
+							 PROPARALLEL_SAFE,	/* parallel safety */
+							 argtypes,	/* parameterTypes */
+							 PointerGetDatum(NULL),	/* allParameterTypes */
+							 PointerGetDatum(NULL),	/* parameterModes */
+							 PointerGetDatum(NULL),	/* parameterNames */
+							 NIL,	/* parameterDefaults */
+							 PointerGetDatum(NULL),	/* trftypes */
+							 PointerGetDatum(NULL),	/* proconfig */
+							 InvalidOid,	/* prosupport */
+							 1.0,	/* procost */
+							 0.0);	/* prorows */
+	/*
+	 * Make the constructor 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);
+	pfree(argtypes);
+
+	/*
+	 * 1-arg constructor - for casts
+	 *
+	 * In theory we shouldn't need both this and the vararg (n-arg) constructor,
+	 * but having a separate 1-arg function lets us define casts against it.
+	 */
+	argtypes = buildoidvector(&rangeOid, 1);
+	myself = ProcedureCreate(name,	/* name: same as multirange type */
+							 namespace,
+							 false,	/* replace */
+							 false,	/* returns set */
+							 multirangeOid,	/* return type */
+							 BOOTSTRAP_SUPERUSERID,	/* proowner */
+							 INTERNALlanguageId,	/* language */
+							 F_FMGR_INTERNAL_VALIDATOR,
+							 "multirange_constructor1",	/* prosrc */
+							 NULL,	/* probin */
+							 PROKIND_FUNCTION,
+							 false,	/* security_definer */
+							 false,	/* leakproof */
+							 true, /* isStrict */
+							 PROVOLATILE_IMMUTABLE,	/* volatility */
+							 PROPARALLEL_SAFE,	/* parallel safety */
+							 argtypes,	/* parameterTypes */
+							 PointerGetDatum(NULL),	/* allParameterTypes */
+							 PointerGetDatum(NULL),	/* parameterModes */
+							 PointerGetDatum(NULL),	/* parameterNames */
+							 NIL,	/* parameterDefaults */
+							 PointerGetDatum(NULL),	/* trftypes */
+							 PointerGetDatum(NULL),	/* proconfig */
+							 InvalidOid,	/* prosupport */
+							 1.0,	/* procost */
+							 0.0);	/* prorows */
+	/* ditto */
+	recordDependencyOn(&myself, &referenced, DEPENDENCY_INTERNAL);
+	pfree(argtypes);
+	*castFuncOid = myself.objectId;
+
+	/* n-arg constructor - vararg */
+	argtypes = buildoidvector(&rangeArrayOid, 1);
+	allParamTypes = ObjectIdGetDatum(rangeArrayOid);
+	allParameterTypes = construct_array(&allParamTypes,
+										1, OIDOID,
+										sizeof(Oid), true, 'i');
+	paramModes = CharGetDatum(FUNC_PARAM_VARIADIC);
+	parameterModes = construct_array(&paramModes, 1, CHAROID,
+									 1, true, 'c');
+	myself = ProcedureCreate(name,	/* name: same as multirange type */
+							 namespace,
+							 false,	/* replace */
+							 false,	/* returns set */
+							 multirangeOid,	/* return type */
+							 BOOTSTRAP_SUPERUSERID,	/* proowner */
+							 INTERNALlanguageId,	/* language */
+							 F_FMGR_INTERNAL_VALIDATOR,
+							 "multirange_constructor2",	/* prosrc */
+							 NULL,	/* probin */
+							 PROKIND_FUNCTION,
+							 false,	/* security_definer */
+							 false,	/* leakproof */
+							 false, /* isStrict */
+							 PROVOLATILE_IMMUTABLE,	/* volatility */
+							 PROPARALLEL_SAFE,	/* parallel safety */
+							 argtypes,	/* parameterTypes */
+							 PointerGetDatum(allParameterTypes),	/* allParameterTypes */
+							 PointerGetDatum(parameterModes),	/* parameterModes */
+							 PointerGetDatum(NULL),	/* parameterNames */
+							 NIL,	/* parameterDefaults */
+							 PointerGetDatum(NULL),	/* trftypes */
+							 PointerGetDatum(NULL),	/* proconfig */
+							 InvalidOid,	/* prosupport */
+							 1.0,	/* procost */
+							 0.0);	/* prorows */
+	/* ditto */
+	recordDependencyOn(&myself, &referenced, DEPENDENCY_INTERNAL);
+	pfree(argtypes);
+	pfree(allParameterTypes);
+	pfree(parameterModes);
+}
 
 /*
  * Find suitable I/O functions for a type.
@@ -2045,6 +2287,72 @@ AssignTypeArrayOid(void)
 	return type_array_oid;
 }
 
+/*
+ *	AssignTypeMultirangeOid
+ *
+ *	Pre-assign the range type's multirange OID for use in pg_type.oid
+ */
+Oid
+AssignTypeMultirangeOid(void)
+{
+	Oid			type_multirange_oid;
+
+	/* Use binary-upgrade override for pg_type.oid? */
+	if (IsBinaryUpgrade)
+	{
+		if (!OidIsValid(binary_upgrade_next_mrng_pg_type_oid))
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+					 errmsg("pg_type multirange OID value not set when in binary upgrade mode")));
+
+		type_multirange_oid = binary_upgrade_next_mrng_pg_type_oid;
+		binary_upgrade_next_mrng_pg_type_oid = InvalidOid;
+	}
+	else
+	{
+		Relation	pg_type = table_open(TypeRelationId, AccessShareLock);
+
+		type_multirange_oid = GetNewOidWithIndex(pg_type, TypeOidIndexId,
+												 Anum_pg_type_oid);
+		table_close(pg_type, AccessShareLock);
+	}
+
+	return type_multirange_oid;
+}
+
+/*
+ *	AssignTypeMultirangeArrayOid
+ *
+ *	Pre-assign the range type's multirange array OID for use in pg_type.typarray
+ */
+Oid
+AssignTypeMultirangeArrayOid(void)
+{
+	Oid			type_multirange_array_oid;
+
+	/* Use binary-upgrade override for pg_type.oid? */
+	if (IsBinaryUpgrade)
+	{
+		if (!OidIsValid(binary_upgrade_next_mrng_array_pg_type_oid))
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+					 errmsg("pg_type multirange array OID value not set when in binary upgrade mode")));
+
+		type_multirange_array_oid = binary_upgrade_next_mrng_array_pg_type_oid;
+		binary_upgrade_next_mrng_array_pg_type_oid = InvalidOid;
+	}
+	else
+	{
+		Relation	pg_type = table_open(TypeRelationId, AccessShareLock);
+
+		type_multirange_array_oid = GetNewOidWithIndex(pg_type, TypeOidIndexId,
+													   Anum_pg_type_oid);
+		table_close(pg_type, AccessShareLock);
+	}
+
+	return type_multirange_array_oid;
+}
+
 
 /*-------------------------------------------------------------------
  * DefineCompositeType
diff --git a/src/backend/executor/functions.c b/src/backend/executor/functions.c
index f940f48c6d..cdeda14dd9 100644
--- a/src/backend/executor/functions.c
+++ b/src/backend/executor/functions.c
@@ -1694,7 +1694,8 @@ check_sql_fn_retval(List *queryTreeList,
 	if (fn_typtype == TYPTYPE_BASE ||
 		fn_typtype == TYPTYPE_DOMAIN ||
 		fn_typtype == TYPTYPE_ENUM ||
-		fn_typtype == TYPTYPE_RANGE)
+		fn_typtype == TYPTYPE_RANGE ||
+		fn_typtype == TYPTYPE_MULTIRANGE)
 	{
 		/*
 		 * For scalar-type returns, the target list must have exactly one
diff --git a/src/backend/parser/parse_coerce.c b/src/backend/parser/parse_coerce.c
index 1b11cf731c..04cbffd7c2 100644
--- a/src/backend/parser/parse_coerce.c
+++ b/src/backend/parser/parse_coerce.c
@@ -189,19 +189,21 @@ coerce_type(ParseState *pstate, Node *node,
 	if (targetTypeId == ANYARRAYOID ||
 		targetTypeId == ANYENUMOID ||
 		targetTypeId == ANYRANGEOID ||
+		targetTypeId == ANYMULTIRANGEOID ||
 		targetTypeId == ANYCOMPATIBLEARRAYOID ||
-		targetTypeId == ANYCOMPATIBLERANGEOID)
+		targetTypeId == ANYCOMPATIBLERANGEOID ||
+		targetTypeId == ANYCOMPATIBLEMULTIRANGEOID)
 	{
 		/*
 		 * 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 the pseudotype's input
-		 * function, 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
+		 * the pseudotype's input function, 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
@@ -1532,8 +1534,8 @@ coerce_to_common_type(ParseState *pstate, Node *node,
  * 1) All arguments declared ANYELEMENT must have the same datatype.
  * 2) All arguments declared ANYARRAY must have the same datatype,
  *	  which must be a varlena array type.
- * 3) All arguments declared ANYRANGE must have the same datatype,
- *	  which must be a range type.
+ * 3) All arguments declared ANYRANGE or ANYMULTIRANGE must be a range or
+ *	  multirange type, all derived from the same base datatype.
  * 4) If there are arguments of more than one of these polymorphic types,
  *	  the array element type and/or range subtype must be the same as each
  *	  other and the same as the ANYELEMENT type.
@@ -1548,8 +1550,8 @@ coerce_to_common_type(ParseState *pstate, Node *node,
  *	  to a common supertype (chosen as per select_common_type's rules).
  *	  ANYCOMPATIBLENONARRAY works like ANYCOMPATIBLE but also requires the
  *	  common supertype to not be an array.  If there are ANYCOMPATIBLEARRAY
- *	  or ANYCOMPATIBLERANGE arguments, their element types or subtypes are
- *	  included while making the choice of common supertype.
+ *	  or ANYCOMPATIBLERANGE or ANYCOMPATIBLEMULTIRANGE arguments, their element
+ *	  types or subtypes are included while making the choice of common supertype.
  * 8) The resolved type of ANYCOMPATIBLEARRAY arguments will be the array
  *	  type over the common supertype (which might not be the same array type
  *	  as any of the original arrays).
@@ -1557,6 +1559,10 @@ coerce_to_common_type(ParseState *pstate, Node *node,
  *	  (after domain flattening), since we have no preference rule that would
  *	  let us choose one over another.  Furthermore, that range's subtype
  *	  must exactly match the common supertype chosen by rule 7.
+ * 10) All ANYCOMPATIBLEMULTIRANGE arguments must be the exact same multirange
+ *	  type (after domain flattening), since we have no preference rule that would
+ *	  let us choose one over another.  Furthermore, that multirange's range's
+ *	  subtype must exactly match the common supertype chosen by rule 7.
  *
  * Domains over arrays match ANYARRAY, and are immediately flattened to their
  * base type.  (Thus, for example, we will consider it a match if one ANYARRAY
@@ -1565,7 +1571,9 @@ coerce_to_common_type(ParseState *pstate, Node *node,
  * for ANYCOMPATIBLEARRAY and ANYCOMPATIBLENONARRAY.
  *
  * Similarly, domains over ranges match ANYRANGE or ANYCOMPATIBLERANGE,
- * and are immediately flattened to their base type.
+ * and are immediately flattened to their base type, and domains over
+ * multiranges match ANYMULTIRANGE or ANYCOMPATIBLEMULTIRANGE and are immediately
+ * flattened to their base type.
  *
  * Note that domains aren't currently considered to match ANYENUM,
  * even if their base type would match.
@@ -1583,8 +1591,12 @@ check_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			anycompatible_range_typeid = InvalidOid;
 	Oid			anycompatible_range_typelem = InvalidOid;
+	Oid			anycompatible_multirange_typeid = InvalidOid;
+	Oid			anycompatible_multirange_typelem = InvalidOid;
+	Oid			range_typelem = InvalidOid;
 	bool		have_anynonarray = false;
 	bool		have_anyenum = false;
 	bool		have_anycompatible_nonarray = false;
@@ -1633,6 +1645,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;
+		}
 		else if (decl_type == ANYCOMPATIBLEOID ||
 				 decl_type == ANYCOMPATIBLENONARRAYOID)
 		{
@@ -1677,6 +1698,42 @@ check_generic_type_consistency(const Oid *actual_arg_types,
 				anycompatible_actual_types[n_anycompatible_args++] = anycompatible_range_typelem;
 			}
 		}
+		else if (decl_type == ANYCOMPATIBLEMULTIRANGEOID)
+		{
+			if (actual_type == UNKNOWNOID)
+				continue;
+			actual_type = getBaseType(actual_type); /* flatten domains */
+			if (OidIsValid(anycompatible_multirange_typeid))
+			{
+				/* All ANYCOMPATIBLEMULTIRANGE arguments must be the same type */
+				if (anycompatible_multirange_typeid != actual_type)
+					return false;
+			}
+			else
+			{
+				anycompatible_multirange_typeid = actual_type;
+				anycompatible_multirange_typelem = get_range_multirange_subtype(actual_type);
+				if (!OidIsValid(anycompatible_multirange_typelem))
+					return false;	/* not a multirange type */
+
+				if (OidIsValid(anycompatible_range_typeid))
+				{
+					/* ANYCOMPATIBLEMULTIRANGE and ANYCOMPATIBLERANGE arguments must match */
+					if (anycompatible_range_typeid != anycompatible_multirange_typelem)
+						return false;
+				}
+				else
+				{
+					anycompatible_range_typeid = anycompatible_multirange_typelem;
+					anycompatible_range_typelem = get_range_subtype(anycompatible_range_typeid);
+					if (!OidIsValid(anycompatible_range_typelem))
+						return false;	/* not a range type */
+				}
+				/* collect the subtype for common-supertype choice */
+				anycompatible_actual_types[n_anycompatible_args++] =
+					anycompatible_range_typelem;
+			}
+		}
 	}
 
 	/* Get the element type based on the array type, if we have one */
@@ -1723,8 +1780,6 @@ check_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))
 	{
-		Oid			range_typelem;
-
 		range_typelem = get_range_subtype(range_typeid);
 		if (!OidIsValid(range_typelem))
 			return false;		/* should be a range, but isn't */
@@ -1743,6 +1798,45 @@ 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))
+	{
+		Oid			multirange_typelem;
+
+		multirange_typelem = get_range_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 */
+		}
+		else if (multirange_typelem != range_typeid)
+		{
+			/* otherwise, they better match */
+			return false;
+		}
+
+		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 */
@@ -1781,8 +1875,10 @@ check_generic_type_consistency(const Oid *actual_arg_types,
 		}
 
 		/*
-		 * the anycompatible type must exactly match the range element type,
-		 * if we were able to identify one
+		 * The anycompatible type must exactly match the range element type,
+		 * if we were able to identify one. This checks compatibility for
+		 * anycompatiblemultirange too since that also sets
+		 * anycompatible_range_typelem above.
 		 */
 		if (OidIsValid(anycompatible_range_typelem) &&
 			anycompatible_range_typelem != anycompatible_typeid)
@@ -1821,21 +1917,27 @@ check_generic_type_consistency(const Oid *actual_arg_types,
  *	  argument's actual type as the function's return type.
  * 2) If return type is ANYARRAY, and any argument is ANYARRAY, use the
  *	  argument's actual type as the function's return type.
- * 3) Similarly, if return type is ANYRANGE, and any argument is ANYRANGE,
- *	  use the argument's actual type as the function's return type.
- * 4) Otherwise, if return type is ANYELEMENT or ANYARRAY, and there is
+ * 3) Similarly, if return type is ANYRANGE or ANYMULTIRANGE, and any
+ *	  argument is ANYRANGE or ANYMULTIRANGE, use that argument's
+ *	  actual type, range type or multirange type as the function's return
+ *	  type.
+ * 4) Otherwise, if return type is ANYMULTIRANGE, and any argument is
+ *	  ANYMULTIRANGE, use the argument's actual type as the function's return
+ *	  type. Or if any argument is ANYRANGE, use its multirange type as the
+ *	  function's return type.
+ * 5) Otherwise, if return type is ANYELEMENT or ANYARRAY, and there is
  *	  at least one ANYELEMENT, ANYARRAY, or ANYRANGE input, deduce the
  *	  return type from those inputs, or throw error if we can't.
- * 5) Otherwise, if return type is ANYRANGE, throw error.  (We have no way to
- *	  select a specific range type if the arguments don't include ANYRANGE.)
- * 6) ANYENUM is treated the same as ANYELEMENT except that if it is used
+ * 6) Otherwise, if return type is ANYRANGE or ANYMULTIRANGE, throw error.
+ *	  (We have no way to select a specific range type if the arguments don't
+ *	  include ANYRANGE.)
  *	  (alone or in combination with plain ANYELEMENT), we add the extra
  *	  condition that the ANYELEMENT type must be an enum.
- * 7) ANYNONARRAY is treated the same as ANYELEMENT except that if it is used,
+ * 8) ANYNONARRAY is treated the same as ANYELEMENT except that if it is used,
  *	  we add the extra condition that the ANYELEMENT type must not be an array.
  *	  (This is a no-op if used in combination with ANYARRAY or ANYENUM, but
  *	  is an extra restriction if not.)
- * 8) ANYCOMPATIBLE, ANYCOMPATIBLEARRAY, ANYCOMPATIBLENONARRAY, and
+ * 9) ANYCOMPATIBLE, ANYCOMPATIBLEARRAY, ANYCOMPATIBLENONARRAY, and
  *	  ANYCOMPATIBLERANGE are handled by resolving the common supertype
  *	  of those arguments (or their element types/subtypes, for array and range
  *	  inputs), and then coercing all those arguments to the common supertype,
@@ -1889,15 +1991,21 @@ 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			anycompatible_typeid = InvalidOid;
 	Oid			anycompatible_array_typeid = InvalidOid;
 	Oid			anycompatible_range_typeid = InvalidOid;
 	Oid			anycompatible_range_typelem = InvalidOid;
+	Oid			anycompatible_multirange_typeid = InvalidOid;
+	Oid			anycompatible_multirange_typelem = InvalidOid;
+	Oid			range_typelem;
+	Oid			multirange_typelem;
 	bool		have_anynonarray = (rettype == ANYNONARRAYOID);
 	bool		have_anyenum = (rettype == ANYENUMOID);
 	bool		have_anycompatible_nonarray = (rettype == ANYCOMPATIBLENONARRAYOID);
 	bool		have_anycompatible_array = (rettype == ANYCOMPATIBLEARRAYOID);
 	bool		have_anycompatible_range = (rettype == ANYCOMPATIBLERANGEOID);
+	bool		have_anycompatible_multirange = (rettype == ANYCOMPATIBLEMULTIRANGEOID);
 	int			n_poly_args = 0;	/* this counts all family-1 arguments */
 	int			n_anycompatible_args = 0;	/* this counts only non-unknowns */
 	Oid			anycompatible_actual_types[FUNC_MAX_ARGS];
@@ -1977,6 +2085,26 @@ enforce_generic_type_consistency(const Oid *actual_arg_types,
 								   format_type_be(actual_type))));
 			range_typeid = actual_type;
 		}
+		else if (decl_type == ANYMULTIRANGEOID)
+		{
+			n_poly_args++;
+			if (actual_type == UNKNOWNOID)
+			{
+				have_poly_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;
+		}
 		else if (decl_type == ANYCOMPATIBLEOID ||
 				 decl_type == ANYCOMPATIBLENONARRAYOID)
 		{
@@ -2045,6 +2173,41 @@ enforce_generic_type_consistency(const Oid *actual_arg_types,
 				anycompatible_actual_types[n_anycompatible_args++] = anycompatible_range_typelem;
 			}
 		}
+		else if (decl_type == ANYCOMPATIBLEMULTIRANGEOID)
+		{
+			have_poly_anycompatible = true;
+			have_anycompatible_multirange = true;
+			if (actual_type == UNKNOWNOID)
+				continue;
+			if (allow_poly && decl_type == actual_type)
+				continue;		/* no new information here */
+			actual_type = getBaseType(actual_type); /* flatten domains */
+			if (OidIsValid(anycompatible_multirange_typeid))
+			{
+				/* All ANYCOMPATIBLEMULTIRANGE arguments must be the same type */
+				if (anycompatible_multirange_typeid != actual_type)
+					ereport(ERROR,
+							(errcode(ERRCODE_DATATYPE_MISMATCH),
+							 errmsg("arguments declared \"anycompatiblemultirange\" are not all alike"),
+							 errdetail("%s versus %s",
+									   format_type_be(anycompatible_multirange_typeid),
+									   format_type_be(actual_type))));
+			}
+			else
+			{
+				anycompatible_multirange_typeid = actual_type;
+				anycompatible_multirange_typelem = get_range_multirange_subtype(actual_type);
+				anycompatible_range_typelem = get_range_subtype(anycompatible_multirange_typelem);
+				if (!OidIsValid(anycompatible_multirange_typelem))
+					ereport(ERROR,
+							(errcode(ERRCODE_DATATYPE_MISMATCH),
+							 errmsg("argument declared %s is not a multirange type but type %s",
+									"anycompatiblemultirange",
+									format_type_be(actual_type))));
+				/* collect the subtype for common-supertype choice */
+				anycompatible_actual_types[n_anycompatible_args++] = anycompatible_range_typelem;
+			}
+		}
 	}
 
 	/*
@@ -2113,8 +2276,6 @@ 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))
 		{
-			Oid			range_typelem;
-
 			range_typelem = get_range_subtype(range_typeid);
 			if (!OidIsValid(range_typelem))
 				ereport(ERROR,
@@ -2143,6 +2304,61 @@ enforce_generic_type_consistency(const Oid *actual_arg_types,
 								   format_type_be(elem_typeid))));
 			}
 		}
+		else
+			range_typelem = InvalidOid;
+
+		/* Get the element type based on the multirange type, if we have one */
+		if (OidIsValid(multirange_typeid))
+		{
+			multirange_typelem = get_range_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))));
+			}
+
+			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 */
+				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))));
+			}
+		}
+		else
+			multirange_typelem = InvalidOid;
 
 		if (!OidIsValid(elem_typeid))
 		{
@@ -2151,6 +2367,7 @@ enforce_generic_type_consistency(const Oid *actual_arg_types,
 				elem_typeid = ANYELEMENTOID;
 				array_typeid = ANYARRAYOID;
 				range_typeid = ANYRANGEOID;
+				multirange_typeid = ANYMULTIRANGEOID;
 			}
 			else
 			{
@@ -2250,6 +2467,7 @@ enforce_generic_type_consistency(const Oid *actual_arg_types,
 				anycompatible_typeid = ANYCOMPATIBLEOID;
 				anycompatible_array_typeid = ANYCOMPATIBLEARRAYOID;
 				anycompatible_range_typeid = ANYCOMPATIBLERANGEOID;
+				anycompatible_multirange_typeid = ANYCOMPATIBLEMULTIRANGEOID;
 			}
 			else
 			{
@@ -2281,6 +2499,8 @@ enforce_generic_type_consistency(const Oid *actual_arg_types,
 				declared_arg_types[j] = anycompatible_array_typeid;
 			else if (decl_type == ANYCOMPATIBLERANGEOID)
 				declared_arg_types[j] = anycompatible_range_typeid;
+			else if (decl_type == ANYCOMPATIBLEMULTIRANGEOID)
+				declared_arg_types[j] = anycompatible_multirange_typeid;
 		}
 	}
 
@@ -2331,6 +2551,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;
+			}
 		}
 	}
 
@@ -2367,6 +2598,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 ANYCOMPATIBLE use the appropriate type */
 	if (rettype == ANYCOMPATIBLEOID ||
 		rettype == ANYCOMPATIBLENONARRAYOID)
@@ -2401,6 +2648,17 @@ enforce_generic_type_consistency(const Oid *actual_arg_types,
 		return anycompatible_range_typeid;
 	}
 
+	/* if we return ANYCOMPATIBLEMULTIRANGE use the appropriate argument type */
+	if (rettype == ANYCOMPATIBLEMULTIRANGEOID)
+	{
+		/* this error is unreachable if the function signature is valid: */
+		if (!OidIsValid(anycompatible_multirange_typeid))
+			ereport(ERROR,
+					(errcode(ERRCODE_DATATYPE_MISMATCH),
+					 errmsg_internal("could not identify anycompatiblemultirange type")));
+		return anycompatible_multirange_typeid;
+	}
+
 	/* we don't return a generic type; send back the original return type */
 	return rettype;
 }
@@ -2418,20 +2676,37 @@ check_valid_polymorphic_signature(Oid ret_type,
 								  const Oid *declared_arg_types,
 								  int nargs)
 {
-	if (ret_type == ANYRANGEOID || ret_type == ANYCOMPATIBLERANGEOID)
+	if (ret_type == ANYRANGEOID || ret_type == ANYMULTIRANGEOID)
 	{
 		/*
-		 * ANYRANGE requires an ANYRANGE input, else we can't tell which of
-		 * several range types with the same element type to use.  Likewise
-		 * for ANYCOMPATIBLERANGE.
+		 * ANYRANGE and ANYMULTIRANGE require an ANYRANGE or ANYMULTIRANGE input,
+		 * else we can't tell which of several range types with the same element
+		 * type to use.
 		 */
 		for (int i = 0; i < nargs; i++)
 		{
-			if (declared_arg_types[i] == ret_type)
+			if (declared_arg_types[i] == ANYRANGEOID ||
+				declared_arg_types[i] == ANYMULTIRANGEOID)
 				return NULL;	/* OK */
 		}
-		return psprintf(_("A result of type %s requires at least one input of type %s."),
-						format_type_be(ret_type), format_type_be(ret_type));
+		return psprintf(_("A result of type %s requires at least one input of type anyrange or anymultirange."),
+						format_type_be(ret_type));
+	}
+	else if (ret_type == ANYCOMPATIBLERANGEOID || ret_type == ANYCOMPATIBLEMULTIRANGEOID)
+	{
+		/*
+		 * ANYCOMPATIBLERANGE and ANYCOMPATIBLEMULTIRANGE require an ANYCOMPATIBLERANGE or
+		 * ANYCOMPATIBLEMULTIRANGE input, else we can't tell which of several range types
+		 * with the same element type to use.
+		 */
+		for (int i = 0; i < nargs; i++)
+		{
+			if (declared_arg_types[i] == ANYCOMPATIBLERANGEOID ||
+				declared_arg_types[i] == ANYCOMPATIBLEMULTIRANGEOID)
+				return NULL;	/* OK */
+		}
+		return psprintf(_("A result of type %s requires at least one input of type anycompatiblerange or anycompatiblemultirange."),
+						format_type_be(ret_type));
 	}
 	else if (IsPolymorphicTypeFamily1(ret_type))
 	{
@@ -2442,7 +2717,7 @@ check_valid_polymorphic_signature(Oid ret_type,
 				return NULL;	/* OK */
 		}
 		/* Keep this list in sync with IsPolymorphicTypeFamily1! */
-		return psprintf(_("A result of type %s requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange."),
+		return psprintf(_("A result of type %s requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange."),
 						format_type_be(ret_type));
 	}
 	else if (IsPolymorphicTypeFamily2(ret_type))
@@ -2594,6 +2869,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 || targettype == ANYCOMPATIBLEMULTIRANGEOID)
+		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 5d2aca8cfe..2cea8e2e17 100644
--- a/src/backend/utils/adt/Makefile
+++ b/src/backend/utils/adt/Makefile
@@ -58,6 +58,8 @@ OBJS = \
 	mac.o \
 	mac8.o \
 	misc.o \
+	multirangetypes.o \
+	multirangetypes_selfuncs.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..203f4b45b7
--- /dev/null
+++ b/src/backend/utils/adt/multirangetypes.c
@@ -0,0 +1,2180 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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/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 */
+	FmgrInfo	typioproc;		/* range type's I/O proc */
+	Oid			typioparam;		/* range type's I/O parameter */
+} 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 mltrngtypid,
+												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;
+	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 = NULL;
+	int32		range_str_len;
+	char	   *range_str_copy;
+
+	cache = get_multirange_io_data(fcinfo, mltrngtypoid, IOFunc_input);
+	rangetyp = cache->typcache->rngtype;
+
+	/* 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->typioproc,
+																 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->typioproc, 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->typioproc,
+														   &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;
+
+		range = RangeTypePGetDatum(ranges[i]);
+		range = PointerGetDatum(SendFunctionCall(&cache->typioproc, range));
+
+		pq_sendint32(buf, VARSIZE(range) - VARHDRSZ);
+		pq_sendbytes(buf, VARDATA(range), VARSIZE(range) - VARHDRSZ);
+	}
+
+	PG_RETURN_BYTEA_P(pq_endtypsend(buf));
+}
+
+/*
+ * 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)
+	{
+		Oid			typiofunc;
+		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,
+						 &typiofunc);
+
+		if (!OidIsValid(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(typiofunc, &cache->typioproc,
+					  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;
+	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++)
+		bytelen += MAXALIGN(VARSIZE(ranges[i]));
+
+	/* 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++)
+	{
+		memcpy(ptr, ranges[i], VARSIZE(ranges[i]));
+		ptr += MAXALIGN(VARSIZE(ranges[i]));
+	}
+
+	return multirange;
+}
+
+/*
+ * Converts a list of arbitrary ranges 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(const 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_constructor2(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,
+				(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+				 errmsg("multirange values cannot contain NULL members")));
+
+	rangeArray = PG_GETARG_ARRAYTYPE_P(0);
+
+	dims = ARR_NDIM(rangeArray);
+	if (dims > 1)
+		ereport(ERROR,
+				(errcode(ERRCODE_CARDINALITY_VIOLATION),
+				 errmsg("multiranges cannot be constructed from multi-dimensional arrays")));
+
+	rngtypid = ARR_ELEMTYPE(rangeArray);
+	if (rngtypid != rangetyp->type_id)
+		ereport(ERROR,
+				(errcode(ERRCODE_DATATYPE_MISMATCH),
+				 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,
+						(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+						 errmsg("multirange values cannot contain NULL members")));
+
+			/* make_multirange will do its own copy */
+			ranges[i] = DatumGetRangeTypeP(elements[i]);
+		}
+	}
+
+	PG_RETURN_MULTIRANGE_P(make_multirange(mltrngtypid, rangetyp, range_count, ranges));
+}
+
+/*
+ * Construct multirange value from a single range.
+ * It'd be nice if we could just use multirange_constructor2
+ * for this case, but we need a non-variadic single-arg function
+ * to let us define a CAST from a range to its multirange.
+ */
+Datum
+multirange_constructor1(PG_FUNCTION_ARGS)
+{
+	Oid			mltrngtypid = get_fn_expr_rettype(fcinfo->flinfo);
+	Oid			rngtypid;
+	TypeCacheEntry *typcache;
+	TypeCacheEntry *rangetyp;
+	RangeType  *range;
+
+	typcache = multirange_get_typcache(fcinfo, mltrngtypid);
+	rangetyp = typcache->rngtype;
+
+	/*
+	 * These checks should be guaranteed by our signature, but let's do them
+	 * just in case.
+	 */
+
+	if (PG_ARGISNULL(0))
+				ereport(ERROR,
+						(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+						 errmsg("multirange values cannot contain NULL members")));
+
+	range = PG_GETARG_RANGE_P(0);
+
+	/* Make sure the range type matches. */
+	rngtypid = RangeTypeGetOid(range);
+	if (rngtypid != rangetyp->type_id)
+		ereport(ERROR,
+				(errcode(ERRCODE_DATATYPE_MISMATCH),
+				 errmsg("type %u does not match constructor type", rngtypid)));
+
+	PG_RETURN_MULTIRANGE_P(make_multirange(mltrngtypid, rangetyp, 1, &range));
+}
+
+/*
+ * 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
+		elog(ERROR,		/* can't happen */
+			 "niladic multirange constructor must not receive arguments");
+}
+
+
+/* multirange, multirange -> multirange type functions */
+
+/* multirange union */
+Datum
+multirange_union(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
+	MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count1;
+	int32		range_count2;
+	int32		range_count3;
+	RangeType **ranges1;
+	RangeType **ranges2;
+	RangeType **ranges3;
+
+	if (MultirangeIsEmpty(mr1))
+		PG_RETURN_MULTIRANGE_P(mr2);
+	if (MultirangeIsEmpty(mr2))
+		PG_RETURN_MULTIRANGE_P(mr1);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr1));
+
+	multirange_deserialize(mr1, &range_count1, &ranges1);
+	multirange_deserialize(mr2, &range_count2, &ranges2);
+
+	range_count3 = range_count1 + range_count2;
+	ranges3 = palloc0(range_count3 * sizeof(RangeType *));
+	memcpy(ranges3, ranges1, range_count1 * sizeof(RangeType *));
+	memcpy(ranges3 + range_count1, ranges2, range_count2 * sizeof(RangeType *));
+	PG_RETURN_MULTIRANGE_P(make_multirange(typcache->type_id, typcache->rngtype,
+										   range_count3, ranges3));
+}
+
+/* multirange minus */
+Datum
+multirange_minus(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
+	MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
+	Oid			mltrngtypoid = MultirangeTypeGetOid(mr1);
+	TypeCacheEntry *typcache;
+	TypeCacheEntry *rangetyp;
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+
+	typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
+	rangetyp = typcache->rngtype;
+
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		PG_RETURN_MULTIRANGE_P(mr1);
+
+	multirange_deserialize(mr1, &range_count1, &ranges1);
+	multirange_deserialize(mr2, &range_count2, &ranges2);
+
+	PG_RETURN_MULTIRANGE_P(multirange_minus_internal(mltrngtypoid,
+													 rangetyp,
+													 range_count1,
+													 ranges1,
+													 range_count2,
+													 ranges2));
+}
+
+MultirangeType *
+multirange_minus_internal(Oid mltrngtypoid, TypeCacheEntry *rangetyp,
+						  int32 range_count1, RangeType **ranges1,
+						  int32 range_count2, RangeType **ranges2)
+{
+	RangeType  *r1;
+	RangeType  *r2;
+	RangeType **ranges3;
+	int32		range_count3;
+	int32		i1;
+	int32		i2;
+
+	/*
+	 * Worst case: every range in ranges1 makes a different cut to some range
+	 * in ranges2.
+	 */
+	ranges3 = palloc0((range_count1 + range_count2) * sizeof(RangeType *));
+	range_count3 = 0;
+
+	/*
+	 * For each range in mr1, keep subtracting until it's gone or the ranges
+	 * in mr2 have passed it. After a subtraction we assign what's left back
+	 * to r1. The parallel progress through mr1 and mr2 is similar to
+	 * multirange_overlaps_multirange_internal.
+	 */
+	r2 = ranges2[0];
+	for (i1 = 0, i2 = 0; i1 < range_count1; i1++)
+	{
+		r1 = ranges1[i1];
+
+		/* Discard r2s while r2 << r1 */
+		while (r2 != NULL && range_before_internal(rangetyp, r2, r1))
+		{
+			r2 = ++i2 >= range_count2 ? NULL : ranges2[i2];
+		}
+
+		while (r2 != NULL)
+		{
+			if (range_split_internal(rangetyp, r1, r2, &ranges3[range_count3], &r1))
+			{
+				/*
+				 * If r2 takes a bite out of the middle of r1, we need two
+				 * outputs
+				 */
+				range_count3++;
+				r2 = ++i2 >= range_count2 ? NULL : ranges2[i2];
+
+			}
+			else if (range_overlaps_internal(rangetyp, r1, r2))
+			{
+				/*
+				 * If r2 overlaps r1, replace r1 with r1 - r2.
+				 */
+				r1 = range_minus_internal(rangetyp, r1, r2);
+
+				/*
+				 * If r2 goes past r1, then we need to stay with it, in case
+				 * it hits future r1s. Otherwise we need to keep r1, in case
+				 * future r2s hit it. Since we already subtracted, there's no
+				 * point in using the overright/overleft calls.
+				 */
+				if (RangeIsEmpty(r1) || range_before_internal(rangetyp, r1, r2))
+					break;
+				else
+					r2 = ++i2 >= range_count2 ? NULL : ranges2[i2];
+
+			}
+			else
+			{
+				/*
+				 * This and all future r2s are past r1, so keep them. Also
+				 * assign whatever is left of r1 to the result.
+				 */
+				break;
+			}
+		}
+
+		/*
+		 * Nothing else can remove anything from r1, so keep it. Even if r1 is
+		 * empty here, make_multirange will remove it.
+		 */
+		ranges3[range_count3++] = r1;
+	}
+
+	return make_multirange(mltrngtypoid, rangetyp, range_count3, ranges3);
+}
+
+/* multirange intersection */
+Datum
+multirange_intersect(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
+	MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
+	Oid			mltrngtypoid = MultirangeTypeGetOid(mr1);
+	TypeCacheEntry *typcache;
+	TypeCacheEntry *rangetyp;
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+
+	typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
+	rangetyp = typcache->rngtype;
+
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		PG_RETURN_MULTIRANGE_P(make_empty_multirange(mltrngtypoid, rangetyp));
+
+	multirange_deserialize(mr1, &range_count1, &ranges1);
+	multirange_deserialize(mr2, &range_count2, &ranges2);
+
+	PG_RETURN_MULTIRANGE_P(multirange_intersect_internal(mltrngtypoid,
+														 rangetyp,
+														 range_count1,
+														 ranges1,
+														 range_count2,
+														 ranges2));
+}
+
+MultirangeType *
+multirange_intersect_internal(Oid mltrngtypoid, TypeCacheEntry *rangetyp,
+							  int32 range_count1, RangeType **ranges1,
+							  int32 range_count2, RangeType **ranges2)
+{
+	RangeType  *r1;
+	RangeType  *r2;
+	RangeType **ranges3;
+	int32		range_count3;
+	int32		i1;
+	int32		i2;
+
+	if (range_count1 == 0 || range_count2 == 0)
+		return make_multirange(mltrngtypoid, rangetyp, 0, NULL);
+
+	/*-----------------------------------------------
+	 * Worst case is a stitching pattern like this:
+	 *
+	 * mr1: --- --- --- ---
+	 * mr2:   --- --- ---
+	 * mr3:   - - - - - -
+	 *
+	 * That seems to be range_count1 + range_count2 - 1,
+	 * but one extra won't hurt.
+	 *-----------------------------------------------
+	 */
+	ranges3 = palloc0((range_count1 + range_count2) * sizeof(RangeType *));
+	range_count3 = 0;
+
+	/*
+	 * For each range in mr1, keep intersecting until the ranges in mr2 have
+	 * passed it. The parallel progress through mr1 and mr2 is similar to
+	 * multirange_minus_multirange_internal, but we don't have to assign back
+	 * to r1.
+	 */
+	r2 = ranges2[0];
+	for (i1 = 0, i2 = 0; i1 < range_count1; i1++)
+	{
+		r1 = ranges1[i1];
+
+		/* Discard r2s while r2 << r1 */
+		while (r2 != NULL && range_before_internal(rangetyp, r2, r1))
+		{
+			r2 = ++i2 >= range_count2 ? NULL : ranges2[i2];
+		}
+
+		while (r2 != NULL)
+		{
+			if (range_overlaps_internal(rangetyp, r1, r2))
+			{
+				/* Keep the overlapping part */
+				ranges3[range_count3++] = range_intersect_internal(rangetyp, r1, r2);
+
+				/* If we "used up" all of r2, go to the next one... */
+				if (range_overleft_internal(rangetyp, r2, r1))
+					r2 = ++i2 >= range_count2 ? NULL : ranges2[i2];
+
+				/* ...otherwise go to the next r1 */
+				else
+					break;
+			}
+			else
+				/* We're past r1, so move to the next one */
+				break;
+		}
+
+		/* If we're out of r2s, there can be no more intersections */
+		if (r2 == NULL)
+			break;
+	}
+
+	return make_multirange(mltrngtypoid, rangetyp, range_count3, ranges3);
+}
+
+/*
+ * range_agg_transfn: combine adjacent/overlapping ranges.
+ *
+ * All we do here is gather the input ranges into an array
+ * so that the finalfn can sort and combine them.
+ */
+Datum
+range_agg_transfn(PG_FUNCTION_ARGS)
+{
+	MemoryContext aggContext;
+	Oid			rngtypoid;
+	ArrayBuildState *state;
+
+	if (!AggCheckCallContext(fcinfo, &aggContext))
+		elog(ERROR, "range_agg_transfn called in non-aggregate context");
+
+	rngtypoid = get_fn_expr_argtype(fcinfo->flinfo, 1);
+	if (!type_is_range(rngtypoid))
+		ereport(ERROR,
+				(errcode(ERRCODE_DATATYPE_MISMATCH),
+				 errmsg("range_agg must be called with a range")));
+
+	if (PG_ARGISNULL(0))
+		state = initArrayResult(rngtypoid, aggContext, false);
+	else
+		state = (ArrayBuildState *) PG_GETARG_POINTER(0);
+
+	/* skip NULLs */
+	if (!PG_ARGISNULL(1))
+		accumArrayResult(state, PG_GETARG_DATUM(1), false, rngtypoid, aggContext);
+
+	PG_RETURN_POINTER(state);
+}
+
+/*
+ * range_agg_finalfn: use our internal array to merge touching ranges.
+ */
+Datum
+range_agg_finalfn(PG_FUNCTION_ARGS)
+{
+	MemoryContext aggContext;
+	Oid			mltrngtypoid;
+	TypeCacheEntry *typcache;
+	ArrayBuildState *state;
+	int32		range_count;
+	RangeType **ranges;
+	int			i;
+
+	if (!AggCheckCallContext(fcinfo, &aggContext))
+		elog(ERROR, "range_agg_finalfn called in non-aggregate context");
+
+	state = PG_ARGISNULL(0) ? NULL : (ArrayBuildState *) PG_GETARG_POINTER(0);
+	if (state == NULL)
+		/* This shouldn't be possible, but just in case.... */
+		PG_RETURN_NULL();
+
+	/* Also return NULL if we had zero inputs, like other aggregates */
+	range_count = state->nelems;
+	if (range_count == 0)
+		PG_RETURN_NULL();
+
+	mltrngtypoid = get_fn_expr_rettype(fcinfo->flinfo);
+	typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
+
+	ranges = palloc0(range_count * sizeof(RangeType *));
+	for (i = 0; i < range_count; i++)
+		ranges[i] = DatumGetRangeTypeP(state->dvalues[i]);
+
+	PG_RETURN_MULTIRANGE_P(make_multirange(mltrngtypoid, typcache->rngtype, range_count, ranges));
+}
+
+Datum
+multirange_intersect_agg_transfn(PG_FUNCTION_ARGS)
+{
+	MemoryContext aggContext;
+	Oid			mltrngtypoid;
+	TypeCacheEntry *typcache;
+	MultirangeType *result;
+	MultirangeType *current;
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+
+	if (!AggCheckCallContext(fcinfo, &aggContext))
+		elog(ERROR, "multirange_intersect_agg_transfn called in non-aggregate context");
+
+	mltrngtypoid = get_fn_expr_argtype(fcinfo->flinfo, 1);
+	if (!type_is_multirange(mltrngtypoid))
+		ereport(ERROR,
+				(errcode(ERRCODE_DATATYPE_MISMATCH),
+				 errmsg("range_intersect_agg must be called with a multirange")));
+
+	typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
+
+	/* strictness ensures these are non-null */
+	result = PG_GETARG_MULTIRANGE_P(0);
+	current = PG_GETARG_MULTIRANGE_P(1);
+
+	multirange_deserialize(result, &range_count1, &ranges1);
+	multirange_deserialize(current, &range_count2, &ranges2);
+
+	result = multirange_intersect_internal(mltrngtypoid,
+										   typcache->rngtype,
+										   range_count1,
+										   ranges1,
+										   range_count2,
+										   ranges2);
+	PG_RETURN_RANGE_P(result);
+}
+
+
+/* multirange -> element type functions */
+
+/* extract lower bound value */
+Datum
+multirange_lower(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+	bool		isnull;
+	Datum		result;
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_NULL();
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	result = range_lower_internal(typcache->rngtype, ranges[0], &isnull);
+
+	if (isnull)
+		PG_RETURN_NULL();
+	else
+		PG_RETURN_DATUM(result);
+}
+
+/* extract upper bound value */
+Datum
+multirange_upper(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+	bool		isnull;
+	Datum		result;
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_NULL();
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	result = range_upper_internal(typcache->rngtype, ranges[range_count - 1], &isnull);
+
+	if (isnull)
+		PG_RETURN_NULL();
+	else
+		PG_RETURN_DATUM(result);
+}
+
+
+/* multirange -> bool functions */
+
+/* is multirange empty? */
+Datum
+multirange_empty(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+
+	PG_RETURN_BOOL(mr->rangeCount == 0);
+}
+
+/* is lower bound inclusive? */
+Datum
+multirange_lower_inc(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	int32		range_count;
+	RangeType **ranges;
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_has_flag(ranges[0], RANGE_LB_INC));
+}
+
+/* is upper bound inclusive? */
+Datum
+multirange_upper_inc(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	int32		range_count;
+	RangeType **ranges;
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_has_flag(ranges[range_count - 1], RANGE_UB_INC));
+}
+
+/* is lower bound infinite? */
+Datum
+multirange_lower_inf(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	int32		range_count;
+	RangeType **ranges;
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_has_flag(ranges[0], RANGE_LB_INF));
+}
+
+/* is upper bound infinite? */
+Datum
+multirange_upper_inf(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	int32		range_count;
+	RangeType **ranges;
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_has_flag(ranges[range_count - 1], RANGE_UB_INF));
+}
+
+
+
+/* multirange, element -> bool functions */
+
+/* contains? */
+Datum
+multirange_contains_elem(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	Datum		val = PG_GETARG_DATUM(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(multirange_contains_elem_internal(typcache, mr, val));
+}
+
+/* contained by? */
+Datum
+elem_contained_by_multirange(PG_FUNCTION_ARGS)
+{
+	Datum		val = PG_GETARG_DATUM(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(multirange_contains_elem_internal(typcache, mr, val));
+}
+
+/*
+ * Test whether multirange mr contains a specific element value.
+ */
+bool
+multirange_contains_elem_internal(TypeCacheEntry *typcache, MultirangeType *mr, Datum val)
+{
+	TypeCacheEntry *rangetyp;
+	int32		range_count;
+	RangeType **ranges;
+	RangeType  *r;
+	int			i;
+
+	rangetyp = typcache->rngtype;
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	for (i = 0; i < range_count; i++)
+	{
+		r = ranges[i];
+		if (range_contains_elem_internal(rangetyp, r, val))
+			return true;
+	}
+
+	return false;
+}
+
+/* multirange, range -> bool functions */
+
+/* contains? */
+Datum
+multirange_contains_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(multirange_contains_range_internal(typcache, mr, r));
+}
+
+/* contained by? */
+Datum
+range_contained_by_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(multirange_contains_range_internal(typcache, mr, r));
+}
+
+/*
+ * Test whether multirange mr contains a specific range r.
+ */
+bool
+multirange_contains_range_internal(TypeCacheEntry *typcache, MultirangeType *mr, RangeType *r)
+{
+	TypeCacheEntry *rangetyp;
+	int32		range_count;
+	RangeType **ranges;
+	RangeType  *mrr;
+	int			i;
+
+	rangetyp = typcache->rngtype;
+
+	/*
+	 * Every multirange contains an infinite number of empty ranges, even an
+	 * empty one.
+	 */
+	if (RangeIsEmpty(r))
+		return true;
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	for (i = 0; i < range_count; i++)
+	{
+		mrr = ranges[i];
+		if (range_contains_internal(rangetyp, mrr, r))
+			return true;
+	}
+
+	return false;
+}
+
+
+/* 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));
+}
+
+/* overlaps? */
+Datum
+range_overlaps_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(range_overlaps_multirange_internal(typcache, r, mr));
+}
+
+Datum
+multirange_overlaps_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(range_overlaps_multirange_internal(typcache, r, mr));
+}
+
+Datum
+multirange_overlaps_multirange(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_overlaps_multirange_internal(typcache, mr1, mr2));
+}
+
+bool
+range_overlaps_multirange_internal(TypeCacheEntry *typcache, RangeType *r, MultirangeType *mr)
+{
+	TypeCacheEntry *rangetyp;
+	int32		range_count;
+	int32		i;
+	RangeType **ranges;
+	RangeType  *mrr;
+
+	/*
+	 * Empties never overlap, even with empties. (This seems strange since
+	 * they *do* contain each other, but we want to follow how ranges work.)
+	 */
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		return false;
+
+	rangetyp = typcache->rngtype;
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	/* Scan through mrr and once it gets to r they either overlap or not */
+	mrr = ranges[0];
+
+	/* Discard mrrs while mrr << r */
+	i = 0;
+	while (range_before_internal(rangetyp, mrr, r))
+	{
+		if (++i >= range_count)
+			return false;
+		mrr = ranges[i];
+	}
+
+	/* Now either we overlap or we passed r */
+	return range_overlaps_internal(rangetyp, mrr, r);
+}
+
+bool
+multirange_overlaps_multirange_internal(TypeCacheEntry *typcache, MultirangeType *mr1,
+										MultirangeType *mr2)
+{
+	TypeCacheEntry *rangetyp;
+	int32		range_count1;
+	int32		range_count2;
+	int32		i1;
+	int32		i2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+	RangeType  *r1;
+	RangeType  *r2;
+
+	/*
+	 * Empties never overlap, even with empties. (This seems strange since
+	 * they *do* contain each other, but we want to follow how ranges work.)
+	 */
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		return false;
+
+	rangetyp = typcache->rngtype;
+
+	multirange_deserialize(mr1, &range_count1, &ranges1);
+	multirange_deserialize(mr2, &range_count2, &ranges2);
+
+	/*
+	 * Every range in mr1 gets a chance to overlap with the ranges in mr2, but
+	 * we can use their ordering to avoid O(n^2). This is similar to
+	 * range_overlaps_multirange where r1 : r2 :: mrr : r, but there if we
+	 * don't find an overlap with r we're done, and here if we don't find an
+	 * overlap with r2 we try the next r2.
+	 */
+	r1 = ranges1[0];
+	for (i1 = 0, i2 = 0; i2 < range_count2; i2++)
+	{
+		r2 = ranges2[i2];
+
+		/* Discard r1s while r1 << r2 */
+		while (range_before_internal(rangetyp, r1, r2))
+		{
+			if (++i1 >= range_count1)
+				return false;
+			r1 = ranges1[i1];
+		}
+
+		/*
+		 * If r1 && r2, we're done, otherwise we failed to find an overlap for
+		 * r2, so go to the next one.
+		 */
+		if (range_overlaps_internal(rangetyp, r1, r2))
+			return true;
+	}
+
+	/* We looked through all of mr2 without finding an overlap */
+	return false;
+}
+
+/* does not extend to right of? */
+Datum
+range_overleft_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_overleft_internal(typcache->rngtype, r, ranges[range_count - 1]));
+}
+
+Datum
+multirange_overleft_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_overleft_internal(typcache->rngtype,
+										   ranges[range_count - 1], r));
+}
+
+Datum
+multirange_overleft_multirange(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
+	MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr1));
+
+	multirange_deserialize(mr1, &range_count1, &ranges1);
+	multirange_deserialize(mr2, &range_count2, &ranges2);
+
+	PG_RETURN_BOOL(range_overleft_internal(typcache->rngtype,
+										   ranges1[range_count1 - 1],
+										   ranges2[range_count2 - 1]));
+}
+
+/* does not extend to left of? */
+Datum
+range_overright_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_overright_internal(typcache->rngtype, r, ranges[0]));
+}
+
+Datum
+multirange_overright_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_overright_internal(typcache->rngtype, ranges[0], r));
+}
+
+Datum
+multirange_overright_multirange(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
+	MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr1));
+
+	multirange_deserialize(mr1, &range_count1, &ranges1);
+	multirange_deserialize(mr2, &range_count2, &ranges2);
+
+	PG_RETURN_BOOL(range_overright_internal(typcache->rngtype,
+											ranges1[0],
+											ranges2[0]));
+}
+
+/* contains? */
+Datum
+multirange_contains_multirange(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_contains_multirange_internal(typcache, mr1, mr2));
+}
+
+/* contained by? */
+Datum
+multirange_contained_by_multirange(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_contains_multirange_internal(typcache, mr2, mr1));
+}
+
+/*
+ * Test whether multirange mr1 contains every range from another multirange mr2.
+ */
+bool
+multirange_contains_multirange_internal(TypeCacheEntry *typcache,
+										MultirangeType *mr1, MultirangeType *mr2)
+{
+	TypeCacheEntry *rangetyp;
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+	RangeType  *r1;
+	RangeType  *r2;
+	int			i1,
+				i2;
+
+	rangetyp = typcache->rngtype;
+
+	multirange_deserialize(mr1, &range_count1, &ranges1);
+	multirange_deserialize(mr2, &range_count2, &ranges2);
+
+	/*
+	 * We follow the same logic for empties as ranges: - an empty multirange
+	 * contains an empty range/multirange. - an empty multirange can't contain
+	 * any other range/multirange. - an empty multirange is contained by any
+	 * other range/multirange.
+	 */
+
+	if (range_count2 == 0)
+		return true;
+	if (range_count1 == 0)
+		return false;
+
+	/*
+	 * Every range in mr2 must be contained by some range in mr1. To avoid
+	 * O(n^2) we walk through both ranges in tandem.
+	 */
+	r1 = ranges1[0];
+	for (i1 = 0, i2 = 0; i2 < range_count2; i2++)
+	{
+		r2 = ranges2[i2];
+
+		/* Discard r1s while r1 << r2 */
+		while (range_before_internal(rangetyp, r1, r2))
+		{
+			if (++i1 >= range_count1)
+				return false;
+			r1 = ranges1[i1];
+		}
+
+		/*
+		 * If r1 @> r2, go to the next r2, otherwise return false (since every
+		 * r1[n] and r1[n+1] must have a gap). Note this will give weird
+		 * answers if you don't canonicalize, e.g. with a custom
+		 * int2multirange {[1,1], [2,2]} there is a "gap". But that is
+		 * consistent with other range operators, e.g. '[1,1]'::int2range -|-
+		 * '[2,2]'::int2range is false.
+		 */
+		if (!range_contains_internal(rangetyp, r1, r2))
+			return false;
+	}
+
+	/* All ranges in mr2 are satisfied */
+	return true;
+}
+
+/* strictly left of? */
+Datum
+range_before_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(range_before_multirange_internal(typcache, r, mr));
+}
+
+Datum
+multirange_before_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(range_after_multirange_internal(typcache, r, mr));
+}
+
+Datum
+multirange_before_multirange(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_before_multirange_internal(typcache, mr1, mr2));
+}
+
+/* strictly right of? */
+Datum
+range_after_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(range_after_multirange_internal(typcache, r, mr));
+}
+
+Datum
+multirange_after_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(range_before_multirange_internal(typcache, r, mr));
+}
+
+Datum
+multirange_after_multirange(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_before_multirange_internal(typcache, mr2, mr1));
+}
+
+/* strictly left of? (internal version) */
+bool
+range_before_multirange_internal(TypeCacheEntry *typcache, RangeType *r,
+								 MultirangeType *mr)
+{
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		return false;
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	return range_before_internal(typcache->rngtype, r, ranges[0]);
+}
+
+bool
+multirange_before_multirange_internal(TypeCacheEntry *typcache, MultirangeType *mr1,
+									  MultirangeType *mr2)
+{
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		return false;
+
+	multirange_deserialize(mr1, &range_count1, &ranges1);
+	multirange_deserialize(mr2, &range_count2, &ranges2);
+
+	return range_before_internal(typcache->rngtype, ranges1[range_count1 - 1],
+								 ranges2[0]);
+}
+
+/* strictly right of? (internal version) */
+bool
+range_after_multirange_internal(TypeCacheEntry *typcache, RangeType *r,
+								MultirangeType *mr)
+{
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		return false;
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	return range_after_internal(typcache->rngtype, r, ranges[range_count - 1]);
+}
+
+/* adjacent to? */
+Datum
+range_adjacent_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		return false;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_adjacent_internal(typcache->rngtype, r, ranges[0]));
+}
+
+Datum
+multirange_adjacent_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		return false;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_adjacent_internal(typcache->rngtype, ranges[range_count - 1], r));
+}
+
+Datum
+multirange_adjacent_multirange(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
+	MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		return false;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr1));
+
+	multirange_deserialize(mr1, &range_count1, &ranges1);
+	multirange_deserialize(mr2, &range_count2, &ranges2);
+
+	PG_RETURN_BOOL(range_adjacent_internal(typcache->rngtype, ranges1[range_count1 - 1], ranges2[0]));
+}
+
+/* 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);
+}
+
+/* multirange -> range functions */
+
+/* Find the smallest range that includes everything in the multirange */
+Datum
+range_merge_from_multirange(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	Oid			mltrngtypoid = MultirangeTypeGetOid(mr);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_RANGE_P(make_empty_range(typcache->rngtype));
+
+	multirange_deserialize(mr, &range_count, &ranges);
+
+	PG_RETURN_RANGE_P(range_union_internal(typcache->rngtype, ranges[0],
+										   ranges[range_count - 1], false));
+}
+
+/* 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/multirangetypes_selfuncs.c b/src/backend/utils/adt/multirangetypes_selfuncs.c
new file mode 100644
index 0000000000..a3854f1812
--- /dev/null
+++ b/src/backend/utils/adt/multirangetypes_selfuncs.c
@@ -0,0 +1,1297 @@
+/*-------------------------------------------------------------------------
+ *
+ * multirangetypes_selfuncs.c
+ *	  Functions for selectivity estimation of multirange operators
+ *
+ * Estimates are based on histograms of lower and upper bounds, and the
+ * fraction of empty multiranges.
+ *
+ * Portions Copyright (c) 1996-2020, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ *	  src/backend/utils/adt/multirangetypes_selfuncs.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include <math.h>
+
+#include "access/htup_details.h"
+#include "catalog/pg_operator.h"
+#include "catalog/pg_statistic.h"
+#include "catalog/pg_type.h"
+#include "utils/float.h"
+#include "utils/fmgrprotos.h"
+#include "utils/lsyscache.h"
+#include "utils/rangetypes.h"
+#include "utils/multirangetypes.h"
+#include "utils/selfuncs.h"
+#include "utils/typcache.h"
+
+static double calc_multirangesel(TypeCacheEntry *typcache, VariableStatData *vardata,
+							const MultirangeType *constval, Oid operator);
+static double default_multirange_selectivity(Oid operator);
+static double default_multirange_selectivity(Oid operator);
+static double calc_hist_selectivity(TypeCacheEntry *typcache,
+									VariableStatData *vardata, const MultirangeType *constval,
+									Oid operator);
+static double calc_hist_selectivity_scalar(TypeCacheEntry *typcache,
+										   const RangeBound *constbound,
+										   const RangeBound *hist, int hist_nvalues,
+										   bool equal);
+static int	rbound_bsearch(TypeCacheEntry *typcache, const RangeBound *value,
+						   const RangeBound *hist, int hist_length, bool equal);
+static float8 get_position(TypeCacheEntry *typcache, const RangeBound *value,
+						   const RangeBound *hist1, const RangeBound *hist2);
+static float8 get_len_position(double value, double hist1, double hist2);
+static float8 get_distance(TypeCacheEntry *typcache, const RangeBound *bound1,
+						   const RangeBound *bound2);
+static int	length_hist_bsearch(Datum *length_hist_values,
+								int length_hist_nvalues, double value, bool equal);
+static double calc_length_hist_frac(Datum *length_hist_values,
+									int length_hist_nvalues, double length1, double length2, bool equal);
+static double calc_hist_selectivity_contained(TypeCacheEntry *typcache,
+											  const RangeBound *lower, RangeBound *upper,
+											  const RangeBound *hist_lower, int hist_nvalues,
+											  Datum *length_hist_values, int length_hist_nvalues);
+static double calc_hist_selectivity_contains(TypeCacheEntry *typcache,
+											 const RangeBound *lower, const RangeBound *upper,
+											 const RangeBound *hist_lower, int hist_nvalues,
+											 Datum *length_hist_values, int length_hist_nvalues);
+
+/*
+ * Returns a default selectivity estimate for given operator, when we don't
+ * have statistics or cannot use them for some reason.
+ */
+static double
+default_multirange_selectivity(Oid operator)
+{
+	switch (operator)
+	{
+		case OID_MULTIRANGE_OVERLAPS_MULTIRANGE_OP:
+		case OID_MULTIRANGE_OVERLAPS_RANGE_OP:
+		case OID_RANGE_OVERLAPS_MULTIRANGE_OP:
+			return 0.01;
+
+		case OID_MULTIRANGE_CONTAINS_RANGE_OP:
+		case OID_MULTIRANGE_CONTAINS_MULTIRANGE_OP:
+		case OID_MULTIRANGE_RANGE_CONTAINED_OP:
+		case OID_MULTIRANGE_MULTIRANGE_CONTAINED_OP:
+			return 0.005;
+
+		case OID_MULTIRANGE_CONTAINS_ELEM_OP:
+		case OID_MULTIRANGE_ELEM_CONTAINED_OP:
+
+			/*
+			 * "multirange @> elem" is more or less identical to a scalar
+			 * inequality "A >= b AND A <= c".
+			 */
+			return DEFAULT_MULTIRANGE_INEQ_SEL;
+
+		case OID_MULTIRANGE_LESS_OP:
+		case OID_MULTIRANGE_LESS_EQUAL_OP:
+		case OID_MULTIRANGE_GREATER_OP:
+		case OID_MULTIRANGE_GREATER_EQUAL_OP:
+		case OID_MULTIRANGE_LEFT_RANGE_OP:
+		case OID_MULTIRANGE_LEFT_MULTIRANGE_OP:
+		case OID_RANGE_LEFT_MULTIRANGE_OP:
+		case OID_MULTIRANGE_RIGHT_RANGE_OP:
+		case OID_MULTIRANGE_RIGHT_MULTIRANGE_OP:
+		case OID_RANGE_RIGHT_MULTIRANGE_OP:
+		case OID_MULTIRANGE_OVERLAPS_LEFT_RANGE_OP:
+		case OID_RANGE_OVERLAPS_LEFT_MULTIRANGE_OP:
+		case OID_MULTIRANGE_OVERLAPS_LEFT_MULTIRANGE_OP:
+		case OID_MULTIRANGE_OVERLAPS_RIGHT_RANGE_OP:
+		case OID_RANGE_OVERLAPS_RIGHT_MULTIRANGE_OP:
+		case OID_MULTIRANGE_OVERLAPS_RIGHT_MULTIRANGE_OP:
+			/* these are similar to regular scalar inequalities */
+			return DEFAULT_INEQ_SEL;
+
+		default:
+			/* all multirange operators should be handled above, but just in case */
+			return 0.01;
+	}
+}
+
+/*
+ * multirangesel -- restriction selectivity for multirange operators
+ */
+Datum
+multirangesel(PG_FUNCTION_ARGS)
+{
+	PlannerInfo *root = (PlannerInfo *) PG_GETARG_POINTER(0);
+	Oid			operator = PG_GETARG_OID(1);
+	List	   *args = (List *) PG_GETARG_POINTER(2);
+	int			varRelid = PG_GETARG_INT32(3);
+	VariableStatData vardata;
+	Node	   *other;
+	bool		varonleft;
+	Selectivity selec;
+	TypeCacheEntry *typcache = NULL;
+	MultirangeType *constmultirange = NULL;
+	RangeType  *constrange = NULL;
+
+	/*
+	 * If expression is not (variable op something) or (something op
+	 * variable), then punt and return a default estimate.
+	 */
+	if (!get_restriction_variable(root, args, varRelid,
+								  &vardata, &other, &varonleft))
+		PG_RETURN_FLOAT8(default_multirange_selectivity(operator));
+
+	/*
+	 * Can't do anything useful if the something is not a constant, either.
+	 */
+	if (!IsA(other, Const))
+	{
+		ReleaseVariableStats(vardata);
+		PG_RETURN_FLOAT8(default_multirange_selectivity(operator));
+	}
+
+	/*
+	 * All the multirange operators are strict, so we can cope with a NULL constant
+	 * right away.
+	 */
+	if (((Const *) other)->constisnull)
+	{
+		ReleaseVariableStats(vardata);
+		PG_RETURN_FLOAT8(0.0);
+	}
+
+	/*
+	 * If var is on the right, commute the operator, so that we can assume the
+	 * var is on the left in what follows.
+	 */
+	if (!varonleft)
+	{
+		/* we have other Op var, commute to make var Op other */
+		operator = get_commutator(operator);
+		if (!operator)
+		{
+			/* Use default selectivity (should we raise an error instead?) */
+			ReleaseVariableStats(vardata);
+			PG_RETURN_FLOAT8(default_multirange_selectivity(operator));
+		}
+	}
+
+	/*
+	 * OK, there's a Var and a Const we're dealing with here.  We need the
+	 * Const to be of same multirange type as the column, else we can't do anything
+	 * useful. (Such cases will likely fail at runtime, but here we'd rather
+	 * just return a default estimate.)
+	 *
+	 * If the operator is "multirange @> element", the constant should be of the
+	 * element type of the multirange column. Convert it to a multirange that includes
+	 * only that single point, so that we don't need special handling for that
+	 * in what follows.
+	 */
+	if (operator == OID_MULTIRANGE_CONTAINS_ELEM_OP)
+	{
+		typcache = multirange_get_typcache(fcinfo, vardata.vartype);
+
+		if (((Const *) other)->consttype == typcache->rngtype->rngelemtype->type_id)
+		{
+			RangeBound	lower,
+						upper;
+
+			lower.inclusive = true;
+			lower.val = ((Const *) other)->constvalue;
+			lower.infinite = false;
+			lower.lower = true;
+			upper.inclusive = true;
+			upper.val = ((Const *) other)->constvalue;
+			upper.infinite = false;
+			upper.lower = false;
+			constrange = range_serialize(typcache->rngtype, &lower, &upper, false);
+			constmultirange = make_multirange(typcache->type_id, typcache->rngtype,
+					1, &constrange);
+		}
+	}
+	else if (operator == OID_MULTIRANGE_CONTAINS_RANGE_OP ||
+			 operator == OID_MULTIRANGE_OVERLAPS_RANGE_OP ||
+			 operator == OID_MULTIRANGE_OVERLAPS_LEFT_RANGE_OP ||
+			 operator == OID_MULTIRANGE_OVERLAPS_RIGHT_RANGE_OP ||
+			 operator == OID_MULTIRANGE_LEFT_RANGE_OP ||
+			 operator == OID_MULTIRANGE_RIGHT_RANGE_OP)
+	{
+		/*
+		 * Promote a range in "multirange OP range" just like
+		 * we do an element in "multirange OP element".
+		 */
+		typcache = multirange_get_typcache(fcinfo, vardata.vartype);
+		if (((Const *) other)->consttype == typcache->rngtype->type_id)
+		{
+			constrange = DatumGetRangeTypeP(((Const *) other)->constvalue);
+			constmultirange = make_multirange(typcache->type_id, typcache->rngtype,
+					1, &constrange);
+		}
+	}
+	else if (operator == OID_RANGE_OVERLAPS_MULTIRANGE_OP ||
+			 operator == OID_RANGE_OVERLAPS_LEFT_MULTIRANGE_OP ||
+			 operator == OID_RANGE_OVERLAPS_RIGHT_MULTIRANGE_OP ||
+			 operator == OID_RANGE_LEFT_MULTIRANGE_OP ||
+			 operator == OID_RANGE_RIGHT_MULTIRANGE_OP ||
+			 operator == OID_MULTIRANGE_ELEM_CONTAINED_OP ||
+			 operator == OID_MULTIRANGE_RANGE_CONTAINED_OP)
+	{
+		/*
+		 * Here, the Var is the elem/range, not the multirange.  For now we just punt and
+		 * return the default estimate.  In future we could disassemble the
+		 * multirange constant to do something more intelligent.
+		 */
+	}
+	else if (((Const *) other)->consttype == vardata.vartype)
+	{
+		/* Both sides are the same multirange type */
+		typcache = multirange_get_typcache(fcinfo, vardata.vartype);
+
+		constmultirange = DatumGetMultirangeTypeP(((Const *) other)->constvalue);
+	}
+
+	/*
+	 * If we got a valid constant on one side of the operator, proceed to
+	 * estimate using statistics. Otherwise punt and return a default constant
+	 * estimate.  Note that calc_multirangesel need not handle
+	 * OID_MULTIRANGE_*_CONTAINED_OP.
+	 */
+	if (constmultirange)
+		selec = calc_multirangesel(typcache, &vardata, constmultirange, operator);
+	else
+		selec = default_multirange_selectivity(operator);
+
+	ReleaseVariableStats(vardata);
+
+	CLAMP_PROBABILITY(selec);
+
+	PG_RETURN_FLOAT8((float8) selec);
+}
+
+static double
+calc_multirangesel(TypeCacheEntry *typcache, VariableStatData *vardata,
+			  const MultirangeType *constval, Oid operator)
+{
+	double		hist_selec;
+	double		selec;
+	float4		empty_frac,
+				null_frac;
+
+	/*
+	 * First look up the fraction of NULLs and empty multiranges from pg_statistic.
+	 */
+	if (HeapTupleIsValid(vardata->statsTuple))
+	{
+		Form_pg_statistic stats;
+		AttStatsSlot sslot;
+
+		stats = (Form_pg_statistic) GETSTRUCT(vardata->statsTuple);
+		null_frac = stats->stanullfrac;
+
+		/* Try to get fraction of empty multiranges */
+		if (get_attstatsslot(&sslot, vardata->statsTuple,
+							 STATISTIC_KIND_RANGE_LENGTH_HISTOGRAM,
+							 InvalidOid,
+							 ATTSTATSSLOT_NUMBERS))
+		{
+			if (sslot.nnumbers != 1)
+				elog(ERROR, "invalid empty fraction statistic");	/* shouldn't happen */
+			empty_frac = sslot.numbers[0];
+			free_attstatsslot(&sslot);
+		}
+		else
+		{
+			/* No empty fraction statistic. Assume no empty ranges. */
+			empty_frac = 0.0;
+		}
+	}
+	else
+	{
+		/*
+		 * No stats are available. Follow through the calculations below
+		 * anyway, assuming no NULLs and no empty multiranges. This still allows us
+		 * to give a better-than-nothing estimate based on whether the
+		 * constant is an empty multirange or not.
+		 */
+		null_frac = 0.0;
+		empty_frac = 0.0;
+	}
+
+	if (MultirangeIsEmpty(constval))
+	{
+		/*
+		 * An empty multirange matches all multiranges, all empty multiranges, or
+		 * nothing, depending on the operator
+		 */
+		switch (operator)
+		{
+				/* these return false if either argument is empty */
+			case OID_RANGE_OVERLAPS_MULTIRANGE_OP:
+			case OID_MULTIRANGE_OVERLAPS_RANGE_OP:
+			case OID_MULTIRANGE_OVERLAPS_MULTIRANGE_OP:
+			case OID_RANGE_OVERLAPS_LEFT_MULTIRANGE_OP:
+			case OID_MULTIRANGE_OVERLAPS_LEFT_RANGE_OP:
+			case OID_MULTIRANGE_OVERLAPS_LEFT_MULTIRANGE_OP:
+			case OID_RANGE_OVERLAPS_RIGHT_MULTIRANGE_OP:
+			case OID_MULTIRANGE_OVERLAPS_RIGHT_RANGE_OP:
+			case OID_MULTIRANGE_OVERLAPS_RIGHT_MULTIRANGE_OP:
+			case OID_MULTIRANGE_LEFT_MULTIRANGE_OP:
+			case OID_MULTIRANGE_RIGHT_MULTIRANGE_OP:
+				/* nothing is less than an empty multirange */
+			case OID_MULTIRANGE_LESS_OP:
+				selec = 0.0;
+				break;
+
+				/* only empty multiranges can be contained by an empty multirange */
+			case OID_MULTIRANGE_RANGE_CONTAINED_OP:
+			case OID_MULTIRANGE_MULTIRANGE_CONTAINED_OP:
+				/* only empty ranges are <= an empty multirange */
+			case OID_MULTIRANGE_LESS_EQUAL_OP:
+				selec = empty_frac;
+				break;
+
+				/* everything contains an empty multirange */
+			case OID_MULTIRANGE_CONTAINS_RANGE_OP:
+			case OID_MULTIRANGE_CONTAINS_MULTIRANGE_OP:
+				/* everything is >= an empty multirange */
+			case OID_MULTIRANGE_GREATER_EQUAL_OP:
+				selec = 1.0;
+				break;
+
+				/* all non-empty multiranges are > an empty multirange */
+			case OID_MULTIRANGE_GREATER_OP:
+				selec = 1.0 - empty_frac;
+				break;
+
+				/* an element cannot be empty */
+			case OID_MULTIRANGE_ELEM_CONTAINED_OP:
+			case OID_MULTIRANGE_CONTAINS_ELEM_OP:
+			default:
+				elog(ERROR, "unexpected operator %u", operator);
+				selec = 0.0;	/* keep compiler quiet */
+				break;
+		}
+	}
+	else
+	{
+		/*
+		 * Calculate selectivity using bound histograms. If that fails for
+		 * some reason, e.g no histogram in pg_statistic, use the default
+		 * constant estimate for the fraction of non-empty values. This is
+		 * still somewhat better than just returning the default estimate,
+		 * because this still takes into account the fraction of empty and
+		 * NULL tuples, if we had statistics for them.
+		 */
+		hist_selec = calc_hist_selectivity(typcache, vardata, constval,
+										   operator);
+		if (hist_selec < 0.0)
+			hist_selec = default_multirange_selectivity(operator);
+
+		/*
+		 * Now merge the results for the empty multiranges and histogram
+		 * calculations, realizing that the histogram covers only the
+		 * non-null, non-empty values.
+		 */
+		if (operator == OID_MULTIRANGE_ELEM_CONTAINED_OP ||
+			operator == OID_MULTIRANGE_RANGE_CONTAINED_OP ||
+			operator == OID_MULTIRANGE_MULTIRANGE_CONTAINED_OP)
+		{
+			/* empty is contained by anything non-empty */
+			selec = (1.0 - empty_frac) * hist_selec + empty_frac;
+		}
+		else
+		{
+			/* with any other operator, empty Op non-empty matches nothing */
+			selec = (1.0 - empty_frac) * hist_selec;
+		}
+	}
+
+	/* all multirange operators are strict */
+	selec *= (1.0 - null_frac);
+
+	/* result should be in range, but make sure... */
+	CLAMP_PROBABILITY(selec);
+
+	return selec;
+}
+
+/*
+ * Calculate multirange operator selectivity using histograms of multirange bounds.
+ *
+ * This estimate is for the portion of values that are not empty and not
+ * NULL.
+ */
+static double
+calc_hist_selectivity(TypeCacheEntry *typcache, VariableStatData *vardata,
+					  const MultirangeType *constval, Oid operator)
+{
+	TypeCacheEntry *rng_typcache = typcache->rngtype;
+	AttStatsSlot	hslot;
+	AttStatsSlot	lslot;
+	int			nhist;
+	RangeBound *hist_lower;
+	RangeBound *hist_upper;
+	int			i;
+	RangeBound	const_lower;
+	RangeBound	const_upper;
+	bool		empty;
+	double		hist_selec;
+	int			range_count;
+	RangeType **ranges;
+	RangeType  *constrange;
+
+	/* Can't use the histogram with insecure multirange support functions */
+	if (!statistic_proc_security_check(vardata,
+									   rng_typcache->rng_cmp_proc_finfo.fn_oid))
+		return -1;
+	if (OidIsValid(rng_typcache->rng_subdiff_finfo.fn_oid) &&
+		!statistic_proc_security_check(vardata,
+									   rng_typcache->rng_subdiff_finfo.fn_oid))
+		return -1;
+
+	/* Try to get histogram of ranges */
+	if (!(HeapTupleIsValid(vardata->statsTuple) &&
+		  get_attstatsslot(&hslot, vardata->statsTuple,
+						   STATISTIC_KIND_BOUNDS_HISTOGRAM, InvalidOid,
+						   ATTSTATSSLOT_VALUES)))
+		return -1.0;
+
+	/* check that it's a histogram, not just a dummy entry */
+	if (hslot.nvalues < 2)
+	{
+		free_attstatsslot(&hslot);
+		return -1.0;
+	}
+
+	/*
+	 * Convert histogram of ranges into histograms of its lower and upper
+	 * bounds.
+	 */
+	nhist = hslot.nvalues;
+	hist_lower = (RangeBound *) palloc(sizeof(RangeBound) * nhist);
+	hist_upper = (RangeBound *) palloc(sizeof(RangeBound) * nhist);
+	for (i = 0; i < nhist; i++)
+	{
+		range_deserialize(rng_typcache, DatumGetRangeTypeP(hslot.values[i]),
+						  &hist_lower[i], &hist_upper[i], &empty);
+		/* The histogram should not contain any empty ranges */
+		if (empty)
+			elog(ERROR, "bounds histogram contains an empty range");
+	}
+
+	/* @> and @< also need a histogram of range lengths */
+	if (operator == OID_MULTIRANGE_CONTAINS_RANGE_OP ||
+		operator == OID_MULTIRANGE_CONTAINS_MULTIRANGE_OP ||
+		operator == OID_MULTIRANGE_RANGE_CONTAINED_OP ||
+		operator == OID_MULTIRANGE_MULTIRANGE_CONTAINED_OP)
+	{
+		if (!(HeapTupleIsValid(vardata->statsTuple) &&
+			  get_attstatsslot(&lslot, vardata->statsTuple,
+							   STATISTIC_KIND_RANGE_LENGTH_HISTOGRAM,
+							   InvalidOid,
+							   ATTSTATSSLOT_VALUES)))
+		{
+			free_attstatsslot(&hslot);
+			return -1.0;
+		}
+
+		/* check that it's a histogram, not just a dummy entry */
+		if (lslot.nvalues < 2)
+		{
+			free_attstatsslot(&lslot);
+			free_attstatsslot(&hslot);
+			return -1.0;
+		}
+	}
+	else
+		memset(&lslot, 0, sizeof(lslot));
+
+	/* Extract the bounds of the constant value. */
+	multirange_deserialize(constval, &range_count, &ranges);
+	Assert(range_count > 0);
+	constrange = range_union_internal(rng_typcache, ranges[0],
+			ranges[range_count - 1], false);
+	range_deserialize(rng_typcache, constrange, &const_lower, &const_upper, &empty);
+
+	/*
+	 * Calculate selectivity comparing the lower or upper bound of the
+	 * constant with the histogram of lower or upper bounds.
+	 */
+	switch (operator)
+	{
+		case OID_MULTIRANGE_LESS_OP:
+
+			/*
+			 * The regular b-tree comparison operators (<, <=, >, >=) compare
+			 * the lower bounds first, and the upper bounds for values with
+			 * equal lower bounds. Estimate that by comparing the lower bounds
+			 * only. This gives a fairly accurate estimate assuming there
+			 * aren't many rows with a lower bound equal to the constant's
+			 * lower bound.
+			 */
+			hist_selec =
+				calc_hist_selectivity_scalar(rng_typcache, &const_lower,
+											 hist_lower, nhist, false);
+			break;
+
+		case OID_MULTIRANGE_LESS_EQUAL_OP:
+			hist_selec =
+				calc_hist_selectivity_scalar(rng_typcache, &const_lower,
+											 hist_lower, nhist, true);
+			break;
+
+		case OID_MULTIRANGE_GREATER_OP:
+			hist_selec =
+				1 - calc_hist_selectivity_scalar(rng_typcache, &const_lower,
+												 hist_lower, nhist, false);
+			break;
+
+		case OID_MULTIRANGE_GREATER_EQUAL_OP:
+			hist_selec =
+				1 - calc_hist_selectivity_scalar(rng_typcache, &const_lower,
+												 hist_lower, nhist, true);
+			break;
+
+		case OID_RANGE_LEFT_MULTIRANGE_OP:
+		case OID_MULTIRANGE_LEFT_RANGE_OP:
+		case OID_MULTIRANGE_LEFT_MULTIRANGE_OP:
+			/* var << const when upper(var) < lower(const) */
+			hist_selec =
+				calc_hist_selectivity_scalar(rng_typcache, &const_lower,
+											 hist_upper, nhist, false);
+			break;
+
+		case OID_RANGE_RIGHT_MULTIRANGE_OP:
+		case OID_MULTIRANGE_RIGHT_RANGE_OP:
+		case OID_MULTIRANGE_RIGHT_MULTIRANGE_OP:
+			/* var >> const when lower(var) > upper(const) */
+			hist_selec =
+				1 - calc_hist_selectivity_scalar(rng_typcache, &const_upper,
+												 hist_lower, nhist, true);
+			break;
+
+		case OID_RANGE_OVERLAPS_RIGHT_MULTIRANGE_OP:
+		case OID_MULTIRANGE_OVERLAPS_RIGHT_RANGE_OP:
+		case OID_MULTIRANGE_OVERLAPS_RIGHT_MULTIRANGE_OP:
+			/* compare lower bounds */
+			hist_selec =
+				1 - calc_hist_selectivity_scalar(rng_typcache, &const_lower,
+												 hist_lower, nhist, false);
+			break;
+
+		case OID_RANGE_OVERLAPS_LEFT_MULTIRANGE_OP:
+		case OID_MULTIRANGE_OVERLAPS_LEFT_RANGE_OP:
+		case OID_MULTIRANGE_OVERLAPS_LEFT_MULTIRANGE_OP:
+			/* compare upper bounds */
+			hist_selec =
+				calc_hist_selectivity_scalar(rng_typcache, &const_upper,
+											 hist_upper, nhist, true);
+			break;
+
+		case OID_RANGE_OVERLAPS_MULTIRANGE_OP:
+		case OID_MULTIRANGE_OVERLAPS_RANGE_OP:
+		case OID_MULTIRANGE_OVERLAPS_MULTIRANGE_OP:
+		case OID_MULTIRANGE_CONTAINS_ELEM_OP:
+
+			/*
+			 * A && B <=> NOT (A << B OR A >> B).
+			 *
+			 * Since A << B and A >> B are mutually exclusive events we can
+			 * sum their probabilities to find probability of (A << B OR A >>
+			 * B).
+			 *
+			 * "multirange @> elem" is equivalent to "multirange && {[elem,elem]}".
+			 * The caller already constructed the singular range from the element
+			 * constant, so just treat it the same as &&.
+			 */
+			hist_selec =
+				calc_hist_selectivity_scalar(rng_typcache, &const_lower, hist_upper,
+											 nhist, false);
+			hist_selec +=
+				(1.0 - calc_hist_selectivity_scalar(rng_typcache, &const_upper, hist_lower,
+													nhist, true));
+			hist_selec = 1.0 - hist_selec;
+			break;
+
+		case OID_MULTIRANGE_CONTAINS_RANGE_OP:
+		case OID_MULTIRANGE_CONTAINS_MULTIRANGE_OP:
+			hist_selec =
+				calc_hist_selectivity_contains(rng_typcache, &const_lower,
+											   &const_upper, hist_lower, nhist,
+											   lslot.values, lslot.nvalues);
+			break;
+
+		case OID_MULTIRANGE_RANGE_CONTAINED_OP:
+		case OID_MULTIRANGE_MULTIRANGE_CONTAINED_OP:
+			if (const_lower.infinite)
+			{
+				/*
+				 * Lower bound no longer matters. Just estimate the fraction
+				 * with an upper bound <= const upper bound
+				 */
+				hist_selec =
+					calc_hist_selectivity_scalar(rng_typcache, &const_upper,
+												 hist_upper, nhist, true);
+			}
+			else if (const_upper.infinite)
+			{
+				hist_selec =
+					1.0 - calc_hist_selectivity_scalar(rng_typcache, &const_lower,
+													   hist_lower, nhist, false);
+			}
+			else
+			{
+				hist_selec =
+					calc_hist_selectivity_contained(rng_typcache, &const_lower,
+													&const_upper, hist_lower, nhist,
+													lslot.values, lslot.nvalues);
+			}
+			break;
+
+		default:
+			elog(ERROR, "unknown multirange operator %u", operator);
+			hist_selec = -1.0;	/* keep compiler quiet */
+			break;
+	}
+
+	free_attstatsslot(&lslot);
+	free_attstatsslot(&hslot);
+
+	return hist_selec;
+}
+
+
+/*
+ * Look up the fraction of values less than (or equal, if 'equal' argument
+ * is true) a given const in a histogram of range bounds.
+ */
+static double
+calc_hist_selectivity_scalar(TypeCacheEntry *typcache, const RangeBound *constbound,
+							 const RangeBound *hist, int hist_nvalues, bool equal)
+{
+	Selectivity selec;
+	int			index;
+
+	/*
+	 * Find the histogram bin the given constant falls into. Estimate
+	 * selectivity as the number of preceding whole bins.
+	 */
+	index = rbound_bsearch(typcache, constbound, hist, hist_nvalues, equal);
+	selec = (Selectivity) (Max(index, 0)) / (Selectivity) (hist_nvalues - 1);
+
+	/* Adjust using linear interpolation within the bin */
+	if (index >= 0 && index < hist_nvalues - 1)
+		selec += get_position(typcache, constbound, &hist[index],
+							  &hist[index + 1]) / (Selectivity) (hist_nvalues - 1);
+
+	return selec;
+}
+
+/*
+ * Binary search on an array of range bounds. Returns greatest index of range
+ * bound in array which is less(less or equal) than given range bound. If all
+ * range bounds in array are greater or equal(greater) than given range bound,
+ * return -1. When "equal" flag is set conditions in brackets are used.
+ *
+ * This function is used in scalar operator selectivity estimation. Another
+ * goal of this function is to find a histogram bin where to stop
+ * interpolation of portion of bounds which are less or equal to given bound.
+ */
+static int
+rbound_bsearch(TypeCacheEntry *typcache, const RangeBound *value, const RangeBound *hist,
+			   int hist_length, bool equal)
+{
+	int			lower = -1,
+				upper = hist_length - 1,
+				cmp,
+				middle;
+
+	while (lower < upper)
+	{
+		middle = (lower + upper + 1) / 2;
+		cmp = range_cmp_bounds(typcache, &hist[middle], value);
+
+		if (cmp < 0 || (equal && cmp == 0))
+			lower = middle;
+		else
+			upper = middle - 1;
+	}
+	return lower;
+}
+
+
+/*
+ * Binary search on length histogram. Returns greatest index of range length in
+ * histogram which is less than (less than or equal) the given length value. If
+ * all lengths in the histogram are greater than (greater than or equal) the
+ * given length, returns -1.
+ */
+static int
+length_hist_bsearch(Datum *length_hist_values, int length_hist_nvalues,
+					double value, bool equal)
+{
+	int			lower = -1,
+				upper = length_hist_nvalues - 1,
+				middle;
+
+	while (lower < upper)
+	{
+		double		middleval;
+
+		middle = (lower + upper + 1) / 2;
+
+		middleval = DatumGetFloat8(length_hist_values[middle]);
+		if (middleval < value || (equal && middleval <= value))
+			lower = middle;
+		else
+			upper = middle - 1;
+	}
+	return lower;
+}
+
+/*
+ * Get relative position of value in histogram bin in [0,1] range.
+ */
+static float8
+get_position(TypeCacheEntry *typcache, const RangeBound *value, const RangeBound *hist1,
+			 const RangeBound *hist2)
+{
+	bool		has_subdiff = OidIsValid(typcache->rng_subdiff_finfo.fn_oid);
+	float8		position;
+
+	if (!hist1->infinite && !hist2->infinite)
+	{
+		float8		bin_width;
+
+		/*
+		 * Both bounds are finite. Assuming the subtype's comparison function
+		 * works sanely, the value must be finite, too, because it lies
+		 * somewhere between the bounds.  If it doesn't, arbitrarily return
+		 * 0.5.
+		 */
+		if (value->infinite)
+			return 0.5;
+
+		/* Can't interpolate without subdiff function */
+		if (!has_subdiff)
+			return 0.5;
+
+		/* Calculate relative position using subdiff function. */
+		bin_width = DatumGetFloat8(FunctionCall2Coll(&typcache->rng_subdiff_finfo,
+													 typcache->rng_collation,
+													 hist2->val,
+													 hist1->val));
+		if (isnan(bin_width) || bin_width <= 0.0)
+			return 0.5;			/* punt for NaN or zero-width bin */
+
+		position = DatumGetFloat8(FunctionCall2Coll(&typcache->rng_subdiff_finfo,
+													typcache->rng_collation,
+													value->val,
+													hist1->val))
+			/ bin_width;
+
+		if (isnan(position))
+			return 0.5;			/* punt for NaN from subdiff, Inf/Inf, etc */
+
+		/* Relative position must be in [0,1] range */
+		position = Max(position, 0.0);
+		position = Min(position, 1.0);
+		return position;
+	}
+	else if (hist1->infinite && !hist2->infinite)
+	{
+		/*
+		 * Lower bin boundary is -infinite, upper is finite. If the value is
+		 * -infinite, return 0.0 to indicate it's equal to the lower bound.
+		 * Otherwise return 1.0 to indicate it's infinitely far from the lower
+		 * bound.
+		 */
+		return ((value->infinite && value->lower) ? 0.0 : 1.0);
+	}
+	else if (!hist1->infinite && hist2->infinite)
+	{
+		/* same as above, but in reverse */
+		return ((value->infinite && !value->lower) ? 1.0 : 0.0);
+	}
+	else
+	{
+		/*
+		 * If both bin boundaries are infinite, they should be equal to each
+		 * other, and the value should also be infinite and equal to both
+		 * bounds. (But don't Assert that, to avoid crashing if a user creates
+		 * a datatype with a broken comparison function).
+		 *
+		 * Assume the value to lie in the middle of the infinite bounds.
+		 */
+		return 0.5;
+	}
+}
+
+
+/*
+ * Get relative position of value in a length histogram bin in [0,1] range.
+ */
+static double
+get_len_position(double value, double hist1, double hist2)
+{
+	if (!isinf(hist1) && !isinf(hist2))
+	{
+		/*
+		 * Both bounds are finite. The value should be finite too, because it
+		 * lies somewhere between the bounds. If it doesn't, just return
+		 * something.
+		 */
+		if (isinf(value))
+			return 0.5;
+
+		return 1.0 - (hist2 - value) / (hist2 - hist1);
+	}
+	else if (isinf(hist1) && !isinf(hist2))
+	{
+		/*
+		 * Lower bin boundary is -infinite, upper is finite. Return 1.0 to
+		 * indicate the value is infinitely far from the lower bound.
+		 */
+		return 1.0;
+	}
+	else if (isinf(hist1) && isinf(hist2))
+	{
+		/* same as above, but in reverse */
+		return 0.0;
+	}
+	else
+	{
+		/*
+		 * If both bin boundaries are infinite, they should be equal to each
+		 * other, and the value should also be infinite and equal to both
+		 * bounds. (But don't Assert that, to avoid crashing unnecessarily if
+		 * the caller messes up)
+		 *
+		 * Assume the value to lie in the middle of the infinite bounds.
+		 */
+		return 0.5;
+	}
+}
+
+/*
+ * Measure distance between two range bounds.
+ */
+static float8
+get_distance(TypeCacheEntry *typcache, const RangeBound *bound1, const RangeBound *bound2)
+{
+	bool		has_subdiff = OidIsValid(typcache->rng_subdiff_finfo.fn_oid);
+
+	if (!bound1->infinite && !bound2->infinite)
+	{
+		/*
+		 * Neither bound is infinite, use subdiff function or return default
+		 * value of 1.0 if no subdiff is available.
+		 */
+		if (has_subdiff)
+		{
+			float8		res;
+
+			res = DatumGetFloat8(FunctionCall2Coll(&typcache->rng_subdiff_finfo,
+												   typcache->rng_collation,
+												   bound2->val,
+												   bound1->val));
+			/* Reject possible NaN result, also negative result */
+			if (isnan(res) || res < 0.0)
+				return 1.0;
+			else
+				return res;
+		}
+		else
+			return 1.0;
+	}
+	else if (bound1->infinite && bound2->infinite)
+	{
+		/* Both bounds are infinite */
+		if (bound1->lower == bound2->lower)
+			return 0.0;
+		else
+			return get_float8_infinity();
+	}
+	else
+	{
+		/* One bound is infinite, the other is not */
+		return get_float8_infinity();
+	}
+}
+
+/*
+ * Calculate the average of function P(x), in the interval [length1, length2],
+ * where P(x) is the fraction of tuples with length < x (or length <= x if
+ * 'equal' is true).
+ */
+static double
+calc_length_hist_frac(Datum *length_hist_values, int length_hist_nvalues,
+					  double length1, double length2, bool equal)
+{
+	double		frac;
+	double		A,
+				B,
+				PA,
+				PB;
+	double		pos;
+	int			i;
+	double		area;
+
+	Assert(length2 >= length1);
+
+	if (length2 < 0.0)
+		return 0.0;				/* shouldn't happen, but doesn't hurt to check */
+
+	/* All lengths in the table are <= infinite. */
+	if (isinf(length2) && equal)
+		return 1.0;
+
+	/*----------
+	 * The average of a function between A and B can be calculated by the
+	 * formula:
+	 *
+	 *			B
+	 *	  1		/
+	 * -------	| P(x)dx
+	 *	B - A	/
+	 *			A
+	 *
+	 * The geometrical interpretation of the integral is the area under the
+	 * graph of P(x). P(x) is defined by the length histogram. We calculate
+	 * the area in a piecewise fashion, iterating through the length histogram
+	 * bins. Each bin is a trapezoid:
+	 *
+	 *		 P(x2)
+	 *		  /|
+	 *		 / |
+	 * P(x1)/  |
+	 *	   |   |
+	 *	   |   |
+	 *	---+---+--
+	 *	   x1  x2
+	 *
+	 * where x1 and x2 are the boundaries of the current histogram, and P(x1)
+	 * and P(x1) are the cumulative fraction of tuples at the boundaries.
+	 *
+	 * The area of each trapezoid is 1/2 * (P(x2) + P(x1)) * (x2 - x1)
+	 *
+	 * The first bin contains the lower bound passed by the caller, so we
+	 * use linear interpolation between the previous and next histogram bin
+	 * boundary to calculate P(x1). Likewise for the last bin: we use linear
+	 * interpolation to calculate P(x2). For the bins in between, x1 and x2
+	 * lie on histogram bin boundaries, so P(x1) and P(x2) are simply:
+	 * P(x1) =	  (bin index) / (number of bins)
+	 * P(x2) = (bin index + 1 / (number of bins)
+	 */
+
+	/* First bin, the one that contains lower bound */
+	i = length_hist_bsearch(length_hist_values, length_hist_nvalues, length1, equal);
+	if (i >= length_hist_nvalues - 1)
+		return 1.0;
+
+	if (i < 0)
+	{
+		i = 0;
+		pos = 0.0;
+	}
+	else
+	{
+		/* interpolate length1's position in the bin */
+		pos = get_len_position(length1,
+							   DatumGetFloat8(length_hist_values[i]),
+							   DatumGetFloat8(length_hist_values[i + 1]));
+	}
+	PB = (((double) i) + pos) / (double) (length_hist_nvalues - 1);
+	B = length1;
+
+	/*
+	 * In the degenerate case that length1 == length2, simply return
+	 * P(length1). This is not merely an optimization: if length1 == length2,
+	 * we'd divide by zero later on.
+	 */
+	if (length2 == length1)
+		return PB;
+
+	/*
+	 * Loop through all the bins, until we hit the last bin, the one that
+	 * contains the upper bound. (if lower and upper bounds are in the same
+	 * bin, this falls out immediately)
+	 */
+	area = 0.0;
+	for (; i < length_hist_nvalues - 1; i++)
+	{
+		double		bin_upper = DatumGetFloat8(length_hist_values[i + 1]);
+
+		/* check if we've reached the last bin */
+		if (!(bin_upper < length2 || (equal && bin_upper <= length2)))
+			break;
+
+		/* the upper bound of previous bin is the lower bound of this bin */
+		A = B;
+		PA = PB;
+
+		B = bin_upper;
+		PB = (double) i / (double) (length_hist_nvalues - 1);
+
+		/*
+		 * Add the area of this trapezoid to the total. The point of the
+		 * if-check is to avoid NaN, in the corner case that PA == PB == 0,
+		 * and B - A == Inf. The area of a zero-height trapezoid (PA == PB ==
+		 * 0) is zero, regardless of the width (B - A).
+		 */
+		if (PA > 0 || PB > 0)
+			area += 0.5 * (PB + PA) * (B - A);
+	}
+
+	/* Last bin */
+	A = B;
+	PA = PB;
+
+	B = length2;				/* last bin ends at the query upper bound */
+	if (i >= length_hist_nvalues - 1)
+		pos = 0.0;
+	else
+	{
+		if (DatumGetFloat8(length_hist_values[i]) == DatumGetFloat8(length_hist_values[i + 1]))
+			pos = 0.0;
+		else
+			pos = get_len_position(length2, DatumGetFloat8(length_hist_values[i]), DatumGetFloat8(length_hist_values[i + 1]));
+	}
+	PB = (((double) i) + pos) / (double) (length_hist_nvalues - 1);
+
+	if (PA > 0 || PB > 0)
+		area += 0.5 * (PB + PA) * (B - A);
+
+	/*
+	 * Ok, we have calculated the area, ie. the integral. Divide by width to
+	 * get the requested average.
+	 *
+	 * Avoid NaN arising from infinite / infinite. This happens at least if
+	 * length2 is infinite. It's not clear what the correct value would be in
+	 * that case, so 0.5 seems as good as any value.
+	 */
+	if (isinf(area) && isinf(length2))
+		frac = 0.5;
+	else
+		frac = area / (length2 - length1);
+
+	return frac;
+}
+
+/*
+ * Calculate selectivity of "var <@ const" operator, ie. estimate the fraction
+ * of multiranges that fall within the constant lower and upper bounds. This uses
+ * the histograms of range lower bounds and range lengths, on the assumption
+ * that the range lengths are independent of the lower bounds.
+ *
+ * The caller has already checked that constant lower and upper bounds are
+ * finite.
+ */
+static double
+calc_hist_selectivity_contained(TypeCacheEntry *typcache,
+								const RangeBound *lower, RangeBound *upper,
+								const RangeBound *hist_lower, int hist_nvalues,
+								Datum *length_hist_values, int length_hist_nvalues)
+{
+	int			i,
+				upper_index;
+	float8		prev_dist;
+	double		bin_width;
+	double		upper_bin_width;
+	double		sum_frac;
+
+	/*
+	 * Begin by finding the bin containing the upper bound, in the lower bound
+	 * histogram. Any range with a lower bound > constant upper bound can't
+	 * match, ie. there are no matches in bins greater than upper_index.
+	 */
+	upper->inclusive = !upper->inclusive;
+	upper->lower = true;
+	upper_index = rbound_bsearch(typcache, upper, hist_lower, hist_nvalues,
+								 false);
+
+	/*
+	 * If the upper bound value is below the histogram's lower limit, there
+	 * are no matches.
+	 */
+	if (upper_index < 0)
+		return 0.0;
+
+	/*
+	 * If the upper bound value is at or beyond the histogram's upper limit,
+	 * start our loop at the last actual bin, as though the upper bound were
+	 * within that bin; get_position will clamp its result to 1.0 anyway.
+	 * (This corresponds to assuming that the data population above the
+	 * histogram's upper limit is empty, exactly like what we just assumed for
+	 * the lower limit.)
+	 */
+	upper_index = Min(upper_index, hist_nvalues - 2);
+
+	/*
+	 * Calculate upper_bin_width, ie. the fraction of the (upper_index,
+	 * upper_index + 1) bin which is greater than upper bound of query range
+	 * using linear interpolation of subdiff function.
+	 */
+	upper_bin_width = get_position(typcache, upper,
+								   &hist_lower[upper_index],
+								   &hist_lower[upper_index + 1]);
+
+	/*
+	 * In the loop, dist and prev_dist are the distance of the "current" bin's
+	 * lower and upper bounds from the constant upper bound.
+	 *
+	 * bin_width represents the width of the current bin. Normally it is 1.0,
+	 * meaning a full width bin, but can be less in the corner cases: start
+	 * and end of the loop. We start with bin_width = upper_bin_width, because
+	 * we begin at the bin containing the upper bound.
+	 */
+	prev_dist = 0.0;
+	bin_width = upper_bin_width;
+
+	sum_frac = 0.0;
+	for (i = upper_index; i >= 0; i--)
+	{
+		double		dist;
+		double		length_hist_frac;
+		bool		final_bin = false;
+
+		/*
+		 * dist -- distance from upper bound of query range to lower bound of
+		 * the current bin in the lower bound histogram. Or to the lower bound
+		 * of the constant range, if this is the final bin, containing the
+		 * constant lower bound.
+		 */
+		if (range_cmp_bounds(typcache, &hist_lower[i], lower) < 0)
+		{
+			dist = get_distance(typcache, lower, upper);
+
+			/*
+			 * Subtract from bin_width the portion of this bin that we want to
+			 * ignore.
+			 */
+			bin_width -= get_position(typcache, lower, &hist_lower[i],
+									  &hist_lower[i + 1]);
+			if (bin_width < 0.0)
+				bin_width = 0.0;
+			final_bin = true;
+		}
+		else
+			dist = get_distance(typcache, &hist_lower[i], upper);
+
+		/*
+		 * Estimate the fraction of tuples in this bin that are narrow enough
+		 * to not exceed the distance to the upper bound of the query range.
+		 */
+		length_hist_frac = calc_length_hist_frac(length_hist_values,
+												 length_hist_nvalues,
+												 prev_dist, dist, true);
+
+		/*
+		 * Add the fraction of tuples in this bin, with a suitable length, to
+		 * the total.
+		 */
+		sum_frac += length_hist_frac * bin_width / (double) (hist_nvalues - 1);
+
+		if (final_bin)
+			break;
+
+		bin_width = 1.0;
+		prev_dist = dist;
+	}
+
+	return sum_frac;
+}
+
+/*
+ * Calculate selectivity of "var @> const" operator, ie. estimate the fraction
+ * of multiranges that contain the constant lower and upper bounds. This uses
+ * the histograms of range lower bounds and range lengths, on the assumption
+ * that the range lengths are independent of the lower bounds.
+ */
+static double
+calc_hist_selectivity_contains(TypeCacheEntry *typcache,
+							   const RangeBound *lower, const RangeBound *upper,
+							   const RangeBound *hist_lower, int hist_nvalues,
+							   Datum *length_hist_values, int length_hist_nvalues)
+{
+	int			i,
+				lower_index;
+	double		bin_width,
+				lower_bin_width;
+	double		sum_frac;
+	float8		prev_dist;
+
+	/* Find the bin containing the lower bound of query range. */
+	lower_index = rbound_bsearch(typcache, lower, hist_lower, hist_nvalues,
+								 true);
+
+	/*
+	 * If the lower bound value is below the histogram's lower limit, there
+	 * are no matches.
+	 */
+	if (lower_index < 0)
+		return 0.0;
+
+	/*
+	 * If the lower bound value is at or beyond the histogram's upper limit,
+	 * start our loop at the last actual bin, as though the upper bound were
+	 * within that bin; get_position will clamp its result to 1.0 anyway.
+	 * (This corresponds to assuming that the data population above the
+	 * histogram's upper limit is empty, exactly like what we just assumed for
+	 * the lower limit.)
+	 */
+	lower_index = Min(lower_index, hist_nvalues - 2);
+
+	/*
+	 * Calculate lower_bin_width, ie. the fraction of the of (lower_index,
+	 * lower_index + 1) bin which is greater than lower bound of query range
+	 * using linear interpolation of subdiff function.
+	 */
+	lower_bin_width = get_position(typcache, lower, &hist_lower[lower_index],
+								   &hist_lower[lower_index + 1]);
+
+	/*
+	 * Loop through all the lower bound bins, smaller than the query lower
+	 * bound. In the loop, dist and prev_dist are the distance of the
+	 * "current" bin's lower and upper bounds from the constant upper bound.
+	 * We begin from query lower bound, and walk backwards, so the first bin's
+	 * upper bound is the query lower bound, and its distance to the query
+	 * upper bound is the length of the query range.
+	 *
+	 * bin_width represents the width of the current bin. Normally it is 1.0,
+	 * meaning a full width bin, except for the first bin, which is only
+	 * counted up to the constant lower bound.
+	 */
+	prev_dist = get_distance(typcache, lower, upper);
+	sum_frac = 0.0;
+	bin_width = lower_bin_width;
+	for (i = lower_index; i >= 0; i--)
+	{
+		float8		dist;
+		double		length_hist_frac;
+
+		/*
+		 * dist -- distance from upper bound of query range to current value
+		 * of lower bound histogram or lower bound of query range (if we've
+		 * reach it).
+		 */
+		dist = get_distance(typcache, &hist_lower[i], upper);
+
+		/*
+		 * Get average fraction of length histogram which covers intervals
+		 * longer than (or equal to) distance to upper bound of query range.
+		 */
+		length_hist_frac =
+			1.0 - calc_length_hist_frac(length_hist_values,
+										length_hist_nvalues,
+										prev_dist, dist, false);
+
+		sum_frac += length_hist_frac * bin_width / (double) (hist_nvalues - 1);
+
+		bin_width = 1.0;
+		prev_dist = dist;
+	}
+
+	return sum_frac;
+}
diff --git a/src/backend/utils/adt/pg_upgrade_support.c b/src/backend/utils/adt/pg_upgrade_support.c
index 18f2ee8226..248ea7f44a 100644
--- a/src/backend/utils/adt/pg_upgrade_support.c
+++ b/src/backend/utils/adt/pg_upgrade_support.c
@@ -51,6 +51,28 @@ binary_upgrade_set_next_array_pg_type_oid(PG_FUNCTION_ARGS)
 	PG_RETURN_VOID();
 }
 
+Datum
+binary_upgrade_set_next_multirange_pg_type_oid(PG_FUNCTION_ARGS)
+{
+	Oid			typoid = PG_GETARG_OID(0);
+
+	CHECK_IS_BINARY_UPGRADE;
+	binary_upgrade_next_mrng_pg_type_oid = typoid;
+
+	PG_RETURN_VOID();
+}
+
+Datum
+binary_upgrade_set_next_multirange_array_pg_type_oid(PG_FUNCTION_ARGS)
+{
+	Oid			typoid = PG_GETARG_OID(0);
+
+	CHECK_IS_BINARY_UPGRADE;
+	binary_upgrade_next_mrng_array_pg_type_oid = typoid;
+
+	PG_RETURN_VOID();
+}
+
 Datum
 binary_upgrade_set_next_toast_pg_type_oid(PG_FUNCTION_ARGS)
 {
diff --git a/src/backend/utils/adt/pseudotypes.c b/src/backend/utils/adt/pseudotypes.c
index 3d6b2f9093..99a93271fe 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"
 
 
 /*
@@ -227,6 +228,43 @@ anycompatiblerange_out(PG_FUNCTION_ARGS)
 	return range_out(fcinfo);
 }
 
+/*
+ * anycompatiblemultirange
+ *
+ * We may as well allow output, since multirange_out will in fact work.
+ */
+PSEUDOTYPE_DUMMY_INPUT_FUNC(anycompatiblemultirange);
+
+Datum
+anycompatiblemultirange_out(PG_FUNCTION_ARGS)
+{
+	return multirange_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
  *
diff --git a/src/backend/utils/adt/rangetypes.c b/src/backend/utils/adt/rangetypes.c
index 01ad8bc240..0af3c67296 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
 {
@@ -431,19 +429,37 @@ range_lower(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
 	TypeCacheEntry *typcache;
+	bool		isnull;
+	Datum		result;
+
+	typcache = range_get_typcache(fcinfo, RangeTypeGetOid(r1));
+
+	result = range_lower_internal(typcache, r1, &isnull);
+
+	if (isnull)
+		PG_RETURN_NULL();
+	else
+		PG_RETURN_DATUM(result);
+}
+
+Datum
+range_lower_internal(TypeCacheEntry *typcache, const RangeType *r1, bool *isnull)
+{
 	RangeBound	lower;
 	RangeBound	upper;
 	bool		empty;
 
-	typcache = range_get_typcache(fcinfo, RangeTypeGetOid(r1));
-
 	range_deserialize(typcache, r1, &lower, &upper, &empty);
 
 	/* Return NULL if there's no finite lower bound */
 	if (empty || lower.infinite)
-		PG_RETURN_NULL();
+	{
+		*isnull = true;
+		return 0;
+	}
 
-	PG_RETURN_DATUM(lower.val);
+	*isnull = false;
+	return lower.val;
 }
 
 /* extract upper bound value */
@@ -452,19 +468,37 @@ range_upper(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
 	TypeCacheEntry *typcache;
+	bool		isnull;
+	Datum		result;
+
+	typcache = range_get_typcache(fcinfo, RangeTypeGetOid(r1));
+
+	result = range_upper_internal(typcache, r1, &isnull);
+
+	if (isnull)
+		PG_RETURN_NULL();
+	else
+		PG_RETURN_DATUM(result);
+}
+
+Datum
+range_upper_internal(TypeCacheEntry *typcache, const RangeType *r1, bool *isnull)
+{
 	RangeBound	lower;
 	RangeBound	upper;
 	bool		empty;
 
-	typcache = range_get_typcache(fcinfo, RangeTypeGetOid(r1));
-
 	range_deserialize(typcache, r1, &lower, &upper, &empty);
 
 	/* Return NULL if there's no finite upper bound */
 	if (empty || upper.infinite)
-		PG_RETURN_NULL();
+	{
+		*isnull = true;
+		return 0;
+	}
 
-	PG_RETURN_DATUM(upper.val);
+	*isnull = false;
+	return upper.val;
 }
 
 
@@ -475,9 +509,8 @@ Datum
 range_empty(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
-	char		flags = range_get_flags(r1);
 
-	PG_RETURN_BOOL(flags & RANGE_EMPTY);
+	PG_RETURN_BOOL(range_has_flag(r1, RANGE_EMPTY));
 }
 
 /* is lower bound inclusive? */
@@ -485,9 +518,8 @@ Datum
 range_lower_inc(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
-	char		flags = range_get_flags(r1);
 
-	PG_RETURN_BOOL(flags & RANGE_LB_INC);
+	PG_RETURN_BOOL(range_has_flag(r1, RANGE_LB_INC));
 }
 
 /* is upper bound inclusive? */
@@ -495,9 +527,8 @@ Datum
 range_upper_inc(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
-	char		flags = range_get_flags(r1);
 
-	PG_RETURN_BOOL(flags & RANGE_UB_INC);
+	PG_RETURN_BOOL(range_has_flag(r1, RANGE_UB_INC));
 }
 
 /* is lower bound infinite? */
@@ -505,9 +536,8 @@ Datum
 range_lower_inf(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
-	char		flags = range_get_flags(r1);
 
-	PG_RETURN_BOOL(flags & RANGE_LB_INF);
+	PG_RETURN_BOOL(range_has_flag(r1, RANGE_LB_INF));
 }
 
 /* is upper bound infinite? */
@@ -515,9 +545,8 @@ Datum
 range_upper_inf(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
-	char		flags = range_get_flags(r1);
 
-	PG_RETURN_BOOL(flags & RANGE_UB_INF);
+	PG_RETURN_BOOL(range_has_flag(r1, RANGE_UB_INF));
 }
 
 
@@ -957,7 +986,25 @@ range_minus(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
 	RangeType  *r2 = PG_GETARG_RANGE_P(1);
+	RangeType  *ret;
 	TypeCacheEntry *typcache;
+
+	/* 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));
+
+	ret = range_minus_internal(typcache, r1, r2);
+	if (ret)
+		PG_RETURN_RANGE_P(ret);
+	else
+		PG_RETURN_NULL();
+}
+
+RangeType *
+range_minus_internal(TypeCacheEntry *typcache, RangeType *r1, RangeType *r2)
+{
 	RangeBound	lower1,
 				lower2;
 	RangeBound	upper1,
@@ -969,18 +1016,12 @@ range_minus(PG_FUNCTION_ARGS)
 				cmp_u1l2,
 				cmp_u1u2;
 
-	/* 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));
-
 	range_deserialize(typcache, r1, &lower1, &upper1, &empty1);
 	range_deserialize(typcache, r2, &lower2, &upper2, &empty2);
 
 	/* if either is empty, r1 is the correct answer */
 	if (empty1 || empty2)
-		PG_RETURN_RANGE_P(r1);
+		return r1;
 
 	cmp_l1l2 = range_cmp_bounds(typcache, &lower1, &lower2);
 	cmp_l1u2 = range_cmp_bounds(typcache, &lower1, &upper2);
@@ -993,34 +1034,34 @@ range_minus(PG_FUNCTION_ARGS)
 				 errmsg("result of range difference would not be contiguous")));
 
 	if (cmp_l1u2 > 0 || cmp_u1l2 < 0)
-		PG_RETURN_RANGE_P(r1);
+		return r1;
 
 	if (cmp_l1l2 >= 0 && cmp_u1u2 <= 0)
-		PG_RETURN_RANGE_P(make_empty_range(typcache));
+		return make_empty_range(typcache);
 
 	if (cmp_l1l2 <= 0 && cmp_u1l2 >= 0 && cmp_u1u2 <= 0)
 	{
 		lower2.inclusive = !lower2.inclusive;
 		lower2.lower = false;	/* it will become the upper bound */
-		PG_RETURN_RANGE_P(make_range(typcache, &lower1, &lower2, false));
+		return make_range(typcache, &lower1, &lower2, false);
 	}
 
 	if (cmp_l1l2 >= 0 && cmp_u1u2 >= 0 && cmp_l1u2 <= 0)
 	{
 		upper2.inclusive = !upper2.inclusive;
 		upper2.lower = true;	/* it will become the lower bound */
-		PG_RETURN_RANGE_P(make_range(typcache, &upper2, &upper1, false));
+		return make_range(typcache, &upper2, &upper1, false);
 	}
 
 	elog(ERROR, "unexpected case in range_minus");
-	PG_RETURN_NULL();
+	return NULL;
 }
 
 /*
  * Set union.  If strict is true, it is an error that the two input ranges
  * are not adjacent or overlapping.
  */
-static RangeType *
+RangeType *
 range_union_internal(TypeCacheEntry *typcache, RangeType *r1, RangeType *r2,
 					 bool strict)
 {
@@ -1101,6 +1142,19 @@ range_intersect(PG_FUNCTION_ARGS)
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
 	RangeType  *r2 = PG_GETARG_RANGE_P(1);
 	TypeCacheEntry *typcache;
+
+	/* 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));
+
+	PG_RETURN_RANGE_P(range_intersect_internal(typcache, r1, r2));
+}
+
+RangeType *
+range_intersect_internal(TypeCacheEntry *typcache, const RangeType *r1, const RangeType *r2)
+{
 	RangeBound	lower1,
 				lower2;
 	RangeBound	upper1,
@@ -1110,17 +1164,11 @@ range_intersect(PG_FUNCTION_ARGS)
 	RangeBound *result_lower;
 	RangeBound *result_upper;
 
-	/* 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));
-
 	range_deserialize(typcache, r1, &lower1, &upper1, &empty1);
 	range_deserialize(typcache, r2, &lower2, &upper2, &empty2);
 
-	if (empty1 || empty2 || !DatumGetBool(range_overlaps(fcinfo)))
-		PG_RETURN_RANGE_P(make_empty_range(typcache));
+	if (empty1 || empty2 || !range_overlaps_internal(typcache, r1, r2))
+		return make_empty_range(typcache);
 
 	if (range_cmp_bounds(typcache, &lower1, &lower2) >= 0)
 		result_lower = &lower1;
@@ -1132,9 +1180,81 @@ range_intersect(PG_FUNCTION_ARGS)
 	else
 		result_upper = &upper2;
 
-	PG_RETURN_RANGE_P(make_range(typcache, result_lower, result_upper, false));
+	return make_range(typcache, result_lower, result_upper, false);
+}
+
+/* range, range -> range, range functions */
+
+/*
+ * range_split_internal - if r2 intersects the middle of r1, leaving non-empty
+ * ranges on both sides, then return true and set output1 and output2 to the
+ * results of r1 - r2 (in order). Otherwise return false and don't set output1
+ * or output2. Neither input range should be empty.
+ */
+bool
+range_split_internal(TypeCacheEntry *typcache, const RangeType *r1, const RangeType *r2,
+					 RangeType **output1, RangeType **output2)
+{
+	RangeBound	lower1,
+				lower2;
+	RangeBound	upper1,
+				upper2;
+	bool		empty1,
+				empty2;
+
+	range_deserialize(typcache, r1, &lower1, &upper1, &empty1);
+	range_deserialize(typcache, r2, &lower2, &upper2, &empty2);
+
+	if (range_cmp_bounds(typcache, &lower1, &lower2) < 0 &&
+		range_cmp_bounds(typcache, &upper1, &upper2) > 0)
+	{
+		/*
+		 * Need to invert inclusive/exclusive for the lower2 and upper2
+		 * points. They can't be infinite though. We're allowed to overwrite
+		 * these RangeBounds since they only exist locally.
+		 */
+		lower2.inclusive = !lower2.inclusive;
+		lower2.lower = false;
+		upper2.inclusive = !upper2.inclusive;
+		upper2.lower = true;
+
+		*output1 = make_range(typcache, &lower1, &lower2, false);
+		*output2 = make_range(typcache, &upper2, &upper1, false);
+		return true;
+	}
+
+	return false;
 }
 
+/* range -> range aggregate functions */
+
+Datum
+range_intersect_agg_transfn(PG_FUNCTION_ARGS)
+{
+	MemoryContext aggContext;
+	Oid			rngtypoid;
+	TypeCacheEntry *typcache;
+	RangeType  *result;
+	RangeType  *current;
+
+	if (!AggCheckCallContext(fcinfo, &aggContext))
+		elog(ERROR, "range_intersect_agg_transfn called in non-aggregate context");
+
+	rngtypoid = get_fn_expr_argtype(fcinfo->flinfo, 1);
+	if (!type_is_range(rngtypoid))
+		ereport(ERROR, (errmsg("range_intersect_agg must be called with a range")));
+
+	typcache = range_get_typcache(fcinfo, rngtypoid);
+
+	/* strictness ensures these are non-null */
+	result = PG_GETARG_RANGE_P(0);
+	current = PG_GETARG_RANGE_P(1);
+
+	result = range_intersect_internal(typcache, result, current);
+	PG_RETURN_RANGE_P(result);
+}
+
+
 /* Btree support */
 
 /* btree comparator */
@@ -1144,12 +1264,6 @@ range_cmp(PG_FUNCTION_ARGS)
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
 	RangeType  *r2 = PG_GETARG_RANGE_P(1);
 	TypeCacheEntry *typcache;
-	RangeBound	lower1,
-				lower2;
-	RangeBound	upper1,
-				upper2;
-	bool		empty1,
-				empty2;
 	int			cmp;
 
 	check_stack_depth();		/* recurses when subtype is a range type */
@@ -1160,6 +1274,28 @@ range_cmp(PG_FUNCTION_ARGS)
 
 	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, const RangeType *r1, const 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);
 
@@ -1177,10 +1313,7 @@ range_cmp(PG_FUNCTION_ARGS)
 			cmp = range_cmp_bounds(typcache, &upper1, &upper2);
 	}
 
-	PG_FREE_IF_COPY(r1, 0);
-	PG_FREE_IF_COPY(r2, 1);
-
-	PG_RETURN_INT32(cmp);
+	return cmp;
 }
 
 /* inequality operators using the range_cmp function */
@@ -1218,13 +1351,10 @@ range_gt(PG_FUNCTION_ARGS)
 
 /* Hash support */
 
-/* hash a range value */
-Datum
-hash_range(PG_FUNCTION_ARGS)
+uint32
+hash_range_internal(TypeCacheEntry *typcache, const RangeType *r)
 {
-	RangeType  *r = PG_GETARG_RANGE_P(0);
 	uint32		result;
-	TypeCacheEntry *typcache;
 	TypeCacheEntry *scache;
 	RangeBound	lower;
 	RangeBound	upper;
@@ -1233,10 +1363,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 +1404,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, const RangeType *r, Datum seed)
+{
+	uint64		result;
 	TypeCacheEntry *scache;
 	RangeBound	lower;
 	RangeBound	upper;
@@ -1300,10 +1433,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 +1471,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));
 }
 
 /*
@@ -1769,6 +1916,21 @@ range_get_flags(const RangeType *range)
 	return *((char *) range + VARSIZE(range) - 1);
 }
 
+/*
+ * range_has_flag: set whether a range has a specific flag.
+ *
+ * This lets expose some of our functions that just check flags
+ * to the rest of the code base (like to multiranges)
+ * without writing full-fledged *_internal versions.
+ */
+bool
+range_has_flag(const RangeType *r1, char flag)
+{
+	char		flags = range_get_flags(r1);
+
+	return flags & flag;
+}
+
 /*
  * range_set_contain_empty: set the RANGE_CONTAIN_EMPTY bit in the value.
  *
@@ -1937,6 +2099,46 @@ range_cmp_bound_values(TypeCacheEntry *typcache, const RangeBound *b1,
 										   b1->val, b2->val));
 }
 
+/*
+ * qsort callback for sorting ranges.
+ *
+ * Two empty ranges compare equal; an empty range sorts to the left of any
+ * non-empty range.  Two non-empty ranges are sorted by lower bound first
+ * and by upper bound next.
+ */
+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/adt/rangetypes_typanalyze.c b/src/backend/utils/adt/rangetypes_typanalyze.c
index 57413b0720..9237648536 100644
--- a/src/backend/utils/adt/rangetypes_typanalyze.c
+++ b/src/backend/utils/adt/rangetypes_typanalyze.c
@@ -30,6 +30,7 @@
 #include "utils/fmgrprotos.h"
 #include "utils/lsyscache.h"
 #include "utils/rangetypes.h"
+#include "utils/multirangetypes.h"
 
 static int	float8_qsort_cmp(const void *a1, const void *a2);
 static int	range_bound_qsort_cmp(const void *a1, const void *a2, void *arg);
@@ -60,6 +61,33 @@ range_typanalyze(PG_FUNCTION_ARGS)
 	PG_RETURN_BOOL(true);
 }
 
+/*
+ * multirange_typanalyze -- typanalyze function for multirange columns
+ *
+ * We do the same analysis as for ranges, but on the smallest range that
+ * completely includes the multirange.
+ */
+Datum
+multirange_typanalyze(PG_FUNCTION_ARGS)
+{
+	VacAttrStats *stats = (VacAttrStats *) PG_GETARG_POINTER(0);
+	TypeCacheEntry *typcache;
+	Form_pg_attribute attr = stats->attr;
+
+	/* Get information about multirange type; note column might be a domain */
+	typcache = multirange_get_typcache(fcinfo, getBaseType(stats->attrtypid));
+
+	if (attr->attstattarget < 0)
+		attr->attstattarget = default_statistics_target;
+
+	stats->compute_stats = compute_range_stats;
+	stats->extra_data = typcache;
+	/* same as in std_typanalyze */
+	stats->minrows = 300 * attr->attstattarget;
+
+	PG_RETURN_BOOL(true);
+}
+
 /*
  * Comparison function for sorting float8s, used for range lengths.
  */
@@ -98,7 +126,8 @@ compute_range_stats(VacAttrStats *stats, AnalyzeAttrFetchFunc fetchfunc,
 					int samplerows, double totalrows)
 {
 	TypeCacheEntry *typcache = (TypeCacheEntry *) stats->extra_data;
-	bool		has_subdiff = OidIsValid(typcache->rng_subdiff_finfo.fn_oid);
+	TypeCacheEntry *mltrng_typcache = NULL;
+	bool		has_subdiff;
 	int			null_cnt = 0;
 	int			non_null_cnt = 0;
 	int			non_empty_cnt = 0;
@@ -112,6 +141,15 @@ compute_range_stats(VacAttrStats *stats, AnalyzeAttrFetchFunc fetchfunc,
 			   *uppers;
 	double		total_width = 0;
 
+	if (typcache->typtype == TYPTYPE_MULTIRANGE)
+	{
+		mltrng_typcache = typcache;
+		typcache = typcache->rngtype;
+	}
+	else
+		Assert(typcache->typtype == TYPTYPE_RANGE);
+	has_subdiff = OidIsValid(typcache->rng_subdiff_finfo.fn_oid);
+
 	/* Allocate memory to hold range bounds and lengths of the sample ranges. */
 	lowers = (RangeBound *) palloc(sizeof(RangeBound) * samplerows);
 	uppers = (RangeBound *) palloc(sizeof(RangeBound) * samplerows);
@@ -123,6 +161,7 @@ compute_range_stats(VacAttrStats *stats, AnalyzeAttrFetchFunc fetchfunc,
 		Datum		value;
 		bool		isnull,
 					empty;
+		MultirangeType *multirange;
 		RangeType  *range;
 		RangeBound	lower,
 					upper;
@@ -145,7 +184,23 @@ compute_range_stats(VacAttrStats *stats, AnalyzeAttrFetchFunc fetchfunc,
 		total_width += VARSIZE_ANY(DatumGetPointer(value));
 
 		/* Get range and deserialize it for further analysis. */
-		range = DatumGetRangeTypeP(value);
+		if (mltrng_typcache != NULL)
+		{
+			/* Treat multiranges like a big range without gaps. */
+			multirange = DatumGetMultirangeTypeP(value);
+			if (MultirangeIsEmpty(multirange))
+				range = make_empty_range(typcache);
+			else
+			{
+				int	range_count;
+				RangeType **ranges;
+				multirange_deserialize(multirange, &range_count, &ranges);
+				range = range_union_internal(typcache, ranges[0],
+						ranges[range_count - 1], false);
+			}
+		}
+		else
+			range = DatumGetRangeTypeP(value);
 		range_deserialize(typcache, range, &lower, &upper, &empty);
 
 		if (!empty)
@@ -262,6 +317,13 @@ compute_range_stats(VacAttrStats *stats, AnalyzeAttrFetchFunc fetchfunc,
 			stats->stakind[slot_idx] = STATISTIC_KIND_BOUNDS_HISTOGRAM;
 			stats->stavalues[slot_idx] = bound_hist_values;
 			stats->numvalues[slot_idx] = num_hist;
+
+			/* Store ranges even if we're analyzing a multirange column */
+			stats->statypid[slot_idx] = typcache->type_id;
+			stats->statyplen[slot_idx] = typcache->typlen;
+			stats->statypbyval[slot_idx] = typcache->typbyval;
+			stats->statypalign[slot_idx] = 'd';
+
 			slot_idx++;
 		}
 
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index f3bf413829..e700f88122 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -2578,6 +2578,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
  *
@@ -3220,7 +3230,7 @@ get_namespace_name_or_temp(Oid nspid)
 		return get_namespace_name(nspid);
 }
 
-/*				---------- PG_RANGE CACHE ----------				 */
+/*				---------- PG_RANGE CACHES ----------				 */
 
 /*
  * get_range_subtype
@@ -3273,6 +3283,56 @@ get_range_collation(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->rngmultitypid;
+		ReleaseSysCache(tp);
+		return result;
+	}
+	else
+		return InvalidOid;
+}
+
+/*
+ * get_range_multirange_subtype
+ *		Returns the subtype of a given multirange type
+ *
+ * Returns InvalidOid if the type is not a multirange type.
+ */
+Oid
+get_range_multirange_subtype(Oid multirangeOid)
+{
+	HeapTuple	tp;
+
+	tp = SearchSysCache1(RANGEMULTIRANGE, 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 53d9ddf159..7654331a59 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -651,6 +651,18 @@ static const struct cachedesc cacheinfo[] = {
 		},
 		64
 	},
+	{RangeRelationId,			/* RANGEMULTIRANGE */
+		RangeMultirangeTypidIndexId,
+		1,
+		{
+			Anum_pg_range_rngmultitypid,
+			0,
+			0,
+			0
+		},
+		4
+	},
+
 	{RangeRelationId,			/* RANGETYPE */
 		RangeTypidIndexId,
 		1,
diff --git a/src/backend/utils/cache/typcache.c b/src/backend/utils/cache/typcache.c
index f51248b70d..2fb87c8453 100644
--- a/src/backend/utils/cache/typcache.c
+++ b/src/backend/utils/cache/typcache.c
@@ -286,6 +286,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);
@@ -302,6 +303,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 TypeCacheTypCallback(Datum arg, int cacheid, uint32 hashvalue);
 static void TypeCacheOpcCallback(Datum arg, int cacheid, uint32 hashvalue);
@@ -553,8 +557,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))
@@ -591,7 +595,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))
@@ -616,7 +620,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))
@@ -641,7 +645,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))
@@ -693,6 +697,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;
@@ -737,6 +748,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;
@@ -816,6 +834,16 @@ lookup_type_cache(Oid type_id, int flags)
 			(void) lookup_type_cache(typentry->rngelemtype->type_id, 0);
 	}
 
+	/*
+	 * 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
 	 */
@@ -927,6 +955,22 @@ load_rangetype_info(TypeCacheEntry *typentry)
 	typentry->rngelemtype = lookup_type_cache(subtypeOid, 0);
 }
 
+/*
+ * load_multirangetype_info --- helper routine to set up multirange type
+ * information
+ */
+static void
+load_multirangetype_info(TypeCacheEntry *typentry)
+{
+	Oid			rangetypeOid;
+
+	rangetypeOid = get_range_multirange_subtype(typentry->type_id);
+	if (!OidIsValid(rangetypeOid))
+		elog(ERROR, "cache lookup failed for multirange type %u",
+			 typentry->type_id);
+
+	typentry->rngtype = lookup_type_cache(rangetypeOid, TYPECACHE_RANGE_INFO);
+}
 
 /*
  * load_domaintype_info --- helper routine to set up domain constraint info
@@ -1527,11 +1571,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
@@ -1574,6 +1618,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 78ed857203..8a5bd9bf19 100644
--- a/src/backend/utils/fmgr/funcapi.c
+++ b/src/backend/utils/fmgr/funcapi.c
@@ -35,6 +35,7 @@ typedef struct polymorphic_actuals
 	Oid			anyelement_type;	/* anyelement mapping, if known */
 	Oid			anyarray_type;	/* anyarray mapping, if known */
 	Oid			anyrange_type;	/* anyrange mapping, if known */
+	Oid			anymultirange_type;	/* anymultirange mapping, if known */
 } polymorphic_actuals;
 
 static void shutdown_MultiFuncCall(Datum arg);
@@ -46,6 +47,7 @@ static TypeFuncClass internal_get_result_type(Oid funcid,
 static void resolve_anyelement_from_others(polymorphic_actuals *actuals);
 static void resolve_anyarray_from_others(polymorphic_actuals *actuals);
 static void resolve_anyrange_from_others(polymorphic_actuals *actuals);
+static void resolve_anymultirange_from_others(polymorphic_actuals *actuals);
 static bool resolve_polymorphic_tupdesc(TupleDesc tupdesc,
 										oidvector *declared_args,
 										Node *call_expr);
@@ -503,6 +505,31 @@ resolve_anyelement_from_others(polymorphic_actuals *actuals)
 							format_type_be(range_base_type))));
 		actuals->anyelement_type = range_typelem;
 	}
+	else if (OidIsValid(actuals->anymultirange_type))
+	{
+		/* Use the element type based on the multirange type */
+		Oid			multirange_base_type = getBaseType(actuals->anymultirange_type);
+		Oid			multirange_typelem =
+			get_range_multirange_subtype(multirange_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(multirange_base_type))));
+
+		Oid			range_base_type = getBaseType(multirange_typelem);
+		Oid			range_typelem = get_range_subtype(range_base_type);
+
+		if (!OidIsValid(range_typelem))
+			ereport(ERROR,
+					(errcode(ERRCODE_DATATYPE_MISMATCH),
+					 errmsg("argument declared %s does not contain a range type but type %s",
+							"anymultirange",
+							format_type_be(range_base_type))));
+		actuals->anyelement_type = range_typelem;
+	}
 	else
 		elog(ERROR, "could not determine polymorphic type");
 }
@@ -540,10 +567,54 @@ static void
 resolve_anyrange_from_others(polymorphic_actuals *actuals)
 {
 	/*
-	 * We can't deduce a range type from other polymorphic inputs, because
-	 * there may be multiple range types with the same subtype.
+	 * We can't deduce a range type from other polymorphic array or base types,
+	 * because there may be multiple range types with the same subtype, but we
+	 * can deduce it from a polymorphic multirange type.
 	 */
-	elog(ERROR, "could not determine polymorphic type");
+	if (OidIsValid(actuals->anymultirange_type))
+	{
+		/* Use the element type based on the multirange type */
+		Oid			multirange_base_type = getBaseType(actuals->anymultirange_type);
+		Oid			multirange_typelem =
+			get_range_multirange_subtype(multirange_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(multirange_base_type))));
+		actuals->anyrange_type = multirange_typelem;
+	}
+	else
+		elog(ERROR, "could not determine polymorphic type");
+}
+
+/*
+ * Resolve actual type of ANYMULTIRANGE from other polymorphic inputs
+ */
+static void
+resolve_anymultirange_from_others(polymorphic_actuals *actuals)
+{
+	/*
+	 * We can't deduce a multirange type from polymorphic array or base types,
+	 * because there may be multiple range types with the same subtype, but we
+	 * can deduce it from a polymorphic range type.
+	 */
+	if (OidIsValid(actuals->anyrange_type))
+	{
+		Oid			range_base_type = getBaseType(actuals->anyrange_type);
+		Oid			multirange_typeid = get_range_multirange(range_base_type);
+
+		if (!OidIsValid(multirange_typeid))
+			ereport(ERROR,
+					(errcode(ERRCODE_UNDEFINED_OBJECT),
+					 errmsg("could not find multirange type for data type %s",
+							format_type_be(actuals->anyrange_type))));
+		actuals->anymultirange_type = multirange_typeid;
+	}
+	else
+		elog(ERROR, "could not determine polymorphic type");
 }
 
 /*
@@ -566,9 +637,11 @@ 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_anycompatible_result = false;
 	bool		have_anycompatible_array_result = false;
 	bool		have_anycompatible_range_result = false;
+	bool		have_anycompatible_multirange_result = false;
 	polymorphic_actuals poly_actuals;
 	polymorphic_actuals anyc_actuals;
 	Oid			anycollation = InvalidOid;
@@ -594,6 +667,10 @@ resolve_polymorphic_tupdesc(TupleDesc tupdesc, oidvector *declared_args,
 				have_polymorphic_result = true;
 				have_anyrange_result = true;
 				break;
+			case ANYMULTIRANGEOID:
+				have_polymorphic_result = true;
+				have_anymultirange_result = true;
+				break;
 			case ANYCOMPATIBLEOID:
 			case ANYCOMPATIBLENONARRAYOID:
 				have_polymorphic_result = true;
@@ -607,6 +684,10 @@ resolve_polymorphic_tupdesc(TupleDesc tupdesc, oidvector *declared_args,
 				have_polymorphic_result = true;
 				have_anycompatible_range_result = true;
 				break;
+			case ANYCOMPATIBLEMULTIRANGEOID:
+				have_polymorphic_result = true;
+				have_anycompatible_multirange_result = true;
+				break;
 			default:
 				break;
 		}
@@ -660,6 +741,15 @@ resolve_polymorphic_tupdesc(TupleDesc tupdesc, oidvector *declared_args,
 						return false;
 				}
 				break;
+			case ANYMULTIRANGEOID:
+				if (!OidIsValid(poly_actuals.anymultirange_type))
+				{
+					poly_actuals.anymultirange_type =
+						get_call_expr_argtype(call_expr, i);
+					if (!OidIsValid(poly_actuals.anymultirange_type))
+						return false;
+				}
+				break;
 			case ANYCOMPATIBLEOID:
 			case ANYCOMPATIBLENONARRAYOID:
 				if (!OidIsValid(anyc_actuals.anyelement_type))
@@ -688,6 +778,15 @@ resolve_polymorphic_tupdesc(TupleDesc tupdesc, oidvector *declared_args,
 						return false;
 				}
 				break;
+			case ANYCOMPATIBLEMULTIRANGEOID:
+				if (!OidIsValid(anyc_actuals.anymultirange_type))
+				{
+					anyc_actuals.anymultirange_type =
+						get_call_expr_argtype(call_expr, i);
+					if (!OidIsValid(anyc_actuals.anymultirange_type))
+						return false;
+				}
+				break;
 			default:
 				break;
 		}
@@ -703,6 +802,9 @@ resolve_polymorphic_tupdesc(TupleDesc tupdesc, oidvector *declared_args,
 	if (have_anyrange_result && !OidIsValid(poly_actuals.anyrange_type))
 		resolve_anyrange_from_others(&poly_actuals);
 
+	if (have_anymultirange_result && !OidIsValid(poly_actuals.anymultirange_type))
+		resolve_anymultirange_from_others(&poly_actuals);
+
 	if (have_anycompatible_result && !OidIsValid(anyc_actuals.anyelement_type))
 		resolve_anyelement_from_others(&anyc_actuals);
 
@@ -712,6 +814,9 @@ resolve_polymorphic_tupdesc(TupleDesc tupdesc, oidvector *declared_args,
 	if (have_anycompatible_range_result && !OidIsValid(anyc_actuals.anyrange_type))
 		resolve_anyrange_from_others(&anyc_actuals);
 
+	if (have_anycompatible_multirange_result && !OidIsValid(anyc_actuals.anymultirange_type))
+		resolve_anymultirange_from_others(&anyc_actuals);
+
 	/*
 	 * Identify the collation to use for polymorphic OUT parameters. (It'll
 	 * necessarily be the same for both anyelement and anyarray, likewise for
@@ -780,6 +885,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),
+								   poly_actuals.anymultirange_type,
+								   -1,
+								   0);
+				/* no collation should be attached to a multirange type */
+				break;
 			case ANYCOMPATIBLEOID:
 			case ANYCOMPATIBLENONARRAYOID:
 				TupleDescInitEntry(tupdesc, i + 1,
@@ -805,6 +918,14 @@ resolve_polymorphic_tupdesc(TupleDesc tupdesc, oidvector *declared_args,
 								   0);
 				/* no collation should be attached to a range type */
 				break;
+			case ANYCOMPATIBLEMULTIRANGEOID:
+				TupleDescInitEntry(tupdesc, i + 1,
+								   NameStr(att->attname),
+								   anyc_actuals.anymultirange_type,
+								   -1,
+								   0);
+				/* no collation should be attached to a multirange type */
+				break;
 			default:
 				break;
 		}
@@ -834,9 +955,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;
 	bool		have_anycompatible_result = false;
 	bool		have_anycompatible_array_result = false;
 	bool		have_anycompatible_range_result = false;
+	bool		have_anycompatible_multirange_result = false;
 	polymorphic_actuals poly_actuals;
 	polymorphic_actuals anyc_actuals;
 	int			inargno;
@@ -912,6 +1035,24 @@ resolve_polymorphic_argtypes(int numargs, Oid *argtypes, char *argmodes,
 					argtypes[i] = poly_actuals.anyrange_type;
 				}
 				break;
+			case ANYMULTIRANGEOID:
+				if (argmode == PROARGMODE_OUT || argmode == PROARGMODE_TABLE)
+				{
+					have_polymorphic_result = true;
+					have_anymultirange_result = true;
+				}
+				else
+				{
+					if (!OidIsValid(poly_actuals.anymultirange_type))
+					{
+						poly_actuals.anymultirange_type =
+							get_call_expr_argtype(call_expr, inargno);
+						if (!OidIsValid(poly_actuals.anymultirange_type))
+							return false;
+					}
+					argtypes[i] = poly_actuals.anymultirange_type;
+				}
+				break;
 			case ANYCOMPATIBLEOID:
 			case ANYCOMPATIBLENONARRAYOID:
 				if (argmode == PROARGMODE_OUT || argmode == PROARGMODE_TABLE)
@@ -967,6 +1108,24 @@ resolve_polymorphic_argtypes(int numargs, Oid *argtypes, char *argmodes,
 					argtypes[i] = anyc_actuals.anyrange_type;
 				}
 				break;
+			case ANYCOMPATIBLEMULTIRANGEOID:
+				if (argmode == PROARGMODE_OUT || argmode == PROARGMODE_TABLE)
+				{
+					have_polymorphic_result = true;
+					have_anycompatible_multirange_result = true;
+				}
+				else
+				{
+					if (!OidIsValid(anyc_actuals.anymultirange_type))
+					{
+						anyc_actuals.anymultirange_type =
+							get_call_expr_argtype(call_expr, inargno);
+						if (!OidIsValid(anyc_actuals.anymultirange_type))
+							return false;
+					}
+					argtypes[i] = anyc_actuals.anymultirange_type;
+				}
+				break;
 			default:
 				break;
 		}
@@ -988,6 +1147,9 @@ resolve_polymorphic_argtypes(int numargs, Oid *argtypes, char *argmodes,
 	if (have_anyrange_result && !OidIsValid(poly_actuals.anyrange_type))
 		resolve_anyrange_from_others(&poly_actuals);
 
+	if (have_anymultirange_result && !OidIsValid(poly_actuals.anymultirange_type))
+		resolve_anymultirange_from_others(&poly_actuals);
+
 	if (have_anycompatible_result && !OidIsValid(anyc_actuals.anyelement_type))
 		resolve_anyelement_from_others(&anyc_actuals);
 
@@ -997,6 +1159,9 @@ resolve_polymorphic_argtypes(int numargs, Oid *argtypes, char *argmodes,
 	if (have_anycompatible_range_result && !OidIsValid(anyc_actuals.anyrange_type))
 		resolve_anyrange_from_others(&anyc_actuals);
 
+	if (have_anycompatible_multirange_result && !OidIsValid(anyc_actuals.anymultirange_type))
+		resolve_anymultirange_from_others(&anyc_actuals);
+
 	/* And finally replace the output column types as needed */
 	for (i = 0; i < numargs; i++)
 	{
@@ -1013,6 +1178,9 @@ resolve_polymorphic_argtypes(int numargs, Oid *argtypes, char *argmodes,
 			case ANYRANGEOID:
 				argtypes[i] = poly_actuals.anyrange_type;
 				break;
+			case ANYMULTIRANGEOID:
+				argtypes[i] = poly_actuals.anymultirange_type;
+				break;
 			case ANYCOMPATIBLEOID:
 			case ANYCOMPATIBLENONARRAYOID:
 				argtypes[i] = anyc_actuals.anyelement_type;
@@ -1023,6 +1191,9 @@ resolve_polymorphic_argtypes(int numargs, Oid *argtypes, char *argmodes,
 			case ANYCOMPATIBLERANGEOID:
 				argtypes[i] = anyc_actuals.anyrange_type;
 				break;
+			case ANYCOMPATIBLEMULTIRANGEOID:
+				argtypes[i] = anyc_actuals.anymultirange_type;
+				break;
 			default:
 				break;
 		}
@@ -1052,6 +1223,7 @@ get_type_func_class(Oid typid, Oid *base_typeid)
 		case TYPTYPE_BASE:
 		case TYPTYPE_ENUM:
 		case TYPTYPE_RANGE:
+		case TYPTYPE_MULTIRANGE:
 			return TYPEFUNC_SCALAR;
 		case TYPTYPE_DOMAIN:
 			*base_typeid = typid = getBaseType(typid);
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index a41a3db876..39e50c6c79 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -271,7 +271,8 @@ static void dumpSearchPath(Archive *AH);
 static void binary_upgrade_set_type_oids_by_type_oid(Archive *fout,
 													 PQExpBuffer upgrade_buffer,
 													 Oid pg_type_oid,
-													 bool force_array_type);
+													 bool force_array_type,
+													 bool include_multirange_type);
 static bool binary_upgrade_set_type_oids_by_rel_oid(Archive *fout,
 													PQExpBuffer upgrade_buffer, Oid pg_rel_oid);
 static void binary_upgrade_set_pg_class_oids(Archive *fout,
@@ -1640,7 +1641,7 @@ selectDumpableType(TypeInfo *tyinfo, Archive *fout)
 	}
 
 	/* skip auto-generated array types */
-	if (tyinfo->isArray)
+	if (tyinfo->isArray || tyinfo->isMultirange)
 	{
 		tyinfo->dobj.objType = DO_DUMMY_TYPE;
 
@@ -4423,16 +4424,49 @@ append_depends_on_extension(Archive *fout,
 	}
 }
 
+static Oid
+get_next_possible_free_pg_type_oid(Archive *fout, PQExpBuffer upgrade_query)
+{
+	/*
+	 * If the old version didn't assign an array type, but the new version
+	 * does, we must select an unused type OID to assign.  This currently only
+	 * happens for domains, when upgrading pre-v11 to v11 and up.
+	 *
+	 * Note: local state here is kind of ugly, but we must have some, since we
+	 * mustn't choose the same unused OID more than once.
+	 */
+	static Oid	next_possible_free_oid = FirstNormalObjectId;
+	PGresult   *res;
+	bool		is_dup;
+
+	do
+	{
+		++next_possible_free_oid;
+		printfPQExpBuffer(upgrade_query,
+						  "SELECT EXISTS(SELECT 1 "
+						  "FROM pg_catalog.pg_type "
+						  "WHERE oid = '%u'::pg_catalog.oid);",
+						  next_possible_free_oid);
+		res = ExecuteSqlQueryForSingleRow(fout, upgrade_query->data);
+		is_dup = (PQgetvalue(res, 0, 0)[0] == 't');
+		PQclear(res);
+	} while (is_dup);
+
+	return next_possible_free_oid;
+}
 
 static void
 binary_upgrade_set_type_oids_by_type_oid(Archive *fout,
 										 PQExpBuffer upgrade_buffer,
 										 Oid pg_type_oid,
-										 bool force_array_type)
+										 bool force_array_type,
+										 bool include_multirange_type)
 {
 	PQExpBuffer upgrade_query = createPQExpBuffer();
 	PGresult   *res;
 	Oid			pg_type_array_oid;
+	Oid			pg_type_multirange_oid;
+	Oid			pg_type_multirange_array_oid;
 
 	appendPQExpBufferStr(upgrade_buffer, "\n-- For binary upgrade, must preserve pg_type oid\n");
 	appendPQExpBuffer(upgrade_buffer,
@@ -4453,33 +4487,7 @@ binary_upgrade_set_type_oids_by_type_oid(Archive *fout,
 	PQclear(res);
 
 	if (!OidIsValid(pg_type_array_oid) && force_array_type)
-	{
-		/*
-		 * If the old version didn't assign an array type, but the new version
-		 * does, we must select an unused type OID to assign.  This currently
-		 * only happens for domains, when upgrading pre-v11 to v11 and up.
-		 *
-		 * Note: local state here is kind of ugly, but we must have some,
-		 * since we mustn't choose the same unused OID more than once.
-		 */
-		static Oid	next_possible_free_oid = FirstNormalObjectId;
-		bool		is_dup;
-
-		do
-		{
-			++next_possible_free_oid;
-			printfPQExpBuffer(upgrade_query,
-							  "SELECT EXISTS(SELECT 1 "
-							  "FROM pg_catalog.pg_type "
-							  "WHERE oid = '%u'::pg_catalog.oid);",
-							  next_possible_free_oid);
-			res = ExecuteSqlQueryForSingleRow(fout, upgrade_query->data);
-			is_dup = (PQgetvalue(res, 0, 0)[0] == 't');
-			PQclear(res);
-		} while (is_dup);
-
-		pg_type_array_oid = next_possible_free_oid;
-	}
+		pg_type_array_oid = get_next_possible_free_pg_type_oid(fout, upgrade_query);
 
 	if (OidIsValid(pg_type_array_oid))
 	{
@@ -4490,6 +4498,46 @@ binary_upgrade_set_type_oids_by_type_oid(Archive *fout,
 						  pg_type_array_oid);
 	}
 
+	/*
+	 * Pre-set the multirange type oid and its own array type oid.
+	 */
+	if (include_multirange_type)
+	{
+		if (fout->remoteVersion >= 130000)
+		{
+			appendPQExpBuffer(upgrade_query,
+							  "SELECT t.oid, t.typarray "
+							  "FROM pg_catalog.pg_type t "
+							  "JOIN pg_catalog.pg_range r "
+							  "ON t.oid = r.rngmultitypid "
+							  "WHERE r.rngtypid = '%u'::pg_catalog.oid;",
+							  pg_type_oid);
+
+			res = ExecuteSqlQueryForSingleRow(fout, upgrade_query->data);
+
+			pg_type_multirange_oid = atooid(PQgetvalue(res, 0, PQfnumber(res, "oid")));
+			pg_type_multirange_array_oid = atooid(PQgetvalue(res, 0, PQfnumber(res, "typarray")));
+
+			PQclear(res);
+		}
+		else
+		{
+			pg_type_multirange_oid = get_next_possible_free_pg_type_oid(fout, upgrade_query);
+			pg_type_multirange_array_oid = get_next_possible_free_pg_type_oid(fout, upgrade_query);
+		}
+
+		appendPQExpBufferStr(upgrade_buffer,
+							 "\n-- For binary upgrade, must preserve multirange pg_type oid\n");
+		appendPQExpBuffer(upgrade_buffer,
+						  "SELECT pg_catalog.binary_upgrade_set_next_multirange_pg_type_oid('%u'::pg_catalog.oid);\n\n",
+						  pg_type_multirange_oid);
+		appendPQExpBufferStr(upgrade_buffer,
+							 "\n-- For binary upgrade, must preserve multirange pg_type array oid\n");
+		appendPQExpBuffer(upgrade_buffer,
+						  "SELECT pg_catalog.binary_upgrade_set_next_multirange_array_pg_type_oid('%u'::pg_catalog.oid);\n\n",
+						  pg_type_multirange_array_oid);
+	}
+
 	destroyPQExpBuffer(upgrade_query);
 }
 
@@ -4523,7 +4571,7 @@ binary_upgrade_set_type_oids_by_rel_oid(Archive *fout,
 	pg_type_oid = atooid(PQgetvalue(upgrade_res, 0, PQfnumber(upgrade_res, "crel")));
 
 	binary_upgrade_set_type_oids_by_type_oid(fout, upgrade_buffer,
-											 pg_type_oid, false);
+											 pg_type_oid, false, false);
 
 	if (!PQgetisnull(upgrade_res, 0, PQfnumber(upgrade_res, "trel")))
 	{
@@ -5072,6 +5120,11 @@ getTypes(Archive *fout, int *numTypes)
 		else
 			tyinfo[i].isArray = false;
 
+		if (tyinfo[i].typtype == 'm')
+			tyinfo[i].isMultirange = true;
+		else
+			tyinfo[i].isMultirange = false;
+
 		/* Decide whether we want to dump it */
 		selectDumpableType(&tyinfo[i], fout);
 
@@ -8213,9 +8266,12 @@ getProcLangs(Archive *fout, int *numProcLangs)
 
 /*
  * getCasts
- *	  get basic information about every cast in the system
+ *	  get basic information about most casts in the system
  *
  * numCasts is set to the number of casts read in
+ *
+ * Skip casts from a range to its multirange, since we'll create those
+ * automatically.
  */
 CastInfo *
 getCasts(Archive *fout, int *numCasts)
@@ -8233,7 +8289,20 @@ getCasts(Archive *fout, int *numCasts)
 	int			i_castcontext;
 	int			i_castmethod;
 
-	if (fout->remoteVersion >= 80400)
+	if (fout->remoteVersion >= 130000)
+	{
+		appendPQExpBufferStr(query, "SELECT tableoid, oid, "
+							 "castsource, casttarget, castfunc, castcontext, "
+							 "castmethod "
+							 "FROM pg_cast c "
+							 "WHERE NOT EXISTS ( "
+							 "SELECT 1 FROM pg_range r "
+							 "WHERE c.castsource = r.rngtypid "
+							 "AND c.casttarget = r.rngmultitypid "
+							 ") "
+							 "ORDER BY 3,4");
+	}
+	else if (fout->remoteVersion >= 80400)
 	{
 		appendPQExpBufferStr(query, "SELECT tableoid, oid, "
 							 "castsource, casttarget, castfunc, castcontext, "
@@ -10426,7 +10495,7 @@ dumpEnumType(Archive *fout, TypeInfo *tyinfo)
 	if (dopt->binary_upgrade)
 		binary_upgrade_set_type_oids_by_type_oid(fout, q,
 												 tyinfo->dobj.catId.oid,
-												 false);
+												 false, false);
 
 	appendPQExpBuffer(q, "CREATE TYPE %s AS ENUM (",
 					  qualtypname);
@@ -10552,7 +10621,7 @@ dumpRangeType(Archive *fout, TypeInfo *tyinfo)
 	if (dopt->binary_upgrade)
 		binary_upgrade_set_type_oids_by_type_oid(fout, q,
 												 tyinfo->dobj.catId.oid,
-												 false);
+												 false, true);
 
 	appendPQExpBuffer(q, "CREATE TYPE %s AS RANGE (",
 					  qualtypname);
@@ -10658,7 +10727,7 @@ dumpUndefinedType(Archive *fout, TypeInfo *tyinfo)
 	if (dopt->binary_upgrade)
 		binary_upgrade_set_type_oids_by_type_oid(fout, q,
 												 tyinfo->dobj.catId.oid,
-												 false);
+												 false, false);
 
 	appendPQExpBuffer(q, "CREATE TYPE %s;\n",
 					  qualtypname);
@@ -10863,7 +10932,7 @@ dumpBaseType(Archive *fout, TypeInfo *tyinfo)
 	if (dopt->binary_upgrade)
 		binary_upgrade_set_type_oids_by_type_oid(fout, q,
 												 tyinfo->dobj.catId.oid,
-												 false);
+												 false, false);
 
 	appendPQExpBuffer(q,
 					  "CREATE TYPE %s (\n"
@@ -11050,7 +11119,8 @@ dumpDomain(Archive *fout, TypeInfo *tyinfo)
 	if (dopt->binary_upgrade)
 		binary_upgrade_set_type_oids_by_type_oid(fout, q,
 												 tyinfo->dobj.catId.oid,
-												 true); /* force array type */
+												 true,		/* force array type */
+												 false);	/* force multirange type */
 
 	qtypname = pg_strdup(fmtId(tyinfo->dobj.name));
 	qualtypname = pg_strdup(fmtQualifiedDumpable(tyinfo));
@@ -11238,7 +11308,7 @@ dumpCompositeType(Archive *fout, TypeInfo *tyinfo)
 	{
 		binary_upgrade_set_type_oids_by_type_oid(fout, q,
 												 tyinfo->dobj.catId.oid,
-												 false);
+												 false, false);
 		binary_upgrade_set_pg_class_oids(fout, q, tyinfo->typrelid, false);
 	}
 
@@ -11512,7 +11582,7 @@ dumpShellType(Archive *fout, ShellTypeInfo *stinfo)
 	if (dopt->binary_upgrade)
 		binary_upgrade_set_type_oids_by_type_oid(fout, q,
 												 stinfo->baseType->dobj.catId.oid,
-												 false);
+												 false, false);
 
 	appendPQExpBuffer(q, "CREATE TYPE %s;\n",
 					  fmtQualifiedDumpable(stinfo));
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 0c2fcfb3a9..da3e318fc4 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -176,6 +176,7 @@ typedef struct _typeInfo
 	char		typrelkind;		/* 'r', 'v', 'c', etc */
 	char		typtype;		/* 'b', 'c', etc */
 	bool		isArray;		/* true if auto-generated array type */
+	bool		isMultirange;	/* true if auto-generated multirange type */
 	bool		isDefined;		/* true if typisdefined */
 	/* If needed, we'll create a "shell type" entry for it; link that here: */
 	struct _shellTypeInfo *shellType;	/* shell-type entry, or NULL */
diff --git a/src/include/access/tupmacs.h b/src/include/access/tupmacs.h
index 70157cf90a..c262265b95 100644
--- a/src/include/access/tupmacs.h
+++ b/src/include/access/tupmacs.h
@@ -139,8 +139,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/binary_upgrade.h b/src/include/catalog/binary_upgrade.h
index 12d94fe1b3..01d95117cd 100644
--- a/src/include/catalog/binary_upgrade.h
+++ b/src/include/catalog/binary_upgrade.h
@@ -16,6 +16,8 @@
 
 extern PGDLLIMPORT Oid binary_upgrade_next_pg_type_oid;
 extern PGDLLIMPORT Oid binary_upgrade_next_array_pg_type_oid;
+extern PGDLLIMPORT Oid binary_upgrade_next_mrng_pg_type_oid;
+extern PGDLLIMPORT Oid binary_upgrade_next_mrng_array_pg_type_oid;
 extern PGDLLIMPORT Oid binary_upgrade_next_toast_pg_type_oid;
 
 extern PGDLLIMPORT Oid binary_upgrade_next_heap_pg_class_oid;
diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h
index 8be303870f..566f9364c5 100644
--- a/src/include/catalog/indexing.h
+++ b/src/include/catalog/indexing.h
@@ -327,6 +327,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_rngmultitypid_index, 8001, on pg_range using btree(rngmultitypid 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_aggregate.dat b/src/include/catalog/pg_aggregate.dat
index ffabe275c0..03bab74253 100644
--- a/src/include/catalog/pg_aggregate.dat
+++ b/src/include/catalog/pg_aggregate.dat
@@ -538,6 +538,17 @@
   aggtransfn => 'bytea_string_agg_transfn',
   aggfinalfn => 'bytea_string_agg_finalfn', aggtranstype => 'internal' },
 
+# range
+{ aggfnoid => 'range_intersect_agg(anyrange)',
+  aggtransfn => 'range_intersect_agg_transfn',
+  aggcombinefn => 'range_intersect_agg_transfn', aggtranstype => 'anyrange' },
+{ aggfnoid => 'range_intersect_agg(anymultirange)',
+  aggtransfn => 'multirange_intersect_agg_transfn',
+  aggcombinefn => 'multirange_intersect_agg_transfn', aggtranstype => 'anymultirange' },
+{ aggfnoid => 'range_agg(anyrange)', aggtransfn => 'range_agg_transfn',
+  aggfinalfn => 'range_agg_finalfn', aggfinalextra => 't',
+  aggtranstype => 'internal' },
+
 # json
 { aggfnoid => 'json_agg', aggtransfn => 'json_agg_transfn',
   aggfinalfn => 'json_agg_finalfn', aggtranstype => 'internal' },
diff --git a/src/include/catalog/pg_amop.dat b/src/include/catalog/pg_amop.dat
index 1dfb6fd373..42293ddffe 100644
--- a/src/include/catalog/pg_amop.dat
+++ b/src/include/catalog/pg_amop.dat
@@ -1379,6 +1379,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 37b580883f..9842e80acf 100644
--- a/src/include/catalog/pg_amproc.dat
+++ b/src/include/catalog/pg_amproc.dat
@@ -285,6 +285,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' },
 { amprocfamily => 'btree/xid8_ops', amproclefttype => 'xid8',
@@ -453,6 +455,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',
@@ -1274,12 +1281,13 @@
   amprocrighttype => 'anyrange', amprocnum => '4',
   amproc => 'brin_inclusion_union' },
 { amprocfamily => 'brin/range_inclusion_ops', amproclefttype => 'anyrange',
-  amprocrighttype => 'anyrange', amprocnum => '11', amproc => 'range_merge' },
+  amprocrighttype => 'anyrange', amprocnum => '11',
+  amproc => 'range_merge(anyrange,anyrange)' },
 { amprocfamily => 'brin/range_inclusion_ops', amproclefttype => 'anyrange',
   amprocrighttype => 'anyrange', amprocnum => '13',
   amproc => 'range_contains' },
 { amprocfamily => 'brin/range_inclusion_ops', amproclefttype => 'anyrange',
-  amprocrighttype => 'anyrange', amprocnum => '14', amproc => 'isempty' },
+  amprocrighttype => 'anyrange', amprocnum => '14', amproc => 'isempty(anyrange)' },
 
 # minmax pg_lsn
 { amprocfamily => 'brin/pg_lsn_minmax_ops', amproclefttype => 'pg_lsn',
diff --git a/src/include/catalog/pg_cast.dat b/src/include/catalog/pg_cast.dat
index 5a58f50fbb..d6ca624add 100644
--- a/src/include/catalog/pg_cast.dat
+++ b/src/include/catalog/pg_cast.dat
@@ -530,4 +530,17 @@
 { castsource => 'jsonb', casttarget => 'float8', castfunc => 'float8(jsonb)',
   castcontext => 'e', castmethod => 'f' },
 
+# range to multirange
+{ castsource => 'int4range', casttarget => 'int4multirange', castfunc => 'int4multirange(int4range)',
+  castcontext => 'e', castmethod => 'f' },
+{ castsource => 'int8range', casttarget => 'int8multirange', castfunc => 'int8multirange(int8range)',
+  castcontext => 'e', castmethod => 'f' },
+{ castsource => 'numrange', casttarget => 'nummultirange', castfunc => 'nummultirange(numrange)',
+  castcontext => 'e', castmethod => 'f' },
+{ castsource => 'daterange', casttarget => 'datemultirange', castfunc => 'datemultirange(daterange)',
+  castcontext => 'e', castmethod => 'f' },
+{ castsource => 'tsrange', casttarget => 'tsmultirange', castfunc => 'tsmultirange(tsrange)',
+  castcontext => 'e', castmethod => 'f' },
+{ castsource => 'tstzrange', casttarget => 'tstzmultirange', castfunc => 'tstzmultirange(tstzrange)',
+  castcontext => 'e', castmethod => 'f' },
 ]
diff --git a/src/include/catalog/pg_opclass.dat b/src/include/catalog/pg_opclass.dat
index f2342bb328..9b0609defa 100644
--- a/src/include/catalog/pg_opclass.dat
+++ b/src/include/catalog/pg_opclass.dat
@@ -230,6 +230,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 5b0e063655..dbe8499e21 100644
--- a/src/include/catalog/pg_operator.dat
+++ b/src/include/catalog/pg_operator.dat
@@ -3338,5 +3338,174 @@
   oprname => '@@', oprleft => 'jsonb', oprright => 'jsonpath',
   oprresult => 'bool', oprcode => 'jsonb_path_match_opr(jsonb,jsonpath)',
   oprrest => 'matchingsel', oprjoin => 'matchingjoinsel' },
+{ 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 => 'multirangesel', 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 => 'multirangesel', 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 => 'multirangesel', 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 => 'multirangesel', oprjoin => 'scalargtjoinsel' },
+{ oid => '8073', oid_symbol => 'OID_RANGE_OVERLAPS_MULTIRANGE_OP',
+  descr => 'overlaps',
+  oprname => '&&', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '&&(anymultirange,anyrange)',
+  oprcode => 'range_overlaps_multirange', oprrest => 'multirangesel',
+  oprjoin => 'areajoinsel' },
+{ oid => '8074', oid_symbol => 'OID_MULTIRANGE_OVERLAPS_RANGE_OP', descr => 'contains',
+  oprname => '&&', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcom => '&&(anyrange,anymultirange)',
+  oprcode => 'multirange_overlaps_range', oprrest => 'multirangesel',
+  oprjoin => 'areajoinsel' },
+{ oid => '8075', oid_symbol => 'OID_MULTIRANGE_OVERLAPS_MULTIRANGE_OP', descr => 'contains',
+  oprname => '&&', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '&&(anymultirange,anymultirange)',
+  oprcode => 'multirange_overlaps_multirange', oprrest => 'multirangesel',
+  oprjoin => 'areajoinsel' },
+{ oid => '8064', oid_symbol => 'OID_MULTIRANGE_CONTAINS_ELEM_OP',
+  descr => 'contains',
+  oprname => '@>', oprleft => 'anymultirange', oprright => 'anyelement',
+  oprresult => 'bool', oprcom => '<@(anyelement,anymultirange)',
+  oprcode => 'multirange_contains_elem', oprrest => 'multirangesel',
+  oprjoin => 'contjoinsel' },
+{ oid => '8065', oid_symbol => 'OID_MULTIRANGE_CONTAINS_RANGE_OP', descr => 'contains',
+  oprname => '@>', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcom => '<@(anyrange,anymultirange)',
+  oprcode => 'multirange_contains_range', oprrest => 'multirangesel',
+  oprjoin => 'contjoinsel' },
+{ oid => '8066', oid_symbol => 'OID_MULTIRANGE_CONTAINS_MULTIRANGE_OP', descr => 'contains',
+  oprname => '@>', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '<@(anymultirange,anymultirange)',
+  oprcode => 'multirange_contains_multirange', oprrest => 'multirangesel',
+  oprjoin => 'contjoinsel' },
+{ oid => '8067', oid_symbol => 'OID_MULTIRANGE_ELEM_CONTAINED_OP',
+  descr => 'is contained by',
+  oprname => '<@', oprleft => 'anyelement', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '@>(anymultirange,anyelement)',
+  oprcode => 'elem_contained_by_multirange', oprrest => 'multirangesel',
+  oprjoin => 'contjoinsel' },
+{ oid => '8068', oid_symbol => 'OID_MULTIRANGE_RANGE_CONTAINED_OP',
+  descr => 'is contained by',
+  oprname => '<@', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '@>(anymultirange,anyrange)',
+  oprcode => 'range_contained_by_multirange', oprrest => 'multirangesel',
+  oprjoin => 'contjoinsel' },
+{ oid => '8069', oid_symbol => 'OID_MULTIRANGE_MULTIRANGE_CONTAINED_OP',
+  descr => 'is contained by',
+  oprname => '<@', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '@>(anymultirange,anymultirange)',
+  oprcode => 'multirange_contained_by_multirange', oprrest => 'multirangesel',
+  oprjoin => 'contjoinsel' },
+{ oid => '8106', oid_symbol => 'OID_RANGE_OVERLAPS_LEFT_MULTIRANGE_OP',
+  descr => 'overlaps or is left of',
+  oprname => '&<', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcode => 'range_overleft_multirange', oprrest => 'multirangesel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8107', oid_symbol => 'OID_MULTIRANGE_OVERLAPS_LEFT_RANGE_OP',
+  descr => 'overlaps or is left of',
+  oprname => '&<', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcode => 'multirange_overleft_range', oprrest => 'multirangesel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8108', oid_symbol => 'OID_MULTIRANGE_OVERLAPS_LEFT_MULTIRANGE_OP',
+  descr => 'overlaps or is left of',
+  oprname => '&<', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcode => 'multirange_overleft_multirange', oprrest => 'multirangesel',
+  oprjoin => 'scalargtjoinsel' },
+{ oid => '8109', oid_symbol => 'OID_RANGE_OVERLAPS_RIGHT_MULTIRANGE_OP',
+  descr => 'overlaps or is right of',
+  oprname => '&>', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcode => 'range_overright_multirange', oprrest => 'multirangesel',
+  oprjoin => 'scalargtjoinsel' },
+{ oid => '8110', oid_symbol => 'OID_MULTIRANGE_OVERLAPS_RIGHT_RANGE_OP',
+  descr => 'overlaps or is right of',
+  oprname => '&>', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcode => 'multirange_overright_range', oprrest => 'multirangesel',
+  oprjoin => 'scalargtjoinsel' },
+{ oid => '8111', oid_symbol => 'OID_MULTIRANGE_OVERLAPS_RIGHT_MULTIRANGE_OP',
+  descr => 'overlaps or is right of',
+  oprname => '&>', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcode => 'multirange_overright_multirange', oprrest => 'multirangesel',
+  oprjoin => 'scalargtjoinsel' },
+{ oid => '8091', oid_symbol => 'OID_RANGE_ADJACENT_MULTIRANGE_OP', descr => 'is adjacent to',
+  oprname => '-|-', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '-|-(anymultirange,anyrange)',
+  oprcode => 'range_adjacent_multirange', oprrest => 'matchingsel',
+  oprjoin => 'matchingjoinsel' },
+{ oid => '8092', oid_symbol => 'OID_MULTIRANGE_ADJACENT_RANGE_OP', descr => 'is adjacent to',
+  oprname => '-|-', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcom => '-|-(anyrange,anymultirange)',
+  oprcode => 'multirange_adjacent_range', oprrest => 'matchingsel',
+  oprjoin => 'matchingjoinsel' },
+{ oid => '8093', oid_symbol => 'OID_MULTIRANGE_ADJACENT_MULTIRANGE_OP', descr => 'is adjacent to',
+  oprname => '-|-', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '-|-(anymultirange,anymultirange)',
+  oprcode => 'multirange_adjacent_multirange', oprrest => 'matchingsel',
+  oprjoin => 'matchingjoinsel' },
+{ oid => '8117', descr => 'multirange union',
+  oprname => '+', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'anymultirange', oprcom => '+(anymultirange,anymultirange)',
+  oprcode => 'multirange_union' },
+{ oid => '8123', descr => 'multirange minus',
+  oprname => '-', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'anymultirange', oprcode => 'multirange_minus' },
+{ oid => '8129', descr => 'multirange intersect',
+  oprname => '*', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'anymultirange', oprcom => '*(anymultirange,anymultirange)',
+  oprcode => 'multirange_intersect' },
+{ oid => '8082', oid_symbol => 'OID_RANGE_LEFT_MULTIRANGE_OP', descr => 'is left of',
+  oprname => '<<', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '>>(anymultirange,anyrange)',
+  oprcode => 'range_before_multirange', oprrest => 'multirangesel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8083', oid_symbol => 'OID_MULTIRANGE_LEFT_RANGE_OP', descr => 'is left of',
+  oprname => '<<', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcom => '>>(anyrange,anymultirange)',
+  oprcode => 'multirange_before_range', oprrest => 'multirangesel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8084', oid_symbol => 'OID_MULTIRANGE_LEFT_MULTIRANGE_OP', descr => 'is left of',
+  oprname => '<<', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '>>(anymultirange,anymultirange)',
+  oprcode => 'multirange_before_multirange', oprrest => 'multirangesel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8085', oid_symbol => 'OID_RANGE_RIGHT_MULTIRANGE_OP', descr => 'is right of',
+  oprname => '>>', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '<<(anymultirange,anyrange)',
+  oprcode => 'range_after_multirange', oprrest => 'multirangesel',
+  oprjoin => 'scalargtjoinsel' },
+{ oid => '8086', oid_symbol => 'OID_MULTIRANGE_RIGHT_RANGE_OP', descr => 'is right of',
+  oprname => '>>', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcom => '<<(anyrange,anymultirange)',
+  oprcode => 'multirange_after_range', oprrest => 'multirangesel',
+  oprjoin => 'scalargtjoinsel' },
+{ oid => '8087', oid_symbol => 'OID_MULTIRANGE_RIGHT_MULTIRANGE_OP', descr => 'is right of',
+  oprname => '>>', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '<<(anymultirange,anymultirange)',
+  oprcode => 'multirange_after_multirange', oprrest => 'multirangesel',
+  oprjoin => 'scalargtjoinsel' },
 
 ]
diff --git a/src/include/catalog/pg_opfamily.dat b/src/include/catalog/pg_opfamily.dat
index cf0fb325b3..ffd1220041 100644
--- a/src/include/catalog/pg_opfamily.dat
+++ b/src/include/catalog/pg_opfamily.dat
@@ -230,5 +230,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 38295aca48..88952c73bb 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -7209,6 +7209,14 @@
   proname => 'anycompatiblerange_out', provolatile => 's',
   prorettype => 'cstring', proargtypes => 'anycompatiblerange',
   prosrc => 'anycompatiblerange_out' },
+{ oid => '8154', descr => 'I/O',
+  proname => 'anycompatiblemultirange_in', provolatile => 's',
+  prorettype => 'anycompatiblemultirange', proargtypes => 'cstring oid int4',
+  prosrc => 'anycompatiblemultirange_in' },
+{ oid => '8155', descr => 'I/O',
+  proname => 'anycompatiblemultirange_out', provolatile => 's',
+  prorettype => 'cstring', proargtypes => 'anycompatiblemultirange',
+  prosrc => 'anycompatiblemultirange_out' },
 
 # tablesample method handlers
 { oid => '3313', descr => 'BERNOULLI tablesample method handler',
@@ -9807,6 +9815,10 @@
   descr => 'the smallest range which includes both of the given ranges',
   proname => 'range_merge', prorettype => 'anyrange',
   proargtypes => 'anyrange anyrange', prosrc => 'range_merge' },
+{ oid => '8143',
+  descr => 'the smallest range which includes the whole multirange',
+  proname => 'range_merge', prorettype => 'anyrange',
+  proargtypes => 'anymultirange', prosrc => 'range_merge_from_multirange' },
 { oid => '3868',
   proname => 'range_intersect', prorettype => 'anyrange',
   proargtypes => 'anyrange anyrange', prosrc => 'range_intersect' },
@@ -9856,6 +9868,13 @@
 { oid => '3169', descr => 'restriction selectivity for range operators',
   proname => 'rangesel', provolatile => 's', prorettype => 'float8',
   proargtypes => 'internal oid internal int4', prosrc => 'rangesel' },
+{ oid => '8133', descr => 'range aggregate by intersecting',
+  proname => 'range_intersect_agg_transfn', prorettype => 'anyrange',
+  proargtypes => 'anyrange anyrange', prosrc => 'range_intersect_agg_transfn'},
+{ oid => '8134', descr => 'range aggregate by intersecting',
+  proname => 'range_intersect_agg', prokind => 'a', proisstrict => 'f',
+  prorettype => 'anyrange', proargtypes => 'anyrange',
+  prosrc => 'aggregate_dummy'},
 
 { oid => '3914', descr => 'convert an int4 range to canonical form',
   proname => 'int4range_canonical', prorettype => 'int4range',
@@ -9924,6 +9943,261 @@
   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 => '8094', descr => 'lower bound of multirange',
+  proname => 'lower', prorettype => 'anyelement', proargtypes => 'anymultirange',
+  prosrc => 'multirange_lower' },
+{ oid => '8095', descr => 'upper bound of multirange',
+  proname => 'upper', prorettype => 'anyelement', proargtypes => 'anymultirange',
+  prosrc => 'multirange_upper' },
+{ oid => '8057', descr => 'is the multirange empty?',
+  proname => 'isempty', prorettype => 'bool',
+  proargtypes => 'anymultirange', prosrc => 'multirange_empty' },
+{ oid => '8096', descr => 'is the multirange\'s lower bound inclusive?',
+  proname => 'lower_inc', prorettype => 'bool', proargtypes => 'anymultirange',
+  prosrc => 'multirange_lower_inc' },
+{ oid => '8097', descr => 'is the multirange\'s upper bound inclusive?',
+  proname => 'upper_inc', prorettype => 'bool', proargtypes => 'anymultirange',
+  prosrc => 'multirange_upper_inc' },
+{ oid => '8098', descr => 'is the multirange\'s lower bound infinite?',
+  proname => 'lower_inf', prorettype => 'bool', proargtypes => 'anymultirange',
+  prosrc => 'multirange_lower_inf' },
+{ oid => '8099', descr => 'is the multirange\'s upper bound infinite?',
+  proname => 'upper_inf', prorettype => 'bool', proargtypes => 'anymultirange',
+  prosrc => 'multirange_upper_inf' },
+{ oid => '8008', descr => 'multirange typanalyze',
+  proname => 'multirange_typanalyze', provolatile => 's', prorettype => 'bool',
+  proargtypes => 'internal', prosrc => 'multirange_typanalyze' },
+{ oid => '8156', descr => 'restriction selectivity for multirange operators',
+  proname => 'multirangesel', provolatile => 's', prorettype => 'float8',
+  proargtypes => 'internal oid internal int4', prosrc => 'multirangesel' },
+{ oid => '8033',
+  proname => 'multirange_eq', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_eq' },
+{ oid => '8034',
+  proname => 'multirange_ne', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_ne' },
+{ oid => '8070',
+  proname => 'range_overlaps_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_overlaps_multirange' },
+{ oid => '8071',
+  proname => 'multirange_overlaps_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_overlaps_range' },
+{ oid => '8072',
+  proname => 'multirange_overlaps_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_overlaps_multirange' },
+{ oid => '8058',
+  proname => 'multirange_contains_elem', prorettype => 'bool',
+  proargtypes => 'anymultirange anyelement', prosrc => 'multirange_contains_elem' },
+{ oid => '8059',
+  proname => 'multirange_contains_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_contains_range' },
+{ oid => '8060',
+  proname => 'multirange_contains_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_contains_multirange' },
+{ oid => '8061',
+  proname => 'elem_contained_by_multirange', prorettype => 'bool',
+  proargtypes => 'anyelement anymultirange', prosrc => 'elem_contained_by_multirange' },
+{ oid => '8062',
+  proname => 'range_contained_by_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_contained_by_multirange' },
+{ oid => '8063',
+  proname => 'multirange_contained_by_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_contained_by_multirange' },
+{ oid => '8088',
+  proname => 'range_adjacent_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_adjacent_multirange' },
+{ oid => '8089',
+  proname => 'multirange_adjacent_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_adjacent_multirange' },
+{ oid => '8090',
+  proname => 'multirange_adjacent_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_adjacent_range' },
+{ oid => '8076',
+  proname => 'range_before_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_before_multirange' },
+{ oid => '8077',
+  proname => 'multirange_before_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_before_range' },
+{ oid => '8078',
+  proname => 'multirange_before_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_before_multirange' },
+{ oid => '8079',
+  proname => 'range_after_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_after_multirange' },
+{ oid => '8080',
+  proname => 'multirange_after_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_after_range' },
+{ oid => '8081',
+  proname => 'multirange_after_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_after_multirange' },
+{ oid => '8100',
+  proname => 'range_overleft_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_overleft_multirange' },
+{ oid => '8101',
+  proname => 'multirange_overleft_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_overleft_range' },
+{ oid => '8102',
+  proname => 'multirange_overleft_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_overleft_multirange' },
+{ oid => '8103',
+  proname => 'range_overright_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_overright_multirange' },
+{ oid => '8104',
+  proname => 'multirange_overright_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_overright_range' },
+{ oid => '8105',
+  proname => 'multirange_overright_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_overright_multirange' },
+{ oid => '8114',
+  proname => 'multirange_union', prorettype => 'anymultirange',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_union' },
+{ oid => '8120',
+  proname => 'multirange_minus', prorettype => 'anymultirange',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_minus' },
+{ oid => '8126',
+  proname => 'multirange_intersect', prorettype => 'anymultirange',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_intersect' },
+{ oid => '8022', descr => 'less-equal-greater',
+  proname => 'multirange_cmp', prorettype => 'int4',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_cmp' },
+{ oid => '8023',
+  proname => 'multirange_lt', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_lt' },
+{ oid => '8024',
+  proname => 'multirange_le', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_le' },
+{ oid => '8025',
+  proname => 'multirange_ge', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_ge' },
+{ oid => '8026',
+  proname => 'multirange_gt', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_gt' },
+{ oid => '8038', descr => 'hash a multirange',
+  proname => 'hash_multirange', prorettype => 'int4', proargtypes => 'anymultirange',
+  prosrc => 'hash_multirange' },
+{ oid => '8039', descr => 'hash a multirange',
+  proname => 'hash_multirange_extended', prorettype => 'int8',
+  proargtypes => 'anymultirange int8', prosrc => 'hash_multirange_extended' },
+
+{ oid => '8045', descr => 'int4multirange constructor',
+  proname => 'int4multirange', proisstrict => 'f',
+  prorettype => 'int4multirange', proargtypes => '',
+  prosrc => 'multirange_constructor0' },
+{ oid => '8146', descr => 'int4multirange constructor',
+  proname => 'int4multirange', proisstrict => 't',
+  prorettype => 'int4multirange', proargtypes => 'int4range',
+  prosrc => 'multirange_constructor1' },
+{ oid => '8046', descr => 'int4multirange constructor',
+  proname => 'int4multirange', provariadic => 'int4range', proisstrict => 'f',
+  prorettype => 'int4multirange', proargtypes => '_int4range',
+  proallargtypes => '{_int4range}', proargmodes => '{v}',
+  prosrc => 'multirange_constructor2' },
+{ oid => '8047', descr => 'nummultirange constructor',
+  proname => 'nummultirange', proisstrict => 'f',
+  prorettype => 'nummultirange', proargtypes => '',
+  prosrc => 'multirange_constructor0' },
+{ oid => '8147', descr => 'nummultirange constructor',
+  proname => 'nummultirange', proisstrict => 't',
+  prorettype => 'nummultirange', proargtypes => 'numrange',
+  prosrc => 'multirange_constructor1' },
+{ oid => '8048', descr => 'nummultirange constructor',
+  proname => 'nummultirange', provariadic => 'numrange', proisstrict => 'f',
+  prorettype => 'nummultirange', proargtypes => '_numrange',
+  proallargtypes => '{_numrange}', proargmodes => '{v}',
+  prosrc => 'multirange_constructor2' },
+{ oid => '8049', descr => 'tsmultirange constructor',
+  proname => 'tsmultirange', proisstrict => 'f',
+  prorettype => 'tsmultirange', proargtypes => '',
+  prosrc => 'multirange_constructor0' },
+{ oid => '8148', descr => 'tsmultirange constructor',
+  proname => 'tsmultirange', proisstrict => 't',
+  prorettype => 'tsmultirange', proargtypes => 'tsrange',
+  prosrc => 'multirange_constructor1' },
+{ oid => '8050', descr => 'tsmultirange constructor',
+  proname => 'tsmultirange', provariadic => 'tsrange', proisstrict => 'f',
+  prorettype => 'tsmultirange', proargtypes => '_tsrange',
+  proallargtypes => '{_tsrange}', proargmodes => '{v}',
+  prosrc => 'multirange_constructor2' },
+{ oid => '8051', descr => 'tstzmultirange constructor',
+  proname => 'tstzmultirange', proisstrict => 'f',
+  prorettype => 'tstzmultirange', proargtypes => '',
+  prosrc => 'multirange_constructor0' },
+{ oid => '8149', descr => 'tstzmultirange constructor',
+  proname => 'tstzmultirange', proisstrict => 't',
+  prorettype => 'tstzmultirange', proargtypes => 'tstzrange',
+  prosrc => 'multirange_constructor1' },
+{ oid => '8052', descr => 'tstzmultirange constructor',
+  proname => 'tstzmultirange', provariadic => 'tstzrange', proisstrict => 'f',
+  prorettype => 'tstzmultirange', proargtypes => '_tstzrange',
+  proallargtypes => '{_tstzrange}', proargmodes => '{v}',
+  prosrc => 'multirange_constructor2' },
+{ oid => '8053', descr => 'datemultirange constructor',
+  proname => 'datemultirange', proisstrict => 'f',
+  prorettype => 'datemultirange', proargtypes => '',
+  prosrc => 'multirange_constructor0' },
+{ oid => '8150', descr => 'datemultirange constructor',
+  proname => 'datemultirange', proisstrict => 't',
+  prorettype => 'datemultirange', proargtypes => 'daterange',
+  prosrc => 'multirange_constructor1' },
+{ oid => '8054', descr => 'datemultirange constructor',
+  proname => 'datemultirange', provariadic => 'daterange', proisstrict => 'f',
+  prorettype => 'datemultirange', proargtypes => '_daterange',
+  proallargtypes => '{_daterange}', proargmodes => '{v}',
+  prosrc => 'multirange_constructor2' },
+{ oid => '8055', descr => 'int8multirange constructor',
+  proname => 'int8multirange', proisstrict => 'f',
+  prorettype => 'int8multirange', proargtypes => '',
+  prosrc => 'multirange_constructor0' },
+{ oid => '8151', descr => 'int8multirange constructor',
+  proname => 'int8multirange', proisstrict => 't',
+  prorettype => 'int8multirange', proargtypes => 'int8range',
+  prosrc => 'multirange_constructor1' },
+{ oid => '8056', descr => 'int8multirange constructor',
+  proname => 'int8multirange', provariadic => 'int8range', proisstrict => 'f',
+  prorettype => 'int8multirange', proargtypes => '_int8range',
+  proallargtypes => '{_int8range}', proargmodes => '{v}',
+  prosrc => 'multirange_constructor2' },
+{ oid => '8152', descr => 'anymultirange cast',
+  proname => 'multirange', proisstrict => 't',
+  prorettype => 'anymultirange', proargtypes => 'anyrange',
+  prosrc => 'multirange_constructor1' },
+{ oid => '8130', descr => 'aggregate transition function',
+  proname => 'range_agg_transfn', proisstrict => 'f', prorettype => 'internal',
+  proargtypes => 'internal anyrange', prosrc => 'range_agg_transfn' },
+{ oid => '8131', descr => 'aggregate final function',
+  proname => 'range_agg_finalfn', proisstrict => 'f', prorettype => 'anymultirange',
+  proargtypes => 'internal anyrange', prosrc => 'range_agg_finalfn' },
+{ oid => '8132', descr => 'combine aggregate input into a multirange',
+  proname => 'range_agg', prokind => 'a', proisstrict => 'f',
+  prorettype => 'anymultirange', proargtypes => 'anyrange',
+  prosrc => 'aggregate_dummy' },
+{ oid => '8135', descr => 'range aggregate by intersecting',
+  proname => 'multirange_intersect_agg_transfn', prorettype => 'anymultirange',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_intersect_agg_transfn'},
+{ oid => '8136', descr => 'range aggregate by intersecting',
+  proname => 'range_intersect_agg', prokind => 'a', proisstrict => 'f',
+  prorettype => 'anymultirange', proargtypes => 'anymultirange',
+  prosrc => 'aggregate_dummy'},
+
 # date, time, timestamp constructors
 { oid => '3846', descr => 'construct date',
   proname => 'make_date', prorettype => 'date', proargtypes => 'int4 int4 int4',
@@ -10306,6 +10580,14 @@
   proname => 'binary_upgrade_set_next_array_pg_type_oid', provolatile => 'v',
   proparallel => 'r', prorettype => 'void', proargtypes => 'oid',
   prosrc => 'binary_upgrade_set_next_array_pg_type_oid' },
+{ oid => '8144', descr => 'for use by pg_upgrade',
+  proname => 'binary_upgrade_set_next_multirange_pg_type_oid', provolatile => 'v',
+  proparallel => 'r', prorettype => 'void', proargtypes => 'oid',
+  prosrc => 'binary_upgrade_set_next_multirange_pg_type_oid' },
+{ oid => '8145', descr => 'for use by pg_upgrade',
+  proname => 'binary_upgrade_set_next_multirange_array_pg_type_oid', provolatile => 'v',
+  proparallel => 'r', prorettype => 'void', proargtypes => 'oid',
+  prosrc => 'binary_upgrade_set_next_multirange_array_pg_type_oid' },
 { oid => '3585', descr => 'for use by pg_upgrade',
   proname => 'binary_upgrade_set_next_toast_pg_type_oid', provolatile => 'v',
   proparallel => 'r', prorettype => 'void', proargtypes => 'oid',
diff --git a/src/include/catalog/pg_range.dat b/src/include/catalog/pg_range.dat
index 479754c245..10060255c9 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',
+  rngmultitypid => 'int4multirange' },
 { rngtypid => 'numrange', rngsubtype => 'numeric',
   rngsubopc => 'btree/numeric_ops', rngcanonical => '-',
-  rngsubdiff => 'numrange_subdiff' },
+  rngsubdiff => 'numrange_subdiff', rngmultitypid => 'nummultirange' },
 { rngtypid => 'tsrange', rngsubtype => 'timestamp',
   rngsubopc => 'btree/timestamp_ops', rngcanonical => '-',
-  rngsubdiff => 'tsrange_subdiff' },
+  rngsubdiff => 'tsrange_subdiff', rngmultitypid => 'tsmultirange' },
 { rngtypid => 'tstzrange', rngsubtype => 'timestamptz',
   rngsubopc => 'btree/timestamptz_ops', rngcanonical => '-',
-  rngsubdiff => 'tstzrange_subdiff' },
+  rngsubdiff => 'tstzrange_subdiff', rngmultitypid => 'tstzmultirange' },
 { rngtypid => 'daterange', rngsubtype => 'date', rngsubopc => 'btree/date_ops',
-  rngcanonical => 'daterange_canonical', rngsubdiff => 'daterange_subdiff' },
+  rngcanonical => 'daterange_canonical', rngsubdiff => 'daterange_subdiff',
+  rngmultitypid => 'datemultirange' },
 { rngtypid => 'int8range', rngsubtype => 'int8', rngsubopc => 'btree/int8_ops',
-  rngcanonical => 'int8range_canonical', rngsubdiff => 'int8range_subdiff' },
+  rngcanonical => 'int8range_canonical', rngsubdiff => 'int8range_subdiff',
+  rngmultitypid => 'int8multirange' },
 
 ]
diff --git a/src/include/catalog/pg_range.h b/src/include/catalog/pg_range.h
index 98172bb1b6..60a8fde518 100644
--- a/src/include/catalog/pg_range.h
+++ b/src/include/catalog/pg_range.h
@@ -34,6 +34,9 @@ CATALOG(pg_range,3541,RangeRelationId)
 	/* OID of range's element type (subtype) */
 	Oid			rngsubtype BKI_LOOKUP(pg_type);
 
+	/* OID of the range's multirange type */
+	Oid			rngmultitypid BKI_LOOKUP(pg_type);
+
 	/* collation for this range type, or 0 */
 	Oid			rngcollation BKI_DEFAULT(0);
 
@@ -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 e8be000835..0c1e1e1b79 100644
--- a/src/include/catalog/pg_type.dat
+++ b/src/include/catalog/pg_type.dat
@@ -500,6 +500,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 => 'R', 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 => 'R', 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 => 'R', 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 => 'R', 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 => 'R', 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 => 'R', 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
@@ -629,5 +663,16 @@
   typtype => 'p', typcategory => 'P', typinput => 'anycompatiblerange_in',
   typoutput => 'anycompatiblerange_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' },
+{ oid => '8153',
+  descr => 'pseudo-type representing a multirange over a polymorphic common type',
+  typname => 'anycompatiblemultirange', typlen => '-1', typbyval => 'f',
+  typtype => 'p', typcategory => 'P', typinput => 'anycompatiblemultirange_in',
+  typoutput => 'anycompatiblemultirange_out', typreceive => '-', typsend => '-',
+  typalign => 'd', typstorage => 'x' },
 
 ]
diff --git a/src/include/catalog/pg_type.h b/src/include/catalog/pg_type.h
index 7b37562648..63e7c940a2 100644
--- a/src/include/catalog/pg_type.h
+++ b/src/include/catalog/pg_type.h
@@ -263,6 +263,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 */
 
@@ -304,13 +305,15 @@ typedef FormData_pg_type *Form_pg_type;
 	 (typid) == ANYARRAYOID || \
 	 (typid) == ANYNONARRAYOID || \
 	 (typid) == ANYENUMOID || \
-	 (typid) == ANYRANGEOID)
+	 (typid) == ANYRANGEOID || \
+	 (typid) == ANYMULTIRANGEOID)
 
 #define IsPolymorphicTypeFamily2(typid)  \
 	((typid) == ANYCOMPATIBLEOID || \
 	 (typid) == ANYCOMPATIBLEARRAYOID || \
 	 (typid) == ANYCOMPATIBLENONARRAYOID || \
-	 (typid) == ANYCOMPATIBLERANGEOID)
+	 (typid) == ANYCOMPATIBLERANGEOID || \
+	 (typid) == ANYCOMPATIBLEMULTIRANGEOID)
 
 #endif							/* EXPOSE_TO_CLIENT_CODE */
 
@@ -369,4 +372,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/commands/typecmds.h b/src/include/commands/typecmds.h
index 23130895af..989914dfe1 100644
--- a/src/include/commands/typecmds.h
+++ b/src/include/commands/typecmds.h
@@ -29,6 +29,8 @@ extern ObjectAddress DefineRange(CreateRangeStmt *stmt);
 extern ObjectAddress AlterEnum(AlterEnumStmt *stmt);
 extern ObjectAddress DefineCompositeType(RangeVar *typevar, List *coldeflist);
 extern Oid	AssignTypeArrayOid(void);
+extern Oid	AssignTypeMultirangeOid(void);
+extern Oid	AssignTypeMultirangeArrayOid(void);
 
 extern ObjectAddress AlterDomainDefault(List *names, Node *defaultRaw);
 extern ObjectAddress AlterDomainNotNull(List *names, bool notNull);
diff --git a/src/include/utils/lsyscache.h b/src/include/utils/lsyscache.h
index fecfe1f4f6..5eb00239b4 100644
--- a/src/include/utils/lsyscache.h
+++ b/src/include/utils/lsyscache.h
@@ -157,6 +157,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);
@@ -183,6 +184,8 @@ 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_collation(Oid rangeOid);
+extern Oid	get_range_multirange(Oid rangeOid);
+extern Oid	get_range_multirange_subtype(Oid multirangeOid);
 extern Oid	get_index_column_opclass(Oid index_oid, int attno);
 extern bool get_index_isreplident(Oid index_oid);
 extern bool get_index_isvalid(Oid index_oid);
diff --git a/src/include/utils/multirangetypes.h b/src/include/utils/multirangetypes.h
new file mode 100644
index 0000000000..086aaaa67a
--- /dev/null
+++ b/src/include/utils/multirangetypes.h
@@ -0,0 +1,103 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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
+{
+	int32		vl_len_;		/* 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);
+extern bool multirange_contains_elem_internal(TypeCacheEntry *typcache, MultirangeType *mr,
+											  Datum elem);
+extern bool multirange_contains_range_internal(TypeCacheEntry *typcache, MultirangeType *mr,
+											   RangeType *r);
+extern bool multirange_contains_multirange_internal(TypeCacheEntry *typcache,
+													MultirangeType *mr1,
+													MultirangeType *mr2);
+extern bool range_overlaps_multirange_internal(TypeCacheEntry *typcache, RangeType *r,
+											   MultirangeType *mr);
+extern bool multirange_overlaps_multirange_internal(TypeCacheEntry *typcache,
+													MultirangeType *mr1,
+													MultirangeType *mr2);
+extern bool range_before_multirange_internal(TypeCacheEntry *typcache, RangeType *r,
+											 MultirangeType *mr);
+extern bool range_after_multirange_internal(TypeCacheEntry *typcache, RangeType *r,
+											MultirangeType *mr);
+extern bool multirange_before_multirange_internal(TypeCacheEntry *typcache,
+												  MultirangeType *mr1,
+												  MultirangeType *mr2);
+extern MultirangeType *multirange_minus_internal(Oid mltrngtypoid,
+												 TypeCacheEntry *rangetyp,
+												 int32 range_count1,
+												 RangeType **ranges1,
+												 int32 range_count2,
+												 RangeType **ranges2);
+extern MultirangeType *multirange_intersect_internal(Oid mltrngtypoid,
+													 TypeCacheEntry *rangetyp,
+													 int32 range_count1,
+													 RangeType **ranges1,
+													 int32 range_count2,
+													 RangeType **ranges2);
+
+/* assorted support functions */
+extern TypeCacheEntry *multirange_get_typcache(FunctionCallInfo fcinfo,
+											   Oid mltrngtypid);
+extern void multirange_deserialize(const 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 b77c41cf1b..ac166868f9 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)
 
@@ -92,13 +94,21 @@ typedef struct
  * prototypes for functions defined in rangetypes.c
  */
 
-extern bool range_contains_elem_internal(TypeCacheEntry *typcache, const RangeType *r, Datum val);
-
 /* internal versions of the above */
+extern int	range_cmp_internal(TypeCacheEntry *typcache, const RangeType *r1,
+							   const RangeType *r2);
+extern uint32 hash_range_internal(TypeCacheEntry *typcache, const RangeType *r);
+extern uint64 hash_range_extended_internal(TypeCacheEntry *typcache, const RangeType *r,
+										   Datum seed);
+extern Datum range_lower_internal(TypeCacheEntry *typcache, const RangeType *r1,
+								  bool *isnull);
+extern Datum range_upper_internal(TypeCacheEntry *typcache, const RangeType *r1,
+								  bool *isnull);
 extern bool range_eq_internal(TypeCacheEntry *typcache, const RangeType *r1,
 							  const RangeType *r2);
 extern bool range_ne_internal(TypeCacheEntry *typcache, const RangeType *r1,
 							  const RangeType *r2);
+extern bool range_contains_elem_internal(TypeCacheEntry *typcache, const RangeType *r, Datum val);
 extern bool range_contains_internal(TypeCacheEntry *typcache, const RangeType *r1,
 									const RangeType *r2);
 extern bool range_contained_by_internal(TypeCacheEntry *typcache, const RangeType *r1,
@@ -115,6 +125,12 @@ extern bool range_overleft_internal(TypeCacheEntry *typcache, const RangeType *r
 									const RangeType *r2);
 extern bool range_overright_internal(TypeCacheEntry *typcache, const RangeType *r1,
 									 const RangeType *r2);
+extern RangeType *range_union_internal(TypeCacheEntry *typcache, RangeType *r1,
+									   RangeType *r2, bool strict);
+extern RangeType *range_minus_internal(TypeCacheEntry *typcache, RangeType *r1,
+									   RangeType *r2);
+extern RangeType *range_intersect_internal(TypeCacheEntry *typcache, const RangeType *r1,
+										   const RangeType *r2);
 
 /* assorted support functions */
 extern TypeCacheEntry *range_get_typcache(FunctionCallInfo fcinfo,
@@ -125,6 +141,7 @@ extern void range_deserialize(TypeCacheEntry *typcache, const RangeType *range,
 							  RangeBound *lower, RangeBound *upper,
 							  bool *empty);
 extern char range_get_flags(const RangeType *range);
+extern bool range_has_flag(const RangeType *range, char flag);
 extern void range_set_contain_empty(RangeType *range);
 extern RangeType *make_range(TypeCacheEntry *typcache, RangeBound *lower,
 							 RangeBound *upper, bool empty);
@@ -132,8 +149,12 @@ extern int	range_cmp_bounds(TypeCacheEntry *typcache, const RangeBound *b1,
 							 const RangeBound *b2);
 extern int	range_cmp_bound_values(TypeCacheEntry *typcache, const RangeBound *b1,
 								   const RangeBound *b2);
+extern int	range_compare(const void *key1, const void *key2, void *arg);
 extern bool bounds_adjacent(TypeCacheEntry *typcache, RangeBound bound1,
 							RangeBound bound2);
 extern RangeType *make_empty_range(TypeCacheEntry *typcache);
+extern bool range_split_internal(TypeCacheEntry *typcache, const RangeType *r1,
+								 const RangeType *r2, RangeType **output1,
+								 RangeType **output2);
 
 #endif							/* RANGETYPES_H */
diff --git a/src/include/utils/selfuncs.h b/src/include/utils/selfuncs.h
index 7ac4a06391..201a5f6a1c 100644
--- a/src/include/utils/selfuncs.h
+++ b/src/include/utils/selfuncs.h
@@ -39,6 +39,9 @@
 /* default selectivity estimate for range inequalities "A > b AND A < c" */
 #define DEFAULT_RANGE_INEQ_SEL	0.005
 
+/* default selectivity estimate for multirange inequalities "A > b AND A < c" */
+#define DEFAULT_MULTIRANGE_INEQ_SEL	0.005
+
 /* default selectivity estimate for pattern-match operators such as LIKE */
 #define DEFAULT_MATCH_SEL	0.005
 
diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h
index f27b73d76d..e8f393520a 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -79,6 +79,7 @@ enum SysCacheIdentifier
 	PUBLICATIONOID,
 	PUBLICATIONREL,
 	PUBLICATIONRELMAP,
+	RANGEMULTIRANGE,
 	RANGETYPE,
 	RELNAMENSP,
 	RELOID,
diff --git a/src/include/utils/typcache.h b/src/include/utils/typcache.h
index cdd20e56d7..33f332a141 100644
--- a/src/include/utils/typcache.h
+++ b/src/include/utils/typcache.h
@@ -100,6 +100,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;	/* multirange's range underlying type */
+
 	/*
 	 * Domain's base type and typmod if it's a domain type.  Zeroes if not
 	 * domain, or if information hasn't been requested.
@@ -143,6 +148,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 828ff5a288..1e3ff02289 100644
--- a/src/pl/plpgsql/src/pl_comp.c
+++ b/src/pl/plpgsql/src/pl_comp.c
@@ -513,6 +513,8 @@ do_compile(FunctionCallInfo fcinfo,
 					else if (rettypeid == ANYRANGEOID ||
 							 rettypeid == ANYCOMPATIBLERANGEOID)
 						rettypeid = INT4RANGEOID;
+					else if (rettypeid == ANYMULTIRANGEOID)
+						rettypeid = INT4MULTIRANGEOID;
 					else		/* ANYELEMENT or ANYNONARRAY or ANYCOMPATIBLE */
 						rettypeid = INT4OID;
 					/* XXX what could we use for ANYENUM? */
@@ -2089,6 +2091,7 @@ build_datatype(HeapTuple typeTup, int32 typmod,
 		case TYPTYPE_BASE:
 		case TYPTYPE_ENUM:
 		case TYPTYPE_RANGE:
+		case TYPTYPE_MULTIRANGE:
 			typ->ttype = PLPGSQL_TTYPE_SCALAR;
 			break;
 		case TYPTYPE_COMPOSITE:
@@ -2507,6 +2510,9 @@ plpgsql_resolve_polymorphic_argtypes(int numargs,
 				case ANYCOMPATIBLERANGEOID:
 					argtypes[i] = INT4RANGEOID;
 					break;
+				case ANYMULTIRANGEOID:
+					argtypes[i] = INT4MULTIRANGEOID;
+					break;
 				default:
 					break;
 			}
diff --git a/src/test/regress/expected/dependency.out b/src/test/regress/expected/dependency.out
index b4a11b8aa9..8232795148 100644
--- a/src/test/regress/expected/dependency.out
+++ b/src/test/regress/expected/dependency.out
@@ -140,6 +140,7 @@ owner of sequence deptest_a_seq
 owner of table deptest
 owner of function deptest_func()
 owner of type deptest_enum
+owner of type deptest_multirange
 owner of type deptest_range
 owner of table deptest2
 owner of sequence ss1
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..e72f99863f
--- /dev/null
+++ b/src/test/regress/expected/multirangetypes.out
@@ -0,0 +1,2437 @@
+-- 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)
+
+--
+-- test casts, both a built-in range type and a user-defined one:
+--
+select 'empty'::int4range::int4multirange;
+ int4multirange 
+----------------
+ {}
+(1 row)
+
+select int4range(1, 3)::int4multirange;
+ int4range 
+-----------
+ {[1,3)}
+(1 row)
+
+select int4range(1, null)::int4multirange;
+ int4range 
+-----------
+ {[1,)}
+(1 row)
+
+select int4range(null, null)::int4multirange;
+ int4range 
+-----------
+ {(,)}
+(1 row)
+
+select 'empty'::textrange::textmultirange;
+ textmultirange 
+----------------
+ {}
+(1 row)
+
+select textrange('a', 'c')::textmultirange;
+ textrange 
+-----------
+ {[a,c)}
+(1 row)
+
+select textrange('a', null)::textmultirange;
+ textrange 
+-----------
+ {[a,)}
+(1 row)
+
+select textrange(null, null)::textmultirange;
+ textrange 
+-----------
+ {(,)}
+(1 row)
+
+--
+-- create some test data and test the operators
+--
+CREATE TABLE nummultirange_test (nmr NUMMULTIRANGE);
+CREATE INDEX nummultirange_test_btree ON nummultirange_test(nmr);
+INSERT INTO nummultirange_test VALUES('{}');
+INSERT INTO nummultirange_test VALUES('{[,)}');
+INSERT INTO nummultirange_test VALUES('{[3,]}');
+INSERT INTO nummultirange_test VALUES('{[,), [3,]}');
+INSERT INTO nummultirange_test VALUES('{[, 5)}');
+INSERT INTO nummultirange_test VALUES(nummultirange());
+INSERT INTO nummultirange_test VALUES(nummultirange(variadic '{}'::numrange[]));
+INSERT INTO nummultirange_test VALUES(nummultirange(numrange(1.1, 2.2)));
+INSERT INTO nummultirange_test VALUES('{empty}');
+INSERT INTO nummultirange_test VALUES(nummultirange(numrange(1.7, 1.7, '[]'), numrange(1.7, 1.9)));
+INSERT INTO nummultirange_test VALUES(nummultirange(numrange(1.7, 1.7, '[]'), numrange(1.9, 2.1)));
+SELECT nmr, isempty(nmr), lower(nmr), upper(nmr) FROM nummultirange_test ORDER BY nmr;
+          nmr          | isempty | lower | upper 
+-----------------------+---------+-------+-------
+ {}                    | t       |       |      
+ {}                    | t       |       |      
+ {}                    | t       |       |      
+ {}                    | t       |       |      
+ {(,5)}                | f       |       |     5
+ {(,)}                 | f       |       |      
+ {(,)}                 | f       |       |      
+ {[1.1,2.2)}           | f       |   1.1 |   2.2
+ {[1.7,1.7],[1.9,2.1)} | f       |   1.7 |   2.1
+ {[1.7,1.9)}           | f       |   1.7 |   1.9
+ {[3,)}                | f       |     3 |      
+(11 rows)
+
+SELECT nmr, lower_inc(nmr), lower_inf(nmr), upper_inc(nmr), upper_inf(nmr) FROM nummultirange_test ORDER BY nmr;
+          nmr          | lower_inc | lower_inf | upper_inc | upper_inf 
+-----------------------+-----------+-----------+-----------+-----------
+ {}                    | f         | f         | f         | f
+ {}                    | f         | f         | f         | f
+ {}                    | f         | f         | f         | f
+ {}                    | f         | f         | f         | f
+ {(,5)}                | f         | t         | f         | f
+ {(,)}                 | f         | t         | f         | t
+ {(,)}                 | f         | t         | f         | t
+ {[1.1,2.2)}           | t         | f         | f         | f
+ {[1.7,1.7],[1.9,2.1)} | t         | f         | f         | f
+ {[1.7,1.9)}           | t         | f         | f         | f
+ {[3,)}                | t         | f         | f         | t
+(11 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr = '{}';
+ nmr 
+-----
+ {}
+ {}
+ {}
+ {}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr = '{(,5)}';
+  nmr   
+--------
+ {(,5)}
+(1 row)
+
+SELECT * FROM nummultirange_test WHERE nmr = '{[3,)}';
+  nmr   
+--------
+ {[3,)}
+(1 row)
+
+SELECT * FROM nummultirange_test WHERE nmr = '{[1.7,1.7]}';
+ nmr 
+-----
+(0 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr = '{[1.7,1.7],[1.9,2.1)}';
+          nmr          
+-----------------------
+ {[1.7,1.7],[1.9,2.1)}
+(1 row)
+
+SELECT * FROM nummultirange_test WHERE nmr < '{}';
+ nmr 
+-----
+(0 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr < '{[-1000.0, -1000.0]}';
+  nmr   
+--------
+ {}
+ {(,)}
+ {(,)}
+ {(,5)}
+ {}
+ {}
+ {}
+(7 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr < '{[0.0, 1.0]}';
+  nmr   
+--------
+ {}
+ {(,)}
+ {(,)}
+ {(,5)}
+ {}
+ {}
+ {}
+(7 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr < '{[1000.0, 1001.0]}';
+          nmr          
+-----------------------
+ {}
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+ {}
+ {}
+ {[1.1,2.2)}
+ {}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(11 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr <= '{}';
+ nmr 
+-----
+ {}
+ {}
+ {}
+ {}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr <= '{[3,)}';
+          nmr          
+-----------------------
+ {}
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+ {}
+ {}
+ {[1.1,2.2)}
+ {}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(11 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr >= '{}';
+          nmr          
+-----------------------
+ {}
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+ {}
+ {}
+ {[1.1,2.2)}
+ {}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(11 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr >= '{[3,)}';
+  nmr   
+--------
+ {[3,)}
+(1 row)
+
+SELECT * FROM nummultirange_test WHERE nmr > '{}';
+          nmr          
+-----------------------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+ {[1.1,2.2)}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(7 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr > '{[-1000.0, -1000.0]}';
+          nmr          
+-----------------------
+ {[3,)}
+ {[1.1,2.2)}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr > '{[0.0, 1.0]}';
+          nmr          
+-----------------------
+ {[3,)}
+ {[1.1,2.2)}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr > '{[1000.0, 1001.0]}';
+ nmr 
+-----
+(0 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr <> '{}';
+          nmr          
+-----------------------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+ {[1.1,2.2)}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(7 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr <> '{(,5)}';
+          nmr          
+-----------------------
+ {}
+ {(,)}
+ {[3,)}
+ {(,)}
+ {}
+ {}
+ {[1.1,2.2)}
+ {}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(10 rows)
+
+select nummultirange(numrange(2.0, 1.0));
+ERROR:  range lower bound must be less than or equal to range upper bound
+select nummultirange(numrange(5.0, 6.0), numrange(1.0, 2.0));
+     nummultirange     
+-----------------------
+ {[1.0,2.0),[5.0,6.0)}
+(1 row)
+
+analyze nummultirange_test;
+-- overlaps
+SELECT * FROM nummultirange_test WHERE range_overlaps_multirange(numrange(4.0, 4.2), nmr);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE numrange(4.0, 4.2) && nmr;
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE multirange_overlaps_range(nmr, numrange(4.0, 4.2));
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr && numrange(4.0, 4.2);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE multirange_overlaps_multirange(nmr, nummultirange(numrange(4.0, 4.2), numrange(6.0, 7.0)));
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr && nummultirange(numrange(4.0, 4.2), numrange(6.0, 7.0));
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr && nummultirange(numrange(6.0, 7.0));
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+(3 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr && nummultirange(numrange(6.0, 7.0), numrange(8.0, 9.0));
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+(3 rows)
+
+-- mr contains x
+SELECT * FROM nummultirange_test WHERE multirange_contains_elem(nmr, 4.0);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr @> 4.0;
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE multirange_contains_range(nmr, numrange(4.0, 4.2));
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr @> numrange(4.0, 4.2);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE multirange_contains_multirange(nmr, '{[4.0,4.2), [6.0, 8.0)}');
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+(3 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr @> '{[4.0,4.2), [6.0, 8.0)}'::nummultirange;
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+(3 rows)
+
+-- x is contained by mr
+SELECT * FROM nummultirange_test WHERE elem_contained_by_multirange(4.0, nmr);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE 4.0 <@ nmr;
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE range_contained_by_multirange(numrange(4.0, 4.2), nmr);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE numrange(4.0, 4.2) <@ nmr;
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE multirange_contained_by_multirange('{[4.0,4.2), [6.0, 8.0)}', nmr);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+(3 rows)
+
+SELECT * FROM nummultirange_test WHERE '{[4.0,4.2), [6.0, 8.0)}'::nummultirange <@ nmr;
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+(3 rows)
+
+-- overlaps
+SELECT 'empty'::numrange && nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT 'empty'::numrange && nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() && 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) && 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() && nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() && nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) && nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) && nummultirange(numrange(1,2), numrange(7,8));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(7,8)) && nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) && nummultirange(numrange(1,2), numrange(3.5,8));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(3.5,8)) && numrange(3,4);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(3.5,8)) && nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+-- contains
+SELECT nummultirange() @> nummultirange();
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange() @> 'empty'::numrange;
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(null,null)) @> numrange(1,2);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(null,null)) @> numrange(null,2);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(null,null)) @> numrange(2,null);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(null,5)) @> numrange(null,3);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(null,5)) @> numrange(null,8);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(5,null)) @> numrange(8,null);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(5,null)) @> numrange(3,null);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,5)) @> numrange(8,9);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,5)) @> numrange(3,9);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,5)) @> numrange(1,4);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,5)) @> numrange(1,5);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(-4,-2), numrange(1,5)) @> numrange(1,5);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,5), numrange(8,9)) @> numrange(1,5);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,5), numrange(8,9)) @> numrange(6,7);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,5), numrange(6,9)) @> numrange(6,7);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[1,5)}'::nummultirange @> '{[1,5)}';
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[-4,-2), [1,5)}'::nummultirange @> '{[1,5)}';
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[1,5), [8,9)}'::nummultirange @> '{[1,5)}';
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[1,5), [8,9)}'::nummultirange @> '{[6,7)}';
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT '{[1,5), [6,9)}'::nummultirange @> '{[6,7)}';
+ ?column? 
+----------
+ t
+(1 row)
+
+-- is contained by
+SELECT nummultirange() <@ nummultirange();
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT 'empty'::numrange <@ nummultirange();
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,2) <@ nummultirange(numrange(null,null));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(null,2) <@ nummultirange(numrange(null,null));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(2,null) <@ nummultirange(numrange(null,null));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(null,3) <@ nummultirange(numrange(null,5));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(null,8) <@ nummultirange(numrange(null,5));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(8,null) <@ nummultirange(numrange(5,null));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(3,null) <@ nummultirange(numrange(5,null));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(8,9) <@ nummultirange(numrange(1,5));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(3,9) <@ nummultirange(numrange(1,5));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(1,4) <@ nummultirange(numrange(1,5));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,5) <@ nummultirange(numrange(1,5));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,5) <@ nummultirange(numrange(-4,-2), numrange(1,5));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,5) <@ nummultirange(numrange(1,5), numrange(8,9));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(6,7) <@ nummultirange(numrange(1,5), numrange(8,9));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(6,7) <@ nummultirange(numrange(1,5), numrange(6,9));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[1,5)}' <@ '{[1,5)}'::nummultirange;
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[1,5)}' <@ '{[-4,-2), [1,5)}'::nummultirange;
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[1,5)}' <@ '{[1,5), [8,9)}'::nummultirange;
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[6,7)}' <@ '{[1,5), [8,9)}'::nummultirange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT '{[6,7)}' <@ '{[1,5), [6,9)}'::nummultirange;
+ ?column? 
+----------
+ t
+(1 row)
+
+-- overleft
+SELECT 'empty'::numrange &< nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT 'empty'::numrange &< nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() &< 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) &< 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() &< nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) &< nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() &< nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(6,7) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(1,2) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,4) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,6) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(3.5,6) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(6,7)) &< numrange(3,4);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) &< numrange(3,4);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,4)) &< numrange(3,4);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,6)) &< numrange(3,4);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3.5,6)) &< numrange(3,4);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(6,7)) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,4)) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,6)) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3.5,6)) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+-- overright
+SELECT nummultirange() &> 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) &> 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT 'empty'::numrange &> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT 'empty'::numrange &> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() &> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() &> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) &> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> numrange(6,7);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> numrange(1,2);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> numrange(1,4);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> numrange(1,6);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> numrange(3.5,6);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(3,4) &> nummultirange(numrange(6,7));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(3,4) &> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(3,4) &> nummultirange(numrange(1,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(3,4) &> nummultirange(numrange(1,6));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(3,4) &> nummultirange(numrange(3.5,6));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(6,7));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(1,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(1,6));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(3.5,6));
+ ?column? 
+----------
+ f
+(1 row)
+
+-- meets
+SELECT 'empty'::numrange -|- nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT 'empty'::numrange -|- nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() -|- 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() -|- nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() -|- nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(1,2) -|- nummultirange(numrange(2,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,2) -|- nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- numrange(2,4);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- numrange(3,4);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- nummultirange(numrange(2,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(5,6)) -|- nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(5,6)) -|- nummultirange(numrange(6,7));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(5,6)) -|- nummultirange(numrange(8,9));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- nummultirange(numrange(2,4), numrange(6,7));
+ ?column? 
+----------
+ t
+(1 row)
+
+-- strictly left
+select 'empty'::numrange << nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(1,2) << nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(1,2) << nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+select numrange(1,2) << nummultirange(numrange(0,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(1,2) << nummultirange(numrange(0,4), numrange(7,8));
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange() << 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange() << numrange(1,2);
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(3,4)) << numrange(3,6);
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(0,2)) << numrange(3,6);
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange(numrange(0,2), numrange(7,8)) << numrange(3,6);
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(-4,-2), numrange(0,2)) << numrange(3,6);
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange() << nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange() << nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(1,2)) << nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(1,2)) << nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(1,2)) << nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange(numrange(1,2)) << nummultirange(numrange(3,4), numrange(7,8));
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange(numrange(1,2), numrange(4,5)) << nummultirange(numrange(3,4), numrange(7,8));
+ ?column? 
+----------
+ f
+(1 row)
+
+-- strictly right
+select nummultirange() >> 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange() >> numrange(1,2);
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(3,4)) >> numrange(1,2);
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange(numrange(0,4)) >> numrange(1,2);
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(0,4), numrange(7,8)) >> numrange(1,2);
+ ?column? 
+----------
+ f
+(1 row)
+
+select 'empty'::numrange >> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(1,2) >> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(3,6) >> nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(3,6) >> nummultirange(numrange(0,2));
+ ?column? 
+----------
+ t
+(1 row)
+
+select numrange(3,6) >> nummultirange(numrange(0,2), numrange(7,8));
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(3,6) >> nummultirange(numrange(-4,-2), numrange(0,2));
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange() >> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(1,2)) >> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange() >> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(1,2)) >> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(3,4)) >> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange(numrange(3,4), numrange(7,8)) >> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange(numrange(3,4), numrange(7,8)) >> nummultirange(numrange(1,2), numrange(4,5));
+ ?column? 
+----------
+ f
+(1 row)
+
+-- union
+SELECT nummultirange() + nummultirange();
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange() + nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) + nummultirange();
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) + nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) + nummultirange(numrange(2,4));
+ ?column? 
+----------
+ {[1,4)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) + nummultirange(numrange(3,4));
+   ?column?    
+---------------
+ {[1,2),[3,4)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) + nummultirange(numrange(2,4));
+ ?column? 
+----------
+ {[1,5)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) + nummultirange(numrange(3,4));
+   ?column?    
+---------------
+ {[1,2),[3,5)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) + nummultirange(numrange(0,9));
+ ?column? 
+----------
+ {[0,9)}
+(1 row)
+
+-- merge
+SELECT range_merge(nummultirange());
+ range_merge 
+-------------
+ empty
+(1 row)
+
+SELECT range_merge(nummultirange(numrange(1,2)));
+ range_merge 
+-------------
+ [1,2)
+(1 row)
+
+SELECT range_merge(nummultirange(numrange(1,2), numrange(7,8)));
+ range_merge 
+-------------
+ [1,8)
+(1 row)
+
+-- minus
+SELECT nummultirange() - nummultirange();
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange() - nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) - nummultirange();
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(3,4)) - nummultirange();
+   ?column?    
+---------------
+ {[1,2),[3,4)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) - nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) - nummultirange(numrange(2,4));
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) - nummultirange(numrange(3,4));
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {[2,4)}
+(1 row)
+
+SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(2,3));
+   ?column?    
+---------------
+ {[1,2),[3,4)}
+(1 row)
+
+SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(0,8));
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(0,2));
+ ?column? 
+----------
+ {[2,4)}
+(1 row)
+
+SELECT nummultirange(numrange(1,8)) - nummultirange(numrange(0,2), numrange(3,4));
+   ?column?    
+---------------
+ {[2,3),[4,8)}
+(1 row)
+
+SELECT nummultirange(numrange(1,8)) - nummultirange(numrange(2,3), numrange(5,null));
+   ?column?    
+---------------
+ {[1,2),[3,5)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(-2,0));
+   ?column?    
+---------------
+ {[1,2),[4,5)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(2,4));
+   ?column?    
+---------------
+ {[1,2),[4,5)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(3,5));
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(0,9));
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange(numrange(1,3), numrange(4,5)) - nummultirange(numrange(2,9));
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(8,9));
+   ?column?    
+---------------
+ {[1,2),[4,5)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(-2,0), numrange(8,9));
+   ?column?    
+---------------
+ {[1,2),[4,5)}
+(1 row)
+
+-- intersection
+SELECT nummultirange() * nummultirange();
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange() * nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) * nummultirange();
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT '{[1,3)}'::nummultirange * '{[1,5)}'::nummultirange;
+ ?column? 
+----------
+ {[1,3)}
+(1 row)
+
+SELECT '{[1,3)}'::nummultirange * '{[0,5)}'::nummultirange;
+ ?column? 
+----------
+ {[1,3)}
+(1 row)
+
+SELECT '{[1,3)}'::nummultirange * '{[0,2)}'::nummultirange;
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT '{[1,3)}'::nummultirange * '{[2,5)}'::nummultirange;
+ ?column? 
+----------
+ {[2,3)}
+(1 row)
+
+SELECT '{[1,4)}'::nummultirange * '{[2,3)}'::nummultirange;
+ ?column? 
+----------
+ {[2,3)}
+(1 row)
+
+SELECT '{[1,4)}'::nummultirange * '{[0,2), [3,5)}'::nummultirange;
+   ?column?    
+---------------
+ {[1,2),[3,4)}
+(1 row)
+
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[0,8), [9,12)}'::nummultirange;
+       ?column?       
+----------------------
+ {[1,4),[7,8),[9,10)}
+(1 row)
+
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[9,12)}'::nummultirange;
+ ?column? 
+----------
+ {[9,10)}
+(1 row)
+
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[-5,-4), [5,6), [9,12)}'::nummultirange;
+ ?column? 
+----------
+ {[9,10)}
+(1 row)
+
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[0,2), [3,8), [9,12)}'::nummultirange;
+          ?column?          
+----------------------------
+ {[1,2),[3,4),[7,8),[9,10)}
+(1 row)
+
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[0,2), [3,8), [9,12)}'::nummultirange;
+          ?column?          
+----------------------------
+ {[1,2),[3,4),[7,8),[9,10)}
+(1 row)
+
+--
+-- range_agg function
+--
+create table reservations ( room_id integer not null, booked_during daterange );
+insert into reservations values
+-- 1: has a meets and a gap
+(1, daterange('2018-07-01', '2018-07-07')),
+(1, daterange('2018-07-07', '2018-07-14')),
+(1, daterange('2018-07-20', '2018-07-22')),
+-- 2: just a single row
+(2, daterange('2018-07-01', '2018-07-03')),
+-- 3: one null range
+(3, NULL),
+-- 4: two null ranges
+(4, NULL),
+(4, NULL),
+-- 5: a null range and a non-null range
+(5, NULL),
+(5, daterange('2018-07-01', '2018-07-03')),
+-- 6: has overlap
+(6, daterange('2018-07-01', '2018-07-07')),
+(6, daterange('2018-07-05', '2018-07-10')),
+-- 7: two ranges that meet: no gap or overlap
+(7, daterange('2018-07-01', '2018-07-07')),
+(7, daterange('2018-07-07', '2018-07-14')),
+-- 8: an empty range
+(8, 'empty'::daterange)
+;
+SELECT   room_id, range_agg(booked_during)
+FROM     reservations
+GROUP BY room_id
+ORDER BY room_id;
+ room_id |                     range_agg                     
+---------+---------------------------------------------------
+       1 | {[07-01-2018,07-14-2018),[07-20-2018,07-22-2018)}
+       2 | {[07-01-2018,07-03-2018)}
+       3 | 
+       4 | 
+       5 | {[07-01-2018,07-03-2018)}
+       6 | {[07-01-2018,07-10-2018)}
+       7 | {[07-01-2018,07-14-2018)}
+       8 | {}
+(8 rows)
+
+-- range_agg on a custom range type too
+SELECT  range_agg(r)
+FROM    (VALUES
+          ('[a,c]'::textrange),
+          ('[b,b]'::textrange),
+          ('[c,f]'::textrange),
+          ('[g,h)'::textrange),
+          ('[h,j)'::textrange)
+        ) t(r);
+   range_agg   
+---------------
+ {[a,f],[g,j)}
+(1 row)
+
+select range_intersect_agg(nmr) from nummultirange_test;
+ range_intersect_agg 
+---------------------
+ {}
+(1 row)
+
+select range_intersect_agg(nmr) from nummultirange_test where false;
+ range_intersect_agg 
+---------------------
+ 
+(1 row)
+
+-- test with just one input:
+select range_intersect_agg(nmr) from (values ('{[1,2]}'::nummultirange)) t(nmr);
+ range_intersect_agg 
+---------------------
+ {[1,2]}
+(1 row)
+
+select range_intersect_agg(nmr) from nummultirange_test where nmr @> 4.0;
+ range_intersect_agg 
+---------------------
+ {[3,5)}
+(1 row)
+
+create table nummultirange_test2(nmr nummultirange);
+create index nummultirange_test2_hash_idx on nummultirange_test2 using hash (nmr);
+INSERT INTO nummultirange_test2 VALUES('{[, 5)}');
+INSERT INTO nummultirange_test2 VALUES(nummultirange(numrange(1.1, 2.2)));
+INSERT INTO nummultirange_test2 VALUES(nummultirange(numrange(1.1, 2.2)));
+INSERT INTO nummultirange_test2 VALUES(nummultirange(numrange(1.1, 2.2,'()')));
+INSERT INTO nummultirange_test2 VALUES('{}');
+select * from nummultirange_test2 where nmr = '{}';
+ nmr 
+-----
+ {}
+(1 row)
+
+select * from nummultirange_test2 where nmr = nummultirange(numrange(1.1, 2.2));
+     nmr     
+-------------
+ {[1.1,2.2)}
+ {[1.1,2.2)}
+(2 rows)
+
+select * from nummultirange_test2 where nmr = nummultirange(numrange(1.1, 2.3));
+ nmr 
+-----
+(0 rows)
+
+set enable_nestloop=t;
+set enable_hashjoin=f;
+set enable_mergejoin=f;
+select * from nummultirange_test natural join nummultirange_test2 order by nmr;
+     nmr     
+-------------
+ {}
+ {}
+ {}
+ {}
+ {(,5)}
+ {[1.1,2.2)}
+ {[1.1,2.2)}
+(7 rows)
+
+set enable_nestloop=f;
+set enable_hashjoin=t;
+set enable_mergejoin=f;
+select * from nummultirange_test natural join nummultirange_test2 order by nmr;
+     nmr     
+-------------
+ {}
+ {}
+ {}
+ {}
+ {(,5)}
+ {[1.1,2.2)}
+ {[1.1,2.2)}
+(7 rows)
+
+set enable_nestloop=f;
+set enable_hashjoin=f;
+set enable_mergejoin=t;
+select * from nummultirange_test natural join nummultirange_test2 order by nmr;
+     nmr     
+-------------
+ {}
+ {}
+ {}
+ {}
+ {(,5)}
+ {[1.1,2.2)}
+ {[1.1,2.2)}
+(7 rows)
+
+set enable_nestloop to default;
+set enable_hashjoin to default;
+set enable_mergejoin to default;
+DROP TABLE nummultirange_test2;
+--
+-- Test user-defined multirange of floats
+--
+select '{[123.001, 5.e9)}'::float8multirange @> 888.882::float8;
+ ?column? 
+----------
+ t
+(1 row)
+
+create table float8multirange_test(f8mr float8multirange, i int);
+insert into float8multirange_test values(float8multirange(float8range(-100.00007, '1.111113e9')), 42);
+select * from float8multirange_test;
+           f8mr            | i  
+---------------------------+----
+ {[-100.00007,1111113000)} | 42
+(1 row)
+
+drop table float8multirange_test;
+--
+-- Test multirange types over domains
+--
+create domain mydomain as int4;
+create type mydomainrange as range(subtype=mydomain);
+select '{[4,50)}'::mydomainmultirange @> 7::mydomain;
+ ?column? 
+----------
+ t
+(1 row)
+
+drop domain mydomain cascade;
+NOTICE:  drop cascades to type mydomainrange
+--
+-- Test domains over multirange types
+--
+create domain restrictedmultirange as int4multirange check (upper(value) < 10);
+select '{[4,5)}'::restrictedmultirange @> 7;
+ ?column? 
+----------
+ f
+(1 row)
+
+select '{[4,50)}'::restrictedmultirange @> 7; -- should fail
+ERROR:  value for domain restrictedmultirange violates check constraint "restrictedmultirange_check"
+drop domain restrictedmultirange;
+--
+-- Test multiple multirange types over the same subtype
+--
+create type textrange1 as range(subtype=text, collation="C");
+create type textrange2 as range(subtype=text, collation="C");
+select textmultirange1(textrange2('a','Z'));  -- should fail
+ERROR:  function textmultirange1(textrange2) does not exist
+LINE 1: select textmultirange1(textrange2('a','Z'));
+               ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+select textmultirange1(textrange1('a','Z')) @> 'b'::text;
+ERROR:  range lower bound must be less than or equal to range upper bound
+select textmultirange2(textrange2('a','z')) @> 'b'::text;
+ ?column? 
+----------
+ t
+(1 row)
+
+drop type textrange1;
+drop type textrange2;
+--
+-- Test polymorphic type system
+--
+create function anyarray_anymultirange_func(a anyarray, r anymultirange)
+  returns anyelement as 'select $1[1] + lower($2);' language sql;
+select anyarray_anymultirange_func(ARRAY[1,2], int4multirange(int4range(10,20)));
+ anyarray_anymultirange_func 
+-----------------------------
+                          11
+(1 row)
+
+-- should fail
+select anyarray_anymultirange_func(ARRAY[1,2], nummultirange(numrange(10,20)));
+ERROR:  function anyarray_anymultirange_func(integer[], nummultirange) does not exist
+LINE 1: select anyarray_anymultirange_func(ARRAY[1,2], nummultirange...
+               ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+drop function anyarray_anymultirange_func(anyarray, anymultirange);
+-- should fail
+create function bogus_func(anyelement)
+  returns anymultirange as 'select int4multirange(int4range(1,10))' language sql;
+ERROR:  cannot determine result data type
+DETAIL:  A result of type anymultirange requires at least one input of type anyrange or anymultirange.
+-- should fail
+create function bogus_func(int)
+  returns anymultirange as 'select int4multirange(int4range(1,10))' language sql;
+ERROR:  cannot determine result data type
+DETAIL:  A result of type anymultirange requires at least one input of type anyrange or anymultirange.
+create function range_add_bounds(anymultirange)
+  returns anyelement as 'select lower($1) + upper($1)' language sql;
+select range_add_bounds(int4multirange(int4range(1, 17)));
+ range_add_bounds 
+------------------
+               18
+(1 row)
+
+select range_add_bounds(nummultirange(numrange(1.0001, 123.123)));
+ range_add_bounds 
+------------------
+         124.1231
+(1 row)
+
+create function multirangetypes_sql(q anymultirange, b anyarray, out c anyelement)
+  as $$ select upper($1) + $2[1] $$
+  language sql;
+select multirangetypes_sql(int4multirange(int4range(1,10)), ARRAY[2,20]);
+ multirangetypes_sql 
+---------------------
+                  12
+(1 row)
+
+select multirangetypes_sql(nummultirange(numrange(1,10)), ARRAY[2,20]);  -- match failure
+ERROR:  function multirangetypes_sql(nummultirange, integer[]) does not exist
+LINE 1: select multirangetypes_sql(nummultirange(numrange(1,10)), AR...
+               ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+create function anycompatiblearray_anycompatiblemultirange_func(a anycompatiblearray, mr anycompatiblemultirange)
+  returns anycompatible as 'select $1[1] + lower($2);' language sql;
+select anycompatiblearray_anycompatiblemultirange_func(ARRAY[1,2], multirange(int4range(10,20)));
+ anycompatiblearray_anycompatiblemultirange_func 
+-------------------------------------------------
+                                              11
+(1 row)
+
+select anycompatiblearray_anycompatiblemultirange_func(ARRAY[1,2], multirange(numrange(10,20)));
+ anycompatiblearray_anycompatiblemultirange_func 
+-------------------------------------------------
+                                              11
+(1 row)
+
+-- should fail
+select anycompatiblearray_anycompatiblemultirange_func(ARRAY[1.1,2], multirange(int4range(10,20)));
+ERROR:  function anycompatiblearray_anycompatiblemultirange_func(numeric[], int4multirange) does not exist
+LINE 1: select anycompatiblearray_anycompatiblemultirange_func(ARRAY...
+               ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+drop function anycompatiblearray_anycompatiblemultirange_func(anycompatiblearray, anycompatiblemultirange);
+create function anycompatiblerange_anycompatiblemultirange_func(r anycompatiblerange, mr anycompatiblemultirange)
+  returns anycompatible as 'select lower($1) + lower($2);' language sql;
+select anycompatiblerange_anycompatiblemultirange_func(int4range(1,2), multirange(int4range(10,20)));
+ anycompatiblerange_anycompatiblemultirange_func 
+-------------------------------------------------
+                                              11
+(1 row)
+
+-- should fail
+select anycompatiblerange_anycompatiblemultirange_func(numrange(1,2), multirange(int4range(10,20)));
+ERROR:  function anycompatiblerange_anycompatiblemultirange_func(numrange, int4multirange) does not exist
+LINE 1: select anycompatiblerange_anycompatiblemultirange_func(numra...
+               ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+drop function anycompatiblerange_anycompatiblemultirange_func(anycompatiblerange, anycompatiblemultirange);
+-- should fail
+create function bogus_func(anycompatible)
+  returns anycompatiblerange as 'select int4range(1,10)' language sql;
+ERROR:  cannot determine result data type
+DETAIL:  A result of type anycompatiblerange requires at least one input of type anycompatiblerange or anycompatiblemultirange.
+--
+-- Arrays of multiranges
+--
+select ARRAY[nummultirange(numrange(1.1, 1.2)), nummultirange(numrange(12.3, 155.5))];
+              array               
+----------------------------------
+ {"{[1.1,1.2)}","{[12.3,155.5)}"}
+(1 row)
+
+create table i8mr_array (f1 int, f2 int8multirange[]);
+insert into i8mr_array values (42, array[int8multirange(int8range(1,10)), int8multirange(int8range(2,20))]);
+select * from i8mr_array;
+ f1 |           f2            
+----+-------------------------
+ 42 | {"{[1,10)}","{[2,20)}"}
+(1 row)
+
+drop table i8mr_array;
+--
+-- Multiranges of arrays
+--
+select arraymultirange(arrayrange(ARRAY[1,2], ARRAY[2,1]));
+   arraymultirange   
+---------------------
+ {["{1,2}","{2,1}")}
+(1 row)
+
+select arraymultirange(arrayrange(ARRAY[2,1], ARRAY[1,2]));  -- fail
+ERROR:  range lower bound must be less than or equal to range upper bound
+select array[1,1] <@ arraymultirange(arrayrange(array[1,2], array[2,1]));
+ ?column? 
+----------
+ f
+(1 row)
+
+select array[1,3] <@ arraymultirange(arrayrange(array[1,2], array[2,1]));
+ ?column? 
+----------
+ t
+(1 row)
+
+--
+-- Ranges of composites
+--
+create type two_ints as (a int, b int);
+create type two_ints_range as range (subtype = two_ints);
+-- with force_parallel_mode on, this exercises tqueue.c's range remapping
+select *, row_to_json(upper(t)) as u from
+  (values (two_ints_multirange(two_ints_range(row(1,2), row(3,4)))),
+          (two_ints_multirange(two_ints_range(row(5,6), row(7,8))))) v(t);
+          t          |       u       
+---------------------+---------------
+ {["(1,2)","(3,4)")} | {"a":3,"b":4}
+ {["(5,6)","(7,8)")} | {"a":7,"b":8}
+(2 rows)
+
+drop type two_ints cascade;
+NOTICE:  drop cascades to type two_ints_range
+--
+-- Check behavior when subtype lacks a hash function
+--
+set enable_sort = off;  -- try to make it pick a hash setop implementation
+select '{(2,5)}'::cashmultirange except select '{(5,6)}'::cashmultirange;
+ cashmultirange  
+-----------------
+ {($2.00,$5.00)}
+(1 row)
+
+reset enable_sort;
+--
+-- OUT/INOUT/TABLE functions
+--
+-- infer anymultirange from anymultirange
+create function mr_outparam_succeed(i anymultirange, out r anymultirange, out t text)
+  as $$ select $1, 'foo'::text $$ language sql;
+select * from mr_outparam_succeed(int4multirange(int4range(1,2)));
+    r    |  t  
+---------+-----
+ {[1,2)} | foo
+(1 row)
+
+-- infer anyarray from anymultirange
+create function mr_outparam_succeed2(i anymultirange, out r anyarray, out t text)
+  as $$ select ARRAY[upper($1)], 'foo'::text $$ language sql;
+select * from mr_outparam_succeed2(int4multirange(int4range(1,2)));
+  r  |  t  
+-----+-----
+ {2} | foo
+(1 row)
+
+-- infer anyrange from anymultirange
+create function mr_outparam_succeed3(i anymultirange, out r anyrange, out t text)
+  as $$ select range_merge($1), 'foo'::text $$ language sql;
+select * from mr_outparam_succeed3(int4multirange(int4range(1,2)));
+   r   |  t  
+-------+-----
+ [1,2) | foo
+(1 row)
+
+-- infer anymultirange from anyrange
+create function mr_outparam_succeed4(i anyrange, out r anymultirange, out t text)
+  as $$ select multirange($1), 'foo'::text $$ language sql;
+select * from mr_outparam_succeed4(int4range(1,2));
+    r    |  t  
+---------+-----
+ {[1,2)} | foo
+(1 row)
+
+-- infer anyelement from anymultirange
+create function mr_inoutparam_succeed(out i anyelement, inout r anymultirange)
+  as $$ select upper($1), $1 $$ language sql;
+select * from mr_inoutparam_succeed(int4multirange(int4range(1,2)));
+ i |    r    
+---+---------
+ 2 | {[1,2)}
+(1 row)
+
+-- infer anyelement+anymultirange from anyelement+anymultirange
+create function mr_table_succeed(i anyelement, r anymultirange) returns table(i anyelement, r anymultirange)
+  as $$ select $1, $2 $$ language sql;
+select * from mr_table_succeed(123, int4multirange(int4range(1,11)));
+  i  |    r     
+-----+----------
+ 123 | {[1,11)}
+(1 row)
+
+-- use anymultirange in plpgsql
+create function mr_polymorphic(i anyrange) returns anymultirange
+  as $$ begin return multirange($1); end; $$ language plpgsql;
+select mr_polymorphic(int4range(1, 4));
+ mr_polymorphic 
+----------------
+ {[1,4)}
+(1 row)
+
+-- should fail
+create function mr_outparam_fail(i anyelement, out r anymultirange, out t text)
+  as $$ select '[1,10]', 'foo' $$ language sql;
+ERROR:  cannot determine result data type
+DETAIL:  A result of type anymultirange requires at least one input of type anyrange or anymultirange.
+--should fail
+create function mr_inoutparam_fail(inout i anyelement, out r anymultirange)
+  as $$ select $1, '[1,10]' $$ language sql;
+ERROR:  cannot determine result data type
+DETAIL:  A result of type anymultirange requires at least one input of type anyrange or anymultirange.
+--should fail
+create function mr_table_fail(i anyelement) returns table(i anyelement, r anymultirange)
+  as $$ select $1, '[1,10]' $$ language sql;
+ERROR:  cannot determine result data type
+DETAIL:  A result of type anymultirange requires at least one input of type anyrange or anymultirange.
diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out
index 27056d70d3..9466846993 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::regtype, p2.prorettype::regtype
 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        
@@ -208,6 +215,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        
@@ -228,6 +237,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        
@@ -340,7 +351,8 @@ WHERE p1.prorettype IN
      '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     
 ------+----------------
@@ -355,20 +367,25 @@ ORDER BY 2;
  3532 | enum_recv
 (9 rows)
 
--- anyrange is tighter than the rest, can only resolve from anyrange input
+-- anyrange and anymultirange are tighter than the rest, can only resolve
+-- from each other
 SELECT p1.oid, p1.proname
 FROM pg_proc as p1
-WHERE p1.prorettype = 'anyrange'::regtype
+WHERE p1.prorettype IN ('anyrange'::regtype, 'anymultirange'::regtype)
   AND NOT
-     'anyrange'::regtype = ANY (p1.proargtypes)
+    ('anyrange'::regtype = ANY (p1.proargtypes) OR
+      'anymultirange'::regtype = ANY (p1.proargtypes))
 ORDER BY 2;
  oid  |     proname      
 ------+------------------
+ 8002 | anymultirange_in
  3832 | anyrange_in
+ 8004 | multirange_in
+ 8006 | multirange_recv
  3876 | range_gist_union
  3834 | range_in
  3836 | range_recv
-(4 rows)
+(7 rows)
 
 -- similarly for the anycompatible family
 SELECT p1.oid, p1.proname
@@ -2193,13 +2210,14 @@ ORDER BY 1, 2, 3;
                     | float_ops        | float4_ops       | real
                     | float_ops        | float8_ops       | double precision
                     | jsonb_ops        | jsonb_ops        | jsonb
+                    | multirange_ops   | multirange_ops   | anymultirange
                     | numeric_ops      | numeric_ops      | numeric
                     | range_ops        | range_ops        | anyrange
                     | record_image_ops | record_image_ops | record
                     | record_ops       | record_ops       | record
                     | tsquery_ops      | tsquery_ops      | tsquery
                     | tsvector_ops     | tsvector_ops     | tsvector
-(14 rows)
+(15 rows)
 
 -- **************** pg_index ****************
 -- Look for illegal values in pg_index fields.
diff --git a/src/test/regress/expected/plpgsql.out b/src/test/regress/expected/plpgsql.out
index d0a6b630b8..d55006d8c9 100644
--- a/src/test/regress/expected/plpgsql.out
+++ b/src/test/regress/expected/plpgsql.out
@@ -1811,7 +1811,7 @@ begin
   return array[x + 1, x + 2];
 end$$ language plpgsql;
 ERROR:  cannot determine result data type
-DETAIL:  A result of type anyrange requires at least one input of type anyrange.
+DETAIL:  A result of type anyrange requires at least one input of type anyrange or anymultirange.
 create function f1(x anyrange) returns anyarray as $$
 begin
   return array[lower(x), upper(x)];
@@ -1856,7 +1856,7 @@ begin
   return array[x + 1, x + 2];
 end$$ language plpgsql;
 ERROR:  cannot determine result data type
-DETAIL:  A result of type anycompatiblerange requires at least one input of type anycompatiblerange.
+DETAIL:  A result of type anycompatiblerange requires at least one input of type anycompatiblerange or anycompatiblemultirange.
 create function f1(x anycompatiblerange, y anycompatiblearray) returns anycompatiblerange as $$
 begin
   return x;
diff --git a/src/test/regress/expected/polymorphism.out b/src/test/regress/expected/polymorphism.out
index 1ff40764d9..248c869931 100644
--- a/src/test/regress/expected/polymorphism.out
+++ b/src/test/regress/expected/polymorphism.out
@@ -61,7 +61,7 @@ create function polyf(x anyelement) returns anyrange as $$
   select array[x + 1, x + 2]
 $$ language sql;
 ERROR:  cannot determine result data type
-DETAIL:  A result of type anyrange requires at least one input of type anyrange.
+DETAIL:  A result of type anyrange requires at least one input of type anyrange or anymultirange.
 create function polyf(x anyrange) returns anyarray as $$
   select array[lower(x), upper(x)]
 $$ language sql;
@@ -97,12 +97,27 @@ LINE 1: select polyf(int4range(42, 49), 11, 4.5) as fail;
                ^
 HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
 drop function polyf(x anycompatiblerange, y anycompatible, z anycompatible);
+create function polyf(x anycompatiblemultirange, y anycompatible, z anycompatible) returns anycompatiblearray as $$
+  select array[lower(x), upper(x), y, z]
+$$ language sql;
+select polyf(multirange(int4range(42, 49)), 11, 2::smallint) as int, polyf(multirange(float8range(4.5, 7.8)), 7.8, 11::real) as num;
+     int      |       num        
+--------------+------------------
+ {42,49,11,2} | {4.5,7.8,7.8,11}
+(1 row)
+
+select polyf(multirange(int4range(42, 49)), 11, 4.5) as fail;  -- range type doesn't fit
+ERROR:  function polyf(int4multirange, integer, numeric) does not exist
+LINE 1: select polyf(multirange(int4range(42, 49)), 11, 4.5) as fail...
+               ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+drop function polyf(x anycompatiblemultirange, y anycompatible, z anycompatible);
 -- fail, can't infer type:
 create function polyf(x anycompatible) returns anycompatiblerange as $$
   select array[x + 1, x + 2]
 $$ language sql;
 ERROR:  cannot determine result data type
-DETAIL:  A result of type anycompatiblerange requires at least one input of type anycompatiblerange.
+DETAIL:  A result of type anycompatiblerange requires at least one input of type anycompatiblerange or anycompatiblemultirange.
 create function polyf(x anycompatiblerange, y anycompatiblearray) returns anycompatiblerange as $$
   select x
 $$ language sql;
@@ -113,6 +128,22 @@ select polyf(int4range(42, 49), array[11]) as int, polyf(float8range(4.5, 7.8),
 (1 row)
 
 drop function polyf(x anycompatiblerange, y anycompatiblearray);
+-- fail, can't infer type:
+create function polyf(x anycompatible) returns anycompatiblemultirange as $$
+  select array[x + 1, x + 2]
+$$ language sql;
+ERROR:  cannot determine result data type
+DETAIL:  A result of type anycompatiblemultirange requires at least one input of type anycompatiblerange or anycompatiblemultirange.
+create function polyf(x anycompatiblemultirange, y anycompatiblearray) returns anycompatiblemultirange as $$
+  select x
+$$ language sql;
+select polyf(multirange(int4range(42, 49)), array[11]) as int, polyf(multirange(float8range(4.5, 7.8)), array[7]) as num;
+    int    |     num     
+-----------+-------------
+ {[42,49)} | {[4.5,7.8)}
+(1 row)
+
+drop function polyf(x anycompatiblemultirange, y anycompatiblearray);
 create function polyf(a anyelement, b anyarray,
                       c anycompatible, d anycompatible,
                       OUT x anyarray, OUT y anycompatiblearray)
@@ -231,7 +262,7 @@ CREATE AGGREGATE myaggp01a(*) (SFUNC = stfnp, STYPE = int4[],
 CREATE AGGREGATE myaggp02a(*) (SFUNC = stfnp, STYPE = anyarray,
   FINALFUNC = ffp, INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 --     N    P
 -- should CREATE
 CREATE AGGREGATE myaggp03a(*) (SFUNC = stfp, STYPE = int4[],
@@ -243,11 +274,11 @@ CREATE AGGREGATE myaggp03b(*) (SFUNC = stfp, STYPE = int4[],
 CREATE AGGREGATE myaggp04a(*) (SFUNC = stfp, STYPE = anyarray,
   FINALFUNC = ffp, INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 CREATE AGGREGATE myaggp04b(*) (SFUNC = stfp, STYPE = anyarray,
   INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 --    Case2 (R = P) && ((B = P) || (B = N))
 --    -------------------------------------
 --    S    tf1      B    tf2
@@ -302,13 +333,13 @@ ERROR:  function tfp(integer[], anyelement) does not exist
 CREATE AGGREGATE myaggp13a(BASETYPE = int, SFUNC = tfnp, STYPE = anyarray,
   FINALFUNC = ffp, INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 --    P    N        N    P
 -- should ERROR: tf2p(anyarray, int) not matched by tf2p(int[],anyelement)
 CREATE AGGREGATE myaggp14a(BASETYPE = int, SFUNC = tf2p, STYPE = anyarray,
   FINALFUNC = ffp, INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 --    P    N        P    N
 -- should ERROR: tfnp(anyarray, anyelement) not matched by tfnp(int[],int)
 CREATE AGGREGATE myaggp15a(BASETYPE = anyelement, SFUNC = tfnp,
@@ -324,21 +355,21 @@ ERROR:  function tf2p(anyarray, anyelement) does not exist
 CREATE AGGREGATE myaggp17a(BASETYPE = int, SFUNC = tf1p, STYPE = anyarray,
   FINALFUNC = ffp, INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 CREATE AGGREGATE myaggp17b(BASETYPE = int, SFUNC = tf1p, STYPE = anyarray,
   INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 --    P    P        N    P
 -- should ERROR: tfp(anyarray, int) not matched by tfp(anyarray, anyelement)
 CREATE AGGREGATE myaggp18a(BASETYPE = int, SFUNC = tfp, STYPE = anyarray,
   FINALFUNC = ffp, INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 CREATE AGGREGATE myaggp18b(BASETYPE = int, SFUNC = tfp, STYPE = anyarray,
   INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 --    P    P        P    N
 -- should ERROR: tf1p(anyarray, anyelement) not matched by tf1p(anyarray, int)
 CREATE AGGREGATE myaggp19a(BASETYPE = anyelement, SFUNC = tf1p,
@@ -368,11 +399,11 @@ CREATE AGGREGATE myaggn01b(*) (SFUNC = stfnp, STYPE = int4[],
 CREATE AGGREGATE myaggn02a(*) (SFUNC = stfnp, STYPE = anyarray,
   FINALFUNC = ffnp, INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 CREATE AGGREGATE myaggn02b(*) (SFUNC = stfnp, STYPE = anyarray,
   INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 --     N    P
 -- should CREATE
 CREATE AGGREGATE myaggn03a(*) (SFUNC = stfp, STYPE = int4[],
@@ -382,7 +413,7 @@ CREATE AGGREGATE myaggn03a(*) (SFUNC = stfp, STYPE = int4[],
 CREATE AGGREGATE myaggn04a(*) (SFUNC = stfp, STYPE = anyarray,
   FINALFUNC = ffnp, INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 --    Case4 (R = N) && ((B = P) || (B = N))
 --    -------------------------------------
 --    S    tf1      B    tf2
@@ -436,21 +467,21 @@ ERROR:  function tfp(integer[], anyelement) does not exist
 CREATE AGGREGATE myaggn13a(BASETYPE = int, SFUNC = tfnp, STYPE = anyarray,
   FINALFUNC = ffnp, INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 CREATE AGGREGATE myaggn13b(BASETYPE = int, SFUNC = tfnp, STYPE = anyarray,
   INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 --    P    N        N    P
 -- should ERROR: tf2p(anyarray, int) not matched by tf2p(int[],anyelement)
 CREATE AGGREGATE myaggn14a(BASETYPE = int, SFUNC = tf2p, STYPE = anyarray,
   FINALFUNC = ffnp, INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 CREATE AGGREGATE myaggn14b(BASETYPE = int, SFUNC = tf2p, STYPE = anyarray,
   INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 --    P    N        P    N
 -- should ERROR: tfnp(anyarray, anyelement) not matched by tfnp(int[],int)
 CREATE AGGREGATE myaggn15a(BASETYPE = anyelement, SFUNC = tfnp,
@@ -472,13 +503,13 @@ ERROR:  function tf2p(anyarray, anyelement) does not exist
 CREATE AGGREGATE myaggn17a(BASETYPE = int, SFUNC = tf1p, STYPE = anyarray,
   FINALFUNC = ffnp, INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 --    P    P        N    P
 -- should ERROR: tfp(anyarray, int) not matched by tfp(anyarray, anyelement)
 CREATE AGGREGATE myaggn18a(BASETYPE = int, SFUNC = tfp, STYPE = anyarray,
   FINALFUNC = ffnp, INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 --    P    P        P    N
 -- should ERROR: tf1p(anyarray, anyelement) not matched by tf1p(anyarray, int)
 CREATE AGGREGATE myaggn19a(BASETYPE = anyelement, SFUNC = tf1p,
@@ -1853,7 +1884,59 @@ returns anycompatiblerange as $$
   select $1
 $$ language sql;
 ERROR:  cannot determine result data type
-DETAIL:  A result of type anycompatiblerange requires at least one input of type anycompatiblerange.
+DETAIL:  A result of type anycompatiblerange requires at least one input of type anycompatiblerange or anycompatiblemultirange.
+create function anyctest(anycompatible, anycompatiblemultirange)
+returns anycompatiblemultirange as $$
+  select $2
+$$ language sql;
+select x, pg_typeof(x) from anyctest(11, multirange(int4range(4,7))) x;
+    x    |   pg_typeof    
+---------+----------------
+ {[4,7)} | int4multirange
+(1 row)
+
+select x, pg_typeof(x) from anyctest(11, multirange(numrange(4,7))) x;
+    x    |   pg_typeof   
+---------+---------------
+ {[4,7)} | nummultirange
+(1 row)
+
+select x, pg_typeof(x) from anyctest(11, 12) x;  -- fail
+ERROR:  function anyctest(integer, integer) does not exist
+LINE 1: select x, pg_typeof(x) from anyctest(11, 12) x;
+                                    ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+select x, pg_typeof(x) from anyctest(11.2, multirange(int4range(4,7))) x;  -- fail
+ERROR:  function anyctest(numeric, int4multirange) does not exist
+LINE 1: select x, pg_typeof(x) from anyctest(11.2, multirange(int4ra...
+                                    ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+select x, pg_typeof(x) from anyctest(11.2, '{[4,7)}') x;  -- fail
+ERROR:  could not identify anycompatiblemultirange type
+drop function anyctest(anycompatible, anycompatiblemultirange);
+create function anyctest(anycompatiblemultirange, anycompatiblemultirange)
+returns anycompatible as $$
+  select lower($1) + upper($2)
+$$ language sql;
+select x, pg_typeof(x) from anyctest(multirange(int4range(11,12)), multirange(int4range(4,7))) x;
+ x  | pg_typeof 
+----+-----------
+ 18 | integer
+(1 row)
+
+select x, pg_typeof(x) from anyctest(multirange(int4range(11,12)), multirange(numrange(4,7))) x; -- fail
+ERROR:  function anyctest(int4multirange, nummultirange) does not exist
+LINE 1: select x, pg_typeof(x) from anyctest(multirange(int4range(11...
+                                    ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+drop function anyctest(anycompatiblemultirange, anycompatiblemultirange);
+-- fail, can't infer result type:
+create function anyctest(anycompatible)
+returns anycompatiblemultirange as $$
+  select $1
+$$ language sql;
+ERROR:  cannot determine result data type
+DETAIL:  A result of type anycompatiblemultirange requires at least one input of type anycompatiblerange or anycompatiblemultirange.
 create function anyctest(anycompatiblenonarray, anycompatiblenonarray)
 returns anycompatiblearray as $$
   select array[$1, $2]
diff --git a/src/test/regress/expected/rangefuncs.out b/src/test/regress/expected/rangefuncs.out
index 7eced28452..71e2769f44 100644
--- a/src/test/regress/expected/rangefuncs.out
+++ b/src/test/regress/expected/rangefuncs.out
@@ -1556,7 +1556,7 @@ DROP FUNCTION dup(anyelement);
 CREATE FUNCTION bad (f1 int, out f2 anyelement, out f3 anyarray)
 AS 'select $1, array[$1,$1]' LANGUAGE sql;
 ERROR:  cannot determine result data type
-DETAIL:  A result of type anyelement requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyelement requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 CREATE FUNCTION dup (f1 anycompatible, f2 anycompatiblearray, f3 out anycompatible, f4 out anycompatiblearray)
 AS 'select $1, $2' LANGUAGE sql;
 SELECT dup(22, array[44]);
diff --git a/src/test/regress/expected/rangetypes.out b/src/test/regress/expected/rangetypes.out
index c421f5394f..6a1bbadc91 100644
--- a/src/test/regress/expected/rangetypes.out
+++ b/src/test/regress/expected/rangetypes.out
@@ -518,6 +518,25 @@ select numrange(1.0, 2.0) * numrange(2.5, 3.0);
  empty
 (1 row)
 
+select range_intersect_agg(nr) from numrange_test;
+ range_intersect_agg 
+---------------------
+ empty
+(1 row)
+
+select range_intersect_agg(nr) from numrange_test where false;
+ range_intersect_agg 
+---------------------
+ 
+(1 row)
+
+select range_intersect_agg(nr) from numrange_test where nr @> 4.0;
+ range_intersect_agg 
+---------------------
+ [3,5)
+(1 row)
+
+analyze numrange_test;
 create table numrange_test2(nr numrange);
 create index numrange_test2_hash_idx on numrange_test2 using hash (nr);
 INSERT INTO numrange_test2 VALUES('[, 5)');
@@ -1371,12 +1390,12 @@ drop function anyarray_anyrange_func(anyarray, anyrange);
 create function bogus_func(anyelement)
   returns anyrange as 'select int4range(1,10)' language sql;
 ERROR:  cannot determine result data type
-DETAIL:  A result of type anyrange requires at least one input of type anyrange.
+DETAIL:  A result of type anyrange requires at least one input of type anyrange or anymultirange.
 -- should fail
 create function bogus_func(int)
   returns anyrange as 'select int4range(1,10)' language sql;
 ERROR:  cannot determine result data type
-DETAIL:  A result of type anyrange requires at least one input of type anyrange.
+DETAIL:  A result of type anyrange requires at least one input of type anyrange or anymultirange.
 create function range_add_bounds(anyrange)
   returns anyelement as 'select lower($1) + upper($1)' language sql;
 select range_add_bounds(int4range(1, 17));
@@ -1430,7 +1449,7 @@ drop function anycompatiblearray_anycompatiblerange_func(anycompatiblearray, any
 create function bogus_func(anycompatible)
   returns anycompatiblerange as 'select int4range(1,10)' language sql;
 ERROR:  cannot determine result data type
-DETAIL:  A result of type anycompatiblerange requires at least one input of type anycompatiblerange.
+DETAIL:  A result of type anycompatiblerange requires at least one input of type anycompatiblerange or anycompatiblemultirange.
 --
 -- Arrays of ranges
 --
@@ -1508,6 +1527,7 @@ reset enable_sort;
 --
 -- OUT/INOUT/TABLE functions
 --
+-- infer anyrange from anyrange
 create function outparam_succeed(i anyrange, out r anyrange, out t text)
   as $$ select $1, 'foo'::text $$ language sql;
 select * from outparam_succeed(int4range(1,2));
@@ -1525,6 +1545,16 @@ select * from outparam2_succeed(int4range(1,11));
  {1,11} | {11,1}
 (1 row)
 
+-- infer anyarray from anyrange
+create function outparam_succeed2(i anyrange, out r anyarray, out t text)
+  as $$ select ARRAY[upper($1)], 'foo'::text $$ language sql;
+select * from outparam_succeed2(int4range(int4range(1,2)));
+  r  |  t  
+-----+-----
+ {2} | foo
+(1 row)
+
+-- infer anyelement from anyrange
 create function inoutparam_succeed(out i anyelement, inout r anyrange)
   as $$ select upper($1), $1 $$ language sql;
 select * from inoutparam_succeed(int4range(1,2));
@@ -1547,14 +1577,14 @@ select * from table_succeed(int4range(1,11));
 create function outparam_fail(i anyelement, out r anyrange, out t text)
   as $$ select '[1,10]', 'foo' $$ language sql;
 ERROR:  cannot determine result data type
-DETAIL:  A result of type anyrange requires at least one input of type anyrange.
+DETAIL:  A result of type anyrange requires at least one input of type anyrange or anymultirange.
 --should fail
 create function inoutparam_fail(inout i anyelement, out r anyrange)
   as $$ select $1, '[1,10]' $$ language sql;
 ERROR:  cannot determine result data type
-DETAIL:  A result of type anyrange requires at least one input of type anyrange.
+DETAIL:  A result of type anyrange requires at least one input of type anyrange or anymultirange.
 --should fail
 create function table_fail(i anyelement) returns table(i anyelement, r anyrange)
   as $$ select $1, '[1,10]' $$ language sql;
 ERROR:  cannot determine result data type
-DETAIL:  A result of type anyrange requires at least one input of type anyrange.
+DETAIL:  A result of type anyrange requires at least one input of type anyrange or anymultirange.
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index 192445878d..d9ce961be2 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -92,6 +92,7 @@ num_exp_sqrt|t
 num_exp_sub|t
 num_input_test|f
 num_result|f
+nummultirange_test|t
 numrange_test|t
 onek|t
 onek2|t
@@ -172,6 +173,7 @@ quad_poly_tbl|t
 radix_text_tbl|t
 ramp|f
 real_city|f
+reservations|f
 road|t
 shighway|t
 slow_emp4000|f
diff --git a/src/test/regress/expected/type_sanity.out b/src/test/regress/expected/type_sanity.out
index 274130e706..8df1ae47e4 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'));
@@ -195,18 +195,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
@@ -240,17 +241,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
@@ -318,18 +320,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
@@ -363,17 +366,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
@@ -631,3 +635,11 @@ WHERE pronargs != 2
 ----------+------------+---------
 (0 rows)
 
+-- every range should have a valid multirange
+SELECT p1.rngtypid, p1.rngsubtype, p1.rngmultitypid
+FROM pg_range p1
+WHERE p1.rngmultitypid IS NULL OR p1.rngmultitypid = 0;
+ rngtypid | rngsubtype | rngmultitypid 
+----------+------------+---------------
+(0 rows)
+
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 026ea880cd..f795398bfc 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -19,6 +19,7 @@ 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 xid
 
@@ -27,7 +28,7 @@ test: strings numerology point lseg line box path polygon circle date time timet
 # geometry depends on point, lseg, box, path, polygon and circle
 # horology depends on interval, timetz, timestamp, timestamptz
 # ----------
-test: geometry horology regex oidjoins type_sanity opr_sanity misc_sanity comments expressions unicode
+test: geometry horology regex oidjoins type_sanity opr_sanity misc_sanity comments expressions unicode multirangetypes
 
 # ----------
 # These four each depend on the previous one
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index 979d926119..dfbd881ebf 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -20,6 +20,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..2a6b4a0a29
--- /dev/null
+++ b/src/test/regress/sql/multirangetypes.sql
@@ -0,0 +1,658 @@
+-- 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'));
+
+--
+-- test casts, both a built-in range type and a user-defined one:
+--
+select 'empty'::int4range::int4multirange;
+select int4range(1, 3)::int4multirange;
+select int4range(1, null)::int4multirange;
+select int4range(null, null)::int4multirange;
+select 'empty'::textrange::textmultirange;
+select textrange('a', 'c')::textmultirange;
+select textrange('a', null)::textmultirange;
+select textrange(null, null)::textmultirange;
+
+--
+-- create some test data and test the operators
+--
+
+CREATE TABLE nummultirange_test (nmr NUMMULTIRANGE);
+CREATE INDEX nummultirange_test_btree ON nummultirange_test(nmr);
+
+INSERT INTO nummultirange_test VALUES('{}');
+INSERT INTO nummultirange_test VALUES('{[,)}');
+INSERT INTO nummultirange_test VALUES('{[3,]}');
+INSERT INTO nummultirange_test VALUES('{[,), [3,]}');
+INSERT INTO nummultirange_test VALUES('{[, 5)}');
+INSERT INTO nummultirange_test VALUES(nummultirange());
+INSERT INTO nummultirange_test VALUES(nummultirange(variadic '{}'::numrange[]));
+INSERT INTO nummultirange_test VALUES(nummultirange(numrange(1.1, 2.2)));
+INSERT INTO nummultirange_test VALUES('{empty}');
+INSERT INTO nummultirange_test VALUES(nummultirange(numrange(1.7, 1.7, '[]'), numrange(1.7, 1.9)));
+INSERT INTO nummultirange_test VALUES(nummultirange(numrange(1.7, 1.7, '[]'), numrange(1.9, 2.1)));
+
+SELECT nmr, isempty(nmr), lower(nmr), upper(nmr) FROM nummultirange_test ORDER BY nmr;
+SELECT nmr, lower_inc(nmr), lower_inf(nmr), upper_inc(nmr), upper_inf(nmr) FROM nummultirange_test ORDER BY nmr;
+
+SELECT * FROM nummultirange_test WHERE nmr = '{}';
+SELECT * FROM nummultirange_test WHERE nmr = '{(,5)}';
+SELECT * FROM nummultirange_test WHERE nmr = '{[3,)}';
+SELECT * FROM nummultirange_test WHERE nmr = '{[1.7,1.7]}';
+SELECT * FROM nummultirange_test WHERE nmr = '{[1.7,1.7],[1.9,2.1)}';
+SELECT * FROM nummultirange_test WHERE nmr < '{}';
+SELECT * FROM nummultirange_test WHERE nmr < '{[-1000.0, -1000.0]}';
+SELECT * FROM nummultirange_test WHERE nmr < '{[0.0, 1.0]}';
+SELECT * FROM nummultirange_test WHERE nmr < '{[1000.0, 1001.0]}';
+SELECT * FROM nummultirange_test WHERE nmr <= '{}';
+SELECT * FROM nummultirange_test WHERE nmr <= '{[3,)}';
+SELECT * FROM nummultirange_test WHERE nmr >= '{}';
+SELECT * FROM nummultirange_test WHERE nmr >= '{[3,)}';
+SELECT * FROM nummultirange_test WHERE nmr > '{}';
+SELECT * FROM nummultirange_test WHERE nmr > '{[-1000.0, -1000.0]}';
+SELECT * FROM nummultirange_test WHERE nmr > '{[0.0, 1.0]}';
+SELECT * FROM nummultirange_test WHERE nmr > '{[1000.0, 1001.0]}';
+SELECT * FROM nummultirange_test WHERE nmr <> '{}';
+SELECT * FROM nummultirange_test WHERE nmr <> '{(,5)}';
+
+select nummultirange(numrange(2.0, 1.0));
+select nummultirange(numrange(5.0, 6.0), numrange(1.0, 2.0));
+
+analyze nummultirange_test;
+
+-- overlaps
+SELECT * FROM nummultirange_test WHERE range_overlaps_multirange(numrange(4.0, 4.2), nmr);
+SELECT * FROM nummultirange_test WHERE numrange(4.0, 4.2) && nmr;
+SELECT * FROM nummultirange_test WHERE multirange_overlaps_range(nmr, numrange(4.0, 4.2));
+SELECT * FROM nummultirange_test WHERE nmr && numrange(4.0, 4.2);
+SELECT * FROM nummultirange_test WHERE multirange_overlaps_multirange(nmr, nummultirange(numrange(4.0, 4.2), numrange(6.0, 7.0)));
+SELECT * FROM nummultirange_test WHERE nmr && nummultirange(numrange(4.0, 4.2), numrange(6.0, 7.0));
+SELECT * FROM nummultirange_test WHERE nmr && nummultirange(numrange(6.0, 7.0));
+SELECT * FROM nummultirange_test WHERE nmr && nummultirange(numrange(6.0, 7.0), numrange(8.0, 9.0));
+
+-- mr contains x
+SELECT * FROM nummultirange_test WHERE multirange_contains_elem(nmr, 4.0);
+SELECT * FROM nummultirange_test WHERE nmr @> 4.0;
+SELECT * FROM nummultirange_test WHERE multirange_contains_range(nmr, numrange(4.0, 4.2));
+SELECT * FROM nummultirange_test WHERE nmr @> numrange(4.0, 4.2);
+SELECT * FROM nummultirange_test WHERE multirange_contains_multirange(nmr, '{[4.0,4.2), [6.0, 8.0)}');
+SELECT * FROM nummultirange_test WHERE nmr @> '{[4.0,4.2), [6.0, 8.0)}'::nummultirange;
+
+-- x is contained by mr
+SELECT * FROM nummultirange_test WHERE elem_contained_by_multirange(4.0, nmr);
+SELECT * FROM nummultirange_test WHERE 4.0 <@ nmr;
+SELECT * FROM nummultirange_test WHERE range_contained_by_multirange(numrange(4.0, 4.2), nmr);
+SELECT * FROM nummultirange_test WHERE numrange(4.0, 4.2) <@ nmr;
+SELECT * FROM nummultirange_test WHERE multirange_contained_by_multirange('{[4.0,4.2), [6.0, 8.0)}', nmr);
+SELECT * FROM nummultirange_test WHERE '{[4.0,4.2), [6.0, 8.0)}'::nummultirange <@ nmr;
+
+-- overlaps
+SELECT 'empty'::numrange && nummultirange();
+SELECT 'empty'::numrange && nummultirange(numrange(1,2));
+SELECT nummultirange() && 'empty'::numrange;
+SELECT nummultirange(numrange(1,2)) && 'empty'::numrange;
+SELECT nummultirange() && nummultirange();
+SELECT nummultirange() && nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) && nummultirange();
+SELECT nummultirange(numrange(3,4)) && nummultirange(numrange(1,2), numrange(7,8));
+SELECT nummultirange(numrange(1,2), numrange(7,8)) && nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(3,4)) && nummultirange(numrange(1,2), numrange(3.5,8));
+SELECT nummultirange(numrange(1,2), numrange(3.5,8)) && numrange(3,4);
+SELECT nummultirange(numrange(1,2), numrange(3.5,8)) && nummultirange(numrange(3,4));
+
+-- contains
+SELECT nummultirange() @> nummultirange();
+SELECT nummultirange() @> 'empty'::numrange;
+SELECT nummultirange(numrange(null,null)) @> numrange(1,2);
+SELECT nummultirange(numrange(null,null)) @> numrange(null,2);
+SELECT nummultirange(numrange(null,null)) @> numrange(2,null);
+SELECT nummultirange(numrange(null,5)) @> numrange(null,3);
+SELECT nummultirange(numrange(null,5)) @> numrange(null,8);
+SELECT nummultirange(numrange(5,null)) @> numrange(8,null);
+SELECT nummultirange(numrange(5,null)) @> numrange(3,null);
+SELECT nummultirange(numrange(1,5)) @> numrange(8,9);
+SELECT nummultirange(numrange(1,5)) @> numrange(3,9);
+SELECT nummultirange(numrange(1,5)) @> numrange(1,4);
+SELECT nummultirange(numrange(1,5)) @> numrange(1,5);
+SELECT nummultirange(numrange(-4,-2), numrange(1,5)) @> numrange(1,5);
+SELECT nummultirange(numrange(1,5), numrange(8,9)) @> numrange(1,5);
+SELECT nummultirange(numrange(1,5), numrange(8,9)) @> numrange(6,7);
+SELECT nummultirange(numrange(1,5), numrange(6,9)) @> numrange(6,7);
+SELECT '{[1,5)}'::nummultirange @> '{[1,5)}';
+SELECT '{[-4,-2), [1,5)}'::nummultirange @> '{[1,5)}';
+SELECT '{[1,5), [8,9)}'::nummultirange @> '{[1,5)}';
+SELECT '{[1,5), [8,9)}'::nummultirange @> '{[6,7)}';
+SELECT '{[1,5), [6,9)}'::nummultirange @> '{[6,7)}';
+
+-- is contained by
+SELECT nummultirange() <@ nummultirange();
+SELECT 'empty'::numrange <@ nummultirange();
+SELECT numrange(1,2) <@ nummultirange(numrange(null,null));
+SELECT numrange(null,2) <@ nummultirange(numrange(null,null));
+SELECT numrange(2,null) <@ nummultirange(numrange(null,null));
+SELECT numrange(null,3) <@ nummultirange(numrange(null,5));
+SELECT numrange(null,8) <@ nummultirange(numrange(null,5));
+SELECT numrange(8,null) <@ nummultirange(numrange(5,null));
+SELECT numrange(3,null) <@ nummultirange(numrange(5,null));
+SELECT numrange(8,9) <@ nummultirange(numrange(1,5));
+SELECT numrange(3,9) <@ nummultirange(numrange(1,5));
+SELECT numrange(1,4) <@ nummultirange(numrange(1,5));
+SELECT numrange(1,5) <@ nummultirange(numrange(1,5));
+SELECT numrange(1,5) <@ nummultirange(numrange(-4,-2), numrange(1,5));
+SELECT numrange(1,5) <@ nummultirange(numrange(1,5), numrange(8,9));
+SELECT numrange(6,7) <@ nummultirange(numrange(1,5), numrange(8,9));
+SELECT numrange(6,7) <@ nummultirange(numrange(1,5), numrange(6,9));
+SELECT '{[1,5)}' <@ '{[1,5)}'::nummultirange;
+SELECT '{[1,5)}' <@ '{[-4,-2), [1,5)}'::nummultirange;
+SELECT '{[1,5)}' <@ '{[1,5), [8,9)}'::nummultirange;
+SELECT '{[6,7)}' <@ '{[1,5), [8,9)}'::nummultirange;
+SELECT '{[6,7)}' <@ '{[1,5), [6,9)}'::nummultirange;
+
+-- overleft
+SELECT 'empty'::numrange &< nummultirange();
+SELECT 'empty'::numrange &< nummultirange(numrange(1,2));
+SELECT nummultirange() &< 'empty'::numrange;
+SELECT nummultirange(numrange(1,2)) &< 'empty'::numrange;
+SELECT nummultirange() &< nummultirange();
+SELECT nummultirange(numrange(1,2)) &< nummultirange();
+SELECT nummultirange() &< nummultirange(numrange(1,2));
+SELECT numrange(6,7) &< nummultirange(numrange(3,4));
+SELECT numrange(1,2) &< nummultirange(numrange(3,4));
+SELECT numrange(1,4) &< nummultirange(numrange(3,4));
+SELECT numrange(1,6) &< nummultirange(numrange(3,4));
+SELECT numrange(3.5,6) &< nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(6,7)) &< numrange(3,4);
+SELECT nummultirange(numrange(1,2)) &< numrange(3,4);
+SELECT nummultirange(numrange(1,4)) &< numrange(3,4);
+SELECT nummultirange(numrange(1,6)) &< numrange(3,4);
+SELECT nummultirange(numrange(3.5,6)) &< numrange(3,4);
+SELECT nummultirange(numrange(6,7)) &< nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,2)) &< nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,4)) &< nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,6)) &< nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(3.5,6)) &< nummultirange(numrange(3,4));
+
+-- overright
+SELECT nummultirange() &> 'empty'::numrange;
+SELECT nummultirange(numrange(1,2)) &> 'empty'::numrange;
+SELECT 'empty'::numrange &> nummultirange();
+SELECT 'empty'::numrange &> nummultirange(numrange(1,2));
+SELECT nummultirange() &> nummultirange();
+SELECT nummultirange() &> nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) &> nummultirange();
+SELECT nummultirange(numrange(3,4)) &> numrange(6,7);
+SELECT nummultirange(numrange(3,4)) &> numrange(1,2);
+SELECT nummultirange(numrange(3,4)) &> numrange(1,4);
+SELECT nummultirange(numrange(3,4)) &> numrange(1,6);
+SELECT nummultirange(numrange(3,4)) &> numrange(3.5,6);
+SELECT numrange(3,4) &> nummultirange(numrange(6,7));
+SELECT numrange(3,4) &> nummultirange(numrange(1,2));
+SELECT numrange(3,4) &> nummultirange(numrange(1,4));
+SELECT numrange(3,4) &> nummultirange(numrange(1,6));
+SELECT numrange(3,4) &> nummultirange(numrange(3.5,6));
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(6,7));
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(1,4));
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(1,6));
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(3.5,6));
+
+-- meets
+SELECT 'empty'::numrange -|- nummultirange();
+SELECT 'empty'::numrange -|- nummultirange(numrange(1,2));
+SELECT nummultirange() -|- 'empty'::numrange;
+SELECT nummultirange(numrange(1,2)) -|- 'empty'::numrange;
+SELECT nummultirange() -|- nummultirange();
+SELECT nummultirange(numrange(1,2)) -|- nummultirange();
+SELECT nummultirange() -|- nummultirange(numrange(1,2));
+SELECT numrange(1,2) -|- nummultirange(numrange(2,4));
+SELECT numrange(1,2) -|- nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,2)) -|- numrange(2,4);
+SELECT nummultirange(numrange(1,2)) -|- numrange(3,4);
+SELECT nummultirange(numrange(1,2)) -|- nummultirange(numrange(2,4));
+SELECT nummultirange(numrange(1,2)) -|- nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,2), numrange(5,6)) -|- nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,2), numrange(5,6)) -|- nummultirange(numrange(6,7));
+SELECT nummultirange(numrange(1,2), numrange(5,6)) -|- nummultirange(numrange(8,9));
+SELECT nummultirange(numrange(1,2)) -|- nummultirange(numrange(2,4), numrange(6,7));
+
+-- strictly left
+select 'empty'::numrange << nummultirange();
+select numrange(1,2) << nummultirange();
+select numrange(1,2) << nummultirange(numrange(3,4));
+select numrange(1,2) << nummultirange(numrange(0,4));
+select numrange(1,2) << nummultirange(numrange(0,4), numrange(7,8));
+select nummultirange() << 'empty'::numrange;
+select nummultirange() << numrange(1,2);
+select nummultirange(numrange(3,4)) << numrange(3,6);
+select nummultirange(numrange(0,2)) << numrange(3,6);
+select nummultirange(numrange(0,2), numrange(7,8)) << numrange(3,6);
+select nummultirange(numrange(-4,-2), numrange(0,2)) << numrange(3,6);
+select nummultirange() << nummultirange();
+select nummultirange() << nummultirange(numrange(1,2));
+select nummultirange(numrange(1,2)) << nummultirange();
+select nummultirange(numrange(1,2)) << nummultirange(numrange(1,2));
+select nummultirange(numrange(1,2)) << nummultirange(numrange(3,4));
+select nummultirange(numrange(1,2)) << nummultirange(numrange(3,4), numrange(7,8));
+select nummultirange(numrange(1,2), numrange(4,5)) << nummultirange(numrange(3,4), numrange(7,8));
+
+-- strictly right
+select nummultirange() >> 'empty'::numrange;
+select nummultirange() >> numrange(1,2);
+select nummultirange(numrange(3,4)) >> numrange(1,2);
+select nummultirange(numrange(0,4)) >> numrange(1,2);
+select nummultirange(numrange(0,4), numrange(7,8)) >> numrange(1,2);
+select 'empty'::numrange >> nummultirange();
+select numrange(1,2) >> nummultirange();
+select numrange(3,6) >> nummultirange(numrange(3,4));
+select numrange(3,6) >> nummultirange(numrange(0,2));
+select numrange(3,6) >> nummultirange(numrange(0,2), numrange(7,8));
+select numrange(3,6) >> nummultirange(numrange(-4,-2), numrange(0,2));
+select nummultirange() >> nummultirange();
+select nummultirange(numrange(1,2)) >> nummultirange();
+select nummultirange() >> nummultirange(numrange(1,2));
+select nummultirange(numrange(1,2)) >> nummultirange(numrange(1,2));
+select nummultirange(numrange(3,4)) >> nummultirange(numrange(1,2));
+select nummultirange(numrange(3,4), numrange(7,8)) >> nummultirange(numrange(1,2));
+select nummultirange(numrange(3,4), numrange(7,8)) >> nummultirange(numrange(1,2), numrange(4,5));
+
+-- union
+SELECT nummultirange() + nummultirange();
+SELECT nummultirange() + nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) + nummultirange();
+SELECT nummultirange(numrange(1,2)) + nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) + nummultirange(numrange(2,4));
+SELECT nummultirange(numrange(1,2)) + nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) + nummultirange(numrange(2,4));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) + nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) + nummultirange(numrange(0,9));
+
+-- merge
+SELECT range_merge(nummultirange());
+SELECT range_merge(nummultirange(numrange(1,2)));
+SELECT range_merge(nummultirange(numrange(1,2), numrange(7,8)));
+
+-- minus
+SELECT nummultirange() - nummultirange();
+SELECT nummultirange() - nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) - nummultirange();
+SELECT nummultirange(numrange(1,2), numrange(3,4)) - nummultirange();
+SELECT nummultirange(numrange(1,2)) - nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) - nummultirange(numrange(2,4));
+SELECT nummultirange(numrange(1,2)) - nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(2,3));
+SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(0,8));
+SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(0,2));
+SELECT nummultirange(numrange(1,8)) - nummultirange(numrange(0,2), numrange(3,4));
+SELECT nummultirange(numrange(1,8)) - nummultirange(numrange(2,3), numrange(5,null));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(-2,0));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(2,4));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(3,5));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(0,9));
+SELECT nummultirange(numrange(1,3), numrange(4,5)) - nummultirange(numrange(2,9));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(8,9));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(-2,0), numrange(8,9));
+
+-- intersection
+SELECT nummultirange() * nummultirange();
+SELECT nummultirange() * nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) * nummultirange();
+SELECT '{[1,3)}'::nummultirange * '{[1,5)}'::nummultirange;
+SELECT '{[1,3)}'::nummultirange * '{[0,5)}'::nummultirange;
+SELECT '{[1,3)}'::nummultirange * '{[0,2)}'::nummultirange;
+SELECT '{[1,3)}'::nummultirange * '{[2,5)}'::nummultirange;
+SELECT '{[1,4)}'::nummultirange * '{[2,3)}'::nummultirange;
+SELECT '{[1,4)}'::nummultirange * '{[0,2), [3,5)}'::nummultirange;
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[0,8), [9,12)}'::nummultirange;
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[9,12)}'::nummultirange;
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[-5,-4), [5,6), [9,12)}'::nummultirange;
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[0,2), [3,8), [9,12)}'::nummultirange;
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[0,2), [3,8), [9,12)}'::nummultirange;
+
+--
+-- range_agg function
+--
+create table reservations ( room_id integer not null, booked_during daterange );
+insert into reservations values
+-- 1: has a meets and a gap
+(1, daterange('2018-07-01', '2018-07-07')),
+(1, daterange('2018-07-07', '2018-07-14')),
+(1, daterange('2018-07-20', '2018-07-22')),
+-- 2: just a single row
+(2, daterange('2018-07-01', '2018-07-03')),
+-- 3: one null range
+(3, NULL),
+-- 4: two null ranges
+(4, NULL),
+(4, NULL),
+-- 5: a null range and a non-null range
+(5, NULL),
+(5, daterange('2018-07-01', '2018-07-03')),
+-- 6: has overlap
+(6, daterange('2018-07-01', '2018-07-07')),
+(6, daterange('2018-07-05', '2018-07-10')),
+-- 7: two ranges that meet: no gap or overlap
+(7, daterange('2018-07-01', '2018-07-07')),
+(7, daterange('2018-07-07', '2018-07-14')),
+-- 8: an empty range
+(8, 'empty'::daterange)
+;
+SELECT   room_id, range_agg(booked_during)
+FROM     reservations
+GROUP BY room_id
+ORDER BY room_id;
+
+-- range_agg on a custom range type too
+SELECT  range_agg(r)
+FROM    (VALUES
+          ('[a,c]'::textrange),
+          ('[b,b]'::textrange),
+          ('[c,f]'::textrange),
+          ('[g,h)'::textrange),
+          ('[h,j)'::textrange)
+        ) t(r);
+
+select range_intersect_agg(nmr) from nummultirange_test;
+select range_intersect_agg(nmr) from nummultirange_test where false;
+-- test with just one input:
+select range_intersect_agg(nmr) from (values ('{[1,2]}'::nummultirange)) t(nmr);
+select range_intersect_agg(nmr) from nummultirange_test where nmr @> 4.0;
+
+create table nummultirange_test2(nmr nummultirange);
+create index nummultirange_test2_hash_idx on nummultirange_test2 using hash (nmr);
+
+INSERT INTO nummultirange_test2 VALUES('{[, 5)}');
+INSERT INTO nummultirange_test2 VALUES(nummultirange(numrange(1.1, 2.2)));
+INSERT INTO nummultirange_test2 VALUES(nummultirange(numrange(1.1, 2.2)));
+INSERT INTO nummultirange_test2 VALUES(nummultirange(numrange(1.1, 2.2,'()')));
+INSERT INTO nummultirange_test2 VALUES('{}');
+
+select * from nummultirange_test2 where nmr = '{}';
+select * from nummultirange_test2 where nmr = nummultirange(numrange(1.1, 2.2));
+select * from nummultirange_test2 where nmr = nummultirange(numrange(1.1, 2.3));
+
+set enable_nestloop=t;
+set enable_hashjoin=f;
+set enable_mergejoin=f;
+select * from nummultirange_test natural join nummultirange_test2 order by nmr;
+set enable_nestloop=f;
+set enable_hashjoin=t;
+set enable_mergejoin=f;
+select * from nummultirange_test natural join nummultirange_test2 order by nmr;
+set enable_nestloop=f;
+set enable_hashjoin=f;
+set enable_mergejoin=t;
+select * from nummultirange_test natural join nummultirange_test2 order by nmr;
+
+set enable_nestloop to default;
+set enable_hashjoin to default;
+set enable_mergejoin to default;
+
+DROP TABLE nummultirange_test2;
+
+--
+-- Test user-defined multirange of floats
+--
+
+select '{[123.001, 5.e9)}'::float8multirange @> 888.882::float8;
+create table float8multirange_test(f8mr float8multirange, i int);
+insert into float8multirange_test values(float8multirange(float8range(-100.00007, '1.111113e9')), 42);
+select * from float8multirange_test;
+drop table float8multirange_test;
+
+--
+-- Test multirange types over domains
+--
+
+create domain mydomain as int4;
+create type mydomainrange as range(subtype=mydomain);
+select '{[4,50)}'::mydomainmultirange @> 7::mydomain;
+drop domain mydomain cascade;
+
+--
+-- Test domains over multirange types
+--
+
+create domain restrictedmultirange as int4multirange check (upper(value) < 10);
+select '{[4,5)}'::restrictedmultirange @> 7;
+select '{[4,50)}'::restrictedmultirange @> 7; -- should fail
+drop domain restrictedmultirange;
+
+--
+-- Test multiple multirange types over the same subtype
+--
+
+create type textrange1 as range(subtype=text, collation="C");
+create type textrange2 as range(subtype=text, collation="C");
+
+select textmultirange1(textrange2('a','Z'));  -- should fail
+select textmultirange1(textrange1('a','Z')) @> 'b'::text;
+select textmultirange2(textrange2('a','z')) @> 'b'::text;
+
+drop type textrange1;
+drop type textrange2;
+
+--
+-- Test polymorphic type system
+--
+
+create function anyarray_anymultirange_func(a anyarray, r anymultirange)
+  returns anyelement as 'select $1[1] + lower($2);' language sql;
+
+select anyarray_anymultirange_func(ARRAY[1,2], int4multirange(int4range(10,20)));
+
+-- should fail
+select anyarray_anymultirange_func(ARRAY[1,2], nummultirange(numrange(10,20)));
+
+drop function anyarray_anymultirange_func(anyarray, anymultirange);
+
+-- should fail
+create function bogus_func(anyelement)
+  returns anymultirange as 'select int4multirange(int4range(1,10))' language sql;
+
+-- should fail
+create function bogus_func(int)
+  returns anymultirange as 'select int4multirange(int4range(1,10))' language sql;
+
+create function range_add_bounds(anymultirange)
+  returns anyelement as 'select lower($1) + upper($1)' language sql;
+
+select range_add_bounds(int4multirange(int4range(1, 17)));
+select range_add_bounds(nummultirange(numrange(1.0001, 123.123)));
+
+create function multirangetypes_sql(q anymultirange, b anyarray, out c anyelement)
+  as $$ select upper($1) + $2[1] $$
+  language sql;
+
+select multirangetypes_sql(int4multirange(int4range(1,10)), ARRAY[2,20]);
+select multirangetypes_sql(nummultirange(numrange(1,10)), ARRAY[2,20]);  -- match failure
+
+create function anycompatiblearray_anycompatiblemultirange_func(a anycompatiblearray, mr anycompatiblemultirange)
+  returns anycompatible as 'select $1[1] + lower($2);' language sql;
+
+select anycompatiblearray_anycompatiblemultirange_func(ARRAY[1,2], multirange(int4range(10,20)));
+
+select anycompatiblearray_anycompatiblemultirange_func(ARRAY[1,2], multirange(numrange(10,20)));
+
+-- should fail
+select anycompatiblearray_anycompatiblemultirange_func(ARRAY[1.1,2], multirange(int4range(10,20)));
+
+drop function anycompatiblearray_anycompatiblemultirange_func(anycompatiblearray, anycompatiblemultirange);
+
+create function anycompatiblerange_anycompatiblemultirange_func(r anycompatiblerange, mr anycompatiblemultirange)
+  returns anycompatible as 'select lower($1) + lower($2);' language sql;
+
+select anycompatiblerange_anycompatiblemultirange_func(int4range(1,2), multirange(int4range(10,20)));
+
+-- should fail
+select anycompatiblerange_anycompatiblemultirange_func(numrange(1,2), multirange(int4range(10,20)));
+
+drop function anycompatiblerange_anycompatiblemultirange_func(anycompatiblerange, anycompatiblemultirange);
+
+-- should fail
+create function bogus_func(anycompatible)
+  returns anycompatiblerange as 'select int4range(1,10)' language sql;
+
+--
+-- Arrays of multiranges
+--
+
+select ARRAY[nummultirange(numrange(1.1, 1.2)), nummultirange(numrange(12.3, 155.5))];
+
+create table i8mr_array (f1 int, f2 int8multirange[]);
+insert into i8mr_array values (42, array[int8multirange(int8range(1,10)), int8multirange(int8range(2,20))]);
+select * from i8mr_array;
+drop table i8mr_array;
+
+--
+-- Multiranges of arrays
+--
+
+select arraymultirange(arrayrange(ARRAY[1,2], ARRAY[2,1]));
+select arraymultirange(arrayrange(ARRAY[2,1], ARRAY[1,2]));  -- fail
+
+select array[1,1] <@ arraymultirange(arrayrange(array[1,2], array[2,1]));
+select array[1,3] <@ arraymultirange(arrayrange(array[1,2], array[2,1]));
+
+--
+-- Ranges of composites
+--
+
+create type two_ints as (a int, b int);
+create type two_ints_range as range (subtype = two_ints);
+
+-- with force_parallel_mode on, this exercises tqueue.c's range remapping
+select *, row_to_json(upper(t)) as u from
+  (values (two_ints_multirange(two_ints_range(row(1,2), row(3,4)))),
+          (two_ints_multirange(two_ints_range(row(5,6), row(7,8))))) v(t);
+
+drop type two_ints cascade;
+
+--
+-- Check behavior when subtype lacks a hash function
+--
+
+set enable_sort = off;  -- try to make it pick a hash setop implementation
+
+select '{(2,5)}'::cashmultirange except select '{(5,6)}'::cashmultirange;
+
+reset enable_sort;
+
+--
+-- OUT/INOUT/TABLE functions
+--
+
+-- infer anymultirange from anymultirange
+create function mr_outparam_succeed(i anymultirange, out r anymultirange, out t text)
+  as $$ select $1, 'foo'::text $$ language sql;
+
+select * from mr_outparam_succeed(int4multirange(int4range(1,2)));
+
+-- infer anyarray from anymultirange
+create function mr_outparam_succeed2(i anymultirange, out r anyarray, out t text)
+  as $$ select ARRAY[upper($1)], 'foo'::text $$ language sql;
+
+select * from mr_outparam_succeed2(int4multirange(int4range(1,2)));
+
+-- infer anyrange from anymultirange
+create function mr_outparam_succeed3(i anymultirange, out r anyrange, out t text)
+  as $$ select range_merge($1), 'foo'::text $$ language sql;
+select * from mr_outparam_succeed3(int4multirange(int4range(1,2)));
+
+-- infer anymultirange from anyrange
+create function mr_outparam_succeed4(i anyrange, out r anymultirange, out t text)
+  as $$ select multirange($1), 'foo'::text $$ language sql;
+
+select * from mr_outparam_succeed4(int4range(1,2));
+
+-- infer anyelement from anymultirange
+create function mr_inoutparam_succeed(out i anyelement, inout r anymultirange)
+  as $$ select upper($1), $1 $$ language sql;
+
+select * from mr_inoutparam_succeed(int4multirange(int4range(1,2)));
+
+-- infer anyelement+anymultirange from anyelement+anymultirange
+create function mr_table_succeed(i anyelement, r anymultirange) returns table(i anyelement, r anymultirange)
+  as $$ select $1, $2 $$ language sql;
+
+select * from mr_table_succeed(123, int4multirange(int4range(1,11)));
+
+-- use anymultirange in plpgsql
+create function mr_polymorphic(i anyrange) returns anymultirange
+  as $$ begin return multirange($1); end; $$ language plpgsql;
+select mr_polymorphic(int4range(1, 4));
+
+-- should fail
+create function mr_outparam_fail(i anyelement, out r anymultirange, out t text)
+  as $$ select '[1,10]', 'foo' $$ language sql;
+
+--should fail
+create function mr_inoutparam_fail(inout i anyelement, out r anymultirange)
+  as $$ select $1, '[1,10]' $$ language sql;
+
+--should fail
+create function mr_table_fail(i anyelement) returns table(i anyelement, r anymultirange)
+  as $$ select $1, '[1,10]' $$ language sql;
diff --git a/src/test/regress/sql/opr_sanity.sql b/src/test/regress/sql/opr_sanity.sql
index 7a9180b081..25958c9477 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::regtype, p2.prorettype::regtype
 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;
 
@@ -279,16 +290,19 @@ WHERE p1.prorettype IN
      '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;
 
--- anyrange is tighter than the rest, can only resolve from anyrange input
+-- anyrange and anymultirange are tighter than the rest, can only resolve
+-- from each other
 
 SELECT p1.oid, p1.proname
 FROM pg_proc as p1
-WHERE p1.prorettype = 'anyrange'::regtype
+WHERE p1.prorettype IN ('anyrange'::regtype, 'anymultirange'::regtype)
   AND NOT
-     'anyrange'::regtype = ANY (p1.proargtypes)
+    ('anyrange'::regtype = ANY (p1.proargtypes) OR
+      'anymultirange'::regtype = ANY (p1.proargtypes))
 ORDER BY 2;
 
 -- similarly for the anycompatible family
diff --git a/src/test/regress/sql/polymorphism.sql b/src/test/regress/sql/polymorphism.sql
index e5222f1f81..ac52c387bb 100644
--- a/src/test/regress/sql/polymorphism.sql
+++ b/src/test/regress/sql/polymorphism.sql
@@ -71,6 +71,16 @@ select polyf(int4range(42, 49), 11, 4.5) as fail;  -- range type doesn't fit
 
 drop function polyf(x anycompatiblerange, y anycompatible, z anycompatible);
 
+create function polyf(x anycompatiblemultirange, y anycompatible, z anycompatible) returns anycompatiblearray as $$
+  select array[lower(x), upper(x), y, z]
+$$ language sql;
+
+select polyf(multirange(int4range(42, 49)), 11, 2::smallint) as int, polyf(multirange(float8range(4.5, 7.8)), 7.8, 11::real) as num;
+
+select polyf(multirange(int4range(42, 49)), 11, 4.5) as fail;  -- range type doesn't fit
+
+drop function polyf(x anycompatiblemultirange, y anycompatible, z anycompatible);
+
 -- fail, can't infer type:
 create function polyf(x anycompatible) returns anycompatiblerange as $$
   select array[x + 1, x + 2]
@@ -84,6 +94,19 @@ select polyf(int4range(42, 49), array[11]) as int, polyf(float8range(4.5, 7.8),
 
 drop function polyf(x anycompatiblerange, y anycompatiblearray);
 
+-- fail, can't infer type:
+create function polyf(x anycompatible) returns anycompatiblemultirange as $$
+  select array[x + 1, x + 2]
+$$ language sql;
+
+create function polyf(x anycompatiblemultirange, y anycompatiblearray) returns anycompatiblemultirange as $$
+  select x
+$$ language sql;
+
+select polyf(multirange(int4range(42, 49)), array[11]) as int, polyf(multirange(float8range(4.5, 7.8)), array[7]) as num;
+
+drop function polyf(x anycompatiblemultirange, y anycompatiblearray);
+
 create function polyf(a anyelement, b anyarray,
                       c anycompatible, d anycompatible,
                       OUT x anyarray, OUT y anycompatiblearray)
@@ -994,6 +1017,35 @@ returns anycompatiblerange as $$
   select $1
 $$ language sql;
 
+create function anyctest(anycompatible, anycompatiblemultirange)
+returns anycompatiblemultirange as $$
+  select $2
+$$ language sql;
+
+select x, pg_typeof(x) from anyctest(11, multirange(int4range(4,7))) x;
+select x, pg_typeof(x) from anyctest(11, multirange(numrange(4,7))) x;
+select x, pg_typeof(x) from anyctest(11, 12) x;  -- fail
+select x, pg_typeof(x) from anyctest(11.2, multirange(int4range(4,7))) x;  -- fail
+select x, pg_typeof(x) from anyctest(11.2, '{[4,7)}') x;  -- fail
+
+drop function anyctest(anycompatible, anycompatiblemultirange);
+
+create function anyctest(anycompatiblemultirange, anycompatiblemultirange)
+returns anycompatible as $$
+  select lower($1) + upper($2)
+$$ language sql;
+
+select x, pg_typeof(x) from anyctest(multirange(int4range(11,12)), multirange(int4range(4,7))) x;
+select x, pg_typeof(x) from anyctest(multirange(int4range(11,12)), multirange(numrange(4,7))) x; -- fail
+
+drop function anyctest(anycompatiblemultirange, anycompatiblemultirange);
+
+-- fail, can't infer result type:
+create function anyctest(anycompatible)
+returns anycompatiblemultirange as $$
+  select $1
+$$ language sql;
+
 create function anyctest(anycompatiblenonarray, anycompatiblenonarray)
 returns anycompatiblearray as $$
   select array[$1, $2]
diff --git a/src/test/regress/sql/rangetypes.sql b/src/test/regress/sql/rangetypes.sql
index 4048b1d185..b69efede3a 100644
--- a/src/test/regress/sql/rangetypes.sql
+++ b/src/test/regress/sql/rangetypes.sql
@@ -118,6 +118,12 @@ select numrange(1.0, 2.0) * numrange(2.0, 3.0);
 select numrange(1.0, 2.0) * numrange(1.5, 3.0);
 select numrange(1.0, 2.0) * numrange(2.5, 3.0);
 
+select range_intersect_agg(nr) from numrange_test;
+select range_intersect_agg(nr) from numrange_test where false;
+select range_intersect_agg(nr) from numrange_test where nr @> 4.0;
+
+analyze numrange_test;
+
 create table numrange_test2(nr numrange);
 create index numrange_test2_hash_idx on numrange_test2 using hash (nr);
 
@@ -528,6 +534,7 @@ reset enable_sort;
 -- OUT/INOUT/TABLE functions
 --
 
+-- infer anyrange from anyrange
 create function outparam_succeed(i anyrange, out r anyrange, out t text)
   as $$ select $1, 'foo'::text $$ language sql;
 
@@ -539,6 +546,13 @@ create function outparam2_succeed(r anyrange, out lu anyarray, out ul anyarray)
 
 select * from outparam2_succeed(int4range(1,11));
 
+-- infer anyarray from anyrange
+create function outparam_succeed2(i anyrange, out r anyarray, out t text)
+  as $$ select ARRAY[upper($1)], 'foo'::text $$ language sql;
+
+select * from outparam_succeed2(int4range(int4range(1,2)));
+
+-- infer anyelement from anyrange
 create function inoutparam_succeed(out i anyelement, inout r anyrange)
   as $$ select upper($1), $1 $$ language sql;
 
diff --git a/src/test/regress/sql/type_sanity.sql b/src/test/regress/sql/type_sanity.sql
index 4b492ce062..07879d9a57 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'));
@@ -153,7 +153,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')
@@ -182,7 +182,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')
@@ -234,7 +234,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')
@@ -263,7 +263,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')
@@ -467,3 +467,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.rngmultitypid
+FROM pg_range p1
+WHERE p1.rngmultitypid IS NULL OR p1.rngmultitypid = 0;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 7eaaad1e14..d79fd2b3d0 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1390,6 +1390,9 @@ MultiXactMember
 MultiXactOffset
 MultiXactStateData
 MultiXactStatus
+MultirangeIOData
+MultirangeParseState
+MultirangeType
 NDBOX
 NODE
 NUMCacheEntry
#127Paul A Jungwirth
pj@illuminatedcomputing.com
In reply to: Paul A Jungwirth (#126)
Re: range_agg

On Sun, Jul 5, 2020 at 12:11 PM Paul A Jungwirth
<pj@illuminatedcomputing.com> wrote:

Just knowing that arrays are
something we do this for is enough to hunt for clues, but if anyone
can point me more directly to code that will help me do it for
multiranges, I'd be appreciative.

It looks like expandeddatum.h is where I should be looking. . . .

Paul

#128Paul A Jungwirth
pj@illuminatedcomputing.com
In reply to: Paul A Jungwirth (#126)
1 attachment(s)
Re: range_agg

On Sun, Jul 5, 2020 at 12:11 PM Paul A Jungwirth
<pj@illuminatedcomputing.com> wrote:

I haven't made much progress storing on-disk multiranges without the
range type oids.

Here is a patch using the TOAST EXTENDED infrastructure to store
multiranges on disk with a new "short" range type struct that omits
the range type oids, but then loads ordinary range structs for its
in-memory operations. One nice thing that fell out from that work is
that I can build an ordinary RangeType ** list at the same time.
(Since RangeTypes are varlena and their bounds may be varlena, you
already needed to get a list like that for nearly any operation.)

This is rebased on the current master, including some changes to doc
tables and pg_upgrade handling of type oids.

Yours,
Paul

Attachments:

v21-multirange.patchapplication/octet-stream; name=v21-multirange.patchDownload
diff --git a/doc/src/sgml/datatype.sgml b/doc/src/sgml/datatype.sgml
index fdc8715a0d..02bd537388 100644
--- a/doc/src/sgml/datatype.sgml
+++ b/doc/src/sgml/datatype.sgml
@@ -4886,6 +4886,10 @@ SELECT * FROM pg_attribute
     <primary>anyrange</primary>
    </indexterm>
 
+   <indexterm zone="datatype-pseudo">
+    <primary>anymultirange</primary>
+   </indexterm>
+
    <indexterm zone="datatype-pseudo">
     <primary>anycompatible</primary>
    </indexterm>
@@ -4902,6 +4906,10 @@ SELECT * FROM pg_attribute
     <primary>anycompatiblerange</primary>
    </indexterm>
 
+   <indexterm zone="datatype-pseudo">
+    <primary>anycompatiblemultirange</primary>
+   </indexterm>
+
    <indexterm zone="datatype-pseudo">
     <primary>void</primary>
    </indexterm>
@@ -5013,6 +5021,13 @@ SELECT * FROM pg_attribute
         <xref linkend="rangetypes"/>).</entry>
        </row>
 
+       <row>
+        <entry><type>anymultirange</type></entry>
+        <entry>Indicates that a function accepts any multirange data type
+        (see <xref linkend="extend-types-polymorphic"/> and
+        <xref linkend="rangetypes"/>).</entry>
+       </row>
+
        <row>
         <entry><type>anycompatible</type></entry>
         <entry>Indicates that a function accepts any data type,
@@ -5042,6 +5057,14 @@ SELECT * FROM pg_attribute
         <xref linkend="rangetypes"/>).</entry>
        </row>
 
+       <row>
+        <entry><type>anycompatiblemultirange</type></entry>
+        <entry>Indicates that a function accepts any multirange data type,
+        with automatic promotion of multiple arguments to a common data type
+        (see <xref linkend="extend-types-polymorphic"/> and
+        <xref linkend="rangetypes"/>).</entry>
+       </row>
+
        <row>
         <entry><type>cstring</type></entry>
         <entry>Indicates that a function accepts or returns a null-terminated C string.</entry>
diff --git a/doc/src/sgml/extend.sgml b/doc/src/sgml/extend.sgml
index 641c9ce3c9..9769948a73 100644
--- a/doc/src/sgml/extend.sgml
+++ b/doc/src/sgml/extend.sgml
@@ -288,6 +288,14 @@
         </entry>
        </row>
 
+       <row>
+        <entry><type>anymultirange</type></entry>
+        <entry>Simple</entry>
+        <entry>Indicates that a function accepts any multirange data type
+        (see <xref linkend="rangetypes"/>)
+        </entry>
+       </row>
+
        <row>
         <entry><type>anycompatible</type></entry>
         <entry>Common</entry>
@@ -319,6 +327,14 @@
         with automatic promotion of multiple arguments to a common data type
         </entry>
        </row>
+
+       <row>
+        <entry><type>anycompatiblemultirange</type></entry>
+        <entry>Common</entry>
+        <entry>Indicates that a function accepts any multirange data type,
+        with automatic promotion of multiple arguments to a common data type
+        </entry>
+       </row>
       </tbody>
      </tgroup>
     </table>
@@ -346,17 +362,15 @@
      position declared as <type>anyarray</type> can have any array data type,
      but similarly they must all be the same type.  And similarly,
      positions declared as <type>anyrange</type> must all be the same range
-     type.  Furthermore, if there are
+     type.  Likewise for <type>anymultirange</type>.
+    </para>
+
+    <para>
+     Furthermore, if there are
      positions declared <type>anyarray</type> and others declared
      <type>anyelement</type>, the actual array type in the
      <type>anyarray</type> positions must be an array whose elements are
      the same type appearing in the <type>anyelement</type> positions.
-     Similarly, if there are positions declared <type>anyrange</type>
-     and others declared <type>anyelement</type> or <type>anyarray</type>,
-     the actual range type in the <type>anyrange</type> positions must be a
-     range whose subtype is the same type appearing in
-     the <type>anyelement</type> positions and the same as the element type
-     of the <type>anyarray</type> positions.
      <type>anynonarray</type> is treated exactly the same as <type>anyelement</type>,
      but adds the additional constraint that the actual type must not be
      an array type.
@@ -365,6 +379,19 @@
      be an enum type.
     </para>
 
+    <para>
+     Similarly, if there are positions declared <type>anyrange</type>
+     and others declared <type>anyelement</type> or <type>anyarray</type>,
+     the actual range type in the <type>anyrange</type> positions must be a
+     range whose subtype is the same type appearing in
+     the <type>anyelement</type> positions and the same as the element type
+     of the <type>anyarray</type> positions.
+     If there are positions declared <type>anymultirange</type>,
+     their actual multirange type must contain ranges matching parameters declared
+     <type>anyrange</type> and base elements matching parameters declared
+     <type>anyelement</type> and <type>anyarray</type>.
+    </para>
+
     <para>
      Thus, when more than one argument position is declared with a polymorphic
      type, the net effect is that only certain combinations of actual argument
@@ -420,7 +447,8 @@
      Selection of the common type considers the actual types
      of <type>anycompatible</type> and <type>anycompatiblenonarray</type>
      inputs, the array element types of <type>anycompatiblearray</type>
-     inputs, and the range subtypes of <type>anycompatiblerange</type>
+     inputs, the range subtypes of <type>anycompatiblerange</type> inputs,
+     and the multirange subtypes of <type>anycompatiablemultirange</type>
      inputs.  If <type>anycompatiblenonarray</type> is present then the
      common type is required to be a non-array type.  Once a common type is
      identified, arguments in <type>anycompatible</type>
@@ -431,12 +459,15 @@
 
     <para>
      Since there is no way to select a range type knowing only its subtype,
-     use of <type>anycompatiblerange</type> requires that all arguments
-     declared with that type have the same actual range type, and that that
-     type's subtype agree with the selected common type, so that no casting
-     of the range values is required.  As with <type>anyrange</type>, use
-     of <type>anycompatiblerange</type> as a function result type requires
-     that there be an <type>anycompatiblerange</type> argument.
+     use of <type>anycompatiblerange</type> and/or
+     <type>anycompatiblemultirange</type> requires that all arguments declared
+     with that type have the same actual range and/or multirange type, and that
+     that type's subtype agree with the selected common type, so that no casting
+     of the range values is required.  As with <type>anyrange</type> and
+     <type>anymultirange</type>, use of <type>anycompatiblerange</type> and
+     <type>anymultirange</type> as a function result type requires that there be
+     an <type>anycompatiblerange</type> or <type>anycompatiblemultirange</type>
+     argument.
     </para>
 
     <para>
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 9a4ac5a1ea..f18cb46419 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -17920,12 +17920,15 @@ SELECT NULLIF(value, '(none)') ...
   <para>
    <xref linkend="range-operators-table"/> shows the specialized operators
    available for range types.
+   <xref linkend="multirange-operators-table"/> shows the specialized operators
+   available for multirange types.
    In addition to those, the usual comparison operators shown in
    <xref linkend="functions-comparison-op-table"/> are available for range
-   types.  The comparison operators order first by the range lower bounds, and
-   only if those are equal do they compare the upper bounds.  This does not
-   usually result in a useful overall ordering, but the operators are provided
-   to allow unique indexes to be constructed on ranges.
+   and multirange types.  The comparison operators order first by the range lower
+   bounds, and only if those are equal do they compare the upper bounds.  The
+   multirange operators compare each range until one is unequal. This
+   does not usually result in a useful overall ordering, but the operators are
+   provided to allow unique indexes to be constructed on ranges.
   </para>
 
    <table id="range-operators-table">
@@ -18135,15 +18138,449 @@ SELECT NULLIF(value, '(none)') ...
     </tgroup>
    </table>
 
+   <table id="multirange-operators-table">
+    <title>Multirange Operators</title>
+    <tgroup cols="1">
+     <thead>
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        Operator
+       </para>
+       <para>
+        Description
+       </para>
+       <para>
+        Example(s)
+       </para></entry>
+      </row>
+     </thead>
+
+     <tbody>
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>@&gt;</literal> <type>anymultirange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Does the first multirange contain the second?
+       </para>
+       <para>
+        <literal>'{[2,4)}'::int4multirange @&gt; '{[2,3)}'::int4multirange</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>@&gt;</literal> <type>anyrange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Does the multirange contain the range?
+       </para>
+       <para>
+        <literal>'{[2,4)}'::int4multirange @&gt; int4range(2,3)</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>@&gt;</literal> <type>anyelement</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Does the multirange contain the element?
+       </para>
+       <para>
+        <literal>'{[2011-01-01,2011-03-01)}'::tsmultirange @&gt; '2011-01-10'::timestamp</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>&lt;@</literal> <type>anymultirange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the first multirange contained by the second?
+       </para>
+       <para>
+        <literal>'{[2,4)}'::int4multirange &lt;@ '{[1,7)}'::int4multirange</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>&lt;@</literal> <type>anyrange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the multirange contained by the range?
+       </para>
+       <para>
+        <literal>'{[2,4)}'::int4multirange &lt;@ int4range(1,7)</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anyrange</type> <literal>&lt;@</literal> <type>anymultirange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the range contained by the multirange?
+       </para>
+       <para>
+        <literal>int4range(2,4) &lt;@ '{[1,7)}'::int4multirange</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anyelement</type> <literal>&lt;@</literal> <type>anymultirange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the element contained by the multirange?
+       </para>
+       <para>
+        <literal>42 &lt;@ '{[1,7)}'::int4multirange</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>&amp;&amp;</literal> <type>anymultirange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Do the multiranges overlap, that is, have any elements in common?
+       </para>
+       <para>
+        <literal>'{[3,7)}'::int8multirange &amp;&amp; '{[4,12)}'::int8multirange</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>&amp;&amp;</literal> <type>anyrange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Does the multirange overlap the range?
+       </para>
+       <para>
+        <literal>'{[3,7)}'::int8multirange &amp;&amp; int8range(4,12)</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anyrange</type> <literal>&amp;&amp;</literal> <type>anymultirange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Does the range overlap the multirange?
+       </para>
+       <para>
+        <literal>int8range(3,7) &amp;&amp; '{[4,12)}'::int8multirange</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>&lt;&lt;</literal> <type>anymultirange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the first multirange strictly left of the second?
+       </para>
+       <para>
+        <literal>'{[1,10)}'::int8multirange &lt;&lt; '{[100,110)}'::int8multirange</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>&lt;&lt;</literal> <type>anyrange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the multirange strictly left of the range?
+       </para>
+       <para>
+        <literal>'{[1,10)}'::int8multirange &lt;&lt; int8range(100,110)</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anyrange</type> <literal>&lt;&lt;</literal> <type>anymultirange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the range strictly left of the multirange?
+       </para>
+       <para>
+        <literal>int8range(1,10) &lt;&lt; '{[100,110)}'::int8multirange</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>&gt;&gt;</literal> <type>anymultirange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the first multirange strictly right of the second?
+       </para>
+       <para>
+        <literal>'{[50,60)}'::int8multirange &gt;&gt; '{[20,30)}'::int8multirange</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>&gt;&gt;</literal> <type>anyrange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the multirange strictly right of the range?
+       </para>
+       <para>
+        <literal>'{[50,60)}'::int8multirange &gt;&gt; int8range(20,30)</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anyrange</type> <literal>&gt;&gt;</literal> <type>anymultirange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the range strictly right of the multirange?
+       </para>
+       <para>
+        <literal>int8range(50,60) &gt;&gt; '{[20,30)}'::int8multirange</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>&amp;&lt;</literal> <type>anymultirange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Does the first multirange not extend to the right of the second?
+       </para>
+       <para>
+        <literal>'{[1,20)}'::int8multirange &amp;&lt; '{[18,20)}'::int8multirange</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>&amp;&lt;</literal> <type>anyrange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Does the multirange not extend to the right of the range?
+       </para>
+       <para>
+        <literal>'{[1,20)}'::int8multirange &amp;&lt; int8range(18,20)</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anyrange</type> <literal>&amp;&lt;</literal> <type>anymultirange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Does the range not extend to the right of the multirange?
+       </para>
+       <para>
+        <literal>int8range(1,20) &amp;&lt; '{[18,20)}'::int8multirange</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>&amp;&gt;</literal> <type>anymultirange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Does the first multirange not extend to the left of the second?
+       </para>
+       <para>
+        <literal>'{[7,20)}'::int8multirange &amp;&gt; '{[5,10)}'::int8multirange</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>&amp;&gt;</literal> <type>anyrange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Does the multirange not extend to the left of the range?
+       </para>
+       <para>
+        <literal>'{[7,20)}'::int8multirange &amp;&gt; int8range(5,10)</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anyrange</type> <literal>&amp;&gt;</literal> <type>anymultirange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Does the range not extend to the left of the multirange?
+       </para>
+       <para>
+        <literal>int8range(7,20) &amp;&gt; '{[5,10)}'::int8multirange</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>-|-</literal> <type>anymultirange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Are the multiranges adjacent?
+       </para>
+       <para>
+        <literal>'{[1.1,2.2)}'::nummultirange -|- '{[2.2,3.3)}'::nummultirange</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>-|-</literal> <type>anyrange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the multirange adjacent to the range?
+       </para>
+       <para>
+        <literal>'{[1.1,2.2)}'::nummultirange -|- numrange(2.2,3.3)</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anyrange</type> <literal>-|-</literal> <type>anymultirange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the range adjacent to the multirange?
+       </para>
+       <para>
+        <literal>numrange(1.1,2.2) -|- '{[2.2,3.3)}'::nummultirange</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>+</literal> <type>anymultirange</type>
+        <returnvalue>anymultirange</returnvalue>
+       </para>
+       <para>
+        Computes the union of the multiranges.  The multiranges need not overlap
+        or be adjacent.
+       </para>
+       <para>
+        <literal>'{[5,10)}'::nummultirange + '{[15,20)}'::nummultirange</literal>
+        <returnvalue>{[5,10), [15,20)}</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>*</literal> <type>anymultirange</type>
+        <returnvalue>anymultirange</returnvalue>
+       </para>
+       <para>
+        Computes the intersection of the multiranges.
+       </para>
+       <para>
+        <literal>'{[5,15)}'::int8multirange * '{[10,20)}'::int8multirange</literal>
+        <returnvalue>{[10,15)}</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>-</literal> <type>anymultirange</type>
+        <returnvalue>anymultirange</returnvalue>
+       </para>
+       <para>
+        Computes the difference of the multiranges.
+       </para>
+       <para>
+        <literal>'{[5,20)}'::int8multirange - '{[10,15)}'::int8multirange</literal>
+        <returnvalue>{[5,10), [15,20)}</returnvalue>
+       </para></entry>
+      </row>
+     </tbody>
+    </tgroup>
+   </table>
+
   <para>
    The left-of/right-of/adjacent operators always return false when an empty
-   range is involved; that is, an empty range is not considered to be either
-   before or after any other range.
+   range or multirange is involved; that is, an empty range is not considered to
+   be either before or after any other range.
+  </para>
+
+  <para>
+   Elsewhere empty ranges and multiranges are treated as the additive identity:
+   anything unioned with an empty value is itself. Anything minus an empty
+   value is itself. An empty multirange has exactly the same points as an empty
+   range. Every range contains the empty range. Every multirange contains as many
+   empty ranges as you like.
+  </para>
+
+  <para>
+   The range union and difference operators will fail if the resulting range would
+   need to contain two disjoint sub-ranges, as such a range cannot be
+   represented. There are separate operators for union and difference that take
+   multirange parameters and return a multirange, and they do not fail even if
+   their arguments are disjoint. So if you need a union or difference operation
+   for ranges that may be disjoint, you can avoid errors by first casting your
+   ranges to multiranges.
   </para>
 
   <para>
    <xref linkend="range-functions-table"/> shows the functions
    available for use with range types.
+   <xref linkend="multirange-functions-table"/> shows the functions
+   available for use with multirange types.
   </para>
 
    <table id="range-functions-table">
@@ -18305,10 +18742,185 @@ SELECT NULLIF(value, '(none)') ...
     </tgroup>
    </table>
 
+   <table id="multirange-functions-table">
+    <title>Multirange Functions</title>
+    <tgroup cols="1">
+     <thead>
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        Function
+       </para>
+       <para>
+        Description
+       </para>
+       <para>
+        Example(s)
+       </para></entry>
+      </row>
+     </thead>
+     <tbody>
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>lower</primary>
+        </indexterm>
+        <function>lower</function> ( <type>anymultirange</type> )
+        <returnvalue>anyelement</returnvalue>
+       </para>
+       <para>
+        Extracts the lower bound of the multirange (<literal>NULL</literal> if the
+        multirange is empty or the lower bound is infinite).
+       </para>
+       <para>
+        <literal>lower('{[1.1,2.2)}'::nummultirange)</literal>
+        <returnvalue>1.1</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>upper</primary>
+        </indexterm>
+        <function>upper</function> ( <type>anymultirange</type> )
+        <returnvalue>anyelement</returnvalue>
+       </para>
+       <para>
+        Extracts the upper bound of the multirange (<literal>NULL</literal> if the
+        multirange is empty or the upper bound is infinite).
+       </para>
+       <para>
+        <literal>upper('{[1.1,2.2)}'::nummultirange)</literal>
+        <returnvalue>2.2</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>isempty</primary>
+        </indexterm>
+        <function>isempty</function> ( <type>anymultirange</type> )
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the multirange empty?
+       </para>
+       <para>
+        <literal>isempty('{[1.1,2.2)}'::nummultirange)</literal>
+        <returnvalue>f</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>lower_inc</primary>
+        </indexterm>
+        <function>lower_inc</function> ( <type>anymultirange</type> )
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the multirange's lower bound inclusive?
+       </para>
+       <para>
+        <literal>lower_inc('{[1.1,2.2)}'::nummultirange)</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>upper_inc</primary>
+        </indexterm>
+        <function>upper_inc</function> ( <type>anymultirange</type> )
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the multirange's upper bound inclusive?
+       </para>
+       <para>
+        <literal>upper_inc('{[1.1,2.2)}'::nummultirange)</literal>
+        <returnvalue>f</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>lower_inf</primary>
+        </indexterm>
+        <function>lower_inf</function> ( <type>anymultirange</type> )
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the multirange's lower bound infinite?
+       </para>
+       <para>
+        <literal>lower_inf('{(,)}'::datemultirange)</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>upper_inf</primary>
+        </indexterm>
+        <function>upper_inf</function> ( <type>anymultirange</type> )
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the multirange's upper bound infinite?
+       </para>
+       <para>
+        <literal>upper_inf('{(,)}'::datemultirange)</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>range_merge</primary>
+        </indexterm>
+        <function>range_merge</function> ( <type>anymultirange</type> )
+        <returnvalue>anyrange</returnvalue>
+       </para>
+       <para>
+        Computes the smallest range that includes the entire multirange.
+       </para>
+       <para>
+        <literal>range_merge('{[1,2), [3,4)}'::int4multirange)</literal>
+        <returnvalue>[1,4)</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>multirange</primary>
+        </indexterm>
+        <function>multirange</function> ( <type>anyrange</type> )
+        <returnvalue>anymultirange</returnvalue>
+       </para>
+       <para>
+        Returns a multirange containing just the given range.
+       </para>
+       <para>
+        <literal>multirange('[1,2)'::int4range)</literal>
+        <returnvalue>{[1,2)}</returnvalue>
+       </para></entry>
+      </row>
+     </tbody>
+    </tgroup>
+   </table>
+
   <para>
    The <function>lower_inc</function>, <function>upper_inc</function>,
    <function>lower_inf</function>, and <function>upper_inf</function>
-   functions all return false for an empty range.
+   functions all return false for an empty range or multirange.
   </para>
   </sect1>
 
@@ -18640,6 +19252,36 @@ SELECT NULLIF(value, '(none)') ...
        <entry>Yes</entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>range_agg</primary>
+        </indexterm>
+        <function>range_agg</function> ( <parameter>value</parameter>
+         <type>anyrange</type> )
+        <returnvalue>anymultirange</returnvalue>
+       </para>
+       <para>
+        Computes the union of the non-null input values.
+       </para></entry>
+       <entry>No</entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>range_intersect_agg</primary>
+        </indexterm>
+        <function>range_intersect_agg</function> ( <parameter>value</parameter>
+         <type>anyrange</type> )
+        <returnvalue>anymultirange</returnvalue>
+       </para>
+       <para>
+        Computes the intersection of the non-null input values.
+       </para></entry>
+       <entry>No</entry>
+      </row>
+
       <row>
        <entry role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/doc/src/sgml/rangetypes.sgml b/doc/src/sgml/rangetypes.sgml
index b75fb3a392..c8784bdce3 100644
--- a/doc/src/sgml/rangetypes.sgml
+++ b/doc/src/sgml/rangetypes.sgml
@@ -27,6 +27,13 @@
   ranges from an instrument, and so forth can also be useful.
  </para>
 
+ <para>
+  Every range type has a corresponding multirange type. A multirange is
+  an ordered list of non-continguous, non-empty, non-null ranges. Most
+  range operators also work on multiranges, and they have a few functions
+  of their own.
+ </para>
+
  <sect2 id="rangetypes-builtin">
   <title>Built-in Range Types</title>
 
@@ -232,10 +239,30 @@ SELECT '[4,4]'::int4range;
 SELECT '[4,4)'::int4range;
 </programlisting>
   </para>
+
+  <para>
+   The input for a multirange is curly brackets (<literal>{</literal> and
+   <literal>}</literal>) containing zero or more valid ranges,
+   separated by commas. Whitespace is permitted around the brackets and
+   commas. This is intended to be reminiscent of array syntax, although
+   multiranges are much simpler: they have just one dimension and there is
+   no need to quote their contents. (The bounds of their ranges may be
+   quoted as above however.)
+  </para>
+
+  <para>
+  Examples:
+<programlisting>
+SELECT '{}'::int4multirange;
+SELECT '{[3,7)}'::int4multirange;
+SELECT '{[3,7), [8,9)}'::int4multirange;
+</programlisting>
+  </para>
+
  </sect2>
 
  <sect2 id="rangetypes-construct">
-  <title>Constructing Ranges</title>
+  <title>Constructing Ranges and Multiranges</title>
 
   <para>
    Each range type has a constructor function with the same name as the range
@@ -267,6 +294,19 @@ SELECT int8range(1, 14, '(]');
 
 -- Using NULL for either bound causes the range to be unbounded on that side.
 SELECT numrange(NULL, 2.2);
+</programlisting>
+  </para>
+
+  <para>
+   Each range type also has a multirange constructor with the same name as the
+   multirange type.  The constructor function takes zero or more arguments
+   which are all ranges of the appropriate type.
+   For example:
+
+<programlisting>
+SELECT nummultirange();
+SELECT nummultirange(numrange(1.0, 14.0));
+SELECT nummultirange(numrange(1.0, 14.0), numrange(20.0, 25.0));
 </programlisting>
   </para>
  </sect2>
@@ -341,6 +381,11 @@ SELECT '[1.234, 5.678]'::floatrange;
    function in this example.
   </para>
 
+  <para>
+   When you define your own range you automatically get a corresponding
+   multirange type.
+  </para>
+
   <para>
    Defining your own range type also allows you to specify a different
    subtype B-tree operator class or collation to use, so as to change the sort
diff --git a/src/backend/catalog/pg_range.c b/src/backend/catalog/pg_range.c
index b5bc36c2bd..1217a6ddd0 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_rngmultitypid - 1] = ObjectIdGetDatum(multirangeTypeOid);
 
 	tup = heap_form_tuple(RelationGetDescr(pg_range), values, nulls);
 
@@ -100,6 +102,12 @@ 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 79ffe317dd..423b440d5f 100644
--- a/src/backend/catalog/pg_type.c
+++ b/src/backend/catalog/pg_type.c
@@ -27,6 +27,7 @@
 #include "catalog/pg_proc.h"
 #include "catalog/pg_type.h"
 #include "commands/typecmds.h"
+#include "mb/pg_wchar.h"
 #include "miscadmin.h"
 #include "parser/scansup.h"
 #include "utils/acl.h"
@@ -36,6 +37,9 @@
 #include "utils/rel.h"
 #include "utils/syscache.h"
 
+static char *makeUniqueTypeName(const char *typeName, Oid typeNamespace,
+								bool tryOriginal);
+
 /* Potentially set by pg_upgrade_support functions */
 Oid			binary_upgrade_next_pg_type_oid = InvalidOid;
 
@@ -784,31 +788,10 @@ RenameTypeInternal(Oid typeOid, const char *newTypeName, Oid typeNamespace)
 char *
 makeArrayTypeName(const char *typeName, Oid typeNamespace)
 {
-	char	   *arr = (char *) palloc(NAMEDATALEN);
-	int			namelen = strlen(typeName);
-	int			i;
-
-	/*
-	 * 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;
-	}
+	char	   *arr;
 
-	if (i >= NAMEDATALEN - 1)
+	arr = makeUniqueTypeName(typeName, typeNamespace, false);
+	if (arr == NULL)
 		ereport(ERROR,
 				(errcode(ERRCODE_DUPLICATE_OBJECT),
 				 errmsg("could not form array type name for type \"%s\"",
@@ -879,3 +862,91 @@ moveArrayTypeName(Oid typeOid, const char *typeName, Oid typeNamespace)
 
 	return true;
 }
+
+
+/*
+ * makeMultirangeTypeName
+ *	  - given a range type name, make a multirange type name for it
+ *
+ * caller is responsible for pfreeing the result
+ */
+char *
+makeMultirangeTypeName(const char *rangeTypeName, Oid typeNamespace)
+{
+	char	   *buf;
+	char	   *mrname;
+	char	   *rangestr;
+
+	/*
+	 * 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...
+	 */
+	rangestr = strstr(rangeTypeName, "range");
+	if (rangestr)
+	{
+		char	*prefix = pnstrdup(rangeTypeName, rangestr - rangeTypeName);
+
+		buf = psprintf("%s%s%s", prefix, "multi", rangestr);
+	}
+	else
+		buf = psprintf("%s_multirange", pnstrdup(rangeTypeName, NAMEDATALEN - 12));
+
+	/* clip it at NAMEDATALEN-1 bytes */
+	buf[pg_mbcliplen(buf, strlen(buf), NAMEDATALEN - 1)] = '\0';
+
+	mrname = makeUniqueTypeName(buf, typeNamespace, true);
+	if (mrname == NULL)
+		ereport(ERROR,
+				(errcode(ERRCODE_DUPLICATE_OBJECT),
+				 errmsg("could not form multirange type name for type \"%s\"",
+						rangeTypeName)));
+
+	return mrname;
+}
+
+/*
+ * makeUniqueTypeName
+ *		Generate a unique name for a prospective new type
+ *
+ * Given a typeName, return a new palloc'ed name by preprending underscores
+ * until a non-conflicting name results.
+ *
+ * If tryOriginal, first try with zero underscores.
+ */
+static char *
+makeUniqueTypeName(const char *typeName, Oid typeNamespace, bool tryOriginal)
+{
+	int			i;
+	int			namelen;
+	char		dest[NAMEDATALEN];
+
+	Assert(strlen(typeName) <= NAMEDATALEN - 1);
+
+	if (tryOriginal &&
+		!SearchSysCacheExists2(TYPENAMENSP,
+							   CStringGetDatum(typeName),
+							   ObjectIdGetDatum(typeNamespace)))
+		return pstrdup(typeName);
+
+	/*
+	 * The idea is to prepend underscores as needed until we make a name that
+	 * doesn't collide with anything ...
+	 */
+	namelen = strlen(typeName);
+	for (i = 1; i < NAMEDATALEN - 1; i++)
+	{
+		dest[i - 1] = '_';
+		strlcpy(dest + i, typeName, NAMEDATALEN - i);
+		if (namelen + i >= NAMEDATALEN)
+			truncate_identifier(dest, NAMEDATALEN, false);
+
+		if (!SearchSysCacheExists2(TYPENAMENSP,
+								   CStringGetDatum(dest),
+								   ObjectIdGetDatum(typeNamespace)))
+			return pstrdup(dest);
+	}
+
+	return NULL;
+}
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index 483bb65ddc..b24fc1f566 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -105,9 +105,14 @@ typedef struct
 
 /* Potentially set by pg_upgrade_support functions */
 Oid			binary_upgrade_next_array_pg_type_oid = InvalidOid;
+Oid			binary_upgrade_next_mrng_pg_type_oid = InvalidOid;
+Oid			binary_upgrade_next_mrng_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 rangeOid,
+									   Oid rangeArrayOid, Oid *castFuncOid);
 static Oid	findTypeInputFunction(List *procname, Oid typeOid);
 static Oid	findTypeOutputFunction(List *procname, Oid typeOid);
 static Oid	findTypeReceiveFunction(List *procname, Oid typeOid);
@@ -739,7 +744,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",
@@ -1280,6 +1286,11 @@ checkEnumOwner(HeapTuple tup)
 /*
  * DefineRange
  *		Registers a new range type.
+ *
+ * Perhaps it might be worthwhile to set pg_type.typelem to the base type,
+ * and likewise on multiranges to set it to the range type. But having a
+ * non-zero typelem is treated elsewhere as a synonym for being an array,
+ * and users might have queries with that same assumption.
  */
 ObjectAddress
 DefineRange(CreateRangeStmt *stmt)
@@ -1288,7 +1299,11 @@ DefineRange(CreateRangeStmt *stmt)
 	Oid			typeNamespace;
 	Oid			typoid;
 	char	   *rangeArrayName;
+	char	   *multirangeTypeName;
+	char	   *multirangeArrayName;
 	Oid			rangeArrayOid;
+	Oid			multirangeOid;
+	Oid			multirangeArrayOid;
 	Oid			rangeSubtype = InvalidOid;
 	List	   *rangeSubOpclassName = NIL;
 	List	   *rangeCollationName = NIL;
@@ -1305,6 +1320,8 @@ DefineRange(CreateRangeStmt *stmt)
 	AclResult	aclresult;
 	ListCell   *lc;
 	ObjectAddress address;
+	ObjectAddress mltrngaddress;
+	Oid			castFuncOid;
 
 	/* Convert list of names to a name and namespace */
 	typeNamespace = QualifiedNameGetCreationNamespace(stmt->typeName,
@@ -1453,8 +1470,10 @@ DefineRange(CreateRangeStmt *stmt)
 	/* alignment must be TYPALIGN_INT or TYPALIGN_DOUBLE for ranges */
 	alignment = (subtypalign == TYPALIGN_DOUBLE) ? TYPALIGN_DOUBLE : TYPALIGN_INT;
 
-	/* Allocate OID for array type */
+	/* Allocate OID for array type, its multirange, and its multirange array */
 	rangeArrayOid = AssignTypeArrayOid();
+	multirangeOid = AssignTypeMultirangeOid();
+	multirangeArrayOid = AssignTypeMultirangeArrayOid();
 
 	/* Create the pg_type entry */
 	address =
@@ -1492,9 +1511,46 @@ DefineRange(CreateRangeStmt *stmt)
 	Assert(typoid == InvalidOid || typoid == address.objectId);
 	typoid = address.objectId;
 
+	/* Create the multirange that goes with it */
+	multirangeTypeName = makeMultirangeTypeName(typeName, typeNamespace);
+
+	mltrngaddress =
+		TypeCreate(multirangeOid,	/* force assignment of this 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_RANGE,	/* type-category (range 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) */
+	Assert(multirangeOid == mltrngaddress.objectId);
+
 	/* Create the entry in pg_range */
 	RangeCreate(typoid, rangeSubtype, rangeCollation, rangeSubOpclass,
-				rangeCanonical, rangeSubtypeDiff);
+				rangeCanonical, rangeSubtypeDiff, multirangeOid);
 
 	/*
 	 * Create the array type that goes with it.
@@ -1535,8 +1591,53 @@ 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 */
+			   multirangeOid,	/* 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,
+							   multirangeOid, typoid, rangeArrayOid,
+							   &castFuncOid);
+
+	/* Create cast from the range type to its multirange type */
+	CastCreate(typoid, multirangeOid, castFuncOid, 'e', 'f', DEPENDENCY_INTERNAL);
+
+	pfree(multirangeTypeName);
+	pfree(multirangeArrayName);
 
 	return address;
 }
@@ -1614,6 +1715,147 @@ 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.
+ *
+ * Sets castFuncOid to the oid of the new constructor that can be used
+ * to cast from a range to a multirange.
+ */
+static void
+makeMultirangeConstructors(const char *name, Oid namespace,
+						   Oid multirangeOid, Oid rangeOid, Oid rangeArrayOid,
+						   Oid *castFuncOid)
+{
+	ObjectAddress myself,
+				referenced;
+	oidvector  *argtypes;
+	Datum		allParamTypes;
+	ArrayType  *allParameterTypes;
+	Datum		paramModes;
+	ArrayType  *parameterModes;
+
+	referenced.classId = TypeRelationId;
+	referenced.objectId = multirangeOid;
+	referenced.objectSubId = 0;
+
+	/* 0-arg constructor - for empty multiranges */
+	argtypes = buildoidvector(NULL, 0);
+	myself = ProcedureCreate(name,	/* name: same as multirange type */
+							 namespace,
+							 false,	/* replace */
+							 false,	/* returns set */
+							 multirangeOid,	/* return type */
+							 BOOTSTRAP_SUPERUSERID,	/* proowner */
+							 INTERNALlanguageId,	/* language */
+							 F_FMGR_INTERNAL_VALIDATOR,
+							 "multirange_constructor0",	/* prosrc */
+							 NULL,	/* probin */
+							 PROKIND_FUNCTION,
+							 false,	/* security_definer */
+							 false,	/* leakproof */
+							 false, /* isStrict */
+							 PROVOLATILE_IMMUTABLE,	/* volatility */
+							 PROPARALLEL_SAFE,	/* parallel safety */
+							 argtypes,	/* parameterTypes */
+							 PointerGetDatum(NULL),	/* allParameterTypes */
+							 PointerGetDatum(NULL),	/* parameterModes */
+							 PointerGetDatum(NULL),	/* parameterNames */
+							 NIL,	/* parameterDefaults */
+							 PointerGetDatum(NULL),	/* trftypes */
+							 PointerGetDatum(NULL),	/* proconfig */
+							 InvalidOid,	/* prosupport */
+							 1.0,	/* procost */
+							 0.0);	/* prorows */
+	/*
+	 * Make the constructor 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);
+	pfree(argtypes);
+
+	/*
+	 * 1-arg constructor - for casts
+	 *
+	 * In theory we shouldn't need both this and the vararg (n-arg) constructor,
+	 * but having a separate 1-arg function lets us define casts against it.
+	 */
+	argtypes = buildoidvector(&rangeOid, 1);
+	myself = ProcedureCreate(name,	/* name: same as multirange type */
+							 namespace,
+							 false,	/* replace */
+							 false,	/* returns set */
+							 multirangeOid,	/* return type */
+							 BOOTSTRAP_SUPERUSERID,	/* proowner */
+							 INTERNALlanguageId,	/* language */
+							 F_FMGR_INTERNAL_VALIDATOR,
+							 "multirange_constructor1",	/* prosrc */
+							 NULL,	/* probin */
+							 PROKIND_FUNCTION,
+							 false,	/* security_definer */
+							 false,	/* leakproof */
+							 true, /* isStrict */
+							 PROVOLATILE_IMMUTABLE,	/* volatility */
+							 PROPARALLEL_SAFE,	/* parallel safety */
+							 argtypes,	/* parameterTypes */
+							 PointerGetDatum(NULL),	/* allParameterTypes */
+							 PointerGetDatum(NULL),	/* parameterModes */
+							 PointerGetDatum(NULL),	/* parameterNames */
+							 NIL,	/* parameterDefaults */
+							 PointerGetDatum(NULL),	/* trftypes */
+							 PointerGetDatum(NULL),	/* proconfig */
+							 InvalidOid,	/* prosupport */
+							 1.0,	/* procost */
+							 0.0);	/* prorows */
+	/* ditto */
+	recordDependencyOn(&myself, &referenced, DEPENDENCY_INTERNAL);
+	pfree(argtypes);
+	*castFuncOid = myself.objectId;
+
+	/* n-arg constructor - vararg */
+	argtypes = buildoidvector(&rangeArrayOid, 1);
+	allParamTypes = ObjectIdGetDatum(rangeArrayOid);
+	allParameterTypes = construct_array(&allParamTypes,
+										1, OIDOID,
+										sizeof(Oid), true, 'i');
+	paramModes = CharGetDatum(FUNC_PARAM_VARIADIC);
+	parameterModes = construct_array(&paramModes, 1, CHAROID,
+									 1, true, 'c');
+	myself = ProcedureCreate(name,	/* name: same as multirange type */
+							 namespace,
+							 false,	/* replace */
+							 false,	/* returns set */
+							 multirangeOid,	/* return type */
+							 BOOTSTRAP_SUPERUSERID,	/* proowner */
+							 INTERNALlanguageId,	/* language */
+							 F_FMGR_INTERNAL_VALIDATOR,
+							 "multirange_constructor2",	/* prosrc */
+							 NULL,	/* probin */
+							 PROKIND_FUNCTION,
+							 false,	/* security_definer */
+							 false,	/* leakproof */
+							 false, /* isStrict */
+							 PROVOLATILE_IMMUTABLE,	/* volatility */
+							 PROPARALLEL_SAFE,	/* parallel safety */
+							 argtypes,	/* parameterTypes */
+							 PointerGetDatum(allParameterTypes),	/* allParameterTypes */
+							 PointerGetDatum(parameterModes),	/* parameterModes */
+							 PointerGetDatum(NULL),	/* parameterNames */
+							 NIL,	/* parameterDefaults */
+							 PointerGetDatum(NULL),	/* trftypes */
+							 PointerGetDatum(NULL),	/* proconfig */
+							 InvalidOid,	/* prosupport */
+							 1.0,	/* procost */
+							 0.0);	/* prorows */
+	/* ditto */
+	recordDependencyOn(&myself, &referenced, DEPENDENCY_INTERNAL);
+	pfree(argtypes);
+	pfree(allParameterTypes);
+	pfree(parameterModes);
+}
 
 /*
  * Find suitable I/O functions for a type.
@@ -2068,6 +2310,72 @@ AssignTypeArrayOid(void)
 	return type_array_oid;
 }
 
+/*
+ *	AssignTypeMultirangeOid
+ *
+ *	Pre-assign the range type's multirange OID for use in pg_type.oid
+ */
+Oid
+AssignTypeMultirangeOid(void)
+{
+	Oid			type_multirange_oid;
+
+	/* Use binary-upgrade override for pg_type.oid? */
+	if (IsBinaryUpgrade)
+	{
+		if (!OidIsValid(binary_upgrade_next_mrng_pg_type_oid))
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+					 errmsg("pg_type multirange OID value not set when in binary upgrade mode")));
+
+		type_multirange_oid = binary_upgrade_next_mrng_pg_type_oid;
+		binary_upgrade_next_mrng_pg_type_oid = InvalidOid;
+	}
+	else
+	{
+		Relation	pg_type = table_open(TypeRelationId, AccessShareLock);
+
+		type_multirange_oid = GetNewOidWithIndex(pg_type, TypeOidIndexId,
+												 Anum_pg_type_oid);
+		table_close(pg_type, AccessShareLock);
+	}
+
+	return type_multirange_oid;
+}
+
+/*
+ *	AssignTypeMultirangeArrayOid
+ *
+ *	Pre-assign the range type's multirange array OID for use in pg_type.typarray
+ */
+Oid
+AssignTypeMultirangeArrayOid(void)
+{
+	Oid			type_multirange_array_oid;
+
+	/* Use binary-upgrade override for pg_type.oid? */
+	if (IsBinaryUpgrade)
+	{
+		if (!OidIsValid(binary_upgrade_next_mrng_array_pg_type_oid))
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+					 errmsg("pg_type multirange array OID value not set when in binary upgrade mode")));
+
+		type_multirange_array_oid = binary_upgrade_next_mrng_array_pg_type_oid;
+		binary_upgrade_next_mrng_array_pg_type_oid = InvalidOid;
+	}
+	else
+	{
+		Relation	pg_type = table_open(TypeRelationId, AccessShareLock);
+
+		type_multirange_array_oid = GetNewOidWithIndex(pg_type, TypeOidIndexId,
+													   Anum_pg_type_oid);
+		table_close(pg_type, AccessShareLock);
+	}
+
+	return type_multirange_array_oid;
+}
+
 
 /*-------------------------------------------------------------------
  * DefineCompositeType
diff --git a/src/backend/executor/functions.c b/src/backend/executor/functions.c
index f940f48c6d..cdeda14dd9 100644
--- a/src/backend/executor/functions.c
+++ b/src/backend/executor/functions.c
@@ -1694,7 +1694,8 @@ check_sql_fn_retval(List *queryTreeList,
 	if (fn_typtype == TYPTYPE_BASE ||
 		fn_typtype == TYPTYPE_DOMAIN ||
 		fn_typtype == TYPTYPE_ENUM ||
-		fn_typtype == TYPTYPE_RANGE)
+		fn_typtype == TYPTYPE_RANGE ||
+		fn_typtype == TYPTYPE_MULTIRANGE)
 	{
 		/*
 		 * For scalar-type returns, the target list must have exactly one
diff --git a/src/backend/parser/parse_coerce.c b/src/backend/parser/parse_coerce.c
index 1b11cf731c..04cbffd7c2 100644
--- a/src/backend/parser/parse_coerce.c
+++ b/src/backend/parser/parse_coerce.c
@@ -189,19 +189,21 @@ coerce_type(ParseState *pstate, Node *node,
 	if (targetTypeId == ANYARRAYOID ||
 		targetTypeId == ANYENUMOID ||
 		targetTypeId == ANYRANGEOID ||
+		targetTypeId == ANYMULTIRANGEOID ||
 		targetTypeId == ANYCOMPATIBLEARRAYOID ||
-		targetTypeId == ANYCOMPATIBLERANGEOID)
+		targetTypeId == ANYCOMPATIBLERANGEOID ||
+		targetTypeId == ANYCOMPATIBLEMULTIRANGEOID)
 	{
 		/*
 		 * 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 the pseudotype's input
-		 * function, 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
+		 * the pseudotype's input function, 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
@@ -1532,8 +1534,8 @@ coerce_to_common_type(ParseState *pstate, Node *node,
  * 1) All arguments declared ANYELEMENT must have the same datatype.
  * 2) All arguments declared ANYARRAY must have the same datatype,
  *	  which must be a varlena array type.
- * 3) All arguments declared ANYRANGE must have the same datatype,
- *	  which must be a range type.
+ * 3) All arguments declared ANYRANGE or ANYMULTIRANGE must be a range or
+ *	  multirange type, all derived from the same base datatype.
  * 4) If there are arguments of more than one of these polymorphic types,
  *	  the array element type and/or range subtype must be the same as each
  *	  other and the same as the ANYELEMENT type.
@@ -1548,8 +1550,8 @@ coerce_to_common_type(ParseState *pstate, Node *node,
  *	  to a common supertype (chosen as per select_common_type's rules).
  *	  ANYCOMPATIBLENONARRAY works like ANYCOMPATIBLE but also requires the
  *	  common supertype to not be an array.  If there are ANYCOMPATIBLEARRAY
- *	  or ANYCOMPATIBLERANGE arguments, their element types or subtypes are
- *	  included while making the choice of common supertype.
+ *	  or ANYCOMPATIBLERANGE or ANYCOMPATIBLEMULTIRANGE arguments, their element
+ *	  types or subtypes are included while making the choice of common supertype.
  * 8) The resolved type of ANYCOMPATIBLEARRAY arguments will be the array
  *	  type over the common supertype (which might not be the same array type
  *	  as any of the original arrays).
@@ -1557,6 +1559,10 @@ coerce_to_common_type(ParseState *pstate, Node *node,
  *	  (after domain flattening), since we have no preference rule that would
  *	  let us choose one over another.  Furthermore, that range's subtype
  *	  must exactly match the common supertype chosen by rule 7.
+ * 10) All ANYCOMPATIBLEMULTIRANGE arguments must be the exact same multirange
+ *	  type (after domain flattening), since we have no preference rule that would
+ *	  let us choose one over another.  Furthermore, that multirange's range's
+ *	  subtype must exactly match the common supertype chosen by rule 7.
  *
  * Domains over arrays match ANYARRAY, and are immediately flattened to their
  * base type.  (Thus, for example, we will consider it a match if one ANYARRAY
@@ -1565,7 +1571,9 @@ coerce_to_common_type(ParseState *pstate, Node *node,
  * for ANYCOMPATIBLEARRAY and ANYCOMPATIBLENONARRAY.
  *
  * Similarly, domains over ranges match ANYRANGE or ANYCOMPATIBLERANGE,
- * and are immediately flattened to their base type.
+ * and are immediately flattened to their base type, and domains over
+ * multiranges match ANYMULTIRANGE or ANYCOMPATIBLEMULTIRANGE and are immediately
+ * flattened to their base type.
  *
  * Note that domains aren't currently considered to match ANYENUM,
  * even if their base type would match.
@@ -1583,8 +1591,12 @@ check_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			anycompatible_range_typeid = InvalidOid;
 	Oid			anycompatible_range_typelem = InvalidOid;
+	Oid			anycompatible_multirange_typeid = InvalidOid;
+	Oid			anycompatible_multirange_typelem = InvalidOid;
+	Oid			range_typelem = InvalidOid;
 	bool		have_anynonarray = false;
 	bool		have_anyenum = false;
 	bool		have_anycompatible_nonarray = false;
@@ -1633,6 +1645,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;
+		}
 		else if (decl_type == ANYCOMPATIBLEOID ||
 				 decl_type == ANYCOMPATIBLENONARRAYOID)
 		{
@@ -1677,6 +1698,42 @@ check_generic_type_consistency(const Oid *actual_arg_types,
 				anycompatible_actual_types[n_anycompatible_args++] = anycompatible_range_typelem;
 			}
 		}
+		else if (decl_type == ANYCOMPATIBLEMULTIRANGEOID)
+		{
+			if (actual_type == UNKNOWNOID)
+				continue;
+			actual_type = getBaseType(actual_type); /* flatten domains */
+			if (OidIsValid(anycompatible_multirange_typeid))
+			{
+				/* All ANYCOMPATIBLEMULTIRANGE arguments must be the same type */
+				if (anycompatible_multirange_typeid != actual_type)
+					return false;
+			}
+			else
+			{
+				anycompatible_multirange_typeid = actual_type;
+				anycompatible_multirange_typelem = get_range_multirange_subtype(actual_type);
+				if (!OidIsValid(anycompatible_multirange_typelem))
+					return false;	/* not a multirange type */
+
+				if (OidIsValid(anycompatible_range_typeid))
+				{
+					/* ANYCOMPATIBLEMULTIRANGE and ANYCOMPATIBLERANGE arguments must match */
+					if (anycompatible_range_typeid != anycompatible_multirange_typelem)
+						return false;
+				}
+				else
+				{
+					anycompatible_range_typeid = anycompatible_multirange_typelem;
+					anycompatible_range_typelem = get_range_subtype(anycompatible_range_typeid);
+					if (!OidIsValid(anycompatible_range_typelem))
+						return false;	/* not a range type */
+				}
+				/* collect the subtype for common-supertype choice */
+				anycompatible_actual_types[n_anycompatible_args++] =
+					anycompatible_range_typelem;
+			}
+		}
 	}
 
 	/* Get the element type based on the array type, if we have one */
@@ -1723,8 +1780,6 @@ check_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))
 	{
-		Oid			range_typelem;
-
 		range_typelem = get_range_subtype(range_typeid);
 		if (!OidIsValid(range_typelem))
 			return false;		/* should be a range, but isn't */
@@ -1743,6 +1798,45 @@ 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))
+	{
+		Oid			multirange_typelem;
+
+		multirange_typelem = get_range_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 */
+		}
+		else if (multirange_typelem != range_typeid)
+		{
+			/* otherwise, they better match */
+			return false;
+		}
+
+		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 */
@@ -1781,8 +1875,10 @@ check_generic_type_consistency(const Oid *actual_arg_types,
 		}
 
 		/*
-		 * the anycompatible type must exactly match the range element type,
-		 * if we were able to identify one
+		 * The anycompatible type must exactly match the range element type,
+		 * if we were able to identify one. This checks compatibility for
+		 * anycompatiblemultirange too since that also sets
+		 * anycompatible_range_typelem above.
 		 */
 		if (OidIsValid(anycompatible_range_typelem) &&
 			anycompatible_range_typelem != anycompatible_typeid)
@@ -1821,21 +1917,27 @@ check_generic_type_consistency(const Oid *actual_arg_types,
  *	  argument's actual type as the function's return type.
  * 2) If return type is ANYARRAY, and any argument is ANYARRAY, use the
  *	  argument's actual type as the function's return type.
- * 3) Similarly, if return type is ANYRANGE, and any argument is ANYRANGE,
- *	  use the argument's actual type as the function's return type.
- * 4) Otherwise, if return type is ANYELEMENT or ANYARRAY, and there is
+ * 3) Similarly, if return type is ANYRANGE or ANYMULTIRANGE, and any
+ *	  argument is ANYRANGE or ANYMULTIRANGE, use that argument's
+ *	  actual type, range type or multirange type as the function's return
+ *	  type.
+ * 4) Otherwise, if return type is ANYMULTIRANGE, and any argument is
+ *	  ANYMULTIRANGE, use the argument's actual type as the function's return
+ *	  type. Or if any argument is ANYRANGE, use its multirange type as the
+ *	  function's return type.
+ * 5) Otherwise, if return type is ANYELEMENT or ANYARRAY, and there is
  *	  at least one ANYELEMENT, ANYARRAY, or ANYRANGE input, deduce the
  *	  return type from those inputs, or throw error if we can't.
- * 5) Otherwise, if return type is ANYRANGE, throw error.  (We have no way to
- *	  select a specific range type if the arguments don't include ANYRANGE.)
- * 6) ANYENUM is treated the same as ANYELEMENT except that if it is used
+ * 6) Otherwise, if return type is ANYRANGE or ANYMULTIRANGE, throw error.
+ *	  (We have no way to select a specific range type if the arguments don't
+ *	  include ANYRANGE.)
  *	  (alone or in combination with plain ANYELEMENT), we add the extra
  *	  condition that the ANYELEMENT type must be an enum.
- * 7) ANYNONARRAY is treated the same as ANYELEMENT except that if it is used,
+ * 8) ANYNONARRAY is treated the same as ANYELEMENT except that if it is used,
  *	  we add the extra condition that the ANYELEMENT type must not be an array.
  *	  (This is a no-op if used in combination with ANYARRAY or ANYENUM, but
  *	  is an extra restriction if not.)
- * 8) ANYCOMPATIBLE, ANYCOMPATIBLEARRAY, ANYCOMPATIBLENONARRAY, and
+ * 9) ANYCOMPATIBLE, ANYCOMPATIBLEARRAY, ANYCOMPATIBLENONARRAY, and
  *	  ANYCOMPATIBLERANGE are handled by resolving the common supertype
  *	  of those arguments (or their element types/subtypes, for array and range
  *	  inputs), and then coercing all those arguments to the common supertype,
@@ -1889,15 +1991,21 @@ 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			anycompatible_typeid = InvalidOid;
 	Oid			anycompatible_array_typeid = InvalidOid;
 	Oid			anycompatible_range_typeid = InvalidOid;
 	Oid			anycompatible_range_typelem = InvalidOid;
+	Oid			anycompatible_multirange_typeid = InvalidOid;
+	Oid			anycompatible_multirange_typelem = InvalidOid;
+	Oid			range_typelem;
+	Oid			multirange_typelem;
 	bool		have_anynonarray = (rettype == ANYNONARRAYOID);
 	bool		have_anyenum = (rettype == ANYENUMOID);
 	bool		have_anycompatible_nonarray = (rettype == ANYCOMPATIBLENONARRAYOID);
 	bool		have_anycompatible_array = (rettype == ANYCOMPATIBLEARRAYOID);
 	bool		have_anycompatible_range = (rettype == ANYCOMPATIBLERANGEOID);
+	bool		have_anycompatible_multirange = (rettype == ANYCOMPATIBLEMULTIRANGEOID);
 	int			n_poly_args = 0;	/* this counts all family-1 arguments */
 	int			n_anycompatible_args = 0;	/* this counts only non-unknowns */
 	Oid			anycompatible_actual_types[FUNC_MAX_ARGS];
@@ -1977,6 +2085,26 @@ enforce_generic_type_consistency(const Oid *actual_arg_types,
 								   format_type_be(actual_type))));
 			range_typeid = actual_type;
 		}
+		else if (decl_type == ANYMULTIRANGEOID)
+		{
+			n_poly_args++;
+			if (actual_type == UNKNOWNOID)
+			{
+				have_poly_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;
+		}
 		else if (decl_type == ANYCOMPATIBLEOID ||
 				 decl_type == ANYCOMPATIBLENONARRAYOID)
 		{
@@ -2045,6 +2173,41 @@ enforce_generic_type_consistency(const Oid *actual_arg_types,
 				anycompatible_actual_types[n_anycompatible_args++] = anycompatible_range_typelem;
 			}
 		}
+		else if (decl_type == ANYCOMPATIBLEMULTIRANGEOID)
+		{
+			have_poly_anycompatible = true;
+			have_anycompatible_multirange = true;
+			if (actual_type == UNKNOWNOID)
+				continue;
+			if (allow_poly && decl_type == actual_type)
+				continue;		/* no new information here */
+			actual_type = getBaseType(actual_type); /* flatten domains */
+			if (OidIsValid(anycompatible_multirange_typeid))
+			{
+				/* All ANYCOMPATIBLEMULTIRANGE arguments must be the same type */
+				if (anycompatible_multirange_typeid != actual_type)
+					ereport(ERROR,
+							(errcode(ERRCODE_DATATYPE_MISMATCH),
+							 errmsg("arguments declared \"anycompatiblemultirange\" are not all alike"),
+							 errdetail("%s versus %s",
+									   format_type_be(anycompatible_multirange_typeid),
+									   format_type_be(actual_type))));
+			}
+			else
+			{
+				anycompatible_multirange_typeid = actual_type;
+				anycompatible_multirange_typelem = get_range_multirange_subtype(actual_type);
+				anycompatible_range_typelem = get_range_subtype(anycompatible_multirange_typelem);
+				if (!OidIsValid(anycompatible_multirange_typelem))
+					ereport(ERROR,
+							(errcode(ERRCODE_DATATYPE_MISMATCH),
+							 errmsg("argument declared %s is not a multirange type but type %s",
+									"anycompatiblemultirange",
+									format_type_be(actual_type))));
+				/* collect the subtype for common-supertype choice */
+				anycompatible_actual_types[n_anycompatible_args++] = anycompatible_range_typelem;
+			}
+		}
 	}
 
 	/*
@@ -2113,8 +2276,6 @@ 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))
 		{
-			Oid			range_typelem;
-
 			range_typelem = get_range_subtype(range_typeid);
 			if (!OidIsValid(range_typelem))
 				ereport(ERROR,
@@ -2143,6 +2304,61 @@ enforce_generic_type_consistency(const Oid *actual_arg_types,
 								   format_type_be(elem_typeid))));
 			}
 		}
+		else
+			range_typelem = InvalidOid;
+
+		/* Get the element type based on the multirange type, if we have one */
+		if (OidIsValid(multirange_typeid))
+		{
+			multirange_typelem = get_range_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))));
+			}
+
+			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 */
+				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))));
+			}
+		}
+		else
+			multirange_typelem = InvalidOid;
 
 		if (!OidIsValid(elem_typeid))
 		{
@@ -2151,6 +2367,7 @@ enforce_generic_type_consistency(const Oid *actual_arg_types,
 				elem_typeid = ANYELEMENTOID;
 				array_typeid = ANYARRAYOID;
 				range_typeid = ANYRANGEOID;
+				multirange_typeid = ANYMULTIRANGEOID;
 			}
 			else
 			{
@@ -2250,6 +2467,7 @@ enforce_generic_type_consistency(const Oid *actual_arg_types,
 				anycompatible_typeid = ANYCOMPATIBLEOID;
 				anycompatible_array_typeid = ANYCOMPATIBLEARRAYOID;
 				anycompatible_range_typeid = ANYCOMPATIBLERANGEOID;
+				anycompatible_multirange_typeid = ANYCOMPATIBLEMULTIRANGEOID;
 			}
 			else
 			{
@@ -2281,6 +2499,8 @@ enforce_generic_type_consistency(const Oid *actual_arg_types,
 				declared_arg_types[j] = anycompatible_array_typeid;
 			else if (decl_type == ANYCOMPATIBLERANGEOID)
 				declared_arg_types[j] = anycompatible_range_typeid;
+			else if (decl_type == ANYCOMPATIBLEMULTIRANGEOID)
+				declared_arg_types[j] = anycompatible_multirange_typeid;
 		}
 	}
 
@@ -2331,6 +2551,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;
+			}
 		}
 	}
 
@@ -2367,6 +2598,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 ANYCOMPATIBLE use the appropriate type */
 	if (rettype == ANYCOMPATIBLEOID ||
 		rettype == ANYCOMPATIBLENONARRAYOID)
@@ -2401,6 +2648,17 @@ enforce_generic_type_consistency(const Oid *actual_arg_types,
 		return anycompatible_range_typeid;
 	}
 
+	/* if we return ANYCOMPATIBLEMULTIRANGE use the appropriate argument type */
+	if (rettype == ANYCOMPATIBLEMULTIRANGEOID)
+	{
+		/* this error is unreachable if the function signature is valid: */
+		if (!OidIsValid(anycompatible_multirange_typeid))
+			ereport(ERROR,
+					(errcode(ERRCODE_DATATYPE_MISMATCH),
+					 errmsg_internal("could not identify anycompatiblemultirange type")));
+		return anycompatible_multirange_typeid;
+	}
+
 	/* we don't return a generic type; send back the original return type */
 	return rettype;
 }
@@ -2418,20 +2676,37 @@ check_valid_polymorphic_signature(Oid ret_type,
 								  const Oid *declared_arg_types,
 								  int nargs)
 {
-	if (ret_type == ANYRANGEOID || ret_type == ANYCOMPATIBLERANGEOID)
+	if (ret_type == ANYRANGEOID || ret_type == ANYMULTIRANGEOID)
 	{
 		/*
-		 * ANYRANGE requires an ANYRANGE input, else we can't tell which of
-		 * several range types with the same element type to use.  Likewise
-		 * for ANYCOMPATIBLERANGE.
+		 * ANYRANGE and ANYMULTIRANGE require an ANYRANGE or ANYMULTIRANGE input,
+		 * else we can't tell which of several range types with the same element
+		 * type to use.
 		 */
 		for (int i = 0; i < nargs; i++)
 		{
-			if (declared_arg_types[i] == ret_type)
+			if (declared_arg_types[i] == ANYRANGEOID ||
+				declared_arg_types[i] == ANYMULTIRANGEOID)
 				return NULL;	/* OK */
 		}
-		return psprintf(_("A result of type %s requires at least one input of type %s."),
-						format_type_be(ret_type), format_type_be(ret_type));
+		return psprintf(_("A result of type %s requires at least one input of type anyrange or anymultirange."),
+						format_type_be(ret_type));
+	}
+	else if (ret_type == ANYCOMPATIBLERANGEOID || ret_type == ANYCOMPATIBLEMULTIRANGEOID)
+	{
+		/*
+		 * ANYCOMPATIBLERANGE and ANYCOMPATIBLEMULTIRANGE require an ANYCOMPATIBLERANGE or
+		 * ANYCOMPATIBLEMULTIRANGE input, else we can't tell which of several range types
+		 * with the same element type to use.
+		 */
+		for (int i = 0; i < nargs; i++)
+		{
+			if (declared_arg_types[i] == ANYCOMPATIBLERANGEOID ||
+				declared_arg_types[i] == ANYCOMPATIBLEMULTIRANGEOID)
+				return NULL;	/* OK */
+		}
+		return psprintf(_("A result of type %s requires at least one input of type anycompatiblerange or anycompatiblemultirange."),
+						format_type_be(ret_type));
 	}
 	else if (IsPolymorphicTypeFamily1(ret_type))
 	{
@@ -2442,7 +2717,7 @@ check_valid_polymorphic_signature(Oid ret_type,
 				return NULL;	/* OK */
 		}
 		/* Keep this list in sync with IsPolymorphicTypeFamily1! */
-		return psprintf(_("A result of type %s requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange."),
+		return psprintf(_("A result of type %s requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange."),
 						format_type_be(ret_type));
 	}
 	else if (IsPolymorphicTypeFamily2(ret_type))
@@ -2594,6 +2869,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 || targettype == ANYCOMPATIBLEMULTIRANGEOID)
+		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 5d2aca8cfe..2cea8e2e17 100644
--- a/src/backend/utils/adt/Makefile
+++ b/src/backend/utils/adt/Makefile
@@ -58,6 +58,8 @@ OBJS = \
 	mac.o \
 	mac8.o \
 	misc.o \
+	multirangetypes.o \
+	multirangetypes_selfuncs.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..6bd5070b96
--- /dev/null
+++ b/src/backend/utils/adt/multirangetypes.c
@@ -0,0 +1,2542 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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/int8.h"
+#include "utils/lsyscache.h"
+#include "utils/rangetypes.h"
+#include "utils/multirangetypes.h"
+#include "utils/timestamp.h"
+#include "utils/array.h"
+#include "utils/memutils.h"
+
+/* fn_extra cache entry for one of the range I/O functions */
+typedef struct MultirangeIOData
+{
+	TypeCacheEntry *typcache;	/* multirange type's typcache entry */
+	FmgrInfo	typioproc;		/* range type's I/O proc */
+	Oid			typioparam;		/* range type's I/O parameter */
+} 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 mltrngtypid,
+												IOFuncSelector func);
+static int32 multirange_canonicalize(TypeCacheEntry *rangetyp, int32 input_range_count,
+									 RangeType **ranges);
+
+
+/*
+ *----------------------------------------------------------
+ * EXPANDED TOAST FUNCTIONS
+ *----------------------------------------------------------
+ */
+
+/* "Methods" required for an expanded object */
+static Size EMR_get_flat_size(ExpandedObjectHeader *eohptr);
+static void EMR_flatten_into(ExpandedObjectHeader *eohptr,
+							void *result, Size allocated_size);
+
+static const ExpandedObjectMethods EMR_methods =
+{
+	EMR_get_flat_size,
+	EMR_flatten_into
+};
+
+/* Other local functions */
+
+static void shortrange_deserialize(TypeCacheEntry *typcache, const ShortRangeType *range,
+		RangeBound *lower, RangeBound *upper, bool *empty);
+static void shortrange_serialize(ShortRangeType *range, TypeCacheEntry *typcache, RangeBound *lower,
+		RangeBound *upper, bool empty);
+
+/*
+ * expand_multirange: convert a multirange Datum into an expanded multirange
+ *
+ * The expanded object will be a child of parentcontext.
+ */
+Datum
+expand_multirange(Datum multirangedatum, MemoryContext parentcontext, TypeCacheEntry *rangetyp)
+{
+	MultirangeType  *multirange;
+	ExpandedMultirangeHeader *emrh;
+	MemoryContext objcxt;
+	MemoryContext oldcxt;
+
+	/*
+	 * Allocate private context for expanded object.  We start by assuming
+	 * that the multirange won't be very large; but if it does grow a lot, don't
+	 * constrain aset.c's large-context behavior.
+	 */
+	objcxt = AllocSetContextCreate(parentcontext,
+								   "expanded multirange",
+								   ALLOCSET_START_SMALL_SIZES);
+
+	/* Set up expanded multirange header */
+	emrh = (ExpandedMultirangeHeader *)
+		MemoryContextAlloc(objcxt, sizeof(ExpandedMultirangeHeader));
+
+	EOH_init_header(&emrh->hdr, &EMR_methods, objcxt);
+	emrh->emr_magic = EMR_MAGIC;
+
+	/*
+	 * Detoast and copy source multirange into private context. We need to do
+	 * this so that we get our own copies of the upper/lower bounds.
+	 *
+	 * Note that this coding risks leaking some memory in the private context
+	 * if we have to fetch data from a TOAST table; however, experimentation
+	 * says that the leak is minimal.  Doing it this way saves a copy step,
+	 * which seems worthwhile, especially if the multirange is large enough to
+	 * need external storage.
+	 */
+	oldcxt = MemoryContextSwitchTo(objcxt);
+	multirange = DatumGetMultirangeTypePCopy(multirangedatum);
+
+	emrh->multirangetypid = multirange->multirangetypid;
+	emrh->rangeCount = multirange->rangeCount;
+
+	/* Convert each ShortRangeType into a RangeType */
+
+	ShortRangeType	*shortrange;
+	Pointer		ptr;
+	Pointer		end;
+	int32		i;
+	RangeBound	lower;
+	RangeBound	upper;
+	bool		empty;
+
+	if (emrh->rangeCount > 0)
+	{
+		emrh->ranges = palloc(emrh->rangeCount * sizeof(RangeType *));
+
+		ptr = (char *) multirange;
+		end = ptr + VARSIZE(multirange);
+		ptr = (char *) MAXALIGN(multirange + 1);
+		i = 0;
+		while (ptr < end)
+		{
+			shortrange = (ShortRangeType *) ptr;
+			shortrange_deserialize(rangetyp, shortrange, &lower, &upper, &empty);
+			emrh->ranges[i++] = make_range(rangetyp, &lower, &upper, empty);
+
+			ptr += MAXALIGN(VARSIZE(shortrange));
+		}
+	}
+	else
+	{
+		emrh->ranges = NULL;
+	}
+	MemoryContextSwitchTo(oldcxt);
+
+	/* return a R/W pointer to the expanded multirange */
+	return EOHPGetRWDatum(&emrh->hdr);
+}
+
+/*
+ * shortrange_deserialize: Extract bounds and empty flag from a ShortRangeType
+ */
+void shortrange_deserialize(TypeCacheEntry *typcache, const ShortRangeType *range,
+							RangeBound *lower, RangeBound *upper, bool *empty)
+{
+	char		flags;
+	int16		typlen;
+	bool		typbyval;
+	char		typalign;
+	Pointer		ptr;
+	Datum		lbound;
+	Datum		ubound;
+
+	/* fetch the flag byte */
+	flags = range->flags;
+
+	/* fetch information about range's element type */
+	typlen = typcache->rngelemtype->typlen;
+	typbyval = typcache->rngelemtype->typbyval;
+	typalign = typcache->rngelemtype->typalign;
+
+	/* initialize data pointer just after the range struct fields */
+	ptr = (Pointer) MAXALIGN(range + 1);
+
+	/* fetch lower bound, if any */
+	if (RANGE_HAS_LBOUND(flags))
+	{
+		/* att_align_pointer cannot be necessary here */
+		lbound = fetch_att(ptr, typbyval, typlen);
+		ptr = (Pointer) att_addlength_pointer(ptr, typlen, ptr);
+	}
+	else
+		lbound = (Datum) 0;
+
+	/* fetch upper bound, if any */
+	if (RANGE_HAS_UBOUND(flags))
+	{
+		ptr = (Pointer) att_align_pointer(ptr, typalign, typlen, ptr);
+		ubound = fetch_att(ptr, typbyval, typlen);
+		/* no need for att_addlength_pointer */
+	}
+	else
+		ubound = (Datum) 0;
+
+	/* emit results */
+
+	*empty = (flags & RANGE_EMPTY) != 0;
+
+	lower->val = lbound;
+	lower->infinite = (flags & RANGE_LB_INF) != 0;
+	lower->inclusive = (flags & RANGE_LB_INC) != 0;
+	lower->lower = true;
+
+	upper->val = ubound;
+	upper->infinite = (flags & RANGE_UB_INF) != 0;
+	upper->inclusive = (flags & RANGE_UB_INC) != 0;
+	upper->lower = false;
+}
+
+/*
+ * get_flat_size method for expanded multirange
+ */
+static Size
+EMR_get_flat_size(ExpandedObjectHeader *eohptr)
+{
+	ExpandedMultirangeHeader *emrh = (ExpandedMultirangeHeader *) eohptr;
+	RangeType  *range;
+	Size		nbytes;
+	int			i;
+
+	Assert(emrh->emr_magic == EMR_MAGIC);
+
+	/* If we have a cached size value, believe that */
+	if (emrh->flat_size)
+		return emrh->flat_size;
+
+	/*
+	 * Compute space needed by examining ranges.
+	 */
+	nbytes = MAXALIGN(sizeof(MultirangeType));
+	for (i = 0; i < emrh->rangeCount; i++)
+	{
+		range = emrh->ranges[i];
+		nbytes += MAXALIGN(VARSIZE(range) - sizeof(char));
+		/* check for overflow of total request */
+		if (!AllocSizeIsValid(nbytes))
+			ereport(ERROR,
+					(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
+					 errmsg("multirange size exceeds the maximum allowed (%d)",
+							(int) MaxAllocSize)));
+	}
+
+	/* cache for next time */
+	emrh->flat_size = nbytes;
+
+	return nbytes;
+}
+
+/*
+ * flatten_into method for expanded multiranges
+ */
+static void
+EMR_flatten_into(ExpandedObjectHeader *eohptr,
+				 void *result, Size allocated_size)
+{
+	ExpandedMultirangeHeader *emrh = (ExpandedMultirangeHeader *) eohptr;
+	MultirangeType  *mrresult = (MultirangeType *) result;
+	TypeCacheEntry	*typcache;
+	int			rangeCount;
+	RangeType	**ranges;
+	RangeBound	lower;
+	RangeBound	upper;
+	bool		empty;
+	Pointer		ptr;
+	int			i;
+
+	Assert(emrh->emr_magic == EMR_MAGIC);
+
+	typcache = lookup_type_cache(emrh->multirangetypid, TYPECACHE_MULTIRANGE_INFO);
+	if (typcache->rngtype == NULL)
+		elog(ERROR, "type %u is not a multirange type", emrh->multirangetypid);
+
+	/* Fill result multirange from RangeTypes */
+	rangeCount = emrh->rangeCount;
+	ranges	= emrh->ranges;
+
+	/* We must ensure that any pad space is zero-filled */
+	memset(mrresult, 0, allocated_size);
+
+	SET_VARSIZE(mrresult, allocated_size);
+
+	/* Now fill in the datum with ShortRangeTypes */
+	mrresult->multirangetypid = emrh->multirangetypid;
+	mrresult->rangeCount = rangeCount;
+
+	ptr = (char *) MAXALIGN(mrresult + 1);
+	for (i = 0; i < rangeCount; i++)
+	{
+		range_deserialize(typcache->rngtype, ranges[i], &lower, &upper, &empty);
+		shortrange_serialize((ShortRangeType *) ptr, typcache->rngtype, &lower, &upper, empty);
+		ptr += MAXALIGN(VARSIZE(ptr));
+	}
+}
+
+/*
+ * shortrange_serialize: fill in pre-allocationed range param based on the
+ * given bounds and flags.
+ */
+static void
+shortrange_serialize(ShortRangeType *range, TypeCacheEntry *typcache,
+					 RangeBound *lower, RangeBound *upper, bool empty)
+{
+	int			cmp;
+	Size		msize;
+	Pointer		ptr;
+	int16		typlen;
+	bool		typbyval;
+	char		typalign;
+	char		typstorage;
+	char		flags = 0;
+
+	/*
+	 * Verify range is not invalid on its face, and construct flags value,
+	 * preventing any non-canonical combinations such as infinite+inclusive.
+	 */
+	Assert(lower->lower);
+	Assert(!upper->lower);
+
+	if (empty)
+		flags |= RANGE_EMPTY;
+	else
+	{
+		cmp = range_cmp_bound_values(typcache, lower, upper);
+
+		/* error check: if lower bound value is above upper, it's wrong */
+		if (cmp > 0)
+			ereport(ERROR,
+					(errcode(ERRCODE_DATA_EXCEPTION),
+					 errmsg("range lower bound must be less than or equal to range upper bound")));
+
+		/* if bounds are equal, and not both inclusive, range is empty */
+		if (cmp == 0 && !(lower->inclusive && upper->inclusive))
+			flags |= RANGE_EMPTY;
+		else
+		{
+			/* infinite boundaries are never inclusive */
+			if (lower->infinite)
+				flags |= RANGE_LB_INF;
+			else if (lower->inclusive)
+				flags |= RANGE_LB_INC;
+			if (upper->infinite)
+				flags |= RANGE_UB_INF;
+			else if (upper->inclusive)
+				flags |= RANGE_UB_INC;
+		}
+	}
+
+	/* Fetch information about range's element type */
+	typlen = typcache->rngelemtype->typlen;
+	typbyval = typcache->rngelemtype->typbyval;
+	typalign = typcache->rngelemtype->typalign;
+	typstorage = typcache->rngelemtype->typstorage;
+
+	/* Count space for varlena header */
+	msize = sizeof(ShortRangeType);
+	Assert(msize == MAXALIGN(msize));
+
+	/* Count space for bounds */
+	if (RANGE_HAS_LBOUND(flags))
+	{
+		/*
+		 * Make sure item to be inserted is not toasted.  It is essential that
+		 * we not insert an out-of-line toast value pointer into a range
+		 * object, for the same reasons that arrays and records can't contain
+		 * them.  It would work to store a compressed-in-line value, but we
+		 * prefer to decompress and then let compression be applied to the
+		 * whole range object if necessary.  But, unlike arrays, we do allow
+		 * short-header varlena objects to stay as-is.
+		 */
+		if (typlen == -1)
+			lower->val = PointerGetDatum(PG_DETOAST_DATUM_PACKED(lower->val));
+
+		msize = datum_compute_size(msize, lower->val, typbyval, typalign,
+								   typlen, typstorage);
+	}
+
+	if (RANGE_HAS_UBOUND(flags))
+	{
+		/* Make sure item to be inserted is not toasted */
+		if (typlen == -1)
+			upper->val = PointerGetDatum(PG_DETOAST_DATUM_PACKED(upper->val));
+
+		msize = datum_compute_size(msize, upper->val, typbyval, typalign,
+								   typlen, typstorage);
+	}
+
+	SET_VARSIZE(range, msize);
+
+	ptr = (char *) (range + 1);
+
+	if (RANGE_HAS_LBOUND(flags))
+	{
+		Assert(lower->lower);
+		ptr = datum_write(ptr, lower->val, typbyval, typalign, typlen,
+						  typstorage);
+	}
+
+	if (RANGE_HAS_UBOUND(flags))
+	{
+		Assert(!upper->lower);
+		ptr = datum_write(ptr, upper->val, typbyval, typalign, typlen,
+						  typstorage);
+	}
+
+	range->flags = flags;
+}
+
+/*
+ *----------------------------------------------------------
+ * 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;
+	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 = NULL;
+	int32		range_str_len;
+	char	   *range_str_copy;
+
+	cache = get_multirange_io_data(fcinfo, mltrngtypoid, IOFunc_input);
+	rangetyp = cache->typcache->rngtype;
+
+	/* 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->typioproc,
+																 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;
+	int32		range_count;
+	int32		i;
+	RangeType **ranges;
+
+	cache = get_multirange_io_data(fcinfo, mltrngtypoid, IOFunc_output);
+
+	initStringInfo(&buf);
+
+	appendStringInfoChar(&buf, '{');
+
+	multirange_deserialize(cache->typcache->rngtype, multirange, &range_count, &ranges);
+	for (i = 0; i < range_count; i++)
+	{
+		if (i > 0)
+			appendStringInfoChar(&buf, ',');
+		range = ranges[i];
+		rangeStr = OutputFunctionCall(&cache->typioproc, RangeTypePGetDatum(range));
+		appendStringInfoString(&buf, rangeStr);
+	}
+
+	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->typioproc,
+														   &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(cache->typcache->rngtype, multirange, &range_count, &ranges);
+	for (i = 0; i < range_count; i++)
+	{
+		Datum		range;
+
+		range = RangeTypePGetDatum(ranges[i]);
+		range = PointerGetDatum(SendFunctionCall(&cache->typioproc, range));
+
+		pq_sendint32(buf, VARSIZE(range) - VARHDRSZ);
+		pq_sendbytes(buf, VARDATA(range), VARSIZE(range) - VARHDRSZ);
+	}
+
+	PG_RETURN_BYTEA_P(pq_endtypsend(buf));
+}
+
+/*
+ * 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)
+	{
+		Oid			typiofunc;
+		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,
+						 &typiofunc);
+
+		if (!OidIsValid(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(typiofunc, &cache->typioproc,
+					  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;
+	int			i;
+	int32		bytelen;
+	Pointer		ptr;
+	RangeBound	lower;
+	RangeBound	upper;
+	bool		empty;
+
+	/* 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 ShortRangeTypes start aligned.
+	 */
+	bytelen = MAXALIGN(sizeof(MultirangeType));
+
+	/* Count space for all ranges */
+	for (i = 0; i < range_count; i++)
+		bytelen += MAXALIGN(VARSIZE(ranges[i]) - sizeof(char));
+
+	/* 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_deserialize(rangetyp, ranges[i], &lower, &upper, &empty);
+		shortrange_serialize((ShortRangeType *) ptr, rangetyp, &lower, &upper, empty);
+		ptr += MAXALIGN(VARSIZE(ptr));
+	}
+
+	return multirange;
+}
+
+/*
+ * Converts a list of arbitrary ranges 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(TypeCacheEntry *rangetyp,
+					   const MultirangeType *multirange, int32 *range_count,
+					   RangeType ***ranges)
+{
+	ExpandedMultirangeHeader *emrh = (ExpandedMultirangeHeader *)
+		DatumGetEOHP(expand_multirange(MultirangeTypePGetDatum(multirange),
+									   CurrentMemoryContext, rangetyp));
+
+	*range_count = emrh->rangeCount;
+	if (*range_count == 0)
+	{
+		*ranges = NULL;
+		return;
+	}
+
+	*ranges = emrh->ranges;
+}
+
+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_constructor2(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,
+				(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+				 errmsg("multirange values cannot contain NULL members")));
+
+	rangeArray = PG_GETARG_ARRAYTYPE_P(0);
+
+	dims = ARR_NDIM(rangeArray);
+	if (dims > 1)
+		ereport(ERROR,
+				(errcode(ERRCODE_CARDINALITY_VIOLATION),
+				 errmsg("multiranges cannot be constructed from multi-dimensional arrays")));
+
+	rngtypid = ARR_ELEMTYPE(rangeArray);
+	if (rngtypid != rangetyp->type_id)
+		ereport(ERROR,
+				(errcode(ERRCODE_DATATYPE_MISMATCH),
+				 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,
+						(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+						 errmsg("multirange values cannot contain NULL members")));
+
+			/* make_multirange will do its own copy */
+			ranges[i] = DatumGetRangeTypeP(elements[i]);
+		}
+	}
+
+	PG_RETURN_MULTIRANGE_P(make_multirange(mltrngtypid, rangetyp, range_count, ranges));
+}
+
+/*
+ * Construct multirange value from a single range.
+ * It'd be nice if we could just use multirange_constructor2
+ * for this case, but we need a non-variadic single-arg function
+ * to let us define a CAST from a range to its multirange.
+ */
+Datum
+multirange_constructor1(PG_FUNCTION_ARGS)
+{
+	Oid			mltrngtypid = get_fn_expr_rettype(fcinfo->flinfo);
+	Oid			rngtypid;
+	TypeCacheEntry *typcache;
+	TypeCacheEntry *rangetyp;
+	RangeType  *range;
+
+	typcache = multirange_get_typcache(fcinfo, mltrngtypid);
+	rangetyp = typcache->rngtype;
+
+	/*
+	 * These checks should be guaranteed by our signature, but let's do them
+	 * just in case.
+	 */
+
+	if (PG_ARGISNULL(0))
+				ereport(ERROR,
+						(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+						 errmsg("multirange values cannot contain NULL members")));
+
+	range = PG_GETARG_RANGE_P(0);
+
+	/* Make sure the range type matches. */
+	rngtypid = RangeTypeGetOid(range);
+	if (rngtypid != rangetyp->type_id)
+		ereport(ERROR,
+				(errcode(ERRCODE_DATATYPE_MISMATCH),
+				 errmsg("type %u does not match constructor type", rngtypid)));
+
+	PG_RETURN_MULTIRANGE_P(make_multirange(mltrngtypid, rangetyp, 1, &range));
+}
+
+/*
+ * 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
+		elog(ERROR,		/* can't happen */
+			 "niladic multirange constructor must not receive arguments");
+}
+
+
+/* multirange, multirange -> multirange type functions */
+
+/* multirange union */
+Datum
+multirange_union(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
+	MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count1;
+	int32		range_count2;
+	int32		range_count3;
+	RangeType **ranges1;
+	RangeType **ranges2;
+	RangeType **ranges3;
+
+	if (MultirangeIsEmpty(mr1))
+		PG_RETURN_MULTIRANGE_P(mr2);
+	if (MultirangeIsEmpty(mr2))
+		PG_RETURN_MULTIRANGE_P(mr1);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr1));
+
+	multirange_deserialize(typcache->rngtype, mr1, &range_count1, &ranges1);
+	multirange_deserialize(typcache->rngtype, mr2, &range_count2, &ranges2);
+
+	range_count3 = range_count1 + range_count2;
+	ranges3 = palloc0(range_count3 * sizeof(RangeType *));
+	memcpy(ranges3, ranges1, range_count1 * sizeof(RangeType *));
+	memcpy(ranges3 + range_count1, ranges2, range_count2 * sizeof(RangeType *));
+	PG_RETURN_MULTIRANGE_P(make_multirange(typcache->type_id, typcache->rngtype,
+										   range_count3, ranges3));
+}
+
+/* multirange minus */
+Datum
+multirange_minus(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
+	MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
+	Oid			mltrngtypoid = MultirangeTypeGetOid(mr1);
+	TypeCacheEntry *typcache;
+	TypeCacheEntry *rangetyp;
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+
+	typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
+	rangetyp = typcache->rngtype;
+
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		PG_RETURN_MULTIRANGE_P(mr1);
+
+	multirange_deserialize(typcache->rngtype, mr1, &range_count1, &ranges1);
+	multirange_deserialize(typcache->rngtype, mr2, &range_count2, &ranges2);
+
+	PG_RETURN_MULTIRANGE_P(multirange_minus_internal(mltrngtypoid,
+													 rangetyp,
+													 range_count1,
+													 ranges1,
+													 range_count2,
+													 ranges2));
+}
+
+MultirangeType *
+multirange_minus_internal(Oid mltrngtypoid, TypeCacheEntry *rangetyp,
+						  int32 range_count1, RangeType **ranges1,
+						  int32 range_count2, RangeType **ranges2)
+{
+	RangeType  *r1;
+	RangeType  *r2;
+	RangeType **ranges3;
+	int32		range_count3;
+	int32		i1;
+	int32		i2;
+
+	/*
+	 * Worst case: every range in ranges1 makes a different cut to some range
+	 * in ranges2.
+	 */
+	ranges3 = palloc0((range_count1 + range_count2) * sizeof(RangeType *));
+	range_count3 = 0;
+
+	/*
+	 * For each range in mr1, keep subtracting until it's gone or the ranges
+	 * in mr2 have passed it. After a subtraction we assign what's left back
+	 * to r1. The parallel progress through mr1 and mr2 is similar to
+	 * multirange_overlaps_multirange_internal.
+	 */
+	r2 = ranges2[0];
+	for (i1 = 0, i2 = 0; i1 < range_count1; i1++)
+	{
+		r1 = ranges1[i1];
+
+		/* Discard r2s while r2 << r1 */
+		while (r2 != NULL && range_before_internal(rangetyp, r2, r1))
+		{
+			r2 = ++i2 >= range_count2 ? NULL : ranges2[i2];
+		}
+
+		while (r2 != NULL)
+		{
+			if (range_split_internal(rangetyp, r1, r2, &ranges3[range_count3], &r1))
+			{
+				/*
+				 * If r2 takes a bite out of the middle of r1, we need two
+				 * outputs
+				 */
+				range_count3++;
+				r2 = ++i2 >= range_count2 ? NULL : ranges2[i2];
+
+			}
+			else if (range_overlaps_internal(rangetyp, r1, r2))
+			{
+				/*
+				 * If r2 overlaps r1, replace r1 with r1 - r2.
+				 */
+				r1 = range_minus_internal(rangetyp, r1, r2);
+
+				/*
+				 * If r2 goes past r1, then we need to stay with it, in case
+				 * it hits future r1s. Otherwise we need to keep r1, in case
+				 * future r2s hit it. Since we already subtracted, there's no
+				 * point in using the overright/overleft calls.
+				 */
+				if (RangeIsEmpty(r1) || range_before_internal(rangetyp, r1, r2))
+					break;
+				else
+					r2 = ++i2 >= range_count2 ? NULL : ranges2[i2];
+
+			}
+			else
+			{
+				/*
+				 * This and all future r2s are past r1, so keep them. Also
+				 * assign whatever is left of r1 to the result.
+				 */
+				break;
+			}
+		}
+
+		/*
+		 * Nothing else can remove anything from r1, so keep it. Even if r1 is
+		 * empty here, make_multirange will remove it.
+		 */
+		ranges3[range_count3++] = r1;
+	}
+
+	return make_multirange(mltrngtypoid, rangetyp, range_count3, ranges3);
+}
+
+/* multirange intersection */
+Datum
+multirange_intersect(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
+	MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
+	Oid			mltrngtypoid = MultirangeTypeGetOid(mr1);
+	TypeCacheEntry *typcache;
+	TypeCacheEntry *rangetyp;
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+
+	typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
+	rangetyp = typcache->rngtype;
+
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		PG_RETURN_MULTIRANGE_P(make_empty_multirange(mltrngtypoid, rangetyp));
+
+	multirange_deserialize(rangetyp, mr1, &range_count1, &ranges1);
+	multirange_deserialize(rangetyp, mr2, &range_count2, &ranges2);
+
+	PG_RETURN_MULTIRANGE_P(multirange_intersect_internal(mltrngtypoid,
+														 rangetyp,
+														 range_count1,
+														 ranges1,
+														 range_count2,
+														 ranges2));
+}
+
+MultirangeType *
+multirange_intersect_internal(Oid mltrngtypoid, TypeCacheEntry *rangetyp,
+							  int32 range_count1, RangeType **ranges1,
+							  int32 range_count2, RangeType **ranges2)
+{
+	RangeType  *r1;
+	RangeType  *r2;
+	RangeType **ranges3;
+	int32		range_count3;
+	int32		i1;
+	int32		i2;
+
+	if (range_count1 == 0 || range_count2 == 0)
+		return make_multirange(mltrngtypoid, rangetyp, 0, NULL);
+
+	/*-----------------------------------------------
+	 * Worst case is a stitching pattern like this:
+	 *
+	 * mr1: --- --- --- ---
+	 * mr2:   --- --- ---
+	 * mr3:   - - - - - -
+	 *
+	 * That seems to be range_count1 + range_count2 - 1,
+	 * but one extra won't hurt.
+	 *-----------------------------------------------
+	 */
+	ranges3 = palloc0((range_count1 + range_count2) * sizeof(RangeType *));
+	range_count3 = 0;
+
+	/*
+	 * For each range in mr1, keep intersecting until the ranges in mr2 have
+	 * passed it. The parallel progress through mr1 and mr2 is similar to
+	 * multirange_minus_multirange_internal, but we don't have to assign back
+	 * to r1.
+	 */
+	r2 = ranges2[0];
+	for (i1 = 0, i2 = 0; i1 < range_count1; i1++)
+	{
+		r1 = ranges1[i1];
+
+		/* Discard r2s while r2 << r1 */
+		while (r2 != NULL && range_before_internal(rangetyp, r2, r1))
+		{
+			r2 = ++i2 >= range_count2 ? NULL : ranges2[i2];
+		}
+
+		while (r2 != NULL)
+		{
+			if (range_overlaps_internal(rangetyp, r1, r2))
+			{
+				/* Keep the overlapping part */
+				ranges3[range_count3++] = range_intersect_internal(rangetyp, r1, r2);
+
+				/* If we "used up" all of r2, go to the next one... */
+				if (range_overleft_internal(rangetyp, r2, r1))
+					r2 = ++i2 >= range_count2 ? NULL : ranges2[i2];
+
+				/* ...otherwise go to the next r1 */
+				else
+					break;
+			}
+			else
+				/* We're past r1, so move to the next one */
+				break;
+		}
+
+		/* If we're out of r2s, there can be no more intersections */
+		if (r2 == NULL)
+			break;
+	}
+
+	return make_multirange(mltrngtypoid, rangetyp, range_count3, ranges3);
+}
+
+/*
+ * range_agg_transfn: combine adjacent/overlapping ranges.
+ *
+ * All we do here is gather the input ranges into an array
+ * so that the finalfn can sort and combine them.
+ */
+Datum
+range_agg_transfn(PG_FUNCTION_ARGS)
+{
+	MemoryContext aggContext;
+	Oid			rngtypoid;
+	ArrayBuildState *state;
+
+	if (!AggCheckCallContext(fcinfo, &aggContext))
+		elog(ERROR, "range_agg_transfn called in non-aggregate context");
+
+	rngtypoid = get_fn_expr_argtype(fcinfo->flinfo, 1);
+	if (!type_is_range(rngtypoid))
+		ereport(ERROR,
+				(errcode(ERRCODE_DATATYPE_MISMATCH),
+				 errmsg("range_agg must be called with a range")));
+
+	if (PG_ARGISNULL(0))
+		state = initArrayResult(rngtypoid, aggContext, false);
+	else
+		state = (ArrayBuildState *) PG_GETARG_POINTER(0);
+
+	/* skip NULLs */
+	if (!PG_ARGISNULL(1))
+		accumArrayResult(state, PG_GETARG_DATUM(1), false, rngtypoid, aggContext);
+
+	PG_RETURN_POINTER(state);
+}
+
+/*
+ * range_agg_finalfn: use our internal array to merge touching ranges.
+ */
+Datum
+range_agg_finalfn(PG_FUNCTION_ARGS)
+{
+	MemoryContext aggContext;
+	Oid			mltrngtypoid;
+	TypeCacheEntry *typcache;
+	ArrayBuildState *state;
+	int32		range_count;
+	RangeType **ranges;
+	int			i;
+
+	if (!AggCheckCallContext(fcinfo, &aggContext))
+		elog(ERROR, "range_agg_finalfn called in non-aggregate context");
+
+	state = PG_ARGISNULL(0) ? NULL : (ArrayBuildState *) PG_GETARG_POINTER(0);
+	if (state == NULL)
+		/* This shouldn't be possible, but just in case.... */
+		PG_RETURN_NULL();
+
+	/* Also return NULL if we had zero inputs, like other aggregates */
+	range_count = state->nelems;
+	if (range_count == 0)
+		PG_RETURN_NULL();
+
+	mltrngtypoid = get_fn_expr_rettype(fcinfo->flinfo);
+	typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
+
+	ranges = palloc0(range_count * sizeof(RangeType *));
+	for (i = 0; i < range_count; i++)
+		ranges[i] = DatumGetRangeTypeP(state->dvalues[i]);
+
+	PG_RETURN_MULTIRANGE_P(make_multirange(mltrngtypoid, typcache->rngtype, range_count, ranges));
+}
+
+Datum
+multirange_intersect_agg_transfn(PG_FUNCTION_ARGS)
+{
+	MemoryContext aggContext;
+	Oid			mltrngtypoid;
+	TypeCacheEntry *typcache;
+	MultirangeType *result;
+	MultirangeType *current;
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+
+	if (!AggCheckCallContext(fcinfo, &aggContext))
+		elog(ERROR, "multirange_intersect_agg_transfn called in non-aggregate context");
+
+	mltrngtypoid = get_fn_expr_argtype(fcinfo->flinfo, 1);
+	if (!type_is_multirange(mltrngtypoid))
+		ereport(ERROR,
+				(errcode(ERRCODE_DATATYPE_MISMATCH),
+				 errmsg("range_intersect_agg must be called with a multirange")));
+
+	typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
+
+	/* strictness ensures these are non-null */
+	result = PG_GETARG_MULTIRANGE_P(0);
+	current = PG_GETARG_MULTIRANGE_P(1);
+
+	multirange_deserialize(typcache->rngtype, result, &range_count1, &ranges1);
+	multirange_deserialize(typcache->rngtype, current, &range_count2, &ranges2);
+
+	result = multirange_intersect_internal(mltrngtypoid,
+										   typcache->rngtype,
+										   range_count1,
+										   ranges1,
+										   range_count2,
+										   ranges2);
+	PG_RETURN_RANGE_P(result);
+}
+
+
+/* multirange -> element type functions */
+
+/* extract lower bound value */
+Datum
+multirange_lower(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+	bool		isnull;
+	Datum		result;
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_NULL();
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(typcache->rngtype, mr, &range_count, &ranges);
+
+	result = range_lower_internal(typcache->rngtype, ranges[0], &isnull);
+
+	if (isnull)
+		PG_RETURN_NULL();
+	else
+		PG_RETURN_DATUM(result);
+}
+
+/* extract upper bound value */
+Datum
+multirange_upper(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+	bool		isnull;
+	Datum		result;
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_NULL();
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(typcache->rngtype, mr, &range_count, &ranges);
+
+	result = range_upper_internal(typcache->rngtype, ranges[range_count - 1], &isnull);
+
+	if (isnull)
+		PG_RETURN_NULL();
+	else
+		PG_RETURN_DATUM(result);
+}
+
+
+/* multirange -> bool functions */
+
+/* is multirange empty? */
+Datum
+multirange_empty(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+
+	PG_RETURN_BOOL(mr->rangeCount == 0);
+}
+
+/* is lower bound inclusive? */
+Datum
+multirange_lower_inc(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+	multirange_deserialize(typcache->rngtype, mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_has_flag(ranges[0], RANGE_LB_INC));
+}
+
+/* is upper bound inclusive? */
+Datum
+multirange_upper_inc(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+	multirange_deserialize(typcache->rngtype, mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_has_flag(ranges[range_count - 1], RANGE_UB_INC));
+}
+
+/* is lower bound infinite? */
+Datum
+multirange_lower_inf(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+	multirange_deserialize(typcache->rngtype, mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_has_flag(ranges[0], RANGE_LB_INF));
+}
+
+/* is upper bound infinite? */
+Datum
+multirange_upper_inf(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+	multirange_deserialize(typcache->rngtype, mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_has_flag(ranges[range_count - 1], RANGE_UB_INF));
+}
+
+
+
+/* multirange, element -> bool functions */
+
+/* contains? */
+Datum
+multirange_contains_elem(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	Datum		val = PG_GETARG_DATUM(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(multirange_contains_elem_internal(typcache, mr, val));
+}
+
+/* contained by? */
+Datum
+elem_contained_by_multirange(PG_FUNCTION_ARGS)
+{
+	Datum		val = PG_GETARG_DATUM(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(multirange_contains_elem_internal(typcache, mr, val));
+}
+
+/*
+ * Test whether multirange mr contains a specific element value.
+ */
+bool
+multirange_contains_elem_internal(TypeCacheEntry *typcache, MultirangeType *mr, Datum val)
+{
+	TypeCacheEntry *rangetyp;
+	int32		range_count;
+	RangeType **ranges;
+	RangeType  *r;
+	int			i;
+
+	rangetyp = typcache->rngtype;
+
+	multirange_deserialize(rangetyp, mr, &range_count, &ranges);
+
+	for (i = 0; i < range_count; i++)
+	{
+		r = ranges[i];
+		if (range_contains_elem_internal(rangetyp, r, val))
+			return true;
+	}
+
+	return false;
+}
+
+/* multirange, range -> bool functions */
+
+/* contains? */
+Datum
+multirange_contains_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(multirange_contains_range_internal(typcache, mr, r));
+}
+
+/* contained by? */
+Datum
+range_contained_by_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(multirange_contains_range_internal(typcache, mr, r));
+}
+
+/*
+ * Test whether multirange mr contains a specific range r.
+ */
+bool
+multirange_contains_range_internal(TypeCacheEntry *typcache, MultirangeType *mr, RangeType *r)
+{
+	TypeCacheEntry *rangetyp;
+	int32		range_count;
+	RangeType **ranges;
+	RangeType  *mrr;
+	int			i;
+
+	rangetyp = typcache->rngtype;
+
+	/*
+	 * Every multirange contains an infinite number of empty ranges, even an
+	 * empty one.
+	 */
+	if (RangeIsEmpty(r))
+		return true;
+
+	multirange_deserialize(rangetyp, mr, &range_count, &ranges);
+
+	for (i = 0; i < range_count; i++)
+	{
+		mrr = ranges[i];
+		if (range_contains_internal(rangetyp, mrr, r))
+			return true;
+	}
+
+	return false;
+}
+
+
+/* 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(typcache->rngtype, mr1, &range_count_1, &ranges1);
+	multirange_deserialize(typcache->rngtype, 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));
+}
+
+/* overlaps? */
+Datum
+range_overlaps_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(range_overlaps_multirange_internal(typcache, r, mr));
+}
+
+Datum
+multirange_overlaps_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(range_overlaps_multirange_internal(typcache, r, mr));
+}
+
+Datum
+multirange_overlaps_multirange(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_overlaps_multirange_internal(typcache, mr1, mr2));
+}
+
+bool
+range_overlaps_multirange_internal(TypeCacheEntry *typcache, RangeType *r, MultirangeType *mr)
+{
+	TypeCacheEntry *rangetyp;
+	int32		range_count;
+	int32		i;
+	RangeType **ranges;
+	RangeType  *mrr;
+
+	/*
+	 * Empties never overlap, even with empties. (This seems strange since
+	 * they *do* contain each other, but we want to follow how ranges work.)
+	 */
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		return false;
+
+	rangetyp = typcache->rngtype;
+
+	multirange_deserialize(rangetyp, mr, &range_count, &ranges);
+
+	/* Scan through mrr and once it gets to r they either overlap or not */
+	mrr = ranges[0];
+
+	/* Discard mrrs while mrr << r */
+	i = 0;
+	while (range_before_internal(rangetyp, mrr, r))
+	{
+		if (++i >= range_count)
+			return false;
+		mrr = ranges[i];
+	}
+
+	/* Now either we overlap or we passed r */
+	return range_overlaps_internal(rangetyp, mrr, r);
+}
+
+bool
+multirange_overlaps_multirange_internal(TypeCacheEntry *typcache, MultirangeType *mr1,
+										MultirangeType *mr2)
+{
+	TypeCacheEntry *rangetyp;
+	int32		range_count1;
+	int32		range_count2;
+	int32		i1;
+	int32		i2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+	RangeType  *r1;
+	RangeType  *r2;
+
+	/*
+	 * Empties never overlap, even with empties. (This seems strange since
+	 * they *do* contain each other, but we want to follow how ranges work.)
+	 */
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		return false;
+
+	rangetyp = typcache->rngtype;
+
+	multirange_deserialize(rangetyp, mr1, &range_count1, &ranges1);
+	multirange_deserialize(rangetyp, mr2, &range_count2, &ranges2);
+
+	/*
+	 * Every range in mr1 gets a chance to overlap with the ranges in mr2, but
+	 * we can use their ordering to avoid O(n^2). This is similar to
+	 * range_overlaps_multirange where r1 : r2 :: mrr : r, but there if we
+	 * don't find an overlap with r we're done, and here if we don't find an
+	 * overlap with r2 we try the next r2.
+	 */
+	r1 = ranges1[0];
+	for (i1 = 0, i2 = 0; i2 < range_count2; i2++)
+	{
+		r2 = ranges2[i2];
+
+		/* Discard r1s while r1 << r2 */
+		while (range_before_internal(rangetyp, r1, r2))
+		{
+			if (++i1 >= range_count1)
+				return false;
+			r1 = ranges1[i1];
+		}
+
+		/*
+		 * If r1 && r2, we're done, otherwise we failed to find an overlap for
+		 * r2, so go to the next one.
+		 */
+		if (range_overlaps_internal(rangetyp, r1, r2))
+			return true;
+	}
+
+	/* We looked through all of mr2 without finding an overlap */
+	return false;
+}
+
+/* does not extend to right of? */
+Datum
+range_overleft_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(typcache->rngtype, mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_overleft_internal(typcache->rngtype, r, ranges[range_count - 1]));
+}
+
+Datum
+multirange_overleft_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(typcache->rngtype, mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_overleft_internal(typcache->rngtype,
+										   ranges[range_count - 1], r));
+}
+
+Datum
+multirange_overleft_multirange(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
+	MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr1));
+
+	multirange_deserialize(typcache->rngtype, mr1, &range_count1, &ranges1);
+	multirange_deserialize(typcache->rngtype, mr2, &range_count2, &ranges2);
+
+	PG_RETURN_BOOL(range_overleft_internal(typcache->rngtype,
+										   ranges1[range_count1 - 1],
+										   ranges2[range_count2 - 1]));
+}
+
+/* does not extend to left of? */
+Datum
+range_overright_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(typcache->rngtype, mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_overright_internal(typcache->rngtype, r, ranges[0]));
+}
+
+Datum
+multirange_overright_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(typcache->rngtype, mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_overright_internal(typcache->rngtype, ranges[0], r));
+}
+
+Datum
+multirange_overright_multirange(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
+	MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr1));
+
+	multirange_deserialize(typcache->rngtype, mr1, &range_count1, &ranges1);
+	multirange_deserialize(typcache->rngtype, mr2, &range_count2, &ranges2);
+
+	PG_RETURN_BOOL(range_overright_internal(typcache->rngtype,
+											ranges1[0],
+											ranges2[0]));
+}
+
+/* contains? */
+Datum
+multirange_contains_multirange(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_contains_multirange_internal(typcache, mr1, mr2));
+}
+
+/* contained by? */
+Datum
+multirange_contained_by_multirange(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_contains_multirange_internal(typcache, mr2, mr1));
+}
+
+/*
+ * Test whether multirange mr1 contains every range from another multirange mr2.
+ */
+bool
+multirange_contains_multirange_internal(TypeCacheEntry *typcache,
+										MultirangeType *mr1, MultirangeType *mr2)
+{
+	TypeCacheEntry *rangetyp;
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+	RangeType  *r1;
+	RangeType  *r2;
+	int			i1,
+				i2;
+
+	rangetyp = typcache->rngtype;
+
+	multirange_deserialize(typcache->rngtype, mr1, &range_count1, &ranges1);
+	multirange_deserialize(typcache->rngtype, mr2, &range_count2, &ranges2);
+
+	/*
+	 * We follow the same logic for empties as ranges: - an empty multirange
+	 * contains an empty range/multirange. - an empty multirange can't contain
+	 * any other range/multirange. - an empty multirange is contained by any
+	 * other range/multirange.
+	 */
+
+	if (range_count2 == 0)
+		return true;
+	if (range_count1 == 0)
+		return false;
+
+	/*
+	 * Every range in mr2 must be contained by some range in mr1. To avoid
+	 * O(n^2) we walk through both ranges in tandem.
+	 */
+	r1 = ranges1[0];
+	for (i1 = 0, i2 = 0; i2 < range_count2; i2++)
+	{
+		r2 = ranges2[i2];
+
+		/* Discard r1s while r1 << r2 */
+		while (range_before_internal(rangetyp, r1, r2))
+		{
+			if (++i1 >= range_count1)
+				return false;
+			r1 = ranges1[i1];
+		}
+
+		/*
+		 * If r1 @> r2, go to the next r2, otherwise return false (since every
+		 * r1[n] and r1[n+1] must have a gap). Note this will give weird
+		 * answers if you don't canonicalize, e.g. with a custom
+		 * int2multirange {[1,1], [2,2]} there is a "gap". But that is
+		 * consistent with other range operators, e.g. '[1,1]'::int2range -|-
+		 * '[2,2]'::int2range is false.
+		 */
+		if (!range_contains_internal(rangetyp, r1, r2))
+			return false;
+	}
+
+	/* All ranges in mr2 are satisfied */
+	return true;
+}
+
+/* strictly left of? */
+Datum
+range_before_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(range_before_multirange_internal(typcache, r, mr));
+}
+
+Datum
+multirange_before_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(range_after_multirange_internal(typcache, r, mr));
+}
+
+Datum
+multirange_before_multirange(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_before_multirange_internal(typcache, mr1, mr2));
+}
+
+/* strictly right of? */
+Datum
+range_after_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(range_after_multirange_internal(typcache, r, mr));
+}
+
+Datum
+multirange_after_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(range_before_multirange_internal(typcache, r, mr));
+}
+
+Datum
+multirange_after_multirange(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_before_multirange_internal(typcache, mr2, mr1));
+}
+
+/* strictly left of? (internal version) */
+bool
+range_before_multirange_internal(TypeCacheEntry *typcache, RangeType *r,
+								 MultirangeType *mr)
+{
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		return false;
+
+	multirange_deserialize(typcache->rngtype, mr, &range_count, &ranges);
+
+	return range_before_internal(typcache->rngtype, r, ranges[0]);
+}
+
+bool
+multirange_before_multirange_internal(TypeCacheEntry *typcache, MultirangeType *mr1,
+									  MultirangeType *mr2)
+{
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		return false;
+
+	multirange_deserialize(typcache->rngtype, mr1, &range_count1, &ranges1);
+	multirange_deserialize(typcache->rngtype, mr2, &range_count2, &ranges2);
+
+	return range_before_internal(typcache->rngtype, ranges1[range_count1 - 1],
+								 ranges2[0]);
+}
+
+/* strictly right of? (internal version) */
+bool
+range_after_multirange_internal(TypeCacheEntry *typcache, RangeType *r,
+								MultirangeType *mr)
+{
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		return false;
+
+	multirange_deserialize(typcache->rngtype, mr, &range_count, &ranges);
+
+	return range_after_internal(typcache->rngtype, r, ranges[range_count - 1]);
+}
+
+/* adjacent to? */
+Datum
+range_adjacent_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		return false;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(typcache->rngtype, mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_adjacent_internal(typcache->rngtype, r, ranges[0]));
+}
+
+Datum
+multirange_adjacent_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		return false;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(typcache->rngtype, mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_adjacent_internal(typcache->rngtype, ranges[range_count - 1], r));
+}
+
+Datum
+multirange_adjacent_multirange(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
+	MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		return false;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr1));
+
+	multirange_deserialize(typcache->rngtype, mr1, &range_count1, &ranges1);
+	multirange_deserialize(typcache->rngtype, mr2, &range_count2, &ranges2);
+
+	PG_RETURN_BOOL(range_adjacent_internal(typcache->rngtype, ranges1[range_count1 - 1], ranges2[0]));
+}
+
+/* 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(typcache->rngtype, mr1, &range_count_1, &ranges1);
+	multirange_deserialize(typcache->rngtype, 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);
+}
+
+/* multirange -> range functions */
+
+/* Find the smallest range that includes everything in the multirange */
+Datum
+range_merge_from_multirange(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	Oid			mltrngtypoid = MultirangeTypeGetOid(mr);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_RANGE_P(make_empty_range(typcache->rngtype));
+
+	multirange_deserialize(typcache->rngtype, mr, &range_count, &ranges);
+
+	PG_RETURN_RANGE_P(range_union_internal(typcache->rngtype, ranges[0],
+										   ranges[range_count - 1], false));
+}
+
+/* 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(typcache->rngtype, 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(typcache->rngtype, 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/multirangetypes_selfuncs.c b/src/backend/utils/adt/multirangetypes_selfuncs.c
new file mode 100644
index 0000000000..ff0a81a49a
--- /dev/null
+++ b/src/backend/utils/adt/multirangetypes_selfuncs.c
@@ -0,0 +1,1297 @@
+/*-------------------------------------------------------------------------
+ *
+ * multirangetypes_selfuncs.c
+ *	  Functions for selectivity estimation of multirange operators
+ *
+ * Estimates are based on histograms of lower and upper bounds, and the
+ * fraction of empty multiranges.
+ *
+ * Portions Copyright (c) 1996-2020, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ *	  src/backend/utils/adt/multirangetypes_selfuncs.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include <math.h>
+
+#include "access/htup_details.h"
+#include "catalog/pg_operator.h"
+#include "catalog/pg_statistic.h"
+#include "catalog/pg_type.h"
+#include "utils/float.h"
+#include "utils/fmgrprotos.h"
+#include "utils/lsyscache.h"
+#include "utils/rangetypes.h"
+#include "utils/multirangetypes.h"
+#include "utils/selfuncs.h"
+#include "utils/typcache.h"
+
+static double calc_multirangesel(TypeCacheEntry *typcache, VariableStatData *vardata,
+							const MultirangeType *constval, Oid operator);
+static double default_multirange_selectivity(Oid operator);
+static double default_multirange_selectivity(Oid operator);
+static double calc_hist_selectivity(TypeCacheEntry *typcache,
+									VariableStatData *vardata, const MultirangeType *constval,
+									Oid operator);
+static double calc_hist_selectivity_scalar(TypeCacheEntry *typcache,
+										   const RangeBound *constbound,
+										   const RangeBound *hist, int hist_nvalues,
+										   bool equal);
+static int	rbound_bsearch(TypeCacheEntry *typcache, const RangeBound *value,
+						   const RangeBound *hist, int hist_length, bool equal);
+static float8 get_position(TypeCacheEntry *typcache, const RangeBound *value,
+						   const RangeBound *hist1, const RangeBound *hist2);
+static float8 get_len_position(double value, double hist1, double hist2);
+static float8 get_distance(TypeCacheEntry *typcache, const RangeBound *bound1,
+						   const RangeBound *bound2);
+static int	length_hist_bsearch(Datum *length_hist_values,
+								int length_hist_nvalues, double value, bool equal);
+static double calc_length_hist_frac(Datum *length_hist_values,
+									int length_hist_nvalues, double length1, double length2, bool equal);
+static double calc_hist_selectivity_contained(TypeCacheEntry *typcache,
+											  const RangeBound *lower, RangeBound *upper,
+											  const RangeBound *hist_lower, int hist_nvalues,
+											  Datum *length_hist_values, int length_hist_nvalues);
+static double calc_hist_selectivity_contains(TypeCacheEntry *typcache,
+											 const RangeBound *lower, const RangeBound *upper,
+											 const RangeBound *hist_lower, int hist_nvalues,
+											 Datum *length_hist_values, int length_hist_nvalues);
+
+/*
+ * Returns a default selectivity estimate for given operator, when we don't
+ * have statistics or cannot use them for some reason.
+ */
+static double
+default_multirange_selectivity(Oid operator)
+{
+	switch (operator)
+	{
+		case OID_MULTIRANGE_OVERLAPS_MULTIRANGE_OP:
+		case OID_MULTIRANGE_OVERLAPS_RANGE_OP:
+		case OID_RANGE_OVERLAPS_MULTIRANGE_OP:
+			return 0.01;
+
+		case OID_MULTIRANGE_CONTAINS_RANGE_OP:
+		case OID_MULTIRANGE_CONTAINS_MULTIRANGE_OP:
+		case OID_MULTIRANGE_RANGE_CONTAINED_OP:
+		case OID_MULTIRANGE_MULTIRANGE_CONTAINED_OP:
+			return 0.005;
+
+		case OID_MULTIRANGE_CONTAINS_ELEM_OP:
+		case OID_MULTIRANGE_ELEM_CONTAINED_OP:
+
+			/*
+			 * "multirange @> elem" is more or less identical to a scalar
+			 * inequality "A >= b AND A <= c".
+			 */
+			return DEFAULT_MULTIRANGE_INEQ_SEL;
+
+		case OID_MULTIRANGE_LESS_OP:
+		case OID_MULTIRANGE_LESS_EQUAL_OP:
+		case OID_MULTIRANGE_GREATER_OP:
+		case OID_MULTIRANGE_GREATER_EQUAL_OP:
+		case OID_MULTIRANGE_LEFT_RANGE_OP:
+		case OID_MULTIRANGE_LEFT_MULTIRANGE_OP:
+		case OID_RANGE_LEFT_MULTIRANGE_OP:
+		case OID_MULTIRANGE_RIGHT_RANGE_OP:
+		case OID_MULTIRANGE_RIGHT_MULTIRANGE_OP:
+		case OID_RANGE_RIGHT_MULTIRANGE_OP:
+		case OID_MULTIRANGE_OVERLAPS_LEFT_RANGE_OP:
+		case OID_RANGE_OVERLAPS_LEFT_MULTIRANGE_OP:
+		case OID_MULTIRANGE_OVERLAPS_LEFT_MULTIRANGE_OP:
+		case OID_MULTIRANGE_OVERLAPS_RIGHT_RANGE_OP:
+		case OID_RANGE_OVERLAPS_RIGHT_MULTIRANGE_OP:
+		case OID_MULTIRANGE_OVERLAPS_RIGHT_MULTIRANGE_OP:
+			/* these are similar to regular scalar inequalities */
+			return DEFAULT_INEQ_SEL;
+
+		default:
+			/* all multirange operators should be handled above, but just in case */
+			return 0.01;
+	}
+}
+
+/*
+ * multirangesel -- restriction selectivity for multirange operators
+ */
+Datum
+multirangesel(PG_FUNCTION_ARGS)
+{
+	PlannerInfo *root = (PlannerInfo *) PG_GETARG_POINTER(0);
+	Oid			operator = PG_GETARG_OID(1);
+	List	   *args = (List *) PG_GETARG_POINTER(2);
+	int			varRelid = PG_GETARG_INT32(3);
+	VariableStatData vardata;
+	Node	   *other;
+	bool		varonleft;
+	Selectivity selec;
+	TypeCacheEntry *typcache = NULL;
+	MultirangeType *constmultirange = NULL;
+	RangeType  *constrange = NULL;
+
+	/*
+	 * If expression is not (variable op something) or (something op
+	 * variable), then punt and return a default estimate.
+	 */
+	if (!get_restriction_variable(root, args, varRelid,
+								  &vardata, &other, &varonleft))
+		PG_RETURN_FLOAT8(default_multirange_selectivity(operator));
+
+	/*
+	 * Can't do anything useful if the something is not a constant, either.
+	 */
+	if (!IsA(other, Const))
+	{
+		ReleaseVariableStats(vardata);
+		PG_RETURN_FLOAT8(default_multirange_selectivity(operator));
+	}
+
+	/*
+	 * All the multirange operators are strict, so we can cope with a NULL constant
+	 * right away.
+	 */
+	if (((Const *) other)->constisnull)
+	{
+		ReleaseVariableStats(vardata);
+		PG_RETURN_FLOAT8(0.0);
+	}
+
+	/*
+	 * If var is on the right, commute the operator, so that we can assume the
+	 * var is on the left in what follows.
+	 */
+	if (!varonleft)
+	{
+		/* we have other Op var, commute to make var Op other */
+		operator = get_commutator(operator);
+		if (!operator)
+		{
+			/* Use default selectivity (should we raise an error instead?) */
+			ReleaseVariableStats(vardata);
+			PG_RETURN_FLOAT8(default_multirange_selectivity(operator));
+		}
+	}
+
+	/*
+	 * OK, there's a Var and a Const we're dealing with here.  We need the
+	 * Const to be of same multirange type as the column, else we can't do anything
+	 * useful. (Such cases will likely fail at runtime, but here we'd rather
+	 * just return a default estimate.)
+	 *
+	 * If the operator is "multirange @> element", the constant should be of the
+	 * element type of the multirange column. Convert it to a multirange that includes
+	 * only that single point, so that we don't need special handling for that
+	 * in what follows.
+	 */
+	if (operator == OID_MULTIRANGE_CONTAINS_ELEM_OP)
+	{
+		typcache = multirange_get_typcache(fcinfo, vardata.vartype);
+
+		if (((Const *) other)->consttype == typcache->rngtype->rngelemtype->type_id)
+		{
+			RangeBound	lower,
+						upper;
+
+			lower.inclusive = true;
+			lower.val = ((Const *) other)->constvalue;
+			lower.infinite = false;
+			lower.lower = true;
+			upper.inclusive = true;
+			upper.val = ((Const *) other)->constvalue;
+			upper.infinite = false;
+			upper.lower = false;
+			constrange = range_serialize(typcache->rngtype, &lower, &upper, false);
+			constmultirange = make_multirange(typcache->type_id, typcache->rngtype,
+					1, &constrange);
+		}
+	}
+	else if (operator == OID_MULTIRANGE_CONTAINS_RANGE_OP ||
+			 operator == OID_MULTIRANGE_OVERLAPS_RANGE_OP ||
+			 operator == OID_MULTIRANGE_OVERLAPS_LEFT_RANGE_OP ||
+			 operator == OID_MULTIRANGE_OVERLAPS_RIGHT_RANGE_OP ||
+			 operator == OID_MULTIRANGE_LEFT_RANGE_OP ||
+			 operator == OID_MULTIRANGE_RIGHT_RANGE_OP)
+	{
+		/*
+		 * Promote a range in "multirange OP range" just like
+		 * we do an element in "multirange OP element".
+		 */
+		typcache = multirange_get_typcache(fcinfo, vardata.vartype);
+		if (((Const *) other)->consttype == typcache->rngtype->type_id)
+		{
+			constrange = DatumGetRangeTypeP(((Const *) other)->constvalue);
+			constmultirange = make_multirange(typcache->type_id, typcache->rngtype,
+					1, &constrange);
+		}
+	}
+	else if (operator == OID_RANGE_OVERLAPS_MULTIRANGE_OP ||
+			 operator == OID_RANGE_OVERLAPS_LEFT_MULTIRANGE_OP ||
+			 operator == OID_RANGE_OVERLAPS_RIGHT_MULTIRANGE_OP ||
+			 operator == OID_RANGE_LEFT_MULTIRANGE_OP ||
+			 operator == OID_RANGE_RIGHT_MULTIRANGE_OP ||
+			 operator == OID_MULTIRANGE_ELEM_CONTAINED_OP ||
+			 operator == OID_MULTIRANGE_RANGE_CONTAINED_OP)
+	{
+		/*
+		 * Here, the Var is the elem/range, not the multirange.  For now we just punt and
+		 * return the default estimate.  In future we could disassemble the
+		 * multirange constant to do something more intelligent.
+		 */
+	}
+	else if (((Const *) other)->consttype == vardata.vartype)
+	{
+		/* Both sides are the same multirange type */
+		typcache = multirange_get_typcache(fcinfo, vardata.vartype);
+
+		constmultirange = DatumGetMultirangeTypeP(((Const *) other)->constvalue);
+	}
+
+	/*
+	 * If we got a valid constant on one side of the operator, proceed to
+	 * estimate using statistics. Otherwise punt and return a default constant
+	 * estimate.  Note that calc_multirangesel need not handle
+	 * OID_MULTIRANGE_*_CONTAINED_OP.
+	 */
+	if (constmultirange)
+		selec = calc_multirangesel(typcache, &vardata, constmultirange, operator);
+	else
+		selec = default_multirange_selectivity(operator);
+
+	ReleaseVariableStats(vardata);
+
+	CLAMP_PROBABILITY(selec);
+
+	PG_RETURN_FLOAT8((float8) selec);
+}
+
+static double
+calc_multirangesel(TypeCacheEntry *typcache, VariableStatData *vardata,
+			  const MultirangeType *constval, Oid operator)
+{
+	double		hist_selec;
+	double		selec;
+	float4		empty_frac,
+				null_frac;
+
+	/*
+	 * First look up the fraction of NULLs and empty multiranges from pg_statistic.
+	 */
+	if (HeapTupleIsValid(vardata->statsTuple))
+	{
+		Form_pg_statistic stats;
+		AttStatsSlot sslot;
+
+		stats = (Form_pg_statistic) GETSTRUCT(vardata->statsTuple);
+		null_frac = stats->stanullfrac;
+
+		/* Try to get fraction of empty multiranges */
+		if (get_attstatsslot(&sslot, vardata->statsTuple,
+							 STATISTIC_KIND_RANGE_LENGTH_HISTOGRAM,
+							 InvalidOid,
+							 ATTSTATSSLOT_NUMBERS))
+		{
+			if (sslot.nnumbers != 1)
+				elog(ERROR, "invalid empty fraction statistic");	/* shouldn't happen */
+			empty_frac = sslot.numbers[0];
+			free_attstatsslot(&sslot);
+		}
+		else
+		{
+			/* No empty fraction statistic. Assume no empty ranges. */
+			empty_frac = 0.0;
+		}
+	}
+	else
+	{
+		/*
+		 * No stats are available. Follow through the calculations below
+		 * anyway, assuming no NULLs and no empty multiranges. This still allows us
+		 * to give a better-than-nothing estimate based on whether the
+		 * constant is an empty multirange or not.
+		 */
+		null_frac = 0.0;
+		empty_frac = 0.0;
+	}
+
+	if (MultirangeIsEmpty(constval))
+	{
+		/*
+		 * An empty multirange matches all multiranges, all empty multiranges, or
+		 * nothing, depending on the operator
+		 */
+		switch (operator)
+		{
+				/* these return false if either argument is empty */
+			case OID_RANGE_OVERLAPS_MULTIRANGE_OP:
+			case OID_MULTIRANGE_OVERLAPS_RANGE_OP:
+			case OID_MULTIRANGE_OVERLAPS_MULTIRANGE_OP:
+			case OID_RANGE_OVERLAPS_LEFT_MULTIRANGE_OP:
+			case OID_MULTIRANGE_OVERLAPS_LEFT_RANGE_OP:
+			case OID_MULTIRANGE_OVERLAPS_LEFT_MULTIRANGE_OP:
+			case OID_RANGE_OVERLAPS_RIGHT_MULTIRANGE_OP:
+			case OID_MULTIRANGE_OVERLAPS_RIGHT_RANGE_OP:
+			case OID_MULTIRANGE_OVERLAPS_RIGHT_MULTIRANGE_OP:
+			case OID_MULTIRANGE_LEFT_MULTIRANGE_OP:
+			case OID_MULTIRANGE_RIGHT_MULTIRANGE_OP:
+				/* nothing is less than an empty multirange */
+			case OID_MULTIRANGE_LESS_OP:
+				selec = 0.0;
+				break;
+
+				/* only empty multiranges can be contained by an empty multirange */
+			case OID_MULTIRANGE_RANGE_CONTAINED_OP:
+			case OID_MULTIRANGE_MULTIRANGE_CONTAINED_OP:
+				/* only empty ranges are <= an empty multirange */
+			case OID_MULTIRANGE_LESS_EQUAL_OP:
+				selec = empty_frac;
+				break;
+
+				/* everything contains an empty multirange */
+			case OID_MULTIRANGE_CONTAINS_RANGE_OP:
+			case OID_MULTIRANGE_CONTAINS_MULTIRANGE_OP:
+				/* everything is >= an empty multirange */
+			case OID_MULTIRANGE_GREATER_EQUAL_OP:
+				selec = 1.0;
+				break;
+
+				/* all non-empty multiranges are > an empty multirange */
+			case OID_MULTIRANGE_GREATER_OP:
+				selec = 1.0 - empty_frac;
+				break;
+
+				/* an element cannot be empty */
+			case OID_MULTIRANGE_ELEM_CONTAINED_OP:
+			case OID_MULTIRANGE_CONTAINS_ELEM_OP:
+			default:
+				elog(ERROR, "unexpected operator %u", operator);
+				selec = 0.0;	/* keep compiler quiet */
+				break;
+		}
+	}
+	else
+	{
+		/*
+		 * Calculate selectivity using bound histograms. If that fails for
+		 * some reason, e.g no histogram in pg_statistic, use the default
+		 * constant estimate for the fraction of non-empty values. This is
+		 * still somewhat better than just returning the default estimate,
+		 * because this still takes into account the fraction of empty and
+		 * NULL tuples, if we had statistics for them.
+		 */
+		hist_selec = calc_hist_selectivity(typcache, vardata, constval,
+										   operator);
+		if (hist_selec < 0.0)
+			hist_selec = default_multirange_selectivity(operator);
+
+		/*
+		 * Now merge the results for the empty multiranges and histogram
+		 * calculations, realizing that the histogram covers only the
+		 * non-null, non-empty values.
+		 */
+		if (operator == OID_MULTIRANGE_ELEM_CONTAINED_OP ||
+			operator == OID_MULTIRANGE_RANGE_CONTAINED_OP ||
+			operator == OID_MULTIRANGE_MULTIRANGE_CONTAINED_OP)
+		{
+			/* empty is contained by anything non-empty */
+			selec = (1.0 - empty_frac) * hist_selec + empty_frac;
+		}
+		else
+		{
+			/* with any other operator, empty Op non-empty matches nothing */
+			selec = (1.0 - empty_frac) * hist_selec;
+		}
+	}
+
+	/* all multirange operators are strict */
+	selec *= (1.0 - null_frac);
+
+	/* result should be in range, but make sure... */
+	CLAMP_PROBABILITY(selec);
+
+	return selec;
+}
+
+/*
+ * Calculate multirange operator selectivity using histograms of multirange bounds.
+ *
+ * This estimate is for the portion of values that are not empty and not
+ * NULL.
+ */
+static double
+calc_hist_selectivity(TypeCacheEntry *typcache, VariableStatData *vardata,
+					  const MultirangeType *constval, Oid operator)
+{
+	TypeCacheEntry *rng_typcache = typcache->rngtype;
+	AttStatsSlot	hslot;
+	AttStatsSlot	lslot;
+	int			nhist;
+	RangeBound *hist_lower;
+	RangeBound *hist_upper;
+	int			i;
+	RangeBound	const_lower;
+	RangeBound	const_upper;
+	bool		empty;
+	double		hist_selec;
+	int			range_count;
+	RangeType **ranges;
+	RangeType  *constrange;
+
+	/* Can't use the histogram with insecure multirange support functions */
+	if (!statistic_proc_security_check(vardata,
+									   rng_typcache->rng_cmp_proc_finfo.fn_oid))
+		return -1;
+	if (OidIsValid(rng_typcache->rng_subdiff_finfo.fn_oid) &&
+		!statistic_proc_security_check(vardata,
+									   rng_typcache->rng_subdiff_finfo.fn_oid))
+		return -1;
+
+	/* Try to get histogram of ranges */
+	if (!(HeapTupleIsValid(vardata->statsTuple) &&
+		  get_attstatsslot(&hslot, vardata->statsTuple,
+						   STATISTIC_KIND_BOUNDS_HISTOGRAM, InvalidOid,
+						   ATTSTATSSLOT_VALUES)))
+		return -1.0;
+
+	/* check that it's a histogram, not just a dummy entry */
+	if (hslot.nvalues < 2)
+	{
+		free_attstatsslot(&hslot);
+		return -1.0;
+	}
+
+	/*
+	 * Convert histogram of ranges into histograms of its lower and upper
+	 * bounds.
+	 */
+	nhist = hslot.nvalues;
+	hist_lower = (RangeBound *) palloc(sizeof(RangeBound) * nhist);
+	hist_upper = (RangeBound *) palloc(sizeof(RangeBound) * nhist);
+	for (i = 0; i < nhist; i++)
+	{
+		range_deserialize(rng_typcache, DatumGetRangeTypeP(hslot.values[i]),
+						  &hist_lower[i], &hist_upper[i], &empty);
+		/* The histogram should not contain any empty ranges */
+		if (empty)
+			elog(ERROR, "bounds histogram contains an empty range");
+	}
+
+	/* @> and @< also need a histogram of range lengths */
+	if (operator == OID_MULTIRANGE_CONTAINS_RANGE_OP ||
+		operator == OID_MULTIRANGE_CONTAINS_MULTIRANGE_OP ||
+		operator == OID_MULTIRANGE_RANGE_CONTAINED_OP ||
+		operator == OID_MULTIRANGE_MULTIRANGE_CONTAINED_OP)
+	{
+		if (!(HeapTupleIsValid(vardata->statsTuple) &&
+			  get_attstatsslot(&lslot, vardata->statsTuple,
+							   STATISTIC_KIND_RANGE_LENGTH_HISTOGRAM,
+							   InvalidOid,
+							   ATTSTATSSLOT_VALUES)))
+		{
+			free_attstatsslot(&hslot);
+			return -1.0;
+		}
+
+		/* check that it's a histogram, not just a dummy entry */
+		if (lslot.nvalues < 2)
+		{
+			free_attstatsslot(&lslot);
+			free_attstatsslot(&hslot);
+			return -1.0;
+		}
+	}
+	else
+		memset(&lslot, 0, sizeof(lslot));
+
+	/* Extract the bounds of the constant value. */
+	multirange_deserialize(rng_typcache, constval, &range_count, &ranges);
+	Assert(range_count > 0);
+	constrange = range_union_internal(rng_typcache, ranges[0],
+			ranges[range_count - 1], false);
+	range_deserialize(rng_typcache, constrange, &const_lower, &const_upper, &empty);
+
+	/*
+	 * Calculate selectivity comparing the lower or upper bound of the
+	 * constant with the histogram of lower or upper bounds.
+	 */
+	switch (operator)
+	{
+		case OID_MULTIRANGE_LESS_OP:
+
+			/*
+			 * The regular b-tree comparison operators (<, <=, >, >=) compare
+			 * the lower bounds first, and the upper bounds for values with
+			 * equal lower bounds. Estimate that by comparing the lower bounds
+			 * only. This gives a fairly accurate estimate assuming there
+			 * aren't many rows with a lower bound equal to the constant's
+			 * lower bound.
+			 */
+			hist_selec =
+				calc_hist_selectivity_scalar(rng_typcache, &const_lower,
+											 hist_lower, nhist, false);
+			break;
+
+		case OID_MULTIRANGE_LESS_EQUAL_OP:
+			hist_selec =
+				calc_hist_selectivity_scalar(rng_typcache, &const_lower,
+											 hist_lower, nhist, true);
+			break;
+
+		case OID_MULTIRANGE_GREATER_OP:
+			hist_selec =
+				1 - calc_hist_selectivity_scalar(rng_typcache, &const_lower,
+												 hist_lower, nhist, false);
+			break;
+
+		case OID_MULTIRANGE_GREATER_EQUAL_OP:
+			hist_selec =
+				1 - calc_hist_selectivity_scalar(rng_typcache, &const_lower,
+												 hist_lower, nhist, true);
+			break;
+
+		case OID_RANGE_LEFT_MULTIRANGE_OP:
+		case OID_MULTIRANGE_LEFT_RANGE_OP:
+		case OID_MULTIRANGE_LEFT_MULTIRANGE_OP:
+			/* var << const when upper(var) < lower(const) */
+			hist_selec =
+				calc_hist_selectivity_scalar(rng_typcache, &const_lower,
+											 hist_upper, nhist, false);
+			break;
+
+		case OID_RANGE_RIGHT_MULTIRANGE_OP:
+		case OID_MULTIRANGE_RIGHT_RANGE_OP:
+		case OID_MULTIRANGE_RIGHT_MULTIRANGE_OP:
+			/* var >> const when lower(var) > upper(const) */
+			hist_selec =
+				1 - calc_hist_selectivity_scalar(rng_typcache, &const_upper,
+												 hist_lower, nhist, true);
+			break;
+
+		case OID_RANGE_OVERLAPS_RIGHT_MULTIRANGE_OP:
+		case OID_MULTIRANGE_OVERLAPS_RIGHT_RANGE_OP:
+		case OID_MULTIRANGE_OVERLAPS_RIGHT_MULTIRANGE_OP:
+			/* compare lower bounds */
+			hist_selec =
+				1 - calc_hist_selectivity_scalar(rng_typcache, &const_lower,
+												 hist_lower, nhist, false);
+			break;
+
+		case OID_RANGE_OVERLAPS_LEFT_MULTIRANGE_OP:
+		case OID_MULTIRANGE_OVERLAPS_LEFT_RANGE_OP:
+		case OID_MULTIRANGE_OVERLAPS_LEFT_MULTIRANGE_OP:
+			/* compare upper bounds */
+			hist_selec =
+				calc_hist_selectivity_scalar(rng_typcache, &const_upper,
+											 hist_upper, nhist, true);
+			break;
+
+		case OID_RANGE_OVERLAPS_MULTIRANGE_OP:
+		case OID_MULTIRANGE_OVERLAPS_RANGE_OP:
+		case OID_MULTIRANGE_OVERLAPS_MULTIRANGE_OP:
+		case OID_MULTIRANGE_CONTAINS_ELEM_OP:
+
+			/*
+			 * A && B <=> NOT (A << B OR A >> B).
+			 *
+			 * Since A << B and A >> B are mutually exclusive events we can
+			 * sum their probabilities to find probability of (A << B OR A >>
+			 * B).
+			 *
+			 * "multirange @> elem" is equivalent to "multirange && {[elem,elem]}".
+			 * The caller already constructed the singular range from the element
+			 * constant, so just treat it the same as &&.
+			 */
+			hist_selec =
+				calc_hist_selectivity_scalar(rng_typcache, &const_lower, hist_upper,
+											 nhist, false);
+			hist_selec +=
+				(1.0 - calc_hist_selectivity_scalar(rng_typcache, &const_upper, hist_lower,
+													nhist, true));
+			hist_selec = 1.0 - hist_selec;
+			break;
+
+		case OID_MULTIRANGE_CONTAINS_RANGE_OP:
+		case OID_MULTIRANGE_CONTAINS_MULTIRANGE_OP:
+			hist_selec =
+				calc_hist_selectivity_contains(rng_typcache, &const_lower,
+											   &const_upper, hist_lower, nhist,
+											   lslot.values, lslot.nvalues);
+			break;
+
+		case OID_MULTIRANGE_RANGE_CONTAINED_OP:
+		case OID_MULTIRANGE_MULTIRANGE_CONTAINED_OP:
+			if (const_lower.infinite)
+			{
+				/*
+				 * Lower bound no longer matters. Just estimate the fraction
+				 * with an upper bound <= const upper bound
+				 */
+				hist_selec =
+					calc_hist_selectivity_scalar(rng_typcache, &const_upper,
+												 hist_upper, nhist, true);
+			}
+			else if (const_upper.infinite)
+			{
+				hist_selec =
+					1.0 - calc_hist_selectivity_scalar(rng_typcache, &const_lower,
+													   hist_lower, nhist, false);
+			}
+			else
+			{
+				hist_selec =
+					calc_hist_selectivity_contained(rng_typcache, &const_lower,
+													&const_upper, hist_lower, nhist,
+													lslot.values, lslot.nvalues);
+			}
+			break;
+
+		default:
+			elog(ERROR, "unknown multirange operator %u", operator);
+			hist_selec = -1.0;	/* keep compiler quiet */
+			break;
+	}
+
+	free_attstatsslot(&lslot);
+	free_attstatsslot(&hslot);
+
+	return hist_selec;
+}
+
+
+/*
+ * Look up the fraction of values less than (or equal, if 'equal' argument
+ * is true) a given const in a histogram of range bounds.
+ */
+static double
+calc_hist_selectivity_scalar(TypeCacheEntry *typcache, const RangeBound *constbound,
+							 const RangeBound *hist, int hist_nvalues, bool equal)
+{
+	Selectivity selec;
+	int			index;
+
+	/*
+	 * Find the histogram bin the given constant falls into. Estimate
+	 * selectivity as the number of preceding whole bins.
+	 */
+	index = rbound_bsearch(typcache, constbound, hist, hist_nvalues, equal);
+	selec = (Selectivity) (Max(index, 0)) / (Selectivity) (hist_nvalues - 1);
+
+	/* Adjust using linear interpolation within the bin */
+	if (index >= 0 && index < hist_nvalues - 1)
+		selec += get_position(typcache, constbound, &hist[index],
+							  &hist[index + 1]) / (Selectivity) (hist_nvalues - 1);
+
+	return selec;
+}
+
+/*
+ * Binary search on an array of range bounds. Returns greatest index of range
+ * bound in array which is less(less or equal) than given range bound. If all
+ * range bounds in array are greater or equal(greater) than given range bound,
+ * return -1. When "equal" flag is set conditions in brackets are used.
+ *
+ * This function is used in scalar operator selectivity estimation. Another
+ * goal of this function is to find a histogram bin where to stop
+ * interpolation of portion of bounds which are less or equal to given bound.
+ */
+static int
+rbound_bsearch(TypeCacheEntry *typcache, const RangeBound *value, const RangeBound *hist,
+			   int hist_length, bool equal)
+{
+	int			lower = -1,
+				upper = hist_length - 1,
+				cmp,
+				middle;
+
+	while (lower < upper)
+	{
+		middle = (lower + upper + 1) / 2;
+		cmp = range_cmp_bounds(typcache, &hist[middle], value);
+
+		if (cmp < 0 || (equal && cmp == 0))
+			lower = middle;
+		else
+			upper = middle - 1;
+	}
+	return lower;
+}
+
+
+/*
+ * Binary search on length histogram. Returns greatest index of range length in
+ * histogram which is less than (less than or equal) the given length value. If
+ * all lengths in the histogram are greater than (greater than or equal) the
+ * given length, returns -1.
+ */
+static int
+length_hist_bsearch(Datum *length_hist_values, int length_hist_nvalues,
+					double value, bool equal)
+{
+	int			lower = -1,
+				upper = length_hist_nvalues - 1,
+				middle;
+
+	while (lower < upper)
+	{
+		double		middleval;
+
+		middle = (lower + upper + 1) / 2;
+
+		middleval = DatumGetFloat8(length_hist_values[middle]);
+		if (middleval < value || (equal && middleval <= value))
+			lower = middle;
+		else
+			upper = middle - 1;
+	}
+	return lower;
+}
+
+/*
+ * Get relative position of value in histogram bin in [0,1] range.
+ */
+static float8
+get_position(TypeCacheEntry *typcache, const RangeBound *value, const RangeBound *hist1,
+			 const RangeBound *hist2)
+{
+	bool		has_subdiff = OidIsValid(typcache->rng_subdiff_finfo.fn_oid);
+	float8		position;
+
+	if (!hist1->infinite && !hist2->infinite)
+	{
+		float8		bin_width;
+
+		/*
+		 * Both bounds are finite. Assuming the subtype's comparison function
+		 * works sanely, the value must be finite, too, because it lies
+		 * somewhere between the bounds.  If it doesn't, arbitrarily return
+		 * 0.5.
+		 */
+		if (value->infinite)
+			return 0.5;
+
+		/* Can't interpolate without subdiff function */
+		if (!has_subdiff)
+			return 0.5;
+
+		/* Calculate relative position using subdiff function. */
+		bin_width = DatumGetFloat8(FunctionCall2Coll(&typcache->rng_subdiff_finfo,
+													 typcache->rng_collation,
+													 hist2->val,
+													 hist1->val));
+		if (isnan(bin_width) || bin_width <= 0.0)
+			return 0.5;			/* punt for NaN or zero-width bin */
+
+		position = DatumGetFloat8(FunctionCall2Coll(&typcache->rng_subdiff_finfo,
+													typcache->rng_collation,
+													value->val,
+													hist1->val))
+			/ bin_width;
+
+		if (isnan(position))
+			return 0.5;			/* punt for NaN from subdiff, Inf/Inf, etc */
+
+		/* Relative position must be in [0,1] range */
+		position = Max(position, 0.0);
+		position = Min(position, 1.0);
+		return position;
+	}
+	else if (hist1->infinite && !hist2->infinite)
+	{
+		/*
+		 * Lower bin boundary is -infinite, upper is finite. If the value is
+		 * -infinite, return 0.0 to indicate it's equal to the lower bound.
+		 * Otherwise return 1.0 to indicate it's infinitely far from the lower
+		 * bound.
+		 */
+		return ((value->infinite && value->lower) ? 0.0 : 1.0);
+	}
+	else if (!hist1->infinite && hist2->infinite)
+	{
+		/* same as above, but in reverse */
+		return ((value->infinite && !value->lower) ? 1.0 : 0.0);
+	}
+	else
+	{
+		/*
+		 * If both bin boundaries are infinite, they should be equal to each
+		 * other, and the value should also be infinite and equal to both
+		 * bounds. (But don't Assert that, to avoid crashing if a user creates
+		 * a datatype with a broken comparison function).
+		 *
+		 * Assume the value to lie in the middle of the infinite bounds.
+		 */
+		return 0.5;
+	}
+}
+
+
+/*
+ * Get relative position of value in a length histogram bin in [0,1] range.
+ */
+static double
+get_len_position(double value, double hist1, double hist2)
+{
+	if (!isinf(hist1) && !isinf(hist2))
+	{
+		/*
+		 * Both bounds are finite. The value should be finite too, because it
+		 * lies somewhere between the bounds. If it doesn't, just return
+		 * something.
+		 */
+		if (isinf(value))
+			return 0.5;
+
+		return 1.0 - (hist2 - value) / (hist2 - hist1);
+	}
+	else if (isinf(hist1) && !isinf(hist2))
+	{
+		/*
+		 * Lower bin boundary is -infinite, upper is finite. Return 1.0 to
+		 * indicate the value is infinitely far from the lower bound.
+		 */
+		return 1.0;
+	}
+	else if (isinf(hist1) && isinf(hist2))
+	{
+		/* same as above, but in reverse */
+		return 0.0;
+	}
+	else
+	{
+		/*
+		 * If both bin boundaries are infinite, they should be equal to each
+		 * other, and the value should also be infinite and equal to both
+		 * bounds. (But don't Assert that, to avoid crashing unnecessarily if
+		 * the caller messes up)
+		 *
+		 * Assume the value to lie in the middle of the infinite bounds.
+		 */
+		return 0.5;
+	}
+}
+
+/*
+ * Measure distance between two range bounds.
+ */
+static float8
+get_distance(TypeCacheEntry *typcache, const RangeBound *bound1, const RangeBound *bound2)
+{
+	bool		has_subdiff = OidIsValid(typcache->rng_subdiff_finfo.fn_oid);
+
+	if (!bound1->infinite && !bound2->infinite)
+	{
+		/*
+		 * Neither bound is infinite, use subdiff function or return default
+		 * value of 1.0 if no subdiff is available.
+		 */
+		if (has_subdiff)
+		{
+			float8		res;
+
+			res = DatumGetFloat8(FunctionCall2Coll(&typcache->rng_subdiff_finfo,
+												   typcache->rng_collation,
+												   bound2->val,
+												   bound1->val));
+			/* Reject possible NaN result, also negative result */
+			if (isnan(res) || res < 0.0)
+				return 1.0;
+			else
+				return res;
+		}
+		else
+			return 1.0;
+	}
+	else if (bound1->infinite && bound2->infinite)
+	{
+		/* Both bounds are infinite */
+		if (bound1->lower == bound2->lower)
+			return 0.0;
+		else
+			return get_float8_infinity();
+	}
+	else
+	{
+		/* One bound is infinite, the other is not */
+		return get_float8_infinity();
+	}
+}
+
+/*
+ * Calculate the average of function P(x), in the interval [length1, length2],
+ * where P(x) is the fraction of tuples with length < x (or length <= x if
+ * 'equal' is true).
+ */
+static double
+calc_length_hist_frac(Datum *length_hist_values, int length_hist_nvalues,
+					  double length1, double length2, bool equal)
+{
+	double		frac;
+	double		A,
+				B,
+				PA,
+				PB;
+	double		pos;
+	int			i;
+	double		area;
+
+	Assert(length2 >= length1);
+
+	if (length2 < 0.0)
+		return 0.0;				/* shouldn't happen, but doesn't hurt to check */
+
+	/* All lengths in the table are <= infinite. */
+	if (isinf(length2) && equal)
+		return 1.0;
+
+	/*----------
+	 * The average of a function between A and B can be calculated by the
+	 * formula:
+	 *
+	 *			B
+	 *	  1		/
+	 * -------	| P(x)dx
+	 *	B - A	/
+	 *			A
+	 *
+	 * The geometrical interpretation of the integral is the area under the
+	 * graph of P(x). P(x) is defined by the length histogram. We calculate
+	 * the area in a piecewise fashion, iterating through the length histogram
+	 * bins. Each bin is a trapezoid:
+	 *
+	 *		 P(x2)
+	 *		  /|
+	 *		 / |
+	 * P(x1)/  |
+	 *	   |   |
+	 *	   |   |
+	 *	---+---+--
+	 *	   x1  x2
+	 *
+	 * where x1 and x2 are the boundaries of the current histogram, and P(x1)
+	 * and P(x1) are the cumulative fraction of tuples at the boundaries.
+	 *
+	 * The area of each trapezoid is 1/2 * (P(x2) + P(x1)) * (x2 - x1)
+	 *
+	 * The first bin contains the lower bound passed by the caller, so we
+	 * use linear interpolation between the previous and next histogram bin
+	 * boundary to calculate P(x1). Likewise for the last bin: we use linear
+	 * interpolation to calculate P(x2). For the bins in between, x1 and x2
+	 * lie on histogram bin boundaries, so P(x1) and P(x2) are simply:
+	 * P(x1) =	  (bin index) / (number of bins)
+	 * P(x2) = (bin index + 1 / (number of bins)
+	 */
+
+	/* First bin, the one that contains lower bound */
+	i = length_hist_bsearch(length_hist_values, length_hist_nvalues, length1, equal);
+	if (i >= length_hist_nvalues - 1)
+		return 1.0;
+
+	if (i < 0)
+	{
+		i = 0;
+		pos = 0.0;
+	}
+	else
+	{
+		/* interpolate length1's position in the bin */
+		pos = get_len_position(length1,
+							   DatumGetFloat8(length_hist_values[i]),
+							   DatumGetFloat8(length_hist_values[i + 1]));
+	}
+	PB = (((double) i) + pos) / (double) (length_hist_nvalues - 1);
+	B = length1;
+
+	/*
+	 * In the degenerate case that length1 == length2, simply return
+	 * P(length1). This is not merely an optimization: if length1 == length2,
+	 * we'd divide by zero later on.
+	 */
+	if (length2 == length1)
+		return PB;
+
+	/*
+	 * Loop through all the bins, until we hit the last bin, the one that
+	 * contains the upper bound. (if lower and upper bounds are in the same
+	 * bin, this falls out immediately)
+	 */
+	area = 0.0;
+	for (; i < length_hist_nvalues - 1; i++)
+	{
+		double		bin_upper = DatumGetFloat8(length_hist_values[i + 1]);
+
+		/* check if we've reached the last bin */
+		if (!(bin_upper < length2 || (equal && bin_upper <= length2)))
+			break;
+
+		/* the upper bound of previous bin is the lower bound of this bin */
+		A = B;
+		PA = PB;
+
+		B = bin_upper;
+		PB = (double) i / (double) (length_hist_nvalues - 1);
+
+		/*
+		 * Add the area of this trapezoid to the total. The point of the
+		 * if-check is to avoid NaN, in the corner case that PA == PB == 0,
+		 * and B - A == Inf. The area of a zero-height trapezoid (PA == PB ==
+		 * 0) is zero, regardless of the width (B - A).
+		 */
+		if (PA > 0 || PB > 0)
+			area += 0.5 * (PB + PA) * (B - A);
+	}
+
+	/* Last bin */
+	A = B;
+	PA = PB;
+
+	B = length2;				/* last bin ends at the query upper bound */
+	if (i >= length_hist_nvalues - 1)
+		pos = 0.0;
+	else
+	{
+		if (DatumGetFloat8(length_hist_values[i]) == DatumGetFloat8(length_hist_values[i + 1]))
+			pos = 0.0;
+		else
+			pos = get_len_position(length2, DatumGetFloat8(length_hist_values[i]), DatumGetFloat8(length_hist_values[i + 1]));
+	}
+	PB = (((double) i) + pos) / (double) (length_hist_nvalues - 1);
+
+	if (PA > 0 || PB > 0)
+		area += 0.5 * (PB + PA) * (B - A);
+
+	/*
+	 * Ok, we have calculated the area, ie. the integral. Divide by width to
+	 * get the requested average.
+	 *
+	 * Avoid NaN arising from infinite / infinite. This happens at least if
+	 * length2 is infinite. It's not clear what the correct value would be in
+	 * that case, so 0.5 seems as good as any value.
+	 */
+	if (isinf(area) && isinf(length2))
+		frac = 0.5;
+	else
+		frac = area / (length2 - length1);
+
+	return frac;
+}
+
+/*
+ * Calculate selectivity of "var <@ const" operator, ie. estimate the fraction
+ * of multiranges that fall within the constant lower and upper bounds. This uses
+ * the histograms of range lower bounds and range lengths, on the assumption
+ * that the range lengths are independent of the lower bounds.
+ *
+ * The caller has already checked that constant lower and upper bounds are
+ * finite.
+ */
+static double
+calc_hist_selectivity_contained(TypeCacheEntry *typcache,
+								const RangeBound *lower, RangeBound *upper,
+								const RangeBound *hist_lower, int hist_nvalues,
+								Datum *length_hist_values, int length_hist_nvalues)
+{
+	int			i,
+				upper_index;
+	float8		prev_dist;
+	double		bin_width;
+	double		upper_bin_width;
+	double		sum_frac;
+
+	/*
+	 * Begin by finding the bin containing the upper bound, in the lower bound
+	 * histogram. Any range with a lower bound > constant upper bound can't
+	 * match, ie. there are no matches in bins greater than upper_index.
+	 */
+	upper->inclusive = !upper->inclusive;
+	upper->lower = true;
+	upper_index = rbound_bsearch(typcache, upper, hist_lower, hist_nvalues,
+								 false);
+
+	/*
+	 * If the upper bound value is below the histogram's lower limit, there
+	 * are no matches.
+	 */
+	if (upper_index < 0)
+		return 0.0;
+
+	/*
+	 * If the upper bound value is at or beyond the histogram's upper limit,
+	 * start our loop at the last actual bin, as though the upper bound were
+	 * within that bin; get_position will clamp its result to 1.0 anyway.
+	 * (This corresponds to assuming that the data population above the
+	 * histogram's upper limit is empty, exactly like what we just assumed for
+	 * the lower limit.)
+	 */
+	upper_index = Min(upper_index, hist_nvalues - 2);
+
+	/*
+	 * Calculate upper_bin_width, ie. the fraction of the (upper_index,
+	 * upper_index + 1) bin which is greater than upper bound of query range
+	 * using linear interpolation of subdiff function.
+	 */
+	upper_bin_width = get_position(typcache, upper,
+								   &hist_lower[upper_index],
+								   &hist_lower[upper_index + 1]);
+
+	/*
+	 * In the loop, dist and prev_dist are the distance of the "current" bin's
+	 * lower and upper bounds from the constant upper bound.
+	 *
+	 * bin_width represents the width of the current bin. Normally it is 1.0,
+	 * meaning a full width bin, but can be less in the corner cases: start
+	 * and end of the loop. We start with bin_width = upper_bin_width, because
+	 * we begin at the bin containing the upper bound.
+	 */
+	prev_dist = 0.0;
+	bin_width = upper_bin_width;
+
+	sum_frac = 0.0;
+	for (i = upper_index; i >= 0; i--)
+	{
+		double		dist;
+		double		length_hist_frac;
+		bool		final_bin = false;
+
+		/*
+		 * dist -- distance from upper bound of query range to lower bound of
+		 * the current bin in the lower bound histogram. Or to the lower bound
+		 * of the constant range, if this is the final bin, containing the
+		 * constant lower bound.
+		 */
+		if (range_cmp_bounds(typcache, &hist_lower[i], lower) < 0)
+		{
+			dist = get_distance(typcache, lower, upper);
+
+			/*
+			 * Subtract from bin_width the portion of this bin that we want to
+			 * ignore.
+			 */
+			bin_width -= get_position(typcache, lower, &hist_lower[i],
+									  &hist_lower[i + 1]);
+			if (bin_width < 0.0)
+				bin_width = 0.0;
+			final_bin = true;
+		}
+		else
+			dist = get_distance(typcache, &hist_lower[i], upper);
+
+		/*
+		 * Estimate the fraction of tuples in this bin that are narrow enough
+		 * to not exceed the distance to the upper bound of the query range.
+		 */
+		length_hist_frac = calc_length_hist_frac(length_hist_values,
+												 length_hist_nvalues,
+												 prev_dist, dist, true);
+
+		/*
+		 * Add the fraction of tuples in this bin, with a suitable length, to
+		 * the total.
+		 */
+		sum_frac += length_hist_frac * bin_width / (double) (hist_nvalues - 1);
+
+		if (final_bin)
+			break;
+
+		bin_width = 1.0;
+		prev_dist = dist;
+	}
+
+	return sum_frac;
+}
+
+/*
+ * Calculate selectivity of "var @> const" operator, ie. estimate the fraction
+ * of multiranges that contain the constant lower and upper bounds. This uses
+ * the histograms of range lower bounds and range lengths, on the assumption
+ * that the range lengths are independent of the lower bounds.
+ */
+static double
+calc_hist_selectivity_contains(TypeCacheEntry *typcache,
+							   const RangeBound *lower, const RangeBound *upper,
+							   const RangeBound *hist_lower, int hist_nvalues,
+							   Datum *length_hist_values, int length_hist_nvalues)
+{
+	int			i,
+				lower_index;
+	double		bin_width,
+				lower_bin_width;
+	double		sum_frac;
+	float8		prev_dist;
+
+	/* Find the bin containing the lower bound of query range. */
+	lower_index = rbound_bsearch(typcache, lower, hist_lower, hist_nvalues,
+								 true);
+
+	/*
+	 * If the lower bound value is below the histogram's lower limit, there
+	 * are no matches.
+	 */
+	if (lower_index < 0)
+		return 0.0;
+
+	/*
+	 * If the lower bound value is at or beyond the histogram's upper limit,
+	 * start our loop at the last actual bin, as though the upper bound were
+	 * within that bin; get_position will clamp its result to 1.0 anyway.
+	 * (This corresponds to assuming that the data population above the
+	 * histogram's upper limit is empty, exactly like what we just assumed for
+	 * the lower limit.)
+	 */
+	lower_index = Min(lower_index, hist_nvalues - 2);
+
+	/*
+	 * Calculate lower_bin_width, ie. the fraction of the of (lower_index,
+	 * lower_index + 1) bin which is greater than lower bound of query range
+	 * using linear interpolation of subdiff function.
+	 */
+	lower_bin_width = get_position(typcache, lower, &hist_lower[lower_index],
+								   &hist_lower[lower_index + 1]);
+
+	/*
+	 * Loop through all the lower bound bins, smaller than the query lower
+	 * bound. In the loop, dist and prev_dist are the distance of the
+	 * "current" bin's lower and upper bounds from the constant upper bound.
+	 * We begin from query lower bound, and walk backwards, so the first bin's
+	 * upper bound is the query lower bound, and its distance to the query
+	 * upper bound is the length of the query range.
+	 *
+	 * bin_width represents the width of the current bin. Normally it is 1.0,
+	 * meaning a full width bin, except for the first bin, which is only
+	 * counted up to the constant lower bound.
+	 */
+	prev_dist = get_distance(typcache, lower, upper);
+	sum_frac = 0.0;
+	bin_width = lower_bin_width;
+	for (i = lower_index; i >= 0; i--)
+	{
+		float8		dist;
+		double		length_hist_frac;
+
+		/*
+		 * dist -- distance from upper bound of query range to current value
+		 * of lower bound histogram or lower bound of query range (if we've
+		 * reach it).
+		 */
+		dist = get_distance(typcache, &hist_lower[i], upper);
+
+		/*
+		 * Get average fraction of length histogram which covers intervals
+		 * longer than (or equal to) distance to upper bound of query range.
+		 */
+		length_hist_frac =
+			1.0 - calc_length_hist_frac(length_hist_values,
+										length_hist_nvalues,
+										prev_dist, dist, false);
+
+		sum_frac += length_hist_frac * bin_width / (double) (hist_nvalues - 1);
+
+		bin_width = 1.0;
+		prev_dist = dist;
+	}
+
+	return sum_frac;
+}
diff --git a/src/backend/utils/adt/pg_upgrade_support.c b/src/backend/utils/adt/pg_upgrade_support.c
index 14d9eb2b5b..7045d13cc0 100644
--- a/src/backend/utils/adt/pg_upgrade_support.c
+++ b/src/backend/utils/adt/pg_upgrade_support.c
@@ -51,6 +51,28 @@ binary_upgrade_set_next_array_pg_type_oid(PG_FUNCTION_ARGS)
 	PG_RETURN_VOID();
 }
 
+Datum
+binary_upgrade_set_next_multirange_pg_type_oid(PG_FUNCTION_ARGS)
+{
+	Oid			typoid = PG_GETARG_OID(0);
+
+	CHECK_IS_BINARY_UPGRADE;
+	binary_upgrade_next_mrng_pg_type_oid = typoid;
+
+	PG_RETURN_VOID();
+}
+
+Datum
+binary_upgrade_set_next_multirange_array_pg_type_oid(PG_FUNCTION_ARGS)
+{
+	Oid			typoid = PG_GETARG_OID(0);
+
+	CHECK_IS_BINARY_UPGRADE;
+	binary_upgrade_next_mrng_array_pg_type_oid = typoid;
+
+	PG_RETURN_VOID();
+}
+
 Datum
 binary_upgrade_set_next_heap_pg_class_oid(PG_FUNCTION_ARGS)
 {
diff --git a/src/backend/utils/adt/pseudotypes.c b/src/backend/utils/adt/pseudotypes.c
index 3d6b2f9093..99a93271fe 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"
 
 
 /*
@@ -227,6 +228,43 @@ anycompatiblerange_out(PG_FUNCTION_ARGS)
 	return range_out(fcinfo);
 }
 
+/*
+ * anycompatiblemultirange
+ *
+ * We may as well allow output, since multirange_out will in fact work.
+ */
+PSEUDOTYPE_DUMMY_INPUT_FUNC(anycompatiblemultirange);
+
+Datum
+anycompatiblemultirange_out(PG_FUNCTION_ARGS)
+{
+	return multirange_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
  *
diff --git a/src/backend/utils/adt/rangetypes.c b/src/backend/utils/adt/rangetypes.c
index 01ad8bc240..ba4caa2d36 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
 {
@@ -64,10 +62,6 @@ static const char *range_parse_bound(const char *string, const char *ptr,
 static char *range_deparse(char flags, const char *lbound_str,
 						   const char *ubound_str);
 static char *range_bound_escape(const char *value);
-static Size datum_compute_size(Size sz, Datum datum, bool typbyval,
-							   char typalign, int16 typlen, char typstorage);
-static Pointer datum_write(Pointer ptr, Datum datum, bool typbyval,
-						   char typalign, int16 typlen, char typstorage);
 
 
 /*
@@ -431,19 +425,37 @@ range_lower(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
 	TypeCacheEntry *typcache;
+	bool		isnull;
+	Datum		result;
+
+	typcache = range_get_typcache(fcinfo, RangeTypeGetOid(r1));
+
+	result = range_lower_internal(typcache, r1, &isnull);
+
+	if (isnull)
+		PG_RETURN_NULL();
+	else
+		PG_RETURN_DATUM(result);
+}
+
+Datum
+range_lower_internal(TypeCacheEntry *typcache, const RangeType *r1, bool *isnull)
+{
 	RangeBound	lower;
 	RangeBound	upper;
 	bool		empty;
 
-	typcache = range_get_typcache(fcinfo, RangeTypeGetOid(r1));
-
 	range_deserialize(typcache, r1, &lower, &upper, &empty);
 
 	/* Return NULL if there's no finite lower bound */
 	if (empty || lower.infinite)
-		PG_RETURN_NULL();
+	{
+		*isnull = true;
+		return 0;
+	}
 
-	PG_RETURN_DATUM(lower.val);
+	*isnull = false;
+	return lower.val;
 }
 
 /* extract upper bound value */
@@ -452,19 +464,37 @@ range_upper(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
 	TypeCacheEntry *typcache;
+	bool		isnull;
+	Datum		result;
+
+	typcache = range_get_typcache(fcinfo, RangeTypeGetOid(r1));
+
+	result = range_upper_internal(typcache, r1, &isnull);
+
+	if (isnull)
+		PG_RETURN_NULL();
+	else
+		PG_RETURN_DATUM(result);
+}
+
+Datum
+range_upper_internal(TypeCacheEntry *typcache, const RangeType *r1, bool *isnull)
+{
 	RangeBound	lower;
 	RangeBound	upper;
 	bool		empty;
 
-	typcache = range_get_typcache(fcinfo, RangeTypeGetOid(r1));
-
 	range_deserialize(typcache, r1, &lower, &upper, &empty);
 
 	/* Return NULL if there's no finite upper bound */
 	if (empty || upper.infinite)
-		PG_RETURN_NULL();
+	{
+		*isnull = true;
+		return 0;
+	}
 
-	PG_RETURN_DATUM(upper.val);
+	*isnull = false;
+	return upper.val;
 }
 
 
@@ -475,9 +505,8 @@ Datum
 range_empty(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
-	char		flags = range_get_flags(r1);
 
-	PG_RETURN_BOOL(flags & RANGE_EMPTY);
+	PG_RETURN_BOOL(range_has_flag(r1, RANGE_EMPTY));
 }
 
 /* is lower bound inclusive? */
@@ -485,9 +514,8 @@ Datum
 range_lower_inc(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
-	char		flags = range_get_flags(r1);
 
-	PG_RETURN_BOOL(flags & RANGE_LB_INC);
+	PG_RETURN_BOOL(range_has_flag(r1, RANGE_LB_INC));
 }
 
 /* is upper bound inclusive? */
@@ -495,9 +523,8 @@ Datum
 range_upper_inc(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
-	char		flags = range_get_flags(r1);
 
-	PG_RETURN_BOOL(flags & RANGE_UB_INC);
+	PG_RETURN_BOOL(range_has_flag(r1, RANGE_UB_INC));
 }
 
 /* is lower bound infinite? */
@@ -505,9 +532,8 @@ Datum
 range_lower_inf(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
-	char		flags = range_get_flags(r1);
 
-	PG_RETURN_BOOL(flags & RANGE_LB_INF);
+	PG_RETURN_BOOL(range_has_flag(r1, RANGE_LB_INF));
 }
 
 /* is upper bound infinite? */
@@ -515,9 +541,8 @@ Datum
 range_upper_inf(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
-	char		flags = range_get_flags(r1);
 
-	PG_RETURN_BOOL(flags & RANGE_UB_INF);
+	PG_RETURN_BOOL(range_has_flag(r1, RANGE_UB_INF));
 }
 
 
@@ -957,7 +982,25 @@ range_minus(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
 	RangeType  *r2 = PG_GETARG_RANGE_P(1);
+	RangeType  *ret;
 	TypeCacheEntry *typcache;
+
+	/* 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));
+
+	ret = range_minus_internal(typcache, r1, r2);
+	if (ret)
+		PG_RETURN_RANGE_P(ret);
+	else
+		PG_RETURN_NULL();
+}
+
+RangeType *
+range_minus_internal(TypeCacheEntry *typcache, RangeType *r1, RangeType *r2)
+{
 	RangeBound	lower1,
 				lower2;
 	RangeBound	upper1,
@@ -969,18 +1012,12 @@ range_minus(PG_FUNCTION_ARGS)
 				cmp_u1l2,
 				cmp_u1u2;
 
-	/* 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));
-
 	range_deserialize(typcache, r1, &lower1, &upper1, &empty1);
 	range_deserialize(typcache, r2, &lower2, &upper2, &empty2);
 
 	/* if either is empty, r1 is the correct answer */
 	if (empty1 || empty2)
-		PG_RETURN_RANGE_P(r1);
+		return r1;
 
 	cmp_l1l2 = range_cmp_bounds(typcache, &lower1, &lower2);
 	cmp_l1u2 = range_cmp_bounds(typcache, &lower1, &upper2);
@@ -993,34 +1030,34 @@ range_minus(PG_FUNCTION_ARGS)
 				 errmsg("result of range difference would not be contiguous")));
 
 	if (cmp_l1u2 > 0 || cmp_u1l2 < 0)
-		PG_RETURN_RANGE_P(r1);
+		return r1;
 
 	if (cmp_l1l2 >= 0 && cmp_u1u2 <= 0)
-		PG_RETURN_RANGE_P(make_empty_range(typcache));
+		return make_empty_range(typcache);
 
 	if (cmp_l1l2 <= 0 && cmp_u1l2 >= 0 && cmp_u1u2 <= 0)
 	{
 		lower2.inclusive = !lower2.inclusive;
 		lower2.lower = false;	/* it will become the upper bound */
-		PG_RETURN_RANGE_P(make_range(typcache, &lower1, &lower2, false));
+		return make_range(typcache, &lower1, &lower2, false);
 	}
 
 	if (cmp_l1l2 >= 0 && cmp_u1u2 >= 0 && cmp_l1u2 <= 0)
 	{
 		upper2.inclusive = !upper2.inclusive;
 		upper2.lower = true;	/* it will become the lower bound */
-		PG_RETURN_RANGE_P(make_range(typcache, &upper2, &upper1, false));
+		return make_range(typcache, &upper2, &upper1, false);
 	}
 
 	elog(ERROR, "unexpected case in range_minus");
-	PG_RETURN_NULL();
+	return NULL;
 }
 
 /*
  * Set union.  If strict is true, it is an error that the two input ranges
  * are not adjacent or overlapping.
  */
-static RangeType *
+RangeType *
 range_union_internal(TypeCacheEntry *typcache, RangeType *r1, RangeType *r2,
 					 bool strict)
 {
@@ -1101,6 +1138,19 @@ range_intersect(PG_FUNCTION_ARGS)
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
 	RangeType  *r2 = PG_GETARG_RANGE_P(1);
 	TypeCacheEntry *typcache;
+
+	/* 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));
+
+	PG_RETURN_RANGE_P(range_intersect_internal(typcache, r1, r2));
+}
+
+RangeType *
+range_intersect_internal(TypeCacheEntry *typcache, const RangeType *r1, const RangeType *r2)
+{
 	RangeBound	lower1,
 				lower2;
 	RangeBound	upper1,
@@ -1110,17 +1160,11 @@ range_intersect(PG_FUNCTION_ARGS)
 	RangeBound *result_lower;
 	RangeBound *result_upper;
 
-	/* 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));
-
 	range_deserialize(typcache, r1, &lower1, &upper1, &empty1);
 	range_deserialize(typcache, r2, &lower2, &upper2, &empty2);
 
-	if (empty1 || empty2 || !DatumGetBool(range_overlaps(fcinfo)))
-		PG_RETURN_RANGE_P(make_empty_range(typcache));
+	if (empty1 || empty2 || !range_overlaps_internal(typcache, r1, r2))
+		return make_empty_range(typcache);
 
 	if (range_cmp_bounds(typcache, &lower1, &lower2) >= 0)
 		result_lower = &lower1;
@@ -1132,9 +1176,81 @@ range_intersect(PG_FUNCTION_ARGS)
 	else
 		result_upper = &upper2;
 
-	PG_RETURN_RANGE_P(make_range(typcache, result_lower, result_upper, false));
+	return make_range(typcache, result_lower, result_upper, false);
+}
+
+/* range, range -> range, range functions */
+
+/*
+ * range_split_internal - if r2 intersects the middle of r1, leaving non-empty
+ * ranges on both sides, then return true and set output1 and output2 to the
+ * results of r1 - r2 (in order). Otherwise return false and don't set output1
+ * or output2. Neither input range should be empty.
+ */
+bool
+range_split_internal(TypeCacheEntry *typcache, const RangeType *r1, const RangeType *r2,
+					 RangeType **output1, RangeType **output2)
+{
+	RangeBound	lower1,
+				lower2;
+	RangeBound	upper1,
+				upper2;
+	bool		empty1,
+				empty2;
+
+	range_deserialize(typcache, r1, &lower1, &upper1, &empty1);
+	range_deserialize(typcache, r2, &lower2, &upper2, &empty2);
+
+	if (range_cmp_bounds(typcache, &lower1, &lower2) < 0 &&
+		range_cmp_bounds(typcache, &upper1, &upper2) > 0)
+	{
+		/*
+		 * Need to invert inclusive/exclusive for the lower2 and upper2
+		 * points. They can't be infinite though. We're allowed to overwrite
+		 * these RangeBounds since they only exist locally.
+		 */
+		lower2.inclusive = !lower2.inclusive;
+		lower2.lower = false;
+		upper2.inclusive = !upper2.inclusive;
+		upper2.lower = true;
+
+		*output1 = make_range(typcache, &lower1, &lower2, false);
+		*output2 = make_range(typcache, &upper2, &upper1, false);
+		return true;
+	}
+
+	return false;
 }
 
+/* range -> range aggregate functions */
+
+Datum
+range_intersect_agg_transfn(PG_FUNCTION_ARGS)
+{
+	MemoryContext aggContext;
+	Oid			rngtypoid;
+	TypeCacheEntry *typcache;
+	RangeType  *result;
+	RangeType  *current;
+
+	if (!AggCheckCallContext(fcinfo, &aggContext))
+		elog(ERROR, "range_intersect_agg_transfn called in non-aggregate context");
+
+	rngtypoid = get_fn_expr_argtype(fcinfo->flinfo, 1);
+	if (!type_is_range(rngtypoid))
+		ereport(ERROR, (errmsg("range_intersect_agg must be called with a range")));
+
+	typcache = range_get_typcache(fcinfo, rngtypoid);
+
+	/* strictness ensures these are non-null */
+	result = PG_GETARG_RANGE_P(0);
+	current = PG_GETARG_RANGE_P(1);
+
+	result = range_intersect_internal(typcache, result, current);
+	PG_RETURN_RANGE_P(result);
+}
+
+
 /* Btree support */
 
 /* btree comparator */
@@ -1144,12 +1260,6 @@ range_cmp(PG_FUNCTION_ARGS)
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
 	RangeType  *r2 = PG_GETARG_RANGE_P(1);
 	TypeCacheEntry *typcache;
-	RangeBound	lower1,
-				lower2;
-	RangeBound	upper1,
-				upper2;
-	bool		empty1,
-				empty2;
 	int			cmp;
 
 	check_stack_depth();		/* recurses when subtype is a range type */
@@ -1160,6 +1270,28 @@ range_cmp(PG_FUNCTION_ARGS)
 
 	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, const RangeType *r1, const 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);
 
@@ -1177,10 +1309,7 @@ range_cmp(PG_FUNCTION_ARGS)
 			cmp = range_cmp_bounds(typcache, &upper1, &upper2);
 	}
 
-	PG_FREE_IF_COPY(r1, 0);
-	PG_FREE_IF_COPY(r2, 1);
-
-	PG_RETURN_INT32(cmp);
+	return cmp;
 }
 
 /* inequality operators using the range_cmp function */
@@ -1218,13 +1347,10 @@ range_gt(PG_FUNCTION_ARGS)
 
 /* Hash support */
 
-/* hash a range value */
-Datum
-hash_range(PG_FUNCTION_ARGS)
+uint32
+hash_range_internal(TypeCacheEntry *typcache, const RangeType *r)
 {
-	RangeType  *r = PG_GETARG_RANGE_P(0);
 	uint32		result;
-	TypeCacheEntry *typcache;
 	TypeCacheEntry *scache;
 	RangeBound	lower;
 	RangeBound	upper;
@@ -1233,10 +1359,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 +1400,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, const RangeType *r, Datum seed)
+{
+	uint64		result;
 	TypeCacheEntry *scache;
 	RangeBound	lower;
 	RangeBound	upper;
@@ -1300,10 +1429,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 +1467,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));
 }
 
 /*
@@ -1769,6 +1912,21 @@ range_get_flags(const RangeType *range)
 	return *((char *) range + VARSIZE(range) - 1);
 }
 
+/*
+ * range_has_flag: set whether a range has a specific flag.
+ *
+ * This lets expose some of our functions that just check flags
+ * to the rest of the code base (like to multiranges)
+ * without writing full-fledged *_internal versions.
+ */
+bool
+range_has_flag(const RangeType *r1, char flag)
+{
+	char		flags = range_get_flags(r1);
+
+	return flags & flag;
+}
+
 /*
  * range_set_contain_empty: set the RANGE_CONTAIN_EMPTY bit in the value.
  *
@@ -1937,6 +2095,46 @@ range_cmp_bound_values(TypeCacheEntry *typcache, const RangeBound *b1,
 										   b1->val, b2->val));
 }
 
+/*
+ * qsort callback for sorting ranges.
+ *
+ * Two empty ranges compare equal; an empty range sorts to the left of any
+ * non-empty range.  Two non-empty ranges are sorted by lower bound first
+ * and by upper bound next.
+ */
+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.
  */
@@ -2395,7 +2593,7 @@ range_contains_elem_internal(TypeCacheEntry *typcache, const RangeType *r, Datum
  * Increment data_length by the space needed by the datum, including any
  * preceding alignment padding.
  */
-static Size
+Size
 datum_compute_size(Size data_length, Datum val, bool typbyval, char typalign,
 				   int16 typlen, char typstorage)
 {
@@ -2421,7 +2619,7 @@ datum_compute_size(Size data_length, Datum val, bool typbyval, char typalign,
  * Write the given datum beginning at ptr (after advancing to correct
  * alignment, if needed).  Return the pointer incremented by space used.
  */
-static Pointer
+Pointer
 datum_write(Pointer ptr, Datum datum, bool typbyval, char typalign,
 			int16 typlen, char typstorage)
 {
diff --git a/src/backend/utils/adt/rangetypes_typanalyze.c b/src/backend/utils/adt/rangetypes_typanalyze.c
index 57413b0720..94679cce69 100644
--- a/src/backend/utils/adt/rangetypes_typanalyze.c
+++ b/src/backend/utils/adt/rangetypes_typanalyze.c
@@ -30,6 +30,7 @@
 #include "utils/fmgrprotos.h"
 #include "utils/lsyscache.h"
 #include "utils/rangetypes.h"
+#include "utils/multirangetypes.h"
 
 static int	float8_qsort_cmp(const void *a1, const void *a2);
 static int	range_bound_qsort_cmp(const void *a1, const void *a2, void *arg);
@@ -60,6 +61,33 @@ range_typanalyze(PG_FUNCTION_ARGS)
 	PG_RETURN_BOOL(true);
 }
 
+/*
+ * multirange_typanalyze -- typanalyze function for multirange columns
+ *
+ * We do the same analysis as for ranges, but on the smallest range that
+ * completely includes the multirange.
+ */
+Datum
+multirange_typanalyze(PG_FUNCTION_ARGS)
+{
+	VacAttrStats *stats = (VacAttrStats *) PG_GETARG_POINTER(0);
+	TypeCacheEntry *typcache;
+	Form_pg_attribute attr = stats->attr;
+
+	/* Get information about multirange type; note column might be a domain */
+	typcache = multirange_get_typcache(fcinfo, getBaseType(stats->attrtypid));
+
+	if (attr->attstattarget < 0)
+		attr->attstattarget = default_statistics_target;
+
+	stats->compute_stats = compute_range_stats;
+	stats->extra_data = typcache;
+	/* same as in std_typanalyze */
+	stats->minrows = 300 * attr->attstattarget;
+
+	PG_RETURN_BOOL(true);
+}
+
 /*
  * Comparison function for sorting float8s, used for range lengths.
  */
@@ -98,7 +126,8 @@ compute_range_stats(VacAttrStats *stats, AnalyzeAttrFetchFunc fetchfunc,
 					int samplerows, double totalrows)
 {
 	TypeCacheEntry *typcache = (TypeCacheEntry *) stats->extra_data;
-	bool		has_subdiff = OidIsValid(typcache->rng_subdiff_finfo.fn_oid);
+	TypeCacheEntry *mltrng_typcache = NULL;
+	bool		has_subdiff;
 	int			null_cnt = 0;
 	int			non_null_cnt = 0;
 	int			non_empty_cnt = 0;
@@ -112,6 +141,15 @@ compute_range_stats(VacAttrStats *stats, AnalyzeAttrFetchFunc fetchfunc,
 			   *uppers;
 	double		total_width = 0;
 
+	if (typcache->typtype == TYPTYPE_MULTIRANGE)
+	{
+		mltrng_typcache = typcache;
+		typcache = typcache->rngtype;
+	}
+	else
+		Assert(typcache->typtype == TYPTYPE_RANGE);
+	has_subdiff = OidIsValid(typcache->rng_subdiff_finfo.fn_oid);
+
 	/* Allocate memory to hold range bounds and lengths of the sample ranges. */
 	lowers = (RangeBound *) palloc(sizeof(RangeBound) * samplerows);
 	uppers = (RangeBound *) palloc(sizeof(RangeBound) * samplerows);
@@ -123,6 +161,7 @@ compute_range_stats(VacAttrStats *stats, AnalyzeAttrFetchFunc fetchfunc,
 		Datum		value;
 		bool		isnull,
 					empty;
+		MultirangeType *multirange;
 		RangeType  *range;
 		RangeBound	lower,
 					upper;
@@ -145,7 +184,23 @@ compute_range_stats(VacAttrStats *stats, AnalyzeAttrFetchFunc fetchfunc,
 		total_width += VARSIZE_ANY(DatumGetPointer(value));
 
 		/* Get range and deserialize it for further analysis. */
-		range = DatumGetRangeTypeP(value);
+		if (mltrng_typcache != NULL)
+		{
+			/* Treat multiranges like a big range without gaps. */
+			multirange = DatumGetMultirangeTypeP(value);
+			if (MultirangeIsEmpty(multirange))
+				range = make_empty_range(typcache);
+			else
+			{
+				int	range_count;
+				RangeType **ranges;
+				multirange_deserialize(typcache, multirange, &range_count, &ranges);
+				range = range_union_internal(typcache, ranges[0],
+						ranges[range_count - 1], false);
+			}
+		}
+		else
+			range = DatumGetRangeTypeP(value);
 		range_deserialize(typcache, range, &lower, &upper, &empty);
 
 		if (!empty)
@@ -262,6 +317,13 @@ compute_range_stats(VacAttrStats *stats, AnalyzeAttrFetchFunc fetchfunc,
 			stats->stakind[slot_idx] = STATISTIC_KIND_BOUNDS_HISTOGRAM;
 			stats->stavalues[slot_idx] = bound_hist_values;
 			stats->numvalues[slot_idx] = num_hist;
+
+			/* Store ranges even if we're analyzing a multirange column */
+			stats->statypid[slot_idx] = typcache->type_id;
+			stats->statyplen[slot_idx] = typcache->typlen;
+			stats->statypbyval[slot_idx] = typcache->typbyval;
+			stats->statypalign[slot_idx] = 'd';
+
 			slot_idx++;
 		}
 
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index f3bf413829..e700f88122 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -2578,6 +2578,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
  *
@@ -3220,7 +3230,7 @@ get_namespace_name_or_temp(Oid nspid)
 		return get_namespace_name(nspid);
 }
 
-/*				---------- PG_RANGE CACHE ----------				 */
+/*				---------- PG_RANGE CACHES ----------				 */
 
 /*
  * get_range_subtype
@@ -3273,6 +3283,56 @@ get_range_collation(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->rngmultitypid;
+		ReleaseSysCache(tp);
+		return result;
+	}
+	else
+		return InvalidOid;
+}
+
+/*
+ * get_range_multirange_subtype
+ *		Returns the subtype of a given multirange type
+ *
+ * Returns InvalidOid if the type is not a multirange type.
+ */
+Oid
+get_range_multirange_subtype(Oid multirangeOid)
+{
+	HeapTuple	tp;
+
+	tp = SearchSysCache1(RANGEMULTIRANGE, 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 53d9ddf159..7654331a59 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -651,6 +651,18 @@ static const struct cachedesc cacheinfo[] = {
 		},
 		64
 	},
+	{RangeRelationId,			/* RANGEMULTIRANGE */
+		RangeMultirangeTypidIndexId,
+		1,
+		{
+			Anum_pg_range_rngmultitypid,
+			0,
+			0,
+			0
+		},
+		4
+	},
+
 	{RangeRelationId,			/* RANGETYPE */
 		RangeTypidIndexId,
 		1,
diff --git a/src/backend/utils/cache/typcache.c b/src/backend/utils/cache/typcache.c
index f51248b70d..2fb87c8453 100644
--- a/src/backend/utils/cache/typcache.c
+++ b/src/backend/utils/cache/typcache.c
@@ -286,6 +286,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);
@@ -302,6 +303,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 TypeCacheTypCallback(Datum arg, int cacheid, uint32 hashvalue);
 static void TypeCacheOpcCallback(Datum arg, int cacheid, uint32 hashvalue);
@@ -553,8 +557,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))
@@ -591,7 +595,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))
@@ -616,7 +620,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))
@@ -641,7 +645,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))
@@ -693,6 +697,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;
@@ -737,6 +748,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;
@@ -816,6 +834,16 @@ lookup_type_cache(Oid type_id, int flags)
 			(void) lookup_type_cache(typentry->rngelemtype->type_id, 0);
 	}
 
+	/*
+	 * 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
 	 */
@@ -927,6 +955,22 @@ load_rangetype_info(TypeCacheEntry *typentry)
 	typentry->rngelemtype = lookup_type_cache(subtypeOid, 0);
 }
 
+/*
+ * load_multirangetype_info --- helper routine to set up multirange type
+ * information
+ */
+static void
+load_multirangetype_info(TypeCacheEntry *typentry)
+{
+	Oid			rangetypeOid;
+
+	rangetypeOid = get_range_multirange_subtype(typentry->type_id);
+	if (!OidIsValid(rangetypeOid))
+		elog(ERROR, "cache lookup failed for multirange type %u",
+			 typentry->type_id);
+
+	typentry->rngtype = lookup_type_cache(rangetypeOid, TYPECACHE_RANGE_INFO);
+}
 
 /*
  * load_domaintype_info --- helper routine to set up domain constraint info
@@ -1527,11 +1571,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
@@ -1574,6 +1618,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 78ed857203..8a5bd9bf19 100644
--- a/src/backend/utils/fmgr/funcapi.c
+++ b/src/backend/utils/fmgr/funcapi.c
@@ -35,6 +35,7 @@ typedef struct polymorphic_actuals
 	Oid			anyelement_type;	/* anyelement mapping, if known */
 	Oid			anyarray_type;	/* anyarray mapping, if known */
 	Oid			anyrange_type;	/* anyrange mapping, if known */
+	Oid			anymultirange_type;	/* anymultirange mapping, if known */
 } polymorphic_actuals;
 
 static void shutdown_MultiFuncCall(Datum arg);
@@ -46,6 +47,7 @@ static TypeFuncClass internal_get_result_type(Oid funcid,
 static void resolve_anyelement_from_others(polymorphic_actuals *actuals);
 static void resolve_anyarray_from_others(polymorphic_actuals *actuals);
 static void resolve_anyrange_from_others(polymorphic_actuals *actuals);
+static void resolve_anymultirange_from_others(polymorphic_actuals *actuals);
 static bool resolve_polymorphic_tupdesc(TupleDesc tupdesc,
 										oidvector *declared_args,
 										Node *call_expr);
@@ -503,6 +505,31 @@ resolve_anyelement_from_others(polymorphic_actuals *actuals)
 							format_type_be(range_base_type))));
 		actuals->anyelement_type = range_typelem;
 	}
+	else if (OidIsValid(actuals->anymultirange_type))
+	{
+		/* Use the element type based on the multirange type */
+		Oid			multirange_base_type = getBaseType(actuals->anymultirange_type);
+		Oid			multirange_typelem =
+			get_range_multirange_subtype(multirange_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(multirange_base_type))));
+
+		Oid			range_base_type = getBaseType(multirange_typelem);
+		Oid			range_typelem = get_range_subtype(range_base_type);
+
+		if (!OidIsValid(range_typelem))
+			ereport(ERROR,
+					(errcode(ERRCODE_DATATYPE_MISMATCH),
+					 errmsg("argument declared %s does not contain a range type but type %s",
+							"anymultirange",
+							format_type_be(range_base_type))));
+		actuals->anyelement_type = range_typelem;
+	}
 	else
 		elog(ERROR, "could not determine polymorphic type");
 }
@@ -540,10 +567,54 @@ static void
 resolve_anyrange_from_others(polymorphic_actuals *actuals)
 {
 	/*
-	 * We can't deduce a range type from other polymorphic inputs, because
-	 * there may be multiple range types with the same subtype.
+	 * We can't deduce a range type from other polymorphic array or base types,
+	 * because there may be multiple range types with the same subtype, but we
+	 * can deduce it from a polymorphic multirange type.
 	 */
-	elog(ERROR, "could not determine polymorphic type");
+	if (OidIsValid(actuals->anymultirange_type))
+	{
+		/* Use the element type based on the multirange type */
+		Oid			multirange_base_type = getBaseType(actuals->anymultirange_type);
+		Oid			multirange_typelem =
+			get_range_multirange_subtype(multirange_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(multirange_base_type))));
+		actuals->anyrange_type = multirange_typelem;
+	}
+	else
+		elog(ERROR, "could not determine polymorphic type");
+}
+
+/*
+ * Resolve actual type of ANYMULTIRANGE from other polymorphic inputs
+ */
+static void
+resolve_anymultirange_from_others(polymorphic_actuals *actuals)
+{
+	/*
+	 * We can't deduce a multirange type from polymorphic array or base types,
+	 * because there may be multiple range types with the same subtype, but we
+	 * can deduce it from a polymorphic range type.
+	 */
+	if (OidIsValid(actuals->anyrange_type))
+	{
+		Oid			range_base_type = getBaseType(actuals->anyrange_type);
+		Oid			multirange_typeid = get_range_multirange(range_base_type);
+
+		if (!OidIsValid(multirange_typeid))
+			ereport(ERROR,
+					(errcode(ERRCODE_UNDEFINED_OBJECT),
+					 errmsg("could not find multirange type for data type %s",
+							format_type_be(actuals->anyrange_type))));
+		actuals->anymultirange_type = multirange_typeid;
+	}
+	else
+		elog(ERROR, "could not determine polymorphic type");
 }
 
 /*
@@ -566,9 +637,11 @@ 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_anycompatible_result = false;
 	bool		have_anycompatible_array_result = false;
 	bool		have_anycompatible_range_result = false;
+	bool		have_anycompatible_multirange_result = false;
 	polymorphic_actuals poly_actuals;
 	polymorphic_actuals anyc_actuals;
 	Oid			anycollation = InvalidOid;
@@ -594,6 +667,10 @@ resolve_polymorphic_tupdesc(TupleDesc tupdesc, oidvector *declared_args,
 				have_polymorphic_result = true;
 				have_anyrange_result = true;
 				break;
+			case ANYMULTIRANGEOID:
+				have_polymorphic_result = true;
+				have_anymultirange_result = true;
+				break;
 			case ANYCOMPATIBLEOID:
 			case ANYCOMPATIBLENONARRAYOID:
 				have_polymorphic_result = true;
@@ -607,6 +684,10 @@ resolve_polymorphic_tupdesc(TupleDesc tupdesc, oidvector *declared_args,
 				have_polymorphic_result = true;
 				have_anycompatible_range_result = true;
 				break;
+			case ANYCOMPATIBLEMULTIRANGEOID:
+				have_polymorphic_result = true;
+				have_anycompatible_multirange_result = true;
+				break;
 			default:
 				break;
 		}
@@ -660,6 +741,15 @@ resolve_polymorphic_tupdesc(TupleDesc tupdesc, oidvector *declared_args,
 						return false;
 				}
 				break;
+			case ANYMULTIRANGEOID:
+				if (!OidIsValid(poly_actuals.anymultirange_type))
+				{
+					poly_actuals.anymultirange_type =
+						get_call_expr_argtype(call_expr, i);
+					if (!OidIsValid(poly_actuals.anymultirange_type))
+						return false;
+				}
+				break;
 			case ANYCOMPATIBLEOID:
 			case ANYCOMPATIBLENONARRAYOID:
 				if (!OidIsValid(anyc_actuals.anyelement_type))
@@ -688,6 +778,15 @@ resolve_polymorphic_tupdesc(TupleDesc tupdesc, oidvector *declared_args,
 						return false;
 				}
 				break;
+			case ANYCOMPATIBLEMULTIRANGEOID:
+				if (!OidIsValid(anyc_actuals.anymultirange_type))
+				{
+					anyc_actuals.anymultirange_type =
+						get_call_expr_argtype(call_expr, i);
+					if (!OidIsValid(anyc_actuals.anymultirange_type))
+						return false;
+				}
+				break;
 			default:
 				break;
 		}
@@ -703,6 +802,9 @@ resolve_polymorphic_tupdesc(TupleDesc tupdesc, oidvector *declared_args,
 	if (have_anyrange_result && !OidIsValid(poly_actuals.anyrange_type))
 		resolve_anyrange_from_others(&poly_actuals);
 
+	if (have_anymultirange_result && !OidIsValid(poly_actuals.anymultirange_type))
+		resolve_anymultirange_from_others(&poly_actuals);
+
 	if (have_anycompatible_result && !OidIsValid(anyc_actuals.anyelement_type))
 		resolve_anyelement_from_others(&anyc_actuals);
 
@@ -712,6 +814,9 @@ resolve_polymorphic_tupdesc(TupleDesc tupdesc, oidvector *declared_args,
 	if (have_anycompatible_range_result && !OidIsValid(anyc_actuals.anyrange_type))
 		resolve_anyrange_from_others(&anyc_actuals);
 
+	if (have_anycompatible_multirange_result && !OidIsValid(anyc_actuals.anymultirange_type))
+		resolve_anymultirange_from_others(&anyc_actuals);
+
 	/*
 	 * Identify the collation to use for polymorphic OUT parameters. (It'll
 	 * necessarily be the same for both anyelement and anyarray, likewise for
@@ -780,6 +885,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),
+								   poly_actuals.anymultirange_type,
+								   -1,
+								   0);
+				/* no collation should be attached to a multirange type */
+				break;
 			case ANYCOMPATIBLEOID:
 			case ANYCOMPATIBLENONARRAYOID:
 				TupleDescInitEntry(tupdesc, i + 1,
@@ -805,6 +918,14 @@ resolve_polymorphic_tupdesc(TupleDesc tupdesc, oidvector *declared_args,
 								   0);
 				/* no collation should be attached to a range type */
 				break;
+			case ANYCOMPATIBLEMULTIRANGEOID:
+				TupleDescInitEntry(tupdesc, i + 1,
+								   NameStr(att->attname),
+								   anyc_actuals.anymultirange_type,
+								   -1,
+								   0);
+				/* no collation should be attached to a multirange type */
+				break;
 			default:
 				break;
 		}
@@ -834,9 +955,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;
 	bool		have_anycompatible_result = false;
 	bool		have_anycompatible_array_result = false;
 	bool		have_anycompatible_range_result = false;
+	bool		have_anycompatible_multirange_result = false;
 	polymorphic_actuals poly_actuals;
 	polymorphic_actuals anyc_actuals;
 	int			inargno;
@@ -912,6 +1035,24 @@ resolve_polymorphic_argtypes(int numargs, Oid *argtypes, char *argmodes,
 					argtypes[i] = poly_actuals.anyrange_type;
 				}
 				break;
+			case ANYMULTIRANGEOID:
+				if (argmode == PROARGMODE_OUT || argmode == PROARGMODE_TABLE)
+				{
+					have_polymorphic_result = true;
+					have_anymultirange_result = true;
+				}
+				else
+				{
+					if (!OidIsValid(poly_actuals.anymultirange_type))
+					{
+						poly_actuals.anymultirange_type =
+							get_call_expr_argtype(call_expr, inargno);
+						if (!OidIsValid(poly_actuals.anymultirange_type))
+							return false;
+					}
+					argtypes[i] = poly_actuals.anymultirange_type;
+				}
+				break;
 			case ANYCOMPATIBLEOID:
 			case ANYCOMPATIBLENONARRAYOID:
 				if (argmode == PROARGMODE_OUT || argmode == PROARGMODE_TABLE)
@@ -967,6 +1108,24 @@ resolve_polymorphic_argtypes(int numargs, Oid *argtypes, char *argmodes,
 					argtypes[i] = anyc_actuals.anyrange_type;
 				}
 				break;
+			case ANYCOMPATIBLEMULTIRANGEOID:
+				if (argmode == PROARGMODE_OUT || argmode == PROARGMODE_TABLE)
+				{
+					have_polymorphic_result = true;
+					have_anycompatible_multirange_result = true;
+				}
+				else
+				{
+					if (!OidIsValid(anyc_actuals.anymultirange_type))
+					{
+						anyc_actuals.anymultirange_type =
+							get_call_expr_argtype(call_expr, inargno);
+						if (!OidIsValid(anyc_actuals.anymultirange_type))
+							return false;
+					}
+					argtypes[i] = anyc_actuals.anymultirange_type;
+				}
+				break;
 			default:
 				break;
 		}
@@ -988,6 +1147,9 @@ resolve_polymorphic_argtypes(int numargs, Oid *argtypes, char *argmodes,
 	if (have_anyrange_result && !OidIsValid(poly_actuals.anyrange_type))
 		resolve_anyrange_from_others(&poly_actuals);
 
+	if (have_anymultirange_result && !OidIsValid(poly_actuals.anymultirange_type))
+		resolve_anymultirange_from_others(&poly_actuals);
+
 	if (have_anycompatible_result && !OidIsValid(anyc_actuals.anyelement_type))
 		resolve_anyelement_from_others(&anyc_actuals);
 
@@ -997,6 +1159,9 @@ resolve_polymorphic_argtypes(int numargs, Oid *argtypes, char *argmodes,
 	if (have_anycompatible_range_result && !OidIsValid(anyc_actuals.anyrange_type))
 		resolve_anyrange_from_others(&anyc_actuals);
 
+	if (have_anycompatible_multirange_result && !OidIsValid(anyc_actuals.anymultirange_type))
+		resolve_anymultirange_from_others(&anyc_actuals);
+
 	/* And finally replace the output column types as needed */
 	for (i = 0; i < numargs; i++)
 	{
@@ -1013,6 +1178,9 @@ resolve_polymorphic_argtypes(int numargs, Oid *argtypes, char *argmodes,
 			case ANYRANGEOID:
 				argtypes[i] = poly_actuals.anyrange_type;
 				break;
+			case ANYMULTIRANGEOID:
+				argtypes[i] = poly_actuals.anymultirange_type;
+				break;
 			case ANYCOMPATIBLEOID:
 			case ANYCOMPATIBLENONARRAYOID:
 				argtypes[i] = anyc_actuals.anyelement_type;
@@ -1023,6 +1191,9 @@ resolve_polymorphic_argtypes(int numargs, Oid *argtypes, char *argmodes,
 			case ANYCOMPATIBLERANGEOID:
 				argtypes[i] = anyc_actuals.anyrange_type;
 				break;
+			case ANYCOMPATIBLEMULTIRANGEOID:
+				argtypes[i] = anyc_actuals.anymultirange_type;
+				break;
 			default:
 				break;
 		}
@@ -1052,6 +1223,7 @@ get_type_func_class(Oid typid, Oid *base_typeid)
 		case TYPTYPE_BASE:
 		case TYPTYPE_ENUM:
 		case TYPTYPE_RANGE:
+		case TYPTYPE_MULTIRANGE:
 			return TYPEFUNC_SCALAR;
 		case TYPTYPE_DOMAIN:
 			*base_typeid = typid = getBaseType(typid);
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 2cb3f9b083..7f2fb0734b 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -272,7 +272,8 @@ static void dumpSearchPath(Archive *AH);
 static void binary_upgrade_set_type_oids_by_type_oid(Archive *fout,
 													 PQExpBuffer upgrade_buffer,
 													 Oid pg_type_oid,
-													 bool force_array_type);
+													 bool force_array_type,
+													 bool include_multirange_type);
 static void binary_upgrade_set_type_oids_by_rel_oid(Archive *fout,
 													PQExpBuffer upgrade_buffer, Oid pg_rel_oid);
 static void binary_upgrade_set_pg_class_oids(Archive *fout,
@@ -1641,7 +1642,7 @@ selectDumpableType(TypeInfo *tyinfo, Archive *fout)
 	}
 
 	/* skip auto-generated array types */
-	if (tyinfo->isArray)
+	if (tyinfo->isArray || tyinfo->isMultirange)
 	{
 		tyinfo->dobj.objType = DO_DUMMY_TYPE;
 
@@ -4439,16 +4440,49 @@ append_depends_on_extension(Archive *fout,
 	}
 }
 
+static Oid
+get_next_possible_free_pg_type_oid(Archive *fout, PQExpBuffer upgrade_query)
+{
+	/*
+	 * If the old version didn't assign an array type, but the new version
+	 * does, we must select an unused type OID to assign.  This currently only
+	 * happens for domains, when upgrading pre-v11 to v11 and up.
+	 *
+	 * Note: local state here is kind of ugly, but we must have some, since we
+	 * mustn't choose the same unused OID more than once.
+	 */
+	static Oid	next_possible_free_oid = FirstNormalObjectId;
+	PGresult   *res;
+	bool		is_dup;
+
+	do
+	{
+		++next_possible_free_oid;
+		printfPQExpBuffer(upgrade_query,
+						  "SELECT EXISTS(SELECT 1 "
+						  "FROM pg_catalog.pg_type "
+						  "WHERE oid = '%u'::pg_catalog.oid);",
+						  next_possible_free_oid);
+		res = ExecuteSqlQueryForSingleRow(fout, upgrade_query->data);
+		is_dup = (PQgetvalue(res, 0, 0)[0] == 't');
+		PQclear(res);
+	} while (is_dup);
+
+	return next_possible_free_oid;
+}
 
 static void
 binary_upgrade_set_type_oids_by_type_oid(Archive *fout,
 										 PQExpBuffer upgrade_buffer,
 										 Oid pg_type_oid,
-										 bool force_array_type)
+										 bool force_array_type,
+										 bool include_multirange_type)
 {
 	PQExpBuffer upgrade_query = createPQExpBuffer();
 	PGresult   *res;
 	Oid			pg_type_array_oid;
+	Oid			pg_type_multirange_oid;
+	Oid			pg_type_multirange_array_oid;
 
 	appendPQExpBufferStr(upgrade_buffer, "\n-- For binary upgrade, must preserve pg_type oid\n");
 	appendPQExpBuffer(upgrade_buffer,
@@ -4469,33 +4503,7 @@ binary_upgrade_set_type_oids_by_type_oid(Archive *fout,
 	PQclear(res);
 
 	if (!OidIsValid(pg_type_array_oid) && force_array_type)
-	{
-		/*
-		 * If the old version didn't assign an array type, but the new version
-		 * does, we must select an unused type OID to assign.  This currently
-		 * only happens for domains, when upgrading pre-v11 to v11 and up.
-		 *
-		 * Note: local state here is kind of ugly, but we must have some,
-		 * since we mustn't choose the same unused OID more than once.
-		 */
-		static Oid	next_possible_free_oid = FirstNormalObjectId;
-		bool		is_dup;
-
-		do
-		{
-			++next_possible_free_oid;
-			printfPQExpBuffer(upgrade_query,
-							  "SELECT EXISTS(SELECT 1 "
-							  "FROM pg_catalog.pg_type "
-							  "WHERE oid = '%u'::pg_catalog.oid);",
-							  next_possible_free_oid);
-			res = ExecuteSqlQueryForSingleRow(fout, upgrade_query->data);
-			is_dup = (PQgetvalue(res, 0, 0)[0] == 't');
-			PQclear(res);
-		} while (is_dup);
-
-		pg_type_array_oid = next_possible_free_oid;
-	}
+		pg_type_array_oid = get_next_possible_free_pg_type_oid(fout, upgrade_query);
 
 	if (OidIsValid(pg_type_array_oid))
 	{
@@ -4506,6 +4514,46 @@ binary_upgrade_set_type_oids_by_type_oid(Archive *fout,
 						  pg_type_array_oid);
 	}
 
+	/*
+	 * Pre-set the multirange type oid and its own array type oid.
+	 */
+	if (include_multirange_type)
+	{
+		if (fout->remoteVersion >= 130000)
+		{
+			appendPQExpBuffer(upgrade_query,
+							  "SELECT t.oid, t.typarray "
+							  "FROM pg_catalog.pg_type t "
+							  "JOIN pg_catalog.pg_range r "
+							  "ON t.oid = r.rngmultitypid "
+							  "WHERE r.rngtypid = '%u'::pg_catalog.oid;",
+							  pg_type_oid);
+
+			res = ExecuteSqlQueryForSingleRow(fout, upgrade_query->data);
+
+			pg_type_multirange_oid = atooid(PQgetvalue(res, 0, PQfnumber(res, "oid")));
+			pg_type_multirange_array_oid = atooid(PQgetvalue(res, 0, PQfnumber(res, "typarray")));
+
+			PQclear(res);
+		}
+		else
+		{
+			pg_type_multirange_oid = get_next_possible_free_pg_type_oid(fout, upgrade_query);
+			pg_type_multirange_array_oid = get_next_possible_free_pg_type_oid(fout, upgrade_query);
+		}
+
+		appendPQExpBufferStr(upgrade_buffer,
+							 "\n-- For binary upgrade, must preserve multirange pg_type oid\n");
+		appendPQExpBuffer(upgrade_buffer,
+						  "SELECT pg_catalog.binary_upgrade_set_next_multirange_pg_type_oid('%u'::pg_catalog.oid);\n\n",
+						  pg_type_multirange_oid);
+		appendPQExpBufferStr(upgrade_buffer,
+							 "\n-- For binary upgrade, must preserve multirange pg_type array oid\n");
+		appendPQExpBuffer(upgrade_buffer,
+						  "SELECT pg_catalog.binary_upgrade_set_next_multirange_array_pg_type_oid('%u'::pg_catalog.oid);\n\n",
+						  pg_type_multirange_array_oid);
+	}
+
 	destroyPQExpBuffer(upgrade_query);
 }
 
@@ -4530,7 +4578,7 @@ binary_upgrade_set_type_oids_by_rel_oid(Archive *fout,
 
 	if (OidIsValid(pg_type_oid))
 		binary_upgrade_set_type_oids_by_type_oid(fout, upgrade_buffer,
-												 pg_type_oid, false);
+												 pg_type_oid, false, false);
 
 	PQclear(upgrade_res);
 	destroyPQExpBuffer(upgrade_query);
@@ -5076,6 +5124,11 @@ getTypes(Archive *fout, int *numTypes)
 		else
 			tyinfo[i].isArray = false;
 
+		if (tyinfo[i].typtype == 'm')
+			tyinfo[i].isMultirange = true;
+		else
+			tyinfo[i].isMultirange = false;
+
 		/* Decide whether we want to dump it */
 		selectDumpableType(&tyinfo[i], fout);
 
@@ -8241,9 +8294,12 @@ getProcLangs(Archive *fout, int *numProcLangs)
 
 /*
  * getCasts
- *	  get basic information about every cast in the system
+ *	  get basic information about most casts in the system
  *
  * numCasts is set to the number of casts read in
+ *
+ * Skip casts from a range to its multirange, since we'll create those
+ * automatically.
  */
 CastInfo *
 getCasts(Archive *fout, int *numCasts)
@@ -8261,7 +8317,20 @@ getCasts(Archive *fout, int *numCasts)
 	int			i_castcontext;
 	int			i_castmethod;
 
-	if (fout->remoteVersion >= 80400)
+	if (fout->remoteVersion >= 130000)
+	{
+		appendPQExpBufferStr(query, "SELECT tableoid, oid, "
+							 "castsource, casttarget, castfunc, castcontext, "
+							 "castmethod "
+							 "FROM pg_cast c "
+							 "WHERE NOT EXISTS ( "
+							 "SELECT 1 FROM pg_range r "
+							 "WHERE c.castsource = r.rngtypid "
+							 "AND c.casttarget = r.rngmultitypid "
+							 ") "
+							 "ORDER BY 3,4");
+	}
+	else if (fout->remoteVersion >= 80400)
 	{
 		appendPQExpBufferStr(query, "SELECT tableoid, oid, "
 							 "castsource, casttarget, castfunc, castcontext, "
@@ -10413,7 +10482,7 @@ dumpEnumType(Archive *fout, TypeInfo *tyinfo)
 	if (dopt->binary_upgrade)
 		binary_upgrade_set_type_oids_by_type_oid(fout, q,
 												 tyinfo->dobj.catId.oid,
-												 false);
+												 false, false);
 
 	appendPQExpBuffer(q, "CREATE TYPE %s AS ENUM (",
 					  qualtypname);
@@ -10539,7 +10608,7 @@ dumpRangeType(Archive *fout, TypeInfo *tyinfo)
 	if (dopt->binary_upgrade)
 		binary_upgrade_set_type_oids_by_type_oid(fout, q,
 												 tyinfo->dobj.catId.oid,
-												 false);
+												 false, true);
 
 	appendPQExpBuffer(q, "CREATE TYPE %s AS RANGE (",
 					  qualtypname);
@@ -10645,7 +10714,7 @@ dumpUndefinedType(Archive *fout, TypeInfo *tyinfo)
 	if (dopt->binary_upgrade)
 		binary_upgrade_set_type_oids_by_type_oid(fout, q,
 												 tyinfo->dobj.catId.oid,
-												 false);
+												 false, false);
 
 	appendPQExpBuffer(q, "CREATE TYPE %s;\n",
 					  qualtypname);
@@ -10850,7 +10919,7 @@ dumpBaseType(Archive *fout, TypeInfo *tyinfo)
 	if (dopt->binary_upgrade)
 		binary_upgrade_set_type_oids_by_type_oid(fout, q,
 												 tyinfo->dobj.catId.oid,
-												 false);
+												 false, false);
 
 	appendPQExpBuffer(q,
 					  "CREATE TYPE %s (\n"
@@ -11037,7 +11106,8 @@ dumpDomain(Archive *fout, TypeInfo *tyinfo)
 	if (dopt->binary_upgrade)
 		binary_upgrade_set_type_oids_by_type_oid(fout, q,
 												 tyinfo->dobj.catId.oid,
-												 true); /* force array type */
+												 true,		/* force array type */
+												 false);	/* force multirange type */
 
 	qtypname = pg_strdup(fmtId(tyinfo->dobj.name));
 	qualtypname = pg_strdup(fmtQualifiedDumpable(tyinfo));
@@ -11225,7 +11295,7 @@ dumpCompositeType(Archive *fout, TypeInfo *tyinfo)
 	{
 		binary_upgrade_set_type_oids_by_type_oid(fout, q,
 												 tyinfo->dobj.catId.oid,
-												 false);
+												 false, false);
 		binary_upgrade_set_pg_class_oids(fout, q, tyinfo->typrelid, false);
 	}
 
@@ -11499,7 +11569,7 @@ dumpShellType(Archive *fout, ShellTypeInfo *stinfo)
 	if (dopt->binary_upgrade)
 		binary_upgrade_set_type_oids_by_type_oid(fout, q,
 												 stinfo->baseType->dobj.catId.oid,
-												 false);
+												 false, false);
 
 	appendPQExpBuffer(q, "CREATE TYPE %s;\n",
 					  fmtQualifiedDumpable(stinfo));
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index da97b731b1..dfe384feaf 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -176,6 +176,7 @@ typedef struct _typeInfo
 	char		typrelkind;		/* 'r', 'v', 'c', etc */
 	char		typtype;		/* 'b', 'c', etc */
 	bool		isArray;		/* true if auto-generated array type */
+	bool		isMultirange;	/* true if auto-generated multirange type */
 	bool		isDefined;		/* true if typisdefined */
 	/* If needed, we'll create a "shell type" entry for it; link that here: */
 	struct _shellTypeInfo *shellType;	/* shell-type entry, or NULL */
diff --git a/src/include/access/tupmacs.h b/src/include/access/tupmacs.h
index 70157cf90a..c262265b95 100644
--- a/src/include/access/tupmacs.h
+++ b/src/include/access/tupmacs.h
@@ -139,8 +139,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/binary_upgrade.h b/src/include/catalog/binary_upgrade.h
index 02fecb90f7..1ff62e9f8e 100644
--- a/src/include/catalog/binary_upgrade.h
+++ b/src/include/catalog/binary_upgrade.h
@@ -16,6 +16,8 @@
 
 extern PGDLLIMPORT Oid binary_upgrade_next_pg_type_oid;
 extern PGDLLIMPORT Oid binary_upgrade_next_array_pg_type_oid;
+extern PGDLLIMPORT Oid binary_upgrade_next_mrng_pg_type_oid;
+extern PGDLLIMPORT Oid binary_upgrade_next_mrng_array_pg_type_oid;
 
 extern PGDLLIMPORT Oid binary_upgrade_next_heap_pg_class_oid;
 extern PGDLLIMPORT Oid binary_upgrade_next_index_pg_class_oid;
diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h
index a7e2a9b26b..23874f282a 100644
--- a/src/include/catalog/indexing.h
+++ b/src/include/catalog/indexing.h
@@ -332,6 +332,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_rngmultitypid_index, 8001, on pg_range using btree(rngmultitypid 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_aggregate.dat b/src/include/catalog/pg_aggregate.dat
index ffabe275c0..03bab74253 100644
--- a/src/include/catalog/pg_aggregate.dat
+++ b/src/include/catalog/pg_aggregate.dat
@@ -538,6 +538,17 @@
   aggtransfn => 'bytea_string_agg_transfn',
   aggfinalfn => 'bytea_string_agg_finalfn', aggtranstype => 'internal' },
 
+# range
+{ aggfnoid => 'range_intersect_agg(anyrange)',
+  aggtransfn => 'range_intersect_agg_transfn',
+  aggcombinefn => 'range_intersect_agg_transfn', aggtranstype => 'anyrange' },
+{ aggfnoid => 'range_intersect_agg(anymultirange)',
+  aggtransfn => 'multirange_intersect_agg_transfn',
+  aggcombinefn => 'multirange_intersect_agg_transfn', aggtranstype => 'anymultirange' },
+{ aggfnoid => 'range_agg(anyrange)', aggtransfn => 'range_agg_transfn',
+  aggfinalfn => 'range_agg_finalfn', aggfinalextra => 't',
+  aggtranstype => 'internal' },
+
 # json
 { aggfnoid => 'json_agg', aggtransfn => 'json_agg_transfn',
   aggfinalfn => 'json_agg_finalfn', aggtranstype => 'internal' },
diff --git a/src/include/catalog/pg_amop.dat b/src/include/catalog/pg_amop.dat
index 1dfb6fd373..42293ddffe 100644
--- a/src/include/catalog/pg_amop.dat
+++ b/src/include/catalog/pg_amop.dat
@@ -1379,6 +1379,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 37b580883f..9842e80acf 100644
--- a/src/include/catalog/pg_amproc.dat
+++ b/src/include/catalog/pg_amproc.dat
@@ -285,6 +285,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' },
 { amprocfamily => 'btree/xid8_ops', amproclefttype => 'xid8',
@@ -453,6 +455,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',
@@ -1274,12 +1281,13 @@
   amprocrighttype => 'anyrange', amprocnum => '4',
   amproc => 'brin_inclusion_union' },
 { amprocfamily => 'brin/range_inclusion_ops', amproclefttype => 'anyrange',
-  amprocrighttype => 'anyrange', amprocnum => '11', amproc => 'range_merge' },
+  amprocrighttype => 'anyrange', amprocnum => '11',
+  amproc => 'range_merge(anyrange,anyrange)' },
 { amprocfamily => 'brin/range_inclusion_ops', amproclefttype => 'anyrange',
   amprocrighttype => 'anyrange', amprocnum => '13',
   amproc => 'range_contains' },
 { amprocfamily => 'brin/range_inclusion_ops', amproclefttype => 'anyrange',
-  amprocrighttype => 'anyrange', amprocnum => '14', amproc => 'isempty' },
+  amprocrighttype => 'anyrange', amprocnum => '14', amproc => 'isempty(anyrange)' },
 
 # minmax pg_lsn
 { amprocfamily => 'brin/pg_lsn_minmax_ops', amproclefttype => 'pg_lsn',
diff --git a/src/include/catalog/pg_cast.dat b/src/include/catalog/pg_cast.dat
index 5a58f50fbb..d6ca624add 100644
--- a/src/include/catalog/pg_cast.dat
+++ b/src/include/catalog/pg_cast.dat
@@ -530,4 +530,17 @@
 { castsource => 'jsonb', casttarget => 'float8', castfunc => 'float8(jsonb)',
   castcontext => 'e', castmethod => 'f' },
 
+# range to multirange
+{ castsource => 'int4range', casttarget => 'int4multirange', castfunc => 'int4multirange(int4range)',
+  castcontext => 'e', castmethod => 'f' },
+{ castsource => 'int8range', casttarget => 'int8multirange', castfunc => 'int8multirange(int8range)',
+  castcontext => 'e', castmethod => 'f' },
+{ castsource => 'numrange', casttarget => 'nummultirange', castfunc => 'nummultirange(numrange)',
+  castcontext => 'e', castmethod => 'f' },
+{ castsource => 'daterange', casttarget => 'datemultirange', castfunc => 'datemultirange(daterange)',
+  castcontext => 'e', castmethod => 'f' },
+{ castsource => 'tsrange', casttarget => 'tsmultirange', castfunc => 'tsmultirange(tsrange)',
+  castcontext => 'e', castmethod => 'f' },
+{ castsource => 'tstzrange', casttarget => 'tstzmultirange', castfunc => 'tstzmultirange(tstzrange)',
+  castcontext => 'e', castmethod => 'f' },
 ]
diff --git a/src/include/catalog/pg_opclass.dat b/src/include/catalog/pg_opclass.dat
index f2342bb328..9b0609defa 100644
--- a/src/include/catalog/pg_opclass.dat
+++ b/src/include/catalog/pg_opclass.dat
@@ -230,6 +230,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 5b0e063655..dbe8499e21 100644
--- a/src/include/catalog/pg_operator.dat
+++ b/src/include/catalog/pg_operator.dat
@@ -3338,5 +3338,174 @@
   oprname => '@@', oprleft => 'jsonb', oprright => 'jsonpath',
   oprresult => 'bool', oprcode => 'jsonb_path_match_opr(jsonb,jsonpath)',
   oprrest => 'matchingsel', oprjoin => 'matchingjoinsel' },
+{ 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 => 'multirangesel', 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 => 'multirangesel', 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 => 'multirangesel', 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 => 'multirangesel', oprjoin => 'scalargtjoinsel' },
+{ oid => '8073', oid_symbol => 'OID_RANGE_OVERLAPS_MULTIRANGE_OP',
+  descr => 'overlaps',
+  oprname => '&&', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '&&(anymultirange,anyrange)',
+  oprcode => 'range_overlaps_multirange', oprrest => 'multirangesel',
+  oprjoin => 'areajoinsel' },
+{ oid => '8074', oid_symbol => 'OID_MULTIRANGE_OVERLAPS_RANGE_OP', descr => 'contains',
+  oprname => '&&', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcom => '&&(anyrange,anymultirange)',
+  oprcode => 'multirange_overlaps_range', oprrest => 'multirangesel',
+  oprjoin => 'areajoinsel' },
+{ oid => '8075', oid_symbol => 'OID_MULTIRANGE_OVERLAPS_MULTIRANGE_OP', descr => 'contains',
+  oprname => '&&', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '&&(anymultirange,anymultirange)',
+  oprcode => 'multirange_overlaps_multirange', oprrest => 'multirangesel',
+  oprjoin => 'areajoinsel' },
+{ oid => '8064', oid_symbol => 'OID_MULTIRANGE_CONTAINS_ELEM_OP',
+  descr => 'contains',
+  oprname => '@>', oprleft => 'anymultirange', oprright => 'anyelement',
+  oprresult => 'bool', oprcom => '<@(anyelement,anymultirange)',
+  oprcode => 'multirange_contains_elem', oprrest => 'multirangesel',
+  oprjoin => 'contjoinsel' },
+{ oid => '8065', oid_symbol => 'OID_MULTIRANGE_CONTAINS_RANGE_OP', descr => 'contains',
+  oprname => '@>', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcom => '<@(anyrange,anymultirange)',
+  oprcode => 'multirange_contains_range', oprrest => 'multirangesel',
+  oprjoin => 'contjoinsel' },
+{ oid => '8066', oid_symbol => 'OID_MULTIRANGE_CONTAINS_MULTIRANGE_OP', descr => 'contains',
+  oprname => '@>', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '<@(anymultirange,anymultirange)',
+  oprcode => 'multirange_contains_multirange', oprrest => 'multirangesel',
+  oprjoin => 'contjoinsel' },
+{ oid => '8067', oid_symbol => 'OID_MULTIRANGE_ELEM_CONTAINED_OP',
+  descr => 'is contained by',
+  oprname => '<@', oprleft => 'anyelement', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '@>(anymultirange,anyelement)',
+  oprcode => 'elem_contained_by_multirange', oprrest => 'multirangesel',
+  oprjoin => 'contjoinsel' },
+{ oid => '8068', oid_symbol => 'OID_MULTIRANGE_RANGE_CONTAINED_OP',
+  descr => 'is contained by',
+  oprname => '<@', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '@>(anymultirange,anyrange)',
+  oprcode => 'range_contained_by_multirange', oprrest => 'multirangesel',
+  oprjoin => 'contjoinsel' },
+{ oid => '8069', oid_symbol => 'OID_MULTIRANGE_MULTIRANGE_CONTAINED_OP',
+  descr => 'is contained by',
+  oprname => '<@', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '@>(anymultirange,anymultirange)',
+  oprcode => 'multirange_contained_by_multirange', oprrest => 'multirangesel',
+  oprjoin => 'contjoinsel' },
+{ oid => '8106', oid_symbol => 'OID_RANGE_OVERLAPS_LEFT_MULTIRANGE_OP',
+  descr => 'overlaps or is left of',
+  oprname => '&<', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcode => 'range_overleft_multirange', oprrest => 'multirangesel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8107', oid_symbol => 'OID_MULTIRANGE_OVERLAPS_LEFT_RANGE_OP',
+  descr => 'overlaps or is left of',
+  oprname => '&<', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcode => 'multirange_overleft_range', oprrest => 'multirangesel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8108', oid_symbol => 'OID_MULTIRANGE_OVERLAPS_LEFT_MULTIRANGE_OP',
+  descr => 'overlaps or is left of',
+  oprname => '&<', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcode => 'multirange_overleft_multirange', oprrest => 'multirangesel',
+  oprjoin => 'scalargtjoinsel' },
+{ oid => '8109', oid_symbol => 'OID_RANGE_OVERLAPS_RIGHT_MULTIRANGE_OP',
+  descr => 'overlaps or is right of',
+  oprname => '&>', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcode => 'range_overright_multirange', oprrest => 'multirangesel',
+  oprjoin => 'scalargtjoinsel' },
+{ oid => '8110', oid_symbol => 'OID_MULTIRANGE_OVERLAPS_RIGHT_RANGE_OP',
+  descr => 'overlaps or is right of',
+  oprname => '&>', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcode => 'multirange_overright_range', oprrest => 'multirangesel',
+  oprjoin => 'scalargtjoinsel' },
+{ oid => '8111', oid_symbol => 'OID_MULTIRANGE_OVERLAPS_RIGHT_MULTIRANGE_OP',
+  descr => 'overlaps or is right of',
+  oprname => '&>', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcode => 'multirange_overright_multirange', oprrest => 'multirangesel',
+  oprjoin => 'scalargtjoinsel' },
+{ oid => '8091', oid_symbol => 'OID_RANGE_ADJACENT_MULTIRANGE_OP', descr => 'is adjacent to',
+  oprname => '-|-', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '-|-(anymultirange,anyrange)',
+  oprcode => 'range_adjacent_multirange', oprrest => 'matchingsel',
+  oprjoin => 'matchingjoinsel' },
+{ oid => '8092', oid_symbol => 'OID_MULTIRANGE_ADJACENT_RANGE_OP', descr => 'is adjacent to',
+  oprname => '-|-', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcom => '-|-(anyrange,anymultirange)',
+  oprcode => 'multirange_adjacent_range', oprrest => 'matchingsel',
+  oprjoin => 'matchingjoinsel' },
+{ oid => '8093', oid_symbol => 'OID_MULTIRANGE_ADJACENT_MULTIRANGE_OP', descr => 'is adjacent to',
+  oprname => '-|-', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '-|-(anymultirange,anymultirange)',
+  oprcode => 'multirange_adjacent_multirange', oprrest => 'matchingsel',
+  oprjoin => 'matchingjoinsel' },
+{ oid => '8117', descr => 'multirange union',
+  oprname => '+', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'anymultirange', oprcom => '+(anymultirange,anymultirange)',
+  oprcode => 'multirange_union' },
+{ oid => '8123', descr => 'multirange minus',
+  oprname => '-', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'anymultirange', oprcode => 'multirange_minus' },
+{ oid => '8129', descr => 'multirange intersect',
+  oprname => '*', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'anymultirange', oprcom => '*(anymultirange,anymultirange)',
+  oprcode => 'multirange_intersect' },
+{ oid => '8082', oid_symbol => 'OID_RANGE_LEFT_MULTIRANGE_OP', descr => 'is left of',
+  oprname => '<<', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '>>(anymultirange,anyrange)',
+  oprcode => 'range_before_multirange', oprrest => 'multirangesel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8083', oid_symbol => 'OID_MULTIRANGE_LEFT_RANGE_OP', descr => 'is left of',
+  oprname => '<<', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcom => '>>(anyrange,anymultirange)',
+  oprcode => 'multirange_before_range', oprrest => 'multirangesel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8084', oid_symbol => 'OID_MULTIRANGE_LEFT_MULTIRANGE_OP', descr => 'is left of',
+  oprname => '<<', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '>>(anymultirange,anymultirange)',
+  oprcode => 'multirange_before_multirange', oprrest => 'multirangesel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8085', oid_symbol => 'OID_RANGE_RIGHT_MULTIRANGE_OP', descr => 'is right of',
+  oprname => '>>', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '<<(anymultirange,anyrange)',
+  oprcode => 'range_after_multirange', oprrest => 'multirangesel',
+  oprjoin => 'scalargtjoinsel' },
+{ oid => '8086', oid_symbol => 'OID_MULTIRANGE_RIGHT_RANGE_OP', descr => 'is right of',
+  oprname => '>>', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcom => '<<(anyrange,anymultirange)',
+  oprcode => 'multirange_after_range', oprrest => 'multirangesel',
+  oprjoin => 'scalargtjoinsel' },
+{ oid => '8087', oid_symbol => 'OID_MULTIRANGE_RIGHT_MULTIRANGE_OP', descr => 'is right of',
+  oprname => '>>', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '<<(anymultirange,anymultirange)',
+  oprcode => 'multirange_after_multirange', oprrest => 'multirangesel',
+  oprjoin => 'scalargtjoinsel' },
 
 ]
diff --git a/src/include/catalog/pg_opfamily.dat b/src/include/catalog/pg_opfamily.dat
index cf0fb325b3..ffd1220041 100644
--- a/src/include/catalog/pg_opfamily.dat
+++ b/src/include/catalog/pg_opfamily.dat
@@ -230,5 +230,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 082a11f270..443e9d4955 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -7218,6 +7218,14 @@
   proname => 'anycompatiblerange_out', provolatile => 's',
   prorettype => 'cstring', proargtypes => 'anycompatiblerange',
   prosrc => 'anycompatiblerange_out' },
+{ oid => '8154', descr => 'I/O',
+  proname => 'anycompatiblemultirange_in', provolatile => 's',
+  prorettype => 'anycompatiblemultirange', proargtypes => 'cstring oid int4',
+  prosrc => 'anycompatiblemultirange_in' },
+{ oid => '8155', descr => 'I/O',
+  proname => 'anycompatiblemultirange_out', provolatile => 's',
+  prorettype => 'cstring', proargtypes => 'anycompatiblemultirange',
+  prosrc => 'anycompatiblemultirange_out' },
 
 # tablesample method handlers
 { oid => '3313', descr => 'BERNOULLI tablesample method handler',
@@ -9817,6 +9825,10 @@
   descr => 'the smallest range which includes both of the given ranges',
   proname => 'range_merge', prorettype => 'anyrange',
   proargtypes => 'anyrange anyrange', prosrc => 'range_merge' },
+{ oid => '8143',
+  descr => 'the smallest range which includes the whole multirange',
+  proname => 'range_merge', prorettype => 'anyrange',
+  proargtypes => 'anymultirange', prosrc => 'range_merge_from_multirange' },
 { oid => '3868',
   proname => 'range_intersect', prorettype => 'anyrange',
   proargtypes => 'anyrange anyrange', prosrc => 'range_intersect' },
@@ -9866,6 +9878,13 @@
 { oid => '3169', descr => 'restriction selectivity for range operators',
   proname => 'rangesel', provolatile => 's', prorettype => 'float8',
   proargtypes => 'internal oid internal int4', prosrc => 'rangesel' },
+{ oid => '8133', descr => 'range aggregate by intersecting',
+  proname => 'range_intersect_agg_transfn', prorettype => 'anyrange',
+  proargtypes => 'anyrange anyrange', prosrc => 'range_intersect_agg_transfn'},
+{ oid => '8134', descr => 'range aggregate by intersecting',
+  proname => 'range_intersect_agg', prokind => 'a', proisstrict => 'f',
+  prorettype => 'anyrange', proargtypes => 'anyrange',
+  prosrc => 'aggregate_dummy'},
 
 { oid => '3914', descr => 'convert an int4 range to canonical form',
   proname => 'int4range_canonical', prorettype => 'int4range',
@@ -9934,6 +9953,261 @@
   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 => '8094', descr => 'lower bound of multirange',
+  proname => 'lower', prorettype => 'anyelement', proargtypes => 'anymultirange',
+  prosrc => 'multirange_lower' },
+{ oid => '8095', descr => 'upper bound of multirange',
+  proname => 'upper', prorettype => 'anyelement', proargtypes => 'anymultirange',
+  prosrc => 'multirange_upper' },
+{ oid => '8057', descr => 'is the multirange empty?',
+  proname => 'isempty', prorettype => 'bool',
+  proargtypes => 'anymultirange', prosrc => 'multirange_empty' },
+{ oid => '8096', descr => 'is the multirange\'s lower bound inclusive?',
+  proname => 'lower_inc', prorettype => 'bool', proargtypes => 'anymultirange',
+  prosrc => 'multirange_lower_inc' },
+{ oid => '8097', descr => 'is the multirange\'s upper bound inclusive?',
+  proname => 'upper_inc', prorettype => 'bool', proargtypes => 'anymultirange',
+  prosrc => 'multirange_upper_inc' },
+{ oid => '8098', descr => 'is the multirange\'s lower bound infinite?',
+  proname => 'lower_inf', prorettype => 'bool', proargtypes => 'anymultirange',
+  prosrc => 'multirange_lower_inf' },
+{ oid => '8099', descr => 'is the multirange\'s upper bound infinite?',
+  proname => 'upper_inf', prorettype => 'bool', proargtypes => 'anymultirange',
+  prosrc => 'multirange_upper_inf' },
+{ oid => '8008', descr => 'multirange typanalyze',
+  proname => 'multirange_typanalyze', provolatile => 's', prorettype => 'bool',
+  proargtypes => 'internal', prosrc => 'multirange_typanalyze' },
+{ oid => '8156', descr => 'restriction selectivity for multirange operators',
+  proname => 'multirangesel', provolatile => 's', prorettype => 'float8',
+  proargtypes => 'internal oid internal int4', prosrc => 'multirangesel' },
+{ oid => '8033',
+  proname => 'multirange_eq', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_eq' },
+{ oid => '8034',
+  proname => 'multirange_ne', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_ne' },
+{ oid => '8070',
+  proname => 'range_overlaps_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_overlaps_multirange' },
+{ oid => '8071',
+  proname => 'multirange_overlaps_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_overlaps_range' },
+{ oid => '8072',
+  proname => 'multirange_overlaps_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_overlaps_multirange' },
+{ oid => '8058',
+  proname => 'multirange_contains_elem', prorettype => 'bool',
+  proargtypes => 'anymultirange anyelement', prosrc => 'multirange_contains_elem' },
+{ oid => '8059',
+  proname => 'multirange_contains_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_contains_range' },
+{ oid => '8060',
+  proname => 'multirange_contains_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_contains_multirange' },
+{ oid => '8061',
+  proname => 'elem_contained_by_multirange', prorettype => 'bool',
+  proargtypes => 'anyelement anymultirange', prosrc => 'elem_contained_by_multirange' },
+{ oid => '8062',
+  proname => 'range_contained_by_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_contained_by_multirange' },
+{ oid => '8063',
+  proname => 'multirange_contained_by_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_contained_by_multirange' },
+{ oid => '8088',
+  proname => 'range_adjacent_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_adjacent_multirange' },
+{ oid => '8089',
+  proname => 'multirange_adjacent_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_adjacent_multirange' },
+{ oid => '8090',
+  proname => 'multirange_adjacent_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_adjacent_range' },
+{ oid => '8076',
+  proname => 'range_before_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_before_multirange' },
+{ oid => '8077',
+  proname => 'multirange_before_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_before_range' },
+{ oid => '8078',
+  proname => 'multirange_before_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_before_multirange' },
+{ oid => '8079',
+  proname => 'range_after_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_after_multirange' },
+{ oid => '8080',
+  proname => 'multirange_after_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_after_range' },
+{ oid => '8081',
+  proname => 'multirange_after_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_after_multirange' },
+{ oid => '8100',
+  proname => 'range_overleft_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_overleft_multirange' },
+{ oid => '8101',
+  proname => 'multirange_overleft_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_overleft_range' },
+{ oid => '8102',
+  proname => 'multirange_overleft_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_overleft_multirange' },
+{ oid => '8103',
+  proname => 'range_overright_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_overright_multirange' },
+{ oid => '8104',
+  proname => 'multirange_overright_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_overright_range' },
+{ oid => '8105',
+  proname => 'multirange_overright_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_overright_multirange' },
+{ oid => '8114',
+  proname => 'multirange_union', prorettype => 'anymultirange',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_union' },
+{ oid => '8120',
+  proname => 'multirange_minus', prorettype => 'anymultirange',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_minus' },
+{ oid => '8126',
+  proname => 'multirange_intersect', prorettype => 'anymultirange',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_intersect' },
+{ oid => '8022', descr => 'less-equal-greater',
+  proname => 'multirange_cmp', prorettype => 'int4',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_cmp' },
+{ oid => '8023',
+  proname => 'multirange_lt', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_lt' },
+{ oid => '8024',
+  proname => 'multirange_le', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_le' },
+{ oid => '8025',
+  proname => 'multirange_ge', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_ge' },
+{ oid => '8026',
+  proname => 'multirange_gt', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_gt' },
+{ oid => '8038', descr => 'hash a multirange',
+  proname => 'hash_multirange', prorettype => 'int4', proargtypes => 'anymultirange',
+  prosrc => 'hash_multirange' },
+{ oid => '8039', descr => 'hash a multirange',
+  proname => 'hash_multirange_extended', prorettype => 'int8',
+  proargtypes => 'anymultirange int8', prosrc => 'hash_multirange_extended' },
+
+{ oid => '8045', descr => 'int4multirange constructor',
+  proname => 'int4multirange', proisstrict => 'f',
+  prorettype => 'int4multirange', proargtypes => '',
+  prosrc => 'multirange_constructor0' },
+{ oid => '8146', descr => 'int4multirange constructor',
+  proname => 'int4multirange', proisstrict => 't',
+  prorettype => 'int4multirange', proargtypes => 'int4range',
+  prosrc => 'multirange_constructor1' },
+{ oid => '8046', descr => 'int4multirange constructor',
+  proname => 'int4multirange', provariadic => 'int4range', proisstrict => 'f',
+  prorettype => 'int4multirange', proargtypes => '_int4range',
+  proallargtypes => '{_int4range}', proargmodes => '{v}',
+  prosrc => 'multirange_constructor2' },
+{ oid => '8047', descr => 'nummultirange constructor',
+  proname => 'nummultirange', proisstrict => 'f',
+  prorettype => 'nummultirange', proargtypes => '',
+  prosrc => 'multirange_constructor0' },
+{ oid => '8147', descr => 'nummultirange constructor',
+  proname => 'nummultirange', proisstrict => 't',
+  prorettype => 'nummultirange', proargtypes => 'numrange',
+  prosrc => 'multirange_constructor1' },
+{ oid => '8048', descr => 'nummultirange constructor',
+  proname => 'nummultirange', provariadic => 'numrange', proisstrict => 'f',
+  prorettype => 'nummultirange', proargtypes => '_numrange',
+  proallargtypes => '{_numrange}', proargmodes => '{v}',
+  prosrc => 'multirange_constructor2' },
+{ oid => '8049', descr => 'tsmultirange constructor',
+  proname => 'tsmultirange', proisstrict => 'f',
+  prorettype => 'tsmultirange', proargtypes => '',
+  prosrc => 'multirange_constructor0' },
+{ oid => '8148', descr => 'tsmultirange constructor',
+  proname => 'tsmultirange', proisstrict => 't',
+  prorettype => 'tsmultirange', proargtypes => 'tsrange',
+  prosrc => 'multirange_constructor1' },
+{ oid => '8050', descr => 'tsmultirange constructor',
+  proname => 'tsmultirange', provariadic => 'tsrange', proisstrict => 'f',
+  prorettype => 'tsmultirange', proargtypes => '_tsrange',
+  proallargtypes => '{_tsrange}', proargmodes => '{v}',
+  prosrc => 'multirange_constructor2' },
+{ oid => '8051', descr => 'tstzmultirange constructor',
+  proname => 'tstzmultirange', proisstrict => 'f',
+  prorettype => 'tstzmultirange', proargtypes => '',
+  prosrc => 'multirange_constructor0' },
+{ oid => '8149', descr => 'tstzmultirange constructor',
+  proname => 'tstzmultirange', proisstrict => 't',
+  prorettype => 'tstzmultirange', proargtypes => 'tstzrange',
+  prosrc => 'multirange_constructor1' },
+{ oid => '8052', descr => 'tstzmultirange constructor',
+  proname => 'tstzmultirange', provariadic => 'tstzrange', proisstrict => 'f',
+  prorettype => 'tstzmultirange', proargtypes => '_tstzrange',
+  proallargtypes => '{_tstzrange}', proargmodes => '{v}',
+  prosrc => 'multirange_constructor2' },
+{ oid => '8053', descr => 'datemultirange constructor',
+  proname => 'datemultirange', proisstrict => 'f',
+  prorettype => 'datemultirange', proargtypes => '',
+  prosrc => 'multirange_constructor0' },
+{ oid => '8150', descr => 'datemultirange constructor',
+  proname => 'datemultirange', proisstrict => 't',
+  prorettype => 'datemultirange', proargtypes => 'daterange',
+  prosrc => 'multirange_constructor1' },
+{ oid => '8054', descr => 'datemultirange constructor',
+  proname => 'datemultirange', provariadic => 'daterange', proisstrict => 'f',
+  prorettype => 'datemultirange', proargtypes => '_daterange',
+  proallargtypes => '{_daterange}', proargmodes => '{v}',
+  prosrc => 'multirange_constructor2' },
+{ oid => '8055', descr => 'int8multirange constructor',
+  proname => 'int8multirange', proisstrict => 'f',
+  prorettype => 'int8multirange', proargtypes => '',
+  prosrc => 'multirange_constructor0' },
+{ oid => '8151', descr => 'int8multirange constructor',
+  proname => 'int8multirange', proisstrict => 't',
+  prorettype => 'int8multirange', proargtypes => 'int8range',
+  prosrc => 'multirange_constructor1' },
+{ oid => '8056', descr => 'int8multirange constructor',
+  proname => 'int8multirange', provariadic => 'int8range', proisstrict => 'f',
+  prorettype => 'int8multirange', proargtypes => '_int8range',
+  proallargtypes => '{_int8range}', proargmodes => '{v}',
+  prosrc => 'multirange_constructor2' },
+{ oid => '8152', descr => 'anymultirange cast',
+  proname => 'multirange', proisstrict => 't',
+  prorettype => 'anymultirange', proargtypes => 'anyrange',
+  prosrc => 'multirange_constructor1' },
+{ oid => '8130', descr => 'aggregate transition function',
+  proname => 'range_agg_transfn', proisstrict => 'f', prorettype => 'internal',
+  proargtypes => 'internal anyrange', prosrc => 'range_agg_transfn' },
+{ oid => '8131', descr => 'aggregate final function',
+  proname => 'range_agg_finalfn', proisstrict => 'f', prorettype => 'anymultirange',
+  proargtypes => 'internal anyrange', prosrc => 'range_agg_finalfn' },
+{ oid => '8132', descr => 'combine aggregate input into a multirange',
+  proname => 'range_agg', prokind => 'a', proisstrict => 'f',
+  prorettype => 'anymultirange', proargtypes => 'anyrange',
+  prosrc => 'aggregate_dummy' },
+{ oid => '8135', descr => 'range aggregate by intersecting',
+  proname => 'multirange_intersect_agg_transfn', prorettype => 'anymultirange',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_intersect_agg_transfn'},
+{ oid => '8136', descr => 'range aggregate by intersecting',
+  proname => 'range_intersect_agg', prokind => 'a', proisstrict => 'f',
+  prorettype => 'anymultirange', proargtypes => 'anymultirange',
+  prosrc => 'aggregate_dummy'},
+
 # date, time, timestamp constructors
 { oid => '3846', descr => 'construct date',
   proname => 'make_date', prorettype => 'date', proargtypes => 'int4 int4 int4',
@@ -10316,6 +10590,14 @@
   proname => 'binary_upgrade_set_next_array_pg_type_oid', provolatile => 'v',
   proparallel => 'r', prorettype => 'void', proargtypes => 'oid',
   prosrc => 'binary_upgrade_set_next_array_pg_type_oid' },
+{ oid => '8144', descr => 'for use by pg_upgrade',
+  proname => 'binary_upgrade_set_next_multirange_pg_type_oid', provolatile => 'v',
+  proparallel => 'r', prorettype => 'void', proargtypes => 'oid',
+  prosrc => 'binary_upgrade_set_next_multirange_pg_type_oid' },
+{ oid => '8145', descr => 'for use by pg_upgrade',
+  proname => 'binary_upgrade_set_next_multirange_array_pg_type_oid', provolatile => 'v',
+  proparallel => 'r', prorettype => 'void', proargtypes => 'oid',
+  prosrc => 'binary_upgrade_set_next_multirange_array_pg_type_oid' },
 { oid => '3586', descr => 'for use by pg_upgrade',
   proname => 'binary_upgrade_set_next_heap_pg_class_oid', provolatile => 'v',
   proparallel => 'r', prorettype => 'void', proargtypes => 'oid',
diff --git a/src/include/catalog/pg_range.dat b/src/include/catalog/pg_range.dat
index 479754c245..10060255c9 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',
+  rngmultitypid => 'int4multirange' },
 { rngtypid => 'numrange', rngsubtype => 'numeric',
   rngsubopc => 'btree/numeric_ops', rngcanonical => '-',
-  rngsubdiff => 'numrange_subdiff' },
+  rngsubdiff => 'numrange_subdiff', rngmultitypid => 'nummultirange' },
 { rngtypid => 'tsrange', rngsubtype => 'timestamp',
   rngsubopc => 'btree/timestamp_ops', rngcanonical => '-',
-  rngsubdiff => 'tsrange_subdiff' },
+  rngsubdiff => 'tsrange_subdiff', rngmultitypid => 'tsmultirange' },
 { rngtypid => 'tstzrange', rngsubtype => 'timestamptz',
   rngsubopc => 'btree/timestamptz_ops', rngcanonical => '-',
-  rngsubdiff => 'tstzrange_subdiff' },
+  rngsubdiff => 'tstzrange_subdiff', rngmultitypid => 'tstzmultirange' },
 { rngtypid => 'daterange', rngsubtype => 'date', rngsubopc => 'btree/date_ops',
-  rngcanonical => 'daterange_canonical', rngsubdiff => 'daterange_subdiff' },
+  rngcanonical => 'daterange_canonical', rngsubdiff => 'daterange_subdiff',
+  rngmultitypid => 'datemultirange' },
 { rngtypid => 'int8range', rngsubtype => 'int8', rngsubopc => 'btree/int8_ops',
-  rngcanonical => 'int8range_canonical', rngsubdiff => 'int8range_subdiff' },
+  rngcanonical => 'int8range_canonical', rngsubdiff => 'int8range_subdiff',
+  rngmultitypid => 'int8multirange' },
 
 ]
diff --git a/src/include/catalog/pg_range.h b/src/include/catalog/pg_range.h
index 98172bb1b6..60a8fde518 100644
--- a/src/include/catalog/pg_range.h
+++ b/src/include/catalog/pg_range.h
@@ -34,6 +34,9 @@ CATALOG(pg_range,3541,RangeRelationId)
 	/* OID of range's element type (subtype) */
 	Oid			rngsubtype BKI_LOOKUP(pg_type);
 
+	/* OID of the range's multirange type */
+	Oid			rngmultitypid BKI_LOOKUP(pg_type);
+
 	/* collation for this range type, or 0 */
 	Oid			rngcollation BKI_DEFAULT(0);
 
@@ -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 b2cec07416..1f97203440 100644
--- a/src/include/catalog/pg_type.dat
+++ b/src/include/catalog/pg_type.dat
@@ -500,6 +500,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 => 'R', 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 => 'R', 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 => 'R', 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 => 'R', 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 => 'R', 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 => 'R', 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
@@ -629,5 +663,16 @@
   typtype => 'p', typcategory => 'P', typinput => 'anycompatiblerange_in',
   typoutput => 'anycompatiblerange_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' },
+{ oid => '8153',
+  descr => 'pseudo-type representing a multirange over a polymorphic common type',
+  typname => 'anycompatiblemultirange', typlen => '-1', typbyval => 'f',
+  typtype => 'p', typcategory => 'P', typinput => 'anycompatiblemultirange_in',
+  typoutput => 'anycompatiblemultirange_out', typreceive => '-', typsend => '-',
+  typalign => 'd', typstorage => 'x' },
 
 ]
diff --git a/src/include/catalog/pg_type.h b/src/include/catalog/pg_type.h
index 7b37562648..63e7c940a2 100644
--- a/src/include/catalog/pg_type.h
+++ b/src/include/catalog/pg_type.h
@@ -263,6 +263,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 */
 
@@ -304,13 +305,15 @@ typedef FormData_pg_type *Form_pg_type;
 	 (typid) == ANYARRAYOID || \
 	 (typid) == ANYNONARRAYOID || \
 	 (typid) == ANYENUMOID || \
-	 (typid) == ANYRANGEOID)
+	 (typid) == ANYRANGEOID || \
+	 (typid) == ANYMULTIRANGEOID)
 
 #define IsPolymorphicTypeFamily2(typid)  \
 	((typid) == ANYCOMPATIBLEOID || \
 	 (typid) == ANYCOMPATIBLEARRAYOID || \
 	 (typid) == ANYCOMPATIBLENONARRAYOID || \
-	 (typid) == ANYCOMPATIBLERANGEOID)
+	 (typid) == ANYCOMPATIBLERANGEOID || \
+	 (typid) == ANYCOMPATIBLEMULTIRANGEOID)
 
 #endif							/* EXPOSE_TO_CLIENT_CODE */
 
@@ -369,4 +372,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/commands/typecmds.h b/src/include/commands/typecmds.h
index 23130895af..989914dfe1 100644
--- a/src/include/commands/typecmds.h
+++ b/src/include/commands/typecmds.h
@@ -29,6 +29,8 @@ extern ObjectAddress DefineRange(CreateRangeStmt *stmt);
 extern ObjectAddress AlterEnum(AlterEnumStmt *stmt);
 extern ObjectAddress DefineCompositeType(RangeVar *typevar, List *coldeflist);
 extern Oid	AssignTypeArrayOid(void);
+extern Oid	AssignTypeMultirangeOid(void);
+extern Oid	AssignTypeMultirangeArrayOid(void);
 
 extern ObjectAddress AlterDomainDefault(List *names, Node *defaultRaw);
 extern ObjectAddress AlterDomainNotNull(List *names, bool notNull);
diff --git a/src/include/utils/lsyscache.h b/src/include/utils/lsyscache.h
index fecfe1f4f6..5eb00239b4 100644
--- a/src/include/utils/lsyscache.h
+++ b/src/include/utils/lsyscache.h
@@ -157,6 +157,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);
@@ -183,6 +184,8 @@ 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_collation(Oid rangeOid);
+extern Oid	get_range_multirange(Oid rangeOid);
+extern Oid	get_range_multirange_subtype(Oid multirangeOid);
 extern Oid	get_index_column_opclass(Oid index_oid, int attno);
 extern bool get_index_isreplident(Oid index_oid);
 extern bool get_index_isvalid(Oid index_oid);
diff --git a/src/include/utils/multirangetypes.h b/src/include/utils/multirangetypes.h
new file mode 100644
index 0000000000..1688bb1df0
--- /dev/null
+++ b/src/include/utils/multirangetypes.h
@@ -0,0 +1,144 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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"
+#include "utils/expandeddatum.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
+{
+	int32		vl_len_;		/* 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, as ShortRangeType
+	 * structs. 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;
+
+/*
+ * Since each RangeType includes its type oid, it seems wasteful to store them
+ * in multiranges like that on-disk. Instead we store ShortRangeType structs
+ * that have everything but the type varlena header and oid. But we do want the
+ * type oid in memory, so that we can call ordinary range functions. We use the
+ * EXPANDED TOAST mechansim to convert between them.
+ */
+typedef struct
+{
+	int32		vl_len_;		/* varlena header */
+	char		flags;			/* range flags */
+	char		_padding;		/* Bounds must be aligned */
+	/* Following the header are zero to two bound values. */
+} ShortRangeType;
+
+#define EMR_MAGIC 689375834		/* ID for debugging crosschecks */
+
+typedef struct ExpandedMultirangeHeader
+{
+	/* Standard header for expanded objects */
+	ExpandedObjectHeader hdr;
+
+	/* Magic value identifying an expanded array (for debugging only) */
+	int			emr_magic;
+
+	Oid			multirangetypid;	/* multirange type's own OID */
+	uint32		rangeCount;			/* the number of ranges */
+	RangeType   **ranges;			/* array of ranges */
+
+	/*
+	 * flat_size is the current space requirement for the flat equivalent of
+	 * the expanded multirange, if known; otherwise it's 0.  We store this to make
+	 * consecutive calls of get_flat_size cheap.
+	 */
+	Size		flat_size;
+} ExpandedMultirangeHeader;
+
+/* 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);
+extern bool multirange_contains_elem_internal(TypeCacheEntry *typcache, MultirangeType *mr,
+											  Datum elem);
+extern bool multirange_contains_range_internal(TypeCacheEntry *typcache, MultirangeType *mr,
+											   RangeType *r);
+extern bool multirange_contains_multirange_internal(TypeCacheEntry *typcache,
+													MultirangeType *mr1,
+													MultirangeType *mr2);
+extern bool range_overlaps_multirange_internal(TypeCacheEntry *typcache, RangeType *r,
+											   MultirangeType *mr);
+extern bool multirange_overlaps_multirange_internal(TypeCacheEntry *typcache,
+													MultirangeType *mr1,
+													MultirangeType *mr2);
+extern bool range_before_multirange_internal(TypeCacheEntry *typcache, RangeType *r,
+											 MultirangeType *mr);
+extern bool range_after_multirange_internal(TypeCacheEntry *typcache, RangeType *r,
+											MultirangeType *mr);
+extern bool multirange_before_multirange_internal(TypeCacheEntry *typcache,
+												  MultirangeType *mr1,
+												  MultirangeType *mr2);
+extern MultirangeType *multirange_minus_internal(Oid mltrngtypoid,
+												 TypeCacheEntry *rangetyp,
+												 int32 range_count1,
+												 RangeType **ranges1,
+												 int32 range_count2,
+												 RangeType **ranges2);
+extern MultirangeType *multirange_intersect_internal(Oid mltrngtypoid,
+													 TypeCacheEntry *rangetyp,
+													 int32 range_count1,
+													 RangeType **ranges1,
+													 int32 range_count2,
+													 RangeType **ranges2);
+
+/* assorted support functions */
+extern TypeCacheEntry *multirange_get_typcache(FunctionCallInfo fcinfo,
+											   Oid mltrngtypid);
+extern void multirange_deserialize(TypeCacheEntry *rangetyp,
+								   const 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);
+
+extern Datum expand_multirange(Datum multirangedatum, MemoryContext parentcontext, TypeCacheEntry *rangetyp);
+
+#endif							/* MULTIRANGETYPES_H */
diff --git a/src/include/utils/rangetypes.h b/src/include/utils/rangetypes.h
index b77c41cf1b..139e19c7d6 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)
 
@@ -92,13 +94,21 @@ typedef struct
  * prototypes for functions defined in rangetypes.c
  */
 
-extern bool range_contains_elem_internal(TypeCacheEntry *typcache, const RangeType *r, Datum val);
-
 /* internal versions of the above */
+extern int	range_cmp_internal(TypeCacheEntry *typcache, const RangeType *r1,
+							   const RangeType *r2);
+extern uint32 hash_range_internal(TypeCacheEntry *typcache, const RangeType *r);
+extern uint64 hash_range_extended_internal(TypeCacheEntry *typcache, const RangeType *r,
+										   Datum seed);
+extern Datum range_lower_internal(TypeCacheEntry *typcache, const RangeType *r1,
+								  bool *isnull);
+extern Datum range_upper_internal(TypeCacheEntry *typcache, const RangeType *r1,
+								  bool *isnull);
 extern bool range_eq_internal(TypeCacheEntry *typcache, const RangeType *r1,
 							  const RangeType *r2);
 extern bool range_ne_internal(TypeCacheEntry *typcache, const RangeType *r1,
 							  const RangeType *r2);
+extern bool range_contains_elem_internal(TypeCacheEntry *typcache, const RangeType *r, Datum val);
 extern bool range_contains_internal(TypeCacheEntry *typcache, const RangeType *r1,
 									const RangeType *r2);
 extern bool range_contained_by_internal(TypeCacheEntry *typcache, const RangeType *r1,
@@ -115,8 +125,18 @@ extern bool range_overleft_internal(TypeCacheEntry *typcache, const RangeType *r
 									const RangeType *r2);
 extern bool range_overright_internal(TypeCacheEntry *typcache, const RangeType *r1,
 									 const RangeType *r2);
+extern RangeType *range_union_internal(TypeCacheEntry *typcache, RangeType *r1,
+									   RangeType *r2, bool strict);
+extern RangeType *range_minus_internal(TypeCacheEntry *typcache, RangeType *r1,
+									   RangeType *r2);
+extern RangeType *range_intersect_internal(TypeCacheEntry *typcache, const RangeType *r1,
+										   const RangeType *r2);
 
 /* assorted support functions */
+extern Size datum_compute_size(Size sz, Datum datum, bool typbyval,
+							   char typalign, int16 typlen, char typstorage);
+extern Pointer datum_write(Pointer ptr, Datum datum, bool typbyval,
+						   char typalign, int16 typlen, char typstorage);
 extern TypeCacheEntry *range_get_typcache(FunctionCallInfo fcinfo,
 										  Oid rngtypid);
 extern RangeType *range_serialize(TypeCacheEntry *typcache, RangeBound *lower,
@@ -125,6 +145,7 @@ extern void range_deserialize(TypeCacheEntry *typcache, const RangeType *range,
 							  RangeBound *lower, RangeBound *upper,
 							  bool *empty);
 extern char range_get_flags(const RangeType *range);
+extern bool range_has_flag(const RangeType *range, char flag);
 extern void range_set_contain_empty(RangeType *range);
 extern RangeType *make_range(TypeCacheEntry *typcache, RangeBound *lower,
 							 RangeBound *upper, bool empty);
@@ -132,8 +153,12 @@ extern int	range_cmp_bounds(TypeCacheEntry *typcache, const RangeBound *b1,
 							 const RangeBound *b2);
 extern int	range_cmp_bound_values(TypeCacheEntry *typcache, const RangeBound *b1,
 								   const RangeBound *b2);
+extern int	range_compare(const void *key1, const void *key2, void *arg);
 extern bool bounds_adjacent(TypeCacheEntry *typcache, RangeBound bound1,
 							RangeBound bound2);
 extern RangeType *make_empty_range(TypeCacheEntry *typcache);
+extern bool range_split_internal(TypeCacheEntry *typcache, const RangeType *r1,
+								 const RangeType *r2, RangeType **output1,
+								 RangeType **output2);
 
 #endif							/* RANGETYPES_H */
diff --git a/src/include/utils/selfuncs.h b/src/include/utils/selfuncs.h
index 7ac4a06391..201a5f6a1c 100644
--- a/src/include/utils/selfuncs.h
+++ b/src/include/utils/selfuncs.h
@@ -39,6 +39,9 @@
 /* default selectivity estimate for range inequalities "A > b AND A < c" */
 #define DEFAULT_RANGE_INEQ_SEL	0.005
 
+/* default selectivity estimate for multirange inequalities "A > b AND A < c" */
+#define DEFAULT_MULTIRANGE_INEQ_SEL	0.005
+
 /* default selectivity estimate for pattern-match operators such as LIKE */
 #define DEFAULT_MATCH_SEL	0.005
 
diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h
index f27b73d76d..e8f393520a 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -79,6 +79,7 @@ enum SysCacheIdentifier
 	PUBLICATIONOID,
 	PUBLICATIONREL,
 	PUBLICATIONRELMAP,
+	RANGEMULTIRANGE,
 	RANGETYPE,
 	RELNAMENSP,
 	RELOID,
diff --git a/src/include/utils/typcache.h b/src/include/utils/typcache.h
index cdd20e56d7..33f332a141 100644
--- a/src/include/utils/typcache.h
+++ b/src/include/utils/typcache.h
@@ -100,6 +100,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;	/* multirange's range underlying type */
+
 	/*
 	 * Domain's base type and typmod if it's a domain type.  Zeroes if not
 	 * domain, or if information hasn't been requested.
@@ -143,6 +148,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 e7f4a5f291..978d25620b 100644
--- a/src/pl/plpgsql/src/pl_comp.c
+++ b/src/pl/plpgsql/src/pl_comp.c
@@ -513,6 +513,8 @@ do_compile(FunctionCallInfo fcinfo,
 					else if (rettypeid == ANYRANGEOID ||
 							 rettypeid == ANYCOMPATIBLERANGEOID)
 						rettypeid = INT4RANGEOID;
+					else if (rettypeid == ANYMULTIRANGEOID)
+						rettypeid = INT4MULTIRANGEOID;
 					else		/* ANYELEMENT or ANYNONARRAY or ANYCOMPATIBLE */
 						rettypeid = INT4OID;
 					/* XXX what could we use for ANYENUM? */
@@ -2107,6 +2109,7 @@ build_datatype(HeapTuple typeTup, int32 typmod,
 		case TYPTYPE_BASE:
 		case TYPTYPE_ENUM:
 		case TYPTYPE_RANGE:
+		case TYPTYPE_MULTIRANGE:
 			typ->ttype = PLPGSQL_TTYPE_SCALAR;
 			break;
 		case TYPTYPE_COMPOSITE:
@@ -2525,6 +2528,9 @@ plpgsql_resolve_polymorphic_argtypes(int numargs,
 				case ANYCOMPATIBLERANGEOID:
 					argtypes[i] = INT4RANGEOID;
 					break;
+				case ANYMULTIRANGEOID:
+					argtypes[i] = INT4MULTIRANGEOID;
+					break;
 				default:
 					break;
 			}
diff --git a/src/test/regress/expected/dependency.out b/src/test/regress/expected/dependency.out
index b4a11b8aa9..8232795148 100644
--- a/src/test/regress/expected/dependency.out
+++ b/src/test/regress/expected/dependency.out
@@ -140,6 +140,7 @@ owner of sequence deptest_a_seq
 owner of table deptest
 owner of function deptest_func()
 owner of type deptest_enum
+owner of type deptest_multirange
 owner of type deptest_range
 owner of table deptest2
 owner of sequence ss1
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..e72f99863f
--- /dev/null
+++ b/src/test/regress/expected/multirangetypes.out
@@ -0,0 +1,2437 @@
+-- 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)
+
+--
+-- test casts, both a built-in range type and a user-defined one:
+--
+select 'empty'::int4range::int4multirange;
+ int4multirange 
+----------------
+ {}
+(1 row)
+
+select int4range(1, 3)::int4multirange;
+ int4range 
+-----------
+ {[1,3)}
+(1 row)
+
+select int4range(1, null)::int4multirange;
+ int4range 
+-----------
+ {[1,)}
+(1 row)
+
+select int4range(null, null)::int4multirange;
+ int4range 
+-----------
+ {(,)}
+(1 row)
+
+select 'empty'::textrange::textmultirange;
+ textmultirange 
+----------------
+ {}
+(1 row)
+
+select textrange('a', 'c')::textmultirange;
+ textrange 
+-----------
+ {[a,c)}
+(1 row)
+
+select textrange('a', null)::textmultirange;
+ textrange 
+-----------
+ {[a,)}
+(1 row)
+
+select textrange(null, null)::textmultirange;
+ textrange 
+-----------
+ {(,)}
+(1 row)
+
+--
+-- create some test data and test the operators
+--
+CREATE TABLE nummultirange_test (nmr NUMMULTIRANGE);
+CREATE INDEX nummultirange_test_btree ON nummultirange_test(nmr);
+INSERT INTO nummultirange_test VALUES('{}');
+INSERT INTO nummultirange_test VALUES('{[,)}');
+INSERT INTO nummultirange_test VALUES('{[3,]}');
+INSERT INTO nummultirange_test VALUES('{[,), [3,]}');
+INSERT INTO nummultirange_test VALUES('{[, 5)}');
+INSERT INTO nummultirange_test VALUES(nummultirange());
+INSERT INTO nummultirange_test VALUES(nummultirange(variadic '{}'::numrange[]));
+INSERT INTO nummultirange_test VALUES(nummultirange(numrange(1.1, 2.2)));
+INSERT INTO nummultirange_test VALUES('{empty}');
+INSERT INTO nummultirange_test VALUES(nummultirange(numrange(1.7, 1.7, '[]'), numrange(1.7, 1.9)));
+INSERT INTO nummultirange_test VALUES(nummultirange(numrange(1.7, 1.7, '[]'), numrange(1.9, 2.1)));
+SELECT nmr, isempty(nmr), lower(nmr), upper(nmr) FROM nummultirange_test ORDER BY nmr;
+          nmr          | isempty | lower | upper 
+-----------------------+---------+-------+-------
+ {}                    | t       |       |      
+ {}                    | t       |       |      
+ {}                    | t       |       |      
+ {}                    | t       |       |      
+ {(,5)}                | f       |       |     5
+ {(,)}                 | f       |       |      
+ {(,)}                 | f       |       |      
+ {[1.1,2.2)}           | f       |   1.1 |   2.2
+ {[1.7,1.7],[1.9,2.1)} | f       |   1.7 |   2.1
+ {[1.7,1.9)}           | f       |   1.7 |   1.9
+ {[3,)}                | f       |     3 |      
+(11 rows)
+
+SELECT nmr, lower_inc(nmr), lower_inf(nmr), upper_inc(nmr), upper_inf(nmr) FROM nummultirange_test ORDER BY nmr;
+          nmr          | lower_inc | lower_inf | upper_inc | upper_inf 
+-----------------------+-----------+-----------+-----------+-----------
+ {}                    | f         | f         | f         | f
+ {}                    | f         | f         | f         | f
+ {}                    | f         | f         | f         | f
+ {}                    | f         | f         | f         | f
+ {(,5)}                | f         | t         | f         | f
+ {(,)}                 | f         | t         | f         | t
+ {(,)}                 | f         | t         | f         | t
+ {[1.1,2.2)}           | t         | f         | f         | f
+ {[1.7,1.7],[1.9,2.1)} | t         | f         | f         | f
+ {[1.7,1.9)}           | t         | f         | f         | f
+ {[3,)}                | t         | f         | f         | t
+(11 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr = '{}';
+ nmr 
+-----
+ {}
+ {}
+ {}
+ {}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr = '{(,5)}';
+  nmr   
+--------
+ {(,5)}
+(1 row)
+
+SELECT * FROM nummultirange_test WHERE nmr = '{[3,)}';
+  nmr   
+--------
+ {[3,)}
+(1 row)
+
+SELECT * FROM nummultirange_test WHERE nmr = '{[1.7,1.7]}';
+ nmr 
+-----
+(0 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr = '{[1.7,1.7],[1.9,2.1)}';
+          nmr          
+-----------------------
+ {[1.7,1.7],[1.9,2.1)}
+(1 row)
+
+SELECT * FROM nummultirange_test WHERE nmr < '{}';
+ nmr 
+-----
+(0 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr < '{[-1000.0, -1000.0]}';
+  nmr   
+--------
+ {}
+ {(,)}
+ {(,)}
+ {(,5)}
+ {}
+ {}
+ {}
+(7 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr < '{[0.0, 1.0]}';
+  nmr   
+--------
+ {}
+ {(,)}
+ {(,)}
+ {(,5)}
+ {}
+ {}
+ {}
+(7 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr < '{[1000.0, 1001.0]}';
+          nmr          
+-----------------------
+ {}
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+ {}
+ {}
+ {[1.1,2.2)}
+ {}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(11 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr <= '{}';
+ nmr 
+-----
+ {}
+ {}
+ {}
+ {}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr <= '{[3,)}';
+          nmr          
+-----------------------
+ {}
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+ {}
+ {}
+ {[1.1,2.2)}
+ {}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(11 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr >= '{}';
+          nmr          
+-----------------------
+ {}
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+ {}
+ {}
+ {[1.1,2.2)}
+ {}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(11 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr >= '{[3,)}';
+  nmr   
+--------
+ {[3,)}
+(1 row)
+
+SELECT * FROM nummultirange_test WHERE nmr > '{}';
+          nmr          
+-----------------------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+ {[1.1,2.2)}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(7 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr > '{[-1000.0, -1000.0]}';
+          nmr          
+-----------------------
+ {[3,)}
+ {[1.1,2.2)}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr > '{[0.0, 1.0]}';
+          nmr          
+-----------------------
+ {[3,)}
+ {[1.1,2.2)}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr > '{[1000.0, 1001.0]}';
+ nmr 
+-----
+(0 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr <> '{}';
+          nmr          
+-----------------------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+ {[1.1,2.2)}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(7 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr <> '{(,5)}';
+          nmr          
+-----------------------
+ {}
+ {(,)}
+ {[3,)}
+ {(,)}
+ {}
+ {}
+ {[1.1,2.2)}
+ {}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(10 rows)
+
+select nummultirange(numrange(2.0, 1.0));
+ERROR:  range lower bound must be less than or equal to range upper bound
+select nummultirange(numrange(5.0, 6.0), numrange(1.0, 2.0));
+     nummultirange     
+-----------------------
+ {[1.0,2.0),[5.0,6.0)}
+(1 row)
+
+analyze nummultirange_test;
+-- overlaps
+SELECT * FROM nummultirange_test WHERE range_overlaps_multirange(numrange(4.0, 4.2), nmr);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE numrange(4.0, 4.2) && nmr;
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE multirange_overlaps_range(nmr, numrange(4.0, 4.2));
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr && numrange(4.0, 4.2);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE multirange_overlaps_multirange(nmr, nummultirange(numrange(4.0, 4.2), numrange(6.0, 7.0)));
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr && nummultirange(numrange(4.0, 4.2), numrange(6.0, 7.0));
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr && nummultirange(numrange(6.0, 7.0));
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+(3 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr && nummultirange(numrange(6.0, 7.0), numrange(8.0, 9.0));
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+(3 rows)
+
+-- mr contains x
+SELECT * FROM nummultirange_test WHERE multirange_contains_elem(nmr, 4.0);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr @> 4.0;
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE multirange_contains_range(nmr, numrange(4.0, 4.2));
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr @> numrange(4.0, 4.2);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE multirange_contains_multirange(nmr, '{[4.0,4.2), [6.0, 8.0)}');
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+(3 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr @> '{[4.0,4.2), [6.0, 8.0)}'::nummultirange;
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+(3 rows)
+
+-- x is contained by mr
+SELECT * FROM nummultirange_test WHERE elem_contained_by_multirange(4.0, nmr);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE 4.0 <@ nmr;
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE range_contained_by_multirange(numrange(4.0, 4.2), nmr);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE numrange(4.0, 4.2) <@ nmr;
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE multirange_contained_by_multirange('{[4.0,4.2), [6.0, 8.0)}', nmr);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+(3 rows)
+
+SELECT * FROM nummultirange_test WHERE '{[4.0,4.2), [6.0, 8.0)}'::nummultirange <@ nmr;
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+(3 rows)
+
+-- overlaps
+SELECT 'empty'::numrange && nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT 'empty'::numrange && nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() && 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) && 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() && nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() && nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) && nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) && nummultirange(numrange(1,2), numrange(7,8));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(7,8)) && nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) && nummultirange(numrange(1,2), numrange(3.5,8));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(3.5,8)) && numrange(3,4);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(3.5,8)) && nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+-- contains
+SELECT nummultirange() @> nummultirange();
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange() @> 'empty'::numrange;
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(null,null)) @> numrange(1,2);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(null,null)) @> numrange(null,2);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(null,null)) @> numrange(2,null);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(null,5)) @> numrange(null,3);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(null,5)) @> numrange(null,8);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(5,null)) @> numrange(8,null);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(5,null)) @> numrange(3,null);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,5)) @> numrange(8,9);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,5)) @> numrange(3,9);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,5)) @> numrange(1,4);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,5)) @> numrange(1,5);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(-4,-2), numrange(1,5)) @> numrange(1,5);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,5), numrange(8,9)) @> numrange(1,5);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,5), numrange(8,9)) @> numrange(6,7);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,5), numrange(6,9)) @> numrange(6,7);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[1,5)}'::nummultirange @> '{[1,5)}';
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[-4,-2), [1,5)}'::nummultirange @> '{[1,5)}';
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[1,5), [8,9)}'::nummultirange @> '{[1,5)}';
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[1,5), [8,9)}'::nummultirange @> '{[6,7)}';
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT '{[1,5), [6,9)}'::nummultirange @> '{[6,7)}';
+ ?column? 
+----------
+ t
+(1 row)
+
+-- is contained by
+SELECT nummultirange() <@ nummultirange();
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT 'empty'::numrange <@ nummultirange();
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,2) <@ nummultirange(numrange(null,null));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(null,2) <@ nummultirange(numrange(null,null));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(2,null) <@ nummultirange(numrange(null,null));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(null,3) <@ nummultirange(numrange(null,5));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(null,8) <@ nummultirange(numrange(null,5));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(8,null) <@ nummultirange(numrange(5,null));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(3,null) <@ nummultirange(numrange(5,null));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(8,9) <@ nummultirange(numrange(1,5));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(3,9) <@ nummultirange(numrange(1,5));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(1,4) <@ nummultirange(numrange(1,5));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,5) <@ nummultirange(numrange(1,5));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,5) <@ nummultirange(numrange(-4,-2), numrange(1,5));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,5) <@ nummultirange(numrange(1,5), numrange(8,9));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(6,7) <@ nummultirange(numrange(1,5), numrange(8,9));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(6,7) <@ nummultirange(numrange(1,5), numrange(6,9));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[1,5)}' <@ '{[1,5)}'::nummultirange;
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[1,5)}' <@ '{[-4,-2), [1,5)}'::nummultirange;
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[1,5)}' <@ '{[1,5), [8,9)}'::nummultirange;
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[6,7)}' <@ '{[1,5), [8,9)}'::nummultirange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT '{[6,7)}' <@ '{[1,5), [6,9)}'::nummultirange;
+ ?column? 
+----------
+ t
+(1 row)
+
+-- overleft
+SELECT 'empty'::numrange &< nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT 'empty'::numrange &< nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() &< 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) &< 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() &< nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) &< nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() &< nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(6,7) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(1,2) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,4) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,6) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(3.5,6) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(6,7)) &< numrange(3,4);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) &< numrange(3,4);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,4)) &< numrange(3,4);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,6)) &< numrange(3,4);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3.5,6)) &< numrange(3,4);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(6,7)) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,4)) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,6)) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3.5,6)) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+-- overright
+SELECT nummultirange() &> 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) &> 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT 'empty'::numrange &> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT 'empty'::numrange &> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() &> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() &> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) &> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> numrange(6,7);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> numrange(1,2);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> numrange(1,4);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> numrange(1,6);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> numrange(3.5,6);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(3,4) &> nummultirange(numrange(6,7));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(3,4) &> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(3,4) &> nummultirange(numrange(1,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(3,4) &> nummultirange(numrange(1,6));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(3,4) &> nummultirange(numrange(3.5,6));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(6,7));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(1,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(1,6));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(3.5,6));
+ ?column? 
+----------
+ f
+(1 row)
+
+-- meets
+SELECT 'empty'::numrange -|- nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT 'empty'::numrange -|- nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() -|- 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() -|- nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() -|- nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(1,2) -|- nummultirange(numrange(2,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,2) -|- nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- numrange(2,4);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- numrange(3,4);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- nummultirange(numrange(2,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(5,6)) -|- nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(5,6)) -|- nummultirange(numrange(6,7));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(5,6)) -|- nummultirange(numrange(8,9));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- nummultirange(numrange(2,4), numrange(6,7));
+ ?column? 
+----------
+ t
+(1 row)
+
+-- strictly left
+select 'empty'::numrange << nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(1,2) << nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(1,2) << nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+select numrange(1,2) << nummultirange(numrange(0,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(1,2) << nummultirange(numrange(0,4), numrange(7,8));
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange() << 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange() << numrange(1,2);
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(3,4)) << numrange(3,6);
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(0,2)) << numrange(3,6);
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange(numrange(0,2), numrange(7,8)) << numrange(3,6);
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(-4,-2), numrange(0,2)) << numrange(3,6);
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange() << nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange() << nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(1,2)) << nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(1,2)) << nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(1,2)) << nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange(numrange(1,2)) << nummultirange(numrange(3,4), numrange(7,8));
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange(numrange(1,2), numrange(4,5)) << nummultirange(numrange(3,4), numrange(7,8));
+ ?column? 
+----------
+ f
+(1 row)
+
+-- strictly right
+select nummultirange() >> 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange() >> numrange(1,2);
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(3,4)) >> numrange(1,2);
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange(numrange(0,4)) >> numrange(1,2);
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(0,4), numrange(7,8)) >> numrange(1,2);
+ ?column? 
+----------
+ f
+(1 row)
+
+select 'empty'::numrange >> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(1,2) >> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(3,6) >> nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(3,6) >> nummultirange(numrange(0,2));
+ ?column? 
+----------
+ t
+(1 row)
+
+select numrange(3,6) >> nummultirange(numrange(0,2), numrange(7,8));
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(3,6) >> nummultirange(numrange(-4,-2), numrange(0,2));
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange() >> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(1,2)) >> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange() >> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(1,2)) >> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(3,4)) >> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange(numrange(3,4), numrange(7,8)) >> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange(numrange(3,4), numrange(7,8)) >> nummultirange(numrange(1,2), numrange(4,5));
+ ?column? 
+----------
+ f
+(1 row)
+
+-- union
+SELECT nummultirange() + nummultirange();
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange() + nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) + nummultirange();
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) + nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) + nummultirange(numrange(2,4));
+ ?column? 
+----------
+ {[1,4)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) + nummultirange(numrange(3,4));
+   ?column?    
+---------------
+ {[1,2),[3,4)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) + nummultirange(numrange(2,4));
+ ?column? 
+----------
+ {[1,5)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) + nummultirange(numrange(3,4));
+   ?column?    
+---------------
+ {[1,2),[3,5)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) + nummultirange(numrange(0,9));
+ ?column? 
+----------
+ {[0,9)}
+(1 row)
+
+-- merge
+SELECT range_merge(nummultirange());
+ range_merge 
+-------------
+ empty
+(1 row)
+
+SELECT range_merge(nummultirange(numrange(1,2)));
+ range_merge 
+-------------
+ [1,2)
+(1 row)
+
+SELECT range_merge(nummultirange(numrange(1,2), numrange(7,8)));
+ range_merge 
+-------------
+ [1,8)
+(1 row)
+
+-- minus
+SELECT nummultirange() - nummultirange();
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange() - nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) - nummultirange();
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(3,4)) - nummultirange();
+   ?column?    
+---------------
+ {[1,2),[3,4)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) - nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) - nummultirange(numrange(2,4));
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) - nummultirange(numrange(3,4));
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {[2,4)}
+(1 row)
+
+SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(2,3));
+   ?column?    
+---------------
+ {[1,2),[3,4)}
+(1 row)
+
+SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(0,8));
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(0,2));
+ ?column? 
+----------
+ {[2,4)}
+(1 row)
+
+SELECT nummultirange(numrange(1,8)) - nummultirange(numrange(0,2), numrange(3,4));
+   ?column?    
+---------------
+ {[2,3),[4,8)}
+(1 row)
+
+SELECT nummultirange(numrange(1,8)) - nummultirange(numrange(2,3), numrange(5,null));
+   ?column?    
+---------------
+ {[1,2),[3,5)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(-2,0));
+   ?column?    
+---------------
+ {[1,2),[4,5)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(2,4));
+   ?column?    
+---------------
+ {[1,2),[4,5)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(3,5));
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(0,9));
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange(numrange(1,3), numrange(4,5)) - nummultirange(numrange(2,9));
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(8,9));
+   ?column?    
+---------------
+ {[1,2),[4,5)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(-2,0), numrange(8,9));
+   ?column?    
+---------------
+ {[1,2),[4,5)}
+(1 row)
+
+-- intersection
+SELECT nummultirange() * nummultirange();
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange() * nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) * nummultirange();
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT '{[1,3)}'::nummultirange * '{[1,5)}'::nummultirange;
+ ?column? 
+----------
+ {[1,3)}
+(1 row)
+
+SELECT '{[1,3)}'::nummultirange * '{[0,5)}'::nummultirange;
+ ?column? 
+----------
+ {[1,3)}
+(1 row)
+
+SELECT '{[1,3)}'::nummultirange * '{[0,2)}'::nummultirange;
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT '{[1,3)}'::nummultirange * '{[2,5)}'::nummultirange;
+ ?column? 
+----------
+ {[2,3)}
+(1 row)
+
+SELECT '{[1,4)}'::nummultirange * '{[2,3)}'::nummultirange;
+ ?column? 
+----------
+ {[2,3)}
+(1 row)
+
+SELECT '{[1,4)}'::nummultirange * '{[0,2), [3,5)}'::nummultirange;
+   ?column?    
+---------------
+ {[1,2),[3,4)}
+(1 row)
+
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[0,8), [9,12)}'::nummultirange;
+       ?column?       
+----------------------
+ {[1,4),[7,8),[9,10)}
+(1 row)
+
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[9,12)}'::nummultirange;
+ ?column? 
+----------
+ {[9,10)}
+(1 row)
+
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[-5,-4), [5,6), [9,12)}'::nummultirange;
+ ?column? 
+----------
+ {[9,10)}
+(1 row)
+
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[0,2), [3,8), [9,12)}'::nummultirange;
+          ?column?          
+----------------------------
+ {[1,2),[3,4),[7,8),[9,10)}
+(1 row)
+
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[0,2), [3,8), [9,12)}'::nummultirange;
+          ?column?          
+----------------------------
+ {[1,2),[3,4),[7,8),[9,10)}
+(1 row)
+
+--
+-- range_agg function
+--
+create table reservations ( room_id integer not null, booked_during daterange );
+insert into reservations values
+-- 1: has a meets and a gap
+(1, daterange('2018-07-01', '2018-07-07')),
+(1, daterange('2018-07-07', '2018-07-14')),
+(1, daterange('2018-07-20', '2018-07-22')),
+-- 2: just a single row
+(2, daterange('2018-07-01', '2018-07-03')),
+-- 3: one null range
+(3, NULL),
+-- 4: two null ranges
+(4, NULL),
+(4, NULL),
+-- 5: a null range and a non-null range
+(5, NULL),
+(5, daterange('2018-07-01', '2018-07-03')),
+-- 6: has overlap
+(6, daterange('2018-07-01', '2018-07-07')),
+(6, daterange('2018-07-05', '2018-07-10')),
+-- 7: two ranges that meet: no gap or overlap
+(7, daterange('2018-07-01', '2018-07-07')),
+(7, daterange('2018-07-07', '2018-07-14')),
+-- 8: an empty range
+(8, 'empty'::daterange)
+;
+SELECT   room_id, range_agg(booked_during)
+FROM     reservations
+GROUP BY room_id
+ORDER BY room_id;
+ room_id |                     range_agg                     
+---------+---------------------------------------------------
+       1 | {[07-01-2018,07-14-2018),[07-20-2018,07-22-2018)}
+       2 | {[07-01-2018,07-03-2018)}
+       3 | 
+       4 | 
+       5 | {[07-01-2018,07-03-2018)}
+       6 | {[07-01-2018,07-10-2018)}
+       7 | {[07-01-2018,07-14-2018)}
+       8 | {}
+(8 rows)
+
+-- range_agg on a custom range type too
+SELECT  range_agg(r)
+FROM    (VALUES
+          ('[a,c]'::textrange),
+          ('[b,b]'::textrange),
+          ('[c,f]'::textrange),
+          ('[g,h)'::textrange),
+          ('[h,j)'::textrange)
+        ) t(r);
+   range_agg   
+---------------
+ {[a,f],[g,j)}
+(1 row)
+
+select range_intersect_agg(nmr) from nummultirange_test;
+ range_intersect_agg 
+---------------------
+ {}
+(1 row)
+
+select range_intersect_agg(nmr) from nummultirange_test where false;
+ range_intersect_agg 
+---------------------
+ 
+(1 row)
+
+-- test with just one input:
+select range_intersect_agg(nmr) from (values ('{[1,2]}'::nummultirange)) t(nmr);
+ range_intersect_agg 
+---------------------
+ {[1,2]}
+(1 row)
+
+select range_intersect_agg(nmr) from nummultirange_test where nmr @> 4.0;
+ range_intersect_agg 
+---------------------
+ {[3,5)}
+(1 row)
+
+create table nummultirange_test2(nmr nummultirange);
+create index nummultirange_test2_hash_idx on nummultirange_test2 using hash (nmr);
+INSERT INTO nummultirange_test2 VALUES('{[, 5)}');
+INSERT INTO nummultirange_test2 VALUES(nummultirange(numrange(1.1, 2.2)));
+INSERT INTO nummultirange_test2 VALUES(nummultirange(numrange(1.1, 2.2)));
+INSERT INTO nummultirange_test2 VALUES(nummultirange(numrange(1.1, 2.2,'()')));
+INSERT INTO nummultirange_test2 VALUES('{}');
+select * from nummultirange_test2 where nmr = '{}';
+ nmr 
+-----
+ {}
+(1 row)
+
+select * from nummultirange_test2 where nmr = nummultirange(numrange(1.1, 2.2));
+     nmr     
+-------------
+ {[1.1,2.2)}
+ {[1.1,2.2)}
+(2 rows)
+
+select * from nummultirange_test2 where nmr = nummultirange(numrange(1.1, 2.3));
+ nmr 
+-----
+(0 rows)
+
+set enable_nestloop=t;
+set enable_hashjoin=f;
+set enable_mergejoin=f;
+select * from nummultirange_test natural join nummultirange_test2 order by nmr;
+     nmr     
+-------------
+ {}
+ {}
+ {}
+ {}
+ {(,5)}
+ {[1.1,2.2)}
+ {[1.1,2.2)}
+(7 rows)
+
+set enable_nestloop=f;
+set enable_hashjoin=t;
+set enable_mergejoin=f;
+select * from nummultirange_test natural join nummultirange_test2 order by nmr;
+     nmr     
+-------------
+ {}
+ {}
+ {}
+ {}
+ {(,5)}
+ {[1.1,2.2)}
+ {[1.1,2.2)}
+(7 rows)
+
+set enable_nestloop=f;
+set enable_hashjoin=f;
+set enable_mergejoin=t;
+select * from nummultirange_test natural join nummultirange_test2 order by nmr;
+     nmr     
+-------------
+ {}
+ {}
+ {}
+ {}
+ {(,5)}
+ {[1.1,2.2)}
+ {[1.1,2.2)}
+(7 rows)
+
+set enable_nestloop to default;
+set enable_hashjoin to default;
+set enable_mergejoin to default;
+DROP TABLE nummultirange_test2;
+--
+-- Test user-defined multirange of floats
+--
+select '{[123.001, 5.e9)}'::float8multirange @> 888.882::float8;
+ ?column? 
+----------
+ t
+(1 row)
+
+create table float8multirange_test(f8mr float8multirange, i int);
+insert into float8multirange_test values(float8multirange(float8range(-100.00007, '1.111113e9')), 42);
+select * from float8multirange_test;
+           f8mr            | i  
+---------------------------+----
+ {[-100.00007,1111113000)} | 42
+(1 row)
+
+drop table float8multirange_test;
+--
+-- Test multirange types over domains
+--
+create domain mydomain as int4;
+create type mydomainrange as range(subtype=mydomain);
+select '{[4,50)}'::mydomainmultirange @> 7::mydomain;
+ ?column? 
+----------
+ t
+(1 row)
+
+drop domain mydomain cascade;
+NOTICE:  drop cascades to type mydomainrange
+--
+-- Test domains over multirange types
+--
+create domain restrictedmultirange as int4multirange check (upper(value) < 10);
+select '{[4,5)}'::restrictedmultirange @> 7;
+ ?column? 
+----------
+ f
+(1 row)
+
+select '{[4,50)}'::restrictedmultirange @> 7; -- should fail
+ERROR:  value for domain restrictedmultirange violates check constraint "restrictedmultirange_check"
+drop domain restrictedmultirange;
+--
+-- Test multiple multirange types over the same subtype
+--
+create type textrange1 as range(subtype=text, collation="C");
+create type textrange2 as range(subtype=text, collation="C");
+select textmultirange1(textrange2('a','Z'));  -- should fail
+ERROR:  function textmultirange1(textrange2) does not exist
+LINE 1: select textmultirange1(textrange2('a','Z'));
+               ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+select textmultirange1(textrange1('a','Z')) @> 'b'::text;
+ERROR:  range lower bound must be less than or equal to range upper bound
+select textmultirange2(textrange2('a','z')) @> 'b'::text;
+ ?column? 
+----------
+ t
+(1 row)
+
+drop type textrange1;
+drop type textrange2;
+--
+-- Test polymorphic type system
+--
+create function anyarray_anymultirange_func(a anyarray, r anymultirange)
+  returns anyelement as 'select $1[1] + lower($2);' language sql;
+select anyarray_anymultirange_func(ARRAY[1,2], int4multirange(int4range(10,20)));
+ anyarray_anymultirange_func 
+-----------------------------
+                          11
+(1 row)
+
+-- should fail
+select anyarray_anymultirange_func(ARRAY[1,2], nummultirange(numrange(10,20)));
+ERROR:  function anyarray_anymultirange_func(integer[], nummultirange) does not exist
+LINE 1: select anyarray_anymultirange_func(ARRAY[1,2], nummultirange...
+               ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+drop function anyarray_anymultirange_func(anyarray, anymultirange);
+-- should fail
+create function bogus_func(anyelement)
+  returns anymultirange as 'select int4multirange(int4range(1,10))' language sql;
+ERROR:  cannot determine result data type
+DETAIL:  A result of type anymultirange requires at least one input of type anyrange or anymultirange.
+-- should fail
+create function bogus_func(int)
+  returns anymultirange as 'select int4multirange(int4range(1,10))' language sql;
+ERROR:  cannot determine result data type
+DETAIL:  A result of type anymultirange requires at least one input of type anyrange or anymultirange.
+create function range_add_bounds(anymultirange)
+  returns anyelement as 'select lower($1) + upper($1)' language sql;
+select range_add_bounds(int4multirange(int4range(1, 17)));
+ range_add_bounds 
+------------------
+               18
+(1 row)
+
+select range_add_bounds(nummultirange(numrange(1.0001, 123.123)));
+ range_add_bounds 
+------------------
+         124.1231
+(1 row)
+
+create function multirangetypes_sql(q anymultirange, b anyarray, out c anyelement)
+  as $$ select upper($1) + $2[1] $$
+  language sql;
+select multirangetypes_sql(int4multirange(int4range(1,10)), ARRAY[2,20]);
+ multirangetypes_sql 
+---------------------
+                  12
+(1 row)
+
+select multirangetypes_sql(nummultirange(numrange(1,10)), ARRAY[2,20]);  -- match failure
+ERROR:  function multirangetypes_sql(nummultirange, integer[]) does not exist
+LINE 1: select multirangetypes_sql(nummultirange(numrange(1,10)), AR...
+               ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+create function anycompatiblearray_anycompatiblemultirange_func(a anycompatiblearray, mr anycompatiblemultirange)
+  returns anycompatible as 'select $1[1] + lower($2);' language sql;
+select anycompatiblearray_anycompatiblemultirange_func(ARRAY[1,2], multirange(int4range(10,20)));
+ anycompatiblearray_anycompatiblemultirange_func 
+-------------------------------------------------
+                                              11
+(1 row)
+
+select anycompatiblearray_anycompatiblemultirange_func(ARRAY[1,2], multirange(numrange(10,20)));
+ anycompatiblearray_anycompatiblemultirange_func 
+-------------------------------------------------
+                                              11
+(1 row)
+
+-- should fail
+select anycompatiblearray_anycompatiblemultirange_func(ARRAY[1.1,2], multirange(int4range(10,20)));
+ERROR:  function anycompatiblearray_anycompatiblemultirange_func(numeric[], int4multirange) does not exist
+LINE 1: select anycompatiblearray_anycompatiblemultirange_func(ARRAY...
+               ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+drop function anycompatiblearray_anycompatiblemultirange_func(anycompatiblearray, anycompatiblemultirange);
+create function anycompatiblerange_anycompatiblemultirange_func(r anycompatiblerange, mr anycompatiblemultirange)
+  returns anycompatible as 'select lower($1) + lower($2);' language sql;
+select anycompatiblerange_anycompatiblemultirange_func(int4range(1,2), multirange(int4range(10,20)));
+ anycompatiblerange_anycompatiblemultirange_func 
+-------------------------------------------------
+                                              11
+(1 row)
+
+-- should fail
+select anycompatiblerange_anycompatiblemultirange_func(numrange(1,2), multirange(int4range(10,20)));
+ERROR:  function anycompatiblerange_anycompatiblemultirange_func(numrange, int4multirange) does not exist
+LINE 1: select anycompatiblerange_anycompatiblemultirange_func(numra...
+               ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+drop function anycompatiblerange_anycompatiblemultirange_func(anycompatiblerange, anycompatiblemultirange);
+-- should fail
+create function bogus_func(anycompatible)
+  returns anycompatiblerange as 'select int4range(1,10)' language sql;
+ERROR:  cannot determine result data type
+DETAIL:  A result of type anycompatiblerange requires at least one input of type anycompatiblerange or anycompatiblemultirange.
+--
+-- Arrays of multiranges
+--
+select ARRAY[nummultirange(numrange(1.1, 1.2)), nummultirange(numrange(12.3, 155.5))];
+              array               
+----------------------------------
+ {"{[1.1,1.2)}","{[12.3,155.5)}"}
+(1 row)
+
+create table i8mr_array (f1 int, f2 int8multirange[]);
+insert into i8mr_array values (42, array[int8multirange(int8range(1,10)), int8multirange(int8range(2,20))]);
+select * from i8mr_array;
+ f1 |           f2            
+----+-------------------------
+ 42 | {"{[1,10)}","{[2,20)}"}
+(1 row)
+
+drop table i8mr_array;
+--
+-- Multiranges of arrays
+--
+select arraymultirange(arrayrange(ARRAY[1,2], ARRAY[2,1]));
+   arraymultirange   
+---------------------
+ {["{1,2}","{2,1}")}
+(1 row)
+
+select arraymultirange(arrayrange(ARRAY[2,1], ARRAY[1,2]));  -- fail
+ERROR:  range lower bound must be less than or equal to range upper bound
+select array[1,1] <@ arraymultirange(arrayrange(array[1,2], array[2,1]));
+ ?column? 
+----------
+ f
+(1 row)
+
+select array[1,3] <@ arraymultirange(arrayrange(array[1,2], array[2,1]));
+ ?column? 
+----------
+ t
+(1 row)
+
+--
+-- Ranges of composites
+--
+create type two_ints as (a int, b int);
+create type two_ints_range as range (subtype = two_ints);
+-- with force_parallel_mode on, this exercises tqueue.c's range remapping
+select *, row_to_json(upper(t)) as u from
+  (values (two_ints_multirange(two_ints_range(row(1,2), row(3,4)))),
+          (two_ints_multirange(two_ints_range(row(5,6), row(7,8))))) v(t);
+          t          |       u       
+---------------------+---------------
+ {["(1,2)","(3,4)")} | {"a":3,"b":4}
+ {["(5,6)","(7,8)")} | {"a":7,"b":8}
+(2 rows)
+
+drop type two_ints cascade;
+NOTICE:  drop cascades to type two_ints_range
+--
+-- Check behavior when subtype lacks a hash function
+--
+set enable_sort = off;  -- try to make it pick a hash setop implementation
+select '{(2,5)}'::cashmultirange except select '{(5,6)}'::cashmultirange;
+ cashmultirange  
+-----------------
+ {($2.00,$5.00)}
+(1 row)
+
+reset enable_sort;
+--
+-- OUT/INOUT/TABLE functions
+--
+-- infer anymultirange from anymultirange
+create function mr_outparam_succeed(i anymultirange, out r anymultirange, out t text)
+  as $$ select $1, 'foo'::text $$ language sql;
+select * from mr_outparam_succeed(int4multirange(int4range(1,2)));
+    r    |  t  
+---------+-----
+ {[1,2)} | foo
+(1 row)
+
+-- infer anyarray from anymultirange
+create function mr_outparam_succeed2(i anymultirange, out r anyarray, out t text)
+  as $$ select ARRAY[upper($1)], 'foo'::text $$ language sql;
+select * from mr_outparam_succeed2(int4multirange(int4range(1,2)));
+  r  |  t  
+-----+-----
+ {2} | foo
+(1 row)
+
+-- infer anyrange from anymultirange
+create function mr_outparam_succeed3(i anymultirange, out r anyrange, out t text)
+  as $$ select range_merge($1), 'foo'::text $$ language sql;
+select * from mr_outparam_succeed3(int4multirange(int4range(1,2)));
+   r   |  t  
+-------+-----
+ [1,2) | foo
+(1 row)
+
+-- infer anymultirange from anyrange
+create function mr_outparam_succeed4(i anyrange, out r anymultirange, out t text)
+  as $$ select multirange($1), 'foo'::text $$ language sql;
+select * from mr_outparam_succeed4(int4range(1,2));
+    r    |  t  
+---------+-----
+ {[1,2)} | foo
+(1 row)
+
+-- infer anyelement from anymultirange
+create function mr_inoutparam_succeed(out i anyelement, inout r anymultirange)
+  as $$ select upper($1), $1 $$ language sql;
+select * from mr_inoutparam_succeed(int4multirange(int4range(1,2)));
+ i |    r    
+---+---------
+ 2 | {[1,2)}
+(1 row)
+
+-- infer anyelement+anymultirange from anyelement+anymultirange
+create function mr_table_succeed(i anyelement, r anymultirange) returns table(i anyelement, r anymultirange)
+  as $$ select $1, $2 $$ language sql;
+select * from mr_table_succeed(123, int4multirange(int4range(1,11)));
+  i  |    r     
+-----+----------
+ 123 | {[1,11)}
+(1 row)
+
+-- use anymultirange in plpgsql
+create function mr_polymorphic(i anyrange) returns anymultirange
+  as $$ begin return multirange($1); end; $$ language plpgsql;
+select mr_polymorphic(int4range(1, 4));
+ mr_polymorphic 
+----------------
+ {[1,4)}
+(1 row)
+
+-- should fail
+create function mr_outparam_fail(i anyelement, out r anymultirange, out t text)
+  as $$ select '[1,10]', 'foo' $$ language sql;
+ERROR:  cannot determine result data type
+DETAIL:  A result of type anymultirange requires at least one input of type anyrange or anymultirange.
+--should fail
+create function mr_inoutparam_fail(inout i anyelement, out r anymultirange)
+  as $$ select $1, '[1,10]' $$ language sql;
+ERROR:  cannot determine result data type
+DETAIL:  A result of type anymultirange requires at least one input of type anyrange or anymultirange.
+--should fail
+create function mr_table_fail(i anyelement) returns table(i anyelement, r anymultirange)
+  as $$ select $1, '[1,10]' $$ language sql;
+ERROR:  cannot determine result data type
+DETAIL:  A result of type anymultirange requires at least one input of type anyrange or anymultirange.
diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out
index 1b3c146e4c..e08253c654 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::regtype, p2.prorettype::regtype
 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        
@@ -208,6 +215,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        
@@ -228,6 +237,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        
@@ -340,7 +351,8 @@ WHERE p1.prorettype IN
      '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     
 ------+----------------
@@ -355,20 +367,25 @@ ORDER BY 2;
  3532 | enum_recv
 (9 rows)
 
--- anyrange is tighter than the rest, can only resolve from anyrange input
+-- anyrange and anymultirange are tighter than the rest, can only resolve
+-- from each other
 SELECT p1.oid, p1.proname
 FROM pg_proc as p1
-WHERE p1.prorettype = 'anyrange'::regtype
+WHERE p1.prorettype IN ('anyrange'::regtype, 'anymultirange'::regtype)
   AND NOT
-     'anyrange'::regtype = ANY (p1.proargtypes)
+    ('anyrange'::regtype = ANY (p1.proargtypes) OR
+      'anymultirange'::regtype = ANY (p1.proargtypes))
 ORDER BY 2;
  oid  |     proname      
 ------+------------------
+ 8002 | anymultirange_in
  3832 | anyrange_in
+ 8004 | multirange_in
+ 8006 | multirange_recv
  3876 | range_gist_union
  3834 | range_in
  3836 | range_recv
-(4 rows)
+(7 rows)
 
 -- similarly for the anycompatible family
 SELECT p1.oid, p1.proname
@@ -2218,13 +2235,14 @@ ORDER BY 1, 2, 3;
                     | float_ops        | float4_ops       | real
                     | float_ops        | float8_ops       | double precision
                     | jsonb_ops        | jsonb_ops        | jsonb
+                    | multirange_ops   | multirange_ops   | anymultirange
                     | numeric_ops      | numeric_ops      | numeric
                     | range_ops        | range_ops        | anyrange
                     | record_image_ops | record_image_ops | record
                     | record_ops       | record_ops       | record
                     | tsquery_ops      | tsquery_ops      | tsquery
                     | tsvector_ops     | tsvector_ops     | tsvector
-(14 rows)
+(15 rows)
 
 -- **************** pg_index ****************
 -- Look for illegal values in pg_index fields.
diff --git a/src/test/regress/expected/plpgsql.out b/src/test/regress/expected/plpgsql.out
index d0a6b630b8..d55006d8c9 100644
--- a/src/test/regress/expected/plpgsql.out
+++ b/src/test/regress/expected/plpgsql.out
@@ -1811,7 +1811,7 @@ begin
   return array[x + 1, x + 2];
 end$$ language plpgsql;
 ERROR:  cannot determine result data type
-DETAIL:  A result of type anyrange requires at least one input of type anyrange.
+DETAIL:  A result of type anyrange requires at least one input of type anyrange or anymultirange.
 create function f1(x anyrange) returns anyarray as $$
 begin
   return array[lower(x), upper(x)];
@@ -1856,7 +1856,7 @@ begin
   return array[x + 1, x + 2];
 end$$ language plpgsql;
 ERROR:  cannot determine result data type
-DETAIL:  A result of type anycompatiblerange requires at least one input of type anycompatiblerange.
+DETAIL:  A result of type anycompatiblerange requires at least one input of type anycompatiblerange or anycompatiblemultirange.
 create function f1(x anycompatiblerange, y anycompatiblearray) returns anycompatiblerange as $$
 begin
   return x;
diff --git a/src/test/regress/expected/polymorphism.out b/src/test/regress/expected/polymorphism.out
index 1ff40764d9..248c869931 100644
--- a/src/test/regress/expected/polymorphism.out
+++ b/src/test/regress/expected/polymorphism.out
@@ -61,7 +61,7 @@ create function polyf(x anyelement) returns anyrange as $$
   select array[x + 1, x + 2]
 $$ language sql;
 ERROR:  cannot determine result data type
-DETAIL:  A result of type anyrange requires at least one input of type anyrange.
+DETAIL:  A result of type anyrange requires at least one input of type anyrange or anymultirange.
 create function polyf(x anyrange) returns anyarray as $$
   select array[lower(x), upper(x)]
 $$ language sql;
@@ -97,12 +97,27 @@ LINE 1: select polyf(int4range(42, 49), 11, 4.5) as fail;
                ^
 HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
 drop function polyf(x anycompatiblerange, y anycompatible, z anycompatible);
+create function polyf(x anycompatiblemultirange, y anycompatible, z anycompatible) returns anycompatiblearray as $$
+  select array[lower(x), upper(x), y, z]
+$$ language sql;
+select polyf(multirange(int4range(42, 49)), 11, 2::smallint) as int, polyf(multirange(float8range(4.5, 7.8)), 7.8, 11::real) as num;
+     int      |       num        
+--------------+------------------
+ {42,49,11,2} | {4.5,7.8,7.8,11}
+(1 row)
+
+select polyf(multirange(int4range(42, 49)), 11, 4.5) as fail;  -- range type doesn't fit
+ERROR:  function polyf(int4multirange, integer, numeric) does not exist
+LINE 1: select polyf(multirange(int4range(42, 49)), 11, 4.5) as fail...
+               ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+drop function polyf(x anycompatiblemultirange, y anycompatible, z anycompatible);
 -- fail, can't infer type:
 create function polyf(x anycompatible) returns anycompatiblerange as $$
   select array[x + 1, x + 2]
 $$ language sql;
 ERROR:  cannot determine result data type
-DETAIL:  A result of type anycompatiblerange requires at least one input of type anycompatiblerange.
+DETAIL:  A result of type anycompatiblerange requires at least one input of type anycompatiblerange or anycompatiblemultirange.
 create function polyf(x anycompatiblerange, y anycompatiblearray) returns anycompatiblerange as $$
   select x
 $$ language sql;
@@ -113,6 +128,22 @@ select polyf(int4range(42, 49), array[11]) as int, polyf(float8range(4.5, 7.8),
 (1 row)
 
 drop function polyf(x anycompatiblerange, y anycompatiblearray);
+-- fail, can't infer type:
+create function polyf(x anycompatible) returns anycompatiblemultirange as $$
+  select array[x + 1, x + 2]
+$$ language sql;
+ERROR:  cannot determine result data type
+DETAIL:  A result of type anycompatiblemultirange requires at least one input of type anycompatiblerange or anycompatiblemultirange.
+create function polyf(x anycompatiblemultirange, y anycompatiblearray) returns anycompatiblemultirange as $$
+  select x
+$$ language sql;
+select polyf(multirange(int4range(42, 49)), array[11]) as int, polyf(multirange(float8range(4.5, 7.8)), array[7]) as num;
+    int    |     num     
+-----------+-------------
+ {[42,49)} | {[4.5,7.8)}
+(1 row)
+
+drop function polyf(x anycompatiblemultirange, y anycompatiblearray);
 create function polyf(a anyelement, b anyarray,
                       c anycompatible, d anycompatible,
                       OUT x anyarray, OUT y anycompatiblearray)
@@ -231,7 +262,7 @@ CREATE AGGREGATE myaggp01a(*) (SFUNC = stfnp, STYPE = int4[],
 CREATE AGGREGATE myaggp02a(*) (SFUNC = stfnp, STYPE = anyarray,
   FINALFUNC = ffp, INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 --     N    P
 -- should CREATE
 CREATE AGGREGATE myaggp03a(*) (SFUNC = stfp, STYPE = int4[],
@@ -243,11 +274,11 @@ CREATE AGGREGATE myaggp03b(*) (SFUNC = stfp, STYPE = int4[],
 CREATE AGGREGATE myaggp04a(*) (SFUNC = stfp, STYPE = anyarray,
   FINALFUNC = ffp, INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 CREATE AGGREGATE myaggp04b(*) (SFUNC = stfp, STYPE = anyarray,
   INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 --    Case2 (R = P) && ((B = P) || (B = N))
 --    -------------------------------------
 --    S    tf1      B    tf2
@@ -302,13 +333,13 @@ ERROR:  function tfp(integer[], anyelement) does not exist
 CREATE AGGREGATE myaggp13a(BASETYPE = int, SFUNC = tfnp, STYPE = anyarray,
   FINALFUNC = ffp, INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 --    P    N        N    P
 -- should ERROR: tf2p(anyarray, int) not matched by tf2p(int[],anyelement)
 CREATE AGGREGATE myaggp14a(BASETYPE = int, SFUNC = tf2p, STYPE = anyarray,
   FINALFUNC = ffp, INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 --    P    N        P    N
 -- should ERROR: tfnp(anyarray, anyelement) not matched by tfnp(int[],int)
 CREATE AGGREGATE myaggp15a(BASETYPE = anyelement, SFUNC = tfnp,
@@ -324,21 +355,21 @@ ERROR:  function tf2p(anyarray, anyelement) does not exist
 CREATE AGGREGATE myaggp17a(BASETYPE = int, SFUNC = tf1p, STYPE = anyarray,
   FINALFUNC = ffp, INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 CREATE AGGREGATE myaggp17b(BASETYPE = int, SFUNC = tf1p, STYPE = anyarray,
   INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 --    P    P        N    P
 -- should ERROR: tfp(anyarray, int) not matched by tfp(anyarray, anyelement)
 CREATE AGGREGATE myaggp18a(BASETYPE = int, SFUNC = tfp, STYPE = anyarray,
   FINALFUNC = ffp, INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 CREATE AGGREGATE myaggp18b(BASETYPE = int, SFUNC = tfp, STYPE = anyarray,
   INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 --    P    P        P    N
 -- should ERROR: tf1p(anyarray, anyelement) not matched by tf1p(anyarray, int)
 CREATE AGGREGATE myaggp19a(BASETYPE = anyelement, SFUNC = tf1p,
@@ -368,11 +399,11 @@ CREATE AGGREGATE myaggn01b(*) (SFUNC = stfnp, STYPE = int4[],
 CREATE AGGREGATE myaggn02a(*) (SFUNC = stfnp, STYPE = anyarray,
   FINALFUNC = ffnp, INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 CREATE AGGREGATE myaggn02b(*) (SFUNC = stfnp, STYPE = anyarray,
   INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 --     N    P
 -- should CREATE
 CREATE AGGREGATE myaggn03a(*) (SFUNC = stfp, STYPE = int4[],
@@ -382,7 +413,7 @@ CREATE AGGREGATE myaggn03a(*) (SFUNC = stfp, STYPE = int4[],
 CREATE AGGREGATE myaggn04a(*) (SFUNC = stfp, STYPE = anyarray,
   FINALFUNC = ffnp, INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 --    Case4 (R = N) && ((B = P) || (B = N))
 --    -------------------------------------
 --    S    tf1      B    tf2
@@ -436,21 +467,21 @@ ERROR:  function tfp(integer[], anyelement) does not exist
 CREATE AGGREGATE myaggn13a(BASETYPE = int, SFUNC = tfnp, STYPE = anyarray,
   FINALFUNC = ffnp, INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 CREATE AGGREGATE myaggn13b(BASETYPE = int, SFUNC = tfnp, STYPE = anyarray,
   INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 --    P    N        N    P
 -- should ERROR: tf2p(anyarray, int) not matched by tf2p(int[],anyelement)
 CREATE AGGREGATE myaggn14a(BASETYPE = int, SFUNC = tf2p, STYPE = anyarray,
   FINALFUNC = ffnp, INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 CREATE AGGREGATE myaggn14b(BASETYPE = int, SFUNC = tf2p, STYPE = anyarray,
   INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 --    P    N        P    N
 -- should ERROR: tfnp(anyarray, anyelement) not matched by tfnp(int[],int)
 CREATE AGGREGATE myaggn15a(BASETYPE = anyelement, SFUNC = tfnp,
@@ -472,13 +503,13 @@ ERROR:  function tf2p(anyarray, anyelement) does not exist
 CREATE AGGREGATE myaggn17a(BASETYPE = int, SFUNC = tf1p, STYPE = anyarray,
   FINALFUNC = ffnp, INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 --    P    P        N    P
 -- should ERROR: tfp(anyarray, int) not matched by tfp(anyarray, anyelement)
 CREATE AGGREGATE myaggn18a(BASETYPE = int, SFUNC = tfp, STYPE = anyarray,
   FINALFUNC = ffnp, INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 --    P    P        P    N
 -- should ERROR: tf1p(anyarray, anyelement) not matched by tf1p(anyarray, int)
 CREATE AGGREGATE myaggn19a(BASETYPE = anyelement, SFUNC = tf1p,
@@ -1853,7 +1884,59 @@ returns anycompatiblerange as $$
   select $1
 $$ language sql;
 ERROR:  cannot determine result data type
-DETAIL:  A result of type anycompatiblerange requires at least one input of type anycompatiblerange.
+DETAIL:  A result of type anycompatiblerange requires at least one input of type anycompatiblerange or anycompatiblemultirange.
+create function anyctest(anycompatible, anycompatiblemultirange)
+returns anycompatiblemultirange as $$
+  select $2
+$$ language sql;
+select x, pg_typeof(x) from anyctest(11, multirange(int4range(4,7))) x;
+    x    |   pg_typeof    
+---------+----------------
+ {[4,7)} | int4multirange
+(1 row)
+
+select x, pg_typeof(x) from anyctest(11, multirange(numrange(4,7))) x;
+    x    |   pg_typeof   
+---------+---------------
+ {[4,7)} | nummultirange
+(1 row)
+
+select x, pg_typeof(x) from anyctest(11, 12) x;  -- fail
+ERROR:  function anyctest(integer, integer) does not exist
+LINE 1: select x, pg_typeof(x) from anyctest(11, 12) x;
+                                    ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+select x, pg_typeof(x) from anyctest(11.2, multirange(int4range(4,7))) x;  -- fail
+ERROR:  function anyctest(numeric, int4multirange) does not exist
+LINE 1: select x, pg_typeof(x) from anyctest(11.2, multirange(int4ra...
+                                    ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+select x, pg_typeof(x) from anyctest(11.2, '{[4,7)}') x;  -- fail
+ERROR:  could not identify anycompatiblemultirange type
+drop function anyctest(anycompatible, anycompatiblemultirange);
+create function anyctest(anycompatiblemultirange, anycompatiblemultirange)
+returns anycompatible as $$
+  select lower($1) + upper($2)
+$$ language sql;
+select x, pg_typeof(x) from anyctest(multirange(int4range(11,12)), multirange(int4range(4,7))) x;
+ x  | pg_typeof 
+----+-----------
+ 18 | integer
+(1 row)
+
+select x, pg_typeof(x) from anyctest(multirange(int4range(11,12)), multirange(numrange(4,7))) x; -- fail
+ERROR:  function anyctest(int4multirange, nummultirange) does not exist
+LINE 1: select x, pg_typeof(x) from anyctest(multirange(int4range(11...
+                                    ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+drop function anyctest(anycompatiblemultirange, anycompatiblemultirange);
+-- fail, can't infer result type:
+create function anyctest(anycompatible)
+returns anycompatiblemultirange as $$
+  select $1
+$$ language sql;
+ERROR:  cannot determine result data type
+DETAIL:  A result of type anycompatiblemultirange requires at least one input of type anycompatiblerange or anycompatiblemultirange.
 create function anyctest(anycompatiblenonarray, anycompatiblenonarray)
 returns anycompatiblearray as $$
   select array[$1, $2]
diff --git a/src/test/regress/expected/rangefuncs.out b/src/test/regress/expected/rangefuncs.out
index 7eced28452..71e2769f44 100644
--- a/src/test/regress/expected/rangefuncs.out
+++ b/src/test/regress/expected/rangefuncs.out
@@ -1556,7 +1556,7 @@ DROP FUNCTION dup(anyelement);
 CREATE FUNCTION bad (f1 int, out f2 anyelement, out f3 anyarray)
 AS 'select $1, array[$1,$1]' LANGUAGE sql;
 ERROR:  cannot determine result data type
-DETAIL:  A result of type anyelement requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyelement requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 CREATE FUNCTION dup (f1 anycompatible, f2 anycompatiblearray, f3 out anycompatible, f4 out anycompatiblearray)
 AS 'select $1, $2' LANGUAGE sql;
 SELECT dup(22, array[44]);
diff --git a/src/test/regress/expected/rangetypes.out b/src/test/regress/expected/rangetypes.out
index c421f5394f..6a1bbadc91 100644
--- a/src/test/regress/expected/rangetypes.out
+++ b/src/test/regress/expected/rangetypes.out
@@ -518,6 +518,25 @@ select numrange(1.0, 2.0) * numrange(2.5, 3.0);
  empty
 (1 row)
 
+select range_intersect_agg(nr) from numrange_test;
+ range_intersect_agg 
+---------------------
+ empty
+(1 row)
+
+select range_intersect_agg(nr) from numrange_test where false;
+ range_intersect_agg 
+---------------------
+ 
+(1 row)
+
+select range_intersect_agg(nr) from numrange_test where nr @> 4.0;
+ range_intersect_agg 
+---------------------
+ [3,5)
+(1 row)
+
+analyze numrange_test;
 create table numrange_test2(nr numrange);
 create index numrange_test2_hash_idx on numrange_test2 using hash (nr);
 INSERT INTO numrange_test2 VALUES('[, 5)');
@@ -1371,12 +1390,12 @@ drop function anyarray_anyrange_func(anyarray, anyrange);
 create function bogus_func(anyelement)
   returns anyrange as 'select int4range(1,10)' language sql;
 ERROR:  cannot determine result data type
-DETAIL:  A result of type anyrange requires at least one input of type anyrange.
+DETAIL:  A result of type anyrange requires at least one input of type anyrange or anymultirange.
 -- should fail
 create function bogus_func(int)
   returns anyrange as 'select int4range(1,10)' language sql;
 ERROR:  cannot determine result data type
-DETAIL:  A result of type anyrange requires at least one input of type anyrange.
+DETAIL:  A result of type anyrange requires at least one input of type anyrange or anymultirange.
 create function range_add_bounds(anyrange)
   returns anyelement as 'select lower($1) + upper($1)' language sql;
 select range_add_bounds(int4range(1, 17));
@@ -1430,7 +1449,7 @@ drop function anycompatiblearray_anycompatiblerange_func(anycompatiblearray, any
 create function bogus_func(anycompatible)
   returns anycompatiblerange as 'select int4range(1,10)' language sql;
 ERROR:  cannot determine result data type
-DETAIL:  A result of type anycompatiblerange requires at least one input of type anycompatiblerange.
+DETAIL:  A result of type anycompatiblerange requires at least one input of type anycompatiblerange or anycompatiblemultirange.
 --
 -- Arrays of ranges
 --
@@ -1508,6 +1527,7 @@ reset enable_sort;
 --
 -- OUT/INOUT/TABLE functions
 --
+-- infer anyrange from anyrange
 create function outparam_succeed(i anyrange, out r anyrange, out t text)
   as $$ select $1, 'foo'::text $$ language sql;
 select * from outparam_succeed(int4range(1,2));
@@ -1525,6 +1545,16 @@ select * from outparam2_succeed(int4range(1,11));
  {1,11} | {11,1}
 (1 row)
 
+-- infer anyarray from anyrange
+create function outparam_succeed2(i anyrange, out r anyarray, out t text)
+  as $$ select ARRAY[upper($1)], 'foo'::text $$ language sql;
+select * from outparam_succeed2(int4range(int4range(1,2)));
+  r  |  t  
+-----+-----
+ {2} | foo
+(1 row)
+
+-- infer anyelement from anyrange
 create function inoutparam_succeed(out i anyelement, inout r anyrange)
   as $$ select upper($1), $1 $$ language sql;
 select * from inoutparam_succeed(int4range(1,2));
@@ -1547,14 +1577,14 @@ select * from table_succeed(int4range(1,11));
 create function outparam_fail(i anyelement, out r anyrange, out t text)
   as $$ select '[1,10]', 'foo' $$ language sql;
 ERROR:  cannot determine result data type
-DETAIL:  A result of type anyrange requires at least one input of type anyrange.
+DETAIL:  A result of type anyrange requires at least one input of type anyrange or anymultirange.
 --should fail
 create function inoutparam_fail(inout i anyelement, out r anyrange)
   as $$ select $1, '[1,10]' $$ language sql;
 ERROR:  cannot determine result data type
-DETAIL:  A result of type anyrange requires at least one input of type anyrange.
+DETAIL:  A result of type anyrange requires at least one input of type anyrange or anymultirange.
 --should fail
 create function table_fail(i anyelement) returns table(i anyelement, r anyrange)
   as $$ select $1, '[1,10]' $$ language sql;
 ERROR:  cannot determine result data type
-DETAIL:  A result of type anyrange requires at least one input of type anyrange.
+DETAIL:  A result of type anyrange requires at least one input of type anyrange or anymultirange.
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index 192445878d..d9ce961be2 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -92,6 +92,7 @@ num_exp_sqrt|t
 num_exp_sub|t
 num_input_test|f
 num_result|f
+nummultirange_test|t
 numrange_test|t
 onek|t
 onek2|t
@@ -172,6 +173,7 @@ quad_poly_tbl|t
 radix_text_tbl|t
 ramp|f
 real_city|f
+reservations|f
 road|t
 shighway|t
 slow_emp4000|f
diff --git a/src/test/regress/expected/type_sanity.out b/src/test/regress/expected/type_sanity.out
index 274130e706..8df1ae47e4 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'));
@@ -195,18 +195,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
@@ -240,17 +241,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
@@ -318,18 +320,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
@@ -363,17 +366,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
@@ -631,3 +635,11 @@ WHERE pronargs != 2
 ----------+------------+---------
 (0 rows)
 
+-- every range should have a valid multirange
+SELECT p1.rngtypid, p1.rngsubtype, p1.rngmultitypid
+FROM pg_range p1
+WHERE p1.rngmultitypid IS NULL OR p1.rngmultitypid = 0;
+ rngtypid | rngsubtype | rngmultitypid 
+----------+------------+---------------
+(0 rows)
+
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 026ea880cd..f795398bfc 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -19,6 +19,7 @@ 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 xid
 
@@ -27,7 +28,7 @@ test: strings numerology point lseg line box path polygon circle date time timet
 # geometry depends on point, lseg, box, path, polygon and circle
 # horology depends on interval, timetz, timestamp, timestamptz
 # ----------
-test: geometry horology regex oidjoins type_sanity opr_sanity misc_sanity comments expressions unicode
+test: geometry horology regex oidjoins type_sanity opr_sanity misc_sanity comments expressions unicode multirangetypes
 
 # ----------
 # These four each depend on the previous one
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index 979d926119..dfbd881ebf 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -20,6 +20,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..2a6b4a0a29
--- /dev/null
+++ b/src/test/regress/sql/multirangetypes.sql
@@ -0,0 +1,658 @@
+-- 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'));
+
+--
+-- test casts, both a built-in range type and a user-defined one:
+--
+select 'empty'::int4range::int4multirange;
+select int4range(1, 3)::int4multirange;
+select int4range(1, null)::int4multirange;
+select int4range(null, null)::int4multirange;
+select 'empty'::textrange::textmultirange;
+select textrange('a', 'c')::textmultirange;
+select textrange('a', null)::textmultirange;
+select textrange(null, null)::textmultirange;
+
+--
+-- create some test data and test the operators
+--
+
+CREATE TABLE nummultirange_test (nmr NUMMULTIRANGE);
+CREATE INDEX nummultirange_test_btree ON nummultirange_test(nmr);
+
+INSERT INTO nummultirange_test VALUES('{}');
+INSERT INTO nummultirange_test VALUES('{[,)}');
+INSERT INTO nummultirange_test VALUES('{[3,]}');
+INSERT INTO nummultirange_test VALUES('{[,), [3,]}');
+INSERT INTO nummultirange_test VALUES('{[, 5)}');
+INSERT INTO nummultirange_test VALUES(nummultirange());
+INSERT INTO nummultirange_test VALUES(nummultirange(variadic '{}'::numrange[]));
+INSERT INTO nummultirange_test VALUES(nummultirange(numrange(1.1, 2.2)));
+INSERT INTO nummultirange_test VALUES('{empty}');
+INSERT INTO nummultirange_test VALUES(nummultirange(numrange(1.7, 1.7, '[]'), numrange(1.7, 1.9)));
+INSERT INTO nummultirange_test VALUES(nummultirange(numrange(1.7, 1.7, '[]'), numrange(1.9, 2.1)));
+
+SELECT nmr, isempty(nmr), lower(nmr), upper(nmr) FROM nummultirange_test ORDER BY nmr;
+SELECT nmr, lower_inc(nmr), lower_inf(nmr), upper_inc(nmr), upper_inf(nmr) FROM nummultirange_test ORDER BY nmr;
+
+SELECT * FROM nummultirange_test WHERE nmr = '{}';
+SELECT * FROM nummultirange_test WHERE nmr = '{(,5)}';
+SELECT * FROM nummultirange_test WHERE nmr = '{[3,)}';
+SELECT * FROM nummultirange_test WHERE nmr = '{[1.7,1.7]}';
+SELECT * FROM nummultirange_test WHERE nmr = '{[1.7,1.7],[1.9,2.1)}';
+SELECT * FROM nummultirange_test WHERE nmr < '{}';
+SELECT * FROM nummultirange_test WHERE nmr < '{[-1000.0, -1000.0]}';
+SELECT * FROM nummultirange_test WHERE nmr < '{[0.0, 1.0]}';
+SELECT * FROM nummultirange_test WHERE nmr < '{[1000.0, 1001.0]}';
+SELECT * FROM nummultirange_test WHERE nmr <= '{}';
+SELECT * FROM nummultirange_test WHERE nmr <= '{[3,)}';
+SELECT * FROM nummultirange_test WHERE nmr >= '{}';
+SELECT * FROM nummultirange_test WHERE nmr >= '{[3,)}';
+SELECT * FROM nummultirange_test WHERE nmr > '{}';
+SELECT * FROM nummultirange_test WHERE nmr > '{[-1000.0, -1000.0]}';
+SELECT * FROM nummultirange_test WHERE nmr > '{[0.0, 1.0]}';
+SELECT * FROM nummultirange_test WHERE nmr > '{[1000.0, 1001.0]}';
+SELECT * FROM nummultirange_test WHERE nmr <> '{}';
+SELECT * FROM nummultirange_test WHERE nmr <> '{(,5)}';
+
+select nummultirange(numrange(2.0, 1.0));
+select nummultirange(numrange(5.0, 6.0), numrange(1.0, 2.0));
+
+analyze nummultirange_test;
+
+-- overlaps
+SELECT * FROM nummultirange_test WHERE range_overlaps_multirange(numrange(4.0, 4.2), nmr);
+SELECT * FROM nummultirange_test WHERE numrange(4.0, 4.2) && nmr;
+SELECT * FROM nummultirange_test WHERE multirange_overlaps_range(nmr, numrange(4.0, 4.2));
+SELECT * FROM nummultirange_test WHERE nmr && numrange(4.0, 4.2);
+SELECT * FROM nummultirange_test WHERE multirange_overlaps_multirange(nmr, nummultirange(numrange(4.0, 4.2), numrange(6.0, 7.0)));
+SELECT * FROM nummultirange_test WHERE nmr && nummultirange(numrange(4.0, 4.2), numrange(6.0, 7.0));
+SELECT * FROM nummultirange_test WHERE nmr && nummultirange(numrange(6.0, 7.0));
+SELECT * FROM nummultirange_test WHERE nmr && nummultirange(numrange(6.0, 7.0), numrange(8.0, 9.0));
+
+-- mr contains x
+SELECT * FROM nummultirange_test WHERE multirange_contains_elem(nmr, 4.0);
+SELECT * FROM nummultirange_test WHERE nmr @> 4.0;
+SELECT * FROM nummultirange_test WHERE multirange_contains_range(nmr, numrange(4.0, 4.2));
+SELECT * FROM nummultirange_test WHERE nmr @> numrange(4.0, 4.2);
+SELECT * FROM nummultirange_test WHERE multirange_contains_multirange(nmr, '{[4.0,4.2), [6.0, 8.0)}');
+SELECT * FROM nummultirange_test WHERE nmr @> '{[4.0,4.2), [6.0, 8.0)}'::nummultirange;
+
+-- x is contained by mr
+SELECT * FROM nummultirange_test WHERE elem_contained_by_multirange(4.0, nmr);
+SELECT * FROM nummultirange_test WHERE 4.0 <@ nmr;
+SELECT * FROM nummultirange_test WHERE range_contained_by_multirange(numrange(4.0, 4.2), nmr);
+SELECT * FROM nummultirange_test WHERE numrange(4.0, 4.2) <@ nmr;
+SELECT * FROM nummultirange_test WHERE multirange_contained_by_multirange('{[4.0,4.2), [6.0, 8.0)}', nmr);
+SELECT * FROM nummultirange_test WHERE '{[4.0,4.2), [6.0, 8.0)}'::nummultirange <@ nmr;
+
+-- overlaps
+SELECT 'empty'::numrange && nummultirange();
+SELECT 'empty'::numrange && nummultirange(numrange(1,2));
+SELECT nummultirange() && 'empty'::numrange;
+SELECT nummultirange(numrange(1,2)) && 'empty'::numrange;
+SELECT nummultirange() && nummultirange();
+SELECT nummultirange() && nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) && nummultirange();
+SELECT nummultirange(numrange(3,4)) && nummultirange(numrange(1,2), numrange(7,8));
+SELECT nummultirange(numrange(1,2), numrange(7,8)) && nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(3,4)) && nummultirange(numrange(1,2), numrange(3.5,8));
+SELECT nummultirange(numrange(1,2), numrange(3.5,8)) && numrange(3,4);
+SELECT nummultirange(numrange(1,2), numrange(3.5,8)) && nummultirange(numrange(3,4));
+
+-- contains
+SELECT nummultirange() @> nummultirange();
+SELECT nummultirange() @> 'empty'::numrange;
+SELECT nummultirange(numrange(null,null)) @> numrange(1,2);
+SELECT nummultirange(numrange(null,null)) @> numrange(null,2);
+SELECT nummultirange(numrange(null,null)) @> numrange(2,null);
+SELECT nummultirange(numrange(null,5)) @> numrange(null,3);
+SELECT nummultirange(numrange(null,5)) @> numrange(null,8);
+SELECT nummultirange(numrange(5,null)) @> numrange(8,null);
+SELECT nummultirange(numrange(5,null)) @> numrange(3,null);
+SELECT nummultirange(numrange(1,5)) @> numrange(8,9);
+SELECT nummultirange(numrange(1,5)) @> numrange(3,9);
+SELECT nummultirange(numrange(1,5)) @> numrange(1,4);
+SELECT nummultirange(numrange(1,5)) @> numrange(1,5);
+SELECT nummultirange(numrange(-4,-2), numrange(1,5)) @> numrange(1,5);
+SELECT nummultirange(numrange(1,5), numrange(8,9)) @> numrange(1,5);
+SELECT nummultirange(numrange(1,5), numrange(8,9)) @> numrange(6,7);
+SELECT nummultirange(numrange(1,5), numrange(6,9)) @> numrange(6,7);
+SELECT '{[1,5)}'::nummultirange @> '{[1,5)}';
+SELECT '{[-4,-2), [1,5)}'::nummultirange @> '{[1,5)}';
+SELECT '{[1,5), [8,9)}'::nummultirange @> '{[1,5)}';
+SELECT '{[1,5), [8,9)}'::nummultirange @> '{[6,7)}';
+SELECT '{[1,5), [6,9)}'::nummultirange @> '{[6,7)}';
+
+-- is contained by
+SELECT nummultirange() <@ nummultirange();
+SELECT 'empty'::numrange <@ nummultirange();
+SELECT numrange(1,2) <@ nummultirange(numrange(null,null));
+SELECT numrange(null,2) <@ nummultirange(numrange(null,null));
+SELECT numrange(2,null) <@ nummultirange(numrange(null,null));
+SELECT numrange(null,3) <@ nummultirange(numrange(null,5));
+SELECT numrange(null,8) <@ nummultirange(numrange(null,5));
+SELECT numrange(8,null) <@ nummultirange(numrange(5,null));
+SELECT numrange(3,null) <@ nummultirange(numrange(5,null));
+SELECT numrange(8,9) <@ nummultirange(numrange(1,5));
+SELECT numrange(3,9) <@ nummultirange(numrange(1,5));
+SELECT numrange(1,4) <@ nummultirange(numrange(1,5));
+SELECT numrange(1,5) <@ nummultirange(numrange(1,5));
+SELECT numrange(1,5) <@ nummultirange(numrange(-4,-2), numrange(1,5));
+SELECT numrange(1,5) <@ nummultirange(numrange(1,5), numrange(8,9));
+SELECT numrange(6,7) <@ nummultirange(numrange(1,5), numrange(8,9));
+SELECT numrange(6,7) <@ nummultirange(numrange(1,5), numrange(6,9));
+SELECT '{[1,5)}' <@ '{[1,5)}'::nummultirange;
+SELECT '{[1,5)}' <@ '{[-4,-2), [1,5)}'::nummultirange;
+SELECT '{[1,5)}' <@ '{[1,5), [8,9)}'::nummultirange;
+SELECT '{[6,7)}' <@ '{[1,5), [8,9)}'::nummultirange;
+SELECT '{[6,7)}' <@ '{[1,5), [6,9)}'::nummultirange;
+
+-- overleft
+SELECT 'empty'::numrange &< nummultirange();
+SELECT 'empty'::numrange &< nummultirange(numrange(1,2));
+SELECT nummultirange() &< 'empty'::numrange;
+SELECT nummultirange(numrange(1,2)) &< 'empty'::numrange;
+SELECT nummultirange() &< nummultirange();
+SELECT nummultirange(numrange(1,2)) &< nummultirange();
+SELECT nummultirange() &< nummultirange(numrange(1,2));
+SELECT numrange(6,7) &< nummultirange(numrange(3,4));
+SELECT numrange(1,2) &< nummultirange(numrange(3,4));
+SELECT numrange(1,4) &< nummultirange(numrange(3,4));
+SELECT numrange(1,6) &< nummultirange(numrange(3,4));
+SELECT numrange(3.5,6) &< nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(6,7)) &< numrange(3,4);
+SELECT nummultirange(numrange(1,2)) &< numrange(3,4);
+SELECT nummultirange(numrange(1,4)) &< numrange(3,4);
+SELECT nummultirange(numrange(1,6)) &< numrange(3,4);
+SELECT nummultirange(numrange(3.5,6)) &< numrange(3,4);
+SELECT nummultirange(numrange(6,7)) &< nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,2)) &< nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,4)) &< nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,6)) &< nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(3.5,6)) &< nummultirange(numrange(3,4));
+
+-- overright
+SELECT nummultirange() &> 'empty'::numrange;
+SELECT nummultirange(numrange(1,2)) &> 'empty'::numrange;
+SELECT 'empty'::numrange &> nummultirange();
+SELECT 'empty'::numrange &> nummultirange(numrange(1,2));
+SELECT nummultirange() &> nummultirange();
+SELECT nummultirange() &> nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) &> nummultirange();
+SELECT nummultirange(numrange(3,4)) &> numrange(6,7);
+SELECT nummultirange(numrange(3,4)) &> numrange(1,2);
+SELECT nummultirange(numrange(3,4)) &> numrange(1,4);
+SELECT nummultirange(numrange(3,4)) &> numrange(1,6);
+SELECT nummultirange(numrange(3,4)) &> numrange(3.5,6);
+SELECT numrange(3,4) &> nummultirange(numrange(6,7));
+SELECT numrange(3,4) &> nummultirange(numrange(1,2));
+SELECT numrange(3,4) &> nummultirange(numrange(1,4));
+SELECT numrange(3,4) &> nummultirange(numrange(1,6));
+SELECT numrange(3,4) &> nummultirange(numrange(3.5,6));
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(6,7));
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(1,4));
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(1,6));
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(3.5,6));
+
+-- meets
+SELECT 'empty'::numrange -|- nummultirange();
+SELECT 'empty'::numrange -|- nummultirange(numrange(1,2));
+SELECT nummultirange() -|- 'empty'::numrange;
+SELECT nummultirange(numrange(1,2)) -|- 'empty'::numrange;
+SELECT nummultirange() -|- nummultirange();
+SELECT nummultirange(numrange(1,2)) -|- nummultirange();
+SELECT nummultirange() -|- nummultirange(numrange(1,2));
+SELECT numrange(1,2) -|- nummultirange(numrange(2,4));
+SELECT numrange(1,2) -|- nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,2)) -|- numrange(2,4);
+SELECT nummultirange(numrange(1,2)) -|- numrange(3,4);
+SELECT nummultirange(numrange(1,2)) -|- nummultirange(numrange(2,4));
+SELECT nummultirange(numrange(1,2)) -|- nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,2), numrange(5,6)) -|- nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,2), numrange(5,6)) -|- nummultirange(numrange(6,7));
+SELECT nummultirange(numrange(1,2), numrange(5,6)) -|- nummultirange(numrange(8,9));
+SELECT nummultirange(numrange(1,2)) -|- nummultirange(numrange(2,4), numrange(6,7));
+
+-- strictly left
+select 'empty'::numrange << nummultirange();
+select numrange(1,2) << nummultirange();
+select numrange(1,2) << nummultirange(numrange(3,4));
+select numrange(1,2) << nummultirange(numrange(0,4));
+select numrange(1,2) << nummultirange(numrange(0,4), numrange(7,8));
+select nummultirange() << 'empty'::numrange;
+select nummultirange() << numrange(1,2);
+select nummultirange(numrange(3,4)) << numrange(3,6);
+select nummultirange(numrange(0,2)) << numrange(3,6);
+select nummultirange(numrange(0,2), numrange(7,8)) << numrange(3,6);
+select nummultirange(numrange(-4,-2), numrange(0,2)) << numrange(3,6);
+select nummultirange() << nummultirange();
+select nummultirange() << nummultirange(numrange(1,2));
+select nummultirange(numrange(1,2)) << nummultirange();
+select nummultirange(numrange(1,2)) << nummultirange(numrange(1,2));
+select nummultirange(numrange(1,2)) << nummultirange(numrange(3,4));
+select nummultirange(numrange(1,2)) << nummultirange(numrange(3,4), numrange(7,8));
+select nummultirange(numrange(1,2), numrange(4,5)) << nummultirange(numrange(3,4), numrange(7,8));
+
+-- strictly right
+select nummultirange() >> 'empty'::numrange;
+select nummultirange() >> numrange(1,2);
+select nummultirange(numrange(3,4)) >> numrange(1,2);
+select nummultirange(numrange(0,4)) >> numrange(1,2);
+select nummultirange(numrange(0,4), numrange(7,8)) >> numrange(1,2);
+select 'empty'::numrange >> nummultirange();
+select numrange(1,2) >> nummultirange();
+select numrange(3,6) >> nummultirange(numrange(3,4));
+select numrange(3,6) >> nummultirange(numrange(0,2));
+select numrange(3,6) >> nummultirange(numrange(0,2), numrange(7,8));
+select numrange(3,6) >> nummultirange(numrange(-4,-2), numrange(0,2));
+select nummultirange() >> nummultirange();
+select nummultirange(numrange(1,2)) >> nummultirange();
+select nummultirange() >> nummultirange(numrange(1,2));
+select nummultirange(numrange(1,2)) >> nummultirange(numrange(1,2));
+select nummultirange(numrange(3,4)) >> nummultirange(numrange(1,2));
+select nummultirange(numrange(3,4), numrange(7,8)) >> nummultirange(numrange(1,2));
+select nummultirange(numrange(3,4), numrange(7,8)) >> nummultirange(numrange(1,2), numrange(4,5));
+
+-- union
+SELECT nummultirange() + nummultirange();
+SELECT nummultirange() + nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) + nummultirange();
+SELECT nummultirange(numrange(1,2)) + nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) + nummultirange(numrange(2,4));
+SELECT nummultirange(numrange(1,2)) + nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) + nummultirange(numrange(2,4));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) + nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) + nummultirange(numrange(0,9));
+
+-- merge
+SELECT range_merge(nummultirange());
+SELECT range_merge(nummultirange(numrange(1,2)));
+SELECT range_merge(nummultirange(numrange(1,2), numrange(7,8)));
+
+-- minus
+SELECT nummultirange() - nummultirange();
+SELECT nummultirange() - nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) - nummultirange();
+SELECT nummultirange(numrange(1,2), numrange(3,4)) - nummultirange();
+SELECT nummultirange(numrange(1,2)) - nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) - nummultirange(numrange(2,4));
+SELECT nummultirange(numrange(1,2)) - nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(2,3));
+SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(0,8));
+SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(0,2));
+SELECT nummultirange(numrange(1,8)) - nummultirange(numrange(0,2), numrange(3,4));
+SELECT nummultirange(numrange(1,8)) - nummultirange(numrange(2,3), numrange(5,null));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(-2,0));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(2,4));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(3,5));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(0,9));
+SELECT nummultirange(numrange(1,3), numrange(4,5)) - nummultirange(numrange(2,9));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(8,9));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(-2,0), numrange(8,9));
+
+-- intersection
+SELECT nummultirange() * nummultirange();
+SELECT nummultirange() * nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) * nummultirange();
+SELECT '{[1,3)}'::nummultirange * '{[1,5)}'::nummultirange;
+SELECT '{[1,3)}'::nummultirange * '{[0,5)}'::nummultirange;
+SELECT '{[1,3)}'::nummultirange * '{[0,2)}'::nummultirange;
+SELECT '{[1,3)}'::nummultirange * '{[2,5)}'::nummultirange;
+SELECT '{[1,4)}'::nummultirange * '{[2,3)}'::nummultirange;
+SELECT '{[1,4)}'::nummultirange * '{[0,2), [3,5)}'::nummultirange;
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[0,8), [9,12)}'::nummultirange;
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[9,12)}'::nummultirange;
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[-5,-4), [5,6), [9,12)}'::nummultirange;
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[0,2), [3,8), [9,12)}'::nummultirange;
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[0,2), [3,8), [9,12)}'::nummultirange;
+
+--
+-- range_agg function
+--
+create table reservations ( room_id integer not null, booked_during daterange );
+insert into reservations values
+-- 1: has a meets and a gap
+(1, daterange('2018-07-01', '2018-07-07')),
+(1, daterange('2018-07-07', '2018-07-14')),
+(1, daterange('2018-07-20', '2018-07-22')),
+-- 2: just a single row
+(2, daterange('2018-07-01', '2018-07-03')),
+-- 3: one null range
+(3, NULL),
+-- 4: two null ranges
+(4, NULL),
+(4, NULL),
+-- 5: a null range and a non-null range
+(5, NULL),
+(5, daterange('2018-07-01', '2018-07-03')),
+-- 6: has overlap
+(6, daterange('2018-07-01', '2018-07-07')),
+(6, daterange('2018-07-05', '2018-07-10')),
+-- 7: two ranges that meet: no gap or overlap
+(7, daterange('2018-07-01', '2018-07-07')),
+(7, daterange('2018-07-07', '2018-07-14')),
+-- 8: an empty range
+(8, 'empty'::daterange)
+;
+SELECT   room_id, range_agg(booked_during)
+FROM     reservations
+GROUP BY room_id
+ORDER BY room_id;
+
+-- range_agg on a custom range type too
+SELECT  range_agg(r)
+FROM    (VALUES
+          ('[a,c]'::textrange),
+          ('[b,b]'::textrange),
+          ('[c,f]'::textrange),
+          ('[g,h)'::textrange),
+          ('[h,j)'::textrange)
+        ) t(r);
+
+select range_intersect_agg(nmr) from nummultirange_test;
+select range_intersect_agg(nmr) from nummultirange_test where false;
+-- test with just one input:
+select range_intersect_agg(nmr) from (values ('{[1,2]}'::nummultirange)) t(nmr);
+select range_intersect_agg(nmr) from nummultirange_test where nmr @> 4.0;
+
+create table nummultirange_test2(nmr nummultirange);
+create index nummultirange_test2_hash_idx on nummultirange_test2 using hash (nmr);
+
+INSERT INTO nummultirange_test2 VALUES('{[, 5)}');
+INSERT INTO nummultirange_test2 VALUES(nummultirange(numrange(1.1, 2.2)));
+INSERT INTO nummultirange_test2 VALUES(nummultirange(numrange(1.1, 2.2)));
+INSERT INTO nummultirange_test2 VALUES(nummultirange(numrange(1.1, 2.2,'()')));
+INSERT INTO nummultirange_test2 VALUES('{}');
+
+select * from nummultirange_test2 where nmr = '{}';
+select * from nummultirange_test2 where nmr = nummultirange(numrange(1.1, 2.2));
+select * from nummultirange_test2 where nmr = nummultirange(numrange(1.1, 2.3));
+
+set enable_nestloop=t;
+set enable_hashjoin=f;
+set enable_mergejoin=f;
+select * from nummultirange_test natural join nummultirange_test2 order by nmr;
+set enable_nestloop=f;
+set enable_hashjoin=t;
+set enable_mergejoin=f;
+select * from nummultirange_test natural join nummultirange_test2 order by nmr;
+set enable_nestloop=f;
+set enable_hashjoin=f;
+set enable_mergejoin=t;
+select * from nummultirange_test natural join nummultirange_test2 order by nmr;
+
+set enable_nestloop to default;
+set enable_hashjoin to default;
+set enable_mergejoin to default;
+
+DROP TABLE nummultirange_test2;
+
+--
+-- Test user-defined multirange of floats
+--
+
+select '{[123.001, 5.e9)}'::float8multirange @> 888.882::float8;
+create table float8multirange_test(f8mr float8multirange, i int);
+insert into float8multirange_test values(float8multirange(float8range(-100.00007, '1.111113e9')), 42);
+select * from float8multirange_test;
+drop table float8multirange_test;
+
+--
+-- Test multirange types over domains
+--
+
+create domain mydomain as int4;
+create type mydomainrange as range(subtype=mydomain);
+select '{[4,50)}'::mydomainmultirange @> 7::mydomain;
+drop domain mydomain cascade;
+
+--
+-- Test domains over multirange types
+--
+
+create domain restrictedmultirange as int4multirange check (upper(value) < 10);
+select '{[4,5)}'::restrictedmultirange @> 7;
+select '{[4,50)}'::restrictedmultirange @> 7; -- should fail
+drop domain restrictedmultirange;
+
+--
+-- Test multiple multirange types over the same subtype
+--
+
+create type textrange1 as range(subtype=text, collation="C");
+create type textrange2 as range(subtype=text, collation="C");
+
+select textmultirange1(textrange2('a','Z'));  -- should fail
+select textmultirange1(textrange1('a','Z')) @> 'b'::text;
+select textmultirange2(textrange2('a','z')) @> 'b'::text;
+
+drop type textrange1;
+drop type textrange2;
+
+--
+-- Test polymorphic type system
+--
+
+create function anyarray_anymultirange_func(a anyarray, r anymultirange)
+  returns anyelement as 'select $1[1] + lower($2);' language sql;
+
+select anyarray_anymultirange_func(ARRAY[1,2], int4multirange(int4range(10,20)));
+
+-- should fail
+select anyarray_anymultirange_func(ARRAY[1,2], nummultirange(numrange(10,20)));
+
+drop function anyarray_anymultirange_func(anyarray, anymultirange);
+
+-- should fail
+create function bogus_func(anyelement)
+  returns anymultirange as 'select int4multirange(int4range(1,10))' language sql;
+
+-- should fail
+create function bogus_func(int)
+  returns anymultirange as 'select int4multirange(int4range(1,10))' language sql;
+
+create function range_add_bounds(anymultirange)
+  returns anyelement as 'select lower($1) + upper($1)' language sql;
+
+select range_add_bounds(int4multirange(int4range(1, 17)));
+select range_add_bounds(nummultirange(numrange(1.0001, 123.123)));
+
+create function multirangetypes_sql(q anymultirange, b anyarray, out c anyelement)
+  as $$ select upper($1) + $2[1] $$
+  language sql;
+
+select multirangetypes_sql(int4multirange(int4range(1,10)), ARRAY[2,20]);
+select multirangetypes_sql(nummultirange(numrange(1,10)), ARRAY[2,20]);  -- match failure
+
+create function anycompatiblearray_anycompatiblemultirange_func(a anycompatiblearray, mr anycompatiblemultirange)
+  returns anycompatible as 'select $1[1] + lower($2);' language sql;
+
+select anycompatiblearray_anycompatiblemultirange_func(ARRAY[1,2], multirange(int4range(10,20)));
+
+select anycompatiblearray_anycompatiblemultirange_func(ARRAY[1,2], multirange(numrange(10,20)));
+
+-- should fail
+select anycompatiblearray_anycompatiblemultirange_func(ARRAY[1.1,2], multirange(int4range(10,20)));
+
+drop function anycompatiblearray_anycompatiblemultirange_func(anycompatiblearray, anycompatiblemultirange);
+
+create function anycompatiblerange_anycompatiblemultirange_func(r anycompatiblerange, mr anycompatiblemultirange)
+  returns anycompatible as 'select lower($1) + lower($2);' language sql;
+
+select anycompatiblerange_anycompatiblemultirange_func(int4range(1,2), multirange(int4range(10,20)));
+
+-- should fail
+select anycompatiblerange_anycompatiblemultirange_func(numrange(1,2), multirange(int4range(10,20)));
+
+drop function anycompatiblerange_anycompatiblemultirange_func(anycompatiblerange, anycompatiblemultirange);
+
+-- should fail
+create function bogus_func(anycompatible)
+  returns anycompatiblerange as 'select int4range(1,10)' language sql;
+
+--
+-- Arrays of multiranges
+--
+
+select ARRAY[nummultirange(numrange(1.1, 1.2)), nummultirange(numrange(12.3, 155.5))];
+
+create table i8mr_array (f1 int, f2 int8multirange[]);
+insert into i8mr_array values (42, array[int8multirange(int8range(1,10)), int8multirange(int8range(2,20))]);
+select * from i8mr_array;
+drop table i8mr_array;
+
+--
+-- Multiranges of arrays
+--
+
+select arraymultirange(arrayrange(ARRAY[1,2], ARRAY[2,1]));
+select arraymultirange(arrayrange(ARRAY[2,1], ARRAY[1,2]));  -- fail
+
+select array[1,1] <@ arraymultirange(arrayrange(array[1,2], array[2,1]));
+select array[1,3] <@ arraymultirange(arrayrange(array[1,2], array[2,1]));
+
+--
+-- Ranges of composites
+--
+
+create type two_ints as (a int, b int);
+create type two_ints_range as range (subtype = two_ints);
+
+-- with force_parallel_mode on, this exercises tqueue.c's range remapping
+select *, row_to_json(upper(t)) as u from
+  (values (two_ints_multirange(two_ints_range(row(1,2), row(3,4)))),
+          (two_ints_multirange(two_ints_range(row(5,6), row(7,8))))) v(t);
+
+drop type two_ints cascade;
+
+--
+-- Check behavior when subtype lacks a hash function
+--
+
+set enable_sort = off;  -- try to make it pick a hash setop implementation
+
+select '{(2,5)}'::cashmultirange except select '{(5,6)}'::cashmultirange;
+
+reset enable_sort;
+
+--
+-- OUT/INOUT/TABLE functions
+--
+
+-- infer anymultirange from anymultirange
+create function mr_outparam_succeed(i anymultirange, out r anymultirange, out t text)
+  as $$ select $1, 'foo'::text $$ language sql;
+
+select * from mr_outparam_succeed(int4multirange(int4range(1,2)));
+
+-- infer anyarray from anymultirange
+create function mr_outparam_succeed2(i anymultirange, out r anyarray, out t text)
+  as $$ select ARRAY[upper($1)], 'foo'::text $$ language sql;
+
+select * from mr_outparam_succeed2(int4multirange(int4range(1,2)));
+
+-- infer anyrange from anymultirange
+create function mr_outparam_succeed3(i anymultirange, out r anyrange, out t text)
+  as $$ select range_merge($1), 'foo'::text $$ language sql;
+select * from mr_outparam_succeed3(int4multirange(int4range(1,2)));
+
+-- infer anymultirange from anyrange
+create function mr_outparam_succeed4(i anyrange, out r anymultirange, out t text)
+  as $$ select multirange($1), 'foo'::text $$ language sql;
+
+select * from mr_outparam_succeed4(int4range(1,2));
+
+-- infer anyelement from anymultirange
+create function mr_inoutparam_succeed(out i anyelement, inout r anymultirange)
+  as $$ select upper($1), $1 $$ language sql;
+
+select * from mr_inoutparam_succeed(int4multirange(int4range(1,2)));
+
+-- infer anyelement+anymultirange from anyelement+anymultirange
+create function mr_table_succeed(i anyelement, r anymultirange) returns table(i anyelement, r anymultirange)
+  as $$ select $1, $2 $$ language sql;
+
+select * from mr_table_succeed(123, int4multirange(int4range(1,11)));
+
+-- use anymultirange in plpgsql
+create function mr_polymorphic(i anyrange) returns anymultirange
+  as $$ begin return multirange($1); end; $$ language plpgsql;
+select mr_polymorphic(int4range(1, 4));
+
+-- should fail
+create function mr_outparam_fail(i anyelement, out r anymultirange, out t text)
+  as $$ select '[1,10]', 'foo' $$ language sql;
+
+--should fail
+create function mr_inoutparam_fail(inout i anyelement, out r anymultirange)
+  as $$ select $1, '[1,10]' $$ language sql;
+
+--should fail
+create function mr_table_fail(i anyelement) returns table(i anyelement, r anymultirange)
+  as $$ select $1, '[1,10]' $$ language sql;
diff --git a/src/test/regress/sql/opr_sanity.sql b/src/test/regress/sql/opr_sanity.sql
index 7a9180b081..25958c9477 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::regtype, p2.prorettype::regtype
 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;
 
@@ -279,16 +290,19 @@ WHERE p1.prorettype IN
      '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;
 
--- anyrange is tighter than the rest, can only resolve from anyrange input
+-- anyrange and anymultirange are tighter than the rest, can only resolve
+-- from each other
 
 SELECT p1.oid, p1.proname
 FROM pg_proc as p1
-WHERE p1.prorettype = 'anyrange'::regtype
+WHERE p1.prorettype IN ('anyrange'::regtype, 'anymultirange'::regtype)
   AND NOT
-     'anyrange'::regtype = ANY (p1.proargtypes)
+    ('anyrange'::regtype = ANY (p1.proargtypes) OR
+      'anymultirange'::regtype = ANY (p1.proargtypes))
 ORDER BY 2;
 
 -- similarly for the anycompatible family
diff --git a/src/test/regress/sql/polymorphism.sql b/src/test/regress/sql/polymorphism.sql
index e5222f1f81..ac52c387bb 100644
--- a/src/test/regress/sql/polymorphism.sql
+++ b/src/test/regress/sql/polymorphism.sql
@@ -71,6 +71,16 @@ select polyf(int4range(42, 49), 11, 4.5) as fail;  -- range type doesn't fit
 
 drop function polyf(x anycompatiblerange, y anycompatible, z anycompatible);
 
+create function polyf(x anycompatiblemultirange, y anycompatible, z anycompatible) returns anycompatiblearray as $$
+  select array[lower(x), upper(x), y, z]
+$$ language sql;
+
+select polyf(multirange(int4range(42, 49)), 11, 2::smallint) as int, polyf(multirange(float8range(4.5, 7.8)), 7.8, 11::real) as num;
+
+select polyf(multirange(int4range(42, 49)), 11, 4.5) as fail;  -- range type doesn't fit
+
+drop function polyf(x anycompatiblemultirange, y anycompatible, z anycompatible);
+
 -- fail, can't infer type:
 create function polyf(x anycompatible) returns anycompatiblerange as $$
   select array[x + 1, x + 2]
@@ -84,6 +94,19 @@ select polyf(int4range(42, 49), array[11]) as int, polyf(float8range(4.5, 7.8),
 
 drop function polyf(x anycompatiblerange, y anycompatiblearray);
 
+-- fail, can't infer type:
+create function polyf(x anycompatible) returns anycompatiblemultirange as $$
+  select array[x + 1, x + 2]
+$$ language sql;
+
+create function polyf(x anycompatiblemultirange, y anycompatiblearray) returns anycompatiblemultirange as $$
+  select x
+$$ language sql;
+
+select polyf(multirange(int4range(42, 49)), array[11]) as int, polyf(multirange(float8range(4.5, 7.8)), array[7]) as num;
+
+drop function polyf(x anycompatiblemultirange, y anycompatiblearray);
+
 create function polyf(a anyelement, b anyarray,
                       c anycompatible, d anycompatible,
                       OUT x anyarray, OUT y anycompatiblearray)
@@ -994,6 +1017,35 @@ returns anycompatiblerange as $$
   select $1
 $$ language sql;
 
+create function anyctest(anycompatible, anycompatiblemultirange)
+returns anycompatiblemultirange as $$
+  select $2
+$$ language sql;
+
+select x, pg_typeof(x) from anyctest(11, multirange(int4range(4,7))) x;
+select x, pg_typeof(x) from anyctest(11, multirange(numrange(4,7))) x;
+select x, pg_typeof(x) from anyctest(11, 12) x;  -- fail
+select x, pg_typeof(x) from anyctest(11.2, multirange(int4range(4,7))) x;  -- fail
+select x, pg_typeof(x) from anyctest(11.2, '{[4,7)}') x;  -- fail
+
+drop function anyctest(anycompatible, anycompatiblemultirange);
+
+create function anyctest(anycompatiblemultirange, anycompatiblemultirange)
+returns anycompatible as $$
+  select lower($1) + upper($2)
+$$ language sql;
+
+select x, pg_typeof(x) from anyctest(multirange(int4range(11,12)), multirange(int4range(4,7))) x;
+select x, pg_typeof(x) from anyctest(multirange(int4range(11,12)), multirange(numrange(4,7))) x; -- fail
+
+drop function anyctest(anycompatiblemultirange, anycompatiblemultirange);
+
+-- fail, can't infer result type:
+create function anyctest(anycompatible)
+returns anycompatiblemultirange as $$
+  select $1
+$$ language sql;
+
 create function anyctest(anycompatiblenonarray, anycompatiblenonarray)
 returns anycompatiblearray as $$
   select array[$1, $2]
diff --git a/src/test/regress/sql/rangetypes.sql b/src/test/regress/sql/rangetypes.sql
index 4048b1d185..b69efede3a 100644
--- a/src/test/regress/sql/rangetypes.sql
+++ b/src/test/regress/sql/rangetypes.sql
@@ -118,6 +118,12 @@ select numrange(1.0, 2.0) * numrange(2.0, 3.0);
 select numrange(1.0, 2.0) * numrange(1.5, 3.0);
 select numrange(1.0, 2.0) * numrange(2.5, 3.0);
 
+select range_intersect_agg(nr) from numrange_test;
+select range_intersect_agg(nr) from numrange_test where false;
+select range_intersect_agg(nr) from numrange_test where nr @> 4.0;
+
+analyze numrange_test;
+
 create table numrange_test2(nr numrange);
 create index numrange_test2_hash_idx on numrange_test2 using hash (nr);
 
@@ -528,6 +534,7 @@ reset enable_sort;
 -- OUT/INOUT/TABLE functions
 --
 
+-- infer anyrange from anyrange
 create function outparam_succeed(i anyrange, out r anyrange, out t text)
   as $$ select $1, 'foo'::text $$ language sql;
 
@@ -539,6 +546,13 @@ create function outparam2_succeed(r anyrange, out lu anyarray, out ul anyarray)
 
 select * from outparam2_succeed(int4range(1,11));
 
+-- infer anyarray from anyrange
+create function outparam_succeed2(i anyrange, out r anyarray, out t text)
+  as $$ select ARRAY[upper($1)], 'foo'::text $$ language sql;
+
+select * from outparam_succeed2(int4range(int4range(1,2)));
+
+-- infer anyelement from anyrange
 create function inoutparam_succeed(out i anyelement, inout r anyrange)
   as $$ select upper($1), $1 $$ language sql;
 
diff --git a/src/test/regress/sql/type_sanity.sql b/src/test/regress/sql/type_sanity.sql
index 4b492ce062..07879d9a57 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'));
@@ -153,7 +153,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')
@@ -182,7 +182,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')
@@ -234,7 +234,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')
@@ -263,7 +263,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')
@@ -467,3 +467,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.rngmultitypid
+FROM pg_range p1
+WHERE p1.rngmultitypid IS NULL OR p1.rngmultitypid = 0;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3d990463ce..498e63532f 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1392,6 +1392,9 @@ MultiXactMember
 MultiXactOffset
 MultiXactStateData
 MultiXactStatus
+MultirangeIOData
+MultirangeParseState
+MultirangeType
 NDBOX
 NODE
 NUMCacheEntry
#129Paul A Jungwirth
pj@illuminatedcomputing.com
In reply to: Paul A Jungwirth (#128)
1 attachment(s)
Re: range_agg

On Sun, Aug 16, 2020 at 12:55 PM Paul A Jungwirth
<pj@illuminatedcomputing.com> wrote:

This is rebased on the current master, including some changes to doc
tables and pg_upgrade handling of type oids.

Here is a rebased version of this patch, including a bunch of cleanup
from Alvaro. (Thanks Alvaro!)

Paul

Attachments:

v22-multirange.patchapplication/octet-stream; name=v22-multirange.patchDownload
diff --git a/doc/src/sgml/datatype.sgml b/doc/src/sgml/datatype.sgml
index 49ea0003aa..4a5021e6d9 100644
--- a/doc/src/sgml/datatype.sgml
+++ b/doc/src/sgml/datatype.sgml
@@ -4886,6 +4886,10 @@ SELECT * FROM pg_attribute
     <primary>anyrange</primary>
    </indexterm>
 
+   <indexterm zone="datatype-pseudo">
+    <primary>anymultirange</primary>
+   </indexterm>
+
    <indexterm zone="datatype-pseudo">
     <primary>anycompatible</primary>
    </indexterm>
@@ -4902,6 +4906,10 @@ SELECT * FROM pg_attribute
     <primary>anycompatiblerange</primary>
    </indexterm>
 
+   <indexterm zone="datatype-pseudo">
+    <primary>anycompatiblemultirange</primary>
+   </indexterm>
+
    <indexterm zone="datatype-pseudo">
     <primary>void</primary>
    </indexterm>
@@ -5013,6 +5021,13 @@ SELECT * FROM pg_attribute
         <xref linkend="rangetypes"/>).</entry>
        </row>
 
+       <row>
+        <entry><type>anymultirange</type></entry>
+        <entry>Indicates that a function accepts any multirange data type
+        (see <xref linkend="extend-types-polymorphic"/> and
+        <xref linkend="rangetypes"/>).</entry>
+       </row>
+
        <row>
         <entry><type>anycompatible</type></entry>
         <entry>Indicates that a function accepts any data type,
@@ -5042,6 +5057,14 @@ SELECT * FROM pg_attribute
         <xref linkend="rangetypes"/>).</entry>
        </row>
 
+       <row>
+        <entry><type>anycompatiblemultirange</type></entry>
+        <entry>Indicates that a function accepts any multirange data type,
+        with automatic promotion of multiple arguments to a common data type
+        (see <xref linkend="extend-types-polymorphic"/> and
+        <xref linkend="rangetypes"/>).</entry>
+       </row>
+
        <row>
         <entry><type>cstring</type></entry>
         <entry>Indicates that a function accepts or returns a null-terminated C string.</entry>
diff --git a/doc/src/sgml/extend.sgml b/doc/src/sgml/extend.sgml
index e486006224..29de900b44 100644
--- a/doc/src/sgml/extend.sgml
+++ b/doc/src/sgml/extend.sgml
@@ -288,6 +288,14 @@
         </entry>
        </row>
 
+       <row>
+        <entry><type>anymultirange</type></entry>
+        <entry>Simple</entry>
+        <entry>Indicates that a function accepts any multirange data type
+        (see <xref linkend="rangetypes"/>)
+        </entry>
+       </row>
+
        <row>
         <entry><type>anycompatible</type></entry>
         <entry>Common</entry>
@@ -319,6 +327,14 @@
         with automatic promotion of multiple arguments to a common data type
         </entry>
        </row>
+
+       <row>
+        <entry><type>anycompatiblemultirange</type></entry>
+        <entry>Common</entry>
+        <entry>Indicates that a function accepts any multirange data type,
+        with automatic promotion of multiple arguments to a common data type
+        </entry>
+       </row>
       </tbody>
      </tgroup>
     </table>
@@ -346,17 +362,15 @@
      position declared as <type>anyarray</type> can have any array data type,
      but similarly they must all be the same type.  And similarly,
      positions declared as <type>anyrange</type> must all be the same range
-     type.  Furthermore, if there are
+     type.  Likewise for <type>anymultirange</type>.
+    </para>
+
+    <para>
+     Furthermore, if there are
      positions declared <type>anyarray</type> and others declared
      <type>anyelement</type>, the actual array type in the
      <type>anyarray</type> positions must be an array whose elements are
      the same type appearing in the <type>anyelement</type> positions.
-     Similarly, if there are positions declared <type>anyrange</type>
-     and others declared <type>anyelement</type> or <type>anyarray</type>,
-     the actual range type in the <type>anyrange</type> positions must be a
-     range whose subtype is the same type appearing in
-     the <type>anyelement</type> positions and the same as the element type
-     of the <type>anyarray</type> positions.
      <type>anynonarray</type> is treated exactly the same as <type>anyelement</type>,
      but adds the additional constraint that the actual type must not be
      an array type.
@@ -365,6 +379,19 @@
      be an enum type.
     </para>
 
+    <para>
+     Similarly, if there are positions declared <type>anyrange</type>
+     and others declared <type>anyelement</type> or <type>anyarray</type>,
+     the actual range type in the <type>anyrange</type> positions must be a
+     range whose subtype is the same type appearing in
+     the <type>anyelement</type> positions and the same as the element type
+     of the <type>anyarray</type> positions.
+     If there are positions declared <type>anymultirange</type>,
+     their actual multirange type must contain ranges matching parameters declared
+     <type>anyrange</type> and base elements matching parameters declared
+     <type>anyelement</type> and <type>anyarray</type>.
+    </para>
+
     <para>
      Thus, when more than one argument position is declared with a polymorphic
      type, the net effect is that only certain combinations of actual argument
@@ -420,7 +447,8 @@
      Selection of the common type considers the actual types
      of <type>anycompatible</type> and <type>anycompatiblenonarray</type>
      inputs, the array element types of <type>anycompatiblearray</type>
-     inputs, and the range subtypes of <type>anycompatiblerange</type>
+     inputs, the range subtypes of <type>anycompatiblerange</type> inputs,
+     and the multirange subtypes of <type>anycompatiablemultirange</type>
      inputs.  If <type>anycompatiblenonarray</type> is present then the
      common type is required to be a non-array type.  Once a common type is
      identified, arguments in <type>anycompatible</type>
@@ -431,12 +459,15 @@
 
     <para>
      Since there is no way to select a range type knowing only its subtype,
-     use of <type>anycompatiblerange</type> requires that all arguments
-     declared with that type have the same actual range type, and that that
-     type's subtype agree with the selected common type, so that no casting
-     of the range values is required.  As with <type>anyrange</type>, use
-     of <type>anycompatiblerange</type> as a function result type requires
-     that there be an <type>anycompatiblerange</type> argument.
+     use of <type>anycompatiblerange</type> and/or
+     <type>anycompatiblemultirange</type> requires that all arguments declared
+     with that type have the same actual range and/or multirange type, and that
+     that type's subtype agree with the selected common type, so that no casting
+     of the range values is required.  As with <type>anyrange</type> and
+     <type>anymultirange</type>, use of <type>anycompatiblerange</type> and
+     <type>anymultirange</type> as a function result type requires that there be
+     an <type>anycompatiblerange</type> or <type>anycompatiblemultirange</type>
+     argument.
     </para>
 
     <para>
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 461b748d89..c298346263 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -17924,12 +17924,15 @@ SELECT NULLIF(value, '(none)') ...
   <para>
    <xref linkend="range-operators-table"/> shows the specialized operators
    available for range types.
+   <xref linkend="multirange-operators-table"/> shows the specialized operators
+   available for multirange types.
    In addition to those, the usual comparison operators shown in
    <xref linkend="functions-comparison-op-table"/> are available for range
-   types.  The comparison operators order first by the range lower bounds, and
-   only if those are equal do they compare the upper bounds.  This does not
-   usually result in a useful overall ordering, but the operators are provided
-   to allow unique indexes to be constructed on ranges.
+   and multirange types.  The comparison operators order first by the range lower
+   bounds, and only if those are equal do they compare the upper bounds.  The
+   multirange operators compare each range until one is unequal. This
+   does not usually result in a useful overall ordering, but the operators are
+   provided to allow unique indexes to be constructed on ranges.
   </para>
 
    <table id="range-operators-table">
@@ -18139,15 +18142,449 @@ SELECT NULLIF(value, '(none)') ...
     </tgroup>
    </table>
 
+   <table id="multirange-operators-table">
+    <title>Multirange Operators</title>
+    <tgroup cols="1">
+     <thead>
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        Operator
+       </para>
+       <para>
+        Description
+       </para>
+       <para>
+        Example(s)
+       </para></entry>
+      </row>
+     </thead>
+
+     <tbody>
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>@&gt;</literal> <type>anymultirange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Does the first multirange contain the second?
+       </para>
+       <para>
+        <literal>'{[2,4)}'::int4multirange @&gt; '{[2,3)}'::int4multirange</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>@&gt;</literal> <type>anyrange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Does the multirange contain the range?
+       </para>
+       <para>
+        <literal>'{[2,4)}'::int4multirange @&gt; int4range(2,3)</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>@&gt;</literal> <type>anyelement</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Does the multirange contain the element?
+       </para>
+       <para>
+        <literal>'{[2011-01-01,2011-03-01)}'::tsmultirange @&gt; '2011-01-10'::timestamp</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>&lt;@</literal> <type>anymultirange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the first multirange contained by the second?
+       </para>
+       <para>
+        <literal>'{[2,4)}'::int4multirange &lt;@ '{[1,7)}'::int4multirange</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>&lt;@</literal> <type>anyrange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the multirange contained by the range?
+       </para>
+       <para>
+        <literal>'{[2,4)}'::int4multirange &lt;@ int4range(1,7)</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anyrange</type> <literal>&lt;@</literal> <type>anymultirange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the range contained by the multirange?
+       </para>
+       <para>
+        <literal>int4range(2,4) &lt;@ '{[1,7)}'::int4multirange</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anyelement</type> <literal>&lt;@</literal> <type>anymultirange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the element contained by the multirange?
+       </para>
+       <para>
+        <literal>42 &lt;@ '{[1,7)}'::int4multirange</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>&amp;&amp;</literal> <type>anymultirange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Do the multiranges overlap, that is, have any elements in common?
+       </para>
+       <para>
+        <literal>'{[3,7)}'::int8multirange &amp;&amp; '{[4,12)}'::int8multirange</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>&amp;&amp;</literal> <type>anyrange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Does the multirange overlap the range?
+       </para>
+       <para>
+        <literal>'{[3,7)}'::int8multirange &amp;&amp; int8range(4,12)</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anyrange</type> <literal>&amp;&amp;</literal> <type>anymultirange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Does the range overlap the multirange?
+       </para>
+       <para>
+        <literal>int8range(3,7) &amp;&amp; '{[4,12)}'::int8multirange</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>&lt;&lt;</literal> <type>anymultirange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the first multirange strictly left of the second?
+       </para>
+       <para>
+        <literal>'{[1,10)}'::int8multirange &lt;&lt; '{[100,110)}'::int8multirange</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>&lt;&lt;</literal> <type>anyrange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the multirange strictly left of the range?
+       </para>
+       <para>
+        <literal>'{[1,10)}'::int8multirange &lt;&lt; int8range(100,110)</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anyrange</type> <literal>&lt;&lt;</literal> <type>anymultirange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the range strictly left of the multirange?
+       </para>
+       <para>
+        <literal>int8range(1,10) &lt;&lt; '{[100,110)}'::int8multirange</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>&gt;&gt;</literal> <type>anymultirange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the first multirange strictly right of the second?
+       </para>
+       <para>
+        <literal>'{[50,60)}'::int8multirange &gt;&gt; '{[20,30)}'::int8multirange</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>&gt;&gt;</literal> <type>anyrange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the multirange strictly right of the range?
+       </para>
+       <para>
+        <literal>'{[50,60)}'::int8multirange &gt;&gt; int8range(20,30)</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anyrange</type> <literal>&gt;&gt;</literal> <type>anymultirange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the range strictly right of the multirange?
+       </para>
+       <para>
+        <literal>int8range(50,60) &gt;&gt; '{[20,30)}'::int8multirange</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>&amp;&lt;</literal> <type>anymultirange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Does the first multirange not extend to the right of the second?
+       </para>
+       <para>
+        <literal>'{[1,20)}'::int8multirange &amp;&lt; '{[18,20)}'::int8multirange</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>&amp;&lt;</literal> <type>anyrange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Does the multirange not extend to the right of the range?
+       </para>
+       <para>
+        <literal>'{[1,20)}'::int8multirange &amp;&lt; int8range(18,20)</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anyrange</type> <literal>&amp;&lt;</literal> <type>anymultirange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Does the range not extend to the right of the multirange?
+       </para>
+       <para>
+        <literal>int8range(1,20) &amp;&lt; '{[18,20)}'::int8multirange</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>&amp;&gt;</literal> <type>anymultirange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Does the first multirange not extend to the left of the second?
+       </para>
+       <para>
+        <literal>'{[7,20)}'::int8multirange &amp;&gt; '{[5,10)}'::int8multirange</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>&amp;&gt;</literal> <type>anyrange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Does the multirange not extend to the left of the range?
+       </para>
+       <para>
+        <literal>'{[7,20)}'::int8multirange &amp;&gt; int8range(5,10)</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anyrange</type> <literal>&amp;&gt;</literal> <type>anymultirange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Does the range not extend to the left of the multirange?
+       </para>
+       <para>
+        <literal>int8range(7,20) &amp;&gt; '{[5,10)}'::int8multirange</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>-|-</literal> <type>anymultirange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Are the multiranges adjacent?
+       </para>
+       <para>
+        <literal>'{[1.1,2.2)}'::nummultirange -|- '{[2.2,3.3)}'::nummultirange</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>-|-</literal> <type>anyrange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the multirange adjacent to the range?
+       </para>
+       <para>
+        <literal>'{[1.1,2.2)}'::nummultirange -|- numrange(2.2,3.3)</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anyrange</type> <literal>-|-</literal> <type>anymultirange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the range adjacent to the multirange?
+       </para>
+       <para>
+        <literal>numrange(1.1,2.2) -|- '{[2.2,3.3)}'::nummultirange</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>+</literal> <type>anymultirange</type>
+        <returnvalue>anymultirange</returnvalue>
+       </para>
+       <para>
+        Computes the union of the multiranges.  The multiranges need not overlap
+        or be adjacent.
+       </para>
+       <para>
+        <literal>'{[5,10)}'::nummultirange + '{[15,20)}'::nummultirange</literal>
+        <returnvalue>{[5,10), [15,20)}</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>*</literal> <type>anymultirange</type>
+        <returnvalue>anymultirange</returnvalue>
+       </para>
+       <para>
+        Computes the intersection of the multiranges.
+       </para>
+       <para>
+        <literal>'{[5,15)}'::int8multirange * '{[10,20)}'::int8multirange</literal>
+        <returnvalue>{[10,15)}</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>-</literal> <type>anymultirange</type>
+        <returnvalue>anymultirange</returnvalue>
+       </para>
+       <para>
+        Computes the difference of the multiranges.
+       </para>
+       <para>
+        <literal>'{[5,20)}'::int8multirange - '{[10,15)}'::int8multirange</literal>
+        <returnvalue>{[5,10), [15,20)}</returnvalue>
+       </para></entry>
+      </row>
+     </tbody>
+    </tgroup>
+   </table>
+
   <para>
    The left-of/right-of/adjacent operators always return false when an empty
-   range is involved; that is, an empty range is not considered to be either
-   before or after any other range.
+   range or multirange is involved; that is, an empty range is not considered to
+   be either before or after any other range.
+  </para>
+
+  <para>
+   Elsewhere empty ranges and multiranges are treated as the additive identity:
+   anything unioned with an empty value is itself. Anything minus an empty
+   value is itself. An empty multirange has exactly the same points as an empty
+   range. Every range contains the empty range. Every multirange contains as many
+   empty ranges as you like.
+  </para>
+
+  <para>
+   The range union and difference operators will fail if the resulting range would
+   need to contain two disjoint sub-ranges, as such a range cannot be
+   represented. There are separate operators for union and difference that take
+   multirange parameters and return a multirange, and they do not fail even if
+   their arguments are disjoint. So if you need a union or difference operation
+   for ranges that may be disjoint, you can avoid errors by first casting your
+   ranges to multiranges.
   </para>
 
   <para>
    <xref linkend="range-functions-table"/> shows the functions
    available for use with range types.
+   <xref linkend="multirange-functions-table"/> shows the functions
+   available for use with multirange types.
   </para>
 
    <table id="range-functions-table">
@@ -18309,10 +18746,185 @@ SELECT NULLIF(value, '(none)') ...
     </tgroup>
    </table>
 
+   <table id="multirange-functions-table">
+    <title>Multirange Functions</title>
+    <tgroup cols="1">
+     <thead>
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        Function
+       </para>
+       <para>
+        Description
+       </para>
+       <para>
+        Example(s)
+       </para></entry>
+      </row>
+     </thead>
+     <tbody>
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>lower</primary>
+        </indexterm>
+        <function>lower</function> ( <type>anymultirange</type> )
+        <returnvalue>anyelement</returnvalue>
+       </para>
+       <para>
+        Extracts the lower bound of the multirange (<literal>NULL</literal> if the
+        multirange is empty or the lower bound is infinite).
+       </para>
+       <para>
+        <literal>lower('{[1.1,2.2)}'::nummultirange)</literal>
+        <returnvalue>1.1</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>upper</primary>
+        </indexterm>
+        <function>upper</function> ( <type>anymultirange</type> )
+        <returnvalue>anyelement</returnvalue>
+       </para>
+       <para>
+        Extracts the upper bound of the multirange (<literal>NULL</literal> if the
+        multirange is empty or the upper bound is infinite).
+       </para>
+       <para>
+        <literal>upper('{[1.1,2.2)}'::nummultirange)</literal>
+        <returnvalue>2.2</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>isempty</primary>
+        </indexterm>
+        <function>isempty</function> ( <type>anymultirange</type> )
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the multirange empty?
+       </para>
+       <para>
+        <literal>isempty('{[1.1,2.2)}'::nummultirange)</literal>
+        <returnvalue>f</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>lower_inc</primary>
+        </indexterm>
+        <function>lower_inc</function> ( <type>anymultirange</type> )
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the multirange's lower bound inclusive?
+       </para>
+       <para>
+        <literal>lower_inc('{[1.1,2.2)}'::nummultirange)</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>upper_inc</primary>
+        </indexterm>
+        <function>upper_inc</function> ( <type>anymultirange</type> )
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the multirange's upper bound inclusive?
+       </para>
+       <para>
+        <literal>upper_inc('{[1.1,2.2)}'::nummultirange)</literal>
+        <returnvalue>f</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>lower_inf</primary>
+        </indexterm>
+        <function>lower_inf</function> ( <type>anymultirange</type> )
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the multirange's lower bound infinite?
+       </para>
+       <para>
+        <literal>lower_inf('{(,)}'::datemultirange)</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>upper_inf</primary>
+        </indexterm>
+        <function>upper_inf</function> ( <type>anymultirange</type> )
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the multirange's upper bound infinite?
+       </para>
+       <para>
+        <literal>upper_inf('{(,)}'::datemultirange)</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>range_merge</primary>
+        </indexterm>
+        <function>range_merge</function> ( <type>anymultirange</type> )
+        <returnvalue>anyrange</returnvalue>
+       </para>
+       <para>
+        Computes the smallest range that includes the entire multirange.
+       </para>
+       <para>
+        <literal>range_merge('{[1,2), [3,4)}'::int4multirange)</literal>
+        <returnvalue>[1,4)</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>multirange</primary>
+        </indexterm>
+        <function>multirange</function> ( <type>anyrange</type> )
+        <returnvalue>anymultirange</returnvalue>
+       </para>
+       <para>
+        Returns a multirange containing just the given range.
+       </para>
+       <para>
+        <literal>multirange('[1,2)'::int4range)</literal>
+        <returnvalue>{[1,2)}</returnvalue>
+       </para></entry>
+      </row>
+     </tbody>
+    </tgroup>
+   </table>
+
   <para>
    The <function>lower_inc</function>, <function>upper_inc</function>,
    <function>lower_inf</function>, and <function>upper_inf</function>
-   functions all return false for an empty range.
+   functions all return false for an empty range or multirange.
   </para>
   </sect1>
 
@@ -18644,6 +19256,36 @@ SELECT NULLIF(value, '(none)') ...
        <entry>Yes</entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>range_agg</primary>
+        </indexterm>
+        <function>range_agg</function> ( <parameter>value</parameter>
+         <type>anyrange</type> )
+        <returnvalue>anymultirange</returnvalue>
+       </para>
+       <para>
+        Computes the union of the non-null input values.
+       </para></entry>
+       <entry>No</entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>range_intersect_agg</primary>
+        </indexterm>
+        <function>range_intersect_agg</function> ( <parameter>value</parameter>
+         <type>anyrange</type> )
+        <returnvalue>anymultirange</returnvalue>
+       </para>
+       <para>
+        Computes the intersection of the non-null input values.
+       </para></entry>
+       <entry>No</entry>
+      </row>
+
       <row>
        <entry role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/doc/src/sgml/rangetypes.sgml b/doc/src/sgml/rangetypes.sgml
index b75fb3a392..c8784bdce3 100644
--- a/doc/src/sgml/rangetypes.sgml
+++ b/doc/src/sgml/rangetypes.sgml
@@ -27,6 +27,13 @@
   ranges from an instrument, and so forth can also be useful.
  </para>
 
+ <para>
+  Every range type has a corresponding multirange type. A multirange is
+  an ordered list of non-continguous, non-empty, non-null ranges. Most
+  range operators also work on multiranges, and they have a few functions
+  of their own.
+ </para>
+
  <sect2 id="rangetypes-builtin">
   <title>Built-in Range Types</title>
 
@@ -232,10 +239,30 @@ SELECT '[4,4]'::int4range;
 SELECT '[4,4)'::int4range;
 </programlisting>
   </para>
+
+  <para>
+   The input for a multirange is curly brackets (<literal>{</literal> and
+   <literal>}</literal>) containing zero or more valid ranges,
+   separated by commas. Whitespace is permitted around the brackets and
+   commas. This is intended to be reminiscent of array syntax, although
+   multiranges are much simpler: they have just one dimension and there is
+   no need to quote their contents. (The bounds of their ranges may be
+   quoted as above however.)
+  </para>
+
+  <para>
+  Examples:
+<programlisting>
+SELECT '{}'::int4multirange;
+SELECT '{[3,7)}'::int4multirange;
+SELECT '{[3,7), [8,9)}'::int4multirange;
+</programlisting>
+  </para>
+
  </sect2>
 
  <sect2 id="rangetypes-construct">
-  <title>Constructing Ranges</title>
+  <title>Constructing Ranges and Multiranges</title>
 
   <para>
    Each range type has a constructor function with the same name as the range
@@ -267,6 +294,19 @@ SELECT int8range(1, 14, '(]');
 
 -- Using NULL for either bound causes the range to be unbounded on that side.
 SELECT numrange(NULL, 2.2);
+</programlisting>
+  </para>
+
+  <para>
+   Each range type also has a multirange constructor with the same name as the
+   multirange type.  The constructor function takes zero or more arguments
+   which are all ranges of the appropriate type.
+   For example:
+
+<programlisting>
+SELECT nummultirange();
+SELECT nummultirange(numrange(1.0, 14.0));
+SELECT nummultirange(numrange(1.0, 14.0), numrange(20.0, 25.0));
 </programlisting>
   </para>
  </sect2>
@@ -341,6 +381,11 @@ SELECT '[1.234, 5.678]'::floatrange;
    function in this example.
   </para>
 
+  <para>
+   When you define your own range you automatically get a corresponding
+   multirange type.
+  </para>
+
   <para>
    Defining your own range type also allows you to specify a different
    subtype B-tree operator class or collation to use, so as to change the sort
diff --git a/src/backend/catalog/pg_range.c b/src/backend/catalog/pg_range.c
index a606d8c3ad..1cbe9fb889 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];
@@ -44,6 +44,7 @@ RangeCreate(Oid rangeTypeOid, Oid rangeSubType, Oid rangeCollation,
 	ObjectAddress myself;
 	ObjectAddress referenced;
 	ObjectAddresses *addrs;
+	ObjectAddress referencing;
 
 	pg_range = table_open(RangeRelationId, RowExclusiveLock);
 
@@ -55,6 +56,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_rngmultitypid - 1] = ObjectIdGetDatum(multirangeTypeOid);
 
 	tup = heap_form_tuple(RelationGetDescr(pg_range), values, nulls);
 
@@ -93,6 +95,12 @@ RangeCreate(Oid rangeTypeOid, Oid rangeSubType, Oid rangeCollation,
 	record_object_address_dependencies(&myself, addrs, DEPENDENCY_NORMAL);
 	free_object_addresses(addrs);
 
+	/* 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 0b04dff773..129c9f04e2 100644
--- a/src/backend/catalog/pg_type.c
+++ b/src/backend/catalog/pg_type.c
@@ -27,6 +27,7 @@
 #include "catalog/pg_proc.h"
 #include "catalog/pg_type.h"
 #include "commands/typecmds.h"
+#include "mb/pg_wchar.h"
 #include "miscadmin.h"
 #include "parser/scansup.h"
 #include "utils/acl.h"
@@ -36,6 +37,9 @@
 #include "utils/rel.h"
 #include "utils/syscache.h"
 
+static char *makeUniqueTypeName(const char *typeName, Oid typeNamespace,
+								bool tryOriginal);
+
 /* Potentially set by pg_upgrade_support functions */
 Oid			binary_upgrade_next_pg_type_oid = InvalidOid;
 
@@ -794,31 +798,10 @@ RenameTypeInternal(Oid typeOid, const char *newTypeName, Oid typeNamespace)
 char *
 makeArrayTypeName(const char *typeName, Oid typeNamespace)
 {
-	char	   *arr = (char *) palloc(NAMEDATALEN);
-	int			namelen = strlen(typeName);
-	int			i;
-
-	/*
-	 * 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;
-	}
+	char	   *arr;
 
-	if (i >= NAMEDATALEN - 1)
+	arr = makeUniqueTypeName(typeName, typeNamespace, false);
+	if (arr == NULL)
 		ereport(ERROR,
 				(errcode(ERRCODE_DUPLICATE_OBJECT),
 				 errmsg("could not form array type name for type \"%s\"",
@@ -889,3 +872,91 @@ moveArrayTypeName(Oid typeOid, const char *typeName, Oid typeNamespace)
 
 	return true;
 }
+
+
+/*
+ * makeMultirangeTypeName
+ *	  - given a range type name, make a multirange type name for it
+ *
+ * caller is responsible for pfreeing the result
+ */
+char *
+makeMultirangeTypeName(const char *rangeTypeName, Oid typeNamespace)
+{
+	char	   *buf;
+	char	   *mrname;
+	char	   *rangestr;
+
+	/*
+	 * 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...
+	 */
+	rangestr = strstr(rangeTypeName, "range");
+	if (rangestr)
+	{
+		char	*prefix = pnstrdup(rangeTypeName, rangestr - rangeTypeName);
+
+		buf = psprintf("%s%s%s", prefix, "multi", rangestr);
+	}
+	else
+		buf = psprintf("%s_multirange", pnstrdup(rangeTypeName, NAMEDATALEN - 12));
+
+	/* clip it at NAMEDATALEN-1 bytes */
+	buf[pg_mbcliplen(buf, strlen(buf), NAMEDATALEN - 1)] = '\0';
+
+	mrname = makeUniqueTypeName(buf, typeNamespace, true);
+	if (mrname == NULL)
+		ereport(ERROR,
+				(errcode(ERRCODE_DUPLICATE_OBJECT),
+				 errmsg("could not form multirange type name for type \"%s\"",
+						rangeTypeName)));
+
+	return mrname;
+}
+
+/*
+ * makeUniqueTypeName
+ *		Generate a unique name for a prospective new type
+ *
+ * Given a typeName, return a new palloc'ed name by preprending underscores
+ * until a non-conflicting name results.
+ *
+ * If tryOriginal, first try with zero underscores.
+ */
+static char *
+makeUniqueTypeName(const char *typeName, Oid typeNamespace, bool tryOriginal)
+{
+	int			i;
+	int			namelen;
+	char		dest[NAMEDATALEN];
+
+	Assert(strlen(typeName) <= NAMEDATALEN - 1);
+
+	if (tryOriginal &&
+		!SearchSysCacheExists2(TYPENAMENSP,
+							   CStringGetDatum(typeName),
+							   ObjectIdGetDatum(typeNamespace)))
+		return pstrdup(typeName);
+
+	/*
+	 * The idea is to prepend underscores as needed until we make a name that
+	 * doesn't collide with anything ...
+	 */
+	namelen = strlen(typeName);
+	for (i = 1; i < NAMEDATALEN - 1; i++)
+	{
+		dest[i - 1] = '_';
+		strlcpy(dest + i, typeName, NAMEDATALEN - i);
+		if (namelen + i >= NAMEDATALEN)
+			truncate_identifier(dest, NAMEDATALEN, false);
+
+		if (!SearchSysCacheExists2(TYPENAMENSP,
+								   CStringGetDatum(dest),
+								   ObjectIdGetDatum(typeNamespace)))
+			return pstrdup(dest);
+	}
+
+	return NULL;
+}
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index 483bb65ddc..b24fc1f566 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -105,9 +105,14 @@ typedef struct
 
 /* Potentially set by pg_upgrade_support functions */
 Oid			binary_upgrade_next_array_pg_type_oid = InvalidOid;
+Oid			binary_upgrade_next_mrng_pg_type_oid = InvalidOid;
+Oid			binary_upgrade_next_mrng_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 rangeOid,
+									   Oid rangeArrayOid, Oid *castFuncOid);
 static Oid	findTypeInputFunction(List *procname, Oid typeOid);
 static Oid	findTypeOutputFunction(List *procname, Oid typeOid);
 static Oid	findTypeReceiveFunction(List *procname, Oid typeOid);
@@ -739,7 +744,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",
@@ -1280,6 +1286,11 @@ checkEnumOwner(HeapTuple tup)
 /*
  * DefineRange
  *		Registers a new range type.
+ *
+ * Perhaps it might be worthwhile to set pg_type.typelem to the base type,
+ * and likewise on multiranges to set it to the range type. But having a
+ * non-zero typelem is treated elsewhere as a synonym for being an array,
+ * and users might have queries with that same assumption.
  */
 ObjectAddress
 DefineRange(CreateRangeStmt *stmt)
@@ -1288,7 +1299,11 @@ DefineRange(CreateRangeStmt *stmt)
 	Oid			typeNamespace;
 	Oid			typoid;
 	char	   *rangeArrayName;
+	char	   *multirangeTypeName;
+	char	   *multirangeArrayName;
 	Oid			rangeArrayOid;
+	Oid			multirangeOid;
+	Oid			multirangeArrayOid;
 	Oid			rangeSubtype = InvalidOid;
 	List	   *rangeSubOpclassName = NIL;
 	List	   *rangeCollationName = NIL;
@@ -1305,6 +1320,8 @@ DefineRange(CreateRangeStmt *stmt)
 	AclResult	aclresult;
 	ListCell   *lc;
 	ObjectAddress address;
+	ObjectAddress mltrngaddress;
+	Oid			castFuncOid;
 
 	/* Convert list of names to a name and namespace */
 	typeNamespace = QualifiedNameGetCreationNamespace(stmt->typeName,
@@ -1453,8 +1470,10 @@ DefineRange(CreateRangeStmt *stmt)
 	/* alignment must be TYPALIGN_INT or TYPALIGN_DOUBLE for ranges */
 	alignment = (subtypalign == TYPALIGN_DOUBLE) ? TYPALIGN_DOUBLE : TYPALIGN_INT;
 
-	/* Allocate OID for array type */
+	/* Allocate OID for array type, its multirange, and its multirange array */
 	rangeArrayOid = AssignTypeArrayOid();
+	multirangeOid = AssignTypeMultirangeOid();
+	multirangeArrayOid = AssignTypeMultirangeArrayOid();
 
 	/* Create the pg_type entry */
 	address =
@@ -1492,9 +1511,46 @@ DefineRange(CreateRangeStmt *stmt)
 	Assert(typoid == InvalidOid || typoid == address.objectId);
 	typoid = address.objectId;
 
+	/* Create the multirange that goes with it */
+	multirangeTypeName = makeMultirangeTypeName(typeName, typeNamespace);
+
+	mltrngaddress =
+		TypeCreate(multirangeOid,	/* force assignment of this 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_RANGE,	/* type-category (range 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) */
+	Assert(multirangeOid == mltrngaddress.objectId);
+
 	/* Create the entry in pg_range */
 	RangeCreate(typoid, rangeSubtype, rangeCollation, rangeSubOpclass,
-				rangeCanonical, rangeSubtypeDiff);
+				rangeCanonical, rangeSubtypeDiff, multirangeOid);
 
 	/*
 	 * Create the array type that goes with it.
@@ -1535,8 +1591,53 @@ 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 */
+			   multirangeOid,	/* 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,
+							   multirangeOid, typoid, rangeArrayOid,
+							   &castFuncOid);
+
+	/* Create cast from the range type to its multirange type */
+	CastCreate(typoid, multirangeOid, castFuncOid, 'e', 'f', DEPENDENCY_INTERNAL);
+
+	pfree(multirangeTypeName);
+	pfree(multirangeArrayName);
 
 	return address;
 }
@@ -1614,6 +1715,147 @@ 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.
+ *
+ * Sets castFuncOid to the oid of the new constructor that can be used
+ * to cast from a range to a multirange.
+ */
+static void
+makeMultirangeConstructors(const char *name, Oid namespace,
+						   Oid multirangeOid, Oid rangeOid, Oid rangeArrayOid,
+						   Oid *castFuncOid)
+{
+	ObjectAddress myself,
+				referenced;
+	oidvector  *argtypes;
+	Datum		allParamTypes;
+	ArrayType  *allParameterTypes;
+	Datum		paramModes;
+	ArrayType  *parameterModes;
+
+	referenced.classId = TypeRelationId;
+	referenced.objectId = multirangeOid;
+	referenced.objectSubId = 0;
+
+	/* 0-arg constructor - for empty multiranges */
+	argtypes = buildoidvector(NULL, 0);
+	myself = ProcedureCreate(name,	/* name: same as multirange type */
+							 namespace,
+							 false,	/* replace */
+							 false,	/* returns set */
+							 multirangeOid,	/* return type */
+							 BOOTSTRAP_SUPERUSERID,	/* proowner */
+							 INTERNALlanguageId,	/* language */
+							 F_FMGR_INTERNAL_VALIDATOR,
+							 "multirange_constructor0",	/* prosrc */
+							 NULL,	/* probin */
+							 PROKIND_FUNCTION,
+							 false,	/* security_definer */
+							 false,	/* leakproof */
+							 false, /* isStrict */
+							 PROVOLATILE_IMMUTABLE,	/* volatility */
+							 PROPARALLEL_SAFE,	/* parallel safety */
+							 argtypes,	/* parameterTypes */
+							 PointerGetDatum(NULL),	/* allParameterTypes */
+							 PointerGetDatum(NULL),	/* parameterModes */
+							 PointerGetDatum(NULL),	/* parameterNames */
+							 NIL,	/* parameterDefaults */
+							 PointerGetDatum(NULL),	/* trftypes */
+							 PointerGetDatum(NULL),	/* proconfig */
+							 InvalidOid,	/* prosupport */
+							 1.0,	/* procost */
+							 0.0);	/* prorows */
+	/*
+	 * Make the constructor 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);
+	pfree(argtypes);
+
+	/*
+	 * 1-arg constructor - for casts
+	 *
+	 * In theory we shouldn't need both this and the vararg (n-arg) constructor,
+	 * but having a separate 1-arg function lets us define casts against it.
+	 */
+	argtypes = buildoidvector(&rangeOid, 1);
+	myself = ProcedureCreate(name,	/* name: same as multirange type */
+							 namespace,
+							 false,	/* replace */
+							 false,	/* returns set */
+							 multirangeOid,	/* return type */
+							 BOOTSTRAP_SUPERUSERID,	/* proowner */
+							 INTERNALlanguageId,	/* language */
+							 F_FMGR_INTERNAL_VALIDATOR,
+							 "multirange_constructor1",	/* prosrc */
+							 NULL,	/* probin */
+							 PROKIND_FUNCTION,
+							 false,	/* security_definer */
+							 false,	/* leakproof */
+							 true, /* isStrict */
+							 PROVOLATILE_IMMUTABLE,	/* volatility */
+							 PROPARALLEL_SAFE,	/* parallel safety */
+							 argtypes,	/* parameterTypes */
+							 PointerGetDatum(NULL),	/* allParameterTypes */
+							 PointerGetDatum(NULL),	/* parameterModes */
+							 PointerGetDatum(NULL),	/* parameterNames */
+							 NIL,	/* parameterDefaults */
+							 PointerGetDatum(NULL),	/* trftypes */
+							 PointerGetDatum(NULL),	/* proconfig */
+							 InvalidOid,	/* prosupport */
+							 1.0,	/* procost */
+							 0.0);	/* prorows */
+	/* ditto */
+	recordDependencyOn(&myself, &referenced, DEPENDENCY_INTERNAL);
+	pfree(argtypes);
+	*castFuncOid = myself.objectId;
+
+	/* n-arg constructor - vararg */
+	argtypes = buildoidvector(&rangeArrayOid, 1);
+	allParamTypes = ObjectIdGetDatum(rangeArrayOid);
+	allParameterTypes = construct_array(&allParamTypes,
+										1, OIDOID,
+										sizeof(Oid), true, 'i');
+	paramModes = CharGetDatum(FUNC_PARAM_VARIADIC);
+	parameterModes = construct_array(&paramModes, 1, CHAROID,
+									 1, true, 'c');
+	myself = ProcedureCreate(name,	/* name: same as multirange type */
+							 namespace,
+							 false,	/* replace */
+							 false,	/* returns set */
+							 multirangeOid,	/* return type */
+							 BOOTSTRAP_SUPERUSERID,	/* proowner */
+							 INTERNALlanguageId,	/* language */
+							 F_FMGR_INTERNAL_VALIDATOR,
+							 "multirange_constructor2",	/* prosrc */
+							 NULL,	/* probin */
+							 PROKIND_FUNCTION,
+							 false,	/* security_definer */
+							 false,	/* leakproof */
+							 false, /* isStrict */
+							 PROVOLATILE_IMMUTABLE,	/* volatility */
+							 PROPARALLEL_SAFE,	/* parallel safety */
+							 argtypes,	/* parameterTypes */
+							 PointerGetDatum(allParameterTypes),	/* allParameterTypes */
+							 PointerGetDatum(parameterModes),	/* parameterModes */
+							 PointerGetDatum(NULL),	/* parameterNames */
+							 NIL,	/* parameterDefaults */
+							 PointerGetDatum(NULL),	/* trftypes */
+							 PointerGetDatum(NULL),	/* proconfig */
+							 InvalidOid,	/* prosupport */
+							 1.0,	/* procost */
+							 0.0);	/* prorows */
+	/* ditto */
+	recordDependencyOn(&myself, &referenced, DEPENDENCY_INTERNAL);
+	pfree(argtypes);
+	pfree(allParameterTypes);
+	pfree(parameterModes);
+}
 
 /*
  * Find suitable I/O functions for a type.
@@ -2068,6 +2310,72 @@ AssignTypeArrayOid(void)
 	return type_array_oid;
 }
 
+/*
+ *	AssignTypeMultirangeOid
+ *
+ *	Pre-assign the range type's multirange OID for use in pg_type.oid
+ */
+Oid
+AssignTypeMultirangeOid(void)
+{
+	Oid			type_multirange_oid;
+
+	/* Use binary-upgrade override for pg_type.oid? */
+	if (IsBinaryUpgrade)
+	{
+		if (!OidIsValid(binary_upgrade_next_mrng_pg_type_oid))
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+					 errmsg("pg_type multirange OID value not set when in binary upgrade mode")));
+
+		type_multirange_oid = binary_upgrade_next_mrng_pg_type_oid;
+		binary_upgrade_next_mrng_pg_type_oid = InvalidOid;
+	}
+	else
+	{
+		Relation	pg_type = table_open(TypeRelationId, AccessShareLock);
+
+		type_multirange_oid = GetNewOidWithIndex(pg_type, TypeOidIndexId,
+												 Anum_pg_type_oid);
+		table_close(pg_type, AccessShareLock);
+	}
+
+	return type_multirange_oid;
+}
+
+/*
+ *	AssignTypeMultirangeArrayOid
+ *
+ *	Pre-assign the range type's multirange array OID for use in pg_type.typarray
+ */
+Oid
+AssignTypeMultirangeArrayOid(void)
+{
+	Oid			type_multirange_array_oid;
+
+	/* Use binary-upgrade override for pg_type.oid? */
+	if (IsBinaryUpgrade)
+	{
+		if (!OidIsValid(binary_upgrade_next_mrng_array_pg_type_oid))
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+					 errmsg("pg_type multirange array OID value not set when in binary upgrade mode")));
+
+		type_multirange_array_oid = binary_upgrade_next_mrng_array_pg_type_oid;
+		binary_upgrade_next_mrng_array_pg_type_oid = InvalidOid;
+	}
+	else
+	{
+		Relation	pg_type = table_open(TypeRelationId, AccessShareLock);
+
+		type_multirange_array_oid = GetNewOidWithIndex(pg_type, TypeOidIndexId,
+													   Anum_pg_type_oid);
+		table_close(pg_type, AccessShareLock);
+	}
+
+	return type_multirange_array_oid;
+}
+
 
 /*-------------------------------------------------------------------
  * DefineCompositeType
diff --git a/src/backend/executor/functions.c b/src/backend/executor/functions.c
index f940f48c6d..cdeda14dd9 100644
--- a/src/backend/executor/functions.c
+++ b/src/backend/executor/functions.c
@@ -1694,7 +1694,8 @@ check_sql_fn_retval(List *queryTreeList,
 	if (fn_typtype == TYPTYPE_BASE ||
 		fn_typtype == TYPTYPE_DOMAIN ||
 		fn_typtype == TYPTYPE_ENUM ||
-		fn_typtype == TYPTYPE_RANGE)
+		fn_typtype == TYPTYPE_RANGE ||
+		fn_typtype == TYPTYPE_MULTIRANGE)
 	{
 		/*
 		 * For scalar-type returns, the target list must have exactly one
diff --git a/src/backend/parser/parse_coerce.c b/src/backend/parser/parse_coerce.c
index 2ffe47026b..37c3a97879 100644
--- a/src/backend/parser/parse_coerce.c
+++ b/src/backend/parser/parse_coerce.c
@@ -189,19 +189,21 @@ coerce_type(ParseState *pstate, Node *node,
 	if (targetTypeId == ANYARRAYOID ||
 		targetTypeId == ANYENUMOID ||
 		targetTypeId == ANYRANGEOID ||
+		targetTypeId == ANYMULTIRANGEOID ||
 		targetTypeId == ANYCOMPATIBLEARRAYOID ||
-		targetTypeId == ANYCOMPATIBLERANGEOID)
+		targetTypeId == ANYCOMPATIBLERANGEOID ||
+		targetTypeId == ANYCOMPATIBLEMULTIRANGEOID)
 	{
 		/*
 		 * 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 the pseudotype's input
-		 * function, 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
+		 * the pseudotype's input function, 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
@@ -1532,8 +1534,8 @@ coerce_to_common_type(ParseState *pstate, Node *node,
  * 1) All arguments declared ANYELEMENT must have the same datatype.
  * 2) All arguments declared ANYARRAY must have the same datatype,
  *	  which must be a varlena array type.
- * 3) All arguments declared ANYRANGE must have the same datatype,
- *	  which must be a range type.
+ * 3) All arguments declared ANYRANGE or ANYMULTIRANGE must be a range or
+ *	  multirange type, all derived from the same base datatype.
  * 4) If there are arguments of more than one of these polymorphic types,
  *	  the array element type and/or range subtype must be the same as each
  *	  other and the same as the ANYELEMENT type.
@@ -1548,8 +1550,8 @@ coerce_to_common_type(ParseState *pstate, Node *node,
  *	  to a common supertype (chosen as per select_common_type's rules).
  *	  ANYCOMPATIBLENONARRAY works like ANYCOMPATIBLE but also requires the
  *	  common supertype to not be an array.  If there are ANYCOMPATIBLEARRAY
- *	  or ANYCOMPATIBLERANGE arguments, their element types or subtypes are
- *	  included while making the choice of common supertype.
+ *	  or ANYCOMPATIBLERANGE or ANYCOMPATIBLEMULTIRANGE arguments, their element
+ *	  types or subtypes are included while making the choice of common supertype.
  * 8) The resolved type of ANYCOMPATIBLEARRAY arguments will be the array
  *	  type over the common supertype (which might not be the same array type
  *	  as any of the original arrays).
@@ -1557,6 +1559,10 @@ coerce_to_common_type(ParseState *pstate, Node *node,
  *	  (after domain flattening), since we have no preference rule that would
  *	  let us choose one over another.  Furthermore, that range's subtype
  *	  must exactly match the common supertype chosen by rule 7.
+ * 10) All ANYCOMPATIBLEMULTIRANGE arguments must be the exact same multirange
+ *	  type (after domain flattening), since we have no preference rule that would
+ *	  let us choose one over another.  Furthermore, that multirange's range's
+ *	  subtype must exactly match the common supertype chosen by rule 7.
  *
  * Domains over arrays match ANYARRAY, and are immediately flattened to their
  * base type.  (Thus, for example, we will consider it a match if one ANYARRAY
@@ -1565,7 +1571,9 @@ coerce_to_common_type(ParseState *pstate, Node *node,
  * for ANYCOMPATIBLEARRAY and ANYCOMPATIBLENONARRAY.
  *
  * Similarly, domains over ranges match ANYRANGE or ANYCOMPATIBLERANGE,
- * and are immediately flattened to their base type.
+ * and are immediately flattened to their base type, and domains over
+ * multiranges match ANYMULTIRANGE or ANYCOMPATIBLEMULTIRANGE and are immediately
+ * flattened to their base type.
  *
  * Note that domains aren't currently considered to match ANYENUM,
  * even if their base type would match.
@@ -1583,8 +1591,12 @@ check_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			anycompatible_range_typeid = InvalidOid;
 	Oid			anycompatible_range_typelem = InvalidOid;
+	Oid			anycompatible_multirange_typeid = InvalidOid;
+	Oid			anycompatible_multirange_typelem = InvalidOid;
+	Oid			range_typelem = InvalidOid;
 	bool		have_anynonarray = false;
 	bool		have_anyenum = false;
 	bool		have_anycompatible_nonarray = false;
@@ -1633,6 +1645,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;
+		}
 		else if (decl_type == ANYCOMPATIBLEOID ||
 				 decl_type == ANYCOMPATIBLENONARRAYOID)
 		{
@@ -1677,6 +1698,42 @@ check_generic_type_consistency(const Oid *actual_arg_types,
 				anycompatible_actual_types[n_anycompatible_args++] = anycompatible_range_typelem;
 			}
 		}
+		else if (decl_type == ANYCOMPATIBLEMULTIRANGEOID)
+		{
+			if (actual_type == UNKNOWNOID)
+				continue;
+			actual_type = getBaseType(actual_type); /* flatten domains */
+			if (OidIsValid(anycompatible_multirange_typeid))
+			{
+				/* All ANYCOMPATIBLEMULTIRANGE arguments must be the same type */
+				if (anycompatible_multirange_typeid != actual_type)
+					return false;
+			}
+			else
+			{
+				anycompatible_multirange_typeid = actual_type;
+				anycompatible_multirange_typelem = get_multirange_range(actual_type);
+				if (!OidIsValid(anycompatible_multirange_typelem))
+					return false;	/* not a multirange type */
+
+				if (OidIsValid(anycompatible_range_typeid))
+				{
+					/* ANYCOMPATIBLEMULTIRANGE and ANYCOMPATIBLERANGE arguments must match */
+					if (anycompatible_range_typeid != anycompatible_multirange_typelem)
+						return false;
+				}
+				else
+				{
+					anycompatible_range_typeid = anycompatible_multirange_typelem;
+					anycompatible_range_typelem = get_range_subtype(anycompatible_range_typeid);
+					if (!OidIsValid(anycompatible_range_typelem))
+						return false;	/* not a range type */
+				}
+				/* collect the subtype for common-supertype choice */
+				anycompatible_actual_types[n_anycompatible_args++] =
+					anycompatible_range_typelem;
+			}
+		}
 	}
 
 	/* Get the element type based on the array type, if we have one */
@@ -1723,8 +1780,6 @@ check_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))
 	{
-		Oid			range_typelem;
-
 		range_typelem = get_range_subtype(range_typeid);
 		if (!OidIsValid(range_typelem))
 			return false;		/* should be a range, but isn't */
@@ -1743,6 +1798,45 @@ 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))
+	{
+		Oid			multirange_typelem;
+
+		multirange_typelem = get_multirange_range(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 */
+		}
+		else if (multirange_typelem != range_typeid)
+		{
+			/* otherwise, they better match */
+			return false;
+		}
+
+		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 */
@@ -1781,8 +1875,10 @@ check_generic_type_consistency(const Oid *actual_arg_types,
 		}
 
 		/*
-		 * the anycompatible type must exactly match the range element type,
-		 * if we were able to identify one
+		 * The anycompatible type must exactly match the range element type,
+		 * if we were able to identify one. This checks compatibility for
+		 * anycompatiblemultirange too since that also sets
+		 * anycompatible_range_typelem above.
 		 */
 		if (OidIsValid(anycompatible_range_typelem) &&
 			anycompatible_range_typelem != anycompatible_typeid)
@@ -1821,21 +1917,27 @@ check_generic_type_consistency(const Oid *actual_arg_types,
  *	  argument's actual type as the function's return type.
  * 2) If return type is ANYARRAY, and any argument is ANYARRAY, use the
  *	  argument's actual type as the function's return type.
- * 3) Similarly, if return type is ANYRANGE, and any argument is ANYRANGE,
- *	  use the argument's actual type as the function's return type.
- * 4) Otherwise, if return type is ANYELEMENT or ANYARRAY, and there is
+ * 3) Similarly, if return type is ANYRANGE or ANYMULTIRANGE, and any
+ *	  argument is ANYRANGE or ANYMULTIRANGE, use that argument's
+ *	  actual type, range type or multirange type as the function's return
+ *	  type.
+ * 4) Otherwise, if return type is ANYMULTIRANGE, and any argument is
+ *	  ANYMULTIRANGE, use the argument's actual type as the function's return
+ *	  type. Or if any argument is ANYRANGE, use its multirange type as the
+ *	  function's return type.
+ * 5) Otherwise, if return type is ANYELEMENT or ANYARRAY, and there is
  *	  at least one ANYELEMENT, ANYARRAY, or ANYRANGE input, deduce the
  *	  return type from those inputs, or throw error if we can't.
- * 5) Otherwise, if return type is ANYRANGE, throw error.  (We have no way to
- *	  select a specific range type if the arguments don't include ANYRANGE.)
- * 6) ANYENUM is treated the same as ANYELEMENT except that if it is used
+ * 6) Otherwise, if return type is ANYRANGE or ANYMULTIRANGE, throw error.
+ *	  (We have no way to select a specific range type if the arguments don't
+ *	  include ANYRANGE.)
  *	  (alone or in combination with plain ANYELEMENT), we add the extra
  *	  condition that the ANYELEMENT type must be an enum.
- * 7) ANYNONARRAY is treated the same as ANYELEMENT except that if it is used,
+ * 8) ANYNONARRAY is treated the same as ANYELEMENT except that if it is used,
  *	  we add the extra condition that the ANYELEMENT type must not be an array.
  *	  (This is a no-op if used in combination with ANYARRAY or ANYENUM, but
  *	  is an extra restriction if not.)
- * 8) ANYCOMPATIBLE, ANYCOMPATIBLEARRAY, ANYCOMPATIBLENONARRAY, and
+ * 9) ANYCOMPATIBLE, ANYCOMPATIBLEARRAY, ANYCOMPATIBLENONARRAY, and
  *	  ANYCOMPATIBLERANGE are handled by resolving the common supertype
  *	  of those arguments (or their element types/subtypes, for array and range
  *	  inputs), and then coercing all those arguments to the common supertype,
@@ -1889,10 +1991,15 @@ 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			anycompatible_typeid = InvalidOid;
 	Oid			anycompatible_array_typeid = InvalidOid;
 	Oid			anycompatible_range_typeid = InvalidOid;
 	Oid			anycompatible_range_typelem = InvalidOid;
+	Oid			anycompatible_multirange_typeid = InvalidOid;
+	Oid			anycompatible_multirange_typelem = InvalidOid;
+	Oid			range_typelem;
+	Oid			multirange_typelem;
 	bool		have_anynonarray = (rettype == ANYNONARRAYOID);
 	bool		have_anyenum = (rettype == ANYENUMOID);
 	bool		have_anycompatible_nonarray = (rettype == ANYCOMPATIBLENONARRAYOID);
@@ -1977,6 +2084,26 @@ enforce_generic_type_consistency(const Oid *actual_arg_types,
 								   format_type_be(actual_type))));
 			range_typeid = actual_type;
 		}
+		else if (decl_type == ANYMULTIRANGEOID)
+		{
+			n_poly_args++;
+			if (actual_type == UNKNOWNOID)
+			{
+				have_poly_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;
+		}
 		else if (decl_type == ANYCOMPATIBLEOID ||
 				 decl_type == ANYCOMPATIBLENONARRAYOID)
 		{
@@ -2045,6 +2172,40 @@ enforce_generic_type_consistency(const Oid *actual_arg_types,
 				anycompatible_actual_types[n_anycompatible_args++] = anycompatible_range_typelem;
 			}
 		}
+		else if (decl_type == ANYCOMPATIBLEMULTIRANGEOID)
+		{
+			have_poly_anycompatible = true;
+			if (actual_type == UNKNOWNOID)
+				continue;
+			if (allow_poly && decl_type == actual_type)
+				continue;		/* no new information here */
+			actual_type = getBaseType(actual_type); /* flatten domains */
+			if (OidIsValid(anycompatible_multirange_typeid))
+			{
+				/* All ANYCOMPATIBLEMULTIRANGE arguments must be the same type */
+				if (anycompatible_multirange_typeid != actual_type)
+					ereport(ERROR,
+							(errcode(ERRCODE_DATATYPE_MISMATCH),
+							 errmsg("arguments declared \"anycompatiblemultirange\" are not all alike"),
+							 errdetail("%s versus %s",
+									   format_type_be(anycompatible_multirange_typeid),
+									   format_type_be(actual_type))));
+			}
+			else
+			{
+				anycompatible_multirange_typeid = actual_type;
+				anycompatible_multirange_typelem = get_multirange_range(actual_type);
+				anycompatible_range_typelem = get_range_subtype(anycompatible_multirange_typelem);
+				if (!OidIsValid(anycompatible_multirange_typelem))
+					ereport(ERROR,
+							(errcode(ERRCODE_DATATYPE_MISMATCH),
+							 errmsg("argument declared %s is not a multirange type but type %s",
+									"anycompatiblemultirange",
+									format_type_be(actual_type))));
+				/* collect the subtype for common-supertype choice */
+				anycompatible_actual_types[n_anycompatible_args++] = anycompatible_range_typelem;
+			}
+		}
 	}
 
 	/*
@@ -2113,8 +2274,6 @@ 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))
 		{
-			Oid			range_typelem;
-
 			range_typelem = get_range_subtype(range_typeid);
 			if (!OidIsValid(range_typelem))
 				ereport(ERROR,
@@ -2143,6 +2302,61 @@ enforce_generic_type_consistency(const Oid *actual_arg_types,
 								   format_type_be(elem_typeid))));
 			}
 		}
+		else
+			range_typelem = InvalidOid;
+
+		/* Get the element type based on the multirange type, if we have one */
+		if (OidIsValid(multirange_typeid))
+		{
+			multirange_typelem = get_multirange_range(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))));
+			}
+
+			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 */
+				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))));
+			}
+		}
+		else
+			multirange_typelem = InvalidOid;
 
 		if (!OidIsValid(elem_typeid))
 		{
@@ -2151,6 +2365,7 @@ enforce_generic_type_consistency(const Oid *actual_arg_types,
 				elem_typeid = ANYELEMENTOID;
 				array_typeid = ANYARRAYOID;
 				range_typeid = ANYRANGEOID;
+				multirange_typeid = ANYMULTIRANGEOID;
 			}
 			else
 			{
@@ -2250,6 +2465,7 @@ enforce_generic_type_consistency(const Oid *actual_arg_types,
 				anycompatible_typeid = ANYCOMPATIBLEOID;
 				anycompatible_array_typeid = ANYCOMPATIBLEARRAYOID;
 				anycompatible_range_typeid = ANYCOMPATIBLERANGEOID;
+				anycompatible_multirange_typeid = ANYCOMPATIBLEMULTIRANGEOID;
 			}
 			else
 			{
@@ -2281,6 +2497,8 @@ enforce_generic_type_consistency(const Oid *actual_arg_types,
 				declared_arg_types[j] = anycompatible_array_typeid;
 			else if (decl_type == ANYCOMPATIBLERANGEOID)
 				declared_arg_types[j] = anycompatible_range_typeid;
+			else if (decl_type == ANYCOMPATIBLEMULTIRANGEOID)
+				declared_arg_types[j] = anycompatible_multirange_typeid;
 		}
 	}
 
@@ -2331,6 +2549,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;
+			}
 		}
 	}
 
@@ -2367,6 +2596,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 ANYCOMPATIBLE use the appropriate type */
 	if (rettype == ANYCOMPATIBLEOID ||
 		rettype == ANYCOMPATIBLENONARRAYOID)
@@ -2401,6 +2646,17 @@ enforce_generic_type_consistency(const Oid *actual_arg_types,
 		return anycompatible_range_typeid;
 	}
 
+	/* if we return ANYCOMPATIBLEMULTIRANGE use the appropriate argument type */
+	if (rettype == ANYCOMPATIBLEMULTIRANGEOID)
+	{
+		/* this error is unreachable if the function signature is valid: */
+		if (!OidIsValid(anycompatible_multirange_typeid))
+			ereport(ERROR,
+					(errcode(ERRCODE_DATATYPE_MISMATCH),
+					 errmsg_internal("could not identify anycompatiblemultirange type")));
+		return anycompatible_multirange_typeid;
+	}
+
 	/* we don't return a generic type; send back the original return type */
 	return rettype;
 }
@@ -2418,20 +2674,37 @@ check_valid_polymorphic_signature(Oid ret_type,
 								  const Oid *declared_arg_types,
 								  int nargs)
 {
-	if (ret_type == ANYRANGEOID || ret_type == ANYCOMPATIBLERANGEOID)
+	if (ret_type == ANYRANGEOID || ret_type == ANYMULTIRANGEOID)
 	{
 		/*
-		 * ANYRANGE requires an ANYRANGE input, else we can't tell which of
-		 * several range types with the same element type to use.  Likewise
-		 * for ANYCOMPATIBLERANGE.
+		 * ANYRANGE and ANYMULTIRANGE require an ANYRANGE or ANYMULTIRANGE input,
+		 * else we can't tell which of several range types with the same element
+		 * type to use.
 		 */
 		for (int i = 0; i < nargs; i++)
 		{
-			if (declared_arg_types[i] == ret_type)
+			if (declared_arg_types[i] == ANYRANGEOID ||
+				declared_arg_types[i] == ANYMULTIRANGEOID)
 				return NULL;	/* OK */
 		}
-		return psprintf(_("A result of type %s requires at least one input of type %s."),
-						format_type_be(ret_type), format_type_be(ret_type));
+		return psprintf(_("A result of type %s requires at least one input of type anyrange or anymultirange."),
+						format_type_be(ret_type));
+	}
+	else if (ret_type == ANYCOMPATIBLERANGEOID || ret_type == ANYCOMPATIBLEMULTIRANGEOID)
+	{
+		/*
+		 * ANYCOMPATIBLERANGE and ANYCOMPATIBLEMULTIRANGE require an ANYCOMPATIBLERANGE or
+		 * ANYCOMPATIBLEMULTIRANGE input, else we can't tell which of several range types
+		 * with the same element type to use.
+		 */
+		for (int i = 0; i < nargs; i++)
+		{
+			if (declared_arg_types[i] == ANYCOMPATIBLERANGEOID ||
+				declared_arg_types[i] == ANYCOMPATIBLEMULTIRANGEOID)
+				return NULL;	/* OK */
+		}
+		return psprintf(_("A result of type %s requires at least one input of type anycompatiblerange or anycompatiblemultirange."),
+						format_type_be(ret_type));
 	}
 	else if (IsPolymorphicTypeFamily1(ret_type))
 	{
@@ -2442,7 +2715,7 @@ check_valid_polymorphic_signature(Oid ret_type,
 				return NULL;	/* OK */
 		}
 		/* Keep this list in sync with IsPolymorphicTypeFamily1! */
-		return psprintf(_("A result of type %s requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange."),
+		return psprintf(_("A result of type %s requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange."),
 						format_type_be(ret_type));
 	}
 	else if (IsPolymorphicTypeFamily2(ret_type))
@@ -2594,6 +2867,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 || targettype == ANYCOMPATIBLEMULTIRANGEOID)
+		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 b4d55e849b..22a412d437 100644
--- a/src/backend/utils/adt/Makefile
+++ b/src/backend/utils/adt/Makefile
@@ -59,6 +59,8 @@ OBJS = \
 	mac8.o \
 	mcxtfuncs.o \
 	misc.o \
+	multirangetypes.o \
+	multirangetypes_selfuncs.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..1240400b0e
--- /dev/null
+++ b/src/backend/utils/adt/multirangetypes.c
@@ -0,0 +1,2536 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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/int8.h"
+#include "utils/lsyscache.h"
+#include "utils/rangetypes.h"
+#include "utils/multirangetypes.h"
+#include "utils/timestamp.h"
+#include "utils/array.h"
+#include "utils/memutils.h"
+
+/* fn_extra cache entry for one of the range I/O functions */
+typedef struct MultirangeIOData
+{
+	TypeCacheEntry *typcache;	/* multirange type's typcache entry */
+	FmgrInfo	typioproc;		/* range type's I/O proc */
+	Oid			typioparam;		/* range type's I/O parameter */
+} 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 mltrngtypid,
+												IOFuncSelector func);
+static int32 multirange_canonicalize(TypeCacheEntry *rangetyp, int32 input_range_count,
+									 RangeType **ranges);
+static void shortrange_deserialize(TypeCacheEntry *typcache,
+								   const ShortRangeType *range,
+								   RangeBound *lower, RangeBound *upper, bool *empty);
+static void shortrange_serialize(ShortRangeType *range, TypeCacheEntry *typcache,
+								 RangeBound *lower, RangeBound *upper, bool empty);
+
+/* "Methods" required for an expanded object */
+static Size EMR_get_flat_size(ExpandedObjectHeader *eohptr);
+static void EMR_flatten_into(ExpandedObjectHeader *eohptr,
+							 void *result, Size allocated_size);
+
+static const ExpandedObjectMethods EMR_methods =
+{
+	EMR_get_flat_size,
+	EMR_flatten_into
+};
+
+/*
+ *----------------------------------------------------------
+ * 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 with 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;
+	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 = NULL;
+	int32		range_str_len;
+	char	   *range_str_copy;
+
+	cache = get_multirange_io_data(fcinfo, mltrngtypoid, IOFunc_input);
+	rangetyp = cache->typcache->rngtype;
+
+	/* 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 + 1;
+					range_str_copy = pnstrdup(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->typioproc,
+																 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;
+	int32		range_count;
+	int32		i;
+	RangeType **ranges;
+
+	cache = get_multirange_io_data(fcinfo, mltrngtypoid, IOFunc_output);
+
+	initStringInfo(&buf);
+
+	appendStringInfoChar(&buf, '{');
+
+	multirange_deserialize(cache->typcache->rngtype, multirange, &range_count, &ranges);
+	for (i = 0; i < range_count; i++)
+	{
+		if (i > 0)
+			appendStringInfoChar(&buf, ',');
+		range = ranges[i];
+		rangeStr = OutputFunctionCall(&cache->typioproc, RangeTypePGetDatum(range));
+		appendStringInfoString(&buf, rangeStr);
+	}
+
+	appendStringInfoChar(&buf, '}');
+
+	PG_RETURN_CSTRING(buf.data);
+}
+
+/*
+ * Binary representation: First a int32-sized count of ranges, followed by
+ * ranges in their native binary representation.
+ */
+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;
+	uint32		range_count;
+	RangeType **ranges;
+	MultirangeType *ret;
+	StringInfoData tmpbuf;
+
+	cache = get_multirange_io_data(fcinfo, mltrngtypoid, IOFunc_receive);
+
+	range_count = pq_getmsgint(buf, 4);
+	ranges = palloc(range_count * sizeof(RangeType *));
+
+	initStringInfo(&tmpbuf);
+	for (int i = 0; i < range_count; i++)
+	{
+		uint32		range_len = pq_getmsgint(buf, 4);
+		const char *range_data = pq_getmsgbytes(buf, range_len);
+
+		resetStringInfo(&tmpbuf);
+		appendBinaryStringInfo(&tmpbuf, range_data, range_len);
+
+		ranges[i] = DatumGetRangeTypeP(ReceiveFunctionCall(&cache->typioproc,
+														   &tmpbuf,
+														   cache->typioparam,
+														   typmod));
+	}
+	pfree(tmpbuf.data);
+
+	pq_getmsgend(buf);
+
+	ret = make_multirange(mltrngtypoid, cache->typcache->rngtype,
+						  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;
+	MultirangeIOData *cache;
+
+	cache = get_multirange_io_data(fcinfo, mltrngtypoid, IOFunc_send);
+
+	/* construct output */
+	pq_begintypsend(buf);
+
+	pq_sendint32(buf, multirange->rangeCount);
+
+	multirange_deserialize(cache->typcache->rngtype, multirange, &range_count, &ranges);
+	for (int i = 0; i < range_count; i++)
+	{
+		Datum		range;
+
+		range = RangeTypePGetDatum(ranges[i]);
+		range = PointerGetDatum(SendFunctionCall(&cache->typioproc, range));
+
+		pq_sendint32(buf, VARSIZE(range) - VARHDRSZ);
+		pq_sendbytes(buf, VARDATA(range), VARSIZE(range) - VARHDRSZ);
+	}
+
+	PG_RETURN_BYTEA_P(pq_endtypsend(buf));
+}
+
+/*
+ * 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)
+	{
+		Oid			typiofunc;
+		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,
+						 &typiofunc);
+
+		if (!OidIsValid(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(typiofunc, &cache->typioproc,
+					  fcinfo->flinfo->fn_mcxt);
+
+		fcinfo->flinfo->fn_extra = (void *) cache;
+	}
+
+	return cache;
+}
+
+/*
+ * Converts a list of arbitrary ranges 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;
+}
+
+/*
+ *----------------------------------------------------------
+ * 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;
+	int			i;
+	int32		bytelen;
+	Pointer		ptr;
+	RangeBound	lower;
+	RangeBound	upper;
+	bool		empty;
+
+	/* 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 ShortRangeTypes start aligned.
+	 */
+	bytelen = MAXALIGN(sizeof(MultirangeType));
+
+	/* Count space for all ranges */
+	for (i = 0; i < range_count; i++)
+		bytelen += MAXALIGN(VARSIZE(ranges[i]) - sizeof(char));
+
+	/* 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_deserialize(rangetyp, ranges[i], &lower, &upper, &empty);
+		shortrange_serialize((ShortRangeType *) ptr, rangetyp, &lower, &upper, empty);
+		ptr += MAXALIGN(VARSIZE(ptr));
+	}
+
+	return multirange;
+}
+
+/*
+ * 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(TypeCacheEntry *rangetyp,
+					   const MultirangeType *multirange, int32 *range_count,
+					   RangeType ***ranges)
+{
+	ExpandedMultirangeHeader *emrh = (ExpandedMultirangeHeader *)
+	DatumGetEOHP(expand_multirange(MultirangeTypePGetDatum(multirange),
+								   CurrentMemoryContext, rangetyp));
+
+	*range_count = emrh->rangeCount;
+	if (*range_count == 0)
+	{
+		*ranges = NULL;
+		return;
+	}
+
+	*ranges = emrh->ranges;
+}
+
+MultirangeType *
+make_empty_multirange(Oid mltrngtypoid, TypeCacheEntry *rangetyp)
+{
+	return make_multirange(mltrngtypoid, rangetyp, 0, NULL);
+}
+
+/*
+ *----------------------------------------------------------
+ * EXPANDED TOAST FUNCTIONS
+ *----------------------------------------------------------
+ */
+
+/*
+ * expand_multirange: convert a multirange Datum into an expanded multirange
+ *
+ * The expanded object will be a child of parentcontext.
+ */
+Datum
+expand_multirange(Datum multirangedatum, MemoryContext parentcontext, TypeCacheEntry *rangetyp)
+{
+	MultirangeType *multirange;
+	ExpandedMultirangeHeader *emrh;
+	MemoryContext objcxt;
+	MemoryContext oldcxt;
+
+	/*
+	 * Allocate private context for expanded object.  We start by assuming
+	 * that the multirange won't be very large; but if it does grow a lot,
+	 * don't constrain aset.c's large-context behavior.
+	 */
+	objcxt = AllocSetContextCreate(parentcontext,
+								   "expanded multirange",
+								   ALLOCSET_START_SMALL_SIZES);
+
+	/* Set up expanded multirange header */
+	emrh = (ExpandedMultirangeHeader *)
+		MemoryContextAlloc(objcxt, sizeof(ExpandedMultirangeHeader));
+
+	EOH_init_header(&emrh->hdr, &EMR_methods, objcxt);
+	emrh->emr_magic = EMR_MAGIC;
+
+	/*
+	 * Detoast and copy source multirange into private context. We need to do
+	 * this so that we get our own copies of the upper/lower bounds.
+	 *
+	 * Note that this coding risks leaking some memory in the private context
+	 * if we have to fetch data from a TOAST table; however, experimentation
+	 * says that the leak is minimal.  Doing it this way saves a copy step,
+	 * which seems worthwhile, especially if the multirange is large enough to
+	 * need external storage.
+	 */
+	oldcxt = MemoryContextSwitchTo(objcxt);
+	multirange = DatumGetMultirangeTypePCopy(multirangedatum);
+
+	emrh->multirangetypid = multirange->multirangetypid;
+	emrh->rangeCount = multirange->rangeCount;
+
+	/* Convert each ShortRangeType into a RangeType */
+	if (emrh->rangeCount > 0)
+	{
+		Pointer		ptr;
+		Pointer		end;
+		int32		i;
+
+		emrh->ranges = palloc(emrh->rangeCount * sizeof(RangeType *));
+
+		ptr = (char *) multirange;
+		end = ptr + VARSIZE(multirange);
+		ptr = (char *) MAXALIGN(multirange + 1);
+		i = 0;
+		while (ptr < end)
+		{
+			ShortRangeType *shortrange;
+			RangeBound	lower;
+			RangeBound	upper;
+			bool		empty;
+
+			shortrange = (ShortRangeType *) ptr;
+			shortrange_deserialize(rangetyp, shortrange, &lower, &upper, &empty);
+			emrh->ranges[i++] = make_range(rangetyp, &lower, &upper, empty);
+
+			ptr += MAXALIGN(VARSIZE(shortrange));
+		}
+	}
+	else
+	{
+		emrh->ranges = NULL;
+	}
+	MemoryContextSwitchTo(oldcxt);
+
+	/* return a R/W pointer to the expanded multirange */
+	return EOHPGetRWDatum(&emrh->hdr);
+}
+
+/*
+ * shortrange_deserialize: Extract bounds and empty flag from a ShortRangeType
+ */
+void
+shortrange_deserialize(TypeCacheEntry *typcache, const ShortRangeType *range,
+					   RangeBound *lower, RangeBound *upper, bool *empty)
+{
+	char		flags;
+	int16		typlen;
+	bool		typbyval;
+	char		typalign;
+	Pointer		ptr;
+	Datum		lbound;
+	Datum		ubound;
+
+	/* fetch the flag byte */
+	flags = range->flags;
+
+	/* fetch information about range's element type */
+	typlen = typcache->rngelemtype->typlen;
+	typbyval = typcache->rngelemtype->typbyval;
+	typalign = typcache->rngelemtype->typalign;
+
+	/* initialize data pointer just after the range struct fields */
+	ptr = (Pointer) MAXALIGN(range + 1);
+
+	/* fetch lower bound, if any */
+	if (RANGE_HAS_LBOUND(flags))
+	{
+		/* att_align_pointer cannot be necessary here */
+		lbound = fetch_att(ptr, typbyval, typlen);
+		ptr = (Pointer) att_addlength_pointer(ptr, typlen, ptr);
+	}
+	else
+		lbound = (Datum) 0;
+
+	/* fetch upper bound, if any */
+	if (RANGE_HAS_UBOUND(flags))
+	{
+		ptr = (Pointer) att_align_pointer(ptr, typalign, typlen, ptr);
+		ubound = fetch_att(ptr, typbyval, typlen);
+		/* no need for att_addlength_pointer */
+	}
+	else
+		ubound = (Datum) 0;
+
+	/* emit results */
+
+	*empty = (flags & RANGE_EMPTY) != 0;
+
+	lower->val = lbound;
+	lower->infinite = (flags & RANGE_LB_INF) != 0;
+	lower->inclusive = (flags & RANGE_LB_INC) != 0;
+	lower->lower = true;
+
+	upper->val = ubound;
+	upper->infinite = (flags & RANGE_UB_INF) != 0;
+	upper->inclusive = (flags & RANGE_UB_INC) != 0;
+	upper->lower = false;
+}
+
+/*
+ * get_flat_size method for expanded multirange
+ */
+static Size
+EMR_get_flat_size(ExpandedObjectHeader *eohptr)
+{
+	ExpandedMultirangeHeader *emrh = (ExpandedMultirangeHeader *) eohptr;
+	RangeType  *range;
+	Size		nbytes;
+	int			i;
+
+	Assert(emrh->emr_magic == EMR_MAGIC);
+
+	/* If we have a cached size value, believe that */
+	if (emrh->flat_size)
+		return emrh->flat_size;
+
+	/*
+	 * Compute space needed by examining ranges.
+	 */
+	nbytes = MAXALIGN(sizeof(MultirangeType));
+	for (i = 0; i < emrh->rangeCount; i++)
+	{
+		range = emrh->ranges[i];
+		nbytes += MAXALIGN(VARSIZE(range) - sizeof(char));
+		/* check for overflow of total request */
+		if (!AllocSizeIsValid(nbytes))
+			ereport(ERROR,
+					(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
+					 errmsg("multirange size exceeds the maximum allowed (%d)",
+							(int) MaxAllocSize)));
+	}
+
+	/* cache for next time */
+	emrh->flat_size = nbytes;
+
+	return nbytes;
+}
+
+/*
+ * flatten_into method for expanded multiranges
+ */
+static void
+EMR_flatten_into(ExpandedObjectHeader *eohptr,
+				 void *result, Size allocated_size)
+{
+	ExpandedMultirangeHeader *emrh = (ExpandedMultirangeHeader *) eohptr;
+	MultirangeType *mrresult = (MultirangeType *) result;
+	TypeCacheEntry *typcache;
+	int			rangeCount;
+	RangeType **ranges;
+	RangeBound	lower;
+	RangeBound	upper;
+	bool		empty;
+	Pointer		ptr;
+	int			i;
+
+	Assert(emrh->emr_magic == EMR_MAGIC);
+
+	typcache = lookup_type_cache(emrh->multirangetypid, TYPECACHE_MULTIRANGE_INFO);
+	if (typcache->rngtype == NULL)
+		elog(ERROR, "type %u is not a multirange type", emrh->multirangetypid);
+
+	/* Fill result multirange from RangeTypes */
+	rangeCount = emrh->rangeCount;
+	ranges = emrh->ranges;
+
+	/* We must ensure that any pad space is zero-filled */
+	memset(mrresult, 0, allocated_size);
+
+	SET_VARSIZE(mrresult, allocated_size);
+
+	/* Now fill in the datum with ShortRangeTypes */
+	mrresult->multirangetypid = emrh->multirangetypid;
+	mrresult->rangeCount = rangeCount;
+
+	ptr = (char *) MAXALIGN(mrresult + 1);
+	for (i = 0; i < rangeCount; i++)
+	{
+		range_deserialize(typcache->rngtype, ranges[i], &lower, &upper, &empty);
+		shortrange_serialize((ShortRangeType *) ptr, typcache->rngtype, &lower, &upper, empty);
+		ptr += MAXALIGN(VARSIZE(ptr));
+	}
+}
+
+/*
+ * shortrange_serialize: fill in pre-allocationed range param based on the
+ * given bounds and flags.
+ */
+static void
+shortrange_serialize(ShortRangeType *range, TypeCacheEntry *typcache,
+					 RangeBound *lower, RangeBound *upper, bool empty)
+{
+	int			cmp;
+	Size		msize;
+	Pointer		ptr;
+	int16		typlen;
+	bool		typbyval;
+	char		typalign;
+	char		typstorage;
+	char		flags = 0;
+
+	/*
+	 * Verify range is not invalid on its face, and construct flags value,
+	 * preventing any non-canonical combinations such as infinite+inclusive.
+	 */
+	Assert(lower->lower);
+	Assert(!upper->lower);
+
+	if (empty)
+		flags |= RANGE_EMPTY;
+	else
+	{
+		cmp = range_cmp_bound_values(typcache, lower, upper);
+
+		/* error check: if lower bound value is above upper, it's wrong */
+		if (cmp > 0)
+			ereport(ERROR,
+					(errcode(ERRCODE_DATA_EXCEPTION),
+					 errmsg("range lower bound must be less than or equal to range upper bound")));
+
+		/* if bounds are equal, and not both inclusive, range is empty */
+		if (cmp == 0 && !(lower->inclusive && upper->inclusive))
+			flags |= RANGE_EMPTY;
+		else
+		{
+			/* infinite boundaries are never inclusive */
+			if (lower->infinite)
+				flags |= RANGE_LB_INF;
+			else if (lower->inclusive)
+				flags |= RANGE_LB_INC;
+			if (upper->infinite)
+				flags |= RANGE_UB_INF;
+			else if (upper->inclusive)
+				flags |= RANGE_UB_INC;
+		}
+	}
+
+	/* Fetch information about range's element type */
+	typlen = typcache->rngelemtype->typlen;
+	typbyval = typcache->rngelemtype->typbyval;
+	typalign = typcache->rngelemtype->typalign;
+	typstorage = typcache->rngelemtype->typstorage;
+
+	/* Count space for varlena header */
+	msize = sizeof(ShortRangeType);
+	Assert(msize == MAXALIGN(msize));
+
+	/* Count space for bounds */
+	if (RANGE_HAS_LBOUND(flags))
+	{
+		/*
+		 * Make sure item to be inserted is not toasted.  It is essential that
+		 * we not insert an out-of-line toast value pointer into a range
+		 * object, for the same reasons that arrays and records can't contain
+		 * them.  It would work to store a compressed-in-line value, but we
+		 * prefer to decompress and then let compression be applied to the
+		 * whole range object if necessary.  But, unlike arrays, we do allow
+		 * short-header varlena objects to stay as-is.
+		 */
+		if (typlen == -1)
+			lower->val = PointerGetDatum(PG_DETOAST_DATUM_PACKED(lower->val));
+
+		msize = datum_compute_size(msize, lower->val, typbyval, typalign,
+								   typlen, typstorage);
+	}
+
+	if (RANGE_HAS_UBOUND(flags))
+	{
+		/* Make sure item to be inserted is not toasted */
+		if (typlen == -1)
+			upper->val = PointerGetDatum(PG_DETOAST_DATUM_PACKED(upper->val));
+
+		msize = datum_compute_size(msize, upper->val, typbyval, typalign,
+								   typlen, typstorage);
+	}
+
+	SET_VARSIZE(range, msize);
+
+	ptr = (char *) (range + 1);
+
+	if (RANGE_HAS_LBOUND(flags))
+	{
+		Assert(lower->lower);
+		ptr = datum_write(ptr, lower->val, typbyval, typalign, typlen,
+						  typstorage);
+	}
+
+	if (RANGE_HAS_UBOUND(flags))
+	{
+		Assert(!upper->lower);
+		ptr = datum_write(ptr, upper->val, typbyval, typalign, typlen,
+						  typstorage);
+	}
+
+	range->flags = flags;
+}
+
+
+/*
+ *----------------------------------------------------------
+ * 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_constructor2(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,
+				(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+				 errmsg("multirange values cannot contain NULL members")));
+
+	rangeArray = PG_GETARG_ARRAYTYPE_P(0);
+
+	dims = ARR_NDIM(rangeArray);
+	if (dims > 1)
+		ereport(ERROR,
+				(errcode(ERRCODE_CARDINALITY_VIOLATION),
+				 errmsg("multiranges cannot be constructed from multi-dimensional arrays")));
+
+	rngtypid = ARR_ELEMTYPE(rangeArray);
+	if (rngtypid != rangetyp->type_id)
+		ereport(ERROR,
+				(errcode(ERRCODE_DATATYPE_MISMATCH),
+				 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,
+						(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+						 errmsg("multirange values cannot contain NULL members")));
+
+			/* make_multirange will do its own copy */
+			ranges[i] = DatumGetRangeTypeP(elements[i]);
+		}
+	}
+
+	PG_RETURN_MULTIRANGE_P(make_multirange(mltrngtypid, rangetyp, range_count, ranges));
+}
+
+/*
+ * Construct multirange value from a single range.
+ * It'd be nice if we could just use multirange_constructor2
+ * for this case, but we need a non-variadic single-arg function
+ * to let us define a CAST from a range to its multirange.
+ */
+Datum
+multirange_constructor1(PG_FUNCTION_ARGS)
+{
+	Oid			mltrngtypid = get_fn_expr_rettype(fcinfo->flinfo);
+	Oid			rngtypid;
+	TypeCacheEntry *typcache;
+	TypeCacheEntry *rangetyp;
+	RangeType  *range;
+
+	typcache = multirange_get_typcache(fcinfo, mltrngtypid);
+	rangetyp = typcache->rngtype;
+
+	/*
+	 * These checks should be guaranteed by our signature, but let's do them
+	 * just in case.
+	 */
+
+	if (PG_ARGISNULL(0))
+		ereport(ERROR,
+				(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+				 errmsg("multirange values cannot contain NULL members")));
+
+	range = PG_GETARG_RANGE_P(0);
+
+	/* Make sure the range type matches. */
+	rngtypid = RangeTypeGetOid(range);
+	if (rngtypid != rangetyp->type_id)
+		ereport(ERROR,
+				(errcode(ERRCODE_DATATYPE_MISMATCH),
+				 errmsg("type %u does not match constructor type", rngtypid)));
+
+	PG_RETURN_MULTIRANGE_P(make_multirange(mltrngtypid, rangetyp, 1, &range));
+}
+
+/*
+ * 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
+		elog(ERROR,				/* can't happen */
+			 "niladic multirange constructor must not receive arguments");
+}
+
+
+/* multirange, multirange -> multirange type functions */
+
+/* multirange union */
+Datum
+multirange_union(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
+	MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count1;
+	int32		range_count2;
+	int32		range_count3;
+	RangeType **ranges1;
+	RangeType **ranges2;
+	RangeType **ranges3;
+
+	if (MultirangeIsEmpty(mr1))
+		PG_RETURN_MULTIRANGE_P(mr2);
+	if (MultirangeIsEmpty(mr2))
+		PG_RETURN_MULTIRANGE_P(mr1);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr1));
+
+	multirange_deserialize(typcache->rngtype, mr1, &range_count1, &ranges1);
+	multirange_deserialize(typcache->rngtype, mr2, &range_count2, &ranges2);
+
+	range_count3 = range_count1 + range_count2;
+	ranges3 = palloc0(range_count3 * sizeof(RangeType *));
+	memcpy(ranges3, ranges1, range_count1 * sizeof(RangeType *));
+	memcpy(ranges3 + range_count1, ranges2, range_count2 * sizeof(RangeType *));
+	PG_RETURN_MULTIRANGE_P(make_multirange(typcache->type_id, typcache->rngtype,
+										   range_count3, ranges3));
+}
+
+/* multirange minus */
+Datum
+multirange_minus(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
+	MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
+	Oid			mltrngtypoid = MultirangeTypeGetOid(mr1);
+	TypeCacheEntry *typcache;
+	TypeCacheEntry *rangetyp;
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+
+	typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
+	rangetyp = typcache->rngtype;
+
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		PG_RETURN_MULTIRANGE_P(mr1);
+
+	multirange_deserialize(typcache->rngtype, mr1, &range_count1, &ranges1);
+	multirange_deserialize(typcache->rngtype, mr2, &range_count2, &ranges2);
+
+	PG_RETURN_MULTIRANGE_P(multirange_minus_internal(mltrngtypoid,
+													 rangetyp,
+													 range_count1,
+													 ranges1,
+													 range_count2,
+													 ranges2));
+}
+
+MultirangeType *
+multirange_minus_internal(Oid mltrngtypoid, TypeCacheEntry *rangetyp,
+						  int32 range_count1, RangeType **ranges1,
+						  int32 range_count2, RangeType **ranges2)
+{
+	RangeType  *r1;
+	RangeType  *r2;
+	RangeType **ranges3;
+	int32		range_count3;
+	int32		i1;
+	int32		i2;
+
+	/*
+	 * Worst case: every range in ranges1 makes a different cut to some range
+	 * in ranges2.
+	 */
+	ranges3 = palloc0((range_count1 + range_count2) * sizeof(RangeType *));
+	range_count3 = 0;
+
+	/*
+	 * For each range in mr1, keep subtracting until it's gone or the ranges
+	 * in mr2 have passed it. After a subtraction we assign what's left back
+	 * to r1. The parallel progress through mr1 and mr2 is similar to
+	 * multirange_overlaps_multirange_internal.
+	 */
+	r2 = ranges2[0];
+	for (i1 = 0, i2 = 0; i1 < range_count1; i1++)
+	{
+		r1 = ranges1[i1];
+
+		/* Discard r2s while r2 << r1 */
+		while (r2 != NULL && range_before_internal(rangetyp, r2, r1))
+		{
+			r2 = ++i2 >= range_count2 ? NULL : ranges2[i2];
+		}
+
+		while (r2 != NULL)
+		{
+			if (range_split_internal(rangetyp, r1, r2, &ranges3[range_count3], &r1))
+			{
+				/*
+				 * If r2 takes a bite out of the middle of r1, we need two
+				 * outputs
+				 */
+				range_count3++;
+				r2 = ++i2 >= range_count2 ? NULL : ranges2[i2];
+
+			}
+			else if (range_overlaps_internal(rangetyp, r1, r2))
+			{
+				/*
+				 * If r2 overlaps r1, replace r1 with r1 - r2.
+				 */
+				r1 = range_minus_internal(rangetyp, r1, r2);
+
+				/*
+				 * If r2 goes past r1, then we need to stay with it, in case
+				 * it hits future r1s. Otherwise we need to keep r1, in case
+				 * future r2s hit it. Since we already subtracted, there's no
+				 * point in using the overright/overleft calls.
+				 */
+				if (RangeIsEmpty(r1) || range_before_internal(rangetyp, r1, r2))
+					break;
+				else
+					r2 = ++i2 >= range_count2 ? NULL : ranges2[i2];
+
+			}
+			else
+			{
+				/*
+				 * This and all future r2s are past r1, so keep them. Also
+				 * assign whatever is left of r1 to the result.
+				 */
+				break;
+			}
+		}
+
+		/*
+		 * Nothing else can remove anything from r1, so keep it. Even if r1 is
+		 * empty here, make_multirange will remove it.
+		 */
+		ranges3[range_count3++] = r1;
+	}
+
+	return make_multirange(mltrngtypoid, rangetyp, range_count3, ranges3);
+}
+
+/* multirange intersection */
+Datum
+multirange_intersect(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
+	MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
+	Oid			mltrngtypoid = MultirangeTypeGetOid(mr1);
+	TypeCacheEntry *typcache;
+	TypeCacheEntry *rangetyp;
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+
+	typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
+	rangetyp = typcache->rngtype;
+
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		PG_RETURN_MULTIRANGE_P(make_empty_multirange(mltrngtypoid, rangetyp));
+
+	multirange_deserialize(rangetyp, mr1, &range_count1, &ranges1);
+	multirange_deserialize(rangetyp, mr2, &range_count2, &ranges2);
+
+	PG_RETURN_MULTIRANGE_P(multirange_intersect_internal(mltrngtypoid,
+														 rangetyp,
+														 range_count1,
+														 ranges1,
+														 range_count2,
+														 ranges2));
+}
+
+MultirangeType *
+multirange_intersect_internal(Oid mltrngtypoid, TypeCacheEntry *rangetyp,
+							  int32 range_count1, RangeType **ranges1,
+							  int32 range_count2, RangeType **ranges2)
+{
+	RangeType  *r1;
+	RangeType  *r2;
+	RangeType **ranges3;
+	int32		range_count3;
+	int32		i1;
+	int32		i2;
+
+	if (range_count1 == 0 || range_count2 == 0)
+		return make_multirange(mltrngtypoid, rangetyp, 0, NULL);
+
+	/*-----------------------------------------------
+	 * Worst case is a stitching pattern like this:
+	 *
+	 * mr1: --- --- --- ---
+	 * mr2:   --- --- ---
+	 * mr3:   - - - - - -
+	 *
+	 * That seems to be range_count1 + range_count2 - 1,
+	 * but one extra won't hurt.
+	 *-----------------------------------------------
+	 */
+	ranges3 = palloc0((range_count1 + range_count2) * sizeof(RangeType *));
+	range_count3 = 0;
+
+	/*
+	 * For each range in mr1, keep intersecting until the ranges in mr2 have
+	 * passed it. The parallel progress through mr1 and mr2 is similar to
+	 * multirange_minus_multirange_internal, but we don't have to assign back
+	 * to r1.
+	 */
+	r2 = ranges2[0];
+	for (i1 = 0, i2 = 0; i1 < range_count1; i1++)
+	{
+		r1 = ranges1[i1];
+
+		/* Discard r2s while r2 << r1 */
+		while (r2 != NULL && range_before_internal(rangetyp, r2, r1))
+		{
+			r2 = ++i2 >= range_count2 ? NULL : ranges2[i2];
+		}
+
+		while (r2 != NULL)
+		{
+			if (range_overlaps_internal(rangetyp, r1, r2))
+			{
+				/* Keep the overlapping part */
+				ranges3[range_count3++] = range_intersect_internal(rangetyp, r1, r2);
+
+				/* If we "used up" all of r2, go to the next one... */
+				if (range_overleft_internal(rangetyp, r2, r1))
+					r2 = ++i2 >= range_count2 ? NULL : ranges2[i2];
+
+				/* ...otherwise go to the next r1 */
+				else
+					break;
+			}
+			else
+				/* We're past r1, so move to the next one */
+				break;
+		}
+
+		/* If we're out of r2s, there can be no more intersections */
+		if (r2 == NULL)
+			break;
+	}
+
+	return make_multirange(mltrngtypoid, rangetyp, range_count3, ranges3);
+}
+
+/*
+ * range_agg_transfn: combine adjacent/overlapping ranges.
+ *
+ * All we do here is gather the input ranges into an array
+ * so that the finalfn can sort and combine them.
+ */
+Datum
+range_agg_transfn(PG_FUNCTION_ARGS)
+{
+	MemoryContext aggContext;
+	Oid			rngtypoid;
+	ArrayBuildState *state;
+
+	if (!AggCheckCallContext(fcinfo, &aggContext))
+		elog(ERROR, "range_agg_transfn called in non-aggregate context");
+
+	rngtypoid = get_fn_expr_argtype(fcinfo->flinfo, 1);
+	if (!type_is_range(rngtypoid))
+		ereport(ERROR,
+				(errcode(ERRCODE_DATATYPE_MISMATCH),
+				 errmsg("range_agg must be called with a range")));
+
+	if (PG_ARGISNULL(0))
+		state = initArrayResult(rngtypoid, aggContext, false);
+	else
+		state = (ArrayBuildState *) PG_GETARG_POINTER(0);
+
+	/* skip NULLs */
+	if (!PG_ARGISNULL(1))
+		accumArrayResult(state, PG_GETARG_DATUM(1), false, rngtypoid, aggContext);
+
+	PG_RETURN_POINTER(state);
+}
+
+/*
+ * range_agg_finalfn: use our internal array to merge touching ranges.
+ */
+Datum
+range_agg_finalfn(PG_FUNCTION_ARGS)
+{
+	MemoryContext aggContext;
+	Oid			mltrngtypoid;
+	TypeCacheEntry *typcache;
+	ArrayBuildState *state;
+	int32		range_count;
+	RangeType **ranges;
+	int			i;
+
+	if (!AggCheckCallContext(fcinfo, &aggContext))
+		elog(ERROR, "range_agg_finalfn called in non-aggregate context");
+
+	state = PG_ARGISNULL(0) ? NULL : (ArrayBuildState *) PG_GETARG_POINTER(0);
+	if (state == NULL)
+		/* This shouldn't be possible, but just in case.... */
+		PG_RETURN_NULL();
+
+	/* Also return NULL if we had zero inputs, like other aggregates */
+	range_count = state->nelems;
+	if (range_count == 0)
+		PG_RETURN_NULL();
+
+	mltrngtypoid = get_fn_expr_rettype(fcinfo->flinfo);
+	typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
+
+	ranges = palloc0(range_count * sizeof(RangeType *));
+	for (i = 0; i < range_count; i++)
+		ranges[i] = DatumGetRangeTypeP(state->dvalues[i]);
+
+	PG_RETURN_MULTIRANGE_P(make_multirange(mltrngtypoid, typcache->rngtype, range_count, ranges));
+}
+
+Datum
+multirange_intersect_agg_transfn(PG_FUNCTION_ARGS)
+{
+	MemoryContext aggContext;
+	Oid			mltrngtypoid;
+	TypeCacheEntry *typcache;
+	MultirangeType *result;
+	MultirangeType *current;
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+
+	if (!AggCheckCallContext(fcinfo, &aggContext))
+		elog(ERROR, "multirange_intersect_agg_transfn called in non-aggregate context");
+
+	mltrngtypoid = get_fn_expr_argtype(fcinfo->flinfo, 1);
+	if (!type_is_multirange(mltrngtypoid))
+		ereport(ERROR,
+				(errcode(ERRCODE_DATATYPE_MISMATCH),
+				 errmsg("range_intersect_agg must be called with a multirange")));
+
+	typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
+
+	/* strictness ensures these are non-null */
+	result = PG_GETARG_MULTIRANGE_P(0);
+	current = PG_GETARG_MULTIRANGE_P(1);
+
+	multirange_deserialize(typcache->rngtype, result, &range_count1, &ranges1);
+	multirange_deserialize(typcache->rngtype, current, &range_count2, &ranges2);
+
+	result = multirange_intersect_internal(mltrngtypoid,
+										   typcache->rngtype,
+										   range_count1,
+										   ranges1,
+										   range_count2,
+										   ranges2);
+	PG_RETURN_RANGE_P(result);
+}
+
+
+/* multirange -> element type functions */
+
+/* extract lower bound value */
+Datum
+multirange_lower(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+	bool		isnull;
+	Datum		result;
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_NULL();
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(typcache->rngtype, mr, &range_count, &ranges);
+
+	result = range_lower_internal(typcache->rngtype, ranges[0], &isnull);
+
+	if (isnull)
+		PG_RETURN_NULL();
+	else
+		PG_RETURN_DATUM(result);
+}
+
+/* extract upper bound value */
+Datum
+multirange_upper(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+	bool		isnull;
+	Datum		result;
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_NULL();
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(typcache->rngtype, mr, &range_count, &ranges);
+
+	result = range_upper_internal(typcache->rngtype, ranges[range_count - 1], &isnull);
+
+	if (isnull)
+		PG_RETURN_NULL();
+	else
+		PG_RETURN_DATUM(result);
+}
+
+
+/* multirange -> bool functions */
+
+/* is multirange empty? */
+Datum
+multirange_empty(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+
+	PG_RETURN_BOOL(mr->rangeCount == 0);
+}
+
+/* is lower bound inclusive? */
+Datum
+multirange_lower_inc(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+	multirange_deserialize(typcache->rngtype, mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_has_flag(ranges[0], RANGE_LB_INC));
+}
+
+/* is upper bound inclusive? */
+Datum
+multirange_upper_inc(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+	multirange_deserialize(typcache->rngtype, mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_has_flag(ranges[range_count - 1], RANGE_UB_INC));
+}
+
+/* is lower bound infinite? */
+Datum
+multirange_lower_inf(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+	multirange_deserialize(typcache->rngtype, mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_has_flag(ranges[0], RANGE_LB_INF));
+}
+
+/* is upper bound infinite? */
+Datum
+multirange_upper_inf(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+	multirange_deserialize(typcache->rngtype, mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_has_flag(ranges[range_count - 1], RANGE_UB_INF));
+}
+
+
+
+/* multirange, element -> bool functions */
+
+/* contains? */
+Datum
+multirange_contains_elem(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	Datum		val = PG_GETARG_DATUM(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(multirange_contains_elem_internal(typcache, mr, val));
+}
+
+/* contained by? */
+Datum
+elem_contained_by_multirange(PG_FUNCTION_ARGS)
+{
+	Datum		val = PG_GETARG_DATUM(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(multirange_contains_elem_internal(typcache, mr, val));
+}
+
+/*
+ * Test whether multirange mr contains a specific element value.
+ */
+bool
+multirange_contains_elem_internal(TypeCacheEntry *typcache, MultirangeType *mr, Datum val)
+{
+	TypeCacheEntry *rangetyp;
+	int32		range_count;
+	RangeType **ranges;
+	RangeType  *r;
+	int			i;
+
+	rangetyp = typcache->rngtype;
+
+	multirange_deserialize(rangetyp, mr, &range_count, &ranges);
+
+	for (i = 0; i < range_count; i++)
+	{
+		r = ranges[i];
+		if (range_contains_elem_internal(rangetyp, r, val))
+			return true;
+	}
+
+	return false;
+}
+
+/* multirange, range -> bool functions */
+
+/* contains? */
+Datum
+multirange_contains_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(multirange_contains_range_internal(typcache, mr, r));
+}
+
+/* contained by? */
+Datum
+range_contained_by_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(multirange_contains_range_internal(typcache, mr, r));
+}
+
+/*
+ * Test whether multirange mr contains a specific range r.
+ */
+bool
+multirange_contains_range_internal(TypeCacheEntry *typcache, MultirangeType *mr, RangeType *r)
+{
+	TypeCacheEntry *rangetyp;
+	int32		range_count;
+	RangeType **ranges;
+	RangeType  *mrr;
+	int			i;
+
+	rangetyp = typcache->rngtype;
+
+	/*
+	 * Every multirange contains an infinite number of empty ranges, even an
+	 * empty one.
+	 */
+	if (RangeIsEmpty(r))
+		return true;
+
+	multirange_deserialize(rangetyp, mr, &range_count, &ranges);
+
+	for (i = 0; i < range_count; i++)
+	{
+		mrr = ranges[i];
+		if (range_contains_internal(rangetyp, mrr, r))
+			return true;
+	}
+
+	return false;
+}
+
+
+/* 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(typcache->rngtype, mr1, &range_count_1, &ranges1);
+	multirange_deserialize(typcache->rngtype, 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));
+}
+
+/* overlaps? */
+Datum
+range_overlaps_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(range_overlaps_multirange_internal(typcache, r, mr));
+}
+
+Datum
+multirange_overlaps_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(range_overlaps_multirange_internal(typcache, r, mr));
+}
+
+Datum
+multirange_overlaps_multirange(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_overlaps_multirange_internal(typcache, mr1, mr2));
+}
+
+bool
+range_overlaps_multirange_internal(TypeCacheEntry *typcache, RangeType *r, MultirangeType *mr)
+{
+	TypeCacheEntry *rangetyp;
+	int32		range_count;
+	int32		i;
+	RangeType **ranges;
+	RangeType  *mrr;
+
+	/*
+	 * Empties never overlap, even with empties. (This seems strange since
+	 * they *do* contain each other, but we want to follow how ranges work.)
+	 */
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		return false;
+
+	rangetyp = typcache->rngtype;
+
+	multirange_deserialize(rangetyp, mr, &range_count, &ranges);
+
+	/* Scan through mrr and once it gets to r they either overlap or not */
+	mrr = ranges[0];
+
+	/* Discard mrrs while mrr << r */
+	i = 0;
+	while (range_before_internal(rangetyp, mrr, r))
+	{
+		if (++i >= range_count)
+			return false;
+		mrr = ranges[i];
+	}
+
+	/* Now either we overlap or we passed r */
+	return range_overlaps_internal(rangetyp, mrr, r);
+}
+
+bool
+multirange_overlaps_multirange_internal(TypeCacheEntry *typcache, MultirangeType *mr1,
+										MultirangeType *mr2)
+{
+	TypeCacheEntry *rangetyp;
+	int32		range_count1;
+	int32		range_count2;
+	int32		i1;
+	int32		i2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+	RangeType  *r1;
+	RangeType  *r2;
+
+	/*
+	 * Empties never overlap, even with empties. (This seems strange since
+	 * they *do* contain each other, but we want to follow how ranges work.)
+	 */
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		return false;
+
+	rangetyp = typcache->rngtype;
+
+	multirange_deserialize(rangetyp, mr1, &range_count1, &ranges1);
+	multirange_deserialize(rangetyp, mr2, &range_count2, &ranges2);
+
+	/*
+	 * Every range in mr1 gets a chance to overlap with the ranges in mr2, but
+	 * we can use their ordering to avoid O(n^2). This is similar to
+	 * range_overlaps_multirange where r1 : r2 :: mrr : r, but there if we
+	 * don't find an overlap with r we're done, and here if we don't find an
+	 * overlap with r2 we try the next r2.
+	 */
+	r1 = ranges1[0];
+	for (i1 = 0, i2 = 0; i2 < range_count2; i2++)
+	{
+		r2 = ranges2[i2];
+
+		/* Discard r1s while r1 << r2 */
+		while (range_before_internal(rangetyp, r1, r2))
+		{
+			if (++i1 >= range_count1)
+				return false;
+			r1 = ranges1[i1];
+		}
+
+		/*
+		 * If r1 && r2, we're done, otherwise we failed to find an overlap for
+		 * r2, so go to the next one.
+		 */
+		if (range_overlaps_internal(rangetyp, r1, r2))
+			return true;
+	}
+
+	/* We looked through all of mr2 without finding an overlap */
+	return false;
+}
+
+/* does not extend to right of? */
+Datum
+range_overleft_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(typcache->rngtype, mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_overleft_internal(typcache->rngtype, r, ranges[range_count - 1]));
+}
+
+Datum
+multirange_overleft_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(typcache->rngtype, mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_overleft_internal(typcache->rngtype,
+										   ranges[range_count - 1], r));
+}
+
+Datum
+multirange_overleft_multirange(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
+	MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr1));
+
+	multirange_deserialize(typcache->rngtype, mr1, &range_count1, &ranges1);
+	multirange_deserialize(typcache->rngtype, mr2, &range_count2, &ranges2);
+
+	PG_RETURN_BOOL(range_overleft_internal(typcache->rngtype,
+										   ranges1[range_count1 - 1],
+										   ranges2[range_count2 - 1]));
+}
+
+/* does not extend to left of? */
+Datum
+range_overright_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(typcache->rngtype, mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_overright_internal(typcache->rngtype, r, ranges[0]));
+}
+
+Datum
+multirange_overright_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(typcache->rngtype, mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_overright_internal(typcache->rngtype, ranges[0], r));
+}
+
+Datum
+multirange_overright_multirange(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
+	MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr1));
+
+	multirange_deserialize(typcache->rngtype, mr1, &range_count1, &ranges1);
+	multirange_deserialize(typcache->rngtype, mr2, &range_count2, &ranges2);
+
+	PG_RETURN_BOOL(range_overright_internal(typcache->rngtype,
+											ranges1[0],
+											ranges2[0]));
+}
+
+/* contains? */
+Datum
+multirange_contains_multirange(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_contains_multirange_internal(typcache, mr1, mr2));
+}
+
+/* contained by? */
+Datum
+multirange_contained_by_multirange(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_contains_multirange_internal(typcache, mr2, mr1));
+}
+
+/*
+ * Test whether multirange mr1 contains every range from another multirange mr2.
+ */
+bool
+multirange_contains_multirange_internal(TypeCacheEntry *typcache,
+										MultirangeType *mr1, MultirangeType *mr2)
+{
+	TypeCacheEntry *rangetyp;
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+	RangeType  *r1;
+	RangeType  *r2;
+	int			i1,
+				i2;
+
+	rangetyp = typcache->rngtype;
+
+	multirange_deserialize(typcache->rngtype, mr1, &range_count1, &ranges1);
+	multirange_deserialize(typcache->rngtype, mr2, &range_count2, &ranges2);
+
+	/*
+	 * We follow the same logic for empties as ranges: - an empty multirange
+	 * contains an empty range/multirange. - an empty multirange can't contain
+	 * any other range/multirange. - an empty multirange is contained by any
+	 * other range/multirange.
+	 */
+
+	if (range_count2 == 0)
+		return true;
+	if (range_count1 == 0)
+		return false;
+
+	/*
+	 * Every range in mr2 must be contained by some range in mr1. To avoid
+	 * O(n^2) we walk through both ranges in tandem.
+	 */
+	r1 = ranges1[0];
+	for (i1 = 0, i2 = 0; i2 < range_count2; i2++)
+	{
+		r2 = ranges2[i2];
+
+		/* Discard r1s while r1 << r2 */
+		while (range_before_internal(rangetyp, r1, r2))
+		{
+			if (++i1 >= range_count1)
+				return false;
+			r1 = ranges1[i1];
+		}
+
+		/*
+		 * If r1 @> r2, go to the next r2, otherwise return false (since every
+		 * r1[n] and r1[n+1] must have a gap). Note this will give weird
+		 * answers if you don't canonicalize, e.g. with a custom
+		 * int2multirange {[1,1], [2,2]} there is a "gap". But that is
+		 * consistent with other range operators, e.g. '[1,1]'::int2range -|-
+		 * '[2,2]'::int2range is false.
+		 */
+		if (!range_contains_internal(rangetyp, r1, r2))
+			return false;
+	}
+
+	/* All ranges in mr2 are satisfied */
+	return true;
+}
+
+/* strictly left of? */
+Datum
+range_before_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(range_before_multirange_internal(typcache, r, mr));
+}
+
+Datum
+multirange_before_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(range_after_multirange_internal(typcache, r, mr));
+}
+
+Datum
+multirange_before_multirange(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_before_multirange_internal(typcache, mr1, mr2));
+}
+
+/* strictly right of? */
+Datum
+range_after_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(range_after_multirange_internal(typcache, r, mr));
+}
+
+Datum
+multirange_after_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(range_before_multirange_internal(typcache, r, mr));
+}
+
+Datum
+multirange_after_multirange(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_before_multirange_internal(typcache, mr2, mr1));
+}
+
+/* strictly left of? (internal version) */
+bool
+range_before_multirange_internal(TypeCacheEntry *typcache, RangeType *r,
+								 MultirangeType *mr)
+{
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		return false;
+
+	multirange_deserialize(typcache->rngtype, mr, &range_count, &ranges);
+
+	return range_before_internal(typcache->rngtype, r, ranges[0]);
+}
+
+bool
+multirange_before_multirange_internal(TypeCacheEntry *typcache, MultirangeType *mr1,
+									  MultirangeType *mr2)
+{
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		return false;
+
+	multirange_deserialize(typcache->rngtype, mr1, &range_count1, &ranges1);
+	multirange_deserialize(typcache->rngtype, mr2, &range_count2, &ranges2);
+
+	return range_before_internal(typcache->rngtype, ranges1[range_count1 - 1],
+								 ranges2[0]);
+}
+
+/* strictly right of? (internal version) */
+bool
+range_after_multirange_internal(TypeCacheEntry *typcache, RangeType *r,
+								MultirangeType *mr)
+{
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		return false;
+
+	multirange_deserialize(typcache->rngtype, mr, &range_count, &ranges);
+
+	return range_after_internal(typcache->rngtype, r, ranges[range_count - 1]);
+}
+
+/* adjacent to? */
+Datum
+range_adjacent_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		return false;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(typcache->rngtype, mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_adjacent_internal(typcache->rngtype, r, ranges[0]));
+}
+
+Datum
+multirange_adjacent_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		return false;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(typcache->rngtype, mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_adjacent_internal(typcache->rngtype, ranges[range_count - 1], r));
+}
+
+Datum
+multirange_adjacent_multirange(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
+	MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		return false;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr1));
+
+	multirange_deserialize(typcache->rngtype, mr1, &range_count1, &ranges1);
+	multirange_deserialize(typcache->rngtype, mr2, &range_count2, &ranges2);
+
+	PG_RETURN_BOOL(range_adjacent_internal(typcache->rngtype, ranges1[range_count1 - 1], ranges2[0]));
+}
+
+/* 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(typcache->rngtype, mr1, &range_count_1, &ranges1);
+	multirange_deserialize(typcache->rngtype, 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);
+}
+
+/* multirange -> range functions */
+
+/* Find the smallest range that includes everything in the multirange */
+Datum
+range_merge_from_multirange(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	Oid			mltrngtypoid = MultirangeTypeGetOid(mr);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_RANGE_P(make_empty_range(typcache->rngtype));
+
+	multirange_deserialize(typcache->rngtype, mr, &range_count, &ranges);
+
+	PG_RETURN_RANGE_P(range_union_internal(typcache->rngtype, ranges[0],
+										   ranges[range_count - 1], false));
+}
+
+/* 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(typcache->rngtype, 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(typcache->rngtype, 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/multirangetypes_selfuncs.c b/src/backend/utils/adt/multirangetypes_selfuncs.c
new file mode 100644
index 0000000000..ff0a81a49a
--- /dev/null
+++ b/src/backend/utils/adt/multirangetypes_selfuncs.c
@@ -0,0 +1,1297 @@
+/*-------------------------------------------------------------------------
+ *
+ * multirangetypes_selfuncs.c
+ *	  Functions for selectivity estimation of multirange operators
+ *
+ * Estimates are based on histograms of lower and upper bounds, and the
+ * fraction of empty multiranges.
+ *
+ * Portions Copyright (c) 1996-2020, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ *	  src/backend/utils/adt/multirangetypes_selfuncs.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include <math.h>
+
+#include "access/htup_details.h"
+#include "catalog/pg_operator.h"
+#include "catalog/pg_statistic.h"
+#include "catalog/pg_type.h"
+#include "utils/float.h"
+#include "utils/fmgrprotos.h"
+#include "utils/lsyscache.h"
+#include "utils/rangetypes.h"
+#include "utils/multirangetypes.h"
+#include "utils/selfuncs.h"
+#include "utils/typcache.h"
+
+static double calc_multirangesel(TypeCacheEntry *typcache, VariableStatData *vardata,
+							const MultirangeType *constval, Oid operator);
+static double default_multirange_selectivity(Oid operator);
+static double default_multirange_selectivity(Oid operator);
+static double calc_hist_selectivity(TypeCacheEntry *typcache,
+									VariableStatData *vardata, const MultirangeType *constval,
+									Oid operator);
+static double calc_hist_selectivity_scalar(TypeCacheEntry *typcache,
+										   const RangeBound *constbound,
+										   const RangeBound *hist, int hist_nvalues,
+										   bool equal);
+static int	rbound_bsearch(TypeCacheEntry *typcache, const RangeBound *value,
+						   const RangeBound *hist, int hist_length, bool equal);
+static float8 get_position(TypeCacheEntry *typcache, const RangeBound *value,
+						   const RangeBound *hist1, const RangeBound *hist2);
+static float8 get_len_position(double value, double hist1, double hist2);
+static float8 get_distance(TypeCacheEntry *typcache, const RangeBound *bound1,
+						   const RangeBound *bound2);
+static int	length_hist_bsearch(Datum *length_hist_values,
+								int length_hist_nvalues, double value, bool equal);
+static double calc_length_hist_frac(Datum *length_hist_values,
+									int length_hist_nvalues, double length1, double length2, bool equal);
+static double calc_hist_selectivity_contained(TypeCacheEntry *typcache,
+											  const RangeBound *lower, RangeBound *upper,
+											  const RangeBound *hist_lower, int hist_nvalues,
+											  Datum *length_hist_values, int length_hist_nvalues);
+static double calc_hist_selectivity_contains(TypeCacheEntry *typcache,
+											 const RangeBound *lower, const RangeBound *upper,
+											 const RangeBound *hist_lower, int hist_nvalues,
+											 Datum *length_hist_values, int length_hist_nvalues);
+
+/*
+ * Returns a default selectivity estimate for given operator, when we don't
+ * have statistics or cannot use them for some reason.
+ */
+static double
+default_multirange_selectivity(Oid operator)
+{
+	switch (operator)
+	{
+		case OID_MULTIRANGE_OVERLAPS_MULTIRANGE_OP:
+		case OID_MULTIRANGE_OVERLAPS_RANGE_OP:
+		case OID_RANGE_OVERLAPS_MULTIRANGE_OP:
+			return 0.01;
+
+		case OID_MULTIRANGE_CONTAINS_RANGE_OP:
+		case OID_MULTIRANGE_CONTAINS_MULTIRANGE_OP:
+		case OID_MULTIRANGE_RANGE_CONTAINED_OP:
+		case OID_MULTIRANGE_MULTIRANGE_CONTAINED_OP:
+			return 0.005;
+
+		case OID_MULTIRANGE_CONTAINS_ELEM_OP:
+		case OID_MULTIRANGE_ELEM_CONTAINED_OP:
+
+			/*
+			 * "multirange @> elem" is more or less identical to a scalar
+			 * inequality "A >= b AND A <= c".
+			 */
+			return DEFAULT_MULTIRANGE_INEQ_SEL;
+
+		case OID_MULTIRANGE_LESS_OP:
+		case OID_MULTIRANGE_LESS_EQUAL_OP:
+		case OID_MULTIRANGE_GREATER_OP:
+		case OID_MULTIRANGE_GREATER_EQUAL_OP:
+		case OID_MULTIRANGE_LEFT_RANGE_OP:
+		case OID_MULTIRANGE_LEFT_MULTIRANGE_OP:
+		case OID_RANGE_LEFT_MULTIRANGE_OP:
+		case OID_MULTIRANGE_RIGHT_RANGE_OP:
+		case OID_MULTIRANGE_RIGHT_MULTIRANGE_OP:
+		case OID_RANGE_RIGHT_MULTIRANGE_OP:
+		case OID_MULTIRANGE_OVERLAPS_LEFT_RANGE_OP:
+		case OID_RANGE_OVERLAPS_LEFT_MULTIRANGE_OP:
+		case OID_MULTIRANGE_OVERLAPS_LEFT_MULTIRANGE_OP:
+		case OID_MULTIRANGE_OVERLAPS_RIGHT_RANGE_OP:
+		case OID_RANGE_OVERLAPS_RIGHT_MULTIRANGE_OP:
+		case OID_MULTIRANGE_OVERLAPS_RIGHT_MULTIRANGE_OP:
+			/* these are similar to regular scalar inequalities */
+			return DEFAULT_INEQ_SEL;
+
+		default:
+			/* all multirange operators should be handled above, but just in case */
+			return 0.01;
+	}
+}
+
+/*
+ * multirangesel -- restriction selectivity for multirange operators
+ */
+Datum
+multirangesel(PG_FUNCTION_ARGS)
+{
+	PlannerInfo *root = (PlannerInfo *) PG_GETARG_POINTER(0);
+	Oid			operator = PG_GETARG_OID(1);
+	List	   *args = (List *) PG_GETARG_POINTER(2);
+	int			varRelid = PG_GETARG_INT32(3);
+	VariableStatData vardata;
+	Node	   *other;
+	bool		varonleft;
+	Selectivity selec;
+	TypeCacheEntry *typcache = NULL;
+	MultirangeType *constmultirange = NULL;
+	RangeType  *constrange = NULL;
+
+	/*
+	 * If expression is not (variable op something) or (something op
+	 * variable), then punt and return a default estimate.
+	 */
+	if (!get_restriction_variable(root, args, varRelid,
+								  &vardata, &other, &varonleft))
+		PG_RETURN_FLOAT8(default_multirange_selectivity(operator));
+
+	/*
+	 * Can't do anything useful if the something is not a constant, either.
+	 */
+	if (!IsA(other, Const))
+	{
+		ReleaseVariableStats(vardata);
+		PG_RETURN_FLOAT8(default_multirange_selectivity(operator));
+	}
+
+	/*
+	 * All the multirange operators are strict, so we can cope with a NULL constant
+	 * right away.
+	 */
+	if (((Const *) other)->constisnull)
+	{
+		ReleaseVariableStats(vardata);
+		PG_RETURN_FLOAT8(0.0);
+	}
+
+	/*
+	 * If var is on the right, commute the operator, so that we can assume the
+	 * var is on the left in what follows.
+	 */
+	if (!varonleft)
+	{
+		/* we have other Op var, commute to make var Op other */
+		operator = get_commutator(operator);
+		if (!operator)
+		{
+			/* Use default selectivity (should we raise an error instead?) */
+			ReleaseVariableStats(vardata);
+			PG_RETURN_FLOAT8(default_multirange_selectivity(operator));
+		}
+	}
+
+	/*
+	 * OK, there's a Var and a Const we're dealing with here.  We need the
+	 * Const to be of same multirange type as the column, else we can't do anything
+	 * useful. (Such cases will likely fail at runtime, but here we'd rather
+	 * just return a default estimate.)
+	 *
+	 * If the operator is "multirange @> element", the constant should be of the
+	 * element type of the multirange column. Convert it to a multirange that includes
+	 * only that single point, so that we don't need special handling for that
+	 * in what follows.
+	 */
+	if (operator == OID_MULTIRANGE_CONTAINS_ELEM_OP)
+	{
+		typcache = multirange_get_typcache(fcinfo, vardata.vartype);
+
+		if (((Const *) other)->consttype == typcache->rngtype->rngelemtype->type_id)
+		{
+			RangeBound	lower,
+						upper;
+
+			lower.inclusive = true;
+			lower.val = ((Const *) other)->constvalue;
+			lower.infinite = false;
+			lower.lower = true;
+			upper.inclusive = true;
+			upper.val = ((Const *) other)->constvalue;
+			upper.infinite = false;
+			upper.lower = false;
+			constrange = range_serialize(typcache->rngtype, &lower, &upper, false);
+			constmultirange = make_multirange(typcache->type_id, typcache->rngtype,
+					1, &constrange);
+		}
+	}
+	else if (operator == OID_MULTIRANGE_CONTAINS_RANGE_OP ||
+			 operator == OID_MULTIRANGE_OVERLAPS_RANGE_OP ||
+			 operator == OID_MULTIRANGE_OVERLAPS_LEFT_RANGE_OP ||
+			 operator == OID_MULTIRANGE_OVERLAPS_RIGHT_RANGE_OP ||
+			 operator == OID_MULTIRANGE_LEFT_RANGE_OP ||
+			 operator == OID_MULTIRANGE_RIGHT_RANGE_OP)
+	{
+		/*
+		 * Promote a range in "multirange OP range" just like
+		 * we do an element in "multirange OP element".
+		 */
+		typcache = multirange_get_typcache(fcinfo, vardata.vartype);
+		if (((Const *) other)->consttype == typcache->rngtype->type_id)
+		{
+			constrange = DatumGetRangeTypeP(((Const *) other)->constvalue);
+			constmultirange = make_multirange(typcache->type_id, typcache->rngtype,
+					1, &constrange);
+		}
+	}
+	else if (operator == OID_RANGE_OVERLAPS_MULTIRANGE_OP ||
+			 operator == OID_RANGE_OVERLAPS_LEFT_MULTIRANGE_OP ||
+			 operator == OID_RANGE_OVERLAPS_RIGHT_MULTIRANGE_OP ||
+			 operator == OID_RANGE_LEFT_MULTIRANGE_OP ||
+			 operator == OID_RANGE_RIGHT_MULTIRANGE_OP ||
+			 operator == OID_MULTIRANGE_ELEM_CONTAINED_OP ||
+			 operator == OID_MULTIRANGE_RANGE_CONTAINED_OP)
+	{
+		/*
+		 * Here, the Var is the elem/range, not the multirange.  For now we just punt and
+		 * return the default estimate.  In future we could disassemble the
+		 * multirange constant to do something more intelligent.
+		 */
+	}
+	else if (((Const *) other)->consttype == vardata.vartype)
+	{
+		/* Both sides are the same multirange type */
+		typcache = multirange_get_typcache(fcinfo, vardata.vartype);
+
+		constmultirange = DatumGetMultirangeTypeP(((Const *) other)->constvalue);
+	}
+
+	/*
+	 * If we got a valid constant on one side of the operator, proceed to
+	 * estimate using statistics. Otherwise punt and return a default constant
+	 * estimate.  Note that calc_multirangesel need not handle
+	 * OID_MULTIRANGE_*_CONTAINED_OP.
+	 */
+	if (constmultirange)
+		selec = calc_multirangesel(typcache, &vardata, constmultirange, operator);
+	else
+		selec = default_multirange_selectivity(operator);
+
+	ReleaseVariableStats(vardata);
+
+	CLAMP_PROBABILITY(selec);
+
+	PG_RETURN_FLOAT8((float8) selec);
+}
+
+static double
+calc_multirangesel(TypeCacheEntry *typcache, VariableStatData *vardata,
+			  const MultirangeType *constval, Oid operator)
+{
+	double		hist_selec;
+	double		selec;
+	float4		empty_frac,
+				null_frac;
+
+	/*
+	 * First look up the fraction of NULLs and empty multiranges from pg_statistic.
+	 */
+	if (HeapTupleIsValid(vardata->statsTuple))
+	{
+		Form_pg_statistic stats;
+		AttStatsSlot sslot;
+
+		stats = (Form_pg_statistic) GETSTRUCT(vardata->statsTuple);
+		null_frac = stats->stanullfrac;
+
+		/* Try to get fraction of empty multiranges */
+		if (get_attstatsslot(&sslot, vardata->statsTuple,
+							 STATISTIC_KIND_RANGE_LENGTH_HISTOGRAM,
+							 InvalidOid,
+							 ATTSTATSSLOT_NUMBERS))
+		{
+			if (sslot.nnumbers != 1)
+				elog(ERROR, "invalid empty fraction statistic");	/* shouldn't happen */
+			empty_frac = sslot.numbers[0];
+			free_attstatsslot(&sslot);
+		}
+		else
+		{
+			/* No empty fraction statistic. Assume no empty ranges. */
+			empty_frac = 0.0;
+		}
+	}
+	else
+	{
+		/*
+		 * No stats are available. Follow through the calculations below
+		 * anyway, assuming no NULLs and no empty multiranges. This still allows us
+		 * to give a better-than-nothing estimate based on whether the
+		 * constant is an empty multirange or not.
+		 */
+		null_frac = 0.0;
+		empty_frac = 0.0;
+	}
+
+	if (MultirangeIsEmpty(constval))
+	{
+		/*
+		 * An empty multirange matches all multiranges, all empty multiranges, or
+		 * nothing, depending on the operator
+		 */
+		switch (operator)
+		{
+				/* these return false if either argument is empty */
+			case OID_RANGE_OVERLAPS_MULTIRANGE_OP:
+			case OID_MULTIRANGE_OVERLAPS_RANGE_OP:
+			case OID_MULTIRANGE_OVERLAPS_MULTIRANGE_OP:
+			case OID_RANGE_OVERLAPS_LEFT_MULTIRANGE_OP:
+			case OID_MULTIRANGE_OVERLAPS_LEFT_RANGE_OP:
+			case OID_MULTIRANGE_OVERLAPS_LEFT_MULTIRANGE_OP:
+			case OID_RANGE_OVERLAPS_RIGHT_MULTIRANGE_OP:
+			case OID_MULTIRANGE_OVERLAPS_RIGHT_RANGE_OP:
+			case OID_MULTIRANGE_OVERLAPS_RIGHT_MULTIRANGE_OP:
+			case OID_MULTIRANGE_LEFT_MULTIRANGE_OP:
+			case OID_MULTIRANGE_RIGHT_MULTIRANGE_OP:
+				/* nothing is less than an empty multirange */
+			case OID_MULTIRANGE_LESS_OP:
+				selec = 0.0;
+				break;
+
+				/* only empty multiranges can be contained by an empty multirange */
+			case OID_MULTIRANGE_RANGE_CONTAINED_OP:
+			case OID_MULTIRANGE_MULTIRANGE_CONTAINED_OP:
+				/* only empty ranges are <= an empty multirange */
+			case OID_MULTIRANGE_LESS_EQUAL_OP:
+				selec = empty_frac;
+				break;
+
+				/* everything contains an empty multirange */
+			case OID_MULTIRANGE_CONTAINS_RANGE_OP:
+			case OID_MULTIRANGE_CONTAINS_MULTIRANGE_OP:
+				/* everything is >= an empty multirange */
+			case OID_MULTIRANGE_GREATER_EQUAL_OP:
+				selec = 1.0;
+				break;
+
+				/* all non-empty multiranges are > an empty multirange */
+			case OID_MULTIRANGE_GREATER_OP:
+				selec = 1.0 - empty_frac;
+				break;
+
+				/* an element cannot be empty */
+			case OID_MULTIRANGE_ELEM_CONTAINED_OP:
+			case OID_MULTIRANGE_CONTAINS_ELEM_OP:
+			default:
+				elog(ERROR, "unexpected operator %u", operator);
+				selec = 0.0;	/* keep compiler quiet */
+				break;
+		}
+	}
+	else
+	{
+		/*
+		 * Calculate selectivity using bound histograms. If that fails for
+		 * some reason, e.g no histogram in pg_statistic, use the default
+		 * constant estimate for the fraction of non-empty values. This is
+		 * still somewhat better than just returning the default estimate,
+		 * because this still takes into account the fraction of empty and
+		 * NULL tuples, if we had statistics for them.
+		 */
+		hist_selec = calc_hist_selectivity(typcache, vardata, constval,
+										   operator);
+		if (hist_selec < 0.0)
+			hist_selec = default_multirange_selectivity(operator);
+
+		/*
+		 * Now merge the results for the empty multiranges and histogram
+		 * calculations, realizing that the histogram covers only the
+		 * non-null, non-empty values.
+		 */
+		if (operator == OID_MULTIRANGE_ELEM_CONTAINED_OP ||
+			operator == OID_MULTIRANGE_RANGE_CONTAINED_OP ||
+			operator == OID_MULTIRANGE_MULTIRANGE_CONTAINED_OP)
+		{
+			/* empty is contained by anything non-empty */
+			selec = (1.0 - empty_frac) * hist_selec + empty_frac;
+		}
+		else
+		{
+			/* with any other operator, empty Op non-empty matches nothing */
+			selec = (1.0 - empty_frac) * hist_selec;
+		}
+	}
+
+	/* all multirange operators are strict */
+	selec *= (1.0 - null_frac);
+
+	/* result should be in range, but make sure... */
+	CLAMP_PROBABILITY(selec);
+
+	return selec;
+}
+
+/*
+ * Calculate multirange operator selectivity using histograms of multirange bounds.
+ *
+ * This estimate is for the portion of values that are not empty and not
+ * NULL.
+ */
+static double
+calc_hist_selectivity(TypeCacheEntry *typcache, VariableStatData *vardata,
+					  const MultirangeType *constval, Oid operator)
+{
+	TypeCacheEntry *rng_typcache = typcache->rngtype;
+	AttStatsSlot	hslot;
+	AttStatsSlot	lslot;
+	int			nhist;
+	RangeBound *hist_lower;
+	RangeBound *hist_upper;
+	int			i;
+	RangeBound	const_lower;
+	RangeBound	const_upper;
+	bool		empty;
+	double		hist_selec;
+	int			range_count;
+	RangeType **ranges;
+	RangeType  *constrange;
+
+	/* Can't use the histogram with insecure multirange support functions */
+	if (!statistic_proc_security_check(vardata,
+									   rng_typcache->rng_cmp_proc_finfo.fn_oid))
+		return -1;
+	if (OidIsValid(rng_typcache->rng_subdiff_finfo.fn_oid) &&
+		!statistic_proc_security_check(vardata,
+									   rng_typcache->rng_subdiff_finfo.fn_oid))
+		return -1;
+
+	/* Try to get histogram of ranges */
+	if (!(HeapTupleIsValid(vardata->statsTuple) &&
+		  get_attstatsslot(&hslot, vardata->statsTuple,
+						   STATISTIC_KIND_BOUNDS_HISTOGRAM, InvalidOid,
+						   ATTSTATSSLOT_VALUES)))
+		return -1.0;
+
+	/* check that it's a histogram, not just a dummy entry */
+	if (hslot.nvalues < 2)
+	{
+		free_attstatsslot(&hslot);
+		return -1.0;
+	}
+
+	/*
+	 * Convert histogram of ranges into histograms of its lower and upper
+	 * bounds.
+	 */
+	nhist = hslot.nvalues;
+	hist_lower = (RangeBound *) palloc(sizeof(RangeBound) * nhist);
+	hist_upper = (RangeBound *) palloc(sizeof(RangeBound) * nhist);
+	for (i = 0; i < nhist; i++)
+	{
+		range_deserialize(rng_typcache, DatumGetRangeTypeP(hslot.values[i]),
+						  &hist_lower[i], &hist_upper[i], &empty);
+		/* The histogram should not contain any empty ranges */
+		if (empty)
+			elog(ERROR, "bounds histogram contains an empty range");
+	}
+
+	/* @> and @< also need a histogram of range lengths */
+	if (operator == OID_MULTIRANGE_CONTAINS_RANGE_OP ||
+		operator == OID_MULTIRANGE_CONTAINS_MULTIRANGE_OP ||
+		operator == OID_MULTIRANGE_RANGE_CONTAINED_OP ||
+		operator == OID_MULTIRANGE_MULTIRANGE_CONTAINED_OP)
+	{
+		if (!(HeapTupleIsValid(vardata->statsTuple) &&
+			  get_attstatsslot(&lslot, vardata->statsTuple,
+							   STATISTIC_KIND_RANGE_LENGTH_HISTOGRAM,
+							   InvalidOid,
+							   ATTSTATSSLOT_VALUES)))
+		{
+			free_attstatsslot(&hslot);
+			return -1.0;
+		}
+
+		/* check that it's a histogram, not just a dummy entry */
+		if (lslot.nvalues < 2)
+		{
+			free_attstatsslot(&lslot);
+			free_attstatsslot(&hslot);
+			return -1.0;
+		}
+	}
+	else
+		memset(&lslot, 0, sizeof(lslot));
+
+	/* Extract the bounds of the constant value. */
+	multirange_deserialize(rng_typcache, constval, &range_count, &ranges);
+	Assert(range_count > 0);
+	constrange = range_union_internal(rng_typcache, ranges[0],
+			ranges[range_count - 1], false);
+	range_deserialize(rng_typcache, constrange, &const_lower, &const_upper, &empty);
+
+	/*
+	 * Calculate selectivity comparing the lower or upper bound of the
+	 * constant with the histogram of lower or upper bounds.
+	 */
+	switch (operator)
+	{
+		case OID_MULTIRANGE_LESS_OP:
+
+			/*
+			 * The regular b-tree comparison operators (<, <=, >, >=) compare
+			 * the lower bounds first, and the upper bounds for values with
+			 * equal lower bounds. Estimate that by comparing the lower bounds
+			 * only. This gives a fairly accurate estimate assuming there
+			 * aren't many rows with a lower bound equal to the constant's
+			 * lower bound.
+			 */
+			hist_selec =
+				calc_hist_selectivity_scalar(rng_typcache, &const_lower,
+											 hist_lower, nhist, false);
+			break;
+
+		case OID_MULTIRANGE_LESS_EQUAL_OP:
+			hist_selec =
+				calc_hist_selectivity_scalar(rng_typcache, &const_lower,
+											 hist_lower, nhist, true);
+			break;
+
+		case OID_MULTIRANGE_GREATER_OP:
+			hist_selec =
+				1 - calc_hist_selectivity_scalar(rng_typcache, &const_lower,
+												 hist_lower, nhist, false);
+			break;
+
+		case OID_MULTIRANGE_GREATER_EQUAL_OP:
+			hist_selec =
+				1 - calc_hist_selectivity_scalar(rng_typcache, &const_lower,
+												 hist_lower, nhist, true);
+			break;
+
+		case OID_RANGE_LEFT_MULTIRANGE_OP:
+		case OID_MULTIRANGE_LEFT_RANGE_OP:
+		case OID_MULTIRANGE_LEFT_MULTIRANGE_OP:
+			/* var << const when upper(var) < lower(const) */
+			hist_selec =
+				calc_hist_selectivity_scalar(rng_typcache, &const_lower,
+											 hist_upper, nhist, false);
+			break;
+
+		case OID_RANGE_RIGHT_MULTIRANGE_OP:
+		case OID_MULTIRANGE_RIGHT_RANGE_OP:
+		case OID_MULTIRANGE_RIGHT_MULTIRANGE_OP:
+			/* var >> const when lower(var) > upper(const) */
+			hist_selec =
+				1 - calc_hist_selectivity_scalar(rng_typcache, &const_upper,
+												 hist_lower, nhist, true);
+			break;
+
+		case OID_RANGE_OVERLAPS_RIGHT_MULTIRANGE_OP:
+		case OID_MULTIRANGE_OVERLAPS_RIGHT_RANGE_OP:
+		case OID_MULTIRANGE_OVERLAPS_RIGHT_MULTIRANGE_OP:
+			/* compare lower bounds */
+			hist_selec =
+				1 - calc_hist_selectivity_scalar(rng_typcache, &const_lower,
+												 hist_lower, nhist, false);
+			break;
+
+		case OID_RANGE_OVERLAPS_LEFT_MULTIRANGE_OP:
+		case OID_MULTIRANGE_OVERLAPS_LEFT_RANGE_OP:
+		case OID_MULTIRANGE_OVERLAPS_LEFT_MULTIRANGE_OP:
+			/* compare upper bounds */
+			hist_selec =
+				calc_hist_selectivity_scalar(rng_typcache, &const_upper,
+											 hist_upper, nhist, true);
+			break;
+
+		case OID_RANGE_OVERLAPS_MULTIRANGE_OP:
+		case OID_MULTIRANGE_OVERLAPS_RANGE_OP:
+		case OID_MULTIRANGE_OVERLAPS_MULTIRANGE_OP:
+		case OID_MULTIRANGE_CONTAINS_ELEM_OP:
+
+			/*
+			 * A && B <=> NOT (A << B OR A >> B).
+			 *
+			 * Since A << B and A >> B are mutually exclusive events we can
+			 * sum their probabilities to find probability of (A << B OR A >>
+			 * B).
+			 *
+			 * "multirange @> elem" is equivalent to "multirange && {[elem,elem]}".
+			 * The caller already constructed the singular range from the element
+			 * constant, so just treat it the same as &&.
+			 */
+			hist_selec =
+				calc_hist_selectivity_scalar(rng_typcache, &const_lower, hist_upper,
+											 nhist, false);
+			hist_selec +=
+				(1.0 - calc_hist_selectivity_scalar(rng_typcache, &const_upper, hist_lower,
+													nhist, true));
+			hist_selec = 1.0 - hist_selec;
+			break;
+
+		case OID_MULTIRANGE_CONTAINS_RANGE_OP:
+		case OID_MULTIRANGE_CONTAINS_MULTIRANGE_OP:
+			hist_selec =
+				calc_hist_selectivity_contains(rng_typcache, &const_lower,
+											   &const_upper, hist_lower, nhist,
+											   lslot.values, lslot.nvalues);
+			break;
+
+		case OID_MULTIRANGE_RANGE_CONTAINED_OP:
+		case OID_MULTIRANGE_MULTIRANGE_CONTAINED_OP:
+			if (const_lower.infinite)
+			{
+				/*
+				 * Lower bound no longer matters. Just estimate the fraction
+				 * with an upper bound <= const upper bound
+				 */
+				hist_selec =
+					calc_hist_selectivity_scalar(rng_typcache, &const_upper,
+												 hist_upper, nhist, true);
+			}
+			else if (const_upper.infinite)
+			{
+				hist_selec =
+					1.0 - calc_hist_selectivity_scalar(rng_typcache, &const_lower,
+													   hist_lower, nhist, false);
+			}
+			else
+			{
+				hist_selec =
+					calc_hist_selectivity_contained(rng_typcache, &const_lower,
+													&const_upper, hist_lower, nhist,
+													lslot.values, lslot.nvalues);
+			}
+			break;
+
+		default:
+			elog(ERROR, "unknown multirange operator %u", operator);
+			hist_selec = -1.0;	/* keep compiler quiet */
+			break;
+	}
+
+	free_attstatsslot(&lslot);
+	free_attstatsslot(&hslot);
+
+	return hist_selec;
+}
+
+
+/*
+ * Look up the fraction of values less than (or equal, if 'equal' argument
+ * is true) a given const in a histogram of range bounds.
+ */
+static double
+calc_hist_selectivity_scalar(TypeCacheEntry *typcache, const RangeBound *constbound,
+							 const RangeBound *hist, int hist_nvalues, bool equal)
+{
+	Selectivity selec;
+	int			index;
+
+	/*
+	 * Find the histogram bin the given constant falls into. Estimate
+	 * selectivity as the number of preceding whole bins.
+	 */
+	index = rbound_bsearch(typcache, constbound, hist, hist_nvalues, equal);
+	selec = (Selectivity) (Max(index, 0)) / (Selectivity) (hist_nvalues - 1);
+
+	/* Adjust using linear interpolation within the bin */
+	if (index >= 0 && index < hist_nvalues - 1)
+		selec += get_position(typcache, constbound, &hist[index],
+							  &hist[index + 1]) / (Selectivity) (hist_nvalues - 1);
+
+	return selec;
+}
+
+/*
+ * Binary search on an array of range bounds. Returns greatest index of range
+ * bound in array which is less(less or equal) than given range bound. If all
+ * range bounds in array are greater or equal(greater) than given range bound,
+ * return -1. When "equal" flag is set conditions in brackets are used.
+ *
+ * This function is used in scalar operator selectivity estimation. Another
+ * goal of this function is to find a histogram bin where to stop
+ * interpolation of portion of bounds which are less or equal to given bound.
+ */
+static int
+rbound_bsearch(TypeCacheEntry *typcache, const RangeBound *value, const RangeBound *hist,
+			   int hist_length, bool equal)
+{
+	int			lower = -1,
+				upper = hist_length - 1,
+				cmp,
+				middle;
+
+	while (lower < upper)
+	{
+		middle = (lower + upper + 1) / 2;
+		cmp = range_cmp_bounds(typcache, &hist[middle], value);
+
+		if (cmp < 0 || (equal && cmp == 0))
+			lower = middle;
+		else
+			upper = middle - 1;
+	}
+	return lower;
+}
+
+
+/*
+ * Binary search on length histogram. Returns greatest index of range length in
+ * histogram which is less than (less than or equal) the given length value. If
+ * all lengths in the histogram are greater than (greater than or equal) the
+ * given length, returns -1.
+ */
+static int
+length_hist_bsearch(Datum *length_hist_values, int length_hist_nvalues,
+					double value, bool equal)
+{
+	int			lower = -1,
+				upper = length_hist_nvalues - 1,
+				middle;
+
+	while (lower < upper)
+	{
+		double		middleval;
+
+		middle = (lower + upper + 1) / 2;
+
+		middleval = DatumGetFloat8(length_hist_values[middle]);
+		if (middleval < value || (equal && middleval <= value))
+			lower = middle;
+		else
+			upper = middle - 1;
+	}
+	return lower;
+}
+
+/*
+ * Get relative position of value in histogram bin in [0,1] range.
+ */
+static float8
+get_position(TypeCacheEntry *typcache, const RangeBound *value, const RangeBound *hist1,
+			 const RangeBound *hist2)
+{
+	bool		has_subdiff = OidIsValid(typcache->rng_subdiff_finfo.fn_oid);
+	float8		position;
+
+	if (!hist1->infinite && !hist2->infinite)
+	{
+		float8		bin_width;
+
+		/*
+		 * Both bounds are finite. Assuming the subtype's comparison function
+		 * works sanely, the value must be finite, too, because it lies
+		 * somewhere between the bounds.  If it doesn't, arbitrarily return
+		 * 0.5.
+		 */
+		if (value->infinite)
+			return 0.5;
+
+		/* Can't interpolate without subdiff function */
+		if (!has_subdiff)
+			return 0.5;
+
+		/* Calculate relative position using subdiff function. */
+		bin_width = DatumGetFloat8(FunctionCall2Coll(&typcache->rng_subdiff_finfo,
+													 typcache->rng_collation,
+													 hist2->val,
+													 hist1->val));
+		if (isnan(bin_width) || bin_width <= 0.0)
+			return 0.5;			/* punt for NaN or zero-width bin */
+
+		position = DatumGetFloat8(FunctionCall2Coll(&typcache->rng_subdiff_finfo,
+													typcache->rng_collation,
+													value->val,
+													hist1->val))
+			/ bin_width;
+
+		if (isnan(position))
+			return 0.5;			/* punt for NaN from subdiff, Inf/Inf, etc */
+
+		/* Relative position must be in [0,1] range */
+		position = Max(position, 0.0);
+		position = Min(position, 1.0);
+		return position;
+	}
+	else if (hist1->infinite && !hist2->infinite)
+	{
+		/*
+		 * Lower bin boundary is -infinite, upper is finite. If the value is
+		 * -infinite, return 0.0 to indicate it's equal to the lower bound.
+		 * Otherwise return 1.0 to indicate it's infinitely far from the lower
+		 * bound.
+		 */
+		return ((value->infinite && value->lower) ? 0.0 : 1.0);
+	}
+	else if (!hist1->infinite && hist2->infinite)
+	{
+		/* same as above, but in reverse */
+		return ((value->infinite && !value->lower) ? 1.0 : 0.0);
+	}
+	else
+	{
+		/*
+		 * If both bin boundaries are infinite, they should be equal to each
+		 * other, and the value should also be infinite and equal to both
+		 * bounds. (But don't Assert that, to avoid crashing if a user creates
+		 * a datatype with a broken comparison function).
+		 *
+		 * Assume the value to lie in the middle of the infinite bounds.
+		 */
+		return 0.5;
+	}
+}
+
+
+/*
+ * Get relative position of value in a length histogram bin in [0,1] range.
+ */
+static double
+get_len_position(double value, double hist1, double hist2)
+{
+	if (!isinf(hist1) && !isinf(hist2))
+	{
+		/*
+		 * Both bounds are finite. The value should be finite too, because it
+		 * lies somewhere between the bounds. If it doesn't, just return
+		 * something.
+		 */
+		if (isinf(value))
+			return 0.5;
+
+		return 1.0 - (hist2 - value) / (hist2 - hist1);
+	}
+	else if (isinf(hist1) && !isinf(hist2))
+	{
+		/*
+		 * Lower bin boundary is -infinite, upper is finite. Return 1.0 to
+		 * indicate the value is infinitely far from the lower bound.
+		 */
+		return 1.0;
+	}
+	else if (isinf(hist1) && isinf(hist2))
+	{
+		/* same as above, but in reverse */
+		return 0.0;
+	}
+	else
+	{
+		/*
+		 * If both bin boundaries are infinite, they should be equal to each
+		 * other, and the value should also be infinite and equal to both
+		 * bounds. (But don't Assert that, to avoid crashing unnecessarily if
+		 * the caller messes up)
+		 *
+		 * Assume the value to lie in the middle of the infinite bounds.
+		 */
+		return 0.5;
+	}
+}
+
+/*
+ * Measure distance between two range bounds.
+ */
+static float8
+get_distance(TypeCacheEntry *typcache, const RangeBound *bound1, const RangeBound *bound2)
+{
+	bool		has_subdiff = OidIsValid(typcache->rng_subdiff_finfo.fn_oid);
+
+	if (!bound1->infinite && !bound2->infinite)
+	{
+		/*
+		 * Neither bound is infinite, use subdiff function or return default
+		 * value of 1.0 if no subdiff is available.
+		 */
+		if (has_subdiff)
+		{
+			float8		res;
+
+			res = DatumGetFloat8(FunctionCall2Coll(&typcache->rng_subdiff_finfo,
+												   typcache->rng_collation,
+												   bound2->val,
+												   bound1->val));
+			/* Reject possible NaN result, also negative result */
+			if (isnan(res) || res < 0.0)
+				return 1.0;
+			else
+				return res;
+		}
+		else
+			return 1.0;
+	}
+	else if (bound1->infinite && bound2->infinite)
+	{
+		/* Both bounds are infinite */
+		if (bound1->lower == bound2->lower)
+			return 0.0;
+		else
+			return get_float8_infinity();
+	}
+	else
+	{
+		/* One bound is infinite, the other is not */
+		return get_float8_infinity();
+	}
+}
+
+/*
+ * Calculate the average of function P(x), in the interval [length1, length2],
+ * where P(x) is the fraction of tuples with length < x (or length <= x if
+ * 'equal' is true).
+ */
+static double
+calc_length_hist_frac(Datum *length_hist_values, int length_hist_nvalues,
+					  double length1, double length2, bool equal)
+{
+	double		frac;
+	double		A,
+				B,
+				PA,
+				PB;
+	double		pos;
+	int			i;
+	double		area;
+
+	Assert(length2 >= length1);
+
+	if (length2 < 0.0)
+		return 0.0;				/* shouldn't happen, but doesn't hurt to check */
+
+	/* All lengths in the table are <= infinite. */
+	if (isinf(length2) && equal)
+		return 1.0;
+
+	/*----------
+	 * The average of a function between A and B can be calculated by the
+	 * formula:
+	 *
+	 *			B
+	 *	  1		/
+	 * -------	| P(x)dx
+	 *	B - A	/
+	 *			A
+	 *
+	 * The geometrical interpretation of the integral is the area under the
+	 * graph of P(x). P(x) is defined by the length histogram. We calculate
+	 * the area in a piecewise fashion, iterating through the length histogram
+	 * bins. Each bin is a trapezoid:
+	 *
+	 *		 P(x2)
+	 *		  /|
+	 *		 / |
+	 * P(x1)/  |
+	 *	   |   |
+	 *	   |   |
+	 *	---+---+--
+	 *	   x1  x2
+	 *
+	 * where x1 and x2 are the boundaries of the current histogram, and P(x1)
+	 * and P(x1) are the cumulative fraction of tuples at the boundaries.
+	 *
+	 * The area of each trapezoid is 1/2 * (P(x2) + P(x1)) * (x2 - x1)
+	 *
+	 * The first bin contains the lower bound passed by the caller, so we
+	 * use linear interpolation between the previous and next histogram bin
+	 * boundary to calculate P(x1). Likewise for the last bin: we use linear
+	 * interpolation to calculate P(x2). For the bins in between, x1 and x2
+	 * lie on histogram bin boundaries, so P(x1) and P(x2) are simply:
+	 * P(x1) =	  (bin index) / (number of bins)
+	 * P(x2) = (bin index + 1 / (number of bins)
+	 */
+
+	/* First bin, the one that contains lower bound */
+	i = length_hist_bsearch(length_hist_values, length_hist_nvalues, length1, equal);
+	if (i >= length_hist_nvalues - 1)
+		return 1.0;
+
+	if (i < 0)
+	{
+		i = 0;
+		pos = 0.0;
+	}
+	else
+	{
+		/* interpolate length1's position in the bin */
+		pos = get_len_position(length1,
+							   DatumGetFloat8(length_hist_values[i]),
+							   DatumGetFloat8(length_hist_values[i + 1]));
+	}
+	PB = (((double) i) + pos) / (double) (length_hist_nvalues - 1);
+	B = length1;
+
+	/*
+	 * In the degenerate case that length1 == length2, simply return
+	 * P(length1). This is not merely an optimization: if length1 == length2,
+	 * we'd divide by zero later on.
+	 */
+	if (length2 == length1)
+		return PB;
+
+	/*
+	 * Loop through all the bins, until we hit the last bin, the one that
+	 * contains the upper bound. (if lower and upper bounds are in the same
+	 * bin, this falls out immediately)
+	 */
+	area = 0.0;
+	for (; i < length_hist_nvalues - 1; i++)
+	{
+		double		bin_upper = DatumGetFloat8(length_hist_values[i + 1]);
+
+		/* check if we've reached the last bin */
+		if (!(bin_upper < length2 || (equal && bin_upper <= length2)))
+			break;
+
+		/* the upper bound of previous bin is the lower bound of this bin */
+		A = B;
+		PA = PB;
+
+		B = bin_upper;
+		PB = (double) i / (double) (length_hist_nvalues - 1);
+
+		/*
+		 * Add the area of this trapezoid to the total. The point of the
+		 * if-check is to avoid NaN, in the corner case that PA == PB == 0,
+		 * and B - A == Inf. The area of a zero-height trapezoid (PA == PB ==
+		 * 0) is zero, regardless of the width (B - A).
+		 */
+		if (PA > 0 || PB > 0)
+			area += 0.5 * (PB + PA) * (B - A);
+	}
+
+	/* Last bin */
+	A = B;
+	PA = PB;
+
+	B = length2;				/* last bin ends at the query upper bound */
+	if (i >= length_hist_nvalues - 1)
+		pos = 0.0;
+	else
+	{
+		if (DatumGetFloat8(length_hist_values[i]) == DatumGetFloat8(length_hist_values[i + 1]))
+			pos = 0.0;
+		else
+			pos = get_len_position(length2, DatumGetFloat8(length_hist_values[i]), DatumGetFloat8(length_hist_values[i + 1]));
+	}
+	PB = (((double) i) + pos) / (double) (length_hist_nvalues - 1);
+
+	if (PA > 0 || PB > 0)
+		area += 0.5 * (PB + PA) * (B - A);
+
+	/*
+	 * Ok, we have calculated the area, ie. the integral. Divide by width to
+	 * get the requested average.
+	 *
+	 * Avoid NaN arising from infinite / infinite. This happens at least if
+	 * length2 is infinite. It's not clear what the correct value would be in
+	 * that case, so 0.5 seems as good as any value.
+	 */
+	if (isinf(area) && isinf(length2))
+		frac = 0.5;
+	else
+		frac = area / (length2 - length1);
+
+	return frac;
+}
+
+/*
+ * Calculate selectivity of "var <@ const" operator, ie. estimate the fraction
+ * of multiranges that fall within the constant lower and upper bounds. This uses
+ * the histograms of range lower bounds and range lengths, on the assumption
+ * that the range lengths are independent of the lower bounds.
+ *
+ * The caller has already checked that constant lower and upper bounds are
+ * finite.
+ */
+static double
+calc_hist_selectivity_contained(TypeCacheEntry *typcache,
+								const RangeBound *lower, RangeBound *upper,
+								const RangeBound *hist_lower, int hist_nvalues,
+								Datum *length_hist_values, int length_hist_nvalues)
+{
+	int			i,
+				upper_index;
+	float8		prev_dist;
+	double		bin_width;
+	double		upper_bin_width;
+	double		sum_frac;
+
+	/*
+	 * Begin by finding the bin containing the upper bound, in the lower bound
+	 * histogram. Any range with a lower bound > constant upper bound can't
+	 * match, ie. there are no matches in bins greater than upper_index.
+	 */
+	upper->inclusive = !upper->inclusive;
+	upper->lower = true;
+	upper_index = rbound_bsearch(typcache, upper, hist_lower, hist_nvalues,
+								 false);
+
+	/*
+	 * If the upper bound value is below the histogram's lower limit, there
+	 * are no matches.
+	 */
+	if (upper_index < 0)
+		return 0.0;
+
+	/*
+	 * If the upper bound value is at or beyond the histogram's upper limit,
+	 * start our loop at the last actual bin, as though the upper bound were
+	 * within that bin; get_position will clamp its result to 1.0 anyway.
+	 * (This corresponds to assuming that the data population above the
+	 * histogram's upper limit is empty, exactly like what we just assumed for
+	 * the lower limit.)
+	 */
+	upper_index = Min(upper_index, hist_nvalues - 2);
+
+	/*
+	 * Calculate upper_bin_width, ie. the fraction of the (upper_index,
+	 * upper_index + 1) bin which is greater than upper bound of query range
+	 * using linear interpolation of subdiff function.
+	 */
+	upper_bin_width = get_position(typcache, upper,
+								   &hist_lower[upper_index],
+								   &hist_lower[upper_index + 1]);
+
+	/*
+	 * In the loop, dist and prev_dist are the distance of the "current" bin's
+	 * lower and upper bounds from the constant upper bound.
+	 *
+	 * bin_width represents the width of the current bin. Normally it is 1.0,
+	 * meaning a full width bin, but can be less in the corner cases: start
+	 * and end of the loop. We start with bin_width = upper_bin_width, because
+	 * we begin at the bin containing the upper bound.
+	 */
+	prev_dist = 0.0;
+	bin_width = upper_bin_width;
+
+	sum_frac = 0.0;
+	for (i = upper_index; i >= 0; i--)
+	{
+		double		dist;
+		double		length_hist_frac;
+		bool		final_bin = false;
+
+		/*
+		 * dist -- distance from upper bound of query range to lower bound of
+		 * the current bin in the lower bound histogram. Or to the lower bound
+		 * of the constant range, if this is the final bin, containing the
+		 * constant lower bound.
+		 */
+		if (range_cmp_bounds(typcache, &hist_lower[i], lower) < 0)
+		{
+			dist = get_distance(typcache, lower, upper);
+
+			/*
+			 * Subtract from bin_width the portion of this bin that we want to
+			 * ignore.
+			 */
+			bin_width -= get_position(typcache, lower, &hist_lower[i],
+									  &hist_lower[i + 1]);
+			if (bin_width < 0.0)
+				bin_width = 0.0;
+			final_bin = true;
+		}
+		else
+			dist = get_distance(typcache, &hist_lower[i], upper);
+
+		/*
+		 * Estimate the fraction of tuples in this bin that are narrow enough
+		 * to not exceed the distance to the upper bound of the query range.
+		 */
+		length_hist_frac = calc_length_hist_frac(length_hist_values,
+												 length_hist_nvalues,
+												 prev_dist, dist, true);
+
+		/*
+		 * Add the fraction of tuples in this bin, with a suitable length, to
+		 * the total.
+		 */
+		sum_frac += length_hist_frac * bin_width / (double) (hist_nvalues - 1);
+
+		if (final_bin)
+			break;
+
+		bin_width = 1.0;
+		prev_dist = dist;
+	}
+
+	return sum_frac;
+}
+
+/*
+ * Calculate selectivity of "var @> const" operator, ie. estimate the fraction
+ * of multiranges that contain the constant lower and upper bounds. This uses
+ * the histograms of range lower bounds and range lengths, on the assumption
+ * that the range lengths are independent of the lower bounds.
+ */
+static double
+calc_hist_selectivity_contains(TypeCacheEntry *typcache,
+							   const RangeBound *lower, const RangeBound *upper,
+							   const RangeBound *hist_lower, int hist_nvalues,
+							   Datum *length_hist_values, int length_hist_nvalues)
+{
+	int			i,
+				lower_index;
+	double		bin_width,
+				lower_bin_width;
+	double		sum_frac;
+	float8		prev_dist;
+
+	/* Find the bin containing the lower bound of query range. */
+	lower_index = rbound_bsearch(typcache, lower, hist_lower, hist_nvalues,
+								 true);
+
+	/*
+	 * If the lower bound value is below the histogram's lower limit, there
+	 * are no matches.
+	 */
+	if (lower_index < 0)
+		return 0.0;
+
+	/*
+	 * If the lower bound value is at or beyond the histogram's upper limit,
+	 * start our loop at the last actual bin, as though the upper bound were
+	 * within that bin; get_position will clamp its result to 1.0 anyway.
+	 * (This corresponds to assuming that the data population above the
+	 * histogram's upper limit is empty, exactly like what we just assumed for
+	 * the lower limit.)
+	 */
+	lower_index = Min(lower_index, hist_nvalues - 2);
+
+	/*
+	 * Calculate lower_bin_width, ie. the fraction of the of (lower_index,
+	 * lower_index + 1) bin which is greater than lower bound of query range
+	 * using linear interpolation of subdiff function.
+	 */
+	lower_bin_width = get_position(typcache, lower, &hist_lower[lower_index],
+								   &hist_lower[lower_index + 1]);
+
+	/*
+	 * Loop through all the lower bound bins, smaller than the query lower
+	 * bound. In the loop, dist and prev_dist are the distance of the
+	 * "current" bin's lower and upper bounds from the constant upper bound.
+	 * We begin from query lower bound, and walk backwards, so the first bin's
+	 * upper bound is the query lower bound, and its distance to the query
+	 * upper bound is the length of the query range.
+	 *
+	 * bin_width represents the width of the current bin. Normally it is 1.0,
+	 * meaning a full width bin, except for the first bin, which is only
+	 * counted up to the constant lower bound.
+	 */
+	prev_dist = get_distance(typcache, lower, upper);
+	sum_frac = 0.0;
+	bin_width = lower_bin_width;
+	for (i = lower_index; i >= 0; i--)
+	{
+		float8		dist;
+		double		length_hist_frac;
+
+		/*
+		 * dist -- distance from upper bound of query range to current value
+		 * of lower bound histogram or lower bound of query range (if we've
+		 * reach it).
+		 */
+		dist = get_distance(typcache, &hist_lower[i], upper);
+
+		/*
+		 * Get average fraction of length histogram which covers intervals
+		 * longer than (or equal to) distance to upper bound of query range.
+		 */
+		length_hist_frac =
+			1.0 - calc_length_hist_frac(length_hist_values,
+										length_hist_nvalues,
+										prev_dist, dist, false);
+
+		sum_frac += length_hist_frac * bin_width / (double) (hist_nvalues - 1);
+
+		bin_width = 1.0;
+		prev_dist = dist;
+	}
+
+	return sum_frac;
+}
diff --git a/src/backend/utils/adt/pg_upgrade_support.c b/src/backend/utils/adt/pg_upgrade_support.c
index 14d9eb2b5b..7045d13cc0 100644
--- a/src/backend/utils/adt/pg_upgrade_support.c
+++ b/src/backend/utils/adt/pg_upgrade_support.c
@@ -51,6 +51,28 @@ binary_upgrade_set_next_array_pg_type_oid(PG_FUNCTION_ARGS)
 	PG_RETURN_VOID();
 }
 
+Datum
+binary_upgrade_set_next_multirange_pg_type_oid(PG_FUNCTION_ARGS)
+{
+	Oid			typoid = PG_GETARG_OID(0);
+
+	CHECK_IS_BINARY_UPGRADE;
+	binary_upgrade_next_mrng_pg_type_oid = typoid;
+
+	PG_RETURN_VOID();
+}
+
+Datum
+binary_upgrade_set_next_multirange_array_pg_type_oid(PG_FUNCTION_ARGS)
+{
+	Oid			typoid = PG_GETARG_OID(0);
+
+	CHECK_IS_BINARY_UPGRADE;
+	binary_upgrade_next_mrng_array_pg_type_oid = typoid;
+
+	PG_RETURN_VOID();
+}
+
 Datum
 binary_upgrade_set_next_heap_pg_class_oid(PG_FUNCTION_ARGS)
 {
diff --git a/src/backend/utils/adt/pseudotypes.c b/src/backend/utils/adt/pseudotypes.c
index 3d6b2f9093..99a93271fe 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"
 
 
 /*
@@ -227,6 +228,43 @@ anycompatiblerange_out(PG_FUNCTION_ARGS)
 	return range_out(fcinfo);
 }
 
+/*
+ * anycompatiblemultirange
+ *
+ * We may as well allow output, since multirange_out will in fact work.
+ */
+PSEUDOTYPE_DUMMY_INPUT_FUNC(anycompatiblemultirange);
+
+Datum
+anycompatiblemultirange_out(PG_FUNCTION_ARGS)
+{
+	return multirange_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
  *
diff --git a/src/backend/utils/adt/rangetypes.c b/src/backend/utils/adt/rangetypes.c
index 01ad8bc240..ba4caa2d36 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
 {
@@ -64,10 +62,6 @@ static const char *range_parse_bound(const char *string, const char *ptr,
 static char *range_deparse(char flags, const char *lbound_str,
 						   const char *ubound_str);
 static char *range_bound_escape(const char *value);
-static Size datum_compute_size(Size sz, Datum datum, bool typbyval,
-							   char typalign, int16 typlen, char typstorage);
-static Pointer datum_write(Pointer ptr, Datum datum, bool typbyval,
-						   char typalign, int16 typlen, char typstorage);
 
 
 /*
@@ -431,19 +425,37 @@ range_lower(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
 	TypeCacheEntry *typcache;
+	bool		isnull;
+	Datum		result;
+
+	typcache = range_get_typcache(fcinfo, RangeTypeGetOid(r1));
+
+	result = range_lower_internal(typcache, r1, &isnull);
+
+	if (isnull)
+		PG_RETURN_NULL();
+	else
+		PG_RETURN_DATUM(result);
+}
+
+Datum
+range_lower_internal(TypeCacheEntry *typcache, const RangeType *r1, bool *isnull)
+{
 	RangeBound	lower;
 	RangeBound	upper;
 	bool		empty;
 
-	typcache = range_get_typcache(fcinfo, RangeTypeGetOid(r1));
-
 	range_deserialize(typcache, r1, &lower, &upper, &empty);
 
 	/* Return NULL if there's no finite lower bound */
 	if (empty || lower.infinite)
-		PG_RETURN_NULL();
+	{
+		*isnull = true;
+		return 0;
+	}
 
-	PG_RETURN_DATUM(lower.val);
+	*isnull = false;
+	return lower.val;
 }
 
 /* extract upper bound value */
@@ -452,19 +464,37 @@ range_upper(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
 	TypeCacheEntry *typcache;
+	bool		isnull;
+	Datum		result;
+
+	typcache = range_get_typcache(fcinfo, RangeTypeGetOid(r1));
+
+	result = range_upper_internal(typcache, r1, &isnull);
+
+	if (isnull)
+		PG_RETURN_NULL();
+	else
+		PG_RETURN_DATUM(result);
+}
+
+Datum
+range_upper_internal(TypeCacheEntry *typcache, const RangeType *r1, bool *isnull)
+{
 	RangeBound	lower;
 	RangeBound	upper;
 	bool		empty;
 
-	typcache = range_get_typcache(fcinfo, RangeTypeGetOid(r1));
-
 	range_deserialize(typcache, r1, &lower, &upper, &empty);
 
 	/* Return NULL if there's no finite upper bound */
 	if (empty || upper.infinite)
-		PG_RETURN_NULL();
+	{
+		*isnull = true;
+		return 0;
+	}
 
-	PG_RETURN_DATUM(upper.val);
+	*isnull = false;
+	return upper.val;
 }
 
 
@@ -475,9 +505,8 @@ Datum
 range_empty(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
-	char		flags = range_get_flags(r1);
 
-	PG_RETURN_BOOL(flags & RANGE_EMPTY);
+	PG_RETURN_BOOL(range_has_flag(r1, RANGE_EMPTY));
 }
 
 /* is lower bound inclusive? */
@@ -485,9 +514,8 @@ Datum
 range_lower_inc(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
-	char		flags = range_get_flags(r1);
 
-	PG_RETURN_BOOL(flags & RANGE_LB_INC);
+	PG_RETURN_BOOL(range_has_flag(r1, RANGE_LB_INC));
 }
 
 /* is upper bound inclusive? */
@@ -495,9 +523,8 @@ Datum
 range_upper_inc(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
-	char		flags = range_get_flags(r1);
 
-	PG_RETURN_BOOL(flags & RANGE_UB_INC);
+	PG_RETURN_BOOL(range_has_flag(r1, RANGE_UB_INC));
 }
 
 /* is lower bound infinite? */
@@ -505,9 +532,8 @@ Datum
 range_lower_inf(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
-	char		flags = range_get_flags(r1);
 
-	PG_RETURN_BOOL(flags & RANGE_LB_INF);
+	PG_RETURN_BOOL(range_has_flag(r1, RANGE_LB_INF));
 }
 
 /* is upper bound infinite? */
@@ -515,9 +541,8 @@ Datum
 range_upper_inf(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
-	char		flags = range_get_flags(r1);
 
-	PG_RETURN_BOOL(flags & RANGE_UB_INF);
+	PG_RETURN_BOOL(range_has_flag(r1, RANGE_UB_INF));
 }
 
 
@@ -957,7 +982,25 @@ range_minus(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
 	RangeType  *r2 = PG_GETARG_RANGE_P(1);
+	RangeType  *ret;
 	TypeCacheEntry *typcache;
+
+	/* 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));
+
+	ret = range_minus_internal(typcache, r1, r2);
+	if (ret)
+		PG_RETURN_RANGE_P(ret);
+	else
+		PG_RETURN_NULL();
+}
+
+RangeType *
+range_minus_internal(TypeCacheEntry *typcache, RangeType *r1, RangeType *r2)
+{
 	RangeBound	lower1,
 				lower2;
 	RangeBound	upper1,
@@ -969,18 +1012,12 @@ range_minus(PG_FUNCTION_ARGS)
 				cmp_u1l2,
 				cmp_u1u2;
 
-	/* 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));
-
 	range_deserialize(typcache, r1, &lower1, &upper1, &empty1);
 	range_deserialize(typcache, r2, &lower2, &upper2, &empty2);
 
 	/* if either is empty, r1 is the correct answer */
 	if (empty1 || empty2)
-		PG_RETURN_RANGE_P(r1);
+		return r1;
 
 	cmp_l1l2 = range_cmp_bounds(typcache, &lower1, &lower2);
 	cmp_l1u2 = range_cmp_bounds(typcache, &lower1, &upper2);
@@ -993,34 +1030,34 @@ range_minus(PG_FUNCTION_ARGS)
 				 errmsg("result of range difference would not be contiguous")));
 
 	if (cmp_l1u2 > 0 || cmp_u1l2 < 0)
-		PG_RETURN_RANGE_P(r1);
+		return r1;
 
 	if (cmp_l1l2 >= 0 && cmp_u1u2 <= 0)
-		PG_RETURN_RANGE_P(make_empty_range(typcache));
+		return make_empty_range(typcache);
 
 	if (cmp_l1l2 <= 0 && cmp_u1l2 >= 0 && cmp_u1u2 <= 0)
 	{
 		lower2.inclusive = !lower2.inclusive;
 		lower2.lower = false;	/* it will become the upper bound */
-		PG_RETURN_RANGE_P(make_range(typcache, &lower1, &lower2, false));
+		return make_range(typcache, &lower1, &lower2, false);
 	}
 
 	if (cmp_l1l2 >= 0 && cmp_u1u2 >= 0 && cmp_l1u2 <= 0)
 	{
 		upper2.inclusive = !upper2.inclusive;
 		upper2.lower = true;	/* it will become the lower bound */
-		PG_RETURN_RANGE_P(make_range(typcache, &upper2, &upper1, false));
+		return make_range(typcache, &upper2, &upper1, false);
 	}
 
 	elog(ERROR, "unexpected case in range_minus");
-	PG_RETURN_NULL();
+	return NULL;
 }
 
 /*
  * Set union.  If strict is true, it is an error that the two input ranges
  * are not adjacent or overlapping.
  */
-static RangeType *
+RangeType *
 range_union_internal(TypeCacheEntry *typcache, RangeType *r1, RangeType *r2,
 					 bool strict)
 {
@@ -1101,6 +1138,19 @@ range_intersect(PG_FUNCTION_ARGS)
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
 	RangeType  *r2 = PG_GETARG_RANGE_P(1);
 	TypeCacheEntry *typcache;
+
+	/* 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));
+
+	PG_RETURN_RANGE_P(range_intersect_internal(typcache, r1, r2));
+}
+
+RangeType *
+range_intersect_internal(TypeCacheEntry *typcache, const RangeType *r1, const RangeType *r2)
+{
 	RangeBound	lower1,
 				lower2;
 	RangeBound	upper1,
@@ -1110,17 +1160,11 @@ range_intersect(PG_FUNCTION_ARGS)
 	RangeBound *result_lower;
 	RangeBound *result_upper;
 
-	/* 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));
-
 	range_deserialize(typcache, r1, &lower1, &upper1, &empty1);
 	range_deserialize(typcache, r2, &lower2, &upper2, &empty2);
 
-	if (empty1 || empty2 || !DatumGetBool(range_overlaps(fcinfo)))
-		PG_RETURN_RANGE_P(make_empty_range(typcache));
+	if (empty1 || empty2 || !range_overlaps_internal(typcache, r1, r2))
+		return make_empty_range(typcache);
 
 	if (range_cmp_bounds(typcache, &lower1, &lower2) >= 0)
 		result_lower = &lower1;
@@ -1132,9 +1176,81 @@ range_intersect(PG_FUNCTION_ARGS)
 	else
 		result_upper = &upper2;
 
-	PG_RETURN_RANGE_P(make_range(typcache, result_lower, result_upper, false));
+	return make_range(typcache, result_lower, result_upper, false);
+}
+
+/* range, range -> range, range functions */
+
+/*
+ * range_split_internal - if r2 intersects the middle of r1, leaving non-empty
+ * ranges on both sides, then return true and set output1 and output2 to the
+ * results of r1 - r2 (in order). Otherwise return false and don't set output1
+ * or output2. Neither input range should be empty.
+ */
+bool
+range_split_internal(TypeCacheEntry *typcache, const RangeType *r1, const RangeType *r2,
+					 RangeType **output1, RangeType **output2)
+{
+	RangeBound	lower1,
+				lower2;
+	RangeBound	upper1,
+				upper2;
+	bool		empty1,
+				empty2;
+
+	range_deserialize(typcache, r1, &lower1, &upper1, &empty1);
+	range_deserialize(typcache, r2, &lower2, &upper2, &empty2);
+
+	if (range_cmp_bounds(typcache, &lower1, &lower2) < 0 &&
+		range_cmp_bounds(typcache, &upper1, &upper2) > 0)
+	{
+		/*
+		 * Need to invert inclusive/exclusive for the lower2 and upper2
+		 * points. They can't be infinite though. We're allowed to overwrite
+		 * these RangeBounds since they only exist locally.
+		 */
+		lower2.inclusive = !lower2.inclusive;
+		lower2.lower = false;
+		upper2.inclusive = !upper2.inclusive;
+		upper2.lower = true;
+
+		*output1 = make_range(typcache, &lower1, &lower2, false);
+		*output2 = make_range(typcache, &upper2, &upper1, false);
+		return true;
+	}
+
+	return false;
 }
 
+/* range -> range aggregate functions */
+
+Datum
+range_intersect_agg_transfn(PG_FUNCTION_ARGS)
+{
+	MemoryContext aggContext;
+	Oid			rngtypoid;
+	TypeCacheEntry *typcache;
+	RangeType  *result;
+	RangeType  *current;
+
+	if (!AggCheckCallContext(fcinfo, &aggContext))
+		elog(ERROR, "range_intersect_agg_transfn called in non-aggregate context");
+
+	rngtypoid = get_fn_expr_argtype(fcinfo->flinfo, 1);
+	if (!type_is_range(rngtypoid))
+		ereport(ERROR, (errmsg("range_intersect_agg must be called with a range")));
+
+	typcache = range_get_typcache(fcinfo, rngtypoid);
+
+	/* strictness ensures these are non-null */
+	result = PG_GETARG_RANGE_P(0);
+	current = PG_GETARG_RANGE_P(1);
+
+	result = range_intersect_internal(typcache, result, current);
+	PG_RETURN_RANGE_P(result);
+}
+
+
 /* Btree support */
 
 /* btree comparator */
@@ -1144,12 +1260,6 @@ range_cmp(PG_FUNCTION_ARGS)
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
 	RangeType  *r2 = PG_GETARG_RANGE_P(1);
 	TypeCacheEntry *typcache;
-	RangeBound	lower1,
-				lower2;
-	RangeBound	upper1,
-				upper2;
-	bool		empty1,
-				empty2;
 	int			cmp;
 
 	check_stack_depth();		/* recurses when subtype is a range type */
@@ -1160,6 +1270,28 @@ range_cmp(PG_FUNCTION_ARGS)
 
 	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, const RangeType *r1, const 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);
 
@@ -1177,10 +1309,7 @@ range_cmp(PG_FUNCTION_ARGS)
 			cmp = range_cmp_bounds(typcache, &upper1, &upper2);
 	}
 
-	PG_FREE_IF_COPY(r1, 0);
-	PG_FREE_IF_COPY(r2, 1);
-
-	PG_RETURN_INT32(cmp);
+	return cmp;
 }
 
 /* inequality operators using the range_cmp function */
@@ -1218,13 +1347,10 @@ range_gt(PG_FUNCTION_ARGS)
 
 /* Hash support */
 
-/* hash a range value */
-Datum
-hash_range(PG_FUNCTION_ARGS)
+uint32
+hash_range_internal(TypeCacheEntry *typcache, const RangeType *r)
 {
-	RangeType  *r = PG_GETARG_RANGE_P(0);
 	uint32		result;
-	TypeCacheEntry *typcache;
 	TypeCacheEntry *scache;
 	RangeBound	lower;
 	RangeBound	upper;
@@ -1233,10 +1359,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 +1400,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, const RangeType *r, Datum seed)
+{
+	uint64		result;
 	TypeCacheEntry *scache;
 	RangeBound	lower;
 	RangeBound	upper;
@@ -1300,10 +1429,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 +1467,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));
 }
 
 /*
@@ -1769,6 +1912,21 @@ range_get_flags(const RangeType *range)
 	return *((char *) range + VARSIZE(range) - 1);
 }
 
+/*
+ * range_has_flag: set whether a range has a specific flag.
+ *
+ * This lets expose some of our functions that just check flags
+ * to the rest of the code base (like to multiranges)
+ * without writing full-fledged *_internal versions.
+ */
+bool
+range_has_flag(const RangeType *r1, char flag)
+{
+	char		flags = range_get_flags(r1);
+
+	return flags & flag;
+}
+
 /*
  * range_set_contain_empty: set the RANGE_CONTAIN_EMPTY bit in the value.
  *
@@ -1937,6 +2095,46 @@ range_cmp_bound_values(TypeCacheEntry *typcache, const RangeBound *b1,
 										   b1->val, b2->val));
 }
 
+/*
+ * qsort callback for sorting ranges.
+ *
+ * Two empty ranges compare equal; an empty range sorts to the left of any
+ * non-empty range.  Two non-empty ranges are sorted by lower bound first
+ * and by upper bound next.
+ */
+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.
  */
@@ -2395,7 +2593,7 @@ range_contains_elem_internal(TypeCacheEntry *typcache, const RangeType *r, Datum
  * Increment data_length by the space needed by the datum, including any
  * preceding alignment padding.
  */
-static Size
+Size
 datum_compute_size(Size data_length, Datum val, bool typbyval, char typalign,
 				   int16 typlen, char typstorage)
 {
@@ -2421,7 +2619,7 @@ datum_compute_size(Size data_length, Datum val, bool typbyval, char typalign,
  * Write the given datum beginning at ptr (after advancing to correct
  * alignment, if needed).  Return the pointer incremented by space used.
  */
-static Pointer
+Pointer
 datum_write(Pointer ptr, Datum datum, bool typbyval, char typalign,
 			int16 typlen, char typstorage)
 {
diff --git a/src/backend/utils/adt/rangetypes_typanalyze.c b/src/backend/utils/adt/rangetypes_typanalyze.c
index 57413b0720..94679cce69 100644
--- a/src/backend/utils/adt/rangetypes_typanalyze.c
+++ b/src/backend/utils/adt/rangetypes_typanalyze.c
@@ -30,6 +30,7 @@
 #include "utils/fmgrprotos.h"
 #include "utils/lsyscache.h"
 #include "utils/rangetypes.h"
+#include "utils/multirangetypes.h"
 
 static int	float8_qsort_cmp(const void *a1, const void *a2);
 static int	range_bound_qsort_cmp(const void *a1, const void *a2, void *arg);
@@ -60,6 +61,33 @@ range_typanalyze(PG_FUNCTION_ARGS)
 	PG_RETURN_BOOL(true);
 }
 
+/*
+ * multirange_typanalyze -- typanalyze function for multirange columns
+ *
+ * We do the same analysis as for ranges, but on the smallest range that
+ * completely includes the multirange.
+ */
+Datum
+multirange_typanalyze(PG_FUNCTION_ARGS)
+{
+	VacAttrStats *stats = (VacAttrStats *) PG_GETARG_POINTER(0);
+	TypeCacheEntry *typcache;
+	Form_pg_attribute attr = stats->attr;
+
+	/* Get information about multirange type; note column might be a domain */
+	typcache = multirange_get_typcache(fcinfo, getBaseType(stats->attrtypid));
+
+	if (attr->attstattarget < 0)
+		attr->attstattarget = default_statistics_target;
+
+	stats->compute_stats = compute_range_stats;
+	stats->extra_data = typcache;
+	/* same as in std_typanalyze */
+	stats->minrows = 300 * attr->attstattarget;
+
+	PG_RETURN_BOOL(true);
+}
+
 /*
  * Comparison function for sorting float8s, used for range lengths.
  */
@@ -98,7 +126,8 @@ compute_range_stats(VacAttrStats *stats, AnalyzeAttrFetchFunc fetchfunc,
 					int samplerows, double totalrows)
 {
 	TypeCacheEntry *typcache = (TypeCacheEntry *) stats->extra_data;
-	bool		has_subdiff = OidIsValid(typcache->rng_subdiff_finfo.fn_oid);
+	TypeCacheEntry *mltrng_typcache = NULL;
+	bool		has_subdiff;
 	int			null_cnt = 0;
 	int			non_null_cnt = 0;
 	int			non_empty_cnt = 0;
@@ -112,6 +141,15 @@ compute_range_stats(VacAttrStats *stats, AnalyzeAttrFetchFunc fetchfunc,
 			   *uppers;
 	double		total_width = 0;
 
+	if (typcache->typtype == TYPTYPE_MULTIRANGE)
+	{
+		mltrng_typcache = typcache;
+		typcache = typcache->rngtype;
+	}
+	else
+		Assert(typcache->typtype == TYPTYPE_RANGE);
+	has_subdiff = OidIsValid(typcache->rng_subdiff_finfo.fn_oid);
+
 	/* Allocate memory to hold range bounds and lengths of the sample ranges. */
 	lowers = (RangeBound *) palloc(sizeof(RangeBound) * samplerows);
 	uppers = (RangeBound *) palloc(sizeof(RangeBound) * samplerows);
@@ -123,6 +161,7 @@ compute_range_stats(VacAttrStats *stats, AnalyzeAttrFetchFunc fetchfunc,
 		Datum		value;
 		bool		isnull,
 					empty;
+		MultirangeType *multirange;
 		RangeType  *range;
 		RangeBound	lower,
 					upper;
@@ -145,7 +184,23 @@ compute_range_stats(VacAttrStats *stats, AnalyzeAttrFetchFunc fetchfunc,
 		total_width += VARSIZE_ANY(DatumGetPointer(value));
 
 		/* Get range and deserialize it for further analysis. */
-		range = DatumGetRangeTypeP(value);
+		if (mltrng_typcache != NULL)
+		{
+			/* Treat multiranges like a big range without gaps. */
+			multirange = DatumGetMultirangeTypeP(value);
+			if (MultirangeIsEmpty(multirange))
+				range = make_empty_range(typcache);
+			else
+			{
+				int	range_count;
+				RangeType **ranges;
+				multirange_deserialize(typcache, multirange, &range_count, &ranges);
+				range = range_union_internal(typcache, ranges[0],
+						ranges[range_count - 1], false);
+			}
+		}
+		else
+			range = DatumGetRangeTypeP(value);
 		range_deserialize(typcache, range, &lower, &upper, &empty);
 
 		if (!empty)
@@ -262,6 +317,13 @@ compute_range_stats(VacAttrStats *stats, AnalyzeAttrFetchFunc fetchfunc,
 			stats->stakind[slot_idx] = STATISTIC_KIND_BOUNDS_HISTOGRAM;
 			stats->stavalues[slot_idx] = bound_hist_values;
 			stats->numvalues[slot_idx] = num_hist;
+
+			/* Store ranges even if we're analyzing a multirange column */
+			stats->statypid[slot_idx] = typcache->type_id;
+			stats->statyplen[slot_idx] = typcache->typlen;
+			stats->statypbyval[slot_idx] = typcache->typbyval;
+			stats->statypalign[slot_idx] = 'd';
+
 			slot_idx++;
 		}
 
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index f3bf413829..429559356d 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -2578,6 +2578,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
  *
@@ -3220,7 +3230,7 @@ get_namespace_name_or_temp(Oid nspid)
 		return get_namespace_name(nspid);
 }
 
-/*				---------- PG_RANGE CACHE ----------				 */
+/*				---------- PG_RANGE CACHES ----------				 */
 
 /*
  * get_range_subtype
@@ -3273,6 +3283,56 @@ get_range_collation(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->rngmultitypid;
+		ReleaseSysCache(tp);
+		return result;
+	}
+	else
+		return InvalidOid;
+}
+
+/*
+ * get_multirange_range
+ *		Returns the range type of a given multirange
+ *
+ * Returns InvalidOid if the type is not a multirange.
+ */
+Oid
+get_multirange_range(Oid multirangeOid)
+{
+	HeapTuple	tp;
+
+	tp = SearchSysCache1(RANGEMULTIRANGE, 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 53d9ddf159..7654331a59 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -651,6 +651,18 @@ static const struct cachedesc cacheinfo[] = {
 		},
 		64
 	},
+	{RangeRelationId,			/* RANGEMULTIRANGE */
+		RangeMultirangeTypidIndexId,
+		1,
+		{
+			Anum_pg_range_rngmultitypid,
+			0,
+			0,
+			0
+		},
+		4
+	},
+
 	{RangeRelationId,			/* RANGETYPE */
 		RangeTypidIndexId,
 		1,
diff --git a/src/backend/utils/cache/typcache.c b/src/backend/utils/cache/typcache.c
index f51248b70d..82b21eadd1 100644
--- a/src/backend/utils/cache/typcache.c
+++ b/src/backend/utils/cache/typcache.c
@@ -286,6 +286,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);
@@ -302,6 +303,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 TypeCacheTypCallback(Datum arg, int cacheid, uint32 hashvalue);
 static void TypeCacheOpcCallback(Datum arg, int cacheid, uint32 hashvalue);
@@ -553,8 +557,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))
@@ -591,7 +595,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))
@@ -616,7 +620,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))
@@ -641,7 +645,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))
@@ -693,6 +697,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;
@@ -737,6 +748,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;
@@ -816,6 +834,16 @@ lookup_type_cache(Oid type_id, int flags)
 			(void) lookup_type_cache(typentry->rngelemtype->type_id, 0);
 	}
 
+	/*
+	 * 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
 	 */
@@ -927,6 +955,22 @@ load_rangetype_info(TypeCacheEntry *typentry)
 	typentry->rngelemtype = lookup_type_cache(subtypeOid, 0);
 }
 
+/*
+ * load_multirangetype_info --- helper routine to set up multirange type
+ * information
+ */
+static void
+load_multirangetype_info(TypeCacheEntry *typentry)
+{
+	Oid			rangetypeOid;
+
+	rangetypeOid = get_multirange_range(typentry->type_id);
+	if (!OidIsValid(rangetypeOid))
+		elog(ERROR, "cache lookup failed for multirange type %u",
+			 typentry->type_id);
+
+	typentry->rngtype = lookup_type_cache(rangetypeOid, TYPECACHE_RANGE_INFO);
+}
 
 /*
  * load_domaintype_info --- helper routine to set up domain constraint info
@@ -1527,11 +1571,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
@@ -1574,6 +1618,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 78ed857203..269c8b36e0 100644
--- a/src/backend/utils/fmgr/funcapi.c
+++ b/src/backend/utils/fmgr/funcapi.c
@@ -35,6 +35,7 @@ typedef struct polymorphic_actuals
 	Oid			anyelement_type;	/* anyelement mapping, if known */
 	Oid			anyarray_type;	/* anyarray mapping, if known */
 	Oid			anyrange_type;	/* anyrange mapping, if known */
+	Oid			anymultirange_type;	/* anymultirange mapping, if known */
 } polymorphic_actuals;
 
 static void shutdown_MultiFuncCall(Datum arg);
@@ -46,6 +47,7 @@ static TypeFuncClass internal_get_result_type(Oid funcid,
 static void resolve_anyelement_from_others(polymorphic_actuals *actuals);
 static void resolve_anyarray_from_others(polymorphic_actuals *actuals);
 static void resolve_anyrange_from_others(polymorphic_actuals *actuals);
+static void resolve_anymultirange_from_others(polymorphic_actuals *actuals);
 static bool resolve_polymorphic_tupdesc(TupleDesc tupdesc,
 										oidvector *declared_args,
 										Node *call_expr);
@@ -503,6 +505,34 @@ resolve_anyelement_from_others(polymorphic_actuals *actuals)
 							format_type_be(range_base_type))));
 		actuals->anyelement_type = range_typelem;
 	}
+	else if (OidIsValid(actuals->anymultirange_type))
+	{
+		/* Use the element type based on the multirange type */
+		Oid			multirange_base_type;
+		Oid			multirange_typelem;
+		Oid			range_base_type;
+		Oid			range_typelem;
+
+		multirange_base_type = getBaseType(actuals->anymultirange_type);
+		multirange_typelem = get_multirange_range(multirange_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(multirange_base_type))));
+
+		range_base_type = getBaseType(multirange_typelem);
+		range_typelem = get_range_subtype(range_base_type);
+
+		if (!OidIsValid(range_typelem))
+			ereport(ERROR,
+					(errcode(ERRCODE_DATATYPE_MISMATCH),
+					 errmsg("argument declared %s does not contain a range type but type %s",
+							"anymultirange",
+							format_type_be(range_base_type))));
+		actuals->anyelement_type = range_typelem;
+	}
 	else
 		elog(ERROR, "could not determine polymorphic type");
 }
@@ -540,10 +570,54 @@ static void
 resolve_anyrange_from_others(polymorphic_actuals *actuals)
 {
 	/*
-	 * We can't deduce a range type from other polymorphic inputs, because
-	 * there may be multiple range types with the same subtype.
+	 * We can't deduce a range type from other polymorphic array or base types,
+	 * because there may be multiple range types with the same subtype, but we
+	 * can deduce it from a polymorphic multirange type.
+	 */
+	if (OidIsValid(actuals->anymultirange_type))
+	{
+		/* Use the element type based on the multirange type */
+		Oid			multirange_base_type = getBaseType(actuals->anymultirange_type);
+		Oid			multirange_typelem =
+			get_multirange_range(multirange_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(multirange_base_type))));
+		actuals->anyrange_type = multirange_typelem;
+	}
+	else
+		elog(ERROR, "could not determine polymorphic type");
+}
+
+/*
+ * Resolve actual type of ANYMULTIRANGE from other polymorphic inputs
+ */
+static void
+resolve_anymultirange_from_others(polymorphic_actuals *actuals)
+{
+	/*
+	 * We can't deduce a multirange type from polymorphic array or base types,
+	 * because there may be multiple range types with the same subtype, but we
+	 * can deduce it from a polymorphic range type.
 	 */
-	elog(ERROR, "could not determine polymorphic type");
+	if (OidIsValid(actuals->anyrange_type))
+	{
+		Oid			range_base_type = getBaseType(actuals->anyrange_type);
+		Oid			multirange_typeid = get_range_multirange(range_base_type);
+
+		if (!OidIsValid(multirange_typeid))
+			ereport(ERROR,
+					(errcode(ERRCODE_UNDEFINED_OBJECT),
+					 errmsg("could not find multirange type for data type %s",
+							format_type_be(actuals->anyrange_type))));
+		actuals->anymultirange_type = multirange_typeid;
+	}
+	else
+		elog(ERROR, "could not determine polymorphic type");
 }
 
 /*
@@ -566,9 +640,11 @@ 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_anycompatible_result = false;
 	bool		have_anycompatible_array_result = false;
 	bool		have_anycompatible_range_result = false;
+	bool		have_anycompatible_multirange_result = false;
 	polymorphic_actuals poly_actuals;
 	polymorphic_actuals anyc_actuals;
 	Oid			anycollation = InvalidOid;
@@ -594,6 +670,10 @@ resolve_polymorphic_tupdesc(TupleDesc tupdesc, oidvector *declared_args,
 				have_polymorphic_result = true;
 				have_anyrange_result = true;
 				break;
+			case ANYMULTIRANGEOID:
+				have_polymorphic_result = true;
+				have_anymultirange_result = true;
+				break;
 			case ANYCOMPATIBLEOID:
 			case ANYCOMPATIBLENONARRAYOID:
 				have_polymorphic_result = true;
@@ -607,6 +687,10 @@ resolve_polymorphic_tupdesc(TupleDesc tupdesc, oidvector *declared_args,
 				have_polymorphic_result = true;
 				have_anycompatible_range_result = true;
 				break;
+			case ANYCOMPATIBLEMULTIRANGEOID:
+				have_polymorphic_result = true;
+				have_anycompatible_multirange_result = true;
+				break;
 			default:
 				break;
 		}
@@ -660,6 +744,15 @@ resolve_polymorphic_tupdesc(TupleDesc tupdesc, oidvector *declared_args,
 						return false;
 				}
 				break;
+			case ANYMULTIRANGEOID:
+				if (!OidIsValid(poly_actuals.anymultirange_type))
+				{
+					poly_actuals.anymultirange_type =
+						get_call_expr_argtype(call_expr, i);
+					if (!OidIsValid(poly_actuals.anymultirange_type))
+						return false;
+				}
+				break;
 			case ANYCOMPATIBLEOID:
 			case ANYCOMPATIBLENONARRAYOID:
 				if (!OidIsValid(anyc_actuals.anyelement_type))
@@ -688,6 +781,15 @@ resolve_polymorphic_tupdesc(TupleDesc tupdesc, oidvector *declared_args,
 						return false;
 				}
 				break;
+			case ANYCOMPATIBLEMULTIRANGEOID:
+				if (!OidIsValid(anyc_actuals.anymultirange_type))
+				{
+					anyc_actuals.anymultirange_type =
+						get_call_expr_argtype(call_expr, i);
+					if (!OidIsValid(anyc_actuals.anymultirange_type))
+						return false;
+				}
+				break;
 			default:
 				break;
 		}
@@ -703,6 +805,9 @@ resolve_polymorphic_tupdesc(TupleDesc tupdesc, oidvector *declared_args,
 	if (have_anyrange_result && !OidIsValid(poly_actuals.anyrange_type))
 		resolve_anyrange_from_others(&poly_actuals);
 
+	if (have_anymultirange_result && !OidIsValid(poly_actuals.anymultirange_type))
+		resolve_anymultirange_from_others(&poly_actuals);
+
 	if (have_anycompatible_result && !OidIsValid(anyc_actuals.anyelement_type))
 		resolve_anyelement_from_others(&anyc_actuals);
 
@@ -712,6 +817,9 @@ resolve_polymorphic_tupdesc(TupleDesc tupdesc, oidvector *declared_args,
 	if (have_anycompatible_range_result && !OidIsValid(anyc_actuals.anyrange_type))
 		resolve_anyrange_from_others(&anyc_actuals);
 
+	if (have_anycompatible_multirange_result && !OidIsValid(anyc_actuals.anymultirange_type))
+		resolve_anymultirange_from_others(&anyc_actuals);
+
 	/*
 	 * Identify the collation to use for polymorphic OUT parameters. (It'll
 	 * necessarily be the same for both anyelement and anyarray, likewise for
@@ -780,6 +888,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),
+								   poly_actuals.anymultirange_type,
+								   -1,
+								   0);
+				/* no collation should be attached to a multirange type */
+				break;
 			case ANYCOMPATIBLEOID:
 			case ANYCOMPATIBLENONARRAYOID:
 				TupleDescInitEntry(tupdesc, i + 1,
@@ -805,6 +921,14 @@ resolve_polymorphic_tupdesc(TupleDesc tupdesc, oidvector *declared_args,
 								   0);
 				/* no collation should be attached to a range type */
 				break;
+			case ANYCOMPATIBLEMULTIRANGEOID:
+				TupleDescInitEntry(tupdesc, i + 1,
+								   NameStr(att->attname),
+								   anyc_actuals.anymultirange_type,
+								   -1,
+								   0);
+				/* no collation should be attached to a multirange type */
+				break;
 			default:
 				break;
 		}
@@ -834,9 +958,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;
 	bool		have_anycompatible_result = false;
 	bool		have_anycompatible_array_result = false;
 	bool		have_anycompatible_range_result = false;
+	bool		have_anycompatible_multirange_result = false;
 	polymorphic_actuals poly_actuals;
 	polymorphic_actuals anyc_actuals;
 	int			inargno;
@@ -912,6 +1038,24 @@ resolve_polymorphic_argtypes(int numargs, Oid *argtypes, char *argmodes,
 					argtypes[i] = poly_actuals.anyrange_type;
 				}
 				break;
+			case ANYMULTIRANGEOID:
+				if (argmode == PROARGMODE_OUT || argmode == PROARGMODE_TABLE)
+				{
+					have_polymorphic_result = true;
+					have_anymultirange_result = true;
+				}
+				else
+				{
+					if (!OidIsValid(poly_actuals.anymultirange_type))
+					{
+						poly_actuals.anymultirange_type =
+							get_call_expr_argtype(call_expr, inargno);
+						if (!OidIsValid(poly_actuals.anymultirange_type))
+							return false;
+					}
+					argtypes[i] = poly_actuals.anymultirange_type;
+				}
+				break;
 			case ANYCOMPATIBLEOID:
 			case ANYCOMPATIBLENONARRAYOID:
 				if (argmode == PROARGMODE_OUT || argmode == PROARGMODE_TABLE)
@@ -967,6 +1111,24 @@ resolve_polymorphic_argtypes(int numargs, Oid *argtypes, char *argmodes,
 					argtypes[i] = anyc_actuals.anyrange_type;
 				}
 				break;
+			case ANYCOMPATIBLEMULTIRANGEOID:
+				if (argmode == PROARGMODE_OUT || argmode == PROARGMODE_TABLE)
+				{
+					have_polymorphic_result = true;
+					have_anycompatible_multirange_result = true;
+				}
+				else
+				{
+					if (!OidIsValid(anyc_actuals.anymultirange_type))
+					{
+						anyc_actuals.anymultirange_type =
+							get_call_expr_argtype(call_expr, inargno);
+						if (!OidIsValid(anyc_actuals.anymultirange_type))
+							return false;
+					}
+					argtypes[i] = anyc_actuals.anymultirange_type;
+				}
+				break;
 			default:
 				break;
 		}
@@ -988,6 +1150,9 @@ resolve_polymorphic_argtypes(int numargs, Oid *argtypes, char *argmodes,
 	if (have_anyrange_result && !OidIsValid(poly_actuals.anyrange_type))
 		resolve_anyrange_from_others(&poly_actuals);
 
+	if (have_anymultirange_result && !OidIsValid(poly_actuals.anymultirange_type))
+		resolve_anymultirange_from_others(&poly_actuals);
+
 	if (have_anycompatible_result && !OidIsValid(anyc_actuals.anyelement_type))
 		resolve_anyelement_from_others(&anyc_actuals);
 
@@ -997,6 +1162,9 @@ resolve_polymorphic_argtypes(int numargs, Oid *argtypes, char *argmodes,
 	if (have_anycompatible_range_result && !OidIsValid(anyc_actuals.anyrange_type))
 		resolve_anyrange_from_others(&anyc_actuals);
 
+	if (have_anycompatible_multirange_result && !OidIsValid(anyc_actuals.anymultirange_type))
+		resolve_anymultirange_from_others(&anyc_actuals);
+
 	/* And finally replace the output column types as needed */
 	for (i = 0; i < numargs; i++)
 	{
@@ -1013,6 +1181,9 @@ resolve_polymorphic_argtypes(int numargs, Oid *argtypes, char *argmodes,
 			case ANYRANGEOID:
 				argtypes[i] = poly_actuals.anyrange_type;
 				break;
+			case ANYMULTIRANGEOID:
+				argtypes[i] = poly_actuals.anymultirange_type;
+				break;
 			case ANYCOMPATIBLEOID:
 			case ANYCOMPATIBLENONARRAYOID:
 				argtypes[i] = anyc_actuals.anyelement_type;
@@ -1023,6 +1194,9 @@ resolve_polymorphic_argtypes(int numargs, Oid *argtypes, char *argmodes,
 			case ANYCOMPATIBLERANGEOID:
 				argtypes[i] = anyc_actuals.anyrange_type;
 				break;
+			case ANYCOMPATIBLEMULTIRANGEOID:
+				argtypes[i] = anyc_actuals.anymultirange_type;
+				break;
 			default:
 				break;
 		}
@@ -1052,6 +1226,7 @@ get_type_func_class(Oid typid, Oid *base_typeid)
 		case TYPTYPE_BASE:
 		case TYPTYPE_ENUM:
 		case TYPTYPE_RANGE:
+		case TYPTYPE_MULTIRANGE:
 			return TYPEFUNC_SCALAR;
 		case TYPTYPE_DOMAIN:
 			*base_typeid = typid = getBaseType(typid);
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index f021bb72f4..37fdac15c1 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -271,7 +271,8 @@ static void dumpSearchPath(Archive *AH);
 static void binary_upgrade_set_type_oids_by_type_oid(Archive *fout,
 													 PQExpBuffer upgrade_buffer,
 													 Oid pg_type_oid,
-													 bool force_array_type);
+													 bool force_array_type,
+													 bool include_multirange_type);
 static void binary_upgrade_set_type_oids_by_rel_oid(Archive *fout,
 													PQExpBuffer upgrade_buffer, Oid pg_rel_oid);
 static void binary_upgrade_set_pg_class_oids(Archive *fout,
@@ -1640,7 +1641,7 @@ selectDumpableType(TypeInfo *tyinfo, Archive *fout)
 	}
 
 	/* skip auto-generated array types */
-	if (tyinfo->isArray)
+	if (tyinfo->isArray || tyinfo->isMultirange)
 	{
 		tyinfo->dobj.objType = DO_DUMMY_TYPE;
 
@@ -4454,16 +4455,49 @@ append_depends_on_extension(Archive *fout,
 	}
 }
 
+static Oid
+get_next_possible_free_pg_type_oid(Archive *fout, PQExpBuffer upgrade_query)
+{
+	/*
+	 * If the old version didn't assign an array type, but the new version
+	 * does, we must select an unused type OID to assign.  This currently only
+	 * happens for domains, when upgrading pre-v11 to v11 and up.
+	 *
+	 * Note: local state here is kind of ugly, but we must have some, since we
+	 * mustn't choose the same unused OID more than once.
+	 */
+	static Oid	next_possible_free_oid = FirstNormalObjectId;
+	PGresult   *res;
+	bool		is_dup;
+
+	do
+	{
+		++next_possible_free_oid;
+		printfPQExpBuffer(upgrade_query,
+						  "SELECT EXISTS(SELECT 1 "
+						  "FROM pg_catalog.pg_type "
+						  "WHERE oid = '%u'::pg_catalog.oid);",
+						  next_possible_free_oid);
+		res = ExecuteSqlQueryForSingleRow(fout, upgrade_query->data);
+		is_dup = (PQgetvalue(res, 0, 0)[0] == 't');
+		PQclear(res);
+	} while (is_dup);
+
+	return next_possible_free_oid;
+}
 
 static void
 binary_upgrade_set_type_oids_by_type_oid(Archive *fout,
 										 PQExpBuffer upgrade_buffer,
 										 Oid pg_type_oid,
-										 bool force_array_type)
+										 bool force_array_type,
+										 bool include_multirange_type)
 {
 	PQExpBuffer upgrade_query = createPQExpBuffer();
 	PGresult   *res;
 	Oid			pg_type_array_oid;
+	Oid			pg_type_multirange_oid;
+	Oid			pg_type_multirange_array_oid;
 
 	appendPQExpBufferStr(upgrade_buffer, "\n-- For binary upgrade, must preserve pg_type oid\n");
 	appendPQExpBuffer(upgrade_buffer,
@@ -4484,33 +4518,7 @@ binary_upgrade_set_type_oids_by_type_oid(Archive *fout,
 	PQclear(res);
 
 	if (!OidIsValid(pg_type_array_oid) && force_array_type)
-	{
-		/*
-		 * If the old version didn't assign an array type, but the new version
-		 * does, we must select an unused type OID to assign.  This currently
-		 * only happens for domains, when upgrading pre-v11 to v11 and up.
-		 *
-		 * Note: local state here is kind of ugly, but we must have some,
-		 * since we mustn't choose the same unused OID more than once.
-		 */
-		static Oid	next_possible_free_oid = FirstNormalObjectId;
-		bool		is_dup;
-
-		do
-		{
-			++next_possible_free_oid;
-			printfPQExpBuffer(upgrade_query,
-							  "SELECT EXISTS(SELECT 1 "
-							  "FROM pg_catalog.pg_type "
-							  "WHERE oid = '%u'::pg_catalog.oid);",
-							  next_possible_free_oid);
-			res = ExecuteSqlQueryForSingleRow(fout, upgrade_query->data);
-			is_dup = (PQgetvalue(res, 0, 0)[0] == 't');
-			PQclear(res);
-		} while (is_dup);
-
-		pg_type_array_oid = next_possible_free_oid;
-	}
+		pg_type_array_oid = get_next_possible_free_pg_type_oid(fout, upgrade_query);
 
 	if (OidIsValid(pg_type_array_oid))
 	{
@@ -4521,6 +4529,46 @@ binary_upgrade_set_type_oids_by_type_oid(Archive *fout,
 						  pg_type_array_oid);
 	}
 
+	/*
+	 * Pre-set the multirange type oid and its own array type oid.
+	 */
+	if (include_multirange_type)
+	{
+		if (fout->remoteVersion >= 130000)
+		{
+			appendPQExpBuffer(upgrade_query,
+							  "SELECT t.oid, t.typarray "
+							  "FROM pg_catalog.pg_type t "
+							  "JOIN pg_catalog.pg_range r "
+							  "ON t.oid = r.rngmultitypid "
+							  "WHERE r.rngtypid = '%u'::pg_catalog.oid;",
+							  pg_type_oid);
+
+			res = ExecuteSqlQueryForSingleRow(fout, upgrade_query->data);
+
+			pg_type_multirange_oid = atooid(PQgetvalue(res, 0, PQfnumber(res, "oid")));
+			pg_type_multirange_array_oid = atooid(PQgetvalue(res, 0, PQfnumber(res, "typarray")));
+
+			PQclear(res);
+		}
+		else
+		{
+			pg_type_multirange_oid = get_next_possible_free_pg_type_oid(fout, upgrade_query);
+			pg_type_multirange_array_oid = get_next_possible_free_pg_type_oid(fout, upgrade_query);
+		}
+
+		appendPQExpBufferStr(upgrade_buffer,
+							 "\n-- For binary upgrade, must preserve multirange pg_type oid\n");
+		appendPQExpBuffer(upgrade_buffer,
+						  "SELECT pg_catalog.binary_upgrade_set_next_multirange_pg_type_oid('%u'::pg_catalog.oid);\n\n",
+						  pg_type_multirange_oid);
+		appendPQExpBufferStr(upgrade_buffer,
+							 "\n-- For binary upgrade, must preserve multirange pg_type array oid\n");
+		appendPQExpBuffer(upgrade_buffer,
+						  "SELECT pg_catalog.binary_upgrade_set_next_multirange_array_pg_type_oid('%u'::pg_catalog.oid);\n\n",
+						  pg_type_multirange_array_oid);
+	}
+
 	destroyPQExpBuffer(upgrade_query);
 }
 
@@ -4545,7 +4593,7 @@ binary_upgrade_set_type_oids_by_rel_oid(Archive *fout,
 
 	if (OidIsValid(pg_type_oid))
 		binary_upgrade_set_type_oids_by_type_oid(fout, upgrade_buffer,
-												 pg_type_oid, false);
+												 pg_type_oid, false, false);
 
 	PQclear(upgrade_res);
 	destroyPQExpBuffer(upgrade_query);
@@ -5090,6 +5138,11 @@ getTypes(Archive *fout, int *numTypes)
 		else
 			tyinfo[i].isArray = false;
 
+		if (tyinfo[i].typtype == 'm')
+			tyinfo[i].isMultirange = true;
+		else
+			tyinfo[i].isMultirange = false;
+
 		/* Decide whether we want to dump it */
 		selectDumpableType(&tyinfo[i], fout);
 
@@ -8246,9 +8299,12 @@ getProcLangs(Archive *fout, int *numProcLangs)
 
 /*
  * getCasts
- *	  get basic information about every cast in the system
+ *	  get basic information about most casts in the system
  *
  * numCasts is set to the number of casts read in
+ *
+ * Skip casts from a range to its multirange, since we'll create those
+ * automatically.
  */
 CastInfo *
 getCasts(Archive *fout, int *numCasts)
@@ -8266,7 +8322,20 @@ getCasts(Archive *fout, int *numCasts)
 	int			i_castcontext;
 	int			i_castmethod;
 
-	if (fout->remoteVersion >= 80400)
+	if (fout->remoteVersion >= 130000)
+	{
+		appendPQExpBufferStr(query, "SELECT tableoid, oid, "
+							 "castsource, casttarget, castfunc, castcontext, "
+							 "castmethod "
+							 "FROM pg_cast c "
+							 "WHERE NOT EXISTS ( "
+							 "SELECT 1 FROM pg_range r "
+							 "WHERE c.castsource = r.rngtypid "
+							 "AND c.casttarget = r.rngmultitypid "
+							 ") "
+							 "ORDER BY 3,4");
+	}
+	else if (fout->remoteVersion >= 80400)
 	{
 		appendPQExpBufferStr(query, "SELECT tableoid, oid, "
 							 "castsource, casttarget, castfunc, castcontext, "
@@ -10416,7 +10485,7 @@ dumpEnumType(Archive *fout, TypeInfo *tyinfo)
 	if (dopt->binary_upgrade)
 		binary_upgrade_set_type_oids_by_type_oid(fout, q,
 												 tyinfo->dobj.catId.oid,
-												 false);
+												 false, false);
 
 	appendPQExpBuffer(q, "CREATE TYPE %s AS ENUM (",
 					  qualtypname);
@@ -10542,7 +10611,7 @@ dumpRangeType(Archive *fout, TypeInfo *tyinfo)
 	if (dopt->binary_upgrade)
 		binary_upgrade_set_type_oids_by_type_oid(fout, q,
 												 tyinfo->dobj.catId.oid,
-												 false);
+												 false, true);
 
 	appendPQExpBuffer(q, "CREATE TYPE %s AS RANGE (",
 					  qualtypname);
@@ -10648,7 +10717,7 @@ dumpUndefinedType(Archive *fout, TypeInfo *tyinfo)
 	if (dopt->binary_upgrade)
 		binary_upgrade_set_type_oids_by_type_oid(fout, q,
 												 tyinfo->dobj.catId.oid,
-												 false);
+												 false, false);
 
 	appendPQExpBuffer(q, "CREATE TYPE %s;\n",
 					  qualtypname);
@@ -10853,7 +10922,7 @@ dumpBaseType(Archive *fout, TypeInfo *tyinfo)
 	if (dopt->binary_upgrade)
 		binary_upgrade_set_type_oids_by_type_oid(fout, q,
 												 tyinfo->dobj.catId.oid,
-												 false);
+												 false, false);
 
 	appendPQExpBuffer(q,
 					  "CREATE TYPE %s (\n"
@@ -11040,7 +11109,8 @@ dumpDomain(Archive *fout, TypeInfo *tyinfo)
 	if (dopt->binary_upgrade)
 		binary_upgrade_set_type_oids_by_type_oid(fout, q,
 												 tyinfo->dobj.catId.oid,
-												 true); /* force array type */
+												 true,		/* force array type */
+												 false);	/* force multirange type */
 
 	qtypname = pg_strdup(fmtId(tyinfo->dobj.name));
 	qualtypname = pg_strdup(fmtQualifiedDumpable(tyinfo));
@@ -11228,7 +11298,7 @@ dumpCompositeType(Archive *fout, TypeInfo *tyinfo)
 	{
 		binary_upgrade_set_type_oids_by_type_oid(fout, q,
 												 tyinfo->dobj.catId.oid,
-												 false);
+												 false, false);
 		binary_upgrade_set_pg_class_oids(fout, q, tyinfo->typrelid, false);
 	}
 
@@ -11502,7 +11572,7 @@ dumpShellType(Archive *fout, ShellTypeInfo *stinfo)
 	if (dopt->binary_upgrade)
 		binary_upgrade_set_type_oids_by_type_oid(fout, q,
 												 stinfo->baseType->dobj.catId.oid,
-												 false);
+												 false, false);
 
 	appendPQExpBuffer(q, "CREATE TYPE %s;\n",
 					  fmtQualifiedDumpable(stinfo));
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index e0b42e8391..c8663bc100 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -176,6 +176,7 @@ typedef struct _typeInfo
 	char		typrelkind;		/* 'r', 'v', 'c', etc */
 	char		typtype;		/* 'b', 'c', etc */
 	bool		isArray;		/* true if auto-generated array type */
+	bool		isMultirange;	/* true if auto-generated multirange type */
 	bool		isDefined;		/* true if typisdefined */
 	/* If needed, we'll create a "shell type" entry for it; link that here: */
 	struct _shellTypeInfo *shellType;	/* shell-type entry, or NULL */
diff --git a/src/include/access/tupmacs.h b/src/include/access/tupmacs.h
index 70157cf90a..c262265b95 100644
--- a/src/include/access/tupmacs.h
+++ b/src/include/access/tupmacs.h
@@ -139,8 +139,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/binary_upgrade.h b/src/include/catalog/binary_upgrade.h
index 02fecb90f7..1ff62e9f8e 100644
--- a/src/include/catalog/binary_upgrade.h
+++ b/src/include/catalog/binary_upgrade.h
@@ -16,6 +16,8 @@
 
 extern PGDLLIMPORT Oid binary_upgrade_next_pg_type_oid;
 extern PGDLLIMPORT Oid binary_upgrade_next_array_pg_type_oid;
+extern PGDLLIMPORT Oid binary_upgrade_next_mrng_pg_type_oid;
+extern PGDLLIMPORT Oid binary_upgrade_next_mrng_array_pg_type_oid;
 
 extern PGDLLIMPORT Oid binary_upgrade_next_heap_pg_class_oid;
 extern PGDLLIMPORT Oid binary_upgrade_next_index_pg_class_oid;
diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h
index d86729dc6c..8cfedd43d4 100644
--- a/src/include/catalog/indexing.h
+++ b/src/include/catalog/indexing.h
@@ -338,6 +338,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_rngmultitypid_index, 8001, on pg_range using btree(rngmultitypid 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_aggregate.dat b/src/include/catalog/pg_aggregate.dat
index ffabe275c0..03bab74253 100644
--- a/src/include/catalog/pg_aggregate.dat
+++ b/src/include/catalog/pg_aggregate.dat
@@ -538,6 +538,17 @@
   aggtransfn => 'bytea_string_agg_transfn',
   aggfinalfn => 'bytea_string_agg_finalfn', aggtranstype => 'internal' },
 
+# range
+{ aggfnoid => 'range_intersect_agg(anyrange)',
+  aggtransfn => 'range_intersect_agg_transfn',
+  aggcombinefn => 'range_intersect_agg_transfn', aggtranstype => 'anyrange' },
+{ aggfnoid => 'range_intersect_agg(anymultirange)',
+  aggtransfn => 'multirange_intersect_agg_transfn',
+  aggcombinefn => 'multirange_intersect_agg_transfn', aggtranstype => 'anymultirange' },
+{ aggfnoid => 'range_agg(anyrange)', aggtransfn => 'range_agg_transfn',
+  aggfinalfn => 'range_agg_finalfn', aggfinalextra => 't',
+  aggtranstype => 'internal' },
+
 # json
 { aggfnoid => 'json_agg', aggtransfn => 'json_agg_transfn',
   aggfinalfn => 'json_agg_finalfn', aggtranstype => 'internal' },
diff --git a/src/include/catalog/pg_amop.dat b/src/include/catalog/pg_amop.dat
index 1dfb6fd373..42293ddffe 100644
--- a/src/include/catalog/pg_amop.dat
+++ b/src/include/catalog/pg_amop.dat
@@ -1379,6 +1379,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 a8e0c4ff8a..bb392cc192 100644
--- a/src/include/catalog/pg_amproc.dat
+++ b/src/include/catalog/pg_amproc.dat
@@ -285,6 +285,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' },
 { amprocfamily => 'btree/xid8_ops', amproclefttype => 'xid8',
@@ -453,6 +455,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',
@@ -1276,12 +1283,13 @@
   amprocrighttype => 'anyrange', amprocnum => '4',
   amproc => 'brin_inclusion_union' },
 { amprocfamily => 'brin/range_inclusion_ops', amproclefttype => 'anyrange',
-  amprocrighttype => 'anyrange', amprocnum => '11', amproc => 'range_merge' },
+  amprocrighttype => 'anyrange', amprocnum => '11',
+  amproc => 'range_merge(anyrange,anyrange)' },
 { amprocfamily => 'brin/range_inclusion_ops', amproclefttype => 'anyrange',
   amprocrighttype => 'anyrange', amprocnum => '13',
   amproc => 'range_contains' },
 { amprocfamily => 'brin/range_inclusion_ops', amproclefttype => 'anyrange',
-  amprocrighttype => 'anyrange', amprocnum => '14', amproc => 'isempty' },
+  amprocrighttype => 'anyrange', amprocnum => '14', amproc => 'isempty(anyrange)' },
 
 # minmax pg_lsn
 { amprocfamily => 'brin/pg_lsn_minmax_ops', amproclefttype => 'pg_lsn',
diff --git a/src/include/catalog/pg_cast.dat b/src/include/catalog/pg_cast.dat
index 5a58f50fbb..d6ca624add 100644
--- a/src/include/catalog/pg_cast.dat
+++ b/src/include/catalog/pg_cast.dat
@@ -530,4 +530,17 @@
 { castsource => 'jsonb', casttarget => 'float8', castfunc => 'float8(jsonb)',
   castcontext => 'e', castmethod => 'f' },
 
+# range to multirange
+{ castsource => 'int4range', casttarget => 'int4multirange', castfunc => 'int4multirange(int4range)',
+  castcontext => 'e', castmethod => 'f' },
+{ castsource => 'int8range', casttarget => 'int8multirange', castfunc => 'int8multirange(int8range)',
+  castcontext => 'e', castmethod => 'f' },
+{ castsource => 'numrange', casttarget => 'nummultirange', castfunc => 'nummultirange(numrange)',
+  castcontext => 'e', castmethod => 'f' },
+{ castsource => 'daterange', casttarget => 'datemultirange', castfunc => 'datemultirange(daterange)',
+  castcontext => 'e', castmethod => 'f' },
+{ castsource => 'tsrange', casttarget => 'tsmultirange', castfunc => 'tsmultirange(tsrange)',
+  castcontext => 'e', castmethod => 'f' },
+{ castsource => 'tstzrange', casttarget => 'tstzmultirange', castfunc => 'tstzmultirange(tstzrange)',
+  castcontext => 'e', castmethod => 'f' },
 ]
diff --git a/src/include/catalog/pg_opclass.dat b/src/include/catalog/pg_opclass.dat
index f2342bb328..9b0609defa 100644
--- a/src/include/catalog/pg_opclass.dat
+++ b/src/include/catalog/pg_opclass.dat
@@ -230,6 +230,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 7cc812adda..dec786d60e 100644
--- a/src/include/catalog/pg_operator.dat
+++ b/src/include/catalog/pg_operator.dat
@@ -3332,5 +3332,174 @@
   oprname => '@@', oprleft => 'jsonb', oprright => 'jsonpath',
   oprresult => 'bool', oprcode => 'jsonb_path_match_opr(jsonb,jsonpath)',
   oprrest => 'matchingsel', oprjoin => 'matchingjoinsel' },
+{ 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 => 'multirangesel', 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 => 'multirangesel', 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 => 'multirangesel', 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 => 'multirangesel', oprjoin => 'scalargtjoinsel' },
+{ oid => '8073', oid_symbol => 'OID_RANGE_OVERLAPS_MULTIRANGE_OP',
+  descr => 'overlaps',
+  oprname => '&&', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '&&(anymultirange,anyrange)',
+  oprcode => 'range_overlaps_multirange', oprrest => 'multirangesel',
+  oprjoin => 'areajoinsel' },
+{ oid => '8074', oid_symbol => 'OID_MULTIRANGE_OVERLAPS_RANGE_OP', descr => 'contains',
+  oprname => '&&', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcom => '&&(anyrange,anymultirange)',
+  oprcode => 'multirange_overlaps_range', oprrest => 'multirangesel',
+  oprjoin => 'areajoinsel' },
+{ oid => '8075', oid_symbol => 'OID_MULTIRANGE_OVERLAPS_MULTIRANGE_OP', descr => 'contains',
+  oprname => '&&', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '&&(anymultirange,anymultirange)',
+  oprcode => 'multirange_overlaps_multirange', oprrest => 'multirangesel',
+  oprjoin => 'areajoinsel' },
+{ oid => '8064', oid_symbol => 'OID_MULTIRANGE_CONTAINS_ELEM_OP',
+  descr => 'contains',
+  oprname => '@>', oprleft => 'anymultirange', oprright => 'anyelement',
+  oprresult => 'bool', oprcom => '<@(anyelement,anymultirange)',
+  oprcode => 'multirange_contains_elem', oprrest => 'multirangesel',
+  oprjoin => 'contjoinsel' },
+{ oid => '8065', oid_symbol => 'OID_MULTIRANGE_CONTAINS_RANGE_OP', descr => 'contains',
+  oprname => '@>', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcom => '<@(anyrange,anymultirange)',
+  oprcode => 'multirange_contains_range', oprrest => 'multirangesel',
+  oprjoin => 'contjoinsel' },
+{ oid => '8066', oid_symbol => 'OID_MULTIRANGE_CONTAINS_MULTIRANGE_OP', descr => 'contains',
+  oprname => '@>', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '<@(anymultirange,anymultirange)',
+  oprcode => 'multirange_contains_multirange', oprrest => 'multirangesel',
+  oprjoin => 'contjoinsel' },
+{ oid => '8067', oid_symbol => 'OID_MULTIRANGE_ELEM_CONTAINED_OP',
+  descr => 'is contained by',
+  oprname => '<@', oprleft => 'anyelement', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '@>(anymultirange,anyelement)',
+  oprcode => 'elem_contained_by_multirange', oprrest => 'multirangesel',
+  oprjoin => 'contjoinsel' },
+{ oid => '8068', oid_symbol => 'OID_MULTIRANGE_RANGE_CONTAINED_OP',
+  descr => 'is contained by',
+  oprname => '<@', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '@>(anymultirange,anyrange)',
+  oprcode => 'range_contained_by_multirange', oprrest => 'multirangesel',
+  oprjoin => 'contjoinsel' },
+{ oid => '8069', oid_symbol => 'OID_MULTIRANGE_MULTIRANGE_CONTAINED_OP',
+  descr => 'is contained by',
+  oprname => '<@', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '@>(anymultirange,anymultirange)',
+  oprcode => 'multirange_contained_by_multirange', oprrest => 'multirangesel',
+  oprjoin => 'contjoinsel' },
+{ oid => '8106', oid_symbol => 'OID_RANGE_OVERLAPS_LEFT_MULTIRANGE_OP',
+  descr => 'overlaps or is left of',
+  oprname => '&<', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcode => 'range_overleft_multirange', oprrest => 'multirangesel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8107', oid_symbol => 'OID_MULTIRANGE_OVERLAPS_LEFT_RANGE_OP',
+  descr => 'overlaps or is left of',
+  oprname => '&<', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcode => 'multirange_overleft_range', oprrest => 'multirangesel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8108', oid_symbol => 'OID_MULTIRANGE_OVERLAPS_LEFT_MULTIRANGE_OP',
+  descr => 'overlaps or is left of',
+  oprname => '&<', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcode => 'multirange_overleft_multirange', oprrest => 'multirangesel',
+  oprjoin => 'scalargtjoinsel' },
+{ oid => '8109', oid_symbol => 'OID_RANGE_OVERLAPS_RIGHT_MULTIRANGE_OP',
+  descr => 'overlaps or is right of',
+  oprname => '&>', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcode => 'range_overright_multirange', oprrest => 'multirangesel',
+  oprjoin => 'scalargtjoinsel' },
+{ oid => '8110', oid_symbol => 'OID_MULTIRANGE_OVERLAPS_RIGHT_RANGE_OP',
+  descr => 'overlaps or is right of',
+  oprname => '&>', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcode => 'multirange_overright_range', oprrest => 'multirangesel',
+  oprjoin => 'scalargtjoinsel' },
+{ oid => '8111', oid_symbol => 'OID_MULTIRANGE_OVERLAPS_RIGHT_MULTIRANGE_OP',
+  descr => 'overlaps or is right of',
+  oprname => '&>', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcode => 'multirange_overright_multirange', oprrest => 'multirangesel',
+  oprjoin => 'scalargtjoinsel' },
+{ oid => '8091', oid_symbol => 'OID_RANGE_ADJACENT_MULTIRANGE_OP', descr => 'is adjacent to',
+  oprname => '-|-', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '-|-(anymultirange,anyrange)',
+  oprcode => 'range_adjacent_multirange', oprrest => 'matchingsel',
+  oprjoin => 'matchingjoinsel' },
+{ oid => '8092', oid_symbol => 'OID_MULTIRANGE_ADJACENT_RANGE_OP', descr => 'is adjacent to',
+  oprname => '-|-', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcom => '-|-(anyrange,anymultirange)',
+  oprcode => 'multirange_adjacent_range', oprrest => 'matchingsel',
+  oprjoin => 'matchingjoinsel' },
+{ oid => '8093', oid_symbol => 'OID_MULTIRANGE_ADJACENT_MULTIRANGE_OP', descr => 'is adjacent to',
+  oprname => '-|-', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '-|-(anymultirange,anymultirange)',
+  oprcode => 'multirange_adjacent_multirange', oprrest => 'matchingsel',
+  oprjoin => 'matchingjoinsel' },
+{ oid => '8117', descr => 'multirange union',
+  oprname => '+', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'anymultirange', oprcom => '+(anymultirange,anymultirange)',
+  oprcode => 'multirange_union' },
+{ oid => '8123', descr => 'multirange minus',
+  oprname => '-', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'anymultirange', oprcode => 'multirange_minus' },
+{ oid => '8129', descr => 'multirange intersect',
+  oprname => '*', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'anymultirange', oprcom => '*(anymultirange,anymultirange)',
+  oprcode => 'multirange_intersect' },
+{ oid => '8082', oid_symbol => 'OID_RANGE_LEFT_MULTIRANGE_OP', descr => 'is left of',
+  oprname => '<<', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '>>(anymultirange,anyrange)',
+  oprcode => 'range_before_multirange', oprrest => 'multirangesel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8083', oid_symbol => 'OID_MULTIRANGE_LEFT_RANGE_OP', descr => 'is left of',
+  oprname => '<<', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcom => '>>(anyrange,anymultirange)',
+  oprcode => 'multirange_before_range', oprrest => 'multirangesel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8084', oid_symbol => 'OID_MULTIRANGE_LEFT_MULTIRANGE_OP', descr => 'is left of',
+  oprname => '<<', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '>>(anymultirange,anymultirange)',
+  oprcode => 'multirange_before_multirange', oprrest => 'multirangesel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8085', oid_symbol => 'OID_RANGE_RIGHT_MULTIRANGE_OP', descr => 'is right of',
+  oprname => '>>', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '<<(anymultirange,anyrange)',
+  oprcode => 'range_after_multirange', oprrest => 'multirangesel',
+  oprjoin => 'scalargtjoinsel' },
+{ oid => '8086', oid_symbol => 'OID_MULTIRANGE_RIGHT_RANGE_OP', descr => 'is right of',
+  oprname => '>>', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcom => '<<(anyrange,anymultirange)',
+  oprcode => 'multirange_after_range', oprrest => 'multirangesel',
+  oprjoin => 'scalargtjoinsel' },
+{ oid => '8087', oid_symbol => 'OID_MULTIRANGE_RIGHT_MULTIRANGE_OP', descr => 'is right of',
+  oprname => '>>', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '<<(anymultirange,anymultirange)',
+  oprcode => 'multirange_after_multirange', oprrest => 'multirangesel',
+  oprjoin => 'scalargtjoinsel' },
 
 ]
diff --git a/src/include/catalog/pg_opfamily.dat b/src/include/catalog/pg_opfamily.dat
index cf0fb325b3..ffd1220041 100644
--- a/src/include/catalog/pg_opfamily.dat
+++ b/src/include/catalog/pg_opfamily.dat
@@ -230,5 +230,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 f48f5fb4d9..fa7b80aacf 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -7224,6 +7224,14 @@
   proname => 'anycompatiblerange_out', provolatile => 's',
   prorettype => 'cstring', proargtypes => 'anycompatiblerange',
   prosrc => 'anycompatiblerange_out' },
+{ oid => '8154', descr => 'I/O',
+  proname => 'anycompatiblemultirange_in', provolatile => 's',
+  prorettype => 'anycompatiblemultirange', proargtypes => 'cstring oid int4',
+  prosrc => 'anycompatiblemultirange_in' },
+{ oid => '8155', descr => 'I/O',
+  proname => 'anycompatiblemultirange_out', provolatile => 's',
+  prorettype => 'cstring', proargtypes => 'anycompatiblemultirange',
+  prosrc => 'anycompatiblemultirange_out' },
 
 # tablesample method handlers
 { oid => '3313', descr => 'BERNOULLI tablesample method handler',
@@ -9835,6 +9843,10 @@
   descr => 'the smallest range which includes both of the given ranges',
   proname => 'range_merge', prorettype => 'anyrange',
   proargtypes => 'anyrange anyrange', prosrc => 'range_merge' },
+{ oid => '8143',
+  descr => 'the smallest range which includes the whole multirange',
+  proname => 'range_merge', prorettype => 'anyrange',
+  proargtypes => 'anymultirange', prosrc => 'range_merge_from_multirange' },
 { oid => '3868',
   proname => 'range_intersect', prorettype => 'anyrange',
   proargtypes => 'anyrange anyrange', prosrc => 'range_intersect' },
@@ -9884,6 +9896,13 @@
 { oid => '3169', descr => 'restriction selectivity for range operators',
   proname => 'rangesel', provolatile => 's', prorettype => 'float8',
   proargtypes => 'internal oid internal int4', prosrc => 'rangesel' },
+{ oid => '8133', descr => 'range aggregate by intersecting',
+  proname => 'range_intersect_agg_transfn', prorettype => 'anyrange',
+  proargtypes => 'anyrange anyrange', prosrc => 'range_intersect_agg_transfn'},
+{ oid => '8134', descr => 'range aggregate by intersecting',
+  proname => 'range_intersect_agg', prokind => 'a', proisstrict => 'f',
+  prorettype => 'anyrange', proargtypes => 'anyrange',
+  prosrc => 'aggregate_dummy'},
 
 { oid => '3914', descr => 'convert an int4 range to canonical form',
   proname => 'int4range_canonical', prorettype => 'int4range',
@@ -9952,6 +9971,261 @@
   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 => '8094', descr => 'lower bound of multirange',
+  proname => 'lower', prorettype => 'anyelement', proargtypes => 'anymultirange',
+  prosrc => 'multirange_lower' },
+{ oid => '8095', descr => 'upper bound of multirange',
+  proname => 'upper', prorettype => 'anyelement', proargtypes => 'anymultirange',
+  prosrc => 'multirange_upper' },
+{ oid => '8057', descr => 'is the multirange empty?',
+  proname => 'isempty', prorettype => 'bool',
+  proargtypes => 'anymultirange', prosrc => 'multirange_empty' },
+{ oid => '8096', descr => 'is the multirange\'s lower bound inclusive?',
+  proname => 'lower_inc', prorettype => 'bool', proargtypes => 'anymultirange',
+  prosrc => 'multirange_lower_inc' },
+{ oid => '8097', descr => 'is the multirange\'s upper bound inclusive?',
+  proname => 'upper_inc', prorettype => 'bool', proargtypes => 'anymultirange',
+  prosrc => 'multirange_upper_inc' },
+{ oid => '8098', descr => 'is the multirange\'s lower bound infinite?',
+  proname => 'lower_inf', prorettype => 'bool', proargtypes => 'anymultirange',
+  prosrc => 'multirange_lower_inf' },
+{ oid => '8099', descr => 'is the multirange\'s upper bound infinite?',
+  proname => 'upper_inf', prorettype => 'bool', proargtypes => 'anymultirange',
+  prosrc => 'multirange_upper_inf' },
+{ oid => '8008', descr => 'multirange typanalyze',
+  proname => 'multirange_typanalyze', provolatile => 's', prorettype => 'bool',
+  proargtypes => 'internal', prosrc => 'multirange_typanalyze' },
+{ oid => '8156', descr => 'restriction selectivity for multirange operators',
+  proname => 'multirangesel', provolatile => 's', prorettype => 'float8',
+  proargtypes => 'internal oid internal int4', prosrc => 'multirangesel' },
+{ oid => '8033',
+  proname => 'multirange_eq', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_eq' },
+{ oid => '8034',
+  proname => 'multirange_ne', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_ne' },
+{ oid => '8070',
+  proname => 'range_overlaps_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_overlaps_multirange' },
+{ oid => '8071',
+  proname => 'multirange_overlaps_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_overlaps_range' },
+{ oid => '8072',
+  proname => 'multirange_overlaps_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_overlaps_multirange' },
+{ oid => '8058',
+  proname => 'multirange_contains_elem', prorettype => 'bool',
+  proargtypes => 'anymultirange anyelement', prosrc => 'multirange_contains_elem' },
+{ oid => '8059',
+  proname => 'multirange_contains_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_contains_range' },
+{ oid => '8060',
+  proname => 'multirange_contains_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_contains_multirange' },
+{ oid => '8061',
+  proname => 'elem_contained_by_multirange', prorettype => 'bool',
+  proargtypes => 'anyelement anymultirange', prosrc => 'elem_contained_by_multirange' },
+{ oid => '8062',
+  proname => 'range_contained_by_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_contained_by_multirange' },
+{ oid => '8063',
+  proname => 'multirange_contained_by_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_contained_by_multirange' },
+{ oid => '8088',
+  proname => 'range_adjacent_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_adjacent_multirange' },
+{ oid => '8089',
+  proname => 'multirange_adjacent_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_adjacent_multirange' },
+{ oid => '8090',
+  proname => 'multirange_adjacent_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_adjacent_range' },
+{ oid => '8076',
+  proname => 'range_before_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_before_multirange' },
+{ oid => '8077',
+  proname => 'multirange_before_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_before_range' },
+{ oid => '8078',
+  proname => 'multirange_before_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_before_multirange' },
+{ oid => '8079',
+  proname => 'range_after_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_after_multirange' },
+{ oid => '8080',
+  proname => 'multirange_after_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_after_range' },
+{ oid => '8081',
+  proname => 'multirange_after_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_after_multirange' },
+{ oid => '8100',
+  proname => 'range_overleft_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_overleft_multirange' },
+{ oid => '8101',
+  proname => 'multirange_overleft_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_overleft_range' },
+{ oid => '8102',
+  proname => 'multirange_overleft_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_overleft_multirange' },
+{ oid => '8103',
+  proname => 'range_overright_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_overright_multirange' },
+{ oid => '8104',
+  proname => 'multirange_overright_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_overright_range' },
+{ oid => '8105',
+  proname => 'multirange_overright_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_overright_multirange' },
+{ oid => '8114',
+  proname => 'multirange_union', prorettype => 'anymultirange',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_union' },
+{ oid => '8120',
+  proname => 'multirange_minus', prorettype => 'anymultirange',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_minus' },
+{ oid => '8126',
+  proname => 'multirange_intersect', prorettype => 'anymultirange',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_intersect' },
+{ oid => '8022', descr => 'less-equal-greater',
+  proname => 'multirange_cmp', prorettype => 'int4',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_cmp' },
+{ oid => '8023',
+  proname => 'multirange_lt', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_lt' },
+{ oid => '8024',
+  proname => 'multirange_le', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_le' },
+{ oid => '8025',
+  proname => 'multirange_ge', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_ge' },
+{ oid => '8026',
+  proname => 'multirange_gt', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_gt' },
+{ oid => '8038', descr => 'hash a multirange',
+  proname => 'hash_multirange', prorettype => 'int4', proargtypes => 'anymultirange',
+  prosrc => 'hash_multirange' },
+{ oid => '8039', descr => 'hash a multirange',
+  proname => 'hash_multirange_extended', prorettype => 'int8',
+  proargtypes => 'anymultirange int8', prosrc => 'hash_multirange_extended' },
+
+{ oid => '8045', descr => 'int4multirange constructor',
+  proname => 'int4multirange', proisstrict => 'f',
+  prorettype => 'int4multirange', proargtypes => '',
+  prosrc => 'multirange_constructor0' },
+{ oid => '8146', descr => 'int4multirange constructor',
+  proname => 'int4multirange', proisstrict => 't',
+  prorettype => 'int4multirange', proargtypes => 'int4range',
+  prosrc => 'multirange_constructor1' },
+{ oid => '8046', descr => 'int4multirange constructor',
+  proname => 'int4multirange', provariadic => 'int4range', proisstrict => 'f',
+  prorettype => 'int4multirange', proargtypes => '_int4range',
+  proallargtypes => '{_int4range}', proargmodes => '{v}',
+  prosrc => 'multirange_constructor2' },
+{ oid => '8047', descr => 'nummultirange constructor',
+  proname => 'nummultirange', proisstrict => 'f',
+  prorettype => 'nummultirange', proargtypes => '',
+  prosrc => 'multirange_constructor0' },
+{ oid => '8147', descr => 'nummultirange constructor',
+  proname => 'nummultirange', proisstrict => 't',
+  prorettype => 'nummultirange', proargtypes => 'numrange',
+  prosrc => 'multirange_constructor1' },
+{ oid => '8048', descr => 'nummultirange constructor',
+  proname => 'nummultirange', provariadic => 'numrange', proisstrict => 'f',
+  prorettype => 'nummultirange', proargtypes => '_numrange',
+  proallargtypes => '{_numrange}', proargmodes => '{v}',
+  prosrc => 'multirange_constructor2' },
+{ oid => '8049', descr => 'tsmultirange constructor',
+  proname => 'tsmultirange', proisstrict => 'f',
+  prorettype => 'tsmultirange', proargtypes => '',
+  prosrc => 'multirange_constructor0' },
+{ oid => '8148', descr => 'tsmultirange constructor',
+  proname => 'tsmultirange', proisstrict => 't',
+  prorettype => 'tsmultirange', proargtypes => 'tsrange',
+  prosrc => 'multirange_constructor1' },
+{ oid => '8050', descr => 'tsmultirange constructor',
+  proname => 'tsmultirange', provariadic => 'tsrange', proisstrict => 'f',
+  prorettype => 'tsmultirange', proargtypes => '_tsrange',
+  proallargtypes => '{_tsrange}', proargmodes => '{v}',
+  prosrc => 'multirange_constructor2' },
+{ oid => '8051', descr => 'tstzmultirange constructor',
+  proname => 'tstzmultirange', proisstrict => 'f',
+  prorettype => 'tstzmultirange', proargtypes => '',
+  prosrc => 'multirange_constructor0' },
+{ oid => '8149', descr => 'tstzmultirange constructor',
+  proname => 'tstzmultirange', proisstrict => 't',
+  prorettype => 'tstzmultirange', proargtypes => 'tstzrange',
+  prosrc => 'multirange_constructor1' },
+{ oid => '8052', descr => 'tstzmultirange constructor',
+  proname => 'tstzmultirange', provariadic => 'tstzrange', proisstrict => 'f',
+  prorettype => 'tstzmultirange', proargtypes => '_tstzrange',
+  proallargtypes => '{_tstzrange}', proargmodes => '{v}',
+  prosrc => 'multirange_constructor2' },
+{ oid => '8053', descr => 'datemultirange constructor',
+  proname => 'datemultirange', proisstrict => 'f',
+  prorettype => 'datemultirange', proargtypes => '',
+  prosrc => 'multirange_constructor0' },
+{ oid => '8150', descr => 'datemultirange constructor',
+  proname => 'datemultirange', proisstrict => 't',
+  prorettype => 'datemultirange', proargtypes => 'daterange',
+  prosrc => 'multirange_constructor1' },
+{ oid => '8054', descr => 'datemultirange constructor',
+  proname => 'datemultirange', provariadic => 'daterange', proisstrict => 'f',
+  prorettype => 'datemultirange', proargtypes => '_daterange',
+  proallargtypes => '{_daterange}', proargmodes => '{v}',
+  prosrc => 'multirange_constructor2' },
+{ oid => '8055', descr => 'int8multirange constructor',
+  proname => 'int8multirange', proisstrict => 'f',
+  prorettype => 'int8multirange', proargtypes => '',
+  prosrc => 'multirange_constructor0' },
+{ oid => '8151', descr => 'int8multirange constructor',
+  proname => 'int8multirange', proisstrict => 't',
+  prorettype => 'int8multirange', proargtypes => 'int8range',
+  prosrc => 'multirange_constructor1' },
+{ oid => '8056', descr => 'int8multirange constructor',
+  proname => 'int8multirange', provariadic => 'int8range', proisstrict => 'f',
+  prorettype => 'int8multirange', proargtypes => '_int8range',
+  proallargtypes => '{_int8range}', proargmodes => '{v}',
+  prosrc => 'multirange_constructor2' },
+{ oid => '8152', descr => 'anymultirange cast',
+  proname => 'multirange', proisstrict => 't',
+  prorettype => 'anymultirange', proargtypes => 'anyrange',
+  prosrc => 'multirange_constructor1' },
+{ oid => '8130', descr => 'aggregate transition function',
+  proname => 'range_agg_transfn', proisstrict => 'f', prorettype => 'internal',
+  proargtypes => 'internal anyrange', prosrc => 'range_agg_transfn' },
+{ oid => '8131', descr => 'aggregate final function',
+  proname => 'range_agg_finalfn', proisstrict => 'f', prorettype => 'anymultirange',
+  proargtypes => 'internal anyrange', prosrc => 'range_agg_finalfn' },
+{ oid => '8132', descr => 'combine aggregate input into a multirange',
+  proname => 'range_agg', prokind => 'a', proisstrict => 'f',
+  prorettype => 'anymultirange', proargtypes => 'anyrange',
+  prosrc => 'aggregate_dummy' },
+{ oid => '8135', descr => 'range aggregate by intersecting',
+  proname => 'multirange_intersect_agg_transfn', prorettype => 'anymultirange',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_intersect_agg_transfn'},
+{ oid => '8136', descr => 'range aggregate by intersecting',
+  proname => 'range_intersect_agg', prokind => 'a', proisstrict => 'f',
+  prorettype => 'anymultirange', proargtypes => 'anymultirange',
+  prosrc => 'aggregate_dummy'},
+
 # date, time, timestamp constructors
 { oid => '3846', descr => 'construct date',
   proname => 'make_date', prorettype => 'date', proargtypes => 'int4 int4 int4',
@@ -10334,6 +10608,14 @@
   proname => 'binary_upgrade_set_next_array_pg_type_oid', provolatile => 'v',
   proparallel => 'r', prorettype => 'void', proargtypes => 'oid',
   prosrc => 'binary_upgrade_set_next_array_pg_type_oid' },
+{ oid => '8144', descr => 'for use by pg_upgrade',
+  proname => 'binary_upgrade_set_next_multirange_pg_type_oid', provolatile => 'v',
+  proparallel => 'r', prorettype => 'void', proargtypes => 'oid',
+  prosrc => 'binary_upgrade_set_next_multirange_pg_type_oid' },
+{ oid => '8145', descr => 'for use by pg_upgrade',
+  proname => 'binary_upgrade_set_next_multirange_array_pg_type_oid', provolatile => 'v',
+  proparallel => 'r', prorettype => 'void', proargtypes => 'oid',
+  prosrc => 'binary_upgrade_set_next_multirange_array_pg_type_oid' },
 { oid => '3586', descr => 'for use by pg_upgrade',
   proname => 'binary_upgrade_set_next_heap_pg_class_oid', provolatile => 'v',
   proparallel => 'r', prorettype => 'void', proargtypes => 'oid',
diff --git a/src/include/catalog/pg_range.dat b/src/include/catalog/pg_range.dat
index 479754c245..10060255c9 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',
+  rngmultitypid => 'int4multirange' },
 { rngtypid => 'numrange', rngsubtype => 'numeric',
   rngsubopc => 'btree/numeric_ops', rngcanonical => '-',
-  rngsubdiff => 'numrange_subdiff' },
+  rngsubdiff => 'numrange_subdiff', rngmultitypid => 'nummultirange' },
 { rngtypid => 'tsrange', rngsubtype => 'timestamp',
   rngsubopc => 'btree/timestamp_ops', rngcanonical => '-',
-  rngsubdiff => 'tsrange_subdiff' },
+  rngsubdiff => 'tsrange_subdiff', rngmultitypid => 'tsmultirange' },
 { rngtypid => 'tstzrange', rngsubtype => 'timestamptz',
   rngsubopc => 'btree/timestamptz_ops', rngcanonical => '-',
-  rngsubdiff => 'tstzrange_subdiff' },
+  rngsubdiff => 'tstzrange_subdiff', rngmultitypid => 'tstzmultirange' },
 { rngtypid => 'daterange', rngsubtype => 'date', rngsubopc => 'btree/date_ops',
-  rngcanonical => 'daterange_canonical', rngsubdiff => 'daterange_subdiff' },
+  rngcanonical => 'daterange_canonical', rngsubdiff => 'daterange_subdiff',
+  rngmultitypid => 'datemultirange' },
 { rngtypid => 'int8range', rngsubtype => 'int8', rngsubopc => 'btree/int8_ops',
-  rngcanonical => 'int8range_canonical', rngsubdiff => 'int8range_subdiff' },
+  rngcanonical => 'int8range_canonical', rngsubdiff => 'int8range_subdiff',
+  rngmultitypid => 'int8multirange' },
 
 ]
diff --git a/src/include/catalog/pg_range.h b/src/include/catalog/pg_range.h
index 98172bb1b6..60a8fde518 100644
--- a/src/include/catalog/pg_range.h
+++ b/src/include/catalog/pg_range.h
@@ -34,6 +34,9 @@ CATALOG(pg_range,3541,RangeRelationId)
 	/* OID of range's element type (subtype) */
 	Oid			rngsubtype BKI_LOOKUP(pg_type);
 
+	/* OID of the range's multirange type */
+	Oid			rngmultitypid BKI_LOOKUP(pg_type);
+
 	/* collation for this range type, or 0 */
 	Oid			rngcollation BKI_DEFAULT(0);
 
@@ -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 b2cec07416..1f97203440 100644
--- a/src/include/catalog/pg_type.dat
+++ b/src/include/catalog/pg_type.dat
@@ -500,6 +500,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 => 'R', 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 => 'R', 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 => 'R', 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 => 'R', 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 => 'R', 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 => 'R', 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
@@ -629,5 +663,16 @@
   typtype => 'p', typcategory => 'P', typinput => 'anycompatiblerange_in',
   typoutput => 'anycompatiblerange_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' },
+{ oid => '8153',
+  descr => 'pseudo-type representing a multirange over a polymorphic common type',
+  typname => 'anycompatiblemultirange', typlen => '-1', typbyval => 'f',
+  typtype => 'p', typcategory => 'P', typinput => 'anycompatiblemultirange_in',
+  typoutput => 'anycompatiblemultirange_out', typreceive => '-', typsend => '-',
+  typalign => 'd', typstorage => 'x' },
 
 ]
diff --git a/src/include/catalog/pg_type.h b/src/include/catalog/pg_type.h
index 7b37562648..63e7c940a2 100644
--- a/src/include/catalog/pg_type.h
+++ b/src/include/catalog/pg_type.h
@@ -263,6 +263,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 */
 
@@ -304,13 +305,15 @@ typedef FormData_pg_type *Form_pg_type;
 	 (typid) == ANYARRAYOID || \
 	 (typid) == ANYNONARRAYOID || \
 	 (typid) == ANYENUMOID || \
-	 (typid) == ANYRANGEOID)
+	 (typid) == ANYRANGEOID || \
+	 (typid) == ANYMULTIRANGEOID)
 
 #define IsPolymorphicTypeFamily2(typid)  \
 	((typid) == ANYCOMPATIBLEOID || \
 	 (typid) == ANYCOMPATIBLEARRAYOID || \
 	 (typid) == ANYCOMPATIBLENONARRAYOID || \
-	 (typid) == ANYCOMPATIBLERANGEOID)
+	 (typid) == ANYCOMPATIBLERANGEOID || \
+	 (typid) == ANYCOMPATIBLEMULTIRANGEOID)
 
 #endif							/* EXPOSE_TO_CLIENT_CODE */
 
@@ -369,4 +372,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/commands/typecmds.h b/src/include/commands/typecmds.h
index 23130895af..989914dfe1 100644
--- a/src/include/commands/typecmds.h
+++ b/src/include/commands/typecmds.h
@@ -29,6 +29,8 @@ extern ObjectAddress DefineRange(CreateRangeStmt *stmt);
 extern ObjectAddress AlterEnum(AlterEnumStmt *stmt);
 extern ObjectAddress DefineCompositeType(RangeVar *typevar, List *coldeflist);
 extern Oid	AssignTypeArrayOid(void);
+extern Oid	AssignTypeMultirangeOid(void);
+extern Oid	AssignTypeMultirangeArrayOid(void);
 
 extern ObjectAddress AlterDomainDefault(List *names, Node *defaultRaw);
 extern ObjectAddress AlterDomainNotNull(List *names, bool notNull);
diff --git a/src/include/utils/lsyscache.h b/src/include/utils/lsyscache.h
index fecfe1f4f6..ed84908d72 100644
--- a/src/include/utils/lsyscache.h
+++ b/src/include/utils/lsyscache.h
@@ -157,6 +157,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);
@@ -183,6 +184,8 @@ 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_collation(Oid rangeOid);
+extern Oid	get_range_multirange(Oid rangeOid);
+extern Oid	get_multirange_range(Oid multirangeOid);
 extern Oid	get_index_column_opclass(Oid index_oid, int attno);
 extern bool get_index_isreplident(Oid index_oid);
 extern bool get_index_isvalid(Oid index_oid);
diff --git a/src/include/utils/multirangetypes.h b/src/include/utils/multirangetypes.h
new file mode 100644
index 0000000000..d39a3d1e06
--- /dev/null
+++ b/src/include/utils/multirangetypes.h
@@ -0,0 +1,144 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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"
+#include "utils/expandeddatum.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
+{
+	int32		vl_len_;		/* varlena header (do not touch directly!) */
+	Oid			multirangetypid;	/* multirange type's own OID */
+	uint32		rangeCount;		/* the number of ranges */
+
+	/*
+	 * Following the count are the range objects themselves, as ShortRangeType
+	 * structs. 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;
+
+/*
+ * Since each RangeType includes its type oid, it seems wasteful to store them
+ * in multiranges like that on-disk. Instead we store ShortRangeType structs
+ * that have everything but the type varlena header and oid. But we do want the
+ * type oid in memory, so that we can call ordinary range functions. We use the
+ * EXPANDED TOAST mechansim to convert between them.
+ */
+typedef struct
+{
+	int32		vl_len_;		/* varlena header */
+	char		flags;			/* range flags */
+	char		_padding;		/* Bounds must be aligned */
+	/* Following the header are zero to two bound values. */
+} ShortRangeType;
+
+#define EMR_MAGIC 689375834		/* ID for debugging crosschecks */
+
+typedef struct ExpandedMultirangeHeader
+{
+	/* Standard header for expanded objects */
+	ExpandedObjectHeader hdr;
+
+	/* Magic value identifying an expanded array (for debugging only) */
+	int			emr_magic;
+
+	Oid			multirangetypid;	/* multirange type's own OID */
+	uint32		rangeCount;			/* the number of ranges */
+	RangeType   **ranges;			/* array of ranges */
+
+	/*
+	 * flat_size is the current space requirement for the flat equivalent of
+	 * the expanded multirange, if known; otherwise it's 0.  We store this to make
+	 * consecutive calls of get_flat_size cheap.
+	 */
+	Size		flat_size;
+} ExpandedMultirangeHeader;
+
+/* 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);
+extern bool multirange_contains_elem_internal(TypeCacheEntry *typcache, MultirangeType *mr,
+											  Datum elem);
+extern bool multirange_contains_range_internal(TypeCacheEntry *typcache, MultirangeType *mr,
+											   RangeType *r);
+extern bool multirange_contains_multirange_internal(TypeCacheEntry *typcache,
+													MultirangeType *mr1,
+													MultirangeType *mr2);
+extern bool range_overlaps_multirange_internal(TypeCacheEntry *typcache, RangeType *r,
+											   MultirangeType *mr);
+extern bool multirange_overlaps_multirange_internal(TypeCacheEntry *typcache,
+													MultirangeType *mr1,
+													MultirangeType *mr2);
+extern bool range_before_multirange_internal(TypeCacheEntry *typcache, RangeType *r,
+											 MultirangeType *mr);
+extern bool range_after_multirange_internal(TypeCacheEntry *typcache, RangeType *r,
+											MultirangeType *mr);
+extern bool multirange_before_multirange_internal(TypeCacheEntry *typcache,
+												  MultirangeType *mr1,
+												  MultirangeType *mr2);
+extern MultirangeType *multirange_minus_internal(Oid mltrngtypoid,
+												 TypeCacheEntry *rangetyp,
+												 int32 range_count1,
+												 RangeType **ranges1,
+												 int32 range_count2,
+												 RangeType **ranges2);
+extern MultirangeType *multirange_intersect_internal(Oid mltrngtypoid,
+													 TypeCacheEntry *rangetyp,
+													 int32 range_count1,
+													 RangeType **ranges1,
+													 int32 range_count2,
+													 RangeType **ranges2);
+
+/* assorted support functions */
+extern TypeCacheEntry *multirange_get_typcache(FunctionCallInfo fcinfo,
+											   Oid mltrngtypid);
+extern void multirange_deserialize(TypeCacheEntry *rangetyp,
+								   const 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);
+
+extern Datum expand_multirange(Datum multirangedatum, MemoryContext parentcontext, TypeCacheEntry *rangetyp);
+
+#endif							/* MULTIRANGETYPES_H */
diff --git a/src/include/utils/rangetypes.h b/src/include/utils/rangetypes.h
index b77c41cf1b..139e19c7d6 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)
 
@@ -92,13 +94,21 @@ typedef struct
  * prototypes for functions defined in rangetypes.c
  */
 
-extern bool range_contains_elem_internal(TypeCacheEntry *typcache, const RangeType *r, Datum val);
-
 /* internal versions of the above */
+extern int	range_cmp_internal(TypeCacheEntry *typcache, const RangeType *r1,
+							   const RangeType *r2);
+extern uint32 hash_range_internal(TypeCacheEntry *typcache, const RangeType *r);
+extern uint64 hash_range_extended_internal(TypeCacheEntry *typcache, const RangeType *r,
+										   Datum seed);
+extern Datum range_lower_internal(TypeCacheEntry *typcache, const RangeType *r1,
+								  bool *isnull);
+extern Datum range_upper_internal(TypeCacheEntry *typcache, const RangeType *r1,
+								  bool *isnull);
 extern bool range_eq_internal(TypeCacheEntry *typcache, const RangeType *r1,
 							  const RangeType *r2);
 extern bool range_ne_internal(TypeCacheEntry *typcache, const RangeType *r1,
 							  const RangeType *r2);
+extern bool range_contains_elem_internal(TypeCacheEntry *typcache, const RangeType *r, Datum val);
 extern bool range_contains_internal(TypeCacheEntry *typcache, const RangeType *r1,
 									const RangeType *r2);
 extern bool range_contained_by_internal(TypeCacheEntry *typcache, const RangeType *r1,
@@ -115,8 +125,18 @@ extern bool range_overleft_internal(TypeCacheEntry *typcache, const RangeType *r
 									const RangeType *r2);
 extern bool range_overright_internal(TypeCacheEntry *typcache, const RangeType *r1,
 									 const RangeType *r2);
+extern RangeType *range_union_internal(TypeCacheEntry *typcache, RangeType *r1,
+									   RangeType *r2, bool strict);
+extern RangeType *range_minus_internal(TypeCacheEntry *typcache, RangeType *r1,
+									   RangeType *r2);
+extern RangeType *range_intersect_internal(TypeCacheEntry *typcache, const RangeType *r1,
+										   const RangeType *r2);
 
 /* assorted support functions */
+extern Size datum_compute_size(Size sz, Datum datum, bool typbyval,
+							   char typalign, int16 typlen, char typstorage);
+extern Pointer datum_write(Pointer ptr, Datum datum, bool typbyval,
+						   char typalign, int16 typlen, char typstorage);
 extern TypeCacheEntry *range_get_typcache(FunctionCallInfo fcinfo,
 										  Oid rngtypid);
 extern RangeType *range_serialize(TypeCacheEntry *typcache, RangeBound *lower,
@@ -125,6 +145,7 @@ extern void range_deserialize(TypeCacheEntry *typcache, const RangeType *range,
 							  RangeBound *lower, RangeBound *upper,
 							  bool *empty);
 extern char range_get_flags(const RangeType *range);
+extern bool range_has_flag(const RangeType *range, char flag);
 extern void range_set_contain_empty(RangeType *range);
 extern RangeType *make_range(TypeCacheEntry *typcache, RangeBound *lower,
 							 RangeBound *upper, bool empty);
@@ -132,8 +153,12 @@ extern int	range_cmp_bounds(TypeCacheEntry *typcache, const RangeBound *b1,
 							 const RangeBound *b2);
 extern int	range_cmp_bound_values(TypeCacheEntry *typcache, const RangeBound *b1,
 								   const RangeBound *b2);
+extern int	range_compare(const void *key1, const void *key2, void *arg);
 extern bool bounds_adjacent(TypeCacheEntry *typcache, RangeBound bound1,
 							RangeBound bound2);
 extern RangeType *make_empty_range(TypeCacheEntry *typcache);
+extern bool range_split_internal(TypeCacheEntry *typcache, const RangeType *r1,
+								 const RangeType *r2, RangeType **output1,
+								 RangeType **output2);
 
 #endif							/* RANGETYPES_H */
diff --git a/src/include/utils/selfuncs.h b/src/include/utils/selfuncs.h
index 7ac4a06391..201a5f6a1c 100644
--- a/src/include/utils/selfuncs.h
+++ b/src/include/utils/selfuncs.h
@@ -39,6 +39,9 @@
 /* default selectivity estimate for range inequalities "A > b AND A < c" */
 #define DEFAULT_RANGE_INEQ_SEL	0.005
 
+/* default selectivity estimate for multirange inequalities "A > b AND A < c" */
+#define DEFAULT_MULTIRANGE_INEQ_SEL	0.005
+
 /* default selectivity estimate for pattern-match operators such as LIKE */
 #define DEFAULT_MATCH_SEL	0.005
 
diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h
index f27b73d76d..e8f393520a 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -79,6 +79,7 @@ enum SysCacheIdentifier
 	PUBLICATIONOID,
 	PUBLICATIONREL,
 	PUBLICATIONRELMAP,
+	RANGEMULTIRANGE,
 	RANGETYPE,
 	RELNAMENSP,
 	RELOID,
diff --git a/src/include/utils/typcache.h b/src/include/utils/typcache.h
index cdd20e56d7..33f332a141 100644
--- a/src/include/utils/typcache.h
+++ b/src/include/utils/typcache.h
@@ -100,6 +100,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;	/* multirange's range underlying type */
+
 	/*
 	 * Domain's base type and typmod if it's a domain type.  Zeroes if not
 	 * domain, or if information hasn't been requested.
@@ -143,6 +148,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 e7f4a5f291..978d25620b 100644
--- a/src/pl/plpgsql/src/pl_comp.c
+++ b/src/pl/plpgsql/src/pl_comp.c
@@ -513,6 +513,8 @@ do_compile(FunctionCallInfo fcinfo,
 					else if (rettypeid == ANYRANGEOID ||
 							 rettypeid == ANYCOMPATIBLERANGEOID)
 						rettypeid = INT4RANGEOID;
+					else if (rettypeid == ANYMULTIRANGEOID)
+						rettypeid = INT4MULTIRANGEOID;
 					else		/* ANYELEMENT or ANYNONARRAY or ANYCOMPATIBLE */
 						rettypeid = INT4OID;
 					/* XXX what could we use for ANYENUM? */
@@ -2107,6 +2109,7 @@ build_datatype(HeapTuple typeTup, int32 typmod,
 		case TYPTYPE_BASE:
 		case TYPTYPE_ENUM:
 		case TYPTYPE_RANGE:
+		case TYPTYPE_MULTIRANGE:
 			typ->ttype = PLPGSQL_TTYPE_SCALAR;
 			break;
 		case TYPTYPE_COMPOSITE:
@@ -2525,6 +2528,9 @@ plpgsql_resolve_polymorphic_argtypes(int numargs,
 				case ANYCOMPATIBLERANGEOID:
 					argtypes[i] = INT4RANGEOID;
 					break;
+				case ANYMULTIRANGEOID:
+					argtypes[i] = INT4MULTIRANGEOID;
+					break;
 				default:
 					break;
 			}
diff --git a/src/test/regress/expected/dependency.out b/src/test/regress/expected/dependency.out
index b4a11b8aa9..8232795148 100644
--- a/src/test/regress/expected/dependency.out
+++ b/src/test/regress/expected/dependency.out
@@ -140,6 +140,7 @@ owner of sequence deptest_a_seq
 owner of table deptest
 owner of function deptest_func()
 owner of type deptest_enum
+owner of type deptest_multirange
 owner of type deptest_range
 owner of table deptest2
 owner of sequence ss1
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..e72f99863f
--- /dev/null
+++ b/src/test/regress/expected/multirangetypes.out
@@ -0,0 +1,2437 @@
+-- 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)
+
+--
+-- test casts, both a built-in range type and a user-defined one:
+--
+select 'empty'::int4range::int4multirange;
+ int4multirange 
+----------------
+ {}
+(1 row)
+
+select int4range(1, 3)::int4multirange;
+ int4range 
+-----------
+ {[1,3)}
+(1 row)
+
+select int4range(1, null)::int4multirange;
+ int4range 
+-----------
+ {[1,)}
+(1 row)
+
+select int4range(null, null)::int4multirange;
+ int4range 
+-----------
+ {(,)}
+(1 row)
+
+select 'empty'::textrange::textmultirange;
+ textmultirange 
+----------------
+ {}
+(1 row)
+
+select textrange('a', 'c')::textmultirange;
+ textrange 
+-----------
+ {[a,c)}
+(1 row)
+
+select textrange('a', null)::textmultirange;
+ textrange 
+-----------
+ {[a,)}
+(1 row)
+
+select textrange(null, null)::textmultirange;
+ textrange 
+-----------
+ {(,)}
+(1 row)
+
+--
+-- create some test data and test the operators
+--
+CREATE TABLE nummultirange_test (nmr NUMMULTIRANGE);
+CREATE INDEX nummultirange_test_btree ON nummultirange_test(nmr);
+INSERT INTO nummultirange_test VALUES('{}');
+INSERT INTO nummultirange_test VALUES('{[,)}');
+INSERT INTO nummultirange_test VALUES('{[3,]}');
+INSERT INTO nummultirange_test VALUES('{[,), [3,]}');
+INSERT INTO nummultirange_test VALUES('{[, 5)}');
+INSERT INTO nummultirange_test VALUES(nummultirange());
+INSERT INTO nummultirange_test VALUES(nummultirange(variadic '{}'::numrange[]));
+INSERT INTO nummultirange_test VALUES(nummultirange(numrange(1.1, 2.2)));
+INSERT INTO nummultirange_test VALUES('{empty}');
+INSERT INTO nummultirange_test VALUES(nummultirange(numrange(1.7, 1.7, '[]'), numrange(1.7, 1.9)));
+INSERT INTO nummultirange_test VALUES(nummultirange(numrange(1.7, 1.7, '[]'), numrange(1.9, 2.1)));
+SELECT nmr, isempty(nmr), lower(nmr), upper(nmr) FROM nummultirange_test ORDER BY nmr;
+          nmr          | isempty | lower | upper 
+-----------------------+---------+-------+-------
+ {}                    | t       |       |      
+ {}                    | t       |       |      
+ {}                    | t       |       |      
+ {}                    | t       |       |      
+ {(,5)}                | f       |       |     5
+ {(,)}                 | f       |       |      
+ {(,)}                 | f       |       |      
+ {[1.1,2.2)}           | f       |   1.1 |   2.2
+ {[1.7,1.7],[1.9,2.1)} | f       |   1.7 |   2.1
+ {[1.7,1.9)}           | f       |   1.7 |   1.9
+ {[3,)}                | f       |     3 |      
+(11 rows)
+
+SELECT nmr, lower_inc(nmr), lower_inf(nmr), upper_inc(nmr), upper_inf(nmr) FROM nummultirange_test ORDER BY nmr;
+          nmr          | lower_inc | lower_inf | upper_inc | upper_inf 
+-----------------------+-----------+-----------+-----------+-----------
+ {}                    | f         | f         | f         | f
+ {}                    | f         | f         | f         | f
+ {}                    | f         | f         | f         | f
+ {}                    | f         | f         | f         | f
+ {(,5)}                | f         | t         | f         | f
+ {(,)}                 | f         | t         | f         | t
+ {(,)}                 | f         | t         | f         | t
+ {[1.1,2.2)}           | t         | f         | f         | f
+ {[1.7,1.7],[1.9,2.1)} | t         | f         | f         | f
+ {[1.7,1.9)}           | t         | f         | f         | f
+ {[3,)}                | t         | f         | f         | t
+(11 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr = '{}';
+ nmr 
+-----
+ {}
+ {}
+ {}
+ {}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr = '{(,5)}';
+  nmr   
+--------
+ {(,5)}
+(1 row)
+
+SELECT * FROM nummultirange_test WHERE nmr = '{[3,)}';
+  nmr   
+--------
+ {[3,)}
+(1 row)
+
+SELECT * FROM nummultirange_test WHERE nmr = '{[1.7,1.7]}';
+ nmr 
+-----
+(0 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr = '{[1.7,1.7],[1.9,2.1)}';
+          nmr          
+-----------------------
+ {[1.7,1.7],[1.9,2.1)}
+(1 row)
+
+SELECT * FROM nummultirange_test WHERE nmr < '{}';
+ nmr 
+-----
+(0 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr < '{[-1000.0, -1000.0]}';
+  nmr   
+--------
+ {}
+ {(,)}
+ {(,)}
+ {(,5)}
+ {}
+ {}
+ {}
+(7 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr < '{[0.0, 1.0]}';
+  nmr   
+--------
+ {}
+ {(,)}
+ {(,)}
+ {(,5)}
+ {}
+ {}
+ {}
+(7 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr < '{[1000.0, 1001.0]}';
+          nmr          
+-----------------------
+ {}
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+ {}
+ {}
+ {[1.1,2.2)}
+ {}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(11 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr <= '{}';
+ nmr 
+-----
+ {}
+ {}
+ {}
+ {}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr <= '{[3,)}';
+          nmr          
+-----------------------
+ {}
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+ {}
+ {}
+ {[1.1,2.2)}
+ {}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(11 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr >= '{}';
+          nmr          
+-----------------------
+ {}
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+ {}
+ {}
+ {[1.1,2.2)}
+ {}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(11 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr >= '{[3,)}';
+  nmr   
+--------
+ {[3,)}
+(1 row)
+
+SELECT * FROM nummultirange_test WHERE nmr > '{}';
+          nmr          
+-----------------------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+ {[1.1,2.2)}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(7 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr > '{[-1000.0, -1000.0]}';
+          nmr          
+-----------------------
+ {[3,)}
+ {[1.1,2.2)}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr > '{[0.0, 1.0]}';
+          nmr          
+-----------------------
+ {[3,)}
+ {[1.1,2.2)}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr > '{[1000.0, 1001.0]}';
+ nmr 
+-----
+(0 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr <> '{}';
+          nmr          
+-----------------------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+ {[1.1,2.2)}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(7 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr <> '{(,5)}';
+          nmr          
+-----------------------
+ {}
+ {(,)}
+ {[3,)}
+ {(,)}
+ {}
+ {}
+ {[1.1,2.2)}
+ {}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(10 rows)
+
+select nummultirange(numrange(2.0, 1.0));
+ERROR:  range lower bound must be less than or equal to range upper bound
+select nummultirange(numrange(5.0, 6.0), numrange(1.0, 2.0));
+     nummultirange     
+-----------------------
+ {[1.0,2.0),[5.0,6.0)}
+(1 row)
+
+analyze nummultirange_test;
+-- overlaps
+SELECT * FROM nummultirange_test WHERE range_overlaps_multirange(numrange(4.0, 4.2), nmr);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE numrange(4.0, 4.2) && nmr;
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE multirange_overlaps_range(nmr, numrange(4.0, 4.2));
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr && numrange(4.0, 4.2);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE multirange_overlaps_multirange(nmr, nummultirange(numrange(4.0, 4.2), numrange(6.0, 7.0)));
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr && nummultirange(numrange(4.0, 4.2), numrange(6.0, 7.0));
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr && nummultirange(numrange(6.0, 7.0));
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+(3 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr && nummultirange(numrange(6.0, 7.0), numrange(8.0, 9.0));
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+(3 rows)
+
+-- mr contains x
+SELECT * FROM nummultirange_test WHERE multirange_contains_elem(nmr, 4.0);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr @> 4.0;
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE multirange_contains_range(nmr, numrange(4.0, 4.2));
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr @> numrange(4.0, 4.2);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE multirange_contains_multirange(nmr, '{[4.0,4.2), [6.0, 8.0)}');
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+(3 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr @> '{[4.0,4.2), [6.0, 8.0)}'::nummultirange;
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+(3 rows)
+
+-- x is contained by mr
+SELECT * FROM nummultirange_test WHERE elem_contained_by_multirange(4.0, nmr);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE 4.0 <@ nmr;
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE range_contained_by_multirange(numrange(4.0, 4.2), nmr);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE numrange(4.0, 4.2) <@ nmr;
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE multirange_contained_by_multirange('{[4.0,4.2), [6.0, 8.0)}', nmr);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+(3 rows)
+
+SELECT * FROM nummultirange_test WHERE '{[4.0,4.2), [6.0, 8.0)}'::nummultirange <@ nmr;
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+(3 rows)
+
+-- overlaps
+SELECT 'empty'::numrange && nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT 'empty'::numrange && nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() && 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) && 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() && nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() && nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) && nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) && nummultirange(numrange(1,2), numrange(7,8));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(7,8)) && nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) && nummultirange(numrange(1,2), numrange(3.5,8));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(3.5,8)) && numrange(3,4);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(3.5,8)) && nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+-- contains
+SELECT nummultirange() @> nummultirange();
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange() @> 'empty'::numrange;
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(null,null)) @> numrange(1,2);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(null,null)) @> numrange(null,2);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(null,null)) @> numrange(2,null);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(null,5)) @> numrange(null,3);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(null,5)) @> numrange(null,8);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(5,null)) @> numrange(8,null);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(5,null)) @> numrange(3,null);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,5)) @> numrange(8,9);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,5)) @> numrange(3,9);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,5)) @> numrange(1,4);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,5)) @> numrange(1,5);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(-4,-2), numrange(1,5)) @> numrange(1,5);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,5), numrange(8,9)) @> numrange(1,5);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,5), numrange(8,9)) @> numrange(6,7);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,5), numrange(6,9)) @> numrange(6,7);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[1,5)}'::nummultirange @> '{[1,5)}';
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[-4,-2), [1,5)}'::nummultirange @> '{[1,5)}';
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[1,5), [8,9)}'::nummultirange @> '{[1,5)}';
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[1,5), [8,9)}'::nummultirange @> '{[6,7)}';
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT '{[1,5), [6,9)}'::nummultirange @> '{[6,7)}';
+ ?column? 
+----------
+ t
+(1 row)
+
+-- is contained by
+SELECT nummultirange() <@ nummultirange();
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT 'empty'::numrange <@ nummultirange();
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,2) <@ nummultirange(numrange(null,null));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(null,2) <@ nummultirange(numrange(null,null));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(2,null) <@ nummultirange(numrange(null,null));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(null,3) <@ nummultirange(numrange(null,5));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(null,8) <@ nummultirange(numrange(null,5));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(8,null) <@ nummultirange(numrange(5,null));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(3,null) <@ nummultirange(numrange(5,null));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(8,9) <@ nummultirange(numrange(1,5));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(3,9) <@ nummultirange(numrange(1,5));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(1,4) <@ nummultirange(numrange(1,5));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,5) <@ nummultirange(numrange(1,5));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,5) <@ nummultirange(numrange(-4,-2), numrange(1,5));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,5) <@ nummultirange(numrange(1,5), numrange(8,9));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(6,7) <@ nummultirange(numrange(1,5), numrange(8,9));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(6,7) <@ nummultirange(numrange(1,5), numrange(6,9));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[1,5)}' <@ '{[1,5)}'::nummultirange;
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[1,5)}' <@ '{[-4,-2), [1,5)}'::nummultirange;
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[1,5)}' <@ '{[1,5), [8,9)}'::nummultirange;
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[6,7)}' <@ '{[1,5), [8,9)}'::nummultirange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT '{[6,7)}' <@ '{[1,5), [6,9)}'::nummultirange;
+ ?column? 
+----------
+ t
+(1 row)
+
+-- overleft
+SELECT 'empty'::numrange &< nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT 'empty'::numrange &< nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() &< 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) &< 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() &< nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) &< nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() &< nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(6,7) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(1,2) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,4) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,6) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(3.5,6) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(6,7)) &< numrange(3,4);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) &< numrange(3,4);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,4)) &< numrange(3,4);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,6)) &< numrange(3,4);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3.5,6)) &< numrange(3,4);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(6,7)) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,4)) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,6)) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3.5,6)) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+-- overright
+SELECT nummultirange() &> 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) &> 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT 'empty'::numrange &> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT 'empty'::numrange &> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() &> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() &> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) &> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> numrange(6,7);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> numrange(1,2);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> numrange(1,4);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> numrange(1,6);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> numrange(3.5,6);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(3,4) &> nummultirange(numrange(6,7));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(3,4) &> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(3,4) &> nummultirange(numrange(1,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(3,4) &> nummultirange(numrange(1,6));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(3,4) &> nummultirange(numrange(3.5,6));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(6,7));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(1,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(1,6));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(3.5,6));
+ ?column? 
+----------
+ f
+(1 row)
+
+-- meets
+SELECT 'empty'::numrange -|- nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT 'empty'::numrange -|- nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() -|- 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() -|- nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() -|- nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(1,2) -|- nummultirange(numrange(2,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,2) -|- nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- numrange(2,4);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- numrange(3,4);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- nummultirange(numrange(2,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(5,6)) -|- nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(5,6)) -|- nummultirange(numrange(6,7));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(5,6)) -|- nummultirange(numrange(8,9));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- nummultirange(numrange(2,4), numrange(6,7));
+ ?column? 
+----------
+ t
+(1 row)
+
+-- strictly left
+select 'empty'::numrange << nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(1,2) << nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(1,2) << nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+select numrange(1,2) << nummultirange(numrange(0,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(1,2) << nummultirange(numrange(0,4), numrange(7,8));
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange() << 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange() << numrange(1,2);
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(3,4)) << numrange(3,6);
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(0,2)) << numrange(3,6);
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange(numrange(0,2), numrange(7,8)) << numrange(3,6);
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(-4,-2), numrange(0,2)) << numrange(3,6);
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange() << nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange() << nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(1,2)) << nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(1,2)) << nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(1,2)) << nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange(numrange(1,2)) << nummultirange(numrange(3,4), numrange(7,8));
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange(numrange(1,2), numrange(4,5)) << nummultirange(numrange(3,4), numrange(7,8));
+ ?column? 
+----------
+ f
+(1 row)
+
+-- strictly right
+select nummultirange() >> 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange() >> numrange(1,2);
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(3,4)) >> numrange(1,2);
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange(numrange(0,4)) >> numrange(1,2);
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(0,4), numrange(7,8)) >> numrange(1,2);
+ ?column? 
+----------
+ f
+(1 row)
+
+select 'empty'::numrange >> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(1,2) >> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(3,6) >> nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(3,6) >> nummultirange(numrange(0,2));
+ ?column? 
+----------
+ t
+(1 row)
+
+select numrange(3,6) >> nummultirange(numrange(0,2), numrange(7,8));
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(3,6) >> nummultirange(numrange(-4,-2), numrange(0,2));
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange() >> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(1,2)) >> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange() >> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(1,2)) >> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(3,4)) >> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange(numrange(3,4), numrange(7,8)) >> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange(numrange(3,4), numrange(7,8)) >> nummultirange(numrange(1,2), numrange(4,5));
+ ?column? 
+----------
+ f
+(1 row)
+
+-- union
+SELECT nummultirange() + nummultirange();
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange() + nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) + nummultirange();
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) + nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) + nummultirange(numrange(2,4));
+ ?column? 
+----------
+ {[1,4)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) + nummultirange(numrange(3,4));
+   ?column?    
+---------------
+ {[1,2),[3,4)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) + nummultirange(numrange(2,4));
+ ?column? 
+----------
+ {[1,5)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) + nummultirange(numrange(3,4));
+   ?column?    
+---------------
+ {[1,2),[3,5)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) + nummultirange(numrange(0,9));
+ ?column? 
+----------
+ {[0,9)}
+(1 row)
+
+-- merge
+SELECT range_merge(nummultirange());
+ range_merge 
+-------------
+ empty
+(1 row)
+
+SELECT range_merge(nummultirange(numrange(1,2)));
+ range_merge 
+-------------
+ [1,2)
+(1 row)
+
+SELECT range_merge(nummultirange(numrange(1,2), numrange(7,8)));
+ range_merge 
+-------------
+ [1,8)
+(1 row)
+
+-- minus
+SELECT nummultirange() - nummultirange();
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange() - nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) - nummultirange();
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(3,4)) - nummultirange();
+   ?column?    
+---------------
+ {[1,2),[3,4)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) - nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) - nummultirange(numrange(2,4));
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) - nummultirange(numrange(3,4));
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {[2,4)}
+(1 row)
+
+SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(2,3));
+   ?column?    
+---------------
+ {[1,2),[3,4)}
+(1 row)
+
+SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(0,8));
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(0,2));
+ ?column? 
+----------
+ {[2,4)}
+(1 row)
+
+SELECT nummultirange(numrange(1,8)) - nummultirange(numrange(0,2), numrange(3,4));
+   ?column?    
+---------------
+ {[2,3),[4,8)}
+(1 row)
+
+SELECT nummultirange(numrange(1,8)) - nummultirange(numrange(2,3), numrange(5,null));
+   ?column?    
+---------------
+ {[1,2),[3,5)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(-2,0));
+   ?column?    
+---------------
+ {[1,2),[4,5)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(2,4));
+   ?column?    
+---------------
+ {[1,2),[4,5)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(3,5));
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(0,9));
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange(numrange(1,3), numrange(4,5)) - nummultirange(numrange(2,9));
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(8,9));
+   ?column?    
+---------------
+ {[1,2),[4,5)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(-2,0), numrange(8,9));
+   ?column?    
+---------------
+ {[1,2),[4,5)}
+(1 row)
+
+-- intersection
+SELECT nummultirange() * nummultirange();
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange() * nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) * nummultirange();
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT '{[1,3)}'::nummultirange * '{[1,5)}'::nummultirange;
+ ?column? 
+----------
+ {[1,3)}
+(1 row)
+
+SELECT '{[1,3)}'::nummultirange * '{[0,5)}'::nummultirange;
+ ?column? 
+----------
+ {[1,3)}
+(1 row)
+
+SELECT '{[1,3)}'::nummultirange * '{[0,2)}'::nummultirange;
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT '{[1,3)}'::nummultirange * '{[2,5)}'::nummultirange;
+ ?column? 
+----------
+ {[2,3)}
+(1 row)
+
+SELECT '{[1,4)}'::nummultirange * '{[2,3)}'::nummultirange;
+ ?column? 
+----------
+ {[2,3)}
+(1 row)
+
+SELECT '{[1,4)}'::nummultirange * '{[0,2), [3,5)}'::nummultirange;
+   ?column?    
+---------------
+ {[1,2),[3,4)}
+(1 row)
+
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[0,8), [9,12)}'::nummultirange;
+       ?column?       
+----------------------
+ {[1,4),[7,8),[9,10)}
+(1 row)
+
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[9,12)}'::nummultirange;
+ ?column? 
+----------
+ {[9,10)}
+(1 row)
+
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[-5,-4), [5,6), [9,12)}'::nummultirange;
+ ?column? 
+----------
+ {[9,10)}
+(1 row)
+
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[0,2), [3,8), [9,12)}'::nummultirange;
+          ?column?          
+----------------------------
+ {[1,2),[3,4),[7,8),[9,10)}
+(1 row)
+
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[0,2), [3,8), [9,12)}'::nummultirange;
+          ?column?          
+----------------------------
+ {[1,2),[3,4),[7,8),[9,10)}
+(1 row)
+
+--
+-- range_agg function
+--
+create table reservations ( room_id integer not null, booked_during daterange );
+insert into reservations values
+-- 1: has a meets and a gap
+(1, daterange('2018-07-01', '2018-07-07')),
+(1, daterange('2018-07-07', '2018-07-14')),
+(1, daterange('2018-07-20', '2018-07-22')),
+-- 2: just a single row
+(2, daterange('2018-07-01', '2018-07-03')),
+-- 3: one null range
+(3, NULL),
+-- 4: two null ranges
+(4, NULL),
+(4, NULL),
+-- 5: a null range and a non-null range
+(5, NULL),
+(5, daterange('2018-07-01', '2018-07-03')),
+-- 6: has overlap
+(6, daterange('2018-07-01', '2018-07-07')),
+(6, daterange('2018-07-05', '2018-07-10')),
+-- 7: two ranges that meet: no gap or overlap
+(7, daterange('2018-07-01', '2018-07-07')),
+(7, daterange('2018-07-07', '2018-07-14')),
+-- 8: an empty range
+(8, 'empty'::daterange)
+;
+SELECT   room_id, range_agg(booked_during)
+FROM     reservations
+GROUP BY room_id
+ORDER BY room_id;
+ room_id |                     range_agg                     
+---------+---------------------------------------------------
+       1 | {[07-01-2018,07-14-2018),[07-20-2018,07-22-2018)}
+       2 | {[07-01-2018,07-03-2018)}
+       3 | 
+       4 | 
+       5 | {[07-01-2018,07-03-2018)}
+       6 | {[07-01-2018,07-10-2018)}
+       7 | {[07-01-2018,07-14-2018)}
+       8 | {}
+(8 rows)
+
+-- range_agg on a custom range type too
+SELECT  range_agg(r)
+FROM    (VALUES
+          ('[a,c]'::textrange),
+          ('[b,b]'::textrange),
+          ('[c,f]'::textrange),
+          ('[g,h)'::textrange),
+          ('[h,j)'::textrange)
+        ) t(r);
+   range_agg   
+---------------
+ {[a,f],[g,j)}
+(1 row)
+
+select range_intersect_agg(nmr) from nummultirange_test;
+ range_intersect_agg 
+---------------------
+ {}
+(1 row)
+
+select range_intersect_agg(nmr) from nummultirange_test where false;
+ range_intersect_agg 
+---------------------
+ 
+(1 row)
+
+-- test with just one input:
+select range_intersect_agg(nmr) from (values ('{[1,2]}'::nummultirange)) t(nmr);
+ range_intersect_agg 
+---------------------
+ {[1,2]}
+(1 row)
+
+select range_intersect_agg(nmr) from nummultirange_test where nmr @> 4.0;
+ range_intersect_agg 
+---------------------
+ {[3,5)}
+(1 row)
+
+create table nummultirange_test2(nmr nummultirange);
+create index nummultirange_test2_hash_idx on nummultirange_test2 using hash (nmr);
+INSERT INTO nummultirange_test2 VALUES('{[, 5)}');
+INSERT INTO nummultirange_test2 VALUES(nummultirange(numrange(1.1, 2.2)));
+INSERT INTO nummultirange_test2 VALUES(nummultirange(numrange(1.1, 2.2)));
+INSERT INTO nummultirange_test2 VALUES(nummultirange(numrange(1.1, 2.2,'()')));
+INSERT INTO nummultirange_test2 VALUES('{}');
+select * from nummultirange_test2 where nmr = '{}';
+ nmr 
+-----
+ {}
+(1 row)
+
+select * from nummultirange_test2 where nmr = nummultirange(numrange(1.1, 2.2));
+     nmr     
+-------------
+ {[1.1,2.2)}
+ {[1.1,2.2)}
+(2 rows)
+
+select * from nummultirange_test2 where nmr = nummultirange(numrange(1.1, 2.3));
+ nmr 
+-----
+(0 rows)
+
+set enable_nestloop=t;
+set enable_hashjoin=f;
+set enable_mergejoin=f;
+select * from nummultirange_test natural join nummultirange_test2 order by nmr;
+     nmr     
+-------------
+ {}
+ {}
+ {}
+ {}
+ {(,5)}
+ {[1.1,2.2)}
+ {[1.1,2.2)}
+(7 rows)
+
+set enable_nestloop=f;
+set enable_hashjoin=t;
+set enable_mergejoin=f;
+select * from nummultirange_test natural join nummultirange_test2 order by nmr;
+     nmr     
+-------------
+ {}
+ {}
+ {}
+ {}
+ {(,5)}
+ {[1.1,2.2)}
+ {[1.1,2.2)}
+(7 rows)
+
+set enable_nestloop=f;
+set enable_hashjoin=f;
+set enable_mergejoin=t;
+select * from nummultirange_test natural join nummultirange_test2 order by nmr;
+     nmr     
+-------------
+ {}
+ {}
+ {}
+ {}
+ {(,5)}
+ {[1.1,2.2)}
+ {[1.1,2.2)}
+(7 rows)
+
+set enable_nestloop to default;
+set enable_hashjoin to default;
+set enable_mergejoin to default;
+DROP TABLE nummultirange_test2;
+--
+-- Test user-defined multirange of floats
+--
+select '{[123.001, 5.e9)}'::float8multirange @> 888.882::float8;
+ ?column? 
+----------
+ t
+(1 row)
+
+create table float8multirange_test(f8mr float8multirange, i int);
+insert into float8multirange_test values(float8multirange(float8range(-100.00007, '1.111113e9')), 42);
+select * from float8multirange_test;
+           f8mr            | i  
+---------------------------+----
+ {[-100.00007,1111113000)} | 42
+(1 row)
+
+drop table float8multirange_test;
+--
+-- Test multirange types over domains
+--
+create domain mydomain as int4;
+create type mydomainrange as range(subtype=mydomain);
+select '{[4,50)}'::mydomainmultirange @> 7::mydomain;
+ ?column? 
+----------
+ t
+(1 row)
+
+drop domain mydomain cascade;
+NOTICE:  drop cascades to type mydomainrange
+--
+-- Test domains over multirange types
+--
+create domain restrictedmultirange as int4multirange check (upper(value) < 10);
+select '{[4,5)}'::restrictedmultirange @> 7;
+ ?column? 
+----------
+ f
+(1 row)
+
+select '{[4,50)}'::restrictedmultirange @> 7; -- should fail
+ERROR:  value for domain restrictedmultirange violates check constraint "restrictedmultirange_check"
+drop domain restrictedmultirange;
+--
+-- Test multiple multirange types over the same subtype
+--
+create type textrange1 as range(subtype=text, collation="C");
+create type textrange2 as range(subtype=text, collation="C");
+select textmultirange1(textrange2('a','Z'));  -- should fail
+ERROR:  function textmultirange1(textrange2) does not exist
+LINE 1: select textmultirange1(textrange2('a','Z'));
+               ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+select textmultirange1(textrange1('a','Z')) @> 'b'::text;
+ERROR:  range lower bound must be less than or equal to range upper bound
+select textmultirange2(textrange2('a','z')) @> 'b'::text;
+ ?column? 
+----------
+ t
+(1 row)
+
+drop type textrange1;
+drop type textrange2;
+--
+-- Test polymorphic type system
+--
+create function anyarray_anymultirange_func(a anyarray, r anymultirange)
+  returns anyelement as 'select $1[1] + lower($2);' language sql;
+select anyarray_anymultirange_func(ARRAY[1,2], int4multirange(int4range(10,20)));
+ anyarray_anymultirange_func 
+-----------------------------
+                          11
+(1 row)
+
+-- should fail
+select anyarray_anymultirange_func(ARRAY[1,2], nummultirange(numrange(10,20)));
+ERROR:  function anyarray_anymultirange_func(integer[], nummultirange) does not exist
+LINE 1: select anyarray_anymultirange_func(ARRAY[1,2], nummultirange...
+               ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+drop function anyarray_anymultirange_func(anyarray, anymultirange);
+-- should fail
+create function bogus_func(anyelement)
+  returns anymultirange as 'select int4multirange(int4range(1,10))' language sql;
+ERROR:  cannot determine result data type
+DETAIL:  A result of type anymultirange requires at least one input of type anyrange or anymultirange.
+-- should fail
+create function bogus_func(int)
+  returns anymultirange as 'select int4multirange(int4range(1,10))' language sql;
+ERROR:  cannot determine result data type
+DETAIL:  A result of type anymultirange requires at least one input of type anyrange or anymultirange.
+create function range_add_bounds(anymultirange)
+  returns anyelement as 'select lower($1) + upper($1)' language sql;
+select range_add_bounds(int4multirange(int4range(1, 17)));
+ range_add_bounds 
+------------------
+               18
+(1 row)
+
+select range_add_bounds(nummultirange(numrange(1.0001, 123.123)));
+ range_add_bounds 
+------------------
+         124.1231
+(1 row)
+
+create function multirangetypes_sql(q anymultirange, b anyarray, out c anyelement)
+  as $$ select upper($1) + $2[1] $$
+  language sql;
+select multirangetypes_sql(int4multirange(int4range(1,10)), ARRAY[2,20]);
+ multirangetypes_sql 
+---------------------
+                  12
+(1 row)
+
+select multirangetypes_sql(nummultirange(numrange(1,10)), ARRAY[2,20]);  -- match failure
+ERROR:  function multirangetypes_sql(nummultirange, integer[]) does not exist
+LINE 1: select multirangetypes_sql(nummultirange(numrange(1,10)), AR...
+               ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+create function anycompatiblearray_anycompatiblemultirange_func(a anycompatiblearray, mr anycompatiblemultirange)
+  returns anycompatible as 'select $1[1] + lower($2);' language sql;
+select anycompatiblearray_anycompatiblemultirange_func(ARRAY[1,2], multirange(int4range(10,20)));
+ anycompatiblearray_anycompatiblemultirange_func 
+-------------------------------------------------
+                                              11
+(1 row)
+
+select anycompatiblearray_anycompatiblemultirange_func(ARRAY[1,2], multirange(numrange(10,20)));
+ anycompatiblearray_anycompatiblemultirange_func 
+-------------------------------------------------
+                                              11
+(1 row)
+
+-- should fail
+select anycompatiblearray_anycompatiblemultirange_func(ARRAY[1.1,2], multirange(int4range(10,20)));
+ERROR:  function anycompatiblearray_anycompatiblemultirange_func(numeric[], int4multirange) does not exist
+LINE 1: select anycompatiblearray_anycompatiblemultirange_func(ARRAY...
+               ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+drop function anycompatiblearray_anycompatiblemultirange_func(anycompatiblearray, anycompatiblemultirange);
+create function anycompatiblerange_anycompatiblemultirange_func(r anycompatiblerange, mr anycompatiblemultirange)
+  returns anycompatible as 'select lower($1) + lower($2);' language sql;
+select anycompatiblerange_anycompatiblemultirange_func(int4range(1,2), multirange(int4range(10,20)));
+ anycompatiblerange_anycompatiblemultirange_func 
+-------------------------------------------------
+                                              11
+(1 row)
+
+-- should fail
+select anycompatiblerange_anycompatiblemultirange_func(numrange(1,2), multirange(int4range(10,20)));
+ERROR:  function anycompatiblerange_anycompatiblemultirange_func(numrange, int4multirange) does not exist
+LINE 1: select anycompatiblerange_anycompatiblemultirange_func(numra...
+               ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+drop function anycompatiblerange_anycompatiblemultirange_func(anycompatiblerange, anycompatiblemultirange);
+-- should fail
+create function bogus_func(anycompatible)
+  returns anycompatiblerange as 'select int4range(1,10)' language sql;
+ERROR:  cannot determine result data type
+DETAIL:  A result of type anycompatiblerange requires at least one input of type anycompatiblerange or anycompatiblemultirange.
+--
+-- Arrays of multiranges
+--
+select ARRAY[nummultirange(numrange(1.1, 1.2)), nummultirange(numrange(12.3, 155.5))];
+              array               
+----------------------------------
+ {"{[1.1,1.2)}","{[12.3,155.5)}"}
+(1 row)
+
+create table i8mr_array (f1 int, f2 int8multirange[]);
+insert into i8mr_array values (42, array[int8multirange(int8range(1,10)), int8multirange(int8range(2,20))]);
+select * from i8mr_array;
+ f1 |           f2            
+----+-------------------------
+ 42 | {"{[1,10)}","{[2,20)}"}
+(1 row)
+
+drop table i8mr_array;
+--
+-- Multiranges of arrays
+--
+select arraymultirange(arrayrange(ARRAY[1,2], ARRAY[2,1]));
+   arraymultirange   
+---------------------
+ {["{1,2}","{2,1}")}
+(1 row)
+
+select arraymultirange(arrayrange(ARRAY[2,1], ARRAY[1,2]));  -- fail
+ERROR:  range lower bound must be less than or equal to range upper bound
+select array[1,1] <@ arraymultirange(arrayrange(array[1,2], array[2,1]));
+ ?column? 
+----------
+ f
+(1 row)
+
+select array[1,3] <@ arraymultirange(arrayrange(array[1,2], array[2,1]));
+ ?column? 
+----------
+ t
+(1 row)
+
+--
+-- Ranges of composites
+--
+create type two_ints as (a int, b int);
+create type two_ints_range as range (subtype = two_ints);
+-- with force_parallel_mode on, this exercises tqueue.c's range remapping
+select *, row_to_json(upper(t)) as u from
+  (values (two_ints_multirange(two_ints_range(row(1,2), row(3,4)))),
+          (two_ints_multirange(two_ints_range(row(5,6), row(7,8))))) v(t);
+          t          |       u       
+---------------------+---------------
+ {["(1,2)","(3,4)")} | {"a":3,"b":4}
+ {["(5,6)","(7,8)")} | {"a":7,"b":8}
+(2 rows)
+
+drop type two_ints cascade;
+NOTICE:  drop cascades to type two_ints_range
+--
+-- Check behavior when subtype lacks a hash function
+--
+set enable_sort = off;  -- try to make it pick a hash setop implementation
+select '{(2,5)}'::cashmultirange except select '{(5,6)}'::cashmultirange;
+ cashmultirange  
+-----------------
+ {($2.00,$5.00)}
+(1 row)
+
+reset enable_sort;
+--
+-- OUT/INOUT/TABLE functions
+--
+-- infer anymultirange from anymultirange
+create function mr_outparam_succeed(i anymultirange, out r anymultirange, out t text)
+  as $$ select $1, 'foo'::text $$ language sql;
+select * from mr_outparam_succeed(int4multirange(int4range(1,2)));
+    r    |  t  
+---------+-----
+ {[1,2)} | foo
+(1 row)
+
+-- infer anyarray from anymultirange
+create function mr_outparam_succeed2(i anymultirange, out r anyarray, out t text)
+  as $$ select ARRAY[upper($1)], 'foo'::text $$ language sql;
+select * from mr_outparam_succeed2(int4multirange(int4range(1,2)));
+  r  |  t  
+-----+-----
+ {2} | foo
+(1 row)
+
+-- infer anyrange from anymultirange
+create function mr_outparam_succeed3(i anymultirange, out r anyrange, out t text)
+  as $$ select range_merge($1), 'foo'::text $$ language sql;
+select * from mr_outparam_succeed3(int4multirange(int4range(1,2)));
+   r   |  t  
+-------+-----
+ [1,2) | foo
+(1 row)
+
+-- infer anymultirange from anyrange
+create function mr_outparam_succeed4(i anyrange, out r anymultirange, out t text)
+  as $$ select multirange($1), 'foo'::text $$ language sql;
+select * from mr_outparam_succeed4(int4range(1,2));
+    r    |  t  
+---------+-----
+ {[1,2)} | foo
+(1 row)
+
+-- infer anyelement from anymultirange
+create function mr_inoutparam_succeed(out i anyelement, inout r anymultirange)
+  as $$ select upper($1), $1 $$ language sql;
+select * from mr_inoutparam_succeed(int4multirange(int4range(1,2)));
+ i |    r    
+---+---------
+ 2 | {[1,2)}
+(1 row)
+
+-- infer anyelement+anymultirange from anyelement+anymultirange
+create function mr_table_succeed(i anyelement, r anymultirange) returns table(i anyelement, r anymultirange)
+  as $$ select $1, $2 $$ language sql;
+select * from mr_table_succeed(123, int4multirange(int4range(1,11)));
+  i  |    r     
+-----+----------
+ 123 | {[1,11)}
+(1 row)
+
+-- use anymultirange in plpgsql
+create function mr_polymorphic(i anyrange) returns anymultirange
+  as $$ begin return multirange($1); end; $$ language plpgsql;
+select mr_polymorphic(int4range(1, 4));
+ mr_polymorphic 
+----------------
+ {[1,4)}
+(1 row)
+
+-- should fail
+create function mr_outparam_fail(i anyelement, out r anymultirange, out t text)
+  as $$ select '[1,10]', 'foo' $$ language sql;
+ERROR:  cannot determine result data type
+DETAIL:  A result of type anymultirange requires at least one input of type anyrange or anymultirange.
+--should fail
+create function mr_inoutparam_fail(inout i anyelement, out r anymultirange)
+  as $$ select $1, '[1,10]' $$ language sql;
+ERROR:  cannot determine result data type
+DETAIL:  A result of type anymultirange requires at least one input of type anyrange or anymultirange.
+--should fail
+create function mr_table_fail(i anyelement) returns table(i anyelement, r anymultirange)
+  as $$ select $1, '[1,10]' $$ language sql;
+ERROR:  cannot determine result data type
+DETAIL:  A result of type anymultirange requires at least one input of type anyrange or anymultirange.
diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out
index 7825a765cd..a3c5bc42a7 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::regtype, p2.prorettype::regtype
 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        
@@ -208,6 +215,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        
@@ -228,6 +237,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        
@@ -340,7 +351,8 @@ WHERE p1.prorettype IN
      '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     
 ------+----------------
@@ -355,20 +367,25 @@ ORDER BY 2;
  3532 | enum_recv
 (9 rows)
 
--- anyrange is tighter than the rest, can only resolve from anyrange input
+-- anyrange and anymultirange are tighter than the rest, can only resolve
+-- from each other
 SELECT p1.oid, p1.proname
 FROM pg_proc as p1
-WHERE p1.prorettype = 'anyrange'::regtype
+WHERE p1.prorettype IN ('anyrange'::regtype, 'anymultirange'::regtype)
   AND NOT
-     'anyrange'::regtype = ANY (p1.proargtypes)
+    ('anyrange'::regtype = ANY (p1.proargtypes) OR
+      'anymultirange'::regtype = ANY (p1.proargtypes))
 ORDER BY 2;
  oid  |     proname      
 ------+------------------
+ 8002 | anymultirange_in
  3832 | anyrange_in
+ 8004 | multirange_in
+ 8006 | multirange_recv
  3876 | range_gist_union
  3834 | range_in
  3836 | range_recv
-(4 rows)
+(7 rows)
 
 -- similarly for the anycompatible family
 SELECT p1.oid, p1.proname
@@ -2205,13 +2222,14 @@ ORDER BY 1, 2, 3;
                     | float_ops        | float4_ops       | real
                     | float_ops        | float8_ops       | double precision
                     | jsonb_ops        | jsonb_ops        | jsonb
+                    | multirange_ops   | multirange_ops   | anymultirange
                     | numeric_ops      | numeric_ops      | numeric
                     | range_ops        | range_ops        | anyrange
                     | record_image_ops | record_image_ops | record
                     | record_ops       | record_ops       | record
                     | tsquery_ops      | tsquery_ops      | tsquery
                     | tsvector_ops     | tsvector_ops     | tsvector
-(14 rows)
+(15 rows)
 
 -- **************** pg_index ****************
 -- Look for illegal values in pg_index fields.
diff --git a/src/test/regress/expected/plpgsql.out b/src/test/regress/expected/plpgsql.out
index d0a6b630b8..d55006d8c9 100644
--- a/src/test/regress/expected/plpgsql.out
+++ b/src/test/regress/expected/plpgsql.out
@@ -1811,7 +1811,7 @@ begin
   return array[x + 1, x + 2];
 end$$ language plpgsql;
 ERROR:  cannot determine result data type
-DETAIL:  A result of type anyrange requires at least one input of type anyrange.
+DETAIL:  A result of type anyrange requires at least one input of type anyrange or anymultirange.
 create function f1(x anyrange) returns anyarray as $$
 begin
   return array[lower(x), upper(x)];
@@ -1856,7 +1856,7 @@ begin
   return array[x + 1, x + 2];
 end$$ language plpgsql;
 ERROR:  cannot determine result data type
-DETAIL:  A result of type anycompatiblerange requires at least one input of type anycompatiblerange.
+DETAIL:  A result of type anycompatiblerange requires at least one input of type anycompatiblerange or anycompatiblemultirange.
 create function f1(x anycompatiblerange, y anycompatiblearray) returns anycompatiblerange as $$
 begin
   return x;
diff --git a/src/test/regress/expected/polymorphism.out b/src/test/regress/expected/polymorphism.out
index 1ff40764d9..248c869931 100644
--- a/src/test/regress/expected/polymorphism.out
+++ b/src/test/regress/expected/polymorphism.out
@@ -61,7 +61,7 @@ create function polyf(x anyelement) returns anyrange as $$
   select array[x + 1, x + 2]
 $$ language sql;
 ERROR:  cannot determine result data type
-DETAIL:  A result of type anyrange requires at least one input of type anyrange.
+DETAIL:  A result of type anyrange requires at least one input of type anyrange or anymultirange.
 create function polyf(x anyrange) returns anyarray as $$
   select array[lower(x), upper(x)]
 $$ language sql;
@@ -97,12 +97,27 @@ LINE 1: select polyf(int4range(42, 49), 11, 4.5) as fail;
                ^
 HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
 drop function polyf(x anycompatiblerange, y anycompatible, z anycompatible);
+create function polyf(x anycompatiblemultirange, y anycompatible, z anycompatible) returns anycompatiblearray as $$
+  select array[lower(x), upper(x), y, z]
+$$ language sql;
+select polyf(multirange(int4range(42, 49)), 11, 2::smallint) as int, polyf(multirange(float8range(4.5, 7.8)), 7.8, 11::real) as num;
+     int      |       num        
+--------------+------------------
+ {42,49,11,2} | {4.5,7.8,7.8,11}
+(1 row)
+
+select polyf(multirange(int4range(42, 49)), 11, 4.5) as fail;  -- range type doesn't fit
+ERROR:  function polyf(int4multirange, integer, numeric) does not exist
+LINE 1: select polyf(multirange(int4range(42, 49)), 11, 4.5) as fail...
+               ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+drop function polyf(x anycompatiblemultirange, y anycompatible, z anycompatible);
 -- fail, can't infer type:
 create function polyf(x anycompatible) returns anycompatiblerange as $$
   select array[x + 1, x + 2]
 $$ language sql;
 ERROR:  cannot determine result data type
-DETAIL:  A result of type anycompatiblerange requires at least one input of type anycompatiblerange.
+DETAIL:  A result of type anycompatiblerange requires at least one input of type anycompatiblerange or anycompatiblemultirange.
 create function polyf(x anycompatiblerange, y anycompatiblearray) returns anycompatiblerange as $$
   select x
 $$ language sql;
@@ -113,6 +128,22 @@ select polyf(int4range(42, 49), array[11]) as int, polyf(float8range(4.5, 7.8),
 (1 row)
 
 drop function polyf(x anycompatiblerange, y anycompatiblearray);
+-- fail, can't infer type:
+create function polyf(x anycompatible) returns anycompatiblemultirange as $$
+  select array[x + 1, x + 2]
+$$ language sql;
+ERROR:  cannot determine result data type
+DETAIL:  A result of type anycompatiblemultirange requires at least one input of type anycompatiblerange or anycompatiblemultirange.
+create function polyf(x anycompatiblemultirange, y anycompatiblearray) returns anycompatiblemultirange as $$
+  select x
+$$ language sql;
+select polyf(multirange(int4range(42, 49)), array[11]) as int, polyf(multirange(float8range(4.5, 7.8)), array[7]) as num;
+    int    |     num     
+-----------+-------------
+ {[42,49)} | {[4.5,7.8)}
+(1 row)
+
+drop function polyf(x anycompatiblemultirange, y anycompatiblearray);
 create function polyf(a anyelement, b anyarray,
                       c anycompatible, d anycompatible,
                       OUT x anyarray, OUT y anycompatiblearray)
@@ -231,7 +262,7 @@ CREATE AGGREGATE myaggp01a(*) (SFUNC = stfnp, STYPE = int4[],
 CREATE AGGREGATE myaggp02a(*) (SFUNC = stfnp, STYPE = anyarray,
   FINALFUNC = ffp, INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 --     N    P
 -- should CREATE
 CREATE AGGREGATE myaggp03a(*) (SFUNC = stfp, STYPE = int4[],
@@ -243,11 +274,11 @@ CREATE AGGREGATE myaggp03b(*) (SFUNC = stfp, STYPE = int4[],
 CREATE AGGREGATE myaggp04a(*) (SFUNC = stfp, STYPE = anyarray,
   FINALFUNC = ffp, INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 CREATE AGGREGATE myaggp04b(*) (SFUNC = stfp, STYPE = anyarray,
   INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 --    Case2 (R = P) && ((B = P) || (B = N))
 --    -------------------------------------
 --    S    tf1      B    tf2
@@ -302,13 +333,13 @@ ERROR:  function tfp(integer[], anyelement) does not exist
 CREATE AGGREGATE myaggp13a(BASETYPE = int, SFUNC = tfnp, STYPE = anyarray,
   FINALFUNC = ffp, INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 --    P    N        N    P
 -- should ERROR: tf2p(anyarray, int) not matched by tf2p(int[],anyelement)
 CREATE AGGREGATE myaggp14a(BASETYPE = int, SFUNC = tf2p, STYPE = anyarray,
   FINALFUNC = ffp, INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 --    P    N        P    N
 -- should ERROR: tfnp(anyarray, anyelement) not matched by tfnp(int[],int)
 CREATE AGGREGATE myaggp15a(BASETYPE = anyelement, SFUNC = tfnp,
@@ -324,21 +355,21 @@ ERROR:  function tf2p(anyarray, anyelement) does not exist
 CREATE AGGREGATE myaggp17a(BASETYPE = int, SFUNC = tf1p, STYPE = anyarray,
   FINALFUNC = ffp, INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 CREATE AGGREGATE myaggp17b(BASETYPE = int, SFUNC = tf1p, STYPE = anyarray,
   INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 --    P    P        N    P
 -- should ERROR: tfp(anyarray, int) not matched by tfp(anyarray, anyelement)
 CREATE AGGREGATE myaggp18a(BASETYPE = int, SFUNC = tfp, STYPE = anyarray,
   FINALFUNC = ffp, INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 CREATE AGGREGATE myaggp18b(BASETYPE = int, SFUNC = tfp, STYPE = anyarray,
   INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 --    P    P        P    N
 -- should ERROR: tf1p(anyarray, anyelement) not matched by tf1p(anyarray, int)
 CREATE AGGREGATE myaggp19a(BASETYPE = anyelement, SFUNC = tf1p,
@@ -368,11 +399,11 @@ CREATE AGGREGATE myaggn01b(*) (SFUNC = stfnp, STYPE = int4[],
 CREATE AGGREGATE myaggn02a(*) (SFUNC = stfnp, STYPE = anyarray,
   FINALFUNC = ffnp, INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 CREATE AGGREGATE myaggn02b(*) (SFUNC = stfnp, STYPE = anyarray,
   INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 --     N    P
 -- should CREATE
 CREATE AGGREGATE myaggn03a(*) (SFUNC = stfp, STYPE = int4[],
@@ -382,7 +413,7 @@ CREATE AGGREGATE myaggn03a(*) (SFUNC = stfp, STYPE = int4[],
 CREATE AGGREGATE myaggn04a(*) (SFUNC = stfp, STYPE = anyarray,
   FINALFUNC = ffnp, INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 --    Case4 (R = N) && ((B = P) || (B = N))
 --    -------------------------------------
 --    S    tf1      B    tf2
@@ -436,21 +467,21 @@ ERROR:  function tfp(integer[], anyelement) does not exist
 CREATE AGGREGATE myaggn13a(BASETYPE = int, SFUNC = tfnp, STYPE = anyarray,
   FINALFUNC = ffnp, INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 CREATE AGGREGATE myaggn13b(BASETYPE = int, SFUNC = tfnp, STYPE = anyarray,
   INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 --    P    N        N    P
 -- should ERROR: tf2p(anyarray, int) not matched by tf2p(int[],anyelement)
 CREATE AGGREGATE myaggn14a(BASETYPE = int, SFUNC = tf2p, STYPE = anyarray,
   FINALFUNC = ffnp, INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 CREATE AGGREGATE myaggn14b(BASETYPE = int, SFUNC = tf2p, STYPE = anyarray,
   INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 --    P    N        P    N
 -- should ERROR: tfnp(anyarray, anyelement) not matched by tfnp(int[],int)
 CREATE AGGREGATE myaggn15a(BASETYPE = anyelement, SFUNC = tfnp,
@@ -472,13 +503,13 @@ ERROR:  function tf2p(anyarray, anyelement) does not exist
 CREATE AGGREGATE myaggn17a(BASETYPE = int, SFUNC = tf1p, STYPE = anyarray,
   FINALFUNC = ffnp, INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 --    P    P        N    P
 -- should ERROR: tfp(anyarray, int) not matched by tfp(anyarray, anyelement)
 CREATE AGGREGATE myaggn18a(BASETYPE = int, SFUNC = tfp, STYPE = anyarray,
   FINALFUNC = ffnp, INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 --    P    P        P    N
 -- should ERROR: tf1p(anyarray, anyelement) not matched by tf1p(anyarray, int)
 CREATE AGGREGATE myaggn19a(BASETYPE = anyelement, SFUNC = tf1p,
@@ -1853,7 +1884,59 @@ returns anycompatiblerange as $$
   select $1
 $$ language sql;
 ERROR:  cannot determine result data type
-DETAIL:  A result of type anycompatiblerange requires at least one input of type anycompatiblerange.
+DETAIL:  A result of type anycompatiblerange requires at least one input of type anycompatiblerange or anycompatiblemultirange.
+create function anyctest(anycompatible, anycompatiblemultirange)
+returns anycompatiblemultirange as $$
+  select $2
+$$ language sql;
+select x, pg_typeof(x) from anyctest(11, multirange(int4range(4,7))) x;
+    x    |   pg_typeof    
+---------+----------------
+ {[4,7)} | int4multirange
+(1 row)
+
+select x, pg_typeof(x) from anyctest(11, multirange(numrange(4,7))) x;
+    x    |   pg_typeof   
+---------+---------------
+ {[4,7)} | nummultirange
+(1 row)
+
+select x, pg_typeof(x) from anyctest(11, 12) x;  -- fail
+ERROR:  function anyctest(integer, integer) does not exist
+LINE 1: select x, pg_typeof(x) from anyctest(11, 12) x;
+                                    ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+select x, pg_typeof(x) from anyctest(11.2, multirange(int4range(4,7))) x;  -- fail
+ERROR:  function anyctest(numeric, int4multirange) does not exist
+LINE 1: select x, pg_typeof(x) from anyctest(11.2, multirange(int4ra...
+                                    ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+select x, pg_typeof(x) from anyctest(11.2, '{[4,7)}') x;  -- fail
+ERROR:  could not identify anycompatiblemultirange type
+drop function anyctest(anycompatible, anycompatiblemultirange);
+create function anyctest(anycompatiblemultirange, anycompatiblemultirange)
+returns anycompatible as $$
+  select lower($1) + upper($2)
+$$ language sql;
+select x, pg_typeof(x) from anyctest(multirange(int4range(11,12)), multirange(int4range(4,7))) x;
+ x  | pg_typeof 
+----+-----------
+ 18 | integer
+(1 row)
+
+select x, pg_typeof(x) from anyctest(multirange(int4range(11,12)), multirange(numrange(4,7))) x; -- fail
+ERROR:  function anyctest(int4multirange, nummultirange) does not exist
+LINE 1: select x, pg_typeof(x) from anyctest(multirange(int4range(11...
+                                    ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+drop function anyctest(anycompatiblemultirange, anycompatiblemultirange);
+-- fail, can't infer result type:
+create function anyctest(anycompatible)
+returns anycompatiblemultirange as $$
+  select $1
+$$ language sql;
+ERROR:  cannot determine result data type
+DETAIL:  A result of type anycompatiblemultirange requires at least one input of type anycompatiblerange or anycompatiblemultirange.
 create function anyctest(anycompatiblenonarray, anycompatiblenonarray)
 returns anycompatiblearray as $$
   select array[$1, $2]
diff --git a/src/test/regress/expected/rangefuncs.out b/src/test/regress/expected/rangefuncs.out
index e618aec2eb..4d3dc9e3c9 100644
--- a/src/test/regress/expected/rangefuncs.out
+++ b/src/test/regress/expected/rangefuncs.out
@@ -1556,7 +1556,7 @@ DROP FUNCTION dup(anyelement);
 CREATE FUNCTION bad (f1 int, out f2 anyelement, out f3 anyarray)
 AS 'select $1, array[$1,$1]' LANGUAGE sql;
 ERROR:  cannot determine result data type
-DETAIL:  A result of type anyelement requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyelement requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 CREATE FUNCTION dup (f1 anycompatible, f2 anycompatiblearray, f3 out anycompatible, f4 out anycompatiblearray)
 AS 'select $1, $2' LANGUAGE sql;
 SELECT dup(22, array[44]);
diff --git a/src/test/regress/expected/rangetypes.out b/src/test/regress/expected/rangetypes.out
index c421f5394f..6a1bbadc91 100644
--- a/src/test/regress/expected/rangetypes.out
+++ b/src/test/regress/expected/rangetypes.out
@@ -518,6 +518,25 @@ select numrange(1.0, 2.0) * numrange(2.5, 3.0);
  empty
 (1 row)
 
+select range_intersect_agg(nr) from numrange_test;
+ range_intersect_agg 
+---------------------
+ empty
+(1 row)
+
+select range_intersect_agg(nr) from numrange_test where false;
+ range_intersect_agg 
+---------------------
+ 
+(1 row)
+
+select range_intersect_agg(nr) from numrange_test where nr @> 4.0;
+ range_intersect_agg 
+---------------------
+ [3,5)
+(1 row)
+
+analyze numrange_test;
 create table numrange_test2(nr numrange);
 create index numrange_test2_hash_idx on numrange_test2 using hash (nr);
 INSERT INTO numrange_test2 VALUES('[, 5)');
@@ -1371,12 +1390,12 @@ drop function anyarray_anyrange_func(anyarray, anyrange);
 create function bogus_func(anyelement)
   returns anyrange as 'select int4range(1,10)' language sql;
 ERROR:  cannot determine result data type
-DETAIL:  A result of type anyrange requires at least one input of type anyrange.
+DETAIL:  A result of type anyrange requires at least one input of type anyrange or anymultirange.
 -- should fail
 create function bogus_func(int)
   returns anyrange as 'select int4range(1,10)' language sql;
 ERROR:  cannot determine result data type
-DETAIL:  A result of type anyrange requires at least one input of type anyrange.
+DETAIL:  A result of type anyrange requires at least one input of type anyrange or anymultirange.
 create function range_add_bounds(anyrange)
   returns anyelement as 'select lower($1) + upper($1)' language sql;
 select range_add_bounds(int4range(1, 17));
@@ -1430,7 +1449,7 @@ drop function anycompatiblearray_anycompatiblerange_func(anycompatiblearray, any
 create function bogus_func(anycompatible)
   returns anycompatiblerange as 'select int4range(1,10)' language sql;
 ERROR:  cannot determine result data type
-DETAIL:  A result of type anycompatiblerange requires at least one input of type anycompatiblerange.
+DETAIL:  A result of type anycompatiblerange requires at least one input of type anycompatiblerange or anycompatiblemultirange.
 --
 -- Arrays of ranges
 --
@@ -1508,6 +1527,7 @@ reset enable_sort;
 --
 -- OUT/INOUT/TABLE functions
 --
+-- infer anyrange from anyrange
 create function outparam_succeed(i anyrange, out r anyrange, out t text)
   as $$ select $1, 'foo'::text $$ language sql;
 select * from outparam_succeed(int4range(1,2));
@@ -1525,6 +1545,16 @@ select * from outparam2_succeed(int4range(1,11));
  {1,11} | {11,1}
 (1 row)
 
+-- infer anyarray from anyrange
+create function outparam_succeed2(i anyrange, out r anyarray, out t text)
+  as $$ select ARRAY[upper($1)], 'foo'::text $$ language sql;
+select * from outparam_succeed2(int4range(int4range(1,2)));
+  r  |  t  
+-----+-----
+ {2} | foo
+(1 row)
+
+-- infer anyelement from anyrange
 create function inoutparam_succeed(out i anyelement, inout r anyrange)
   as $$ select upper($1), $1 $$ language sql;
 select * from inoutparam_succeed(int4range(1,2));
@@ -1547,14 +1577,14 @@ select * from table_succeed(int4range(1,11));
 create function outparam_fail(i anyelement, out r anyrange, out t text)
   as $$ select '[1,10]', 'foo' $$ language sql;
 ERROR:  cannot determine result data type
-DETAIL:  A result of type anyrange requires at least one input of type anyrange.
+DETAIL:  A result of type anyrange requires at least one input of type anyrange or anymultirange.
 --should fail
 create function inoutparam_fail(inout i anyelement, out r anyrange)
   as $$ select $1, '[1,10]' $$ language sql;
 ERROR:  cannot determine result data type
-DETAIL:  A result of type anyrange requires at least one input of type anyrange.
+DETAIL:  A result of type anyrange requires at least one input of type anyrange or anymultirange.
 --should fail
 create function table_fail(i anyelement) returns table(i anyelement, r anyrange)
   as $$ select $1, '[1,10]' $$ language sql;
 ERROR:  cannot determine result data type
-DETAIL:  A result of type anyrange requires at least one input of type anyrange.
+DETAIL:  A result of type anyrange requires at least one input of type anyrange or anymultirange.
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index 192445878d..d9ce961be2 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -92,6 +92,7 @@ num_exp_sqrt|t
 num_exp_sub|t
 num_input_test|f
 num_result|f
+nummultirange_test|t
 numrange_test|t
 onek|t
 onek2|t
@@ -172,6 +173,7 @@ quad_poly_tbl|t
 radix_text_tbl|t
 ramp|f
 real_city|f
+reservations|f
 road|t
 shighway|t
 slow_emp4000|f
diff --git a/src/test/regress/expected/type_sanity.out b/src/test/regress/expected/type_sanity.out
index 274130e706..8df1ae47e4 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'));
@@ -195,18 +195,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
@@ -240,17 +241,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
@@ -318,18 +320,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
@@ -363,17 +366,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
@@ -631,3 +635,11 @@ WHERE pronargs != 2
 ----------+------------+---------
 (0 rows)
 
+-- every range should have a valid multirange
+SELECT p1.rngtypid, p1.rngsubtype, p1.rngmultitypid
+FROM pg_range p1
+WHERE p1.rngmultitypid IS NULL OR p1.rngmultitypid = 0;
+ rngtypid | rngsubtype | rngmultitypid 
+----------+------------+---------------
+(0 rows)
+
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 026ea880cd..f795398bfc 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -19,6 +19,7 @@ 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 xid
 
@@ -27,7 +28,7 @@ test: strings numerology point lseg line box path polygon circle date time timet
 # geometry depends on point, lseg, box, path, polygon and circle
 # horology depends on interval, timetz, timestamp, timestamptz
 # ----------
-test: geometry horology regex oidjoins type_sanity opr_sanity misc_sanity comments expressions unicode
+test: geometry horology regex oidjoins type_sanity opr_sanity misc_sanity comments expressions unicode multirangetypes
 
 # ----------
 # These four each depend on the previous one
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index 979d926119..dfbd881ebf 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -20,6 +20,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..2a6b4a0a29
--- /dev/null
+++ b/src/test/regress/sql/multirangetypes.sql
@@ -0,0 +1,658 @@
+-- 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'));
+
+--
+-- test casts, both a built-in range type and a user-defined one:
+--
+select 'empty'::int4range::int4multirange;
+select int4range(1, 3)::int4multirange;
+select int4range(1, null)::int4multirange;
+select int4range(null, null)::int4multirange;
+select 'empty'::textrange::textmultirange;
+select textrange('a', 'c')::textmultirange;
+select textrange('a', null)::textmultirange;
+select textrange(null, null)::textmultirange;
+
+--
+-- create some test data and test the operators
+--
+
+CREATE TABLE nummultirange_test (nmr NUMMULTIRANGE);
+CREATE INDEX nummultirange_test_btree ON nummultirange_test(nmr);
+
+INSERT INTO nummultirange_test VALUES('{}');
+INSERT INTO nummultirange_test VALUES('{[,)}');
+INSERT INTO nummultirange_test VALUES('{[3,]}');
+INSERT INTO nummultirange_test VALUES('{[,), [3,]}');
+INSERT INTO nummultirange_test VALUES('{[, 5)}');
+INSERT INTO nummultirange_test VALUES(nummultirange());
+INSERT INTO nummultirange_test VALUES(nummultirange(variadic '{}'::numrange[]));
+INSERT INTO nummultirange_test VALUES(nummultirange(numrange(1.1, 2.2)));
+INSERT INTO nummultirange_test VALUES('{empty}');
+INSERT INTO nummultirange_test VALUES(nummultirange(numrange(1.7, 1.7, '[]'), numrange(1.7, 1.9)));
+INSERT INTO nummultirange_test VALUES(nummultirange(numrange(1.7, 1.7, '[]'), numrange(1.9, 2.1)));
+
+SELECT nmr, isempty(nmr), lower(nmr), upper(nmr) FROM nummultirange_test ORDER BY nmr;
+SELECT nmr, lower_inc(nmr), lower_inf(nmr), upper_inc(nmr), upper_inf(nmr) FROM nummultirange_test ORDER BY nmr;
+
+SELECT * FROM nummultirange_test WHERE nmr = '{}';
+SELECT * FROM nummultirange_test WHERE nmr = '{(,5)}';
+SELECT * FROM nummultirange_test WHERE nmr = '{[3,)}';
+SELECT * FROM nummultirange_test WHERE nmr = '{[1.7,1.7]}';
+SELECT * FROM nummultirange_test WHERE nmr = '{[1.7,1.7],[1.9,2.1)}';
+SELECT * FROM nummultirange_test WHERE nmr < '{}';
+SELECT * FROM nummultirange_test WHERE nmr < '{[-1000.0, -1000.0]}';
+SELECT * FROM nummultirange_test WHERE nmr < '{[0.0, 1.0]}';
+SELECT * FROM nummultirange_test WHERE nmr < '{[1000.0, 1001.0]}';
+SELECT * FROM nummultirange_test WHERE nmr <= '{}';
+SELECT * FROM nummultirange_test WHERE nmr <= '{[3,)}';
+SELECT * FROM nummultirange_test WHERE nmr >= '{}';
+SELECT * FROM nummultirange_test WHERE nmr >= '{[3,)}';
+SELECT * FROM nummultirange_test WHERE nmr > '{}';
+SELECT * FROM nummultirange_test WHERE nmr > '{[-1000.0, -1000.0]}';
+SELECT * FROM nummultirange_test WHERE nmr > '{[0.0, 1.0]}';
+SELECT * FROM nummultirange_test WHERE nmr > '{[1000.0, 1001.0]}';
+SELECT * FROM nummultirange_test WHERE nmr <> '{}';
+SELECT * FROM nummultirange_test WHERE nmr <> '{(,5)}';
+
+select nummultirange(numrange(2.0, 1.0));
+select nummultirange(numrange(5.0, 6.0), numrange(1.0, 2.0));
+
+analyze nummultirange_test;
+
+-- overlaps
+SELECT * FROM nummultirange_test WHERE range_overlaps_multirange(numrange(4.0, 4.2), nmr);
+SELECT * FROM nummultirange_test WHERE numrange(4.0, 4.2) && nmr;
+SELECT * FROM nummultirange_test WHERE multirange_overlaps_range(nmr, numrange(4.0, 4.2));
+SELECT * FROM nummultirange_test WHERE nmr && numrange(4.0, 4.2);
+SELECT * FROM nummultirange_test WHERE multirange_overlaps_multirange(nmr, nummultirange(numrange(4.0, 4.2), numrange(6.0, 7.0)));
+SELECT * FROM nummultirange_test WHERE nmr && nummultirange(numrange(4.0, 4.2), numrange(6.0, 7.0));
+SELECT * FROM nummultirange_test WHERE nmr && nummultirange(numrange(6.0, 7.0));
+SELECT * FROM nummultirange_test WHERE nmr && nummultirange(numrange(6.0, 7.0), numrange(8.0, 9.0));
+
+-- mr contains x
+SELECT * FROM nummultirange_test WHERE multirange_contains_elem(nmr, 4.0);
+SELECT * FROM nummultirange_test WHERE nmr @> 4.0;
+SELECT * FROM nummultirange_test WHERE multirange_contains_range(nmr, numrange(4.0, 4.2));
+SELECT * FROM nummultirange_test WHERE nmr @> numrange(4.0, 4.2);
+SELECT * FROM nummultirange_test WHERE multirange_contains_multirange(nmr, '{[4.0,4.2), [6.0, 8.0)}');
+SELECT * FROM nummultirange_test WHERE nmr @> '{[4.0,4.2), [6.0, 8.0)}'::nummultirange;
+
+-- x is contained by mr
+SELECT * FROM nummultirange_test WHERE elem_contained_by_multirange(4.0, nmr);
+SELECT * FROM nummultirange_test WHERE 4.0 <@ nmr;
+SELECT * FROM nummultirange_test WHERE range_contained_by_multirange(numrange(4.0, 4.2), nmr);
+SELECT * FROM nummultirange_test WHERE numrange(4.0, 4.2) <@ nmr;
+SELECT * FROM nummultirange_test WHERE multirange_contained_by_multirange('{[4.0,4.2), [6.0, 8.0)}', nmr);
+SELECT * FROM nummultirange_test WHERE '{[4.0,4.2), [6.0, 8.0)}'::nummultirange <@ nmr;
+
+-- overlaps
+SELECT 'empty'::numrange && nummultirange();
+SELECT 'empty'::numrange && nummultirange(numrange(1,2));
+SELECT nummultirange() && 'empty'::numrange;
+SELECT nummultirange(numrange(1,2)) && 'empty'::numrange;
+SELECT nummultirange() && nummultirange();
+SELECT nummultirange() && nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) && nummultirange();
+SELECT nummultirange(numrange(3,4)) && nummultirange(numrange(1,2), numrange(7,8));
+SELECT nummultirange(numrange(1,2), numrange(7,8)) && nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(3,4)) && nummultirange(numrange(1,2), numrange(3.5,8));
+SELECT nummultirange(numrange(1,2), numrange(3.5,8)) && numrange(3,4);
+SELECT nummultirange(numrange(1,2), numrange(3.5,8)) && nummultirange(numrange(3,4));
+
+-- contains
+SELECT nummultirange() @> nummultirange();
+SELECT nummultirange() @> 'empty'::numrange;
+SELECT nummultirange(numrange(null,null)) @> numrange(1,2);
+SELECT nummultirange(numrange(null,null)) @> numrange(null,2);
+SELECT nummultirange(numrange(null,null)) @> numrange(2,null);
+SELECT nummultirange(numrange(null,5)) @> numrange(null,3);
+SELECT nummultirange(numrange(null,5)) @> numrange(null,8);
+SELECT nummultirange(numrange(5,null)) @> numrange(8,null);
+SELECT nummultirange(numrange(5,null)) @> numrange(3,null);
+SELECT nummultirange(numrange(1,5)) @> numrange(8,9);
+SELECT nummultirange(numrange(1,5)) @> numrange(3,9);
+SELECT nummultirange(numrange(1,5)) @> numrange(1,4);
+SELECT nummultirange(numrange(1,5)) @> numrange(1,5);
+SELECT nummultirange(numrange(-4,-2), numrange(1,5)) @> numrange(1,5);
+SELECT nummultirange(numrange(1,5), numrange(8,9)) @> numrange(1,5);
+SELECT nummultirange(numrange(1,5), numrange(8,9)) @> numrange(6,7);
+SELECT nummultirange(numrange(1,5), numrange(6,9)) @> numrange(6,7);
+SELECT '{[1,5)}'::nummultirange @> '{[1,5)}';
+SELECT '{[-4,-2), [1,5)}'::nummultirange @> '{[1,5)}';
+SELECT '{[1,5), [8,9)}'::nummultirange @> '{[1,5)}';
+SELECT '{[1,5), [8,9)}'::nummultirange @> '{[6,7)}';
+SELECT '{[1,5), [6,9)}'::nummultirange @> '{[6,7)}';
+
+-- is contained by
+SELECT nummultirange() <@ nummultirange();
+SELECT 'empty'::numrange <@ nummultirange();
+SELECT numrange(1,2) <@ nummultirange(numrange(null,null));
+SELECT numrange(null,2) <@ nummultirange(numrange(null,null));
+SELECT numrange(2,null) <@ nummultirange(numrange(null,null));
+SELECT numrange(null,3) <@ nummultirange(numrange(null,5));
+SELECT numrange(null,8) <@ nummultirange(numrange(null,5));
+SELECT numrange(8,null) <@ nummultirange(numrange(5,null));
+SELECT numrange(3,null) <@ nummultirange(numrange(5,null));
+SELECT numrange(8,9) <@ nummultirange(numrange(1,5));
+SELECT numrange(3,9) <@ nummultirange(numrange(1,5));
+SELECT numrange(1,4) <@ nummultirange(numrange(1,5));
+SELECT numrange(1,5) <@ nummultirange(numrange(1,5));
+SELECT numrange(1,5) <@ nummultirange(numrange(-4,-2), numrange(1,5));
+SELECT numrange(1,5) <@ nummultirange(numrange(1,5), numrange(8,9));
+SELECT numrange(6,7) <@ nummultirange(numrange(1,5), numrange(8,9));
+SELECT numrange(6,7) <@ nummultirange(numrange(1,5), numrange(6,9));
+SELECT '{[1,5)}' <@ '{[1,5)}'::nummultirange;
+SELECT '{[1,5)}' <@ '{[-4,-2), [1,5)}'::nummultirange;
+SELECT '{[1,5)}' <@ '{[1,5), [8,9)}'::nummultirange;
+SELECT '{[6,7)}' <@ '{[1,5), [8,9)}'::nummultirange;
+SELECT '{[6,7)}' <@ '{[1,5), [6,9)}'::nummultirange;
+
+-- overleft
+SELECT 'empty'::numrange &< nummultirange();
+SELECT 'empty'::numrange &< nummultirange(numrange(1,2));
+SELECT nummultirange() &< 'empty'::numrange;
+SELECT nummultirange(numrange(1,2)) &< 'empty'::numrange;
+SELECT nummultirange() &< nummultirange();
+SELECT nummultirange(numrange(1,2)) &< nummultirange();
+SELECT nummultirange() &< nummultirange(numrange(1,2));
+SELECT numrange(6,7) &< nummultirange(numrange(3,4));
+SELECT numrange(1,2) &< nummultirange(numrange(3,4));
+SELECT numrange(1,4) &< nummultirange(numrange(3,4));
+SELECT numrange(1,6) &< nummultirange(numrange(3,4));
+SELECT numrange(3.5,6) &< nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(6,7)) &< numrange(3,4);
+SELECT nummultirange(numrange(1,2)) &< numrange(3,4);
+SELECT nummultirange(numrange(1,4)) &< numrange(3,4);
+SELECT nummultirange(numrange(1,6)) &< numrange(3,4);
+SELECT nummultirange(numrange(3.5,6)) &< numrange(3,4);
+SELECT nummultirange(numrange(6,7)) &< nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,2)) &< nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,4)) &< nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,6)) &< nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(3.5,6)) &< nummultirange(numrange(3,4));
+
+-- overright
+SELECT nummultirange() &> 'empty'::numrange;
+SELECT nummultirange(numrange(1,2)) &> 'empty'::numrange;
+SELECT 'empty'::numrange &> nummultirange();
+SELECT 'empty'::numrange &> nummultirange(numrange(1,2));
+SELECT nummultirange() &> nummultirange();
+SELECT nummultirange() &> nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) &> nummultirange();
+SELECT nummultirange(numrange(3,4)) &> numrange(6,7);
+SELECT nummultirange(numrange(3,4)) &> numrange(1,2);
+SELECT nummultirange(numrange(3,4)) &> numrange(1,4);
+SELECT nummultirange(numrange(3,4)) &> numrange(1,6);
+SELECT nummultirange(numrange(3,4)) &> numrange(3.5,6);
+SELECT numrange(3,4) &> nummultirange(numrange(6,7));
+SELECT numrange(3,4) &> nummultirange(numrange(1,2));
+SELECT numrange(3,4) &> nummultirange(numrange(1,4));
+SELECT numrange(3,4) &> nummultirange(numrange(1,6));
+SELECT numrange(3,4) &> nummultirange(numrange(3.5,6));
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(6,7));
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(1,4));
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(1,6));
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(3.5,6));
+
+-- meets
+SELECT 'empty'::numrange -|- nummultirange();
+SELECT 'empty'::numrange -|- nummultirange(numrange(1,2));
+SELECT nummultirange() -|- 'empty'::numrange;
+SELECT nummultirange(numrange(1,2)) -|- 'empty'::numrange;
+SELECT nummultirange() -|- nummultirange();
+SELECT nummultirange(numrange(1,2)) -|- nummultirange();
+SELECT nummultirange() -|- nummultirange(numrange(1,2));
+SELECT numrange(1,2) -|- nummultirange(numrange(2,4));
+SELECT numrange(1,2) -|- nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,2)) -|- numrange(2,4);
+SELECT nummultirange(numrange(1,2)) -|- numrange(3,4);
+SELECT nummultirange(numrange(1,2)) -|- nummultirange(numrange(2,4));
+SELECT nummultirange(numrange(1,2)) -|- nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,2), numrange(5,6)) -|- nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,2), numrange(5,6)) -|- nummultirange(numrange(6,7));
+SELECT nummultirange(numrange(1,2), numrange(5,6)) -|- nummultirange(numrange(8,9));
+SELECT nummultirange(numrange(1,2)) -|- nummultirange(numrange(2,4), numrange(6,7));
+
+-- strictly left
+select 'empty'::numrange << nummultirange();
+select numrange(1,2) << nummultirange();
+select numrange(1,2) << nummultirange(numrange(3,4));
+select numrange(1,2) << nummultirange(numrange(0,4));
+select numrange(1,2) << nummultirange(numrange(0,4), numrange(7,8));
+select nummultirange() << 'empty'::numrange;
+select nummultirange() << numrange(1,2);
+select nummultirange(numrange(3,4)) << numrange(3,6);
+select nummultirange(numrange(0,2)) << numrange(3,6);
+select nummultirange(numrange(0,2), numrange(7,8)) << numrange(3,6);
+select nummultirange(numrange(-4,-2), numrange(0,2)) << numrange(3,6);
+select nummultirange() << nummultirange();
+select nummultirange() << nummultirange(numrange(1,2));
+select nummultirange(numrange(1,2)) << nummultirange();
+select nummultirange(numrange(1,2)) << nummultirange(numrange(1,2));
+select nummultirange(numrange(1,2)) << nummultirange(numrange(3,4));
+select nummultirange(numrange(1,2)) << nummultirange(numrange(3,4), numrange(7,8));
+select nummultirange(numrange(1,2), numrange(4,5)) << nummultirange(numrange(3,4), numrange(7,8));
+
+-- strictly right
+select nummultirange() >> 'empty'::numrange;
+select nummultirange() >> numrange(1,2);
+select nummultirange(numrange(3,4)) >> numrange(1,2);
+select nummultirange(numrange(0,4)) >> numrange(1,2);
+select nummultirange(numrange(0,4), numrange(7,8)) >> numrange(1,2);
+select 'empty'::numrange >> nummultirange();
+select numrange(1,2) >> nummultirange();
+select numrange(3,6) >> nummultirange(numrange(3,4));
+select numrange(3,6) >> nummultirange(numrange(0,2));
+select numrange(3,6) >> nummultirange(numrange(0,2), numrange(7,8));
+select numrange(3,6) >> nummultirange(numrange(-4,-2), numrange(0,2));
+select nummultirange() >> nummultirange();
+select nummultirange(numrange(1,2)) >> nummultirange();
+select nummultirange() >> nummultirange(numrange(1,2));
+select nummultirange(numrange(1,2)) >> nummultirange(numrange(1,2));
+select nummultirange(numrange(3,4)) >> nummultirange(numrange(1,2));
+select nummultirange(numrange(3,4), numrange(7,8)) >> nummultirange(numrange(1,2));
+select nummultirange(numrange(3,4), numrange(7,8)) >> nummultirange(numrange(1,2), numrange(4,5));
+
+-- union
+SELECT nummultirange() + nummultirange();
+SELECT nummultirange() + nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) + nummultirange();
+SELECT nummultirange(numrange(1,2)) + nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) + nummultirange(numrange(2,4));
+SELECT nummultirange(numrange(1,2)) + nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) + nummultirange(numrange(2,4));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) + nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) + nummultirange(numrange(0,9));
+
+-- merge
+SELECT range_merge(nummultirange());
+SELECT range_merge(nummultirange(numrange(1,2)));
+SELECT range_merge(nummultirange(numrange(1,2), numrange(7,8)));
+
+-- minus
+SELECT nummultirange() - nummultirange();
+SELECT nummultirange() - nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) - nummultirange();
+SELECT nummultirange(numrange(1,2), numrange(3,4)) - nummultirange();
+SELECT nummultirange(numrange(1,2)) - nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) - nummultirange(numrange(2,4));
+SELECT nummultirange(numrange(1,2)) - nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(2,3));
+SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(0,8));
+SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(0,2));
+SELECT nummultirange(numrange(1,8)) - nummultirange(numrange(0,2), numrange(3,4));
+SELECT nummultirange(numrange(1,8)) - nummultirange(numrange(2,3), numrange(5,null));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(-2,0));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(2,4));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(3,5));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(0,9));
+SELECT nummultirange(numrange(1,3), numrange(4,5)) - nummultirange(numrange(2,9));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(8,9));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(-2,0), numrange(8,9));
+
+-- intersection
+SELECT nummultirange() * nummultirange();
+SELECT nummultirange() * nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) * nummultirange();
+SELECT '{[1,3)}'::nummultirange * '{[1,5)}'::nummultirange;
+SELECT '{[1,3)}'::nummultirange * '{[0,5)}'::nummultirange;
+SELECT '{[1,3)}'::nummultirange * '{[0,2)}'::nummultirange;
+SELECT '{[1,3)}'::nummultirange * '{[2,5)}'::nummultirange;
+SELECT '{[1,4)}'::nummultirange * '{[2,3)}'::nummultirange;
+SELECT '{[1,4)}'::nummultirange * '{[0,2), [3,5)}'::nummultirange;
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[0,8), [9,12)}'::nummultirange;
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[9,12)}'::nummultirange;
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[-5,-4), [5,6), [9,12)}'::nummultirange;
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[0,2), [3,8), [9,12)}'::nummultirange;
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[0,2), [3,8), [9,12)}'::nummultirange;
+
+--
+-- range_agg function
+--
+create table reservations ( room_id integer not null, booked_during daterange );
+insert into reservations values
+-- 1: has a meets and a gap
+(1, daterange('2018-07-01', '2018-07-07')),
+(1, daterange('2018-07-07', '2018-07-14')),
+(1, daterange('2018-07-20', '2018-07-22')),
+-- 2: just a single row
+(2, daterange('2018-07-01', '2018-07-03')),
+-- 3: one null range
+(3, NULL),
+-- 4: two null ranges
+(4, NULL),
+(4, NULL),
+-- 5: a null range and a non-null range
+(5, NULL),
+(5, daterange('2018-07-01', '2018-07-03')),
+-- 6: has overlap
+(6, daterange('2018-07-01', '2018-07-07')),
+(6, daterange('2018-07-05', '2018-07-10')),
+-- 7: two ranges that meet: no gap or overlap
+(7, daterange('2018-07-01', '2018-07-07')),
+(7, daterange('2018-07-07', '2018-07-14')),
+-- 8: an empty range
+(8, 'empty'::daterange)
+;
+SELECT   room_id, range_agg(booked_during)
+FROM     reservations
+GROUP BY room_id
+ORDER BY room_id;
+
+-- range_agg on a custom range type too
+SELECT  range_agg(r)
+FROM    (VALUES
+          ('[a,c]'::textrange),
+          ('[b,b]'::textrange),
+          ('[c,f]'::textrange),
+          ('[g,h)'::textrange),
+          ('[h,j)'::textrange)
+        ) t(r);
+
+select range_intersect_agg(nmr) from nummultirange_test;
+select range_intersect_agg(nmr) from nummultirange_test where false;
+-- test with just one input:
+select range_intersect_agg(nmr) from (values ('{[1,2]}'::nummultirange)) t(nmr);
+select range_intersect_agg(nmr) from nummultirange_test where nmr @> 4.0;
+
+create table nummultirange_test2(nmr nummultirange);
+create index nummultirange_test2_hash_idx on nummultirange_test2 using hash (nmr);
+
+INSERT INTO nummultirange_test2 VALUES('{[, 5)}');
+INSERT INTO nummultirange_test2 VALUES(nummultirange(numrange(1.1, 2.2)));
+INSERT INTO nummultirange_test2 VALUES(nummultirange(numrange(1.1, 2.2)));
+INSERT INTO nummultirange_test2 VALUES(nummultirange(numrange(1.1, 2.2,'()')));
+INSERT INTO nummultirange_test2 VALUES('{}');
+
+select * from nummultirange_test2 where nmr = '{}';
+select * from nummultirange_test2 where nmr = nummultirange(numrange(1.1, 2.2));
+select * from nummultirange_test2 where nmr = nummultirange(numrange(1.1, 2.3));
+
+set enable_nestloop=t;
+set enable_hashjoin=f;
+set enable_mergejoin=f;
+select * from nummultirange_test natural join nummultirange_test2 order by nmr;
+set enable_nestloop=f;
+set enable_hashjoin=t;
+set enable_mergejoin=f;
+select * from nummultirange_test natural join nummultirange_test2 order by nmr;
+set enable_nestloop=f;
+set enable_hashjoin=f;
+set enable_mergejoin=t;
+select * from nummultirange_test natural join nummultirange_test2 order by nmr;
+
+set enable_nestloop to default;
+set enable_hashjoin to default;
+set enable_mergejoin to default;
+
+DROP TABLE nummultirange_test2;
+
+--
+-- Test user-defined multirange of floats
+--
+
+select '{[123.001, 5.e9)}'::float8multirange @> 888.882::float8;
+create table float8multirange_test(f8mr float8multirange, i int);
+insert into float8multirange_test values(float8multirange(float8range(-100.00007, '1.111113e9')), 42);
+select * from float8multirange_test;
+drop table float8multirange_test;
+
+--
+-- Test multirange types over domains
+--
+
+create domain mydomain as int4;
+create type mydomainrange as range(subtype=mydomain);
+select '{[4,50)}'::mydomainmultirange @> 7::mydomain;
+drop domain mydomain cascade;
+
+--
+-- Test domains over multirange types
+--
+
+create domain restrictedmultirange as int4multirange check (upper(value) < 10);
+select '{[4,5)}'::restrictedmultirange @> 7;
+select '{[4,50)}'::restrictedmultirange @> 7; -- should fail
+drop domain restrictedmultirange;
+
+--
+-- Test multiple multirange types over the same subtype
+--
+
+create type textrange1 as range(subtype=text, collation="C");
+create type textrange2 as range(subtype=text, collation="C");
+
+select textmultirange1(textrange2('a','Z'));  -- should fail
+select textmultirange1(textrange1('a','Z')) @> 'b'::text;
+select textmultirange2(textrange2('a','z')) @> 'b'::text;
+
+drop type textrange1;
+drop type textrange2;
+
+--
+-- Test polymorphic type system
+--
+
+create function anyarray_anymultirange_func(a anyarray, r anymultirange)
+  returns anyelement as 'select $1[1] + lower($2);' language sql;
+
+select anyarray_anymultirange_func(ARRAY[1,2], int4multirange(int4range(10,20)));
+
+-- should fail
+select anyarray_anymultirange_func(ARRAY[1,2], nummultirange(numrange(10,20)));
+
+drop function anyarray_anymultirange_func(anyarray, anymultirange);
+
+-- should fail
+create function bogus_func(anyelement)
+  returns anymultirange as 'select int4multirange(int4range(1,10))' language sql;
+
+-- should fail
+create function bogus_func(int)
+  returns anymultirange as 'select int4multirange(int4range(1,10))' language sql;
+
+create function range_add_bounds(anymultirange)
+  returns anyelement as 'select lower($1) + upper($1)' language sql;
+
+select range_add_bounds(int4multirange(int4range(1, 17)));
+select range_add_bounds(nummultirange(numrange(1.0001, 123.123)));
+
+create function multirangetypes_sql(q anymultirange, b anyarray, out c anyelement)
+  as $$ select upper($1) + $2[1] $$
+  language sql;
+
+select multirangetypes_sql(int4multirange(int4range(1,10)), ARRAY[2,20]);
+select multirangetypes_sql(nummultirange(numrange(1,10)), ARRAY[2,20]);  -- match failure
+
+create function anycompatiblearray_anycompatiblemultirange_func(a anycompatiblearray, mr anycompatiblemultirange)
+  returns anycompatible as 'select $1[1] + lower($2);' language sql;
+
+select anycompatiblearray_anycompatiblemultirange_func(ARRAY[1,2], multirange(int4range(10,20)));
+
+select anycompatiblearray_anycompatiblemultirange_func(ARRAY[1,2], multirange(numrange(10,20)));
+
+-- should fail
+select anycompatiblearray_anycompatiblemultirange_func(ARRAY[1.1,2], multirange(int4range(10,20)));
+
+drop function anycompatiblearray_anycompatiblemultirange_func(anycompatiblearray, anycompatiblemultirange);
+
+create function anycompatiblerange_anycompatiblemultirange_func(r anycompatiblerange, mr anycompatiblemultirange)
+  returns anycompatible as 'select lower($1) + lower($2);' language sql;
+
+select anycompatiblerange_anycompatiblemultirange_func(int4range(1,2), multirange(int4range(10,20)));
+
+-- should fail
+select anycompatiblerange_anycompatiblemultirange_func(numrange(1,2), multirange(int4range(10,20)));
+
+drop function anycompatiblerange_anycompatiblemultirange_func(anycompatiblerange, anycompatiblemultirange);
+
+-- should fail
+create function bogus_func(anycompatible)
+  returns anycompatiblerange as 'select int4range(1,10)' language sql;
+
+--
+-- Arrays of multiranges
+--
+
+select ARRAY[nummultirange(numrange(1.1, 1.2)), nummultirange(numrange(12.3, 155.5))];
+
+create table i8mr_array (f1 int, f2 int8multirange[]);
+insert into i8mr_array values (42, array[int8multirange(int8range(1,10)), int8multirange(int8range(2,20))]);
+select * from i8mr_array;
+drop table i8mr_array;
+
+--
+-- Multiranges of arrays
+--
+
+select arraymultirange(arrayrange(ARRAY[1,2], ARRAY[2,1]));
+select arraymultirange(arrayrange(ARRAY[2,1], ARRAY[1,2]));  -- fail
+
+select array[1,1] <@ arraymultirange(arrayrange(array[1,2], array[2,1]));
+select array[1,3] <@ arraymultirange(arrayrange(array[1,2], array[2,1]));
+
+--
+-- Ranges of composites
+--
+
+create type two_ints as (a int, b int);
+create type two_ints_range as range (subtype = two_ints);
+
+-- with force_parallel_mode on, this exercises tqueue.c's range remapping
+select *, row_to_json(upper(t)) as u from
+  (values (two_ints_multirange(two_ints_range(row(1,2), row(3,4)))),
+          (two_ints_multirange(two_ints_range(row(5,6), row(7,8))))) v(t);
+
+drop type two_ints cascade;
+
+--
+-- Check behavior when subtype lacks a hash function
+--
+
+set enable_sort = off;  -- try to make it pick a hash setop implementation
+
+select '{(2,5)}'::cashmultirange except select '{(5,6)}'::cashmultirange;
+
+reset enable_sort;
+
+--
+-- OUT/INOUT/TABLE functions
+--
+
+-- infer anymultirange from anymultirange
+create function mr_outparam_succeed(i anymultirange, out r anymultirange, out t text)
+  as $$ select $1, 'foo'::text $$ language sql;
+
+select * from mr_outparam_succeed(int4multirange(int4range(1,2)));
+
+-- infer anyarray from anymultirange
+create function mr_outparam_succeed2(i anymultirange, out r anyarray, out t text)
+  as $$ select ARRAY[upper($1)], 'foo'::text $$ language sql;
+
+select * from mr_outparam_succeed2(int4multirange(int4range(1,2)));
+
+-- infer anyrange from anymultirange
+create function mr_outparam_succeed3(i anymultirange, out r anyrange, out t text)
+  as $$ select range_merge($1), 'foo'::text $$ language sql;
+select * from mr_outparam_succeed3(int4multirange(int4range(1,2)));
+
+-- infer anymultirange from anyrange
+create function mr_outparam_succeed4(i anyrange, out r anymultirange, out t text)
+  as $$ select multirange($1), 'foo'::text $$ language sql;
+
+select * from mr_outparam_succeed4(int4range(1,2));
+
+-- infer anyelement from anymultirange
+create function mr_inoutparam_succeed(out i anyelement, inout r anymultirange)
+  as $$ select upper($1), $1 $$ language sql;
+
+select * from mr_inoutparam_succeed(int4multirange(int4range(1,2)));
+
+-- infer anyelement+anymultirange from anyelement+anymultirange
+create function mr_table_succeed(i anyelement, r anymultirange) returns table(i anyelement, r anymultirange)
+  as $$ select $1, $2 $$ language sql;
+
+select * from mr_table_succeed(123, int4multirange(int4range(1,11)));
+
+-- use anymultirange in plpgsql
+create function mr_polymorphic(i anyrange) returns anymultirange
+  as $$ begin return multirange($1); end; $$ language plpgsql;
+select mr_polymorphic(int4range(1, 4));
+
+-- should fail
+create function mr_outparam_fail(i anyelement, out r anymultirange, out t text)
+  as $$ select '[1,10]', 'foo' $$ language sql;
+
+--should fail
+create function mr_inoutparam_fail(inout i anyelement, out r anymultirange)
+  as $$ select $1, '[1,10]' $$ language sql;
+
+--should fail
+create function mr_table_fail(i anyelement) returns table(i anyelement, r anymultirange)
+  as $$ select $1, '[1,10]' $$ language sql;
diff --git a/src/test/regress/sql/opr_sanity.sql b/src/test/regress/sql/opr_sanity.sql
index 307aab1deb..3d6b60179b 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::regtype, p2.prorettype::regtype
 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;
 
@@ -279,16 +290,19 @@ WHERE p1.prorettype IN
      '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;
 
--- anyrange is tighter than the rest, can only resolve from anyrange input
+-- anyrange and anymultirange are tighter than the rest, can only resolve
+-- from each other
 
 SELECT p1.oid, p1.proname
 FROM pg_proc as p1
-WHERE p1.prorettype = 'anyrange'::regtype
+WHERE p1.prorettype IN ('anyrange'::regtype, 'anymultirange'::regtype)
   AND NOT
-     'anyrange'::regtype = ANY (p1.proargtypes)
+    ('anyrange'::regtype = ANY (p1.proargtypes) OR
+      'anymultirange'::regtype = ANY (p1.proargtypes))
 ORDER BY 2;
 
 -- similarly for the anycompatible family
diff --git a/src/test/regress/sql/polymorphism.sql b/src/test/regress/sql/polymorphism.sql
index e5222f1f81..ac52c387bb 100644
--- a/src/test/regress/sql/polymorphism.sql
+++ b/src/test/regress/sql/polymorphism.sql
@@ -71,6 +71,16 @@ select polyf(int4range(42, 49), 11, 4.5) as fail;  -- range type doesn't fit
 
 drop function polyf(x anycompatiblerange, y anycompatible, z anycompatible);
 
+create function polyf(x anycompatiblemultirange, y anycompatible, z anycompatible) returns anycompatiblearray as $$
+  select array[lower(x), upper(x), y, z]
+$$ language sql;
+
+select polyf(multirange(int4range(42, 49)), 11, 2::smallint) as int, polyf(multirange(float8range(4.5, 7.8)), 7.8, 11::real) as num;
+
+select polyf(multirange(int4range(42, 49)), 11, 4.5) as fail;  -- range type doesn't fit
+
+drop function polyf(x anycompatiblemultirange, y anycompatible, z anycompatible);
+
 -- fail, can't infer type:
 create function polyf(x anycompatible) returns anycompatiblerange as $$
   select array[x + 1, x + 2]
@@ -84,6 +94,19 @@ select polyf(int4range(42, 49), array[11]) as int, polyf(float8range(4.5, 7.8),
 
 drop function polyf(x anycompatiblerange, y anycompatiblearray);
 
+-- fail, can't infer type:
+create function polyf(x anycompatible) returns anycompatiblemultirange as $$
+  select array[x + 1, x + 2]
+$$ language sql;
+
+create function polyf(x anycompatiblemultirange, y anycompatiblearray) returns anycompatiblemultirange as $$
+  select x
+$$ language sql;
+
+select polyf(multirange(int4range(42, 49)), array[11]) as int, polyf(multirange(float8range(4.5, 7.8)), array[7]) as num;
+
+drop function polyf(x anycompatiblemultirange, y anycompatiblearray);
+
 create function polyf(a anyelement, b anyarray,
                       c anycompatible, d anycompatible,
                       OUT x anyarray, OUT y anycompatiblearray)
@@ -994,6 +1017,35 @@ returns anycompatiblerange as $$
   select $1
 $$ language sql;
 
+create function anyctest(anycompatible, anycompatiblemultirange)
+returns anycompatiblemultirange as $$
+  select $2
+$$ language sql;
+
+select x, pg_typeof(x) from anyctest(11, multirange(int4range(4,7))) x;
+select x, pg_typeof(x) from anyctest(11, multirange(numrange(4,7))) x;
+select x, pg_typeof(x) from anyctest(11, 12) x;  -- fail
+select x, pg_typeof(x) from anyctest(11.2, multirange(int4range(4,7))) x;  -- fail
+select x, pg_typeof(x) from anyctest(11.2, '{[4,7)}') x;  -- fail
+
+drop function anyctest(anycompatible, anycompatiblemultirange);
+
+create function anyctest(anycompatiblemultirange, anycompatiblemultirange)
+returns anycompatible as $$
+  select lower($1) + upper($2)
+$$ language sql;
+
+select x, pg_typeof(x) from anyctest(multirange(int4range(11,12)), multirange(int4range(4,7))) x;
+select x, pg_typeof(x) from anyctest(multirange(int4range(11,12)), multirange(numrange(4,7))) x; -- fail
+
+drop function anyctest(anycompatiblemultirange, anycompatiblemultirange);
+
+-- fail, can't infer result type:
+create function anyctest(anycompatible)
+returns anycompatiblemultirange as $$
+  select $1
+$$ language sql;
+
 create function anyctest(anycompatiblenonarray, anycompatiblenonarray)
 returns anycompatiblearray as $$
   select array[$1, $2]
diff --git a/src/test/regress/sql/rangetypes.sql b/src/test/regress/sql/rangetypes.sql
index 4048b1d185..b69efede3a 100644
--- a/src/test/regress/sql/rangetypes.sql
+++ b/src/test/regress/sql/rangetypes.sql
@@ -118,6 +118,12 @@ select numrange(1.0, 2.0) * numrange(2.0, 3.0);
 select numrange(1.0, 2.0) * numrange(1.5, 3.0);
 select numrange(1.0, 2.0) * numrange(2.5, 3.0);
 
+select range_intersect_agg(nr) from numrange_test;
+select range_intersect_agg(nr) from numrange_test where false;
+select range_intersect_agg(nr) from numrange_test where nr @> 4.0;
+
+analyze numrange_test;
+
 create table numrange_test2(nr numrange);
 create index numrange_test2_hash_idx on numrange_test2 using hash (nr);
 
@@ -528,6 +534,7 @@ reset enable_sort;
 -- OUT/INOUT/TABLE functions
 --
 
+-- infer anyrange from anyrange
 create function outparam_succeed(i anyrange, out r anyrange, out t text)
   as $$ select $1, 'foo'::text $$ language sql;
 
@@ -539,6 +546,13 @@ create function outparam2_succeed(r anyrange, out lu anyarray, out ul anyarray)
 
 select * from outparam2_succeed(int4range(1,11));
 
+-- infer anyarray from anyrange
+create function outparam_succeed2(i anyrange, out r anyarray, out t text)
+  as $$ select ARRAY[upper($1)], 'foo'::text $$ language sql;
+
+select * from outparam_succeed2(int4range(int4range(1,2)));
+
+-- infer anyelement from anyrange
 create function inoutparam_succeed(out i anyelement, inout r anyrange)
   as $$ select upper($1), $1 $$ language sql;
 
diff --git a/src/test/regress/sql/type_sanity.sql b/src/test/regress/sql/type_sanity.sql
index 4b492ce062..07879d9a57 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'));
@@ -153,7 +153,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')
@@ -182,7 +182,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')
@@ -234,7 +234,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')
@@ -263,7 +263,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')
@@ -467,3 +467,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.rngmultitypid
+FROM pg_range p1
+WHERE p1.rngmultitypid IS NULL OR p1.rngmultitypid = 0;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index b1afb345c3..de7f52c2fd 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1393,6 +1393,9 @@ MultiXactMember
 MultiXactOffset
 MultiXactStateData
 MultiXactStatus
+MultirangeIOData
+MultirangeParseState
+MultirangeType
 NDBOX
 NODE
 NUMCacheEntry
@@ -2269,6 +2272,7 @@ ShellTypeInfo
 ShippableCacheEntry
 ShippableCacheKey
 ShmemIndexEnt
+ShortRangeType
 ShutdownForeignScan_function
 ShutdownInformation
 ShutdownMode
#130Pavel Stehule
pavel.stehule@gmail.com
In reply to: Paul A Jungwirth (#129)
Re: range_agg

čt 24. 9. 2020 v 2:05 odesílatel Paul A Jungwirth <
pj@illuminatedcomputing.com> napsal:

On Sun, Aug 16, 2020 at 12:55 PM Paul A Jungwirth
<pj@illuminatedcomputing.com> wrote:

This is rebased on the current master, including some changes to doc
tables and pg_upgrade handling of type oids.

Here is a rebased version of this patch, including a bunch of cleanup
from Alvaro. (Thanks Alvaro!)

I tested this patch and It looks well, I have not any objections

1. there are not new warnings
2. make check-world passed
3. build doc without problems
4. doc is enough, regress tests too
5. there was not objection against this feature in discussion, and I think
it is interesting and useful feature - good additional to arrays

Regards

Pavel

Show quoted text

Paul

#131Alexander Korotkov
aekorotkov@gmail.com
In reply to: Paul A Jungwirth (#129)
Re: range_agg

Hi!

On Thu, Sep 24, 2020 at 3:05 AM Paul A Jungwirth
<pj@illuminatedcomputing.com> wrote:

On Sun, Aug 16, 2020 at 12:55 PM Paul A Jungwirth
<pj@illuminatedcomputing.com> wrote:

This is rebased on the current master, including some changes to doc
tables and pg_upgrade handling of type oids.

Here is a rebased version of this patch, including a bunch of cleanup
from Alvaro. (Thanks Alvaro!)

I'd like to review this patch. Could you please rebase it once again? Thanks.

------
Regards,
Alexander Korotkov

#132Paul A Jungwirth
pj@illuminatedcomputing.com
In reply to: Alexander Korotkov (#131)
1 attachment(s)
Re: range_agg

On Fri, Nov 27, 2020 at 12:35 AM Alexander Korotkov
<aekorotkov@gmail.com> wrote:

I'd like to review this patch. Could you please rebase it once again? Thanks.

Thanks! Here is a rebased version. It also includes one more cleanup
commit from Alvaro since the last one.

Yours,
Paul

Attachments:

v23-multirange.patchapplication/octet-stream; name=v23-multirange.patchDownload
diff --git a/doc/src/sgml/datatype.sgml b/doc/src/sgml/datatype.sgml
index c2951854b5c..e4038783e95 100644
--- a/doc/src/sgml/datatype.sgml
+++ b/doc/src/sgml/datatype.sgml
@@ -4900,6 +4900,10 @@ SELECT * FROM pg_attribute
     <primary>anyrange</primary>
    </indexterm>
 
+   <indexterm zone="datatype-pseudo">
+    <primary>anymultirange</primary>
+   </indexterm>
+
    <indexterm zone="datatype-pseudo">
     <primary>anycompatible</primary>
    </indexterm>
@@ -4916,6 +4920,10 @@ SELECT * FROM pg_attribute
     <primary>anycompatiblerange</primary>
    </indexterm>
 
+   <indexterm zone="datatype-pseudo">
+    <primary>anycompatiblemultirange</primary>
+   </indexterm>
+
    <indexterm zone="datatype-pseudo">
     <primary>void</primary>
    </indexterm>
@@ -5027,6 +5035,13 @@ SELECT * FROM pg_attribute
         <xref linkend="rangetypes"/>).</entry>
        </row>
 
+       <row>
+        <entry><type>anymultirange</type></entry>
+        <entry>Indicates that a function accepts any multirange data type
+        (see <xref linkend="extend-types-polymorphic"/> and
+        <xref linkend="rangetypes"/>).</entry>
+       </row>
+
        <row>
         <entry><type>anycompatible</type></entry>
         <entry>Indicates that a function accepts any data type,
@@ -5056,6 +5071,14 @@ SELECT * FROM pg_attribute
         <xref linkend="rangetypes"/>).</entry>
        </row>
 
+       <row>
+        <entry><type>anycompatiblemultirange</type></entry>
+        <entry>Indicates that a function accepts any multirange data type,
+        with automatic promotion of multiple arguments to a common data type
+        (see <xref linkend="extend-types-polymorphic"/> and
+        <xref linkend="rangetypes"/>).</entry>
+       </row>
+
        <row>
         <entry><type>cstring</type></entry>
         <entry>Indicates that a function accepts or returns a null-terminated C string.</entry>
diff --git a/doc/src/sgml/extend.sgml b/doc/src/sgml/extend.sgml
index 1c37026bb05..6e3d82b85b8 100644
--- a/doc/src/sgml/extend.sgml
+++ b/doc/src/sgml/extend.sgml
@@ -288,6 +288,14 @@
         </entry>
        </row>
 
+       <row>
+        <entry><type>anymultirange</type></entry>
+        <entry>Simple</entry>
+        <entry>Indicates that a function accepts any multirange data type
+        (see <xref linkend="rangetypes"/>)
+        </entry>
+       </row>
+
        <row>
         <entry><type>anycompatible</type></entry>
         <entry>Common</entry>
@@ -319,6 +327,14 @@
         with automatic promotion of multiple arguments to a common data type
         </entry>
        </row>
+
+       <row>
+        <entry><type>anycompatiblemultirange</type></entry>
+        <entry>Common</entry>
+        <entry>Indicates that a function accepts any multirange data type,
+        with automatic promotion of multiple arguments to a common data type
+        </entry>
+       </row>
       </tbody>
      </tgroup>
     </table>
@@ -346,17 +362,15 @@
      position declared as <type>anyarray</type> can have any array data type,
      but similarly they must all be the same type.  And similarly,
      positions declared as <type>anyrange</type> must all be the same range
-     type.  Furthermore, if there are
+     type.  Likewise for <type>anymultirange</type>.
+    </para>
+
+    <para>
+     Furthermore, if there are
      positions declared <type>anyarray</type> and others declared
      <type>anyelement</type>, the actual array type in the
      <type>anyarray</type> positions must be an array whose elements are
      the same type appearing in the <type>anyelement</type> positions.
-     Similarly, if there are positions declared <type>anyrange</type>
-     and others declared <type>anyelement</type> or <type>anyarray</type>,
-     the actual range type in the <type>anyrange</type> positions must be a
-     range whose subtype is the same type appearing in
-     the <type>anyelement</type> positions and the same as the element type
-     of the <type>anyarray</type> positions.
      <type>anynonarray</type> is treated exactly the same as <type>anyelement</type>,
      but adds the additional constraint that the actual type must not be
      an array type.
@@ -365,6 +379,19 @@
      be an enum type.
     </para>
 
+    <para>
+     Similarly, if there are positions declared <type>anyrange</type>
+     and others declared <type>anyelement</type> or <type>anyarray</type>,
+     the actual range type in the <type>anyrange</type> positions must be a
+     range whose subtype is the same type appearing in
+     the <type>anyelement</type> positions and the same as the element type
+     of the <type>anyarray</type> positions.
+     If there are positions declared <type>anymultirange</type>,
+     their actual multirange type must contain ranges matching parameters declared
+     <type>anyrange</type> and base elements matching parameters declared
+     <type>anyelement</type> and <type>anyarray</type>.
+    </para>
+
     <para>
      Thus, when more than one argument position is declared with a polymorphic
      type, the net effect is that only certain combinations of actual argument
@@ -420,7 +447,8 @@
      Selection of the common type considers the actual types
      of <type>anycompatible</type> and <type>anycompatiblenonarray</type>
      inputs, the array element types of <type>anycompatiblearray</type>
-     inputs, and the range subtypes of <type>anycompatiblerange</type>
+     inputs, the range subtypes of <type>anycompatiblerange</type> inputs,
+     and the multirange subtypes of <type>anycompatiablemultirange</type>
      inputs.  If <type>anycompatiblenonarray</type> is present then the
      common type is required to be a non-array type.  Once a common type is
      identified, arguments in <type>anycompatible</type>
@@ -431,12 +459,15 @@
 
     <para>
      Since there is no way to select a range type knowing only its subtype,
-     use of <type>anycompatiblerange</type> requires that all arguments
-     declared with that type have the same actual range type, and that that
-     type's subtype agree with the selected common type, so that no casting
-     of the range values is required.  As with <type>anyrange</type>, use
-     of <type>anycompatiblerange</type> as a function result type requires
-     that there be an <type>anycompatiblerange</type> argument.
+     use of <type>anycompatiblerange</type> and/or
+     <type>anycompatiblemultirange</type> requires that all arguments declared
+     with that type have the same actual range and/or multirange type, and that
+     that type's subtype agree with the selected common type, so that no casting
+     of the range values is required.  As with <type>anyrange</type> and
+     <type>anymultirange</type>, use of <type>anycompatiblerange</type> and
+     <type>anymultirange</type> as a function result type requires that there be
+     an <type>anycompatiblerange</type> or <type>anycompatiblemultirange</type>
+     argument.
     </para>
 
     <para>
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 507bc1a6683..73da2644263 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -17891,12 +17891,15 @@ SELECT NULLIF(value, '(none)') ...
   <para>
    <xref linkend="range-operators-table"/> shows the specialized operators
    available for range types.
+   <xref linkend="multirange-operators-table"/> shows the specialized operators
+   available for multirange types.
    In addition to those, the usual comparison operators shown in
    <xref linkend="functions-comparison-op-table"/> are available for range
-   types.  The comparison operators order first by the range lower bounds, and
-   only if those are equal do they compare the upper bounds.  This does not
-   usually result in a useful overall ordering, but the operators are provided
-   to allow unique indexes to be constructed on ranges.
+   and multirange types.  The comparison operators order first by the range lower
+   bounds, and only if those are equal do they compare the upper bounds.  The
+   multirange operators compare each range until one is unequal. This
+   does not usually result in a useful overall ordering, but the operators are
+   provided to allow unique indexes to be constructed on ranges.
   </para>
 
    <table id="range-operators-table">
@@ -18106,15 +18109,449 @@ SELECT NULLIF(value, '(none)') ...
     </tgroup>
    </table>
 
+   <table id="multirange-operators-table">
+    <title>Multirange Operators</title>
+    <tgroup cols="1">
+     <thead>
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        Operator
+       </para>
+       <para>
+        Description
+       </para>
+       <para>
+        Example(s)
+       </para></entry>
+      </row>
+     </thead>
+
+     <tbody>
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>@&gt;</literal> <type>anymultirange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Does the first multirange contain the second?
+       </para>
+       <para>
+        <literal>'{[2,4)}'::int4multirange @&gt; '{[2,3)}'::int4multirange</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>@&gt;</literal> <type>anyrange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Does the multirange contain the range?
+       </para>
+       <para>
+        <literal>'{[2,4)}'::int4multirange @&gt; int4range(2,3)</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>@&gt;</literal> <type>anyelement</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Does the multirange contain the element?
+       </para>
+       <para>
+        <literal>'{[2011-01-01,2011-03-01)}'::tsmultirange @&gt; '2011-01-10'::timestamp</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>&lt;@</literal> <type>anymultirange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the first multirange contained by the second?
+       </para>
+       <para>
+        <literal>'{[2,4)}'::int4multirange &lt;@ '{[1,7)}'::int4multirange</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>&lt;@</literal> <type>anyrange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the multirange contained by the range?
+       </para>
+       <para>
+        <literal>'{[2,4)}'::int4multirange &lt;@ int4range(1,7)</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anyrange</type> <literal>&lt;@</literal> <type>anymultirange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the range contained by the multirange?
+       </para>
+       <para>
+        <literal>int4range(2,4) &lt;@ '{[1,7)}'::int4multirange</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anyelement</type> <literal>&lt;@</literal> <type>anymultirange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the element contained by the multirange?
+       </para>
+       <para>
+        <literal>42 &lt;@ '{[1,7)}'::int4multirange</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>&amp;&amp;</literal> <type>anymultirange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Do the multiranges overlap, that is, have any elements in common?
+       </para>
+       <para>
+        <literal>'{[3,7)}'::int8multirange &amp;&amp; '{[4,12)}'::int8multirange</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>&amp;&amp;</literal> <type>anyrange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Does the multirange overlap the range?
+       </para>
+       <para>
+        <literal>'{[3,7)}'::int8multirange &amp;&amp; int8range(4,12)</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anyrange</type> <literal>&amp;&amp;</literal> <type>anymultirange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Does the range overlap the multirange?
+       </para>
+       <para>
+        <literal>int8range(3,7) &amp;&amp; '{[4,12)}'::int8multirange</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>&lt;&lt;</literal> <type>anymultirange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the first multirange strictly left of the second?
+       </para>
+       <para>
+        <literal>'{[1,10)}'::int8multirange &lt;&lt; '{[100,110)}'::int8multirange</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>&lt;&lt;</literal> <type>anyrange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the multirange strictly left of the range?
+       </para>
+       <para>
+        <literal>'{[1,10)}'::int8multirange &lt;&lt; int8range(100,110)</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anyrange</type> <literal>&lt;&lt;</literal> <type>anymultirange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the range strictly left of the multirange?
+       </para>
+       <para>
+        <literal>int8range(1,10) &lt;&lt; '{[100,110)}'::int8multirange</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>&gt;&gt;</literal> <type>anymultirange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the first multirange strictly right of the second?
+       </para>
+       <para>
+        <literal>'{[50,60)}'::int8multirange &gt;&gt; '{[20,30)}'::int8multirange</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>&gt;&gt;</literal> <type>anyrange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the multirange strictly right of the range?
+       </para>
+       <para>
+        <literal>'{[50,60)}'::int8multirange &gt;&gt; int8range(20,30)</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anyrange</type> <literal>&gt;&gt;</literal> <type>anymultirange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the range strictly right of the multirange?
+       </para>
+       <para>
+        <literal>int8range(50,60) &gt;&gt; '{[20,30)}'::int8multirange</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>&amp;&lt;</literal> <type>anymultirange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Does the first multirange not extend to the right of the second?
+       </para>
+       <para>
+        <literal>'{[1,20)}'::int8multirange &amp;&lt; '{[18,20)}'::int8multirange</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>&amp;&lt;</literal> <type>anyrange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Does the multirange not extend to the right of the range?
+       </para>
+       <para>
+        <literal>'{[1,20)}'::int8multirange &amp;&lt; int8range(18,20)</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anyrange</type> <literal>&amp;&lt;</literal> <type>anymultirange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Does the range not extend to the right of the multirange?
+       </para>
+       <para>
+        <literal>int8range(1,20) &amp;&lt; '{[18,20)}'::int8multirange</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>&amp;&gt;</literal> <type>anymultirange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Does the first multirange not extend to the left of the second?
+       </para>
+       <para>
+        <literal>'{[7,20)}'::int8multirange &amp;&gt; '{[5,10)}'::int8multirange</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>&amp;&gt;</literal> <type>anyrange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Does the multirange not extend to the left of the range?
+       </para>
+       <para>
+        <literal>'{[7,20)}'::int8multirange &amp;&gt; int8range(5,10)</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anyrange</type> <literal>&amp;&gt;</literal> <type>anymultirange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Does the range not extend to the left of the multirange?
+       </para>
+       <para>
+        <literal>int8range(7,20) &amp;&gt; '{[5,10)}'::int8multirange</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>-|-</literal> <type>anymultirange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Are the multiranges adjacent?
+       </para>
+       <para>
+        <literal>'{[1.1,2.2)}'::nummultirange -|- '{[2.2,3.3)}'::nummultirange</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>-|-</literal> <type>anyrange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the multirange adjacent to the range?
+       </para>
+       <para>
+        <literal>'{[1.1,2.2)}'::nummultirange -|- numrange(2.2,3.3)</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anyrange</type> <literal>-|-</literal> <type>anymultirange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the range adjacent to the multirange?
+       </para>
+       <para>
+        <literal>numrange(1.1,2.2) -|- '{[2.2,3.3)}'::nummultirange</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>+</literal> <type>anymultirange</type>
+        <returnvalue>anymultirange</returnvalue>
+       </para>
+       <para>
+        Computes the union of the multiranges.  The multiranges need not overlap
+        or be adjacent.
+       </para>
+       <para>
+        <literal>'{[5,10)}'::nummultirange + '{[15,20)}'::nummultirange</literal>
+        <returnvalue>{[5,10), [15,20)}</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>*</literal> <type>anymultirange</type>
+        <returnvalue>anymultirange</returnvalue>
+       </para>
+       <para>
+        Computes the intersection of the multiranges.
+       </para>
+       <para>
+        <literal>'{[5,15)}'::int8multirange * '{[10,20)}'::int8multirange</literal>
+        <returnvalue>{[10,15)}</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>-</literal> <type>anymultirange</type>
+        <returnvalue>anymultirange</returnvalue>
+       </para>
+       <para>
+        Computes the difference of the multiranges.
+       </para>
+       <para>
+        <literal>'{[5,20)}'::int8multirange - '{[10,15)}'::int8multirange</literal>
+        <returnvalue>{[5,10), [15,20)}</returnvalue>
+       </para></entry>
+      </row>
+     </tbody>
+    </tgroup>
+   </table>
+
   <para>
    The left-of/right-of/adjacent operators always return false when an empty
-   range is involved; that is, an empty range is not considered to be either
-   before or after any other range.
+   range or multirange is involved; that is, an empty range is not considered to
+   be either before or after any other range.
+  </para>
+
+  <para>
+   Elsewhere empty ranges and multiranges are treated as the additive identity:
+   anything unioned with an empty value is itself. Anything minus an empty
+   value is itself. An empty multirange has exactly the same points as an empty
+   range. Every range contains the empty range. Every multirange contains as many
+   empty ranges as you like.
+  </para>
+
+  <para>
+   The range union and difference operators will fail if the resulting range would
+   need to contain two disjoint sub-ranges, as such a range cannot be
+   represented. There are separate operators for union and difference that take
+   multirange parameters and return a multirange, and they do not fail even if
+   their arguments are disjoint. So if you need a union or difference operation
+   for ranges that may be disjoint, you can avoid errors by first casting your
+   ranges to multiranges.
   </para>
 
   <para>
    <xref linkend="range-functions-table"/> shows the functions
    available for use with range types.
+   <xref linkend="multirange-functions-table"/> shows the functions
+   available for use with multirange types.
   </para>
 
    <table id="range-functions-table">
@@ -18276,10 +18713,185 @@ SELECT NULLIF(value, '(none)') ...
     </tgroup>
    </table>
 
+   <table id="multirange-functions-table">
+    <title>Multirange Functions</title>
+    <tgroup cols="1">
+     <thead>
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        Function
+       </para>
+       <para>
+        Description
+       </para>
+       <para>
+        Example(s)
+       </para></entry>
+      </row>
+     </thead>
+     <tbody>
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>lower</primary>
+        </indexterm>
+        <function>lower</function> ( <type>anymultirange</type> )
+        <returnvalue>anyelement</returnvalue>
+       </para>
+       <para>
+        Extracts the lower bound of the multirange (<literal>NULL</literal> if the
+        multirange is empty or the lower bound is infinite).
+       </para>
+       <para>
+        <literal>lower('{[1.1,2.2)}'::nummultirange)</literal>
+        <returnvalue>1.1</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>upper</primary>
+        </indexterm>
+        <function>upper</function> ( <type>anymultirange</type> )
+        <returnvalue>anyelement</returnvalue>
+       </para>
+       <para>
+        Extracts the upper bound of the multirange (<literal>NULL</literal> if the
+        multirange is empty or the upper bound is infinite).
+       </para>
+       <para>
+        <literal>upper('{[1.1,2.2)}'::nummultirange)</literal>
+        <returnvalue>2.2</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>isempty</primary>
+        </indexterm>
+        <function>isempty</function> ( <type>anymultirange</type> )
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the multirange empty?
+       </para>
+       <para>
+        <literal>isempty('{[1.1,2.2)}'::nummultirange)</literal>
+        <returnvalue>f</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>lower_inc</primary>
+        </indexterm>
+        <function>lower_inc</function> ( <type>anymultirange</type> )
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the multirange's lower bound inclusive?
+       </para>
+       <para>
+        <literal>lower_inc('{[1.1,2.2)}'::nummultirange)</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>upper_inc</primary>
+        </indexterm>
+        <function>upper_inc</function> ( <type>anymultirange</type> )
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the multirange's upper bound inclusive?
+       </para>
+       <para>
+        <literal>upper_inc('{[1.1,2.2)}'::nummultirange)</literal>
+        <returnvalue>f</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>lower_inf</primary>
+        </indexterm>
+        <function>lower_inf</function> ( <type>anymultirange</type> )
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the multirange's lower bound infinite?
+       </para>
+       <para>
+        <literal>lower_inf('{(,)}'::datemultirange)</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>upper_inf</primary>
+        </indexterm>
+        <function>upper_inf</function> ( <type>anymultirange</type> )
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the multirange's upper bound infinite?
+       </para>
+       <para>
+        <literal>upper_inf('{(,)}'::datemultirange)</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>range_merge</primary>
+        </indexterm>
+        <function>range_merge</function> ( <type>anymultirange</type> )
+        <returnvalue>anyrange</returnvalue>
+       </para>
+       <para>
+        Computes the smallest range that includes the entire multirange.
+       </para>
+       <para>
+        <literal>range_merge('{[1,2), [3,4)}'::int4multirange)</literal>
+        <returnvalue>[1,4)</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>multirange</primary>
+        </indexterm>
+        <function>multirange</function> ( <type>anyrange</type> )
+        <returnvalue>anymultirange</returnvalue>
+       </para>
+       <para>
+        Returns a multirange containing just the given range.
+       </para>
+       <para>
+        <literal>multirange('[1,2)'::int4range)</literal>
+        <returnvalue>{[1,2)}</returnvalue>
+       </para></entry>
+      </row>
+     </tbody>
+    </tgroup>
+   </table>
+
   <para>
    The <function>lower_inc</function>, <function>upper_inc</function>,
    <function>lower_inf</function>, and <function>upper_inf</function>
-   functions all return false for an empty range.
+   functions all return false for an empty range or multirange.
   </para>
   </sect1>
 
@@ -18611,6 +19223,36 @@ SELECT NULLIF(value, '(none)') ...
        <entry>Yes</entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>range_agg</primary>
+        </indexterm>
+        <function>range_agg</function> ( <parameter>value</parameter>
+         <type>anyrange</type> )
+        <returnvalue>anymultirange</returnvalue>
+       </para>
+       <para>
+        Computes the union of the non-null input values.
+       </para></entry>
+       <entry>No</entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>range_intersect_agg</primary>
+        </indexterm>
+        <function>range_intersect_agg</function> ( <parameter>value</parameter>
+         <type>anyrange</type> )
+        <returnvalue>anymultirange</returnvalue>
+       </para>
+       <para>
+        Computes the intersection of the non-null input values.
+       </para></entry>
+       <entry>No</entry>
+      </row>
+
       <row>
        <entry role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/doc/src/sgml/rangetypes.sgml b/doc/src/sgml/rangetypes.sgml
index b75fb3a3929..c8784bdce38 100644
--- a/doc/src/sgml/rangetypes.sgml
+++ b/doc/src/sgml/rangetypes.sgml
@@ -27,6 +27,13 @@
   ranges from an instrument, and so forth can also be useful.
  </para>
 
+ <para>
+  Every range type has a corresponding multirange type. A multirange is
+  an ordered list of non-continguous, non-empty, non-null ranges. Most
+  range operators also work on multiranges, and they have a few functions
+  of their own.
+ </para>
+
  <sect2 id="rangetypes-builtin">
   <title>Built-in Range Types</title>
 
@@ -232,10 +239,30 @@ SELECT '[4,4]'::int4range;
 SELECT '[4,4)'::int4range;
 </programlisting>
   </para>
+
+  <para>
+   The input for a multirange is curly brackets (<literal>{</literal> and
+   <literal>}</literal>) containing zero or more valid ranges,
+   separated by commas. Whitespace is permitted around the brackets and
+   commas. This is intended to be reminiscent of array syntax, although
+   multiranges are much simpler: they have just one dimension and there is
+   no need to quote their contents. (The bounds of their ranges may be
+   quoted as above however.)
+  </para>
+
+  <para>
+  Examples:
+<programlisting>
+SELECT '{}'::int4multirange;
+SELECT '{[3,7)}'::int4multirange;
+SELECT '{[3,7), [8,9)}'::int4multirange;
+</programlisting>
+  </para>
+
  </sect2>
 
  <sect2 id="rangetypes-construct">
-  <title>Constructing Ranges</title>
+  <title>Constructing Ranges and Multiranges</title>
 
   <para>
    Each range type has a constructor function with the same name as the range
@@ -267,6 +294,19 @@ SELECT int8range(1, 14, '(]');
 
 -- Using NULL for either bound causes the range to be unbounded on that side.
 SELECT numrange(NULL, 2.2);
+</programlisting>
+  </para>
+
+  <para>
+   Each range type also has a multirange constructor with the same name as the
+   multirange type.  The constructor function takes zero or more arguments
+   which are all ranges of the appropriate type.
+   For example:
+
+<programlisting>
+SELECT nummultirange();
+SELECT nummultirange(numrange(1.0, 14.0));
+SELECT nummultirange(numrange(1.0, 14.0), numrange(20.0, 25.0));
 </programlisting>
   </para>
  </sect2>
@@ -341,6 +381,11 @@ SELECT '[1.234, 5.678]'::floatrange;
    function in this example.
   </para>
 
+  <para>
+   When you define your own range you automatically get a corresponding
+   multirange type.
+  </para>
+
   <para>
    Defining your own range type also allows you to specify a different
    subtype B-tree operator class or collation to use, so as to change the sort
diff --git a/src/backend/catalog/pg_range.c b/src/backend/catalog/pg_range.c
index a606d8c3adb..91b0fb0611a 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;
 	ObjectAddresses *addrs;
 
 	pg_range = table_open(RangeRelationId, RowExclusiveLock);
@@ -55,6 +56,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_rngmultitypid - 1] = ObjectIdGetDatum(multirangeTypeOid);
 
 	tup = heap_form_tuple(RelationGetDescr(pg_range), values, nulls);
 
@@ -93,6 +95,12 @@ RangeCreate(Oid rangeTypeOid, Oid rangeSubType, Oid rangeCollation,
 	record_object_address_dependencies(&myself, addrs, DEPENDENCY_NORMAL);
 	free_object_addresses(addrs);
 
+	/* 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 aeb4a54f635..44df297dda0 100644
--- a/src/backend/catalog/pg_type.c
+++ b/src/backend/catalog/pg_type.c
@@ -28,6 +28,7 @@
 #include "catalog/pg_proc.h"
 #include "catalog/pg_type.h"
 #include "commands/typecmds.h"
+#include "mb/pg_wchar.h"
 #include "miscadmin.h"
 #include "parser/scansup.h"
 #include "utils/acl.h"
@@ -37,6 +38,9 @@
 #include "utils/rel.h"
 #include "utils/syscache.h"
 
+static char *makeUniqueTypeName(const char *typeName, Oid typeNamespace,
+								bool tryOriginal);
+
 /* Potentially set by pg_upgrade_support functions */
 Oid			binary_upgrade_next_pg_type_oid = InvalidOid;
 
@@ -854,31 +858,10 @@ RenameTypeInternal(Oid typeOid, const char *newTypeName, Oid typeNamespace)
 char *
 makeArrayTypeName(const char *typeName, Oid typeNamespace)
 {
-	char	   *arr = (char *) palloc(NAMEDATALEN);
-	int			namelen = strlen(typeName);
-	int			i;
-
-	/*
-	 * 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;
-	}
+	char	   *arr;
 
-	if (i >= NAMEDATALEN - 1)
+	arr = makeUniqueTypeName(typeName, typeNamespace, false);
+	if (arr == NULL)
 		ereport(ERROR,
 				(errcode(ERRCODE_DUPLICATE_OBJECT),
 				 errmsg("could not form array type name for type \"%s\"",
@@ -949,3 +932,91 @@ moveArrayTypeName(Oid typeOid, const char *typeName, Oid typeNamespace)
 
 	return true;
 }
+
+
+/*
+ * makeMultirangeTypeName
+ *	  - given a range type name, make a multirange type name for it
+ *
+ * caller is responsible for pfreeing the result
+ */
+char *
+makeMultirangeTypeName(const char *rangeTypeName, Oid typeNamespace)
+{
+	char	   *buf;
+	char	   *mrname;
+	char	   *rangestr;
+
+	/*
+	 * 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...
+	 */
+	rangestr = strstr(rangeTypeName, "range");
+	if (rangestr)
+	{
+		char	*prefix = pnstrdup(rangeTypeName, rangestr - rangeTypeName);
+
+		buf = psprintf("%s%s%s", prefix, "multi", rangestr);
+	}
+	else
+		buf = psprintf("%s_multirange", pnstrdup(rangeTypeName, NAMEDATALEN - 12));
+
+	/* clip it at NAMEDATALEN-1 bytes */
+	buf[pg_mbcliplen(buf, strlen(buf), NAMEDATALEN - 1)] = '\0';
+
+	mrname = makeUniqueTypeName(buf, typeNamespace, true);
+	if (mrname == NULL)
+		ereport(ERROR,
+				(errcode(ERRCODE_DUPLICATE_OBJECT),
+				 errmsg("could not form multirange type name for type \"%s\"",
+						rangeTypeName)));
+
+	return mrname;
+}
+
+/*
+ * makeUniqueTypeName
+ *		Generate a unique name for a prospective new type
+ *
+ * Given a typeName, return a new palloc'ed name by preprending underscores
+ * until a non-conflicting name results.
+ *
+ * If tryOriginal, first try with zero underscores.
+ */
+static char *
+makeUniqueTypeName(const char *typeName, Oid typeNamespace, bool tryOriginal)
+{
+	int			i;
+	int			namelen;
+	char		dest[NAMEDATALEN];
+
+	Assert(strlen(typeName) <= NAMEDATALEN - 1);
+
+	if (tryOriginal &&
+		!SearchSysCacheExists2(TYPENAMENSP,
+							   CStringGetDatum(typeName),
+							   ObjectIdGetDatum(typeNamespace)))
+		return pstrdup(typeName);
+
+	/*
+	 * The idea is to prepend underscores as needed until we make a name that
+	 * doesn't collide with anything ...
+	 */
+	namelen = strlen(typeName);
+	for (i = 1; i < NAMEDATALEN - 1; i++)
+	{
+		dest[i - 1] = '_';
+		strlcpy(dest + i, typeName, NAMEDATALEN - i);
+		if (namelen + i >= NAMEDATALEN)
+			truncate_identifier(dest, NAMEDATALEN, false);
+
+		if (!SearchSysCacheExists2(TYPENAMENSP,
+								   CStringGetDatum(dest),
+								   ObjectIdGetDatum(typeNamespace)))
+			return pstrdup(dest);
+	}
+
+	return NULL;
+}
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index 483bb65ddc8..b24fc1f5660 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -105,9 +105,14 @@ typedef struct
 
 /* Potentially set by pg_upgrade_support functions */
 Oid			binary_upgrade_next_array_pg_type_oid = InvalidOid;
+Oid			binary_upgrade_next_mrng_pg_type_oid = InvalidOid;
+Oid			binary_upgrade_next_mrng_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 rangeOid,
+									   Oid rangeArrayOid, Oid *castFuncOid);
 static Oid	findTypeInputFunction(List *procname, Oid typeOid);
 static Oid	findTypeOutputFunction(List *procname, Oid typeOid);
 static Oid	findTypeReceiveFunction(List *procname, Oid typeOid);
@@ -739,7 +744,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",
@@ -1280,6 +1286,11 @@ checkEnumOwner(HeapTuple tup)
 /*
  * DefineRange
  *		Registers a new range type.
+ *
+ * Perhaps it might be worthwhile to set pg_type.typelem to the base type,
+ * and likewise on multiranges to set it to the range type. But having a
+ * non-zero typelem is treated elsewhere as a synonym for being an array,
+ * and users might have queries with that same assumption.
  */
 ObjectAddress
 DefineRange(CreateRangeStmt *stmt)
@@ -1288,7 +1299,11 @@ DefineRange(CreateRangeStmt *stmt)
 	Oid			typeNamespace;
 	Oid			typoid;
 	char	   *rangeArrayName;
+	char	   *multirangeTypeName;
+	char	   *multirangeArrayName;
 	Oid			rangeArrayOid;
+	Oid			multirangeOid;
+	Oid			multirangeArrayOid;
 	Oid			rangeSubtype = InvalidOid;
 	List	   *rangeSubOpclassName = NIL;
 	List	   *rangeCollationName = NIL;
@@ -1305,6 +1320,8 @@ DefineRange(CreateRangeStmt *stmt)
 	AclResult	aclresult;
 	ListCell   *lc;
 	ObjectAddress address;
+	ObjectAddress mltrngaddress;
+	Oid			castFuncOid;
 
 	/* Convert list of names to a name and namespace */
 	typeNamespace = QualifiedNameGetCreationNamespace(stmt->typeName,
@@ -1453,8 +1470,10 @@ DefineRange(CreateRangeStmt *stmt)
 	/* alignment must be TYPALIGN_INT or TYPALIGN_DOUBLE for ranges */
 	alignment = (subtypalign == TYPALIGN_DOUBLE) ? TYPALIGN_DOUBLE : TYPALIGN_INT;
 
-	/* Allocate OID for array type */
+	/* Allocate OID for array type, its multirange, and its multirange array */
 	rangeArrayOid = AssignTypeArrayOid();
+	multirangeOid = AssignTypeMultirangeOid();
+	multirangeArrayOid = AssignTypeMultirangeArrayOid();
 
 	/* Create the pg_type entry */
 	address =
@@ -1492,9 +1511,46 @@ DefineRange(CreateRangeStmt *stmt)
 	Assert(typoid == InvalidOid || typoid == address.objectId);
 	typoid = address.objectId;
 
+	/* Create the multirange that goes with it */
+	multirangeTypeName = makeMultirangeTypeName(typeName, typeNamespace);
+
+	mltrngaddress =
+		TypeCreate(multirangeOid,	/* force assignment of this 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_RANGE,	/* type-category (range 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) */
+	Assert(multirangeOid == mltrngaddress.objectId);
+
 	/* Create the entry in pg_range */
 	RangeCreate(typoid, rangeSubtype, rangeCollation, rangeSubOpclass,
-				rangeCanonical, rangeSubtypeDiff);
+				rangeCanonical, rangeSubtypeDiff, multirangeOid);
 
 	/*
 	 * Create the array type that goes with it.
@@ -1535,8 +1591,53 @@ 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 */
+			   multirangeOid,	/* 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,
+							   multirangeOid, typoid, rangeArrayOid,
+							   &castFuncOid);
+
+	/* Create cast from the range type to its multirange type */
+	CastCreate(typoid, multirangeOid, castFuncOid, 'e', 'f', DEPENDENCY_INTERNAL);
+
+	pfree(multirangeTypeName);
+	pfree(multirangeArrayName);
 
 	return address;
 }
@@ -1614,6 +1715,147 @@ 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.
+ *
+ * Sets castFuncOid to the oid of the new constructor that can be used
+ * to cast from a range to a multirange.
+ */
+static void
+makeMultirangeConstructors(const char *name, Oid namespace,
+						   Oid multirangeOid, Oid rangeOid, Oid rangeArrayOid,
+						   Oid *castFuncOid)
+{
+	ObjectAddress myself,
+				referenced;
+	oidvector  *argtypes;
+	Datum		allParamTypes;
+	ArrayType  *allParameterTypes;
+	Datum		paramModes;
+	ArrayType  *parameterModes;
+
+	referenced.classId = TypeRelationId;
+	referenced.objectId = multirangeOid;
+	referenced.objectSubId = 0;
+
+	/* 0-arg constructor - for empty multiranges */
+	argtypes = buildoidvector(NULL, 0);
+	myself = ProcedureCreate(name,	/* name: same as multirange type */
+							 namespace,
+							 false,	/* replace */
+							 false,	/* returns set */
+							 multirangeOid,	/* return type */
+							 BOOTSTRAP_SUPERUSERID,	/* proowner */
+							 INTERNALlanguageId,	/* language */
+							 F_FMGR_INTERNAL_VALIDATOR,
+							 "multirange_constructor0",	/* prosrc */
+							 NULL,	/* probin */
+							 PROKIND_FUNCTION,
+							 false,	/* security_definer */
+							 false,	/* leakproof */
+							 false, /* isStrict */
+							 PROVOLATILE_IMMUTABLE,	/* volatility */
+							 PROPARALLEL_SAFE,	/* parallel safety */
+							 argtypes,	/* parameterTypes */
+							 PointerGetDatum(NULL),	/* allParameterTypes */
+							 PointerGetDatum(NULL),	/* parameterModes */
+							 PointerGetDatum(NULL),	/* parameterNames */
+							 NIL,	/* parameterDefaults */
+							 PointerGetDatum(NULL),	/* trftypes */
+							 PointerGetDatum(NULL),	/* proconfig */
+							 InvalidOid,	/* prosupport */
+							 1.0,	/* procost */
+							 0.0);	/* prorows */
+	/*
+	 * Make the constructor 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);
+	pfree(argtypes);
+
+	/*
+	 * 1-arg constructor - for casts
+	 *
+	 * In theory we shouldn't need both this and the vararg (n-arg) constructor,
+	 * but having a separate 1-arg function lets us define casts against it.
+	 */
+	argtypes = buildoidvector(&rangeOid, 1);
+	myself = ProcedureCreate(name,	/* name: same as multirange type */
+							 namespace,
+							 false,	/* replace */
+							 false,	/* returns set */
+							 multirangeOid,	/* return type */
+							 BOOTSTRAP_SUPERUSERID,	/* proowner */
+							 INTERNALlanguageId,	/* language */
+							 F_FMGR_INTERNAL_VALIDATOR,
+							 "multirange_constructor1",	/* prosrc */
+							 NULL,	/* probin */
+							 PROKIND_FUNCTION,
+							 false,	/* security_definer */
+							 false,	/* leakproof */
+							 true, /* isStrict */
+							 PROVOLATILE_IMMUTABLE,	/* volatility */
+							 PROPARALLEL_SAFE,	/* parallel safety */
+							 argtypes,	/* parameterTypes */
+							 PointerGetDatum(NULL),	/* allParameterTypes */
+							 PointerGetDatum(NULL),	/* parameterModes */
+							 PointerGetDatum(NULL),	/* parameterNames */
+							 NIL,	/* parameterDefaults */
+							 PointerGetDatum(NULL),	/* trftypes */
+							 PointerGetDatum(NULL),	/* proconfig */
+							 InvalidOid,	/* prosupport */
+							 1.0,	/* procost */
+							 0.0);	/* prorows */
+	/* ditto */
+	recordDependencyOn(&myself, &referenced, DEPENDENCY_INTERNAL);
+	pfree(argtypes);
+	*castFuncOid = myself.objectId;
+
+	/* n-arg constructor - vararg */
+	argtypes = buildoidvector(&rangeArrayOid, 1);
+	allParamTypes = ObjectIdGetDatum(rangeArrayOid);
+	allParameterTypes = construct_array(&allParamTypes,
+										1, OIDOID,
+										sizeof(Oid), true, 'i');
+	paramModes = CharGetDatum(FUNC_PARAM_VARIADIC);
+	parameterModes = construct_array(&paramModes, 1, CHAROID,
+									 1, true, 'c');
+	myself = ProcedureCreate(name,	/* name: same as multirange type */
+							 namespace,
+							 false,	/* replace */
+							 false,	/* returns set */
+							 multirangeOid,	/* return type */
+							 BOOTSTRAP_SUPERUSERID,	/* proowner */
+							 INTERNALlanguageId,	/* language */
+							 F_FMGR_INTERNAL_VALIDATOR,
+							 "multirange_constructor2",	/* prosrc */
+							 NULL,	/* probin */
+							 PROKIND_FUNCTION,
+							 false,	/* security_definer */
+							 false,	/* leakproof */
+							 false, /* isStrict */
+							 PROVOLATILE_IMMUTABLE,	/* volatility */
+							 PROPARALLEL_SAFE,	/* parallel safety */
+							 argtypes,	/* parameterTypes */
+							 PointerGetDatum(allParameterTypes),	/* allParameterTypes */
+							 PointerGetDatum(parameterModes),	/* parameterModes */
+							 PointerGetDatum(NULL),	/* parameterNames */
+							 NIL,	/* parameterDefaults */
+							 PointerGetDatum(NULL),	/* trftypes */
+							 PointerGetDatum(NULL),	/* proconfig */
+							 InvalidOid,	/* prosupport */
+							 1.0,	/* procost */
+							 0.0);	/* prorows */
+	/* ditto */
+	recordDependencyOn(&myself, &referenced, DEPENDENCY_INTERNAL);
+	pfree(argtypes);
+	pfree(allParameterTypes);
+	pfree(parameterModes);
+}
 
 /*
  * Find suitable I/O functions for a type.
@@ -2068,6 +2310,72 @@ AssignTypeArrayOid(void)
 	return type_array_oid;
 }
 
+/*
+ *	AssignTypeMultirangeOid
+ *
+ *	Pre-assign the range type's multirange OID for use in pg_type.oid
+ */
+Oid
+AssignTypeMultirangeOid(void)
+{
+	Oid			type_multirange_oid;
+
+	/* Use binary-upgrade override for pg_type.oid? */
+	if (IsBinaryUpgrade)
+	{
+		if (!OidIsValid(binary_upgrade_next_mrng_pg_type_oid))
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+					 errmsg("pg_type multirange OID value not set when in binary upgrade mode")));
+
+		type_multirange_oid = binary_upgrade_next_mrng_pg_type_oid;
+		binary_upgrade_next_mrng_pg_type_oid = InvalidOid;
+	}
+	else
+	{
+		Relation	pg_type = table_open(TypeRelationId, AccessShareLock);
+
+		type_multirange_oid = GetNewOidWithIndex(pg_type, TypeOidIndexId,
+												 Anum_pg_type_oid);
+		table_close(pg_type, AccessShareLock);
+	}
+
+	return type_multirange_oid;
+}
+
+/*
+ *	AssignTypeMultirangeArrayOid
+ *
+ *	Pre-assign the range type's multirange array OID for use in pg_type.typarray
+ */
+Oid
+AssignTypeMultirangeArrayOid(void)
+{
+	Oid			type_multirange_array_oid;
+
+	/* Use binary-upgrade override for pg_type.oid? */
+	if (IsBinaryUpgrade)
+	{
+		if (!OidIsValid(binary_upgrade_next_mrng_array_pg_type_oid))
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+					 errmsg("pg_type multirange array OID value not set when in binary upgrade mode")));
+
+		type_multirange_array_oid = binary_upgrade_next_mrng_array_pg_type_oid;
+		binary_upgrade_next_mrng_array_pg_type_oid = InvalidOid;
+	}
+	else
+	{
+		Relation	pg_type = table_open(TypeRelationId, AccessShareLock);
+
+		type_multirange_array_oid = GetNewOidWithIndex(pg_type, TypeOidIndexId,
+													   Anum_pg_type_oid);
+		table_close(pg_type, AccessShareLock);
+	}
+
+	return type_multirange_array_oid;
+}
+
 
 /*-------------------------------------------------------------------
  * DefineCompositeType
diff --git a/src/backend/executor/functions.c b/src/backend/executor/functions.c
index 459a33375b1..ca8d637e73a 100644
--- a/src/backend/executor/functions.c
+++ b/src/backend/executor/functions.c
@@ -1711,7 +1711,8 @@ check_sql_fn_retval(List *queryTreeLists,
 	if (fn_typtype == TYPTYPE_BASE ||
 		fn_typtype == TYPTYPE_DOMAIN ||
 		fn_typtype == TYPTYPE_ENUM ||
-		fn_typtype == TYPTYPE_RANGE)
+		fn_typtype == TYPTYPE_RANGE ||
+		fn_typtype == TYPTYPE_MULTIRANGE)
 	{
 		/*
 		 * For scalar-type returns, the target list must have exactly one
diff --git a/src/backend/parser/parse_coerce.c b/src/backend/parser/parse_coerce.c
index a2924e3d1ce..fb33b30e369 100644
--- a/src/backend/parser/parse_coerce.c
+++ b/src/backend/parser/parse_coerce.c
@@ -189,19 +189,21 @@ coerce_type(ParseState *pstate, Node *node,
 	if (targetTypeId == ANYARRAYOID ||
 		targetTypeId == ANYENUMOID ||
 		targetTypeId == ANYRANGEOID ||
+		targetTypeId == ANYMULTIRANGEOID ||
 		targetTypeId == ANYCOMPATIBLEARRAYOID ||
-		targetTypeId == ANYCOMPATIBLERANGEOID)
+		targetTypeId == ANYCOMPATIBLERANGEOID ||
+		targetTypeId == ANYCOMPATIBLEMULTIRANGEOID)
 	{
 		/*
 		 * 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 the pseudotype's input
-		 * function, 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
+		 * the pseudotype's input function, 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
@@ -1569,8 +1571,8 @@ select_common_typmod(ParseState *pstate, List *exprs, Oid common_type)
  * 1) All arguments declared ANYELEMENT must have the same datatype.
  * 2) All arguments declared ANYARRAY must have the same datatype,
  *	  which must be a varlena array type.
- * 3) All arguments declared ANYRANGE must have the same datatype,
- *	  which must be a range type.
+ * 3) All arguments declared ANYRANGE or ANYMULTIRANGE must be a range or
+ *	  multirange type, all derived from the same base datatype.
  * 4) If there are arguments of more than one of these polymorphic types,
  *	  the array element type and/or range subtype must be the same as each
  *	  other and the same as the ANYELEMENT type.
@@ -1585,8 +1587,8 @@ select_common_typmod(ParseState *pstate, List *exprs, Oid common_type)
  *	  to a common supertype (chosen as per select_common_type's rules).
  *	  ANYCOMPATIBLENONARRAY works like ANYCOMPATIBLE but also requires the
  *	  common supertype to not be an array.  If there are ANYCOMPATIBLEARRAY
- *	  or ANYCOMPATIBLERANGE arguments, their element types or subtypes are
- *	  included while making the choice of common supertype.
+ *	  or ANYCOMPATIBLERANGE or ANYCOMPATIBLEMULTIRANGE arguments, their element
+ *	  types or subtypes are included while making the choice of common supertype.
  * 8) The resolved type of ANYCOMPATIBLEARRAY arguments will be the array
  *	  type over the common supertype (which might not be the same array type
  *	  as any of the original arrays).
@@ -1594,6 +1596,10 @@ select_common_typmod(ParseState *pstate, List *exprs, Oid common_type)
  *	  (after domain flattening), since we have no preference rule that would
  *	  let us choose one over another.  Furthermore, that range's subtype
  *	  must exactly match the common supertype chosen by rule 7.
+ * 10) All ANYCOMPATIBLEMULTIRANGE arguments must be the exact same multirange
+ *	  type (after domain flattening), since we have no preference rule that would
+ *	  let us choose one over another.  Furthermore, that multirange's range's
+ *	  subtype must exactly match the common supertype chosen by rule 7.
  *
  * Domains over arrays match ANYARRAY, and are immediately flattened to their
  * base type.  (Thus, for example, we will consider it a match if one ANYARRAY
@@ -1602,7 +1608,9 @@ select_common_typmod(ParseState *pstate, List *exprs, Oid common_type)
  * for ANYCOMPATIBLEARRAY and ANYCOMPATIBLENONARRAY.
  *
  * Similarly, domains over ranges match ANYRANGE or ANYCOMPATIBLERANGE,
- * and are immediately flattened to their base type.
+ * and are immediately flattened to their base type, and domains over
+ * multiranges match ANYMULTIRANGE or ANYCOMPATIBLEMULTIRANGE and are immediately
+ * flattened to their base type.
  *
  * Note that domains aren't currently considered to match ANYENUM,
  * even if their base type would match.
@@ -1620,8 +1628,12 @@ check_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			anycompatible_range_typeid = InvalidOid;
 	Oid			anycompatible_range_typelem = InvalidOid;
+	Oid			anycompatible_multirange_typeid = InvalidOid;
+	Oid			anycompatible_multirange_typelem = InvalidOid;
+	Oid			range_typelem = InvalidOid;
 	bool		have_anynonarray = false;
 	bool		have_anyenum = false;
 	bool		have_anycompatible_nonarray = false;
@@ -1670,6 +1682,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;
+		}
 		else if (decl_type == ANYCOMPATIBLEOID ||
 				 decl_type == ANYCOMPATIBLENONARRAYOID)
 		{
@@ -1714,6 +1735,42 @@ check_generic_type_consistency(const Oid *actual_arg_types,
 				anycompatible_actual_types[n_anycompatible_args++] = anycompatible_range_typelem;
 			}
 		}
+		else if (decl_type == ANYCOMPATIBLEMULTIRANGEOID)
+		{
+			if (actual_type == UNKNOWNOID)
+				continue;
+			actual_type = getBaseType(actual_type); /* flatten domains */
+			if (OidIsValid(anycompatible_multirange_typeid))
+			{
+				/* All ANYCOMPATIBLEMULTIRANGE arguments must be the same type */
+				if (anycompatible_multirange_typeid != actual_type)
+					return false;
+			}
+			else
+			{
+				anycompatible_multirange_typeid = actual_type;
+				anycompatible_multirange_typelem = get_multirange_range(actual_type);
+				if (!OidIsValid(anycompatible_multirange_typelem))
+					return false;	/* not a multirange type */
+
+				if (OidIsValid(anycompatible_range_typeid))
+				{
+					/* ANYCOMPATIBLEMULTIRANGE and ANYCOMPATIBLERANGE arguments must match */
+					if (anycompatible_range_typeid != anycompatible_multirange_typelem)
+						return false;
+				}
+				else
+				{
+					anycompatible_range_typeid = anycompatible_multirange_typelem;
+					anycompatible_range_typelem = get_range_subtype(anycompatible_range_typeid);
+					if (!OidIsValid(anycompatible_range_typelem))
+						return false;	/* not a range type */
+				}
+				/* collect the subtype for common-supertype choice */
+				anycompatible_actual_types[n_anycompatible_args++] =
+					anycompatible_range_typelem;
+			}
+		}
 	}
 
 	/* Get the element type based on the array type, if we have one */
@@ -1760,8 +1817,6 @@ check_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))
 	{
-		Oid			range_typelem;
-
 		range_typelem = get_range_subtype(range_typeid);
 		if (!OidIsValid(range_typelem))
 			return false;		/* should be a range, but isn't */
@@ -1780,6 +1835,45 @@ 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))
+	{
+		Oid			multirange_typelem;
+
+		multirange_typelem = get_multirange_range(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 */
+		}
+		else if (multirange_typelem != range_typeid)
+		{
+			/* otherwise, they better match */
+			return false;
+		}
+
+		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 */
@@ -1818,8 +1912,10 @@ check_generic_type_consistency(const Oid *actual_arg_types,
 		}
 
 		/*
-		 * the anycompatible type must exactly match the range element type,
-		 * if we were able to identify one
+		 * The anycompatible type must exactly match the range element type,
+		 * if we were able to identify one. This checks compatibility for
+		 * anycompatiblemultirange too since that also sets
+		 * anycompatible_range_typelem above.
 		 */
 		if (OidIsValid(anycompatible_range_typelem) &&
 			anycompatible_range_typelem != anycompatible_typeid)
@@ -1858,21 +1954,27 @@ check_generic_type_consistency(const Oid *actual_arg_types,
  *	  argument's actual type as the function's return type.
  * 2) If return type is ANYARRAY, and any argument is ANYARRAY, use the
  *	  argument's actual type as the function's return type.
- * 3) Similarly, if return type is ANYRANGE, and any argument is ANYRANGE,
- *	  use the argument's actual type as the function's return type.
- * 4) Otherwise, if return type is ANYELEMENT or ANYARRAY, and there is
+ * 3) Similarly, if return type is ANYRANGE or ANYMULTIRANGE, and any
+ *	  argument is ANYRANGE or ANYMULTIRANGE, use that argument's
+ *	  actual type, range type or multirange type as the function's return
+ *	  type.
+ * 4) Otherwise, if return type is ANYMULTIRANGE, and any argument is
+ *	  ANYMULTIRANGE, use the argument's actual type as the function's return
+ *	  type. Or if any argument is ANYRANGE, use its multirange type as the
+ *	  function's return type.
+ * 5) Otherwise, if return type is ANYELEMENT or ANYARRAY, and there is
  *	  at least one ANYELEMENT, ANYARRAY, or ANYRANGE input, deduce the
  *	  return type from those inputs, or throw error if we can't.
- * 5) Otherwise, if return type is ANYRANGE, throw error.  (We have no way to
- *	  select a specific range type if the arguments don't include ANYRANGE.)
- * 6) ANYENUM is treated the same as ANYELEMENT except that if it is used
+ * 6) Otherwise, if return type is ANYRANGE or ANYMULTIRANGE, throw error.
+ *	  (We have no way to select a specific range type if the arguments don't
+ *	  include ANYRANGE.)
  *	  (alone or in combination with plain ANYELEMENT), we add the extra
  *	  condition that the ANYELEMENT type must be an enum.
- * 7) ANYNONARRAY is treated the same as ANYELEMENT except that if it is used,
+ * 8) ANYNONARRAY is treated the same as ANYELEMENT except that if it is used,
  *	  we add the extra condition that the ANYELEMENT type must not be an array.
  *	  (This is a no-op if used in combination with ANYARRAY or ANYENUM, but
  *	  is an extra restriction if not.)
- * 8) ANYCOMPATIBLE, ANYCOMPATIBLEARRAY, ANYCOMPATIBLENONARRAY, and
+ * 9) ANYCOMPATIBLE, ANYCOMPATIBLEARRAY, ANYCOMPATIBLENONARRAY, and
  *	  ANYCOMPATIBLERANGE are handled by resolving the common supertype
  *	  of those arguments (or their element types/subtypes, for array and range
  *	  inputs), and then coercing all those arguments to the common supertype,
@@ -1926,10 +2028,15 @@ 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			anycompatible_typeid = InvalidOid;
 	Oid			anycompatible_array_typeid = InvalidOid;
 	Oid			anycompatible_range_typeid = InvalidOid;
 	Oid			anycompatible_range_typelem = InvalidOid;
+	Oid			anycompatible_multirange_typeid = InvalidOid;
+	Oid			anycompatible_multirange_typelem = InvalidOid;
+	Oid			range_typelem;
+	Oid			multirange_typelem;
 	bool		have_anynonarray = (rettype == ANYNONARRAYOID);
 	bool		have_anyenum = (rettype == ANYENUMOID);
 	bool		have_anycompatible_nonarray = (rettype == ANYCOMPATIBLENONARRAYOID);
@@ -2014,6 +2121,26 @@ enforce_generic_type_consistency(const Oid *actual_arg_types,
 								   format_type_be(actual_type))));
 			range_typeid = actual_type;
 		}
+		else if (decl_type == ANYMULTIRANGEOID)
+		{
+			n_poly_args++;
+			if (actual_type == UNKNOWNOID)
+			{
+				have_poly_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;
+		}
 		else if (decl_type == ANYCOMPATIBLEOID ||
 				 decl_type == ANYCOMPATIBLENONARRAYOID)
 		{
@@ -2082,6 +2209,40 @@ enforce_generic_type_consistency(const Oid *actual_arg_types,
 				anycompatible_actual_types[n_anycompatible_args++] = anycompatible_range_typelem;
 			}
 		}
+		else if (decl_type == ANYCOMPATIBLEMULTIRANGEOID)
+		{
+			have_poly_anycompatible = true;
+			if (actual_type == UNKNOWNOID)
+				continue;
+			if (allow_poly && decl_type == actual_type)
+				continue;		/* no new information here */
+			actual_type = getBaseType(actual_type); /* flatten domains */
+			if (OidIsValid(anycompatible_multirange_typeid))
+			{
+				/* All ANYCOMPATIBLEMULTIRANGE arguments must be the same type */
+				if (anycompatible_multirange_typeid != actual_type)
+					ereport(ERROR,
+							(errcode(ERRCODE_DATATYPE_MISMATCH),
+							 errmsg("arguments declared \"anycompatiblemultirange\" are not all alike"),
+							 errdetail("%s versus %s",
+									   format_type_be(anycompatible_multirange_typeid),
+									   format_type_be(actual_type))));
+			}
+			else
+			{
+				anycompatible_multirange_typeid = actual_type;
+				anycompatible_multirange_typelem = get_multirange_range(actual_type);
+				anycompatible_range_typelem = get_range_subtype(anycompatible_multirange_typelem);
+				if (!OidIsValid(anycompatible_multirange_typelem))
+					ereport(ERROR,
+							(errcode(ERRCODE_DATATYPE_MISMATCH),
+							 errmsg("argument declared %s is not a multirange type but type %s",
+									"anycompatiblemultirange",
+									format_type_be(actual_type))));
+				/* collect the subtype for common-supertype choice */
+				anycompatible_actual_types[n_anycompatible_args++] = anycompatible_range_typelem;
+			}
+		}
 	}
 
 	/*
@@ -2150,8 +2311,6 @@ 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))
 		{
-			Oid			range_typelem;
-
 			range_typelem = get_range_subtype(range_typeid);
 			if (!OidIsValid(range_typelem))
 				ereport(ERROR,
@@ -2180,6 +2339,61 @@ enforce_generic_type_consistency(const Oid *actual_arg_types,
 								   format_type_be(elem_typeid))));
 			}
 		}
+		else
+			range_typelem = InvalidOid;
+
+		/* Get the element type based on the multirange type, if we have one */
+		if (OidIsValid(multirange_typeid))
+		{
+			multirange_typelem = get_multirange_range(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))));
+			}
+
+			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 */
+				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))));
+			}
+		}
+		else
+			multirange_typelem = InvalidOid;
 
 		if (!OidIsValid(elem_typeid))
 		{
@@ -2188,6 +2402,7 @@ enforce_generic_type_consistency(const Oid *actual_arg_types,
 				elem_typeid = ANYELEMENTOID;
 				array_typeid = ANYARRAYOID;
 				range_typeid = ANYRANGEOID;
+				multirange_typeid = ANYMULTIRANGEOID;
 			}
 			else
 			{
@@ -2287,6 +2502,7 @@ enforce_generic_type_consistency(const Oid *actual_arg_types,
 				anycompatible_typeid = ANYCOMPATIBLEOID;
 				anycompatible_array_typeid = ANYCOMPATIBLEARRAYOID;
 				anycompatible_range_typeid = ANYCOMPATIBLERANGEOID;
+				anycompatible_multirange_typeid = ANYCOMPATIBLEMULTIRANGEOID;
 			}
 			else
 			{
@@ -2318,6 +2534,8 @@ enforce_generic_type_consistency(const Oid *actual_arg_types,
 				declared_arg_types[j] = anycompatible_array_typeid;
 			else if (decl_type == ANYCOMPATIBLERANGEOID)
 				declared_arg_types[j] = anycompatible_range_typeid;
+			else if (decl_type == ANYCOMPATIBLEMULTIRANGEOID)
+				declared_arg_types[j] = anycompatible_multirange_typeid;
 		}
 	}
 
@@ -2368,6 +2586,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;
+			}
 		}
 	}
 
@@ -2404,6 +2633,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 ANYCOMPATIBLE use the appropriate type */
 	if (rettype == ANYCOMPATIBLEOID ||
 		rettype == ANYCOMPATIBLENONARRAYOID)
@@ -2438,6 +2683,17 @@ enforce_generic_type_consistency(const Oid *actual_arg_types,
 		return anycompatible_range_typeid;
 	}
 
+	/* if we return ANYCOMPATIBLEMULTIRANGE use the appropriate argument type */
+	if (rettype == ANYCOMPATIBLEMULTIRANGEOID)
+	{
+		/* this error is unreachable if the function signature is valid: */
+		if (!OidIsValid(anycompatible_multirange_typeid))
+			ereport(ERROR,
+					(errcode(ERRCODE_DATATYPE_MISMATCH),
+					 errmsg_internal("could not identify anycompatiblemultirange type")));
+		return anycompatible_multirange_typeid;
+	}
+
 	/* we don't return a generic type; send back the original return type */
 	return rettype;
 }
@@ -2455,20 +2711,37 @@ check_valid_polymorphic_signature(Oid ret_type,
 								  const Oid *declared_arg_types,
 								  int nargs)
 {
-	if (ret_type == ANYRANGEOID || ret_type == ANYCOMPATIBLERANGEOID)
+	if (ret_type == ANYRANGEOID || ret_type == ANYMULTIRANGEOID)
 	{
 		/*
-		 * ANYRANGE requires an ANYRANGE input, else we can't tell which of
-		 * several range types with the same element type to use.  Likewise
-		 * for ANYCOMPATIBLERANGE.
+		 * ANYRANGE and ANYMULTIRANGE require an ANYRANGE or ANYMULTIRANGE input,
+		 * else we can't tell which of several range types with the same element
+		 * type to use.
 		 */
 		for (int i = 0; i < nargs; i++)
 		{
-			if (declared_arg_types[i] == ret_type)
+			if (declared_arg_types[i] == ANYRANGEOID ||
+				declared_arg_types[i] == ANYMULTIRANGEOID)
 				return NULL;	/* OK */
 		}
-		return psprintf(_("A result of type %s requires at least one input of type %s."),
-						format_type_be(ret_type), format_type_be(ret_type));
+		return psprintf(_("A result of type %s requires at least one input of type anyrange or anymultirange."),
+						format_type_be(ret_type));
+	}
+	else if (ret_type == ANYCOMPATIBLERANGEOID || ret_type == ANYCOMPATIBLEMULTIRANGEOID)
+	{
+		/*
+		 * ANYCOMPATIBLERANGE and ANYCOMPATIBLEMULTIRANGE require an ANYCOMPATIBLERANGE or
+		 * ANYCOMPATIBLEMULTIRANGE input, else we can't tell which of several range types
+		 * with the same element type to use.
+		 */
+		for (int i = 0; i < nargs; i++)
+		{
+			if (declared_arg_types[i] == ANYCOMPATIBLERANGEOID ||
+				declared_arg_types[i] == ANYCOMPATIBLEMULTIRANGEOID)
+				return NULL;	/* OK */
+		}
+		return psprintf(_("A result of type %s requires at least one input of type anycompatiblerange or anycompatiblemultirange."),
+						format_type_be(ret_type));
 	}
 	else if (IsPolymorphicTypeFamily1(ret_type))
 	{
@@ -2479,7 +2752,7 @@ check_valid_polymorphic_signature(Oid ret_type,
 				return NULL;	/* OK */
 		}
 		/* Keep this list in sync with IsPolymorphicTypeFamily1! */
-		return psprintf(_("A result of type %s requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange."),
+		return psprintf(_("A result of type %s requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange."),
 						format_type_be(ret_type));
 	}
 	else if (IsPolymorphicTypeFamily2(ret_type))
@@ -2631,6 +2904,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 || targettype == ANYCOMPATIBLEMULTIRANGEOID)
+		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 b4d55e849b3..22a412d4379 100644
--- a/src/backend/utils/adt/Makefile
+++ b/src/backend/utils/adt/Makefile
@@ -59,6 +59,8 @@ OBJS = \
 	mac8.o \
 	mcxtfuncs.o \
 	misc.o \
+	multirangetypes.o \
+	multirangetypes_selfuncs.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 00000000000..1240400b0e6
--- /dev/null
+++ b/src/backend/utils/adt/multirangetypes.c
@@ -0,0 +1,2536 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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/int8.h"
+#include "utils/lsyscache.h"
+#include "utils/rangetypes.h"
+#include "utils/multirangetypes.h"
+#include "utils/timestamp.h"
+#include "utils/array.h"
+#include "utils/memutils.h"
+
+/* fn_extra cache entry for one of the range I/O functions */
+typedef struct MultirangeIOData
+{
+	TypeCacheEntry *typcache;	/* multirange type's typcache entry */
+	FmgrInfo	typioproc;		/* range type's I/O proc */
+	Oid			typioparam;		/* range type's I/O parameter */
+} 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 mltrngtypid,
+												IOFuncSelector func);
+static int32 multirange_canonicalize(TypeCacheEntry *rangetyp, int32 input_range_count,
+									 RangeType **ranges);
+static void shortrange_deserialize(TypeCacheEntry *typcache,
+								   const ShortRangeType *range,
+								   RangeBound *lower, RangeBound *upper, bool *empty);
+static void shortrange_serialize(ShortRangeType *range, TypeCacheEntry *typcache,
+								 RangeBound *lower, RangeBound *upper, bool empty);
+
+/* "Methods" required for an expanded object */
+static Size EMR_get_flat_size(ExpandedObjectHeader *eohptr);
+static void EMR_flatten_into(ExpandedObjectHeader *eohptr,
+							 void *result, Size allocated_size);
+
+static const ExpandedObjectMethods EMR_methods =
+{
+	EMR_get_flat_size,
+	EMR_flatten_into
+};
+
+/*
+ *----------------------------------------------------------
+ * 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 with 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;
+	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 = NULL;
+	int32		range_str_len;
+	char	   *range_str_copy;
+
+	cache = get_multirange_io_data(fcinfo, mltrngtypoid, IOFunc_input);
+	rangetyp = cache->typcache->rngtype;
+
+	/* 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 + 1;
+					range_str_copy = pnstrdup(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->typioproc,
+																 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;
+	int32		range_count;
+	int32		i;
+	RangeType **ranges;
+
+	cache = get_multirange_io_data(fcinfo, mltrngtypoid, IOFunc_output);
+
+	initStringInfo(&buf);
+
+	appendStringInfoChar(&buf, '{');
+
+	multirange_deserialize(cache->typcache->rngtype, multirange, &range_count, &ranges);
+	for (i = 0; i < range_count; i++)
+	{
+		if (i > 0)
+			appendStringInfoChar(&buf, ',');
+		range = ranges[i];
+		rangeStr = OutputFunctionCall(&cache->typioproc, RangeTypePGetDatum(range));
+		appendStringInfoString(&buf, rangeStr);
+	}
+
+	appendStringInfoChar(&buf, '}');
+
+	PG_RETURN_CSTRING(buf.data);
+}
+
+/*
+ * Binary representation: First a int32-sized count of ranges, followed by
+ * ranges in their native binary representation.
+ */
+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;
+	uint32		range_count;
+	RangeType **ranges;
+	MultirangeType *ret;
+	StringInfoData tmpbuf;
+
+	cache = get_multirange_io_data(fcinfo, mltrngtypoid, IOFunc_receive);
+
+	range_count = pq_getmsgint(buf, 4);
+	ranges = palloc(range_count * sizeof(RangeType *));
+
+	initStringInfo(&tmpbuf);
+	for (int i = 0; i < range_count; i++)
+	{
+		uint32		range_len = pq_getmsgint(buf, 4);
+		const char *range_data = pq_getmsgbytes(buf, range_len);
+
+		resetStringInfo(&tmpbuf);
+		appendBinaryStringInfo(&tmpbuf, range_data, range_len);
+
+		ranges[i] = DatumGetRangeTypeP(ReceiveFunctionCall(&cache->typioproc,
+														   &tmpbuf,
+														   cache->typioparam,
+														   typmod));
+	}
+	pfree(tmpbuf.data);
+
+	pq_getmsgend(buf);
+
+	ret = make_multirange(mltrngtypoid, cache->typcache->rngtype,
+						  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;
+	MultirangeIOData *cache;
+
+	cache = get_multirange_io_data(fcinfo, mltrngtypoid, IOFunc_send);
+
+	/* construct output */
+	pq_begintypsend(buf);
+
+	pq_sendint32(buf, multirange->rangeCount);
+
+	multirange_deserialize(cache->typcache->rngtype, multirange, &range_count, &ranges);
+	for (int i = 0; i < range_count; i++)
+	{
+		Datum		range;
+
+		range = RangeTypePGetDatum(ranges[i]);
+		range = PointerGetDatum(SendFunctionCall(&cache->typioproc, range));
+
+		pq_sendint32(buf, VARSIZE(range) - VARHDRSZ);
+		pq_sendbytes(buf, VARDATA(range), VARSIZE(range) - VARHDRSZ);
+	}
+
+	PG_RETURN_BYTEA_P(pq_endtypsend(buf));
+}
+
+/*
+ * 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)
+	{
+		Oid			typiofunc;
+		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,
+						 &typiofunc);
+
+		if (!OidIsValid(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(typiofunc, &cache->typioproc,
+					  fcinfo->flinfo->fn_mcxt);
+
+		fcinfo->flinfo->fn_extra = (void *) cache;
+	}
+
+	return cache;
+}
+
+/*
+ * Converts a list of arbitrary ranges 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;
+}
+
+/*
+ *----------------------------------------------------------
+ * 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;
+	int			i;
+	int32		bytelen;
+	Pointer		ptr;
+	RangeBound	lower;
+	RangeBound	upper;
+	bool		empty;
+
+	/* 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 ShortRangeTypes start aligned.
+	 */
+	bytelen = MAXALIGN(sizeof(MultirangeType));
+
+	/* Count space for all ranges */
+	for (i = 0; i < range_count; i++)
+		bytelen += MAXALIGN(VARSIZE(ranges[i]) - sizeof(char));
+
+	/* 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_deserialize(rangetyp, ranges[i], &lower, &upper, &empty);
+		shortrange_serialize((ShortRangeType *) ptr, rangetyp, &lower, &upper, empty);
+		ptr += MAXALIGN(VARSIZE(ptr));
+	}
+
+	return multirange;
+}
+
+/*
+ * 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(TypeCacheEntry *rangetyp,
+					   const MultirangeType *multirange, int32 *range_count,
+					   RangeType ***ranges)
+{
+	ExpandedMultirangeHeader *emrh = (ExpandedMultirangeHeader *)
+	DatumGetEOHP(expand_multirange(MultirangeTypePGetDatum(multirange),
+								   CurrentMemoryContext, rangetyp));
+
+	*range_count = emrh->rangeCount;
+	if (*range_count == 0)
+	{
+		*ranges = NULL;
+		return;
+	}
+
+	*ranges = emrh->ranges;
+}
+
+MultirangeType *
+make_empty_multirange(Oid mltrngtypoid, TypeCacheEntry *rangetyp)
+{
+	return make_multirange(mltrngtypoid, rangetyp, 0, NULL);
+}
+
+/*
+ *----------------------------------------------------------
+ * EXPANDED TOAST FUNCTIONS
+ *----------------------------------------------------------
+ */
+
+/*
+ * expand_multirange: convert a multirange Datum into an expanded multirange
+ *
+ * The expanded object will be a child of parentcontext.
+ */
+Datum
+expand_multirange(Datum multirangedatum, MemoryContext parentcontext, TypeCacheEntry *rangetyp)
+{
+	MultirangeType *multirange;
+	ExpandedMultirangeHeader *emrh;
+	MemoryContext objcxt;
+	MemoryContext oldcxt;
+
+	/*
+	 * Allocate private context for expanded object.  We start by assuming
+	 * that the multirange won't be very large; but if it does grow a lot,
+	 * don't constrain aset.c's large-context behavior.
+	 */
+	objcxt = AllocSetContextCreate(parentcontext,
+								   "expanded multirange",
+								   ALLOCSET_START_SMALL_SIZES);
+
+	/* Set up expanded multirange header */
+	emrh = (ExpandedMultirangeHeader *)
+		MemoryContextAlloc(objcxt, sizeof(ExpandedMultirangeHeader));
+
+	EOH_init_header(&emrh->hdr, &EMR_methods, objcxt);
+	emrh->emr_magic = EMR_MAGIC;
+
+	/*
+	 * Detoast and copy source multirange into private context. We need to do
+	 * this so that we get our own copies of the upper/lower bounds.
+	 *
+	 * Note that this coding risks leaking some memory in the private context
+	 * if we have to fetch data from a TOAST table; however, experimentation
+	 * says that the leak is minimal.  Doing it this way saves a copy step,
+	 * which seems worthwhile, especially if the multirange is large enough to
+	 * need external storage.
+	 */
+	oldcxt = MemoryContextSwitchTo(objcxt);
+	multirange = DatumGetMultirangeTypePCopy(multirangedatum);
+
+	emrh->multirangetypid = multirange->multirangetypid;
+	emrh->rangeCount = multirange->rangeCount;
+
+	/* Convert each ShortRangeType into a RangeType */
+	if (emrh->rangeCount > 0)
+	{
+		Pointer		ptr;
+		Pointer		end;
+		int32		i;
+
+		emrh->ranges = palloc(emrh->rangeCount * sizeof(RangeType *));
+
+		ptr = (char *) multirange;
+		end = ptr + VARSIZE(multirange);
+		ptr = (char *) MAXALIGN(multirange + 1);
+		i = 0;
+		while (ptr < end)
+		{
+			ShortRangeType *shortrange;
+			RangeBound	lower;
+			RangeBound	upper;
+			bool		empty;
+
+			shortrange = (ShortRangeType *) ptr;
+			shortrange_deserialize(rangetyp, shortrange, &lower, &upper, &empty);
+			emrh->ranges[i++] = make_range(rangetyp, &lower, &upper, empty);
+
+			ptr += MAXALIGN(VARSIZE(shortrange));
+		}
+	}
+	else
+	{
+		emrh->ranges = NULL;
+	}
+	MemoryContextSwitchTo(oldcxt);
+
+	/* return a R/W pointer to the expanded multirange */
+	return EOHPGetRWDatum(&emrh->hdr);
+}
+
+/*
+ * shortrange_deserialize: Extract bounds and empty flag from a ShortRangeType
+ */
+void
+shortrange_deserialize(TypeCacheEntry *typcache, const ShortRangeType *range,
+					   RangeBound *lower, RangeBound *upper, bool *empty)
+{
+	char		flags;
+	int16		typlen;
+	bool		typbyval;
+	char		typalign;
+	Pointer		ptr;
+	Datum		lbound;
+	Datum		ubound;
+
+	/* fetch the flag byte */
+	flags = range->flags;
+
+	/* fetch information about range's element type */
+	typlen = typcache->rngelemtype->typlen;
+	typbyval = typcache->rngelemtype->typbyval;
+	typalign = typcache->rngelemtype->typalign;
+
+	/* initialize data pointer just after the range struct fields */
+	ptr = (Pointer) MAXALIGN(range + 1);
+
+	/* fetch lower bound, if any */
+	if (RANGE_HAS_LBOUND(flags))
+	{
+		/* att_align_pointer cannot be necessary here */
+		lbound = fetch_att(ptr, typbyval, typlen);
+		ptr = (Pointer) att_addlength_pointer(ptr, typlen, ptr);
+	}
+	else
+		lbound = (Datum) 0;
+
+	/* fetch upper bound, if any */
+	if (RANGE_HAS_UBOUND(flags))
+	{
+		ptr = (Pointer) att_align_pointer(ptr, typalign, typlen, ptr);
+		ubound = fetch_att(ptr, typbyval, typlen);
+		/* no need for att_addlength_pointer */
+	}
+	else
+		ubound = (Datum) 0;
+
+	/* emit results */
+
+	*empty = (flags & RANGE_EMPTY) != 0;
+
+	lower->val = lbound;
+	lower->infinite = (flags & RANGE_LB_INF) != 0;
+	lower->inclusive = (flags & RANGE_LB_INC) != 0;
+	lower->lower = true;
+
+	upper->val = ubound;
+	upper->infinite = (flags & RANGE_UB_INF) != 0;
+	upper->inclusive = (flags & RANGE_UB_INC) != 0;
+	upper->lower = false;
+}
+
+/*
+ * get_flat_size method for expanded multirange
+ */
+static Size
+EMR_get_flat_size(ExpandedObjectHeader *eohptr)
+{
+	ExpandedMultirangeHeader *emrh = (ExpandedMultirangeHeader *) eohptr;
+	RangeType  *range;
+	Size		nbytes;
+	int			i;
+
+	Assert(emrh->emr_magic == EMR_MAGIC);
+
+	/* If we have a cached size value, believe that */
+	if (emrh->flat_size)
+		return emrh->flat_size;
+
+	/*
+	 * Compute space needed by examining ranges.
+	 */
+	nbytes = MAXALIGN(sizeof(MultirangeType));
+	for (i = 0; i < emrh->rangeCount; i++)
+	{
+		range = emrh->ranges[i];
+		nbytes += MAXALIGN(VARSIZE(range) - sizeof(char));
+		/* check for overflow of total request */
+		if (!AllocSizeIsValid(nbytes))
+			ereport(ERROR,
+					(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
+					 errmsg("multirange size exceeds the maximum allowed (%d)",
+							(int) MaxAllocSize)));
+	}
+
+	/* cache for next time */
+	emrh->flat_size = nbytes;
+
+	return nbytes;
+}
+
+/*
+ * flatten_into method for expanded multiranges
+ */
+static void
+EMR_flatten_into(ExpandedObjectHeader *eohptr,
+				 void *result, Size allocated_size)
+{
+	ExpandedMultirangeHeader *emrh = (ExpandedMultirangeHeader *) eohptr;
+	MultirangeType *mrresult = (MultirangeType *) result;
+	TypeCacheEntry *typcache;
+	int			rangeCount;
+	RangeType **ranges;
+	RangeBound	lower;
+	RangeBound	upper;
+	bool		empty;
+	Pointer		ptr;
+	int			i;
+
+	Assert(emrh->emr_magic == EMR_MAGIC);
+
+	typcache = lookup_type_cache(emrh->multirangetypid, TYPECACHE_MULTIRANGE_INFO);
+	if (typcache->rngtype == NULL)
+		elog(ERROR, "type %u is not a multirange type", emrh->multirangetypid);
+
+	/* Fill result multirange from RangeTypes */
+	rangeCount = emrh->rangeCount;
+	ranges = emrh->ranges;
+
+	/* We must ensure that any pad space is zero-filled */
+	memset(mrresult, 0, allocated_size);
+
+	SET_VARSIZE(mrresult, allocated_size);
+
+	/* Now fill in the datum with ShortRangeTypes */
+	mrresult->multirangetypid = emrh->multirangetypid;
+	mrresult->rangeCount = rangeCount;
+
+	ptr = (char *) MAXALIGN(mrresult + 1);
+	for (i = 0; i < rangeCount; i++)
+	{
+		range_deserialize(typcache->rngtype, ranges[i], &lower, &upper, &empty);
+		shortrange_serialize((ShortRangeType *) ptr, typcache->rngtype, &lower, &upper, empty);
+		ptr += MAXALIGN(VARSIZE(ptr));
+	}
+}
+
+/*
+ * shortrange_serialize: fill in pre-allocationed range param based on the
+ * given bounds and flags.
+ */
+static void
+shortrange_serialize(ShortRangeType *range, TypeCacheEntry *typcache,
+					 RangeBound *lower, RangeBound *upper, bool empty)
+{
+	int			cmp;
+	Size		msize;
+	Pointer		ptr;
+	int16		typlen;
+	bool		typbyval;
+	char		typalign;
+	char		typstorage;
+	char		flags = 0;
+
+	/*
+	 * Verify range is not invalid on its face, and construct flags value,
+	 * preventing any non-canonical combinations such as infinite+inclusive.
+	 */
+	Assert(lower->lower);
+	Assert(!upper->lower);
+
+	if (empty)
+		flags |= RANGE_EMPTY;
+	else
+	{
+		cmp = range_cmp_bound_values(typcache, lower, upper);
+
+		/* error check: if lower bound value is above upper, it's wrong */
+		if (cmp > 0)
+			ereport(ERROR,
+					(errcode(ERRCODE_DATA_EXCEPTION),
+					 errmsg("range lower bound must be less than or equal to range upper bound")));
+
+		/* if bounds are equal, and not both inclusive, range is empty */
+		if (cmp == 0 && !(lower->inclusive && upper->inclusive))
+			flags |= RANGE_EMPTY;
+		else
+		{
+			/* infinite boundaries are never inclusive */
+			if (lower->infinite)
+				flags |= RANGE_LB_INF;
+			else if (lower->inclusive)
+				flags |= RANGE_LB_INC;
+			if (upper->infinite)
+				flags |= RANGE_UB_INF;
+			else if (upper->inclusive)
+				flags |= RANGE_UB_INC;
+		}
+	}
+
+	/* Fetch information about range's element type */
+	typlen = typcache->rngelemtype->typlen;
+	typbyval = typcache->rngelemtype->typbyval;
+	typalign = typcache->rngelemtype->typalign;
+	typstorage = typcache->rngelemtype->typstorage;
+
+	/* Count space for varlena header */
+	msize = sizeof(ShortRangeType);
+	Assert(msize == MAXALIGN(msize));
+
+	/* Count space for bounds */
+	if (RANGE_HAS_LBOUND(flags))
+	{
+		/*
+		 * Make sure item to be inserted is not toasted.  It is essential that
+		 * we not insert an out-of-line toast value pointer into a range
+		 * object, for the same reasons that arrays and records can't contain
+		 * them.  It would work to store a compressed-in-line value, but we
+		 * prefer to decompress and then let compression be applied to the
+		 * whole range object if necessary.  But, unlike arrays, we do allow
+		 * short-header varlena objects to stay as-is.
+		 */
+		if (typlen == -1)
+			lower->val = PointerGetDatum(PG_DETOAST_DATUM_PACKED(lower->val));
+
+		msize = datum_compute_size(msize, lower->val, typbyval, typalign,
+								   typlen, typstorage);
+	}
+
+	if (RANGE_HAS_UBOUND(flags))
+	{
+		/* Make sure item to be inserted is not toasted */
+		if (typlen == -1)
+			upper->val = PointerGetDatum(PG_DETOAST_DATUM_PACKED(upper->val));
+
+		msize = datum_compute_size(msize, upper->val, typbyval, typalign,
+								   typlen, typstorage);
+	}
+
+	SET_VARSIZE(range, msize);
+
+	ptr = (char *) (range + 1);
+
+	if (RANGE_HAS_LBOUND(flags))
+	{
+		Assert(lower->lower);
+		ptr = datum_write(ptr, lower->val, typbyval, typalign, typlen,
+						  typstorage);
+	}
+
+	if (RANGE_HAS_UBOUND(flags))
+	{
+		Assert(!upper->lower);
+		ptr = datum_write(ptr, upper->val, typbyval, typalign, typlen,
+						  typstorage);
+	}
+
+	range->flags = flags;
+}
+
+
+/*
+ *----------------------------------------------------------
+ * 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_constructor2(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,
+				(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+				 errmsg("multirange values cannot contain NULL members")));
+
+	rangeArray = PG_GETARG_ARRAYTYPE_P(0);
+
+	dims = ARR_NDIM(rangeArray);
+	if (dims > 1)
+		ereport(ERROR,
+				(errcode(ERRCODE_CARDINALITY_VIOLATION),
+				 errmsg("multiranges cannot be constructed from multi-dimensional arrays")));
+
+	rngtypid = ARR_ELEMTYPE(rangeArray);
+	if (rngtypid != rangetyp->type_id)
+		ereport(ERROR,
+				(errcode(ERRCODE_DATATYPE_MISMATCH),
+				 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,
+						(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+						 errmsg("multirange values cannot contain NULL members")));
+
+			/* make_multirange will do its own copy */
+			ranges[i] = DatumGetRangeTypeP(elements[i]);
+		}
+	}
+
+	PG_RETURN_MULTIRANGE_P(make_multirange(mltrngtypid, rangetyp, range_count, ranges));
+}
+
+/*
+ * Construct multirange value from a single range.
+ * It'd be nice if we could just use multirange_constructor2
+ * for this case, but we need a non-variadic single-arg function
+ * to let us define a CAST from a range to its multirange.
+ */
+Datum
+multirange_constructor1(PG_FUNCTION_ARGS)
+{
+	Oid			mltrngtypid = get_fn_expr_rettype(fcinfo->flinfo);
+	Oid			rngtypid;
+	TypeCacheEntry *typcache;
+	TypeCacheEntry *rangetyp;
+	RangeType  *range;
+
+	typcache = multirange_get_typcache(fcinfo, mltrngtypid);
+	rangetyp = typcache->rngtype;
+
+	/*
+	 * These checks should be guaranteed by our signature, but let's do them
+	 * just in case.
+	 */
+
+	if (PG_ARGISNULL(0))
+		ereport(ERROR,
+				(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+				 errmsg("multirange values cannot contain NULL members")));
+
+	range = PG_GETARG_RANGE_P(0);
+
+	/* Make sure the range type matches. */
+	rngtypid = RangeTypeGetOid(range);
+	if (rngtypid != rangetyp->type_id)
+		ereport(ERROR,
+				(errcode(ERRCODE_DATATYPE_MISMATCH),
+				 errmsg("type %u does not match constructor type", rngtypid)));
+
+	PG_RETURN_MULTIRANGE_P(make_multirange(mltrngtypid, rangetyp, 1, &range));
+}
+
+/*
+ * 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
+		elog(ERROR,				/* can't happen */
+			 "niladic multirange constructor must not receive arguments");
+}
+
+
+/* multirange, multirange -> multirange type functions */
+
+/* multirange union */
+Datum
+multirange_union(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
+	MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count1;
+	int32		range_count2;
+	int32		range_count3;
+	RangeType **ranges1;
+	RangeType **ranges2;
+	RangeType **ranges3;
+
+	if (MultirangeIsEmpty(mr1))
+		PG_RETURN_MULTIRANGE_P(mr2);
+	if (MultirangeIsEmpty(mr2))
+		PG_RETURN_MULTIRANGE_P(mr1);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr1));
+
+	multirange_deserialize(typcache->rngtype, mr1, &range_count1, &ranges1);
+	multirange_deserialize(typcache->rngtype, mr2, &range_count2, &ranges2);
+
+	range_count3 = range_count1 + range_count2;
+	ranges3 = palloc0(range_count3 * sizeof(RangeType *));
+	memcpy(ranges3, ranges1, range_count1 * sizeof(RangeType *));
+	memcpy(ranges3 + range_count1, ranges2, range_count2 * sizeof(RangeType *));
+	PG_RETURN_MULTIRANGE_P(make_multirange(typcache->type_id, typcache->rngtype,
+										   range_count3, ranges3));
+}
+
+/* multirange minus */
+Datum
+multirange_minus(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
+	MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
+	Oid			mltrngtypoid = MultirangeTypeGetOid(mr1);
+	TypeCacheEntry *typcache;
+	TypeCacheEntry *rangetyp;
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+
+	typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
+	rangetyp = typcache->rngtype;
+
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		PG_RETURN_MULTIRANGE_P(mr1);
+
+	multirange_deserialize(typcache->rngtype, mr1, &range_count1, &ranges1);
+	multirange_deserialize(typcache->rngtype, mr2, &range_count2, &ranges2);
+
+	PG_RETURN_MULTIRANGE_P(multirange_minus_internal(mltrngtypoid,
+													 rangetyp,
+													 range_count1,
+													 ranges1,
+													 range_count2,
+													 ranges2));
+}
+
+MultirangeType *
+multirange_minus_internal(Oid mltrngtypoid, TypeCacheEntry *rangetyp,
+						  int32 range_count1, RangeType **ranges1,
+						  int32 range_count2, RangeType **ranges2)
+{
+	RangeType  *r1;
+	RangeType  *r2;
+	RangeType **ranges3;
+	int32		range_count3;
+	int32		i1;
+	int32		i2;
+
+	/*
+	 * Worst case: every range in ranges1 makes a different cut to some range
+	 * in ranges2.
+	 */
+	ranges3 = palloc0((range_count1 + range_count2) * sizeof(RangeType *));
+	range_count3 = 0;
+
+	/*
+	 * For each range in mr1, keep subtracting until it's gone or the ranges
+	 * in mr2 have passed it. After a subtraction we assign what's left back
+	 * to r1. The parallel progress through mr1 and mr2 is similar to
+	 * multirange_overlaps_multirange_internal.
+	 */
+	r2 = ranges2[0];
+	for (i1 = 0, i2 = 0; i1 < range_count1; i1++)
+	{
+		r1 = ranges1[i1];
+
+		/* Discard r2s while r2 << r1 */
+		while (r2 != NULL && range_before_internal(rangetyp, r2, r1))
+		{
+			r2 = ++i2 >= range_count2 ? NULL : ranges2[i2];
+		}
+
+		while (r2 != NULL)
+		{
+			if (range_split_internal(rangetyp, r1, r2, &ranges3[range_count3], &r1))
+			{
+				/*
+				 * If r2 takes a bite out of the middle of r1, we need two
+				 * outputs
+				 */
+				range_count3++;
+				r2 = ++i2 >= range_count2 ? NULL : ranges2[i2];
+
+			}
+			else if (range_overlaps_internal(rangetyp, r1, r2))
+			{
+				/*
+				 * If r2 overlaps r1, replace r1 with r1 - r2.
+				 */
+				r1 = range_minus_internal(rangetyp, r1, r2);
+
+				/*
+				 * If r2 goes past r1, then we need to stay with it, in case
+				 * it hits future r1s. Otherwise we need to keep r1, in case
+				 * future r2s hit it. Since we already subtracted, there's no
+				 * point in using the overright/overleft calls.
+				 */
+				if (RangeIsEmpty(r1) || range_before_internal(rangetyp, r1, r2))
+					break;
+				else
+					r2 = ++i2 >= range_count2 ? NULL : ranges2[i2];
+
+			}
+			else
+			{
+				/*
+				 * This and all future r2s are past r1, so keep them. Also
+				 * assign whatever is left of r1 to the result.
+				 */
+				break;
+			}
+		}
+
+		/*
+		 * Nothing else can remove anything from r1, so keep it. Even if r1 is
+		 * empty here, make_multirange will remove it.
+		 */
+		ranges3[range_count3++] = r1;
+	}
+
+	return make_multirange(mltrngtypoid, rangetyp, range_count3, ranges3);
+}
+
+/* multirange intersection */
+Datum
+multirange_intersect(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
+	MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
+	Oid			mltrngtypoid = MultirangeTypeGetOid(mr1);
+	TypeCacheEntry *typcache;
+	TypeCacheEntry *rangetyp;
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+
+	typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
+	rangetyp = typcache->rngtype;
+
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		PG_RETURN_MULTIRANGE_P(make_empty_multirange(mltrngtypoid, rangetyp));
+
+	multirange_deserialize(rangetyp, mr1, &range_count1, &ranges1);
+	multirange_deserialize(rangetyp, mr2, &range_count2, &ranges2);
+
+	PG_RETURN_MULTIRANGE_P(multirange_intersect_internal(mltrngtypoid,
+														 rangetyp,
+														 range_count1,
+														 ranges1,
+														 range_count2,
+														 ranges2));
+}
+
+MultirangeType *
+multirange_intersect_internal(Oid mltrngtypoid, TypeCacheEntry *rangetyp,
+							  int32 range_count1, RangeType **ranges1,
+							  int32 range_count2, RangeType **ranges2)
+{
+	RangeType  *r1;
+	RangeType  *r2;
+	RangeType **ranges3;
+	int32		range_count3;
+	int32		i1;
+	int32		i2;
+
+	if (range_count1 == 0 || range_count2 == 0)
+		return make_multirange(mltrngtypoid, rangetyp, 0, NULL);
+
+	/*-----------------------------------------------
+	 * Worst case is a stitching pattern like this:
+	 *
+	 * mr1: --- --- --- ---
+	 * mr2:   --- --- ---
+	 * mr3:   - - - - - -
+	 *
+	 * That seems to be range_count1 + range_count2 - 1,
+	 * but one extra won't hurt.
+	 *-----------------------------------------------
+	 */
+	ranges3 = palloc0((range_count1 + range_count2) * sizeof(RangeType *));
+	range_count3 = 0;
+
+	/*
+	 * For each range in mr1, keep intersecting until the ranges in mr2 have
+	 * passed it. The parallel progress through mr1 and mr2 is similar to
+	 * multirange_minus_multirange_internal, but we don't have to assign back
+	 * to r1.
+	 */
+	r2 = ranges2[0];
+	for (i1 = 0, i2 = 0; i1 < range_count1; i1++)
+	{
+		r1 = ranges1[i1];
+
+		/* Discard r2s while r2 << r1 */
+		while (r2 != NULL && range_before_internal(rangetyp, r2, r1))
+		{
+			r2 = ++i2 >= range_count2 ? NULL : ranges2[i2];
+		}
+
+		while (r2 != NULL)
+		{
+			if (range_overlaps_internal(rangetyp, r1, r2))
+			{
+				/* Keep the overlapping part */
+				ranges3[range_count3++] = range_intersect_internal(rangetyp, r1, r2);
+
+				/* If we "used up" all of r2, go to the next one... */
+				if (range_overleft_internal(rangetyp, r2, r1))
+					r2 = ++i2 >= range_count2 ? NULL : ranges2[i2];
+
+				/* ...otherwise go to the next r1 */
+				else
+					break;
+			}
+			else
+				/* We're past r1, so move to the next one */
+				break;
+		}
+
+		/* If we're out of r2s, there can be no more intersections */
+		if (r2 == NULL)
+			break;
+	}
+
+	return make_multirange(mltrngtypoid, rangetyp, range_count3, ranges3);
+}
+
+/*
+ * range_agg_transfn: combine adjacent/overlapping ranges.
+ *
+ * All we do here is gather the input ranges into an array
+ * so that the finalfn can sort and combine them.
+ */
+Datum
+range_agg_transfn(PG_FUNCTION_ARGS)
+{
+	MemoryContext aggContext;
+	Oid			rngtypoid;
+	ArrayBuildState *state;
+
+	if (!AggCheckCallContext(fcinfo, &aggContext))
+		elog(ERROR, "range_agg_transfn called in non-aggregate context");
+
+	rngtypoid = get_fn_expr_argtype(fcinfo->flinfo, 1);
+	if (!type_is_range(rngtypoid))
+		ereport(ERROR,
+				(errcode(ERRCODE_DATATYPE_MISMATCH),
+				 errmsg("range_agg must be called with a range")));
+
+	if (PG_ARGISNULL(0))
+		state = initArrayResult(rngtypoid, aggContext, false);
+	else
+		state = (ArrayBuildState *) PG_GETARG_POINTER(0);
+
+	/* skip NULLs */
+	if (!PG_ARGISNULL(1))
+		accumArrayResult(state, PG_GETARG_DATUM(1), false, rngtypoid, aggContext);
+
+	PG_RETURN_POINTER(state);
+}
+
+/*
+ * range_agg_finalfn: use our internal array to merge touching ranges.
+ */
+Datum
+range_agg_finalfn(PG_FUNCTION_ARGS)
+{
+	MemoryContext aggContext;
+	Oid			mltrngtypoid;
+	TypeCacheEntry *typcache;
+	ArrayBuildState *state;
+	int32		range_count;
+	RangeType **ranges;
+	int			i;
+
+	if (!AggCheckCallContext(fcinfo, &aggContext))
+		elog(ERROR, "range_agg_finalfn called in non-aggregate context");
+
+	state = PG_ARGISNULL(0) ? NULL : (ArrayBuildState *) PG_GETARG_POINTER(0);
+	if (state == NULL)
+		/* This shouldn't be possible, but just in case.... */
+		PG_RETURN_NULL();
+
+	/* Also return NULL if we had zero inputs, like other aggregates */
+	range_count = state->nelems;
+	if (range_count == 0)
+		PG_RETURN_NULL();
+
+	mltrngtypoid = get_fn_expr_rettype(fcinfo->flinfo);
+	typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
+
+	ranges = palloc0(range_count * sizeof(RangeType *));
+	for (i = 0; i < range_count; i++)
+		ranges[i] = DatumGetRangeTypeP(state->dvalues[i]);
+
+	PG_RETURN_MULTIRANGE_P(make_multirange(mltrngtypoid, typcache->rngtype, range_count, ranges));
+}
+
+Datum
+multirange_intersect_agg_transfn(PG_FUNCTION_ARGS)
+{
+	MemoryContext aggContext;
+	Oid			mltrngtypoid;
+	TypeCacheEntry *typcache;
+	MultirangeType *result;
+	MultirangeType *current;
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+
+	if (!AggCheckCallContext(fcinfo, &aggContext))
+		elog(ERROR, "multirange_intersect_agg_transfn called in non-aggregate context");
+
+	mltrngtypoid = get_fn_expr_argtype(fcinfo->flinfo, 1);
+	if (!type_is_multirange(mltrngtypoid))
+		ereport(ERROR,
+				(errcode(ERRCODE_DATATYPE_MISMATCH),
+				 errmsg("range_intersect_agg must be called with a multirange")));
+
+	typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
+
+	/* strictness ensures these are non-null */
+	result = PG_GETARG_MULTIRANGE_P(0);
+	current = PG_GETARG_MULTIRANGE_P(1);
+
+	multirange_deserialize(typcache->rngtype, result, &range_count1, &ranges1);
+	multirange_deserialize(typcache->rngtype, current, &range_count2, &ranges2);
+
+	result = multirange_intersect_internal(mltrngtypoid,
+										   typcache->rngtype,
+										   range_count1,
+										   ranges1,
+										   range_count2,
+										   ranges2);
+	PG_RETURN_RANGE_P(result);
+}
+
+
+/* multirange -> element type functions */
+
+/* extract lower bound value */
+Datum
+multirange_lower(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+	bool		isnull;
+	Datum		result;
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_NULL();
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(typcache->rngtype, mr, &range_count, &ranges);
+
+	result = range_lower_internal(typcache->rngtype, ranges[0], &isnull);
+
+	if (isnull)
+		PG_RETURN_NULL();
+	else
+		PG_RETURN_DATUM(result);
+}
+
+/* extract upper bound value */
+Datum
+multirange_upper(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+	bool		isnull;
+	Datum		result;
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_NULL();
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(typcache->rngtype, mr, &range_count, &ranges);
+
+	result = range_upper_internal(typcache->rngtype, ranges[range_count - 1], &isnull);
+
+	if (isnull)
+		PG_RETURN_NULL();
+	else
+		PG_RETURN_DATUM(result);
+}
+
+
+/* multirange -> bool functions */
+
+/* is multirange empty? */
+Datum
+multirange_empty(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+
+	PG_RETURN_BOOL(mr->rangeCount == 0);
+}
+
+/* is lower bound inclusive? */
+Datum
+multirange_lower_inc(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+	multirange_deserialize(typcache->rngtype, mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_has_flag(ranges[0], RANGE_LB_INC));
+}
+
+/* is upper bound inclusive? */
+Datum
+multirange_upper_inc(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+	multirange_deserialize(typcache->rngtype, mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_has_flag(ranges[range_count - 1], RANGE_UB_INC));
+}
+
+/* is lower bound infinite? */
+Datum
+multirange_lower_inf(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+	multirange_deserialize(typcache->rngtype, mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_has_flag(ranges[0], RANGE_LB_INF));
+}
+
+/* is upper bound infinite? */
+Datum
+multirange_upper_inf(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+	multirange_deserialize(typcache->rngtype, mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_has_flag(ranges[range_count - 1], RANGE_UB_INF));
+}
+
+
+
+/* multirange, element -> bool functions */
+
+/* contains? */
+Datum
+multirange_contains_elem(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	Datum		val = PG_GETARG_DATUM(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(multirange_contains_elem_internal(typcache, mr, val));
+}
+
+/* contained by? */
+Datum
+elem_contained_by_multirange(PG_FUNCTION_ARGS)
+{
+	Datum		val = PG_GETARG_DATUM(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(multirange_contains_elem_internal(typcache, mr, val));
+}
+
+/*
+ * Test whether multirange mr contains a specific element value.
+ */
+bool
+multirange_contains_elem_internal(TypeCacheEntry *typcache, MultirangeType *mr, Datum val)
+{
+	TypeCacheEntry *rangetyp;
+	int32		range_count;
+	RangeType **ranges;
+	RangeType  *r;
+	int			i;
+
+	rangetyp = typcache->rngtype;
+
+	multirange_deserialize(rangetyp, mr, &range_count, &ranges);
+
+	for (i = 0; i < range_count; i++)
+	{
+		r = ranges[i];
+		if (range_contains_elem_internal(rangetyp, r, val))
+			return true;
+	}
+
+	return false;
+}
+
+/* multirange, range -> bool functions */
+
+/* contains? */
+Datum
+multirange_contains_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(multirange_contains_range_internal(typcache, mr, r));
+}
+
+/* contained by? */
+Datum
+range_contained_by_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(multirange_contains_range_internal(typcache, mr, r));
+}
+
+/*
+ * Test whether multirange mr contains a specific range r.
+ */
+bool
+multirange_contains_range_internal(TypeCacheEntry *typcache, MultirangeType *mr, RangeType *r)
+{
+	TypeCacheEntry *rangetyp;
+	int32		range_count;
+	RangeType **ranges;
+	RangeType  *mrr;
+	int			i;
+
+	rangetyp = typcache->rngtype;
+
+	/*
+	 * Every multirange contains an infinite number of empty ranges, even an
+	 * empty one.
+	 */
+	if (RangeIsEmpty(r))
+		return true;
+
+	multirange_deserialize(rangetyp, mr, &range_count, &ranges);
+
+	for (i = 0; i < range_count; i++)
+	{
+		mrr = ranges[i];
+		if (range_contains_internal(rangetyp, mrr, r))
+			return true;
+	}
+
+	return false;
+}
+
+
+/* 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(typcache->rngtype, mr1, &range_count_1, &ranges1);
+	multirange_deserialize(typcache->rngtype, 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));
+}
+
+/* overlaps? */
+Datum
+range_overlaps_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(range_overlaps_multirange_internal(typcache, r, mr));
+}
+
+Datum
+multirange_overlaps_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(range_overlaps_multirange_internal(typcache, r, mr));
+}
+
+Datum
+multirange_overlaps_multirange(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_overlaps_multirange_internal(typcache, mr1, mr2));
+}
+
+bool
+range_overlaps_multirange_internal(TypeCacheEntry *typcache, RangeType *r, MultirangeType *mr)
+{
+	TypeCacheEntry *rangetyp;
+	int32		range_count;
+	int32		i;
+	RangeType **ranges;
+	RangeType  *mrr;
+
+	/*
+	 * Empties never overlap, even with empties. (This seems strange since
+	 * they *do* contain each other, but we want to follow how ranges work.)
+	 */
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		return false;
+
+	rangetyp = typcache->rngtype;
+
+	multirange_deserialize(rangetyp, mr, &range_count, &ranges);
+
+	/* Scan through mrr and once it gets to r they either overlap or not */
+	mrr = ranges[0];
+
+	/* Discard mrrs while mrr << r */
+	i = 0;
+	while (range_before_internal(rangetyp, mrr, r))
+	{
+		if (++i >= range_count)
+			return false;
+		mrr = ranges[i];
+	}
+
+	/* Now either we overlap or we passed r */
+	return range_overlaps_internal(rangetyp, mrr, r);
+}
+
+bool
+multirange_overlaps_multirange_internal(TypeCacheEntry *typcache, MultirangeType *mr1,
+										MultirangeType *mr2)
+{
+	TypeCacheEntry *rangetyp;
+	int32		range_count1;
+	int32		range_count2;
+	int32		i1;
+	int32		i2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+	RangeType  *r1;
+	RangeType  *r2;
+
+	/*
+	 * Empties never overlap, even with empties. (This seems strange since
+	 * they *do* contain each other, but we want to follow how ranges work.)
+	 */
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		return false;
+
+	rangetyp = typcache->rngtype;
+
+	multirange_deserialize(rangetyp, mr1, &range_count1, &ranges1);
+	multirange_deserialize(rangetyp, mr2, &range_count2, &ranges2);
+
+	/*
+	 * Every range in mr1 gets a chance to overlap with the ranges in mr2, but
+	 * we can use their ordering to avoid O(n^2). This is similar to
+	 * range_overlaps_multirange where r1 : r2 :: mrr : r, but there if we
+	 * don't find an overlap with r we're done, and here if we don't find an
+	 * overlap with r2 we try the next r2.
+	 */
+	r1 = ranges1[0];
+	for (i1 = 0, i2 = 0; i2 < range_count2; i2++)
+	{
+		r2 = ranges2[i2];
+
+		/* Discard r1s while r1 << r2 */
+		while (range_before_internal(rangetyp, r1, r2))
+		{
+			if (++i1 >= range_count1)
+				return false;
+			r1 = ranges1[i1];
+		}
+
+		/*
+		 * If r1 && r2, we're done, otherwise we failed to find an overlap for
+		 * r2, so go to the next one.
+		 */
+		if (range_overlaps_internal(rangetyp, r1, r2))
+			return true;
+	}
+
+	/* We looked through all of mr2 without finding an overlap */
+	return false;
+}
+
+/* does not extend to right of? */
+Datum
+range_overleft_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(typcache->rngtype, mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_overleft_internal(typcache->rngtype, r, ranges[range_count - 1]));
+}
+
+Datum
+multirange_overleft_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(typcache->rngtype, mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_overleft_internal(typcache->rngtype,
+										   ranges[range_count - 1], r));
+}
+
+Datum
+multirange_overleft_multirange(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
+	MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr1));
+
+	multirange_deserialize(typcache->rngtype, mr1, &range_count1, &ranges1);
+	multirange_deserialize(typcache->rngtype, mr2, &range_count2, &ranges2);
+
+	PG_RETURN_BOOL(range_overleft_internal(typcache->rngtype,
+										   ranges1[range_count1 - 1],
+										   ranges2[range_count2 - 1]));
+}
+
+/* does not extend to left of? */
+Datum
+range_overright_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(typcache->rngtype, mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_overright_internal(typcache->rngtype, r, ranges[0]));
+}
+
+Datum
+multirange_overright_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(typcache->rngtype, mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_overright_internal(typcache->rngtype, ranges[0], r));
+}
+
+Datum
+multirange_overright_multirange(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
+	MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr1));
+
+	multirange_deserialize(typcache->rngtype, mr1, &range_count1, &ranges1);
+	multirange_deserialize(typcache->rngtype, mr2, &range_count2, &ranges2);
+
+	PG_RETURN_BOOL(range_overright_internal(typcache->rngtype,
+											ranges1[0],
+											ranges2[0]));
+}
+
+/* contains? */
+Datum
+multirange_contains_multirange(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_contains_multirange_internal(typcache, mr1, mr2));
+}
+
+/* contained by? */
+Datum
+multirange_contained_by_multirange(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_contains_multirange_internal(typcache, mr2, mr1));
+}
+
+/*
+ * Test whether multirange mr1 contains every range from another multirange mr2.
+ */
+bool
+multirange_contains_multirange_internal(TypeCacheEntry *typcache,
+										MultirangeType *mr1, MultirangeType *mr2)
+{
+	TypeCacheEntry *rangetyp;
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+	RangeType  *r1;
+	RangeType  *r2;
+	int			i1,
+				i2;
+
+	rangetyp = typcache->rngtype;
+
+	multirange_deserialize(typcache->rngtype, mr1, &range_count1, &ranges1);
+	multirange_deserialize(typcache->rngtype, mr2, &range_count2, &ranges2);
+
+	/*
+	 * We follow the same logic for empties as ranges: - an empty multirange
+	 * contains an empty range/multirange. - an empty multirange can't contain
+	 * any other range/multirange. - an empty multirange is contained by any
+	 * other range/multirange.
+	 */
+
+	if (range_count2 == 0)
+		return true;
+	if (range_count1 == 0)
+		return false;
+
+	/*
+	 * Every range in mr2 must be contained by some range in mr1. To avoid
+	 * O(n^2) we walk through both ranges in tandem.
+	 */
+	r1 = ranges1[0];
+	for (i1 = 0, i2 = 0; i2 < range_count2; i2++)
+	{
+		r2 = ranges2[i2];
+
+		/* Discard r1s while r1 << r2 */
+		while (range_before_internal(rangetyp, r1, r2))
+		{
+			if (++i1 >= range_count1)
+				return false;
+			r1 = ranges1[i1];
+		}
+
+		/*
+		 * If r1 @> r2, go to the next r2, otherwise return false (since every
+		 * r1[n] and r1[n+1] must have a gap). Note this will give weird
+		 * answers if you don't canonicalize, e.g. with a custom
+		 * int2multirange {[1,1], [2,2]} there is a "gap". But that is
+		 * consistent with other range operators, e.g. '[1,1]'::int2range -|-
+		 * '[2,2]'::int2range is false.
+		 */
+		if (!range_contains_internal(rangetyp, r1, r2))
+			return false;
+	}
+
+	/* All ranges in mr2 are satisfied */
+	return true;
+}
+
+/* strictly left of? */
+Datum
+range_before_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(range_before_multirange_internal(typcache, r, mr));
+}
+
+Datum
+multirange_before_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(range_after_multirange_internal(typcache, r, mr));
+}
+
+Datum
+multirange_before_multirange(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_before_multirange_internal(typcache, mr1, mr2));
+}
+
+/* strictly right of? */
+Datum
+range_after_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(range_after_multirange_internal(typcache, r, mr));
+}
+
+Datum
+multirange_after_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(range_before_multirange_internal(typcache, r, mr));
+}
+
+Datum
+multirange_after_multirange(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_before_multirange_internal(typcache, mr2, mr1));
+}
+
+/* strictly left of? (internal version) */
+bool
+range_before_multirange_internal(TypeCacheEntry *typcache, RangeType *r,
+								 MultirangeType *mr)
+{
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		return false;
+
+	multirange_deserialize(typcache->rngtype, mr, &range_count, &ranges);
+
+	return range_before_internal(typcache->rngtype, r, ranges[0]);
+}
+
+bool
+multirange_before_multirange_internal(TypeCacheEntry *typcache, MultirangeType *mr1,
+									  MultirangeType *mr2)
+{
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		return false;
+
+	multirange_deserialize(typcache->rngtype, mr1, &range_count1, &ranges1);
+	multirange_deserialize(typcache->rngtype, mr2, &range_count2, &ranges2);
+
+	return range_before_internal(typcache->rngtype, ranges1[range_count1 - 1],
+								 ranges2[0]);
+}
+
+/* strictly right of? (internal version) */
+bool
+range_after_multirange_internal(TypeCacheEntry *typcache, RangeType *r,
+								MultirangeType *mr)
+{
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		return false;
+
+	multirange_deserialize(typcache->rngtype, mr, &range_count, &ranges);
+
+	return range_after_internal(typcache->rngtype, r, ranges[range_count - 1]);
+}
+
+/* adjacent to? */
+Datum
+range_adjacent_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		return false;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(typcache->rngtype, mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_adjacent_internal(typcache->rngtype, r, ranges[0]));
+}
+
+Datum
+multirange_adjacent_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		return false;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(typcache->rngtype, mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_adjacent_internal(typcache->rngtype, ranges[range_count - 1], r));
+}
+
+Datum
+multirange_adjacent_multirange(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
+	MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		return false;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr1));
+
+	multirange_deserialize(typcache->rngtype, mr1, &range_count1, &ranges1);
+	multirange_deserialize(typcache->rngtype, mr2, &range_count2, &ranges2);
+
+	PG_RETURN_BOOL(range_adjacent_internal(typcache->rngtype, ranges1[range_count1 - 1], ranges2[0]));
+}
+
+/* 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(typcache->rngtype, mr1, &range_count_1, &ranges1);
+	multirange_deserialize(typcache->rngtype, 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);
+}
+
+/* multirange -> range functions */
+
+/* Find the smallest range that includes everything in the multirange */
+Datum
+range_merge_from_multirange(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	Oid			mltrngtypoid = MultirangeTypeGetOid(mr);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_RANGE_P(make_empty_range(typcache->rngtype));
+
+	multirange_deserialize(typcache->rngtype, mr, &range_count, &ranges);
+
+	PG_RETURN_RANGE_P(range_union_internal(typcache->rngtype, ranges[0],
+										   ranges[range_count - 1], false));
+}
+
+/* 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(typcache->rngtype, 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(typcache->rngtype, 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/multirangetypes_selfuncs.c b/src/backend/utils/adt/multirangetypes_selfuncs.c
new file mode 100644
index 00000000000..ff0a81a49ad
--- /dev/null
+++ b/src/backend/utils/adt/multirangetypes_selfuncs.c
@@ -0,0 +1,1297 @@
+/*-------------------------------------------------------------------------
+ *
+ * multirangetypes_selfuncs.c
+ *	  Functions for selectivity estimation of multirange operators
+ *
+ * Estimates are based on histograms of lower and upper bounds, and the
+ * fraction of empty multiranges.
+ *
+ * Portions Copyright (c) 1996-2020, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ *	  src/backend/utils/adt/multirangetypes_selfuncs.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include <math.h>
+
+#include "access/htup_details.h"
+#include "catalog/pg_operator.h"
+#include "catalog/pg_statistic.h"
+#include "catalog/pg_type.h"
+#include "utils/float.h"
+#include "utils/fmgrprotos.h"
+#include "utils/lsyscache.h"
+#include "utils/rangetypes.h"
+#include "utils/multirangetypes.h"
+#include "utils/selfuncs.h"
+#include "utils/typcache.h"
+
+static double calc_multirangesel(TypeCacheEntry *typcache, VariableStatData *vardata,
+							const MultirangeType *constval, Oid operator);
+static double default_multirange_selectivity(Oid operator);
+static double default_multirange_selectivity(Oid operator);
+static double calc_hist_selectivity(TypeCacheEntry *typcache,
+									VariableStatData *vardata, const MultirangeType *constval,
+									Oid operator);
+static double calc_hist_selectivity_scalar(TypeCacheEntry *typcache,
+										   const RangeBound *constbound,
+										   const RangeBound *hist, int hist_nvalues,
+										   bool equal);
+static int	rbound_bsearch(TypeCacheEntry *typcache, const RangeBound *value,
+						   const RangeBound *hist, int hist_length, bool equal);
+static float8 get_position(TypeCacheEntry *typcache, const RangeBound *value,
+						   const RangeBound *hist1, const RangeBound *hist2);
+static float8 get_len_position(double value, double hist1, double hist2);
+static float8 get_distance(TypeCacheEntry *typcache, const RangeBound *bound1,
+						   const RangeBound *bound2);
+static int	length_hist_bsearch(Datum *length_hist_values,
+								int length_hist_nvalues, double value, bool equal);
+static double calc_length_hist_frac(Datum *length_hist_values,
+									int length_hist_nvalues, double length1, double length2, bool equal);
+static double calc_hist_selectivity_contained(TypeCacheEntry *typcache,
+											  const RangeBound *lower, RangeBound *upper,
+											  const RangeBound *hist_lower, int hist_nvalues,
+											  Datum *length_hist_values, int length_hist_nvalues);
+static double calc_hist_selectivity_contains(TypeCacheEntry *typcache,
+											 const RangeBound *lower, const RangeBound *upper,
+											 const RangeBound *hist_lower, int hist_nvalues,
+											 Datum *length_hist_values, int length_hist_nvalues);
+
+/*
+ * Returns a default selectivity estimate for given operator, when we don't
+ * have statistics or cannot use them for some reason.
+ */
+static double
+default_multirange_selectivity(Oid operator)
+{
+	switch (operator)
+	{
+		case OID_MULTIRANGE_OVERLAPS_MULTIRANGE_OP:
+		case OID_MULTIRANGE_OVERLAPS_RANGE_OP:
+		case OID_RANGE_OVERLAPS_MULTIRANGE_OP:
+			return 0.01;
+
+		case OID_MULTIRANGE_CONTAINS_RANGE_OP:
+		case OID_MULTIRANGE_CONTAINS_MULTIRANGE_OP:
+		case OID_MULTIRANGE_RANGE_CONTAINED_OP:
+		case OID_MULTIRANGE_MULTIRANGE_CONTAINED_OP:
+			return 0.005;
+
+		case OID_MULTIRANGE_CONTAINS_ELEM_OP:
+		case OID_MULTIRANGE_ELEM_CONTAINED_OP:
+
+			/*
+			 * "multirange @> elem" is more or less identical to a scalar
+			 * inequality "A >= b AND A <= c".
+			 */
+			return DEFAULT_MULTIRANGE_INEQ_SEL;
+
+		case OID_MULTIRANGE_LESS_OP:
+		case OID_MULTIRANGE_LESS_EQUAL_OP:
+		case OID_MULTIRANGE_GREATER_OP:
+		case OID_MULTIRANGE_GREATER_EQUAL_OP:
+		case OID_MULTIRANGE_LEFT_RANGE_OP:
+		case OID_MULTIRANGE_LEFT_MULTIRANGE_OP:
+		case OID_RANGE_LEFT_MULTIRANGE_OP:
+		case OID_MULTIRANGE_RIGHT_RANGE_OP:
+		case OID_MULTIRANGE_RIGHT_MULTIRANGE_OP:
+		case OID_RANGE_RIGHT_MULTIRANGE_OP:
+		case OID_MULTIRANGE_OVERLAPS_LEFT_RANGE_OP:
+		case OID_RANGE_OVERLAPS_LEFT_MULTIRANGE_OP:
+		case OID_MULTIRANGE_OVERLAPS_LEFT_MULTIRANGE_OP:
+		case OID_MULTIRANGE_OVERLAPS_RIGHT_RANGE_OP:
+		case OID_RANGE_OVERLAPS_RIGHT_MULTIRANGE_OP:
+		case OID_MULTIRANGE_OVERLAPS_RIGHT_MULTIRANGE_OP:
+			/* these are similar to regular scalar inequalities */
+			return DEFAULT_INEQ_SEL;
+
+		default:
+			/* all multirange operators should be handled above, but just in case */
+			return 0.01;
+	}
+}
+
+/*
+ * multirangesel -- restriction selectivity for multirange operators
+ */
+Datum
+multirangesel(PG_FUNCTION_ARGS)
+{
+	PlannerInfo *root = (PlannerInfo *) PG_GETARG_POINTER(0);
+	Oid			operator = PG_GETARG_OID(1);
+	List	   *args = (List *) PG_GETARG_POINTER(2);
+	int			varRelid = PG_GETARG_INT32(3);
+	VariableStatData vardata;
+	Node	   *other;
+	bool		varonleft;
+	Selectivity selec;
+	TypeCacheEntry *typcache = NULL;
+	MultirangeType *constmultirange = NULL;
+	RangeType  *constrange = NULL;
+
+	/*
+	 * If expression is not (variable op something) or (something op
+	 * variable), then punt and return a default estimate.
+	 */
+	if (!get_restriction_variable(root, args, varRelid,
+								  &vardata, &other, &varonleft))
+		PG_RETURN_FLOAT8(default_multirange_selectivity(operator));
+
+	/*
+	 * Can't do anything useful if the something is not a constant, either.
+	 */
+	if (!IsA(other, Const))
+	{
+		ReleaseVariableStats(vardata);
+		PG_RETURN_FLOAT8(default_multirange_selectivity(operator));
+	}
+
+	/*
+	 * All the multirange operators are strict, so we can cope with a NULL constant
+	 * right away.
+	 */
+	if (((Const *) other)->constisnull)
+	{
+		ReleaseVariableStats(vardata);
+		PG_RETURN_FLOAT8(0.0);
+	}
+
+	/*
+	 * If var is on the right, commute the operator, so that we can assume the
+	 * var is on the left in what follows.
+	 */
+	if (!varonleft)
+	{
+		/* we have other Op var, commute to make var Op other */
+		operator = get_commutator(operator);
+		if (!operator)
+		{
+			/* Use default selectivity (should we raise an error instead?) */
+			ReleaseVariableStats(vardata);
+			PG_RETURN_FLOAT8(default_multirange_selectivity(operator));
+		}
+	}
+
+	/*
+	 * OK, there's a Var and a Const we're dealing with here.  We need the
+	 * Const to be of same multirange type as the column, else we can't do anything
+	 * useful. (Such cases will likely fail at runtime, but here we'd rather
+	 * just return a default estimate.)
+	 *
+	 * If the operator is "multirange @> element", the constant should be of the
+	 * element type of the multirange column. Convert it to a multirange that includes
+	 * only that single point, so that we don't need special handling for that
+	 * in what follows.
+	 */
+	if (operator == OID_MULTIRANGE_CONTAINS_ELEM_OP)
+	{
+		typcache = multirange_get_typcache(fcinfo, vardata.vartype);
+
+		if (((Const *) other)->consttype == typcache->rngtype->rngelemtype->type_id)
+		{
+			RangeBound	lower,
+						upper;
+
+			lower.inclusive = true;
+			lower.val = ((Const *) other)->constvalue;
+			lower.infinite = false;
+			lower.lower = true;
+			upper.inclusive = true;
+			upper.val = ((Const *) other)->constvalue;
+			upper.infinite = false;
+			upper.lower = false;
+			constrange = range_serialize(typcache->rngtype, &lower, &upper, false);
+			constmultirange = make_multirange(typcache->type_id, typcache->rngtype,
+					1, &constrange);
+		}
+	}
+	else if (operator == OID_MULTIRANGE_CONTAINS_RANGE_OP ||
+			 operator == OID_MULTIRANGE_OVERLAPS_RANGE_OP ||
+			 operator == OID_MULTIRANGE_OVERLAPS_LEFT_RANGE_OP ||
+			 operator == OID_MULTIRANGE_OVERLAPS_RIGHT_RANGE_OP ||
+			 operator == OID_MULTIRANGE_LEFT_RANGE_OP ||
+			 operator == OID_MULTIRANGE_RIGHT_RANGE_OP)
+	{
+		/*
+		 * Promote a range in "multirange OP range" just like
+		 * we do an element in "multirange OP element".
+		 */
+		typcache = multirange_get_typcache(fcinfo, vardata.vartype);
+		if (((Const *) other)->consttype == typcache->rngtype->type_id)
+		{
+			constrange = DatumGetRangeTypeP(((Const *) other)->constvalue);
+			constmultirange = make_multirange(typcache->type_id, typcache->rngtype,
+					1, &constrange);
+		}
+	}
+	else if (operator == OID_RANGE_OVERLAPS_MULTIRANGE_OP ||
+			 operator == OID_RANGE_OVERLAPS_LEFT_MULTIRANGE_OP ||
+			 operator == OID_RANGE_OVERLAPS_RIGHT_MULTIRANGE_OP ||
+			 operator == OID_RANGE_LEFT_MULTIRANGE_OP ||
+			 operator == OID_RANGE_RIGHT_MULTIRANGE_OP ||
+			 operator == OID_MULTIRANGE_ELEM_CONTAINED_OP ||
+			 operator == OID_MULTIRANGE_RANGE_CONTAINED_OP)
+	{
+		/*
+		 * Here, the Var is the elem/range, not the multirange.  For now we just punt and
+		 * return the default estimate.  In future we could disassemble the
+		 * multirange constant to do something more intelligent.
+		 */
+	}
+	else if (((Const *) other)->consttype == vardata.vartype)
+	{
+		/* Both sides are the same multirange type */
+		typcache = multirange_get_typcache(fcinfo, vardata.vartype);
+
+		constmultirange = DatumGetMultirangeTypeP(((Const *) other)->constvalue);
+	}
+
+	/*
+	 * If we got a valid constant on one side of the operator, proceed to
+	 * estimate using statistics. Otherwise punt and return a default constant
+	 * estimate.  Note that calc_multirangesel need not handle
+	 * OID_MULTIRANGE_*_CONTAINED_OP.
+	 */
+	if (constmultirange)
+		selec = calc_multirangesel(typcache, &vardata, constmultirange, operator);
+	else
+		selec = default_multirange_selectivity(operator);
+
+	ReleaseVariableStats(vardata);
+
+	CLAMP_PROBABILITY(selec);
+
+	PG_RETURN_FLOAT8((float8) selec);
+}
+
+static double
+calc_multirangesel(TypeCacheEntry *typcache, VariableStatData *vardata,
+			  const MultirangeType *constval, Oid operator)
+{
+	double		hist_selec;
+	double		selec;
+	float4		empty_frac,
+				null_frac;
+
+	/*
+	 * First look up the fraction of NULLs and empty multiranges from pg_statistic.
+	 */
+	if (HeapTupleIsValid(vardata->statsTuple))
+	{
+		Form_pg_statistic stats;
+		AttStatsSlot sslot;
+
+		stats = (Form_pg_statistic) GETSTRUCT(vardata->statsTuple);
+		null_frac = stats->stanullfrac;
+
+		/* Try to get fraction of empty multiranges */
+		if (get_attstatsslot(&sslot, vardata->statsTuple,
+							 STATISTIC_KIND_RANGE_LENGTH_HISTOGRAM,
+							 InvalidOid,
+							 ATTSTATSSLOT_NUMBERS))
+		{
+			if (sslot.nnumbers != 1)
+				elog(ERROR, "invalid empty fraction statistic");	/* shouldn't happen */
+			empty_frac = sslot.numbers[0];
+			free_attstatsslot(&sslot);
+		}
+		else
+		{
+			/* No empty fraction statistic. Assume no empty ranges. */
+			empty_frac = 0.0;
+		}
+	}
+	else
+	{
+		/*
+		 * No stats are available. Follow through the calculations below
+		 * anyway, assuming no NULLs and no empty multiranges. This still allows us
+		 * to give a better-than-nothing estimate based on whether the
+		 * constant is an empty multirange or not.
+		 */
+		null_frac = 0.0;
+		empty_frac = 0.0;
+	}
+
+	if (MultirangeIsEmpty(constval))
+	{
+		/*
+		 * An empty multirange matches all multiranges, all empty multiranges, or
+		 * nothing, depending on the operator
+		 */
+		switch (operator)
+		{
+				/* these return false if either argument is empty */
+			case OID_RANGE_OVERLAPS_MULTIRANGE_OP:
+			case OID_MULTIRANGE_OVERLAPS_RANGE_OP:
+			case OID_MULTIRANGE_OVERLAPS_MULTIRANGE_OP:
+			case OID_RANGE_OVERLAPS_LEFT_MULTIRANGE_OP:
+			case OID_MULTIRANGE_OVERLAPS_LEFT_RANGE_OP:
+			case OID_MULTIRANGE_OVERLAPS_LEFT_MULTIRANGE_OP:
+			case OID_RANGE_OVERLAPS_RIGHT_MULTIRANGE_OP:
+			case OID_MULTIRANGE_OVERLAPS_RIGHT_RANGE_OP:
+			case OID_MULTIRANGE_OVERLAPS_RIGHT_MULTIRANGE_OP:
+			case OID_MULTIRANGE_LEFT_MULTIRANGE_OP:
+			case OID_MULTIRANGE_RIGHT_MULTIRANGE_OP:
+				/* nothing is less than an empty multirange */
+			case OID_MULTIRANGE_LESS_OP:
+				selec = 0.0;
+				break;
+
+				/* only empty multiranges can be contained by an empty multirange */
+			case OID_MULTIRANGE_RANGE_CONTAINED_OP:
+			case OID_MULTIRANGE_MULTIRANGE_CONTAINED_OP:
+				/* only empty ranges are <= an empty multirange */
+			case OID_MULTIRANGE_LESS_EQUAL_OP:
+				selec = empty_frac;
+				break;
+
+				/* everything contains an empty multirange */
+			case OID_MULTIRANGE_CONTAINS_RANGE_OP:
+			case OID_MULTIRANGE_CONTAINS_MULTIRANGE_OP:
+				/* everything is >= an empty multirange */
+			case OID_MULTIRANGE_GREATER_EQUAL_OP:
+				selec = 1.0;
+				break;
+
+				/* all non-empty multiranges are > an empty multirange */
+			case OID_MULTIRANGE_GREATER_OP:
+				selec = 1.0 - empty_frac;
+				break;
+
+				/* an element cannot be empty */
+			case OID_MULTIRANGE_ELEM_CONTAINED_OP:
+			case OID_MULTIRANGE_CONTAINS_ELEM_OP:
+			default:
+				elog(ERROR, "unexpected operator %u", operator);
+				selec = 0.0;	/* keep compiler quiet */
+				break;
+		}
+	}
+	else
+	{
+		/*
+		 * Calculate selectivity using bound histograms. If that fails for
+		 * some reason, e.g no histogram in pg_statistic, use the default
+		 * constant estimate for the fraction of non-empty values. This is
+		 * still somewhat better than just returning the default estimate,
+		 * because this still takes into account the fraction of empty and
+		 * NULL tuples, if we had statistics for them.
+		 */
+		hist_selec = calc_hist_selectivity(typcache, vardata, constval,
+										   operator);
+		if (hist_selec < 0.0)
+			hist_selec = default_multirange_selectivity(operator);
+
+		/*
+		 * Now merge the results for the empty multiranges and histogram
+		 * calculations, realizing that the histogram covers only the
+		 * non-null, non-empty values.
+		 */
+		if (operator == OID_MULTIRANGE_ELEM_CONTAINED_OP ||
+			operator == OID_MULTIRANGE_RANGE_CONTAINED_OP ||
+			operator == OID_MULTIRANGE_MULTIRANGE_CONTAINED_OP)
+		{
+			/* empty is contained by anything non-empty */
+			selec = (1.0 - empty_frac) * hist_selec + empty_frac;
+		}
+		else
+		{
+			/* with any other operator, empty Op non-empty matches nothing */
+			selec = (1.0 - empty_frac) * hist_selec;
+		}
+	}
+
+	/* all multirange operators are strict */
+	selec *= (1.0 - null_frac);
+
+	/* result should be in range, but make sure... */
+	CLAMP_PROBABILITY(selec);
+
+	return selec;
+}
+
+/*
+ * Calculate multirange operator selectivity using histograms of multirange bounds.
+ *
+ * This estimate is for the portion of values that are not empty and not
+ * NULL.
+ */
+static double
+calc_hist_selectivity(TypeCacheEntry *typcache, VariableStatData *vardata,
+					  const MultirangeType *constval, Oid operator)
+{
+	TypeCacheEntry *rng_typcache = typcache->rngtype;
+	AttStatsSlot	hslot;
+	AttStatsSlot	lslot;
+	int			nhist;
+	RangeBound *hist_lower;
+	RangeBound *hist_upper;
+	int			i;
+	RangeBound	const_lower;
+	RangeBound	const_upper;
+	bool		empty;
+	double		hist_selec;
+	int			range_count;
+	RangeType **ranges;
+	RangeType  *constrange;
+
+	/* Can't use the histogram with insecure multirange support functions */
+	if (!statistic_proc_security_check(vardata,
+									   rng_typcache->rng_cmp_proc_finfo.fn_oid))
+		return -1;
+	if (OidIsValid(rng_typcache->rng_subdiff_finfo.fn_oid) &&
+		!statistic_proc_security_check(vardata,
+									   rng_typcache->rng_subdiff_finfo.fn_oid))
+		return -1;
+
+	/* Try to get histogram of ranges */
+	if (!(HeapTupleIsValid(vardata->statsTuple) &&
+		  get_attstatsslot(&hslot, vardata->statsTuple,
+						   STATISTIC_KIND_BOUNDS_HISTOGRAM, InvalidOid,
+						   ATTSTATSSLOT_VALUES)))
+		return -1.0;
+
+	/* check that it's a histogram, not just a dummy entry */
+	if (hslot.nvalues < 2)
+	{
+		free_attstatsslot(&hslot);
+		return -1.0;
+	}
+
+	/*
+	 * Convert histogram of ranges into histograms of its lower and upper
+	 * bounds.
+	 */
+	nhist = hslot.nvalues;
+	hist_lower = (RangeBound *) palloc(sizeof(RangeBound) * nhist);
+	hist_upper = (RangeBound *) palloc(sizeof(RangeBound) * nhist);
+	for (i = 0; i < nhist; i++)
+	{
+		range_deserialize(rng_typcache, DatumGetRangeTypeP(hslot.values[i]),
+						  &hist_lower[i], &hist_upper[i], &empty);
+		/* The histogram should not contain any empty ranges */
+		if (empty)
+			elog(ERROR, "bounds histogram contains an empty range");
+	}
+
+	/* @> and @< also need a histogram of range lengths */
+	if (operator == OID_MULTIRANGE_CONTAINS_RANGE_OP ||
+		operator == OID_MULTIRANGE_CONTAINS_MULTIRANGE_OP ||
+		operator == OID_MULTIRANGE_RANGE_CONTAINED_OP ||
+		operator == OID_MULTIRANGE_MULTIRANGE_CONTAINED_OP)
+	{
+		if (!(HeapTupleIsValid(vardata->statsTuple) &&
+			  get_attstatsslot(&lslot, vardata->statsTuple,
+							   STATISTIC_KIND_RANGE_LENGTH_HISTOGRAM,
+							   InvalidOid,
+							   ATTSTATSSLOT_VALUES)))
+		{
+			free_attstatsslot(&hslot);
+			return -1.0;
+		}
+
+		/* check that it's a histogram, not just a dummy entry */
+		if (lslot.nvalues < 2)
+		{
+			free_attstatsslot(&lslot);
+			free_attstatsslot(&hslot);
+			return -1.0;
+		}
+	}
+	else
+		memset(&lslot, 0, sizeof(lslot));
+
+	/* Extract the bounds of the constant value. */
+	multirange_deserialize(rng_typcache, constval, &range_count, &ranges);
+	Assert(range_count > 0);
+	constrange = range_union_internal(rng_typcache, ranges[0],
+			ranges[range_count - 1], false);
+	range_deserialize(rng_typcache, constrange, &const_lower, &const_upper, &empty);
+
+	/*
+	 * Calculate selectivity comparing the lower or upper bound of the
+	 * constant with the histogram of lower or upper bounds.
+	 */
+	switch (operator)
+	{
+		case OID_MULTIRANGE_LESS_OP:
+
+			/*
+			 * The regular b-tree comparison operators (<, <=, >, >=) compare
+			 * the lower bounds first, and the upper bounds for values with
+			 * equal lower bounds. Estimate that by comparing the lower bounds
+			 * only. This gives a fairly accurate estimate assuming there
+			 * aren't many rows with a lower bound equal to the constant's
+			 * lower bound.
+			 */
+			hist_selec =
+				calc_hist_selectivity_scalar(rng_typcache, &const_lower,
+											 hist_lower, nhist, false);
+			break;
+
+		case OID_MULTIRANGE_LESS_EQUAL_OP:
+			hist_selec =
+				calc_hist_selectivity_scalar(rng_typcache, &const_lower,
+											 hist_lower, nhist, true);
+			break;
+
+		case OID_MULTIRANGE_GREATER_OP:
+			hist_selec =
+				1 - calc_hist_selectivity_scalar(rng_typcache, &const_lower,
+												 hist_lower, nhist, false);
+			break;
+
+		case OID_MULTIRANGE_GREATER_EQUAL_OP:
+			hist_selec =
+				1 - calc_hist_selectivity_scalar(rng_typcache, &const_lower,
+												 hist_lower, nhist, true);
+			break;
+
+		case OID_RANGE_LEFT_MULTIRANGE_OP:
+		case OID_MULTIRANGE_LEFT_RANGE_OP:
+		case OID_MULTIRANGE_LEFT_MULTIRANGE_OP:
+			/* var << const when upper(var) < lower(const) */
+			hist_selec =
+				calc_hist_selectivity_scalar(rng_typcache, &const_lower,
+											 hist_upper, nhist, false);
+			break;
+
+		case OID_RANGE_RIGHT_MULTIRANGE_OP:
+		case OID_MULTIRANGE_RIGHT_RANGE_OP:
+		case OID_MULTIRANGE_RIGHT_MULTIRANGE_OP:
+			/* var >> const when lower(var) > upper(const) */
+			hist_selec =
+				1 - calc_hist_selectivity_scalar(rng_typcache, &const_upper,
+												 hist_lower, nhist, true);
+			break;
+
+		case OID_RANGE_OVERLAPS_RIGHT_MULTIRANGE_OP:
+		case OID_MULTIRANGE_OVERLAPS_RIGHT_RANGE_OP:
+		case OID_MULTIRANGE_OVERLAPS_RIGHT_MULTIRANGE_OP:
+			/* compare lower bounds */
+			hist_selec =
+				1 - calc_hist_selectivity_scalar(rng_typcache, &const_lower,
+												 hist_lower, nhist, false);
+			break;
+
+		case OID_RANGE_OVERLAPS_LEFT_MULTIRANGE_OP:
+		case OID_MULTIRANGE_OVERLAPS_LEFT_RANGE_OP:
+		case OID_MULTIRANGE_OVERLAPS_LEFT_MULTIRANGE_OP:
+			/* compare upper bounds */
+			hist_selec =
+				calc_hist_selectivity_scalar(rng_typcache, &const_upper,
+											 hist_upper, nhist, true);
+			break;
+
+		case OID_RANGE_OVERLAPS_MULTIRANGE_OP:
+		case OID_MULTIRANGE_OVERLAPS_RANGE_OP:
+		case OID_MULTIRANGE_OVERLAPS_MULTIRANGE_OP:
+		case OID_MULTIRANGE_CONTAINS_ELEM_OP:
+
+			/*
+			 * A && B <=> NOT (A << B OR A >> B).
+			 *
+			 * Since A << B and A >> B are mutually exclusive events we can
+			 * sum their probabilities to find probability of (A << B OR A >>
+			 * B).
+			 *
+			 * "multirange @> elem" is equivalent to "multirange && {[elem,elem]}".
+			 * The caller already constructed the singular range from the element
+			 * constant, so just treat it the same as &&.
+			 */
+			hist_selec =
+				calc_hist_selectivity_scalar(rng_typcache, &const_lower, hist_upper,
+											 nhist, false);
+			hist_selec +=
+				(1.0 - calc_hist_selectivity_scalar(rng_typcache, &const_upper, hist_lower,
+													nhist, true));
+			hist_selec = 1.0 - hist_selec;
+			break;
+
+		case OID_MULTIRANGE_CONTAINS_RANGE_OP:
+		case OID_MULTIRANGE_CONTAINS_MULTIRANGE_OP:
+			hist_selec =
+				calc_hist_selectivity_contains(rng_typcache, &const_lower,
+											   &const_upper, hist_lower, nhist,
+											   lslot.values, lslot.nvalues);
+			break;
+
+		case OID_MULTIRANGE_RANGE_CONTAINED_OP:
+		case OID_MULTIRANGE_MULTIRANGE_CONTAINED_OP:
+			if (const_lower.infinite)
+			{
+				/*
+				 * Lower bound no longer matters. Just estimate the fraction
+				 * with an upper bound <= const upper bound
+				 */
+				hist_selec =
+					calc_hist_selectivity_scalar(rng_typcache, &const_upper,
+												 hist_upper, nhist, true);
+			}
+			else if (const_upper.infinite)
+			{
+				hist_selec =
+					1.0 - calc_hist_selectivity_scalar(rng_typcache, &const_lower,
+													   hist_lower, nhist, false);
+			}
+			else
+			{
+				hist_selec =
+					calc_hist_selectivity_contained(rng_typcache, &const_lower,
+													&const_upper, hist_lower, nhist,
+													lslot.values, lslot.nvalues);
+			}
+			break;
+
+		default:
+			elog(ERROR, "unknown multirange operator %u", operator);
+			hist_selec = -1.0;	/* keep compiler quiet */
+			break;
+	}
+
+	free_attstatsslot(&lslot);
+	free_attstatsslot(&hslot);
+
+	return hist_selec;
+}
+
+
+/*
+ * Look up the fraction of values less than (or equal, if 'equal' argument
+ * is true) a given const in a histogram of range bounds.
+ */
+static double
+calc_hist_selectivity_scalar(TypeCacheEntry *typcache, const RangeBound *constbound,
+							 const RangeBound *hist, int hist_nvalues, bool equal)
+{
+	Selectivity selec;
+	int			index;
+
+	/*
+	 * Find the histogram bin the given constant falls into. Estimate
+	 * selectivity as the number of preceding whole bins.
+	 */
+	index = rbound_bsearch(typcache, constbound, hist, hist_nvalues, equal);
+	selec = (Selectivity) (Max(index, 0)) / (Selectivity) (hist_nvalues - 1);
+
+	/* Adjust using linear interpolation within the bin */
+	if (index >= 0 && index < hist_nvalues - 1)
+		selec += get_position(typcache, constbound, &hist[index],
+							  &hist[index + 1]) / (Selectivity) (hist_nvalues - 1);
+
+	return selec;
+}
+
+/*
+ * Binary search on an array of range bounds. Returns greatest index of range
+ * bound in array which is less(less or equal) than given range bound. If all
+ * range bounds in array are greater or equal(greater) than given range bound,
+ * return -1. When "equal" flag is set conditions in brackets are used.
+ *
+ * This function is used in scalar operator selectivity estimation. Another
+ * goal of this function is to find a histogram bin where to stop
+ * interpolation of portion of bounds which are less or equal to given bound.
+ */
+static int
+rbound_bsearch(TypeCacheEntry *typcache, const RangeBound *value, const RangeBound *hist,
+			   int hist_length, bool equal)
+{
+	int			lower = -1,
+				upper = hist_length - 1,
+				cmp,
+				middle;
+
+	while (lower < upper)
+	{
+		middle = (lower + upper + 1) / 2;
+		cmp = range_cmp_bounds(typcache, &hist[middle], value);
+
+		if (cmp < 0 || (equal && cmp == 0))
+			lower = middle;
+		else
+			upper = middle - 1;
+	}
+	return lower;
+}
+
+
+/*
+ * Binary search on length histogram. Returns greatest index of range length in
+ * histogram which is less than (less than or equal) the given length value. If
+ * all lengths in the histogram are greater than (greater than or equal) the
+ * given length, returns -1.
+ */
+static int
+length_hist_bsearch(Datum *length_hist_values, int length_hist_nvalues,
+					double value, bool equal)
+{
+	int			lower = -1,
+				upper = length_hist_nvalues - 1,
+				middle;
+
+	while (lower < upper)
+	{
+		double		middleval;
+
+		middle = (lower + upper + 1) / 2;
+
+		middleval = DatumGetFloat8(length_hist_values[middle]);
+		if (middleval < value || (equal && middleval <= value))
+			lower = middle;
+		else
+			upper = middle - 1;
+	}
+	return lower;
+}
+
+/*
+ * Get relative position of value in histogram bin in [0,1] range.
+ */
+static float8
+get_position(TypeCacheEntry *typcache, const RangeBound *value, const RangeBound *hist1,
+			 const RangeBound *hist2)
+{
+	bool		has_subdiff = OidIsValid(typcache->rng_subdiff_finfo.fn_oid);
+	float8		position;
+
+	if (!hist1->infinite && !hist2->infinite)
+	{
+		float8		bin_width;
+
+		/*
+		 * Both bounds are finite. Assuming the subtype's comparison function
+		 * works sanely, the value must be finite, too, because it lies
+		 * somewhere between the bounds.  If it doesn't, arbitrarily return
+		 * 0.5.
+		 */
+		if (value->infinite)
+			return 0.5;
+
+		/* Can't interpolate without subdiff function */
+		if (!has_subdiff)
+			return 0.5;
+
+		/* Calculate relative position using subdiff function. */
+		bin_width = DatumGetFloat8(FunctionCall2Coll(&typcache->rng_subdiff_finfo,
+													 typcache->rng_collation,
+													 hist2->val,
+													 hist1->val));
+		if (isnan(bin_width) || bin_width <= 0.0)
+			return 0.5;			/* punt for NaN or zero-width bin */
+
+		position = DatumGetFloat8(FunctionCall2Coll(&typcache->rng_subdiff_finfo,
+													typcache->rng_collation,
+													value->val,
+													hist1->val))
+			/ bin_width;
+
+		if (isnan(position))
+			return 0.5;			/* punt for NaN from subdiff, Inf/Inf, etc */
+
+		/* Relative position must be in [0,1] range */
+		position = Max(position, 0.0);
+		position = Min(position, 1.0);
+		return position;
+	}
+	else if (hist1->infinite && !hist2->infinite)
+	{
+		/*
+		 * Lower bin boundary is -infinite, upper is finite. If the value is
+		 * -infinite, return 0.0 to indicate it's equal to the lower bound.
+		 * Otherwise return 1.0 to indicate it's infinitely far from the lower
+		 * bound.
+		 */
+		return ((value->infinite && value->lower) ? 0.0 : 1.0);
+	}
+	else if (!hist1->infinite && hist2->infinite)
+	{
+		/* same as above, but in reverse */
+		return ((value->infinite && !value->lower) ? 1.0 : 0.0);
+	}
+	else
+	{
+		/*
+		 * If both bin boundaries are infinite, they should be equal to each
+		 * other, and the value should also be infinite and equal to both
+		 * bounds. (But don't Assert that, to avoid crashing if a user creates
+		 * a datatype with a broken comparison function).
+		 *
+		 * Assume the value to lie in the middle of the infinite bounds.
+		 */
+		return 0.5;
+	}
+}
+
+
+/*
+ * Get relative position of value in a length histogram bin in [0,1] range.
+ */
+static double
+get_len_position(double value, double hist1, double hist2)
+{
+	if (!isinf(hist1) && !isinf(hist2))
+	{
+		/*
+		 * Both bounds are finite. The value should be finite too, because it
+		 * lies somewhere between the bounds. If it doesn't, just return
+		 * something.
+		 */
+		if (isinf(value))
+			return 0.5;
+
+		return 1.0 - (hist2 - value) / (hist2 - hist1);
+	}
+	else if (isinf(hist1) && !isinf(hist2))
+	{
+		/*
+		 * Lower bin boundary is -infinite, upper is finite. Return 1.0 to
+		 * indicate the value is infinitely far from the lower bound.
+		 */
+		return 1.0;
+	}
+	else if (isinf(hist1) && isinf(hist2))
+	{
+		/* same as above, but in reverse */
+		return 0.0;
+	}
+	else
+	{
+		/*
+		 * If both bin boundaries are infinite, they should be equal to each
+		 * other, and the value should also be infinite and equal to both
+		 * bounds. (But don't Assert that, to avoid crashing unnecessarily if
+		 * the caller messes up)
+		 *
+		 * Assume the value to lie in the middle of the infinite bounds.
+		 */
+		return 0.5;
+	}
+}
+
+/*
+ * Measure distance between two range bounds.
+ */
+static float8
+get_distance(TypeCacheEntry *typcache, const RangeBound *bound1, const RangeBound *bound2)
+{
+	bool		has_subdiff = OidIsValid(typcache->rng_subdiff_finfo.fn_oid);
+
+	if (!bound1->infinite && !bound2->infinite)
+	{
+		/*
+		 * Neither bound is infinite, use subdiff function or return default
+		 * value of 1.0 if no subdiff is available.
+		 */
+		if (has_subdiff)
+		{
+			float8		res;
+
+			res = DatumGetFloat8(FunctionCall2Coll(&typcache->rng_subdiff_finfo,
+												   typcache->rng_collation,
+												   bound2->val,
+												   bound1->val));
+			/* Reject possible NaN result, also negative result */
+			if (isnan(res) || res < 0.0)
+				return 1.0;
+			else
+				return res;
+		}
+		else
+			return 1.0;
+	}
+	else if (bound1->infinite && bound2->infinite)
+	{
+		/* Both bounds are infinite */
+		if (bound1->lower == bound2->lower)
+			return 0.0;
+		else
+			return get_float8_infinity();
+	}
+	else
+	{
+		/* One bound is infinite, the other is not */
+		return get_float8_infinity();
+	}
+}
+
+/*
+ * Calculate the average of function P(x), in the interval [length1, length2],
+ * where P(x) is the fraction of tuples with length < x (or length <= x if
+ * 'equal' is true).
+ */
+static double
+calc_length_hist_frac(Datum *length_hist_values, int length_hist_nvalues,
+					  double length1, double length2, bool equal)
+{
+	double		frac;
+	double		A,
+				B,
+				PA,
+				PB;
+	double		pos;
+	int			i;
+	double		area;
+
+	Assert(length2 >= length1);
+
+	if (length2 < 0.0)
+		return 0.0;				/* shouldn't happen, but doesn't hurt to check */
+
+	/* All lengths in the table are <= infinite. */
+	if (isinf(length2) && equal)
+		return 1.0;
+
+	/*----------
+	 * The average of a function between A and B can be calculated by the
+	 * formula:
+	 *
+	 *			B
+	 *	  1		/
+	 * -------	| P(x)dx
+	 *	B - A	/
+	 *			A
+	 *
+	 * The geometrical interpretation of the integral is the area under the
+	 * graph of P(x). P(x) is defined by the length histogram. We calculate
+	 * the area in a piecewise fashion, iterating through the length histogram
+	 * bins. Each bin is a trapezoid:
+	 *
+	 *		 P(x2)
+	 *		  /|
+	 *		 / |
+	 * P(x1)/  |
+	 *	   |   |
+	 *	   |   |
+	 *	---+---+--
+	 *	   x1  x2
+	 *
+	 * where x1 and x2 are the boundaries of the current histogram, and P(x1)
+	 * and P(x1) are the cumulative fraction of tuples at the boundaries.
+	 *
+	 * The area of each trapezoid is 1/2 * (P(x2) + P(x1)) * (x2 - x1)
+	 *
+	 * The first bin contains the lower bound passed by the caller, so we
+	 * use linear interpolation between the previous and next histogram bin
+	 * boundary to calculate P(x1). Likewise for the last bin: we use linear
+	 * interpolation to calculate P(x2). For the bins in between, x1 and x2
+	 * lie on histogram bin boundaries, so P(x1) and P(x2) are simply:
+	 * P(x1) =	  (bin index) / (number of bins)
+	 * P(x2) = (bin index + 1 / (number of bins)
+	 */
+
+	/* First bin, the one that contains lower bound */
+	i = length_hist_bsearch(length_hist_values, length_hist_nvalues, length1, equal);
+	if (i >= length_hist_nvalues - 1)
+		return 1.0;
+
+	if (i < 0)
+	{
+		i = 0;
+		pos = 0.0;
+	}
+	else
+	{
+		/* interpolate length1's position in the bin */
+		pos = get_len_position(length1,
+							   DatumGetFloat8(length_hist_values[i]),
+							   DatumGetFloat8(length_hist_values[i + 1]));
+	}
+	PB = (((double) i) + pos) / (double) (length_hist_nvalues - 1);
+	B = length1;
+
+	/*
+	 * In the degenerate case that length1 == length2, simply return
+	 * P(length1). This is not merely an optimization: if length1 == length2,
+	 * we'd divide by zero later on.
+	 */
+	if (length2 == length1)
+		return PB;
+
+	/*
+	 * Loop through all the bins, until we hit the last bin, the one that
+	 * contains the upper bound. (if lower and upper bounds are in the same
+	 * bin, this falls out immediately)
+	 */
+	area = 0.0;
+	for (; i < length_hist_nvalues - 1; i++)
+	{
+		double		bin_upper = DatumGetFloat8(length_hist_values[i + 1]);
+
+		/* check if we've reached the last bin */
+		if (!(bin_upper < length2 || (equal && bin_upper <= length2)))
+			break;
+
+		/* the upper bound of previous bin is the lower bound of this bin */
+		A = B;
+		PA = PB;
+
+		B = bin_upper;
+		PB = (double) i / (double) (length_hist_nvalues - 1);
+
+		/*
+		 * Add the area of this trapezoid to the total. The point of the
+		 * if-check is to avoid NaN, in the corner case that PA == PB == 0,
+		 * and B - A == Inf. The area of a zero-height trapezoid (PA == PB ==
+		 * 0) is zero, regardless of the width (B - A).
+		 */
+		if (PA > 0 || PB > 0)
+			area += 0.5 * (PB + PA) * (B - A);
+	}
+
+	/* Last bin */
+	A = B;
+	PA = PB;
+
+	B = length2;				/* last bin ends at the query upper bound */
+	if (i >= length_hist_nvalues - 1)
+		pos = 0.0;
+	else
+	{
+		if (DatumGetFloat8(length_hist_values[i]) == DatumGetFloat8(length_hist_values[i + 1]))
+			pos = 0.0;
+		else
+			pos = get_len_position(length2, DatumGetFloat8(length_hist_values[i]), DatumGetFloat8(length_hist_values[i + 1]));
+	}
+	PB = (((double) i) + pos) / (double) (length_hist_nvalues - 1);
+
+	if (PA > 0 || PB > 0)
+		area += 0.5 * (PB + PA) * (B - A);
+
+	/*
+	 * Ok, we have calculated the area, ie. the integral. Divide by width to
+	 * get the requested average.
+	 *
+	 * Avoid NaN arising from infinite / infinite. This happens at least if
+	 * length2 is infinite. It's not clear what the correct value would be in
+	 * that case, so 0.5 seems as good as any value.
+	 */
+	if (isinf(area) && isinf(length2))
+		frac = 0.5;
+	else
+		frac = area / (length2 - length1);
+
+	return frac;
+}
+
+/*
+ * Calculate selectivity of "var <@ const" operator, ie. estimate the fraction
+ * of multiranges that fall within the constant lower and upper bounds. This uses
+ * the histograms of range lower bounds and range lengths, on the assumption
+ * that the range lengths are independent of the lower bounds.
+ *
+ * The caller has already checked that constant lower and upper bounds are
+ * finite.
+ */
+static double
+calc_hist_selectivity_contained(TypeCacheEntry *typcache,
+								const RangeBound *lower, RangeBound *upper,
+								const RangeBound *hist_lower, int hist_nvalues,
+								Datum *length_hist_values, int length_hist_nvalues)
+{
+	int			i,
+				upper_index;
+	float8		prev_dist;
+	double		bin_width;
+	double		upper_bin_width;
+	double		sum_frac;
+
+	/*
+	 * Begin by finding the bin containing the upper bound, in the lower bound
+	 * histogram. Any range with a lower bound > constant upper bound can't
+	 * match, ie. there are no matches in bins greater than upper_index.
+	 */
+	upper->inclusive = !upper->inclusive;
+	upper->lower = true;
+	upper_index = rbound_bsearch(typcache, upper, hist_lower, hist_nvalues,
+								 false);
+
+	/*
+	 * If the upper bound value is below the histogram's lower limit, there
+	 * are no matches.
+	 */
+	if (upper_index < 0)
+		return 0.0;
+
+	/*
+	 * If the upper bound value is at or beyond the histogram's upper limit,
+	 * start our loop at the last actual bin, as though the upper bound were
+	 * within that bin; get_position will clamp its result to 1.0 anyway.
+	 * (This corresponds to assuming that the data population above the
+	 * histogram's upper limit is empty, exactly like what we just assumed for
+	 * the lower limit.)
+	 */
+	upper_index = Min(upper_index, hist_nvalues - 2);
+
+	/*
+	 * Calculate upper_bin_width, ie. the fraction of the (upper_index,
+	 * upper_index + 1) bin which is greater than upper bound of query range
+	 * using linear interpolation of subdiff function.
+	 */
+	upper_bin_width = get_position(typcache, upper,
+								   &hist_lower[upper_index],
+								   &hist_lower[upper_index + 1]);
+
+	/*
+	 * In the loop, dist and prev_dist are the distance of the "current" bin's
+	 * lower and upper bounds from the constant upper bound.
+	 *
+	 * bin_width represents the width of the current bin. Normally it is 1.0,
+	 * meaning a full width bin, but can be less in the corner cases: start
+	 * and end of the loop. We start with bin_width = upper_bin_width, because
+	 * we begin at the bin containing the upper bound.
+	 */
+	prev_dist = 0.0;
+	bin_width = upper_bin_width;
+
+	sum_frac = 0.0;
+	for (i = upper_index; i >= 0; i--)
+	{
+		double		dist;
+		double		length_hist_frac;
+		bool		final_bin = false;
+
+		/*
+		 * dist -- distance from upper bound of query range to lower bound of
+		 * the current bin in the lower bound histogram. Or to the lower bound
+		 * of the constant range, if this is the final bin, containing the
+		 * constant lower bound.
+		 */
+		if (range_cmp_bounds(typcache, &hist_lower[i], lower) < 0)
+		{
+			dist = get_distance(typcache, lower, upper);
+
+			/*
+			 * Subtract from bin_width the portion of this bin that we want to
+			 * ignore.
+			 */
+			bin_width -= get_position(typcache, lower, &hist_lower[i],
+									  &hist_lower[i + 1]);
+			if (bin_width < 0.0)
+				bin_width = 0.0;
+			final_bin = true;
+		}
+		else
+			dist = get_distance(typcache, &hist_lower[i], upper);
+
+		/*
+		 * Estimate the fraction of tuples in this bin that are narrow enough
+		 * to not exceed the distance to the upper bound of the query range.
+		 */
+		length_hist_frac = calc_length_hist_frac(length_hist_values,
+												 length_hist_nvalues,
+												 prev_dist, dist, true);
+
+		/*
+		 * Add the fraction of tuples in this bin, with a suitable length, to
+		 * the total.
+		 */
+		sum_frac += length_hist_frac * bin_width / (double) (hist_nvalues - 1);
+
+		if (final_bin)
+			break;
+
+		bin_width = 1.0;
+		prev_dist = dist;
+	}
+
+	return sum_frac;
+}
+
+/*
+ * Calculate selectivity of "var @> const" operator, ie. estimate the fraction
+ * of multiranges that contain the constant lower and upper bounds. This uses
+ * the histograms of range lower bounds and range lengths, on the assumption
+ * that the range lengths are independent of the lower bounds.
+ */
+static double
+calc_hist_selectivity_contains(TypeCacheEntry *typcache,
+							   const RangeBound *lower, const RangeBound *upper,
+							   const RangeBound *hist_lower, int hist_nvalues,
+							   Datum *length_hist_values, int length_hist_nvalues)
+{
+	int			i,
+				lower_index;
+	double		bin_width,
+				lower_bin_width;
+	double		sum_frac;
+	float8		prev_dist;
+
+	/* Find the bin containing the lower bound of query range. */
+	lower_index = rbound_bsearch(typcache, lower, hist_lower, hist_nvalues,
+								 true);
+
+	/*
+	 * If the lower bound value is below the histogram's lower limit, there
+	 * are no matches.
+	 */
+	if (lower_index < 0)
+		return 0.0;
+
+	/*
+	 * If the lower bound value is at or beyond the histogram's upper limit,
+	 * start our loop at the last actual bin, as though the upper bound were
+	 * within that bin; get_position will clamp its result to 1.0 anyway.
+	 * (This corresponds to assuming that the data population above the
+	 * histogram's upper limit is empty, exactly like what we just assumed for
+	 * the lower limit.)
+	 */
+	lower_index = Min(lower_index, hist_nvalues - 2);
+
+	/*
+	 * Calculate lower_bin_width, ie. the fraction of the of (lower_index,
+	 * lower_index + 1) bin which is greater than lower bound of query range
+	 * using linear interpolation of subdiff function.
+	 */
+	lower_bin_width = get_position(typcache, lower, &hist_lower[lower_index],
+								   &hist_lower[lower_index + 1]);
+
+	/*
+	 * Loop through all the lower bound bins, smaller than the query lower
+	 * bound. In the loop, dist and prev_dist are the distance of the
+	 * "current" bin's lower and upper bounds from the constant upper bound.
+	 * We begin from query lower bound, and walk backwards, so the first bin's
+	 * upper bound is the query lower bound, and its distance to the query
+	 * upper bound is the length of the query range.
+	 *
+	 * bin_width represents the width of the current bin. Normally it is 1.0,
+	 * meaning a full width bin, except for the first bin, which is only
+	 * counted up to the constant lower bound.
+	 */
+	prev_dist = get_distance(typcache, lower, upper);
+	sum_frac = 0.0;
+	bin_width = lower_bin_width;
+	for (i = lower_index; i >= 0; i--)
+	{
+		float8		dist;
+		double		length_hist_frac;
+
+		/*
+		 * dist -- distance from upper bound of query range to current value
+		 * of lower bound histogram or lower bound of query range (if we've
+		 * reach it).
+		 */
+		dist = get_distance(typcache, &hist_lower[i], upper);
+
+		/*
+		 * Get average fraction of length histogram which covers intervals
+		 * longer than (or equal to) distance to upper bound of query range.
+		 */
+		length_hist_frac =
+			1.0 - calc_length_hist_frac(length_hist_values,
+										length_hist_nvalues,
+										prev_dist, dist, false);
+
+		sum_frac += length_hist_frac * bin_width / (double) (hist_nvalues - 1);
+
+		bin_width = 1.0;
+		prev_dist = dist;
+	}
+
+	return sum_frac;
+}
diff --git a/src/backend/utils/adt/pg_upgrade_support.c b/src/backend/utils/adt/pg_upgrade_support.c
index 8a5953881ee..521ebaaab6d 100644
--- a/src/backend/utils/adt/pg_upgrade_support.c
+++ b/src/backend/utils/adt/pg_upgrade_support.c
@@ -52,6 +52,28 @@ binary_upgrade_set_next_array_pg_type_oid(PG_FUNCTION_ARGS)
 	PG_RETURN_VOID();
 }
 
+Datum
+binary_upgrade_set_next_multirange_pg_type_oid(PG_FUNCTION_ARGS)
+{
+	Oid			typoid = PG_GETARG_OID(0);
+
+	CHECK_IS_BINARY_UPGRADE;
+	binary_upgrade_next_mrng_pg_type_oid = typoid;
+
+	PG_RETURN_VOID();
+}
+
+Datum
+binary_upgrade_set_next_multirange_array_pg_type_oid(PG_FUNCTION_ARGS)
+{
+	Oid			typoid = PG_GETARG_OID(0);
+
+	CHECK_IS_BINARY_UPGRADE;
+	binary_upgrade_next_mrng_array_pg_type_oid = typoid;
+
+	PG_RETURN_VOID();
+}
+
 Datum
 binary_upgrade_set_next_heap_pg_class_oid(PG_FUNCTION_ARGS)
 {
diff --git a/src/backend/utils/adt/pseudotypes.c b/src/backend/utils/adt/pseudotypes.c
index 3d6b2f90935..99a93271fe1 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"
 
 
 /*
@@ -227,6 +228,43 @@ anycompatiblerange_out(PG_FUNCTION_ARGS)
 	return range_out(fcinfo);
 }
 
+/*
+ * anycompatiblemultirange
+ *
+ * We may as well allow output, since multirange_out will in fact work.
+ */
+PSEUDOTYPE_DUMMY_INPUT_FUNC(anycompatiblemultirange);
+
+Datum
+anycompatiblemultirange_out(PG_FUNCTION_ARGS)
+{
+	return multirange_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
  *
diff --git a/src/backend/utils/adt/rangetypes.c b/src/backend/utils/adt/rangetypes.c
index 01ad8bc240b..ba4caa2d36a 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
 {
@@ -64,10 +62,6 @@ static const char *range_parse_bound(const char *string, const char *ptr,
 static char *range_deparse(char flags, const char *lbound_str,
 						   const char *ubound_str);
 static char *range_bound_escape(const char *value);
-static Size datum_compute_size(Size sz, Datum datum, bool typbyval,
-							   char typalign, int16 typlen, char typstorage);
-static Pointer datum_write(Pointer ptr, Datum datum, bool typbyval,
-						   char typalign, int16 typlen, char typstorage);
 
 
 /*
@@ -431,19 +425,37 @@ range_lower(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
 	TypeCacheEntry *typcache;
+	bool		isnull;
+	Datum		result;
+
+	typcache = range_get_typcache(fcinfo, RangeTypeGetOid(r1));
+
+	result = range_lower_internal(typcache, r1, &isnull);
+
+	if (isnull)
+		PG_RETURN_NULL();
+	else
+		PG_RETURN_DATUM(result);
+}
+
+Datum
+range_lower_internal(TypeCacheEntry *typcache, const RangeType *r1, bool *isnull)
+{
 	RangeBound	lower;
 	RangeBound	upper;
 	bool		empty;
 
-	typcache = range_get_typcache(fcinfo, RangeTypeGetOid(r1));
-
 	range_deserialize(typcache, r1, &lower, &upper, &empty);
 
 	/* Return NULL if there's no finite lower bound */
 	if (empty || lower.infinite)
-		PG_RETURN_NULL();
+	{
+		*isnull = true;
+		return 0;
+	}
 
-	PG_RETURN_DATUM(lower.val);
+	*isnull = false;
+	return lower.val;
 }
 
 /* extract upper bound value */
@@ -452,19 +464,37 @@ range_upper(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
 	TypeCacheEntry *typcache;
+	bool		isnull;
+	Datum		result;
+
+	typcache = range_get_typcache(fcinfo, RangeTypeGetOid(r1));
+
+	result = range_upper_internal(typcache, r1, &isnull);
+
+	if (isnull)
+		PG_RETURN_NULL();
+	else
+		PG_RETURN_DATUM(result);
+}
+
+Datum
+range_upper_internal(TypeCacheEntry *typcache, const RangeType *r1, bool *isnull)
+{
 	RangeBound	lower;
 	RangeBound	upper;
 	bool		empty;
 
-	typcache = range_get_typcache(fcinfo, RangeTypeGetOid(r1));
-
 	range_deserialize(typcache, r1, &lower, &upper, &empty);
 
 	/* Return NULL if there's no finite upper bound */
 	if (empty || upper.infinite)
-		PG_RETURN_NULL();
+	{
+		*isnull = true;
+		return 0;
+	}
 
-	PG_RETURN_DATUM(upper.val);
+	*isnull = false;
+	return upper.val;
 }
 
 
@@ -475,9 +505,8 @@ Datum
 range_empty(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
-	char		flags = range_get_flags(r1);
 
-	PG_RETURN_BOOL(flags & RANGE_EMPTY);
+	PG_RETURN_BOOL(range_has_flag(r1, RANGE_EMPTY));
 }
 
 /* is lower bound inclusive? */
@@ -485,9 +514,8 @@ Datum
 range_lower_inc(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
-	char		flags = range_get_flags(r1);
 
-	PG_RETURN_BOOL(flags & RANGE_LB_INC);
+	PG_RETURN_BOOL(range_has_flag(r1, RANGE_LB_INC));
 }
 
 /* is upper bound inclusive? */
@@ -495,9 +523,8 @@ Datum
 range_upper_inc(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
-	char		flags = range_get_flags(r1);
 
-	PG_RETURN_BOOL(flags & RANGE_UB_INC);
+	PG_RETURN_BOOL(range_has_flag(r1, RANGE_UB_INC));
 }
 
 /* is lower bound infinite? */
@@ -505,9 +532,8 @@ Datum
 range_lower_inf(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
-	char		flags = range_get_flags(r1);
 
-	PG_RETURN_BOOL(flags & RANGE_LB_INF);
+	PG_RETURN_BOOL(range_has_flag(r1, RANGE_LB_INF));
 }
 
 /* is upper bound infinite? */
@@ -515,9 +541,8 @@ Datum
 range_upper_inf(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
-	char		flags = range_get_flags(r1);
 
-	PG_RETURN_BOOL(flags & RANGE_UB_INF);
+	PG_RETURN_BOOL(range_has_flag(r1, RANGE_UB_INF));
 }
 
 
@@ -957,7 +982,25 @@ range_minus(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
 	RangeType  *r2 = PG_GETARG_RANGE_P(1);
+	RangeType  *ret;
 	TypeCacheEntry *typcache;
+
+	/* 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));
+
+	ret = range_minus_internal(typcache, r1, r2);
+	if (ret)
+		PG_RETURN_RANGE_P(ret);
+	else
+		PG_RETURN_NULL();
+}
+
+RangeType *
+range_minus_internal(TypeCacheEntry *typcache, RangeType *r1, RangeType *r2)
+{
 	RangeBound	lower1,
 				lower2;
 	RangeBound	upper1,
@@ -969,18 +1012,12 @@ range_minus(PG_FUNCTION_ARGS)
 				cmp_u1l2,
 				cmp_u1u2;
 
-	/* 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));
-
 	range_deserialize(typcache, r1, &lower1, &upper1, &empty1);
 	range_deserialize(typcache, r2, &lower2, &upper2, &empty2);
 
 	/* if either is empty, r1 is the correct answer */
 	if (empty1 || empty2)
-		PG_RETURN_RANGE_P(r1);
+		return r1;
 
 	cmp_l1l2 = range_cmp_bounds(typcache, &lower1, &lower2);
 	cmp_l1u2 = range_cmp_bounds(typcache, &lower1, &upper2);
@@ -993,34 +1030,34 @@ range_minus(PG_FUNCTION_ARGS)
 				 errmsg("result of range difference would not be contiguous")));
 
 	if (cmp_l1u2 > 0 || cmp_u1l2 < 0)
-		PG_RETURN_RANGE_P(r1);
+		return r1;
 
 	if (cmp_l1l2 >= 0 && cmp_u1u2 <= 0)
-		PG_RETURN_RANGE_P(make_empty_range(typcache));
+		return make_empty_range(typcache);
 
 	if (cmp_l1l2 <= 0 && cmp_u1l2 >= 0 && cmp_u1u2 <= 0)
 	{
 		lower2.inclusive = !lower2.inclusive;
 		lower2.lower = false;	/* it will become the upper bound */
-		PG_RETURN_RANGE_P(make_range(typcache, &lower1, &lower2, false));
+		return make_range(typcache, &lower1, &lower2, false);
 	}
 
 	if (cmp_l1l2 >= 0 && cmp_u1u2 >= 0 && cmp_l1u2 <= 0)
 	{
 		upper2.inclusive = !upper2.inclusive;
 		upper2.lower = true;	/* it will become the lower bound */
-		PG_RETURN_RANGE_P(make_range(typcache, &upper2, &upper1, false));
+		return make_range(typcache, &upper2, &upper1, false);
 	}
 
 	elog(ERROR, "unexpected case in range_minus");
-	PG_RETURN_NULL();
+	return NULL;
 }
 
 /*
  * Set union.  If strict is true, it is an error that the two input ranges
  * are not adjacent or overlapping.
  */
-static RangeType *
+RangeType *
 range_union_internal(TypeCacheEntry *typcache, RangeType *r1, RangeType *r2,
 					 bool strict)
 {
@@ -1101,6 +1138,19 @@ range_intersect(PG_FUNCTION_ARGS)
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
 	RangeType  *r2 = PG_GETARG_RANGE_P(1);
 	TypeCacheEntry *typcache;
+
+	/* 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));
+
+	PG_RETURN_RANGE_P(range_intersect_internal(typcache, r1, r2));
+}
+
+RangeType *
+range_intersect_internal(TypeCacheEntry *typcache, const RangeType *r1, const RangeType *r2)
+{
 	RangeBound	lower1,
 				lower2;
 	RangeBound	upper1,
@@ -1110,17 +1160,11 @@ range_intersect(PG_FUNCTION_ARGS)
 	RangeBound *result_lower;
 	RangeBound *result_upper;
 
-	/* 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));
-
 	range_deserialize(typcache, r1, &lower1, &upper1, &empty1);
 	range_deserialize(typcache, r2, &lower2, &upper2, &empty2);
 
-	if (empty1 || empty2 || !DatumGetBool(range_overlaps(fcinfo)))
-		PG_RETURN_RANGE_P(make_empty_range(typcache));
+	if (empty1 || empty2 || !range_overlaps_internal(typcache, r1, r2))
+		return make_empty_range(typcache);
 
 	if (range_cmp_bounds(typcache, &lower1, &lower2) >= 0)
 		result_lower = &lower1;
@@ -1132,9 +1176,81 @@ range_intersect(PG_FUNCTION_ARGS)
 	else
 		result_upper = &upper2;
 
-	PG_RETURN_RANGE_P(make_range(typcache, result_lower, result_upper, false));
+	return make_range(typcache, result_lower, result_upper, false);
+}
+
+/* range, range -> range, range functions */
+
+/*
+ * range_split_internal - if r2 intersects the middle of r1, leaving non-empty
+ * ranges on both sides, then return true and set output1 and output2 to the
+ * results of r1 - r2 (in order). Otherwise return false and don't set output1
+ * or output2. Neither input range should be empty.
+ */
+bool
+range_split_internal(TypeCacheEntry *typcache, const RangeType *r1, const RangeType *r2,
+					 RangeType **output1, RangeType **output2)
+{
+	RangeBound	lower1,
+				lower2;
+	RangeBound	upper1,
+				upper2;
+	bool		empty1,
+				empty2;
+
+	range_deserialize(typcache, r1, &lower1, &upper1, &empty1);
+	range_deserialize(typcache, r2, &lower2, &upper2, &empty2);
+
+	if (range_cmp_bounds(typcache, &lower1, &lower2) < 0 &&
+		range_cmp_bounds(typcache, &upper1, &upper2) > 0)
+	{
+		/*
+		 * Need to invert inclusive/exclusive for the lower2 and upper2
+		 * points. They can't be infinite though. We're allowed to overwrite
+		 * these RangeBounds since they only exist locally.
+		 */
+		lower2.inclusive = !lower2.inclusive;
+		lower2.lower = false;
+		upper2.inclusive = !upper2.inclusive;
+		upper2.lower = true;
+
+		*output1 = make_range(typcache, &lower1, &lower2, false);
+		*output2 = make_range(typcache, &upper2, &upper1, false);
+		return true;
+	}
+
+	return false;
 }
 
+/* range -> range aggregate functions */
+
+Datum
+range_intersect_agg_transfn(PG_FUNCTION_ARGS)
+{
+	MemoryContext aggContext;
+	Oid			rngtypoid;
+	TypeCacheEntry *typcache;
+	RangeType  *result;
+	RangeType  *current;
+
+	if (!AggCheckCallContext(fcinfo, &aggContext))
+		elog(ERROR, "range_intersect_agg_transfn called in non-aggregate context");
+
+	rngtypoid = get_fn_expr_argtype(fcinfo->flinfo, 1);
+	if (!type_is_range(rngtypoid))
+		ereport(ERROR, (errmsg("range_intersect_agg must be called with a range")));
+
+	typcache = range_get_typcache(fcinfo, rngtypoid);
+
+	/* strictness ensures these are non-null */
+	result = PG_GETARG_RANGE_P(0);
+	current = PG_GETARG_RANGE_P(1);
+
+	result = range_intersect_internal(typcache, result, current);
+	PG_RETURN_RANGE_P(result);
+}
+
+
 /* Btree support */
 
 /* btree comparator */
@@ -1144,12 +1260,6 @@ range_cmp(PG_FUNCTION_ARGS)
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
 	RangeType  *r2 = PG_GETARG_RANGE_P(1);
 	TypeCacheEntry *typcache;
-	RangeBound	lower1,
-				lower2;
-	RangeBound	upper1,
-				upper2;
-	bool		empty1,
-				empty2;
 	int			cmp;
 
 	check_stack_depth();		/* recurses when subtype is a range type */
@@ -1160,6 +1270,28 @@ range_cmp(PG_FUNCTION_ARGS)
 
 	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, const RangeType *r1, const 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);
 
@@ -1177,10 +1309,7 @@ range_cmp(PG_FUNCTION_ARGS)
 			cmp = range_cmp_bounds(typcache, &upper1, &upper2);
 	}
 
-	PG_FREE_IF_COPY(r1, 0);
-	PG_FREE_IF_COPY(r2, 1);
-
-	PG_RETURN_INT32(cmp);
+	return cmp;
 }
 
 /* inequality operators using the range_cmp function */
@@ -1218,13 +1347,10 @@ range_gt(PG_FUNCTION_ARGS)
 
 /* Hash support */
 
-/* hash a range value */
-Datum
-hash_range(PG_FUNCTION_ARGS)
+uint32
+hash_range_internal(TypeCacheEntry *typcache, const RangeType *r)
 {
-	RangeType  *r = PG_GETARG_RANGE_P(0);
 	uint32		result;
-	TypeCacheEntry *typcache;
 	TypeCacheEntry *scache;
 	RangeBound	lower;
 	RangeBound	upper;
@@ -1233,10 +1359,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 +1400,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, const RangeType *r, Datum seed)
+{
+	uint64		result;
 	TypeCacheEntry *scache;
 	RangeBound	lower;
 	RangeBound	upper;
@@ -1300,10 +1429,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 +1467,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));
 }
 
 /*
@@ -1769,6 +1912,21 @@ range_get_flags(const RangeType *range)
 	return *((char *) range + VARSIZE(range) - 1);
 }
 
+/*
+ * range_has_flag: set whether a range has a specific flag.
+ *
+ * This lets expose some of our functions that just check flags
+ * to the rest of the code base (like to multiranges)
+ * without writing full-fledged *_internal versions.
+ */
+bool
+range_has_flag(const RangeType *r1, char flag)
+{
+	char		flags = range_get_flags(r1);
+
+	return flags & flag;
+}
+
 /*
  * range_set_contain_empty: set the RANGE_CONTAIN_EMPTY bit in the value.
  *
@@ -1937,6 +2095,46 @@ range_cmp_bound_values(TypeCacheEntry *typcache, const RangeBound *b1,
 										   b1->val, b2->val));
 }
 
+/*
+ * qsort callback for sorting ranges.
+ *
+ * Two empty ranges compare equal; an empty range sorts to the left of any
+ * non-empty range.  Two non-empty ranges are sorted by lower bound first
+ * and by upper bound next.
+ */
+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.
  */
@@ -2395,7 +2593,7 @@ range_contains_elem_internal(TypeCacheEntry *typcache, const RangeType *r, Datum
  * Increment data_length by the space needed by the datum, including any
  * preceding alignment padding.
  */
-static Size
+Size
 datum_compute_size(Size data_length, Datum val, bool typbyval, char typalign,
 				   int16 typlen, char typstorage)
 {
@@ -2421,7 +2619,7 @@ datum_compute_size(Size data_length, Datum val, bool typbyval, char typalign,
  * Write the given datum beginning at ptr (after advancing to correct
  * alignment, if needed).  Return the pointer incremented by space used.
  */
-static Pointer
+Pointer
 datum_write(Pointer ptr, Datum datum, bool typbyval, char typalign,
 			int16 typlen, char typstorage)
 {
diff --git a/src/backend/utils/adt/rangetypes_typanalyze.c b/src/backend/utils/adt/rangetypes_typanalyze.c
index 57413b07201..94679cce69b 100644
--- a/src/backend/utils/adt/rangetypes_typanalyze.c
+++ b/src/backend/utils/adt/rangetypes_typanalyze.c
@@ -30,6 +30,7 @@
 #include "utils/fmgrprotos.h"
 #include "utils/lsyscache.h"
 #include "utils/rangetypes.h"
+#include "utils/multirangetypes.h"
 
 static int	float8_qsort_cmp(const void *a1, const void *a2);
 static int	range_bound_qsort_cmp(const void *a1, const void *a2, void *arg);
@@ -60,6 +61,33 @@ range_typanalyze(PG_FUNCTION_ARGS)
 	PG_RETURN_BOOL(true);
 }
 
+/*
+ * multirange_typanalyze -- typanalyze function for multirange columns
+ *
+ * We do the same analysis as for ranges, but on the smallest range that
+ * completely includes the multirange.
+ */
+Datum
+multirange_typanalyze(PG_FUNCTION_ARGS)
+{
+	VacAttrStats *stats = (VacAttrStats *) PG_GETARG_POINTER(0);
+	TypeCacheEntry *typcache;
+	Form_pg_attribute attr = stats->attr;
+
+	/* Get information about multirange type; note column might be a domain */
+	typcache = multirange_get_typcache(fcinfo, getBaseType(stats->attrtypid));
+
+	if (attr->attstattarget < 0)
+		attr->attstattarget = default_statistics_target;
+
+	stats->compute_stats = compute_range_stats;
+	stats->extra_data = typcache;
+	/* same as in std_typanalyze */
+	stats->minrows = 300 * attr->attstattarget;
+
+	PG_RETURN_BOOL(true);
+}
+
 /*
  * Comparison function for sorting float8s, used for range lengths.
  */
@@ -98,7 +126,8 @@ compute_range_stats(VacAttrStats *stats, AnalyzeAttrFetchFunc fetchfunc,
 					int samplerows, double totalrows)
 {
 	TypeCacheEntry *typcache = (TypeCacheEntry *) stats->extra_data;
-	bool		has_subdiff = OidIsValid(typcache->rng_subdiff_finfo.fn_oid);
+	TypeCacheEntry *mltrng_typcache = NULL;
+	bool		has_subdiff;
 	int			null_cnt = 0;
 	int			non_null_cnt = 0;
 	int			non_empty_cnt = 0;
@@ -112,6 +141,15 @@ compute_range_stats(VacAttrStats *stats, AnalyzeAttrFetchFunc fetchfunc,
 			   *uppers;
 	double		total_width = 0;
 
+	if (typcache->typtype == TYPTYPE_MULTIRANGE)
+	{
+		mltrng_typcache = typcache;
+		typcache = typcache->rngtype;
+	}
+	else
+		Assert(typcache->typtype == TYPTYPE_RANGE);
+	has_subdiff = OidIsValid(typcache->rng_subdiff_finfo.fn_oid);
+
 	/* Allocate memory to hold range bounds and lengths of the sample ranges. */
 	lowers = (RangeBound *) palloc(sizeof(RangeBound) * samplerows);
 	uppers = (RangeBound *) palloc(sizeof(RangeBound) * samplerows);
@@ -123,6 +161,7 @@ compute_range_stats(VacAttrStats *stats, AnalyzeAttrFetchFunc fetchfunc,
 		Datum		value;
 		bool		isnull,
 					empty;
+		MultirangeType *multirange;
 		RangeType  *range;
 		RangeBound	lower,
 					upper;
@@ -145,7 +184,23 @@ compute_range_stats(VacAttrStats *stats, AnalyzeAttrFetchFunc fetchfunc,
 		total_width += VARSIZE_ANY(DatumGetPointer(value));
 
 		/* Get range and deserialize it for further analysis. */
-		range = DatumGetRangeTypeP(value);
+		if (mltrng_typcache != NULL)
+		{
+			/* Treat multiranges like a big range without gaps. */
+			multirange = DatumGetMultirangeTypeP(value);
+			if (MultirangeIsEmpty(multirange))
+				range = make_empty_range(typcache);
+			else
+			{
+				int	range_count;
+				RangeType **ranges;
+				multirange_deserialize(typcache, multirange, &range_count, &ranges);
+				range = range_union_internal(typcache, ranges[0],
+						ranges[range_count - 1], false);
+			}
+		}
+		else
+			range = DatumGetRangeTypeP(value);
 		range_deserialize(typcache, range, &lower, &upper, &empty);
 
 		if (!empty)
@@ -262,6 +317,13 @@ compute_range_stats(VacAttrStats *stats, AnalyzeAttrFetchFunc fetchfunc,
 			stats->stakind[slot_idx] = STATISTIC_KIND_BOUNDS_HISTOGRAM;
 			stats->stavalues[slot_idx] = bound_hist_values;
 			stats->numvalues[slot_idx] = num_hist;
+
+			/* Store ranges even if we're analyzing a multirange column */
+			stats->statypid[slot_idx] = typcache->type_id;
+			stats->statyplen[slot_idx] = typcache->typlen;
+			stats->statypbyval[slot_idx] = typcache->typbyval;
+			stats->statypalign[slot_idx] = 'd';
+
 			slot_idx++;
 		}
 
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index ae232991623..2138af7079a 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -2583,6 +2583,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
  *
@@ -3225,7 +3235,7 @@ get_namespace_name_or_temp(Oid nspid)
 		return get_namespace_name(nspid);
 }
 
-/*				---------- PG_RANGE CACHE ----------				 */
+/*				---------- PG_RANGE CACHES ----------				 */
 
 /*
  * get_range_subtype
@@ -3278,6 +3288,56 @@ get_range_collation(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->rngmultitypid;
+		ReleaseSysCache(tp);
+		return result;
+	}
+	else
+		return InvalidOid;
+}
+
+/*
+ * get_multirange_range
+ *		Returns the range type of a given multirange
+ *
+ * Returns InvalidOid if the type is not a multirange.
+ */
+Oid
+get_multirange_range(Oid multirangeOid)
+{
+	HeapTuple	tp;
+
+	tp = SearchSysCache1(RANGEMULTIRANGE, 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 809b27a038f..89f08a4f43c 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -650,6 +650,18 @@ static const struct cachedesc cacheinfo[] = {
 		},
 		64
 	},
+	{RangeRelationId,			/* RANGEMULTIRANGE */
+		RangeMultirangeTypidIndexId,
+		1,
+		{
+			Anum_pg_range_rngmultitypid,
+			0,
+			0,
+			0
+		},
+		4
+	},
+
 	{RangeRelationId,			/* RANGETYPE */
 		RangeTypidIndexId,
 		1,
diff --git a/src/backend/utils/cache/typcache.c b/src/backend/utils/cache/typcache.c
index dca1d48e895..7ea5744060c 100644
--- a/src/backend/utils/cache/typcache.c
+++ b/src/backend/utils/cache/typcache.c
@@ -287,6 +287,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);
@@ -305,6 +306,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 TypeCacheTypCallback(Datum arg, int cacheid, uint32 hashvalue);
 static void TypeCacheOpcCallback(Datum arg, int cacheid, uint32 hashvalue);
@@ -556,8 +560,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))
@@ -594,7 +598,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))
@@ -619,7 +623,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))
@@ -644,7 +648,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))
@@ -694,6 +698,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;
@@ -736,6 +747,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;
@@ -815,6 +833,16 @@ lookup_type_cache(Oid type_id, int flags)
 			(void) lookup_type_cache(typentry->rngelemtype->type_id, 0);
 	}
 
+	/*
+	 * 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
 	 */
@@ -926,6 +954,22 @@ load_rangetype_info(TypeCacheEntry *typentry)
 	typentry->rngelemtype = lookup_type_cache(subtypeOid, 0);
 }
 
+/*
+ * load_multirangetype_info --- helper routine to set up multirange type
+ * information
+ */
+static void
+load_multirangetype_info(TypeCacheEntry *typentry)
+{
+	Oid			rangetypeOid;
+
+	rangetypeOid = get_multirange_range(typentry->type_id);
+	if (!OidIsValid(rangetypeOid))
+		elog(ERROR, "cache lookup failed for multirange type %u",
+			 typentry->type_id);
+
+	typentry->rngtype = lookup_type_cache(rangetypeOid, TYPECACHE_RANGE_INFO);
+}
 
 /*
  * load_domaintype_info --- helper routine to set up domain constraint info
@@ -1558,11 +1602,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
@@ -1605,6 +1649,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 9696c88f241..843a2d81071 100644
--- a/src/backend/utils/fmgr/funcapi.c
+++ b/src/backend/utils/fmgr/funcapi.c
@@ -35,6 +35,7 @@ typedef struct polymorphic_actuals
 	Oid			anyelement_type;	/* anyelement mapping, if known */
 	Oid			anyarray_type;	/* anyarray mapping, if known */
 	Oid			anyrange_type;	/* anyrange mapping, if known */
+	Oid			anymultirange_type;	/* anymultirange mapping, if known */
 } polymorphic_actuals;
 
 static void shutdown_MultiFuncCall(Datum arg);
@@ -46,6 +47,7 @@ static TypeFuncClass internal_get_result_type(Oid funcid,
 static void resolve_anyelement_from_others(polymorphic_actuals *actuals);
 static void resolve_anyarray_from_others(polymorphic_actuals *actuals);
 static void resolve_anyrange_from_others(polymorphic_actuals *actuals);
+static void resolve_anymultirange_from_others(polymorphic_actuals *actuals);
 static bool resolve_polymorphic_tupdesc(TupleDesc tupdesc,
 										oidvector *declared_args,
 										Node *call_expr);
@@ -503,6 +505,34 @@ resolve_anyelement_from_others(polymorphic_actuals *actuals)
 							format_type_be(range_base_type))));
 		actuals->anyelement_type = range_typelem;
 	}
+	else if (OidIsValid(actuals->anymultirange_type))
+	{
+		/* Use the element type based on the multirange type */
+		Oid			multirange_base_type;
+		Oid			multirange_typelem;
+		Oid			range_base_type;
+		Oid			range_typelem;
+
+		multirange_base_type = getBaseType(actuals->anymultirange_type);
+		multirange_typelem = get_multirange_range(multirange_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(multirange_base_type))));
+
+		range_base_type = getBaseType(multirange_typelem);
+		range_typelem = get_range_subtype(range_base_type);
+
+		if (!OidIsValid(range_typelem))
+			ereport(ERROR,
+					(errcode(ERRCODE_DATATYPE_MISMATCH),
+					 errmsg("argument declared %s does not contain a range type but type %s",
+							"anymultirange",
+							format_type_be(range_base_type))));
+		actuals->anyelement_type = range_typelem;
+	}
 	else
 		elog(ERROR, "could not determine polymorphic type");
 }
@@ -540,10 +570,54 @@ static void
 resolve_anyrange_from_others(polymorphic_actuals *actuals)
 {
 	/*
-	 * We can't deduce a range type from other polymorphic inputs, because
-	 * there may be multiple range types with the same subtype.
+	 * We can't deduce a range type from other polymorphic array or base types,
+	 * because there may be multiple range types with the same subtype, but we
+	 * can deduce it from a polymorphic multirange type.
+	 */
+	if (OidIsValid(actuals->anymultirange_type))
+	{
+		/* Use the element type based on the multirange type */
+		Oid			multirange_base_type = getBaseType(actuals->anymultirange_type);
+		Oid			multirange_typelem =
+			get_multirange_range(multirange_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(multirange_base_type))));
+		actuals->anyrange_type = multirange_typelem;
+	}
+	else
+		elog(ERROR, "could not determine polymorphic type");
+}
+
+/*
+ * Resolve actual type of ANYMULTIRANGE from other polymorphic inputs
+ */
+static void
+resolve_anymultirange_from_others(polymorphic_actuals *actuals)
+{
+	/*
+	 * We can't deduce a multirange type from polymorphic array or base types,
+	 * because there may be multiple range types with the same subtype, but we
+	 * can deduce it from a polymorphic range type.
 	 */
-	elog(ERROR, "could not determine polymorphic type");
+	if (OidIsValid(actuals->anyrange_type))
+	{
+		Oid			range_base_type = getBaseType(actuals->anyrange_type);
+		Oid			multirange_typeid = get_range_multirange(range_base_type);
+
+		if (!OidIsValid(multirange_typeid))
+			ereport(ERROR,
+					(errcode(ERRCODE_UNDEFINED_OBJECT),
+					 errmsg("could not find multirange type for data type %s",
+							format_type_be(actuals->anyrange_type))));
+		actuals->anymultirange_type = multirange_typeid;
+	}
+	else
+		elog(ERROR, "could not determine polymorphic type");
 }
 
 /*
@@ -566,9 +640,11 @@ 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_anycompatible_result = false;
 	bool		have_anycompatible_array_result = false;
 	bool		have_anycompatible_range_result = false;
+	bool		have_anycompatible_multirange_result = false;
 	polymorphic_actuals poly_actuals;
 	polymorphic_actuals anyc_actuals;
 	Oid			anycollation = InvalidOid;
@@ -594,6 +670,10 @@ resolve_polymorphic_tupdesc(TupleDesc tupdesc, oidvector *declared_args,
 				have_polymorphic_result = true;
 				have_anyrange_result = true;
 				break;
+			case ANYMULTIRANGEOID:
+				have_polymorphic_result = true;
+				have_anymultirange_result = true;
+				break;
 			case ANYCOMPATIBLEOID:
 			case ANYCOMPATIBLENONARRAYOID:
 				have_polymorphic_result = true;
@@ -607,6 +687,10 @@ resolve_polymorphic_tupdesc(TupleDesc tupdesc, oidvector *declared_args,
 				have_polymorphic_result = true;
 				have_anycompatible_range_result = true;
 				break;
+			case ANYCOMPATIBLEMULTIRANGEOID:
+				have_polymorphic_result = true;
+				have_anycompatible_multirange_result = true;
+				break;
 			default:
 				break;
 		}
@@ -660,6 +744,15 @@ resolve_polymorphic_tupdesc(TupleDesc tupdesc, oidvector *declared_args,
 						return false;
 				}
 				break;
+			case ANYMULTIRANGEOID:
+				if (!OidIsValid(poly_actuals.anymultirange_type))
+				{
+					poly_actuals.anymultirange_type =
+						get_call_expr_argtype(call_expr, i);
+					if (!OidIsValid(poly_actuals.anymultirange_type))
+						return false;
+				}
+				break;
 			case ANYCOMPATIBLEOID:
 			case ANYCOMPATIBLENONARRAYOID:
 				if (!OidIsValid(anyc_actuals.anyelement_type))
@@ -688,6 +781,15 @@ resolve_polymorphic_tupdesc(TupleDesc tupdesc, oidvector *declared_args,
 						return false;
 				}
 				break;
+			case ANYCOMPATIBLEMULTIRANGEOID:
+				if (!OidIsValid(anyc_actuals.anymultirange_type))
+				{
+					anyc_actuals.anymultirange_type =
+						get_call_expr_argtype(call_expr, i);
+					if (!OidIsValid(anyc_actuals.anymultirange_type))
+						return false;
+				}
+				break;
 			default:
 				break;
 		}
@@ -703,6 +805,9 @@ resolve_polymorphic_tupdesc(TupleDesc tupdesc, oidvector *declared_args,
 	if (have_anyrange_result && !OidIsValid(poly_actuals.anyrange_type))
 		resolve_anyrange_from_others(&poly_actuals);
 
+	if (have_anymultirange_result && !OidIsValid(poly_actuals.anymultirange_type))
+		resolve_anymultirange_from_others(&poly_actuals);
+
 	if (have_anycompatible_result && !OidIsValid(anyc_actuals.anyelement_type))
 		resolve_anyelement_from_others(&anyc_actuals);
 
@@ -712,6 +817,9 @@ resolve_polymorphic_tupdesc(TupleDesc tupdesc, oidvector *declared_args,
 	if (have_anycompatible_range_result && !OidIsValid(anyc_actuals.anyrange_type))
 		resolve_anyrange_from_others(&anyc_actuals);
 
+	if (have_anycompatible_multirange_result && !OidIsValid(anyc_actuals.anymultirange_type))
+		resolve_anymultirange_from_others(&anyc_actuals);
+
 	/*
 	 * Identify the collation to use for polymorphic OUT parameters. (It'll
 	 * necessarily be the same for both anyelement and anyarray, likewise for
@@ -780,6 +888,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),
+								   poly_actuals.anymultirange_type,
+								   -1,
+								   0);
+				/* no collation should be attached to a multirange type */
+				break;
 			case ANYCOMPATIBLEOID:
 			case ANYCOMPATIBLENONARRAYOID:
 				TupleDescInitEntry(tupdesc, i + 1,
@@ -805,6 +921,14 @@ resolve_polymorphic_tupdesc(TupleDesc tupdesc, oidvector *declared_args,
 								   0);
 				/* no collation should be attached to a range type */
 				break;
+			case ANYCOMPATIBLEMULTIRANGEOID:
+				TupleDescInitEntry(tupdesc, i + 1,
+								   NameStr(att->attname),
+								   anyc_actuals.anymultirange_type,
+								   -1,
+								   0);
+				/* no collation should be attached to a multirange type */
+				break;
 			default:
 				break;
 		}
@@ -834,9 +958,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;
 	bool		have_anycompatible_result = false;
 	bool		have_anycompatible_array_result = false;
 	bool		have_anycompatible_range_result = false;
+	bool		have_anycompatible_multirange_result = false;
 	polymorphic_actuals poly_actuals;
 	polymorphic_actuals anyc_actuals;
 	int			inargno;
@@ -912,6 +1038,24 @@ resolve_polymorphic_argtypes(int numargs, Oid *argtypes, char *argmodes,
 					argtypes[i] = poly_actuals.anyrange_type;
 				}
 				break;
+			case ANYMULTIRANGEOID:
+				if (argmode == PROARGMODE_OUT || argmode == PROARGMODE_TABLE)
+				{
+					have_polymorphic_result = true;
+					have_anymultirange_result = true;
+				}
+				else
+				{
+					if (!OidIsValid(poly_actuals.anymultirange_type))
+					{
+						poly_actuals.anymultirange_type =
+							get_call_expr_argtype(call_expr, inargno);
+						if (!OidIsValid(poly_actuals.anymultirange_type))
+							return false;
+					}
+					argtypes[i] = poly_actuals.anymultirange_type;
+				}
+				break;
 			case ANYCOMPATIBLEOID:
 			case ANYCOMPATIBLENONARRAYOID:
 				if (argmode == PROARGMODE_OUT || argmode == PROARGMODE_TABLE)
@@ -967,6 +1111,24 @@ resolve_polymorphic_argtypes(int numargs, Oid *argtypes, char *argmodes,
 					argtypes[i] = anyc_actuals.anyrange_type;
 				}
 				break;
+			case ANYCOMPATIBLEMULTIRANGEOID:
+				if (argmode == PROARGMODE_OUT || argmode == PROARGMODE_TABLE)
+				{
+					have_polymorphic_result = true;
+					have_anycompatible_multirange_result = true;
+				}
+				else
+				{
+					if (!OidIsValid(anyc_actuals.anymultirange_type))
+					{
+						anyc_actuals.anymultirange_type =
+							get_call_expr_argtype(call_expr, inargno);
+						if (!OidIsValid(anyc_actuals.anymultirange_type))
+							return false;
+					}
+					argtypes[i] = anyc_actuals.anymultirange_type;
+				}
+				break;
 			default:
 				break;
 		}
@@ -988,6 +1150,9 @@ resolve_polymorphic_argtypes(int numargs, Oid *argtypes, char *argmodes,
 	if (have_anyrange_result && !OidIsValid(poly_actuals.anyrange_type))
 		resolve_anyrange_from_others(&poly_actuals);
 
+	if (have_anymultirange_result && !OidIsValid(poly_actuals.anymultirange_type))
+		resolve_anymultirange_from_others(&poly_actuals);
+
 	if (have_anycompatible_result && !OidIsValid(anyc_actuals.anyelement_type))
 		resolve_anyelement_from_others(&anyc_actuals);
 
@@ -997,6 +1162,9 @@ resolve_polymorphic_argtypes(int numargs, Oid *argtypes, char *argmodes,
 	if (have_anycompatible_range_result && !OidIsValid(anyc_actuals.anyrange_type))
 		resolve_anyrange_from_others(&anyc_actuals);
 
+	if (have_anycompatible_multirange_result && !OidIsValid(anyc_actuals.anymultirange_type))
+		resolve_anymultirange_from_others(&anyc_actuals);
+
 	/* And finally replace the output column types as needed */
 	for (i = 0; i < numargs; i++)
 	{
@@ -1013,6 +1181,9 @@ resolve_polymorphic_argtypes(int numargs, Oid *argtypes, char *argmodes,
 			case ANYRANGEOID:
 				argtypes[i] = poly_actuals.anyrange_type;
 				break;
+			case ANYMULTIRANGEOID:
+				argtypes[i] = poly_actuals.anymultirange_type;
+				break;
 			case ANYCOMPATIBLEOID:
 			case ANYCOMPATIBLENONARRAYOID:
 				argtypes[i] = anyc_actuals.anyelement_type;
@@ -1023,6 +1194,9 @@ resolve_polymorphic_argtypes(int numargs, Oid *argtypes, char *argmodes,
 			case ANYCOMPATIBLERANGEOID:
 				argtypes[i] = anyc_actuals.anyrange_type;
 				break;
+			case ANYCOMPATIBLEMULTIRANGEOID:
+				argtypes[i] = anyc_actuals.anymultirange_type;
+				break;
 			default:
 				break;
 		}
@@ -1052,6 +1226,7 @@ get_type_func_class(Oid typid, Oid *base_typeid)
 		case TYPTYPE_BASE:
 		case TYPTYPE_ENUM:
 		case TYPTYPE_RANGE:
+		case TYPTYPE_MULTIRANGE:
 			return TYPEFUNC_SCALAR;
 		case TYPTYPE_DOMAIN:
 			*base_typeid = typid = getBaseType(typid);
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index dc1d41dd8d2..02d377e5acd 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -272,7 +272,8 @@ static void dumpSearchPath(Archive *AH);
 static void binary_upgrade_set_type_oids_by_type_oid(Archive *fout,
 													 PQExpBuffer upgrade_buffer,
 													 Oid pg_type_oid,
-													 bool force_array_type);
+													 bool force_array_type,
+													 bool include_multirange_type);
 static void binary_upgrade_set_type_oids_by_rel_oid(Archive *fout,
 													PQExpBuffer upgrade_buffer, Oid pg_rel_oid);
 static void binary_upgrade_set_pg_class_oids(Archive *fout,
@@ -1653,7 +1654,7 @@ selectDumpableType(TypeInfo *tyinfo, Archive *fout)
 	}
 
 	/* skip auto-generated array types */
-	if (tyinfo->isArray)
+	if (tyinfo->isArray || tyinfo->isMultirange)
 	{
 		tyinfo->dobj.objType = DO_DUMMY_TYPE;
 
@@ -4461,16 +4462,49 @@ append_depends_on_extension(Archive *fout,
 	}
 }
 
+static Oid
+get_next_possible_free_pg_type_oid(Archive *fout, PQExpBuffer upgrade_query)
+{
+	/*
+	 * If the old version didn't assign an array type, but the new version
+	 * does, we must select an unused type OID to assign.  This currently only
+	 * happens for domains, when upgrading pre-v11 to v11 and up.
+	 *
+	 * Note: local state here is kind of ugly, but we must have some, since we
+	 * mustn't choose the same unused OID more than once.
+	 */
+	static Oid	next_possible_free_oid = FirstNormalObjectId;
+	PGresult   *res;
+	bool		is_dup;
+
+	do
+	{
+		++next_possible_free_oid;
+		printfPQExpBuffer(upgrade_query,
+						  "SELECT EXISTS(SELECT 1 "
+						  "FROM pg_catalog.pg_type "
+						  "WHERE oid = '%u'::pg_catalog.oid);",
+						  next_possible_free_oid);
+		res = ExecuteSqlQueryForSingleRow(fout, upgrade_query->data);
+		is_dup = (PQgetvalue(res, 0, 0)[0] == 't');
+		PQclear(res);
+	} while (is_dup);
+
+	return next_possible_free_oid;
+}
 
 static void
 binary_upgrade_set_type_oids_by_type_oid(Archive *fout,
 										 PQExpBuffer upgrade_buffer,
 										 Oid pg_type_oid,
-										 bool force_array_type)
+										 bool force_array_type,
+										 bool include_multirange_type)
 {
 	PQExpBuffer upgrade_query = createPQExpBuffer();
 	PGresult   *res;
 	Oid			pg_type_array_oid;
+	Oid			pg_type_multirange_oid;
+	Oid			pg_type_multirange_array_oid;
 
 	appendPQExpBufferStr(upgrade_buffer, "\n-- For binary upgrade, must preserve pg_type oid\n");
 	appendPQExpBuffer(upgrade_buffer,
@@ -4491,33 +4525,7 @@ binary_upgrade_set_type_oids_by_type_oid(Archive *fout,
 	PQclear(res);
 
 	if (!OidIsValid(pg_type_array_oid) && force_array_type)
-	{
-		/*
-		 * If the old version didn't assign an array type, but the new version
-		 * does, we must select an unused type OID to assign.  This currently
-		 * only happens for domains, when upgrading pre-v11 to v11 and up.
-		 *
-		 * Note: local state here is kind of ugly, but we must have some,
-		 * since we mustn't choose the same unused OID more than once.
-		 */
-		static Oid	next_possible_free_oid = FirstNormalObjectId;
-		bool		is_dup;
-
-		do
-		{
-			++next_possible_free_oid;
-			printfPQExpBuffer(upgrade_query,
-							  "SELECT EXISTS(SELECT 1 "
-							  "FROM pg_catalog.pg_type "
-							  "WHERE oid = '%u'::pg_catalog.oid);",
-							  next_possible_free_oid);
-			res = ExecuteSqlQueryForSingleRow(fout, upgrade_query->data);
-			is_dup = (PQgetvalue(res, 0, 0)[0] == 't');
-			PQclear(res);
-		} while (is_dup);
-
-		pg_type_array_oid = next_possible_free_oid;
-	}
+		pg_type_array_oid = get_next_possible_free_pg_type_oid(fout, upgrade_query);
 
 	if (OidIsValid(pg_type_array_oid))
 	{
@@ -4528,6 +4536,46 @@ binary_upgrade_set_type_oids_by_type_oid(Archive *fout,
 						  pg_type_array_oid);
 	}
 
+	/*
+	 * Pre-set the multirange type oid and its own array type oid.
+	 */
+	if (include_multirange_type)
+	{
+		if (fout->remoteVersion >= 130000)
+		{
+			appendPQExpBuffer(upgrade_query,
+							  "SELECT t.oid, t.typarray "
+							  "FROM pg_catalog.pg_type t "
+							  "JOIN pg_catalog.pg_range r "
+							  "ON t.oid = r.rngmultitypid "
+							  "WHERE r.rngtypid = '%u'::pg_catalog.oid;",
+							  pg_type_oid);
+
+			res = ExecuteSqlQueryForSingleRow(fout, upgrade_query->data);
+
+			pg_type_multirange_oid = atooid(PQgetvalue(res, 0, PQfnumber(res, "oid")));
+			pg_type_multirange_array_oid = atooid(PQgetvalue(res, 0, PQfnumber(res, "typarray")));
+
+			PQclear(res);
+		}
+		else
+		{
+			pg_type_multirange_oid = get_next_possible_free_pg_type_oid(fout, upgrade_query);
+			pg_type_multirange_array_oid = get_next_possible_free_pg_type_oid(fout, upgrade_query);
+		}
+
+		appendPQExpBufferStr(upgrade_buffer,
+							 "\n-- For binary upgrade, must preserve multirange pg_type oid\n");
+		appendPQExpBuffer(upgrade_buffer,
+						  "SELECT pg_catalog.binary_upgrade_set_next_multirange_pg_type_oid('%u'::pg_catalog.oid);\n\n",
+						  pg_type_multirange_oid);
+		appendPQExpBufferStr(upgrade_buffer,
+							 "\n-- For binary upgrade, must preserve multirange pg_type array oid\n");
+		appendPQExpBuffer(upgrade_buffer,
+						  "SELECT pg_catalog.binary_upgrade_set_next_multirange_array_pg_type_oid('%u'::pg_catalog.oid);\n\n",
+						  pg_type_multirange_array_oid);
+	}
+
 	destroyPQExpBuffer(upgrade_query);
 }
 
@@ -4552,7 +4600,7 @@ binary_upgrade_set_type_oids_by_rel_oid(Archive *fout,
 
 	if (OidIsValid(pg_type_oid))
 		binary_upgrade_set_type_oids_by_type_oid(fout, upgrade_buffer,
-												 pg_type_oid, false);
+												 pg_type_oid, false, false);
 
 	PQclear(upgrade_res);
 	destroyPQExpBuffer(upgrade_query);
@@ -5097,6 +5145,11 @@ getTypes(Archive *fout, int *numTypes)
 		else
 			tyinfo[i].isArray = false;
 
+		if (tyinfo[i].typtype == 'm')
+			tyinfo[i].isMultirange = true;
+		else
+			tyinfo[i].isMultirange = false;
+
 		/* Decide whether we want to dump it */
 		selectDumpableType(&tyinfo[i], fout);
 
@@ -8326,9 +8379,12 @@ getProcLangs(Archive *fout, int *numProcLangs)
 
 /*
  * getCasts
- *	  get basic information about every cast in the system
+ *	  get basic information about most casts in the system
  *
  * numCasts is set to the number of casts read in
+ *
+ * Skip casts from a range to its multirange, since we'll create those
+ * automatically.
  */
 CastInfo *
 getCasts(Archive *fout, int *numCasts)
@@ -8346,7 +8402,20 @@ getCasts(Archive *fout, int *numCasts)
 	int			i_castcontext;
 	int			i_castmethod;
 
-	if (fout->remoteVersion >= 80400)
+	if (fout->remoteVersion >= 130000)
+	{
+		appendPQExpBufferStr(query, "SELECT tableoid, oid, "
+							 "castsource, casttarget, castfunc, castcontext, "
+							 "castmethod "
+							 "FROM pg_cast c "
+							 "WHERE NOT EXISTS ( "
+							 "SELECT 1 FROM pg_range r "
+							 "WHERE c.castsource = r.rngtypid "
+							 "AND c.casttarget = r.rngmultitypid "
+							 ") "
+							 "ORDER BY 3,4");
+	}
+	else if (fout->remoteVersion >= 80400)
 	{
 		appendPQExpBufferStr(query, "SELECT tableoid, oid, "
 							 "castsource, casttarget, castfunc, castcontext, "
@@ -10496,7 +10565,7 @@ dumpEnumType(Archive *fout, TypeInfo *tyinfo)
 	if (dopt->binary_upgrade)
 		binary_upgrade_set_type_oids_by_type_oid(fout, q,
 												 tyinfo->dobj.catId.oid,
-												 false);
+												 false, false);
 
 	appendPQExpBuffer(q, "CREATE TYPE %s AS ENUM (",
 					  qualtypname);
@@ -10622,7 +10691,7 @@ dumpRangeType(Archive *fout, TypeInfo *tyinfo)
 	if (dopt->binary_upgrade)
 		binary_upgrade_set_type_oids_by_type_oid(fout, q,
 												 tyinfo->dobj.catId.oid,
-												 false);
+												 false, true);
 
 	appendPQExpBuffer(q, "CREATE TYPE %s AS RANGE (",
 					  qualtypname);
@@ -10728,7 +10797,7 @@ dumpUndefinedType(Archive *fout, TypeInfo *tyinfo)
 	if (dopt->binary_upgrade)
 		binary_upgrade_set_type_oids_by_type_oid(fout, q,
 												 tyinfo->dobj.catId.oid,
-												 false);
+												 false, false);
 
 	appendPQExpBuffer(q, "CREATE TYPE %s;\n",
 					  qualtypname);
@@ -10933,7 +11002,7 @@ dumpBaseType(Archive *fout, TypeInfo *tyinfo)
 	if (dopt->binary_upgrade)
 		binary_upgrade_set_type_oids_by_type_oid(fout, q,
 												 tyinfo->dobj.catId.oid,
-												 false);
+												 false, false);
 
 	appendPQExpBuffer(q,
 					  "CREATE TYPE %s (\n"
@@ -11120,7 +11189,8 @@ dumpDomain(Archive *fout, TypeInfo *tyinfo)
 	if (dopt->binary_upgrade)
 		binary_upgrade_set_type_oids_by_type_oid(fout, q,
 												 tyinfo->dobj.catId.oid,
-												 true); /* force array type */
+												 true,		/* force array type */
+												 false);	/* force multirange type */
 
 	qtypname = pg_strdup(fmtId(tyinfo->dobj.name));
 	qualtypname = pg_strdup(fmtQualifiedDumpable(tyinfo));
@@ -11308,7 +11378,7 @@ dumpCompositeType(Archive *fout, TypeInfo *tyinfo)
 	{
 		binary_upgrade_set_type_oids_by_type_oid(fout, q,
 												 tyinfo->dobj.catId.oid,
-												 false);
+												 false, false);
 		binary_upgrade_set_pg_class_oids(fout, q, tyinfo->typrelid, false);
 	}
 
@@ -11582,7 +11652,7 @@ dumpShellType(Archive *fout, ShellTypeInfo *stinfo)
 	if (dopt->binary_upgrade)
 		binary_upgrade_set_type_oids_by_type_oid(fout, q,
 												 stinfo->baseType->dobj.catId.oid,
-												 false);
+												 false, false);
 
 	appendPQExpBuffer(q, "CREATE TYPE %s;\n",
 					  fmtQualifiedDumpable(stinfo));
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 317bb839702..d7f77f1d3e0 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -176,6 +176,7 @@ typedef struct _typeInfo
 	char		typrelkind;		/* 'r', 'v', 'c', etc */
 	char		typtype;		/* 'b', 'c', etc */
 	bool		isArray;		/* true if auto-generated array type */
+	bool		isMultirange;	/* true if auto-generated multirange type */
 	bool		isDefined;		/* true if typisdefined */
 	/* If needed, we'll create a "shell type" entry for it; link that here: */
 	struct _shellTypeInfo *shellType;	/* shell-type entry, or NULL */
diff --git a/src/include/access/tupmacs.h b/src/include/access/tupmacs.h
index 70157cf90ab..c262265b951 100644
--- a/src/include/access/tupmacs.h
+++ b/src/include/access/tupmacs.h
@@ -139,8 +139,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/binary_upgrade.h b/src/include/catalog/binary_upgrade.h
index 02fecb90f79..1ff62e9f8e6 100644
--- a/src/include/catalog/binary_upgrade.h
+++ b/src/include/catalog/binary_upgrade.h
@@ -16,6 +16,8 @@
 
 extern PGDLLIMPORT Oid binary_upgrade_next_pg_type_oid;
 extern PGDLLIMPORT Oid binary_upgrade_next_array_pg_type_oid;
+extern PGDLLIMPORT Oid binary_upgrade_next_mrng_pg_type_oid;
+extern PGDLLIMPORT Oid binary_upgrade_next_mrng_array_pg_type_oid;
 
 extern PGDLLIMPORT Oid binary_upgrade_next_heap_pg_class_oid;
 extern PGDLLIMPORT Oid binary_upgrade_next_index_pg_class_oid;
diff --git a/src/include/catalog/pg_aggregate.dat b/src/include/catalog/pg_aggregate.dat
index ffabe275c00..03bab74253d 100644
--- a/src/include/catalog/pg_aggregate.dat
+++ b/src/include/catalog/pg_aggregate.dat
@@ -538,6 +538,17 @@
   aggtransfn => 'bytea_string_agg_transfn',
   aggfinalfn => 'bytea_string_agg_finalfn', aggtranstype => 'internal' },
 
+# range
+{ aggfnoid => 'range_intersect_agg(anyrange)',
+  aggtransfn => 'range_intersect_agg_transfn',
+  aggcombinefn => 'range_intersect_agg_transfn', aggtranstype => 'anyrange' },
+{ aggfnoid => 'range_intersect_agg(anymultirange)',
+  aggtransfn => 'multirange_intersect_agg_transfn',
+  aggcombinefn => 'multirange_intersect_agg_transfn', aggtranstype => 'anymultirange' },
+{ aggfnoid => 'range_agg(anyrange)', aggtransfn => 'range_agg_transfn',
+  aggfinalfn => 'range_agg_finalfn', aggfinalextra => 't',
+  aggtranstype => 'internal' },
+
 # json
 { aggfnoid => 'json_agg', aggtransfn => 'json_agg_transfn',
   aggfinalfn => 'json_agg_finalfn', aggtranstype => 'internal' },
diff --git a/src/include/catalog/pg_amop.dat b/src/include/catalog/pg_amop.dat
index 2c899f19d92..78d7d2c5232 100644
--- a/src/include/catalog/pg_amop.dat
+++ b/src/include/catalog/pg_amop.dat
@@ -1374,6 +1374,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 db3e8c2d01a..9d423d535cd 100644
--- a/src/include/catalog/pg_amproc.dat
+++ b/src/include/catalog/pg_amproc.dat
@@ -285,6 +285,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' },
 { amprocfamily => 'btree/xid8_ops', amproclefttype => 'xid8',
@@ -457,6 +459,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',
@@ -1280,12 +1287,13 @@
   amprocrighttype => 'anyrange', amprocnum => '4',
   amproc => 'brin_inclusion_union' },
 { amprocfamily => 'brin/range_inclusion_ops', amproclefttype => 'anyrange',
-  amprocrighttype => 'anyrange', amprocnum => '11', amproc => 'range_merge' },
+  amprocrighttype => 'anyrange', amprocnum => '11',
+  amproc => 'range_merge(anyrange,anyrange)' },
 { amprocfamily => 'brin/range_inclusion_ops', amproclefttype => 'anyrange',
   amprocrighttype => 'anyrange', amprocnum => '13',
   amproc => 'range_contains' },
 { amprocfamily => 'brin/range_inclusion_ops', amproclefttype => 'anyrange',
-  amprocrighttype => 'anyrange', amprocnum => '14', amproc => 'isempty' },
+  amprocrighttype => 'anyrange', amprocnum => '14', amproc => 'isempty(anyrange)' },
 
 # minmax pg_lsn
 { amprocfamily => 'brin/pg_lsn_minmax_ops', amproclefttype => 'pg_lsn',
diff --git a/src/include/catalog/pg_cast.dat b/src/include/catalog/pg_cast.dat
index 5a58f50fbb2..d6ca624addc 100644
--- a/src/include/catalog/pg_cast.dat
+++ b/src/include/catalog/pg_cast.dat
@@ -530,4 +530,17 @@
 { castsource => 'jsonb', casttarget => 'float8', castfunc => 'float8(jsonb)',
   castcontext => 'e', castmethod => 'f' },
 
+# range to multirange
+{ castsource => 'int4range', casttarget => 'int4multirange', castfunc => 'int4multirange(int4range)',
+  castcontext => 'e', castmethod => 'f' },
+{ castsource => 'int8range', casttarget => 'int8multirange', castfunc => 'int8multirange(int8range)',
+  castcontext => 'e', castmethod => 'f' },
+{ castsource => 'numrange', casttarget => 'nummultirange', castfunc => 'nummultirange(numrange)',
+  castcontext => 'e', castmethod => 'f' },
+{ castsource => 'daterange', casttarget => 'datemultirange', castfunc => 'datemultirange(daterange)',
+  castcontext => 'e', castmethod => 'f' },
+{ castsource => 'tsrange', casttarget => 'tsmultirange', castfunc => 'tsmultirange(tsrange)',
+  castcontext => 'e', castmethod => 'f' },
+{ castsource => 'tstzrange', casttarget => 'tstzmultirange', castfunc => 'tstzmultirange(tstzrange)',
+  castcontext => 'e', castmethod => 'f' },
 ]
diff --git a/src/include/catalog/pg_opclass.dat b/src/include/catalog/pg_opclass.dat
index be5712692fe..4c5e475ff7b 100644
--- a/src/include/catalog/pg_opclass.dat
+++ b/src/include/catalog/pg_opclass.dat
@@ -232,6 +232,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 7ae19235ee2..f44a826e2e9 100644
--- a/src/include/catalog/pg_operator.dat
+++ b/src/include/catalog/pg_operator.dat
@@ -3277,5 +3277,174 @@
   oprname => '@@', oprleft => 'jsonb', oprright => 'jsonpath',
   oprresult => 'bool', oprcode => 'jsonb_path_match_opr(jsonb,jsonpath)',
   oprrest => 'matchingsel', oprjoin => 'matchingjoinsel' },
+{ 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 => 'multirangesel', 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 => 'multirangesel', 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 => 'multirangesel', 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 => 'multirangesel', oprjoin => 'scalargtjoinsel' },
+{ oid => '8073', oid_symbol => 'OID_RANGE_OVERLAPS_MULTIRANGE_OP',
+  descr => 'overlaps',
+  oprname => '&&', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '&&(anymultirange,anyrange)',
+  oprcode => 'range_overlaps_multirange', oprrest => 'multirangesel',
+  oprjoin => 'areajoinsel' },
+{ oid => '8074', oid_symbol => 'OID_MULTIRANGE_OVERLAPS_RANGE_OP', descr => 'contains',
+  oprname => '&&', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcom => '&&(anyrange,anymultirange)',
+  oprcode => 'multirange_overlaps_range', oprrest => 'multirangesel',
+  oprjoin => 'areajoinsel' },
+{ oid => '8075', oid_symbol => 'OID_MULTIRANGE_OVERLAPS_MULTIRANGE_OP', descr => 'contains',
+  oprname => '&&', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '&&(anymultirange,anymultirange)',
+  oprcode => 'multirange_overlaps_multirange', oprrest => 'multirangesel',
+  oprjoin => 'areajoinsel' },
+{ oid => '8064', oid_symbol => 'OID_MULTIRANGE_CONTAINS_ELEM_OP',
+  descr => 'contains',
+  oprname => '@>', oprleft => 'anymultirange', oprright => 'anyelement',
+  oprresult => 'bool', oprcom => '<@(anyelement,anymultirange)',
+  oprcode => 'multirange_contains_elem', oprrest => 'multirangesel',
+  oprjoin => 'contjoinsel' },
+{ oid => '8065', oid_symbol => 'OID_MULTIRANGE_CONTAINS_RANGE_OP', descr => 'contains',
+  oprname => '@>', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcom => '<@(anyrange,anymultirange)',
+  oprcode => 'multirange_contains_range', oprrest => 'multirangesel',
+  oprjoin => 'contjoinsel' },
+{ oid => '8066', oid_symbol => 'OID_MULTIRANGE_CONTAINS_MULTIRANGE_OP', descr => 'contains',
+  oprname => '@>', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '<@(anymultirange,anymultirange)',
+  oprcode => 'multirange_contains_multirange', oprrest => 'multirangesel',
+  oprjoin => 'contjoinsel' },
+{ oid => '8067', oid_symbol => 'OID_MULTIRANGE_ELEM_CONTAINED_OP',
+  descr => 'is contained by',
+  oprname => '<@', oprleft => 'anyelement', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '@>(anymultirange,anyelement)',
+  oprcode => 'elem_contained_by_multirange', oprrest => 'multirangesel',
+  oprjoin => 'contjoinsel' },
+{ oid => '8068', oid_symbol => 'OID_MULTIRANGE_RANGE_CONTAINED_OP',
+  descr => 'is contained by',
+  oprname => '<@', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '@>(anymultirange,anyrange)',
+  oprcode => 'range_contained_by_multirange', oprrest => 'multirangesel',
+  oprjoin => 'contjoinsel' },
+{ oid => '8069', oid_symbol => 'OID_MULTIRANGE_MULTIRANGE_CONTAINED_OP',
+  descr => 'is contained by',
+  oprname => '<@', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '@>(anymultirange,anymultirange)',
+  oprcode => 'multirange_contained_by_multirange', oprrest => 'multirangesel',
+  oprjoin => 'contjoinsel' },
+{ oid => '8106', oid_symbol => 'OID_RANGE_OVERLAPS_LEFT_MULTIRANGE_OP',
+  descr => 'overlaps or is left of',
+  oprname => '&<', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcode => 'range_overleft_multirange', oprrest => 'multirangesel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8107', oid_symbol => 'OID_MULTIRANGE_OVERLAPS_LEFT_RANGE_OP',
+  descr => 'overlaps or is left of',
+  oprname => '&<', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcode => 'multirange_overleft_range', oprrest => 'multirangesel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8108', oid_symbol => 'OID_MULTIRANGE_OVERLAPS_LEFT_MULTIRANGE_OP',
+  descr => 'overlaps or is left of',
+  oprname => '&<', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcode => 'multirange_overleft_multirange', oprrest => 'multirangesel',
+  oprjoin => 'scalargtjoinsel' },
+{ oid => '8109', oid_symbol => 'OID_RANGE_OVERLAPS_RIGHT_MULTIRANGE_OP',
+  descr => 'overlaps or is right of',
+  oprname => '&>', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcode => 'range_overright_multirange', oprrest => 'multirangesel',
+  oprjoin => 'scalargtjoinsel' },
+{ oid => '8110', oid_symbol => 'OID_MULTIRANGE_OVERLAPS_RIGHT_RANGE_OP',
+  descr => 'overlaps or is right of',
+  oprname => '&>', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcode => 'multirange_overright_range', oprrest => 'multirangesel',
+  oprjoin => 'scalargtjoinsel' },
+{ oid => '8111', oid_symbol => 'OID_MULTIRANGE_OVERLAPS_RIGHT_MULTIRANGE_OP',
+  descr => 'overlaps or is right of',
+  oprname => '&>', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcode => 'multirange_overright_multirange', oprrest => 'multirangesel',
+  oprjoin => 'scalargtjoinsel' },
+{ oid => '8091', oid_symbol => 'OID_RANGE_ADJACENT_MULTIRANGE_OP', descr => 'is adjacent to',
+  oprname => '-|-', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '-|-(anymultirange,anyrange)',
+  oprcode => 'range_adjacent_multirange', oprrest => 'matchingsel',
+  oprjoin => 'matchingjoinsel' },
+{ oid => '8092', oid_symbol => 'OID_MULTIRANGE_ADJACENT_RANGE_OP', descr => 'is adjacent to',
+  oprname => '-|-', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcom => '-|-(anyrange,anymultirange)',
+  oprcode => 'multirange_adjacent_range', oprrest => 'matchingsel',
+  oprjoin => 'matchingjoinsel' },
+{ oid => '8093', oid_symbol => 'OID_MULTIRANGE_ADJACENT_MULTIRANGE_OP', descr => 'is adjacent to',
+  oprname => '-|-', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '-|-(anymultirange,anymultirange)',
+  oprcode => 'multirange_adjacent_multirange', oprrest => 'matchingsel',
+  oprjoin => 'matchingjoinsel' },
+{ oid => '8117', descr => 'multirange union',
+  oprname => '+', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'anymultirange', oprcom => '+(anymultirange,anymultirange)',
+  oprcode => 'multirange_union' },
+{ oid => '8123', descr => 'multirange minus',
+  oprname => '-', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'anymultirange', oprcode => 'multirange_minus' },
+{ oid => '8129', descr => 'multirange intersect',
+  oprname => '*', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'anymultirange', oprcom => '*(anymultirange,anymultirange)',
+  oprcode => 'multirange_intersect' },
+{ oid => '8082', oid_symbol => 'OID_RANGE_LEFT_MULTIRANGE_OP', descr => 'is left of',
+  oprname => '<<', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '>>(anymultirange,anyrange)',
+  oprcode => 'range_before_multirange', oprrest => 'multirangesel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8083', oid_symbol => 'OID_MULTIRANGE_LEFT_RANGE_OP', descr => 'is left of',
+  oprname => '<<', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcom => '>>(anyrange,anymultirange)',
+  oprcode => 'multirange_before_range', oprrest => 'multirangesel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8084', oid_symbol => 'OID_MULTIRANGE_LEFT_MULTIRANGE_OP', descr => 'is left of',
+  oprname => '<<', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '>>(anymultirange,anymultirange)',
+  oprcode => 'multirange_before_multirange', oprrest => 'multirangesel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8085', oid_symbol => 'OID_RANGE_RIGHT_MULTIRANGE_OP', descr => 'is right of',
+  oprname => '>>', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '<<(anymultirange,anyrange)',
+  oprcode => 'range_after_multirange', oprrest => 'multirangesel',
+  oprjoin => 'scalargtjoinsel' },
+{ oid => '8086', oid_symbol => 'OID_MULTIRANGE_RIGHT_RANGE_OP', descr => 'is right of',
+  oprname => '>>', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcom => '<<(anyrange,anymultirange)',
+  oprcode => 'multirange_after_range', oprrest => 'multirangesel',
+  oprjoin => 'scalargtjoinsel' },
+{ oid => '8087', oid_symbol => 'OID_MULTIRANGE_RIGHT_MULTIRANGE_OP', descr => 'is right of',
+  oprname => '>>', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '<<(anymultirange,anymultirange)',
+  oprcode => 'multirange_after_multirange', oprrest => 'multirangesel',
+  oprjoin => 'scalargtjoinsel' },
 
 ]
diff --git a/src/include/catalog/pg_opfamily.dat b/src/include/catalog/pg_opfamily.dat
index 11c7ad2c145..3ff81026fc7 100644
--- a/src/include/catalog/pg_opfamily.dat
+++ b/src/include/catalog/pg_opfamily.dat
@@ -232,5 +232,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 e7fbda9f81a..c771d12dba2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -7252,6 +7252,14 @@
   proname => 'anycompatiblerange_out', provolatile => 's',
   prorettype => 'cstring', proargtypes => 'anycompatiblerange',
   prosrc => 'anycompatiblerange_out' },
+{ oid => '8154', descr => 'I/O',
+  proname => 'anycompatiblemultirange_in', provolatile => 's',
+  prorettype => 'anycompatiblemultirange', proargtypes => 'cstring oid int4',
+  prosrc => 'anycompatiblemultirange_in' },
+{ oid => '8155', descr => 'I/O',
+  proname => 'anycompatiblemultirange_out', provolatile => 's',
+  prorettype => 'cstring', proargtypes => 'anycompatiblemultirange',
+  prosrc => 'anycompatiblemultirange_out' },
 
 # tablesample method handlers
 { oid => '3313', descr => 'BERNOULLI tablesample method handler',
@@ -9872,6 +9880,10 @@
   descr => 'the smallest range which includes both of the given ranges',
   proname => 'range_merge', prorettype => 'anyrange',
   proargtypes => 'anyrange anyrange', prosrc => 'range_merge' },
+{ oid => '8143',
+  descr => 'the smallest range which includes the whole multirange',
+  proname => 'range_merge', prorettype => 'anyrange',
+  proargtypes => 'anymultirange', prosrc => 'range_merge_from_multirange' },
 { oid => '3868',
   proname => 'range_intersect', prorettype => 'anyrange',
   proargtypes => 'anyrange anyrange', prosrc => 'range_intersect' },
@@ -9921,6 +9933,13 @@
 { oid => '3169', descr => 'restriction selectivity for range operators',
   proname => 'rangesel', provolatile => 's', prorettype => 'float8',
   proargtypes => 'internal oid internal int4', prosrc => 'rangesel' },
+{ oid => '8133', descr => 'range aggregate by intersecting',
+  proname => 'range_intersect_agg_transfn', prorettype => 'anyrange',
+  proargtypes => 'anyrange anyrange', prosrc => 'range_intersect_agg_transfn'},
+{ oid => '8134', descr => 'range aggregate by intersecting',
+  proname => 'range_intersect_agg', prokind => 'a', proisstrict => 'f',
+  prorettype => 'anyrange', proargtypes => 'anyrange',
+  prosrc => 'aggregate_dummy'},
 
 { oid => '3914', descr => 'convert an int4 range to canonical form',
   proname => 'int4range_canonical', prorettype => 'int4range',
@@ -9989,6 +10008,261 @@
   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 => '8094', descr => 'lower bound of multirange',
+  proname => 'lower', prorettype => 'anyelement', proargtypes => 'anymultirange',
+  prosrc => 'multirange_lower' },
+{ oid => '8095', descr => 'upper bound of multirange',
+  proname => 'upper', prorettype => 'anyelement', proargtypes => 'anymultirange',
+  prosrc => 'multirange_upper' },
+{ oid => '8057', descr => 'is the multirange empty?',
+  proname => 'isempty', prorettype => 'bool',
+  proargtypes => 'anymultirange', prosrc => 'multirange_empty' },
+{ oid => '8096', descr => 'is the multirange\'s lower bound inclusive?',
+  proname => 'lower_inc', prorettype => 'bool', proargtypes => 'anymultirange',
+  prosrc => 'multirange_lower_inc' },
+{ oid => '8097', descr => 'is the multirange\'s upper bound inclusive?',
+  proname => 'upper_inc', prorettype => 'bool', proargtypes => 'anymultirange',
+  prosrc => 'multirange_upper_inc' },
+{ oid => '8098', descr => 'is the multirange\'s lower bound infinite?',
+  proname => 'lower_inf', prorettype => 'bool', proargtypes => 'anymultirange',
+  prosrc => 'multirange_lower_inf' },
+{ oid => '8099', descr => 'is the multirange\'s upper bound infinite?',
+  proname => 'upper_inf', prorettype => 'bool', proargtypes => 'anymultirange',
+  prosrc => 'multirange_upper_inf' },
+{ oid => '8008', descr => 'multirange typanalyze',
+  proname => 'multirange_typanalyze', provolatile => 's', prorettype => 'bool',
+  proargtypes => 'internal', prosrc => 'multirange_typanalyze' },
+{ oid => '8156', descr => 'restriction selectivity for multirange operators',
+  proname => 'multirangesel', provolatile => 's', prorettype => 'float8',
+  proargtypes => 'internal oid internal int4', prosrc => 'multirangesel' },
+{ oid => '8033',
+  proname => 'multirange_eq', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_eq' },
+{ oid => '8034',
+  proname => 'multirange_ne', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_ne' },
+{ oid => '8070',
+  proname => 'range_overlaps_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_overlaps_multirange' },
+{ oid => '8071',
+  proname => 'multirange_overlaps_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_overlaps_range' },
+{ oid => '8072',
+  proname => 'multirange_overlaps_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_overlaps_multirange' },
+{ oid => '8058',
+  proname => 'multirange_contains_elem', prorettype => 'bool',
+  proargtypes => 'anymultirange anyelement', prosrc => 'multirange_contains_elem' },
+{ oid => '8059',
+  proname => 'multirange_contains_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_contains_range' },
+{ oid => '8060',
+  proname => 'multirange_contains_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_contains_multirange' },
+{ oid => '8061',
+  proname => 'elem_contained_by_multirange', prorettype => 'bool',
+  proargtypes => 'anyelement anymultirange', prosrc => 'elem_contained_by_multirange' },
+{ oid => '8062',
+  proname => 'range_contained_by_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_contained_by_multirange' },
+{ oid => '8063',
+  proname => 'multirange_contained_by_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_contained_by_multirange' },
+{ oid => '8088',
+  proname => 'range_adjacent_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_adjacent_multirange' },
+{ oid => '8089',
+  proname => 'multirange_adjacent_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_adjacent_multirange' },
+{ oid => '8090',
+  proname => 'multirange_adjacent_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_adjacent_range' },
+{ oid => '8076',
+  proname => 'range_before_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_before_multirange' },
+{ oid => '8077',
+  proname => 'multirange_before_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_before_range' },
+{ oid => '8078',
+  proname => 'multirange_before_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_before_multirange' },
+{ oid => '8079',
+  proname => 'range_after_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_after_multirange' },
+{ oid => '8080',
+  proname => 'multirange_after_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_after_range' },
+{ oid => '8081',
+  proname => 'multirange_after_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_after_multirange' },
+{ oid => '8100',
+  proname => 'range_overleft_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_overleft_multirange' },
+{ oid => '8101',
+  proname => 'multirange_overleft_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_overleft_range' },
+{ oid => '8102',
+  proname => 'multirange_overleft_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_overleft_multirange' },
+{ oid => '8103',
+  proname => 'range_overright_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_overright_multirange' },
+{ oid => '8104',
+  proname => 'multirange_overright_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_overright_range' },
+{ oid => '8105',
+  proname => 'multirange_overright_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_overright_multirange' },
+{ oid => '8114',
+  proname => 'multirange_union', prorettype => 'anymultirange',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_union' },
+{ oid => '8120',
+  proname => 'multirange_minus', prorettype => 'anymultirange',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_minus' },
+{ oid => '8126',
+  proname => 'multirange_intersect', prorettype => 'anymultirange',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_intersect' },
+{ oid => '8022', descr => 'less-equal-greater',
+  proname => 'multirange_cmp', prorettype => 'int4',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_cmp' },
+{ oid => '8023',
+  proname => 'multirange_lt', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_lt' },
+{ oid => '8024',
+  proname => 'multirange_le', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_le' },
+{ oid => '8025',
+  proname => 'multirange_ge', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_ge' },
+{ oid => '8026',
+  proname => 'multirange_gt', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_gt' },
+{ oid => '8038', descr => 'hash a multirange',
+  proname => 'hash_multirange', prorettype => 'int4', proargtypes => 'anymultirange',
+  prosrc => 'hash_multirange' },
+{ oid => '8039', descr => 'hash a multirange',
+  proname => 'hash_multirange_extended', prorettype => 'int8',
+  proargtypes => 'anymultirange int8', prosrc => 'hash_multirange_extended' },
+
+{ oid => '8045', descr => 'int4multirange constructor',
+  proname => 'int4multirange', proisstrict => 'f',
+  prorettype => 'int4multirange', proargtypes => '',
+  prosrc => 'multirange_constructor0' },
+{ oid => '8146', descr => 'int4multirange constructor',
+  proname => 'int4multirange', proisstrict => 't',
+  prorettype => 'int4multirange', proargtypes => 'int4range',
+  prosrc => 'multirange_constructor1' },
+{ oid => '8046', descr => 'int4multirange constructor',
+  proname => 'int4multirange', provariadic => 'int4range', proisstrict => 'f',
+  prorettype => 'int4multirange', proargtypes => '_int4range',
+  proallargtypes => '{_int4range}', proargmodes => '{v}',
+  prosrc => 'multirange_constructor2' },
+{ oid => '8047', descr => 'nummultirange constructor',
+  proname => 'nummultirange', proisstrict => 'f',
+  prorettype => 'nummultirange', proargtypes => '',
+  prosrc => 'multirange_constructor0' },
+{ oid => '8147', descr => 'nummultirange constructor',
+  proname => 'nummultirange', proisstrict => 't',
+  prorettype => 'nummultirange', proargtypes => 'numrange',
+  prosrc => 'multirange_constructor1' },
+{ oid => '8048', descr => 'nummultirange constructor',
+  proname => 'nummultirange', provariadic => 'numrange', proisstrict => 'f',
+  prorettype => 'nummultirange', proargtypes => '_numrange',
+  proallargtypes => '{_numrange}', proargmodes => '{v}',
+  prosrc => 'multirange_constructor2' },
+{ oid => '8049', descr => 'tsmultirange constructor',
+  proname => 'tsmultirange', proisstrict => 'f',
+  prorettype => 'tsmultirange', proargtypes => '',
+  prosrc => 'multirange_constructor0' },
+{ oid => '8148', descr => 'tsmultirange constructor',
+  proname => 'tsmultirange', proisstrict => 't',
+  prorettype => 'tsmultirange', proargtypes => 'tsrange',
+  prosrc => 'multirange_constructor1' },
+{ oid => '8050', descr => 'tsmultirange constructor',
+  proname => 'tsmultirange', provariadic => 'tsrange', proisstrict => 'f',
+  prorettype => 'tsmultirange', proargtypes => '_tsrange',
+  proallargtypes => '{_tsrange}', proargmodes => '{v}',
+  prosrc => 'multirange_constructor2' },
+{ oid => '8051', descr => 'tstzmultirange constructor',
+  proname => 'tstzmultirange', proisstrict => 'f',
+  prorettype => 'tstzmultirange', proargtypes => '',
+  prosrc => 'multirange_constructor0' },
+{ oid => '8149', descr => 'tstzmultirange constructor',
+  proname => 'tstzmultirange', proisstrict => 't',
+  prorettype => 'tstzmultirange', proargtypes => 'tstzrange',
+  prosrc => 'multirange_constructor1' },
+{ oid => '8052', descr => 'tstzmultirange constructor',
+  proname => 'tstzmultirange', provariadic => 'tstzrange', proisstrict => 'f',
+  prorettype => 'tstzmultirange', proargtypes => '_tstzrange',
+  proallargtypes => '{_tstzrange}', proargmodes => '{v}',
+  prosrc => 'multirange_constructor2' },
+{ oid => '8053', descr => 'datemultirange constructor',
+  proname => 'datemultirange', proisstrict => 'f',
+  prorettype => 'datemultirange', proargtypes => '',
+  prosrc => 'multirange_constructor0' },
+{ oid => '8150', descr => 'datemultirange constructor',
+  proname => 'datemultirange', proisstrict => 't',
+  prorettype => 'datemultirange', proargtypes => 'daterange',
+  prosrc => 'multirange_constructor1' },
+{ oid => '8054', descr => 'datemultirange constructor',
+  proname => 'datemultirange', provariadic => 'daterange', proisstrict => 'f',
+  prorettype => 'datemultirange', proargtypes => '_daterange',
+  proallargtypes => '{_daterange}', proargmodes => '{v}',
+  prosrc => 'multirange_constructor2' },
+{ oid => '8055', descr => 'int8multirange constructor',
+  proname => 'int8multirange', proisstrict => 'f',
+  prorettype => 'int8multirange', proargtypes => '',
+  prosrc => 'multirange_constructor0' },
+{ oid => '8151', descr => 'int8multirange constructor',
+  proname => 'int8multirange', proisstrict => 't',
+  prorettype => 'int8multirange', proargtypes => 'int8range',
+  prosrc => 'multirange_constructor1' },
+{ oid => '8056', descr => 'int8multirange constructor',
+  proname => 'int8multirange', provariadic => 'int8range', proisstrict => 'f',
+  prorettype => 'int8multirange', proargtypes => '_int8range',
+  proallargtypes => '{_int8range}', proargmodes => '{v}',
+  prosrc => 'multirange_constructor2' },
+{ oid => '8152', descr => 'anymultirange cast',
+  proname => 'multirange', proisstrict => 't',
+  prorettype => 'anymultirange', proargtypes => 'anyrange',
+  prosrc => 'multirange_constructor1' },
+{ oid => '8130', descr => 'aggregate transition function',
+  proname => 'range_agg_transfn', proisstrict => 'f', prorettype => 'internal',
+  proargtypes => 'internal anyrange', prosrc => 'range_agg_transfn' },
+{ oid => '8131', descr => 'aggregate final function',
+  proname => 'range_agg_finalfn', proisstrict => 'f', prorettype => 'anymultirange',
+  proargtypes => 'internal anyrange', prosrc => 'range_agg_finalfn' },
+{ oid => '8132', descr => 'combine aggregate input into a multirange',
+  proname => 'range_agg', prokind => 'a', proisstrict => 'f',
+  prorettype => 'anymultirange', proargtypes => 'anyrange',
+  prosrc => 'aggregate_dummy' },
+{ oid => '8135', descr => 'range aggregate by intersecting',
+  proname => 'multirange_intersect_agg_transfn', prorettype => 'anymultirange',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_intersect_agg_transfn'},
+{ oid => '8136', descr => 'range aggregate by intersecting',
+  proname => 'range_intersect_agg', prokind => 'a', proisstrict => 'f',
+  prorettype => 'anymultirange', proargtypes => 'anymultirange',
+  prosrc => 'aggregate_dummy'},
+
 # date, time, timestamp constructors
 { oid => '3846', descr => 'construct date',
   proname => 'make_date', prorettype => 'date', proargtypes => 'int4 int4 int4',
@@ -10371,6 +10645,14 @@
   proname => 'binary_upgrade_set_next_array_pg_type_oid', provolatile => 'v',
   proparallel => 'r', prorettype => 'void', proargtypes => 'oid',
   prosrc => 'binary_upgrade_set_next_array_pg_type_oid' },
+{ oid => '8144', descr => 'for use by pg_upgrade',
+  proname => 'binary_upgrade_set_next_multirange_pg_type_oid', provolatile => 'v',
+  proparallel => 'r', prorettype => 'void', proargtypes => 'oid',
+  prosrc => 'binary_upgrade_set_next_multirange_pg_type_oid' },
+{ oid => '8145', descr => 'for use by pg_upgrade',
+  proname => 'binary_upgrade_set_next_multirange_array_pg_type_oid', provolatile => 'v',
+  proparallel => 'r', prorettype => 'void', proargtypes => 'oid',
+  prosrc => 'binary_upgrade_set_next_multirange_array_pg_type_oid' },
 { oid => '3586', descr => 'for use by pg_upgrade',
   proname => 'binary_upgrade_set_next_heap_pg_class_oid', provolatile => 'v',
   proparallel => 'r', prorettype => 'void', proargtypes => 'oid',
diff --git a/src/include/catalog/pg_range.dat b/src/include/catalog/pg_range.dat
index 479754c2455..10060255c95 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',
+  rngmultitypid => 'int4multirange' },
 { rngtypid => 'numrange', rngsubtype => 'numeric',
   rngsubopc => 'btree/numeric_ops', rngcanonical => '-',
-  rngsubdiff => 'numrange_subdiff' },
+  rngsubdiff => 'numrange_subdiff', rngmultitypid => 'nummultirange' },
 { rngtypid => 'tsrange', rngsubtype => 'timestamp',
   rngsubopc => 'btree/timestamp_ops', rngcanonical => '-',
-  rngsubdiff => 'tsrange_subdiff' },
+  rngsubdiff => 'tsrange_subdiff', rngmultitypid => 'tsmultirange' },
 { rngtypid => 'tstzrange', rngsubtype => 'timestamptz',
   rngsubopc => 'btree/timestamptz_ops', rngcanonical => '-',
-  rngsubdiff => 'tstzrange_subdiff' },
+  rngsubdiff => 'tstzrange_subdiff', rngmultitypid => 'tstzmultirange' },
 { rngtypid => 'daterange', rngsubtype => 'date', rngsubopc => 'btree/date_ops',
-  rngcanonical => 'daterange_canonical', rngsubdiff => 'daterange_subdiff' },
+  rngcanonical => 'daterange_canonical', rngsubdiff => 'daterange_subdiff',
+  rngmultitypid => 'datemultirange' },
 { rngtypid => 'int8range', rngsubtype => 'int8', rngsubopc => 'btree/int8_ops',
-  rngcanonical => 'int8range_canonical', rngsubdiff => 'int8range_subdiff' },
+  rngcanonical => 'int8range_canonical', rngsubdiff => 'int8range_subdiff',
+  rngmultitypid => 'int8multirange' },
 
 ]
diff --git a/src/include/catalog/pg_range.h b/src/include/catalog/pg_range.h
index c28bb57b6c9..07e6dbba667 100644
--- a/src/include/catalog/pg_range.h
+++ b/src/include/catalog/pg_range.h
@@ -34,6 +34,9 @@ CATALOG(pg_range,3541,RangeRelationId)
 	/* OID of range's element type (subtype) */
 	Oid			rngsubtype BKI_LOOKUP(pg_type);
 
+	/* OID of the range's multirange type */
+	Oid			rngmultitypid BKI_LOOKUP(pg_type);
+
 	/* collation for this range type, or 0 */
 	Oid			rngcollation BKI_DEFAULT(0);
 
@@ -57,13 +60,17 @@ typedef FormData_pg_range *Form_pg_range;
 DECLARE_UNIQUE_INDEX(pg_range_rngtypid_index, 3542, on pg_range using btree(rngtypid oid_ops));
 #define RangeTypidIndexId					3542
 
+DECLARE_UNIQUE_INDEX(pg_range_rngmultitypid_index, 8001, on pg_range using btree(rngmultitypid oid_ops));
+#define RangeMultirangeTypidIndexId			8001
+
+
 /*
  * prototypes for functions in pg_range.c
  */
 
 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 21a467a7a7a..35953b51f9f 100644
--- a/src/include/catalog/pg_type.dat
+++ b/src/include/catalog/pg_type.dat
@@ -490,6 +490,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 => 'R', 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 => 'R', 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 => 'R', 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 => 'R', 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 => 'R', 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 => 'R', 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
@@ -619,5 +653,16 @@
   typtype => 'p', typcategory => 'P', typinput => 'anycompatiblerange_in',
   typoutput => 'anycompatiblerange_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' },
+{ oid => '8153',
+  descr => 'pseudo-type representing a multirange over a polymorphic common type',
+  typname => 'anycompatiblemultirange', typlen => '-1', typbyval => 'f',
+  typtype => 'p', typcategory => 'P', typinput => 'anycompatiblemultirange_in',
+  typoutput => 'anycompatiblemultirange_out', typreceive => '-', typsend => '-',
+  typalign => 'd', typstorage => 'x' },
 
 ]
diff --git a/src/include/catalog/pg_type.h b/src/include/catalog/pg_type.h
index 6099e5f57ca..c5dd43a0abe 100644
--- a/src/include/catalog/pg_type.h
+++ b/src/include/catalog/pg_type.h
@@ -270,6 +270,7 @@ DECLARE_UNIQUE_INDEX(pg_type_typname_nsp_index, 2704, on pg_type using btree(typ
 #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 */
 
@@ -311,13 +312,15 @@ DECLARE_UNIQUE_INDEX(pg_type_typname_nsp_index, 2704, on pg_type using btree(typ
 	 (typid) == ANYARRAYOID || \
 	 (typid) == ANYNONARRAYOID || \
 	 (typid) == ANYENUMOID || \
-	 (typid) == ANYRANGEOID)
+	 (typid) == ANYRANGEOID || \
+	 (typid) == ANYMULTIRANGEOID)
 
 #define IsPolymorphicTypeFamily2(typid)  \
 	((typid) == ANYCOMPATIBLEOID || \
 	 (typid) == ANYCOMPATIBLEARRAYOID || \
 	 (typid) == ANYCOMPATIBLENONARRAYOID || \
-	 (typid) == ANYCOMPATIBLERANGEOID)
+	 (typid) == ANYCOMPATIBLERANGEOID || \
+	 (typid) == ANYCOMPATIBLEMULTIRANGEOID)
 
 /*
  * Backwards compatibility for ancient random spellings of pg_type OID macros.
@@ -385,4 +388,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/commands/typecmds.h b/src/include/commands/typecmds.h
index 23130895af4..989914dfe11 100644
--- a/src/include/commands/typecmds.h
+++ b/src/include/commands/typecmds.h
@@ -29,6 +29,8 @@ extern ObjectAddress DefineRange(CreateRangeStmt *stmt);
 extern ObjectAddress AlterEnum(AlterEnumStmt *stmt);
 extern ObjectAddress DefineCompositeType(RangeVar *typevar, List *coldeflist);
 extern Oid	AssignTypeArrayOid(void);
+extern Oid	AssignTypeMultirangeOid(void);
+extern Oid	AssignTypeMultirangeArrayOid(void);
 
 extern ObjectAddress AlterDomainDefault(List *names, Node *defaultRaw);
 extern ObjectAddress AlterDomainNotNull(List *names, bool notNull);
diff --git a/src/include/utils/lsyscache.h b/src/include/utils/lsyscache.h
index fecfe1f4f6e..ed84908d727 100644
--- a/src/include/utils/lsyscache.h
+++ b/src/include/utils/lsyscache.h
@@ -157,6 +157,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);
@@ -183,6 +184,8 @@ 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_collation(Oid rangeOid);
+extern Oid	get_range_multirange(Oid rangeOid);
+extern Oid	get_multirange_range(Oid multirangeOid);
 extern Oid	get_index_column_opclass(Oid index_oid, int attno);
 extern bool get_index_isreplident(Oid index_oid);
 extern bool get_index_isvalid(Oid index_oid);
diff --git a/src/include/utils/multirangetypes.h b/src/include/utils/multirangetypes.h
new file mode 100644
index 00000000000..e0961e5fe14
--- /dev/null
+++ b/src/include/utils/multirangetypes.h
@@ -0,0 +1,144 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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"
+#include "utils/expandeddatum.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
+{
+	int32		vl_len_;		/* varlena header (do not touch directly!) */
+	Oid			multirangetypid;	/* multirange type's own OID */
+	uint32		rangeCount;		/* the number of ranges */
+
+	/*
+	 * Following the count are the range objects themselves, as ShortRangeType
+	 * structs. 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;
+
+/*
+ * Since each RangeType includes its type oid, it seems wasteful to store them
+ * in multiranges like that on-disk. Instead we store ShortRangeType structs
+ * that have everything but the type varlena header and oid. But we do want the
+ * type oid in memory, so that we can call ordinary range functions. We use the
+ * EXPANDED TOAST mechansim to convert between them.
+ */
+typedef struct
+{
+	int32		vl_len_;		/* varlena header */
+	char		flags;			/* range flags */
+	char		_padding;		/* Bounds must be aligned */
+	/* Following the header are zero to two bound values. */
+} ShortRangeType;
+
+#define EMR_MAGIC 689375834		/* ID for debugging crosschecks */
+
+typedef struct ExpandedMultirangeHeader
+{
+	/* Standard header for expanded objects */
+	ExpandedObjectHeader hdr;
+
+	/* Magic value identifying an expanded array (for debugging only) */
+	int			emr_magic;
+
+	Oid			multirangetypid;	/* multirange type's own OID */
+	uint32		rangeCount;			/* the number of ranges */
+	RangeType   **ranges;			/* array of ranges */
+
+	/*
+	 * flat_size is the current space requirement for the flat equivalent of
+	 * the expanded multirange, if known; otherwise it's 0.  We store this to make
+	 * consecutive calls of get_flat_size cheap.
+	 */
+	Size		flat_size;
+} ExpandedMultirangeHeader;
+
+/* Use these macros in preference to accessing these fields 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);
+extern bool multirange_contains_elem_internal(TypeCacheEntry *typcache, MultirangeType *mr,
+											  Datum elem);
+extern bool multirange_contains_range_internal(TypeCacheEntry *typcache, MultirangeType *mr,
+											   RangeType *r);
+extern bool multirange_contains_multirange_internal(TypeCacheEntry *typcache,
+													MultirangeType *mr1,
+													MultirangeType *mr2);
+extern bool range_overlaps_multirange_internal(TypeCacheEntry *typcache, RangeType *r,
+											   MultirangeType *mr);
+extern bool multirange_overlaps_multirange_internal(TypeCacheEntry *typcache,
+													MultirangeType *mr1,
+													MultirangeType *mr2);
+extern bool range_before_multirange_internal(TypeCacheEntry *typcache, RangeType *r,
+											 MultirangeType *mr);
+extern bool range_after_multirange_internal(TypeCacheEntry *typcache, RangeType *r,
+											MultirangeType *mr);
+extern bool multirange_before_multirange_internal(TypeCacheEntry *typcache,
+												  MultirangeType *mr1,
+												  MultirangeType *mr2);
+extern MultirangeType *multirange_minus_internal(Oid mltrngtypoid,
+												 TypeCacheEntry *rangetyp,
+												 int32 range_count1,
+												 RangeType **ranges1,
+												 int32 range_count2,
+												 RangeType **ranges2);
+extern MultirangeType *multirange_intersect_internal(Oid mltrngtypoid,
+													 TypeCacheEntry *rangetyp,
+													 int32 range_count1,
+													 RangeType **ranges1,
+													 int32 range_count2,
+													 RangeType **ranges2);
+
+/* assorted support functions */
+extern TypeCacheEntry *multirange_get_typcache(FunctionCallInfo fcinfo,
+											   Oid mltrngtypid);
+extern void multirange_deserialize(TypeCacheEntry *rangetyp,
+								   const 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);
+
+extern Datum expand_multirange(Datum multirangedatum, MemoryContext parentcontext, TypeCacheEntry *rangetyp);
+
+#endif							/* MULTIRANGETYPES_H */
diff --git a/src/include/utils/rangetypes.h b/src/include/utils/rangetypes.h
index b77c41cf1b8..139e19c7d66 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)
 
@@ -92,13 +94,21 @@ typedef struct
  * prototypes for functions defined in rangetypes.c
  */
 
-extern bool range_contains_elem_internal(TypeCacheEntry *typcache, const RangeType *r, Datum val);
-
 /* internal versions of the above */
+extern int	range_cmp_internal(TypeCacheEntry *typcache, const RangeType *r1,
+							   const RangeType *r2);
+extern uint32 hash_range_internal(TypeCacheEntry *typcache, const RangeType *r);
+extern uint64 hash_range_extended_internal(TypeCacheEntry *typcache, const RangeType *r,
+										   Datum seed);
+extern Datum range_lower_internal(TypeCacheEntry *typcache, const RangeType *r1,
+								  bool *isnull);
+extern Datum range_upper_internal(TypeCacheEntry *typcache, const RangeType *r1,
+								  bool *isnull);
 extern bool range_eq_internal(TypeCacheEntry *typcache, const RangeType *r1,
 							  const RangeType *r2);
 extern bool range_ne_internal(TypeCacheEntry *typcache, const RangeType *r1,
 							  const RangeType *r2);
+extern bool range_contains_elem_internal(TypeCacheEntry *typcache, const RangeType *r, Datum val);
 extern bool range_contains_internal(TypeCacheEntry *typcache, const RangeType *r1,
 									const RangeType *r2);
 extern bool range_contained_by_internal(TypeCacheEntry *typcache, const RangeType *r1,
@@ -115,8 +125,18 @@ extern bool range_overleft_internal(TypeCacheEntry *typcache, const RangeType *r
 									const RangeType *r2);
 extern bool range_overright_internal(TypeCacheEntry *typcache, const RangeType *r1,
 									 const RangeType *r2);
+extern RangeType *range_union_internal(TypeCacheEntry *typcache, RangeType *r1,
+									   RangeType *r2, bool strict);
+extern RangeType *range_minus_internal(TypeCacheEntry *typcache, RangeType *r1,
+									   RangeType *r2);
+extern RangeType *range_intersect_internal(TypeCacheEntry *typcache, const RangeType *r1,
+										   const RangeType *r2);
 
 /* assorted support functions */
+extern Size datum_compute_size(Size sz, Datum datum, bool typbyval,
+							   char typalign, int16 typlen, char typstorage);
+extern Pointer datum_write(Pointer ptr, Datum datum, bool typbyval,
+						   char typalign, int16 typlen, char typstorage);
 extern TypeCacheEntry *range_get_typcache(FunctionCallInfo fcinfo,
 										  Oid rngtypid);
 extern RangeType *range_serialize(TypeCacheEntry *typcache, RangeBound *lower,
@@ -125,6 +145,7 @@ extern void range_deserialize(TypeCacheEntry *typcache, const RangeType *range,
 							  RangeBound *lower, RangeBound *upper,
 							  bool *empty);
 extern char range_get_flags(const RangeType *range);
+extern bool range_has_flag(const RangeType *range, char flag);
 extern void range_set_contain_empty(RangeType *range);
 extern RangeType *make_range(TypeCacheEntry *typcache, RangeBound *lower,
 							 RangeBound *upper, bool empty);
@@ -132,8 +153,12 @@ extern int	range_cmp_bounds(TypeCacheEntry *typcache, const RangeBound *b1,
 							 const RangeBound *b2);
 extern int	range_cmp_bound_values(TypeCacheEntry *typcache, const RangeBound *b1,
 								   const RangeBound *b2);
+extern int	range_compare(const void *key1, const void *key2, void *arg);
 extern bool bounds_adjacent(TypeCacheEntry *typcache, RangeBound bound1,
 							RangeBound bound2);
 extern RangeType *make_empty_range(TypeCacheEntry *typcache);
+extern bool range_split_internal(TypeCacheEntry *typcache, const RangeType *r1,
+								 const RangeType *r2, RangeType **output1,
+								 RangeType **output2);
 
 #endif							/* RANGETYPES_H */
diff --git a/src/include/utils/selfuncs.h b/src/include/utils/selfuncs.h
index 3a2cfb7efa6..68ffd7761f1 100644
--- a/src/include/utils/selfuncs.h
+++ b/src/include/utils/selfuncs.h
@@ -39,6 +39,9 @@
 /* default selectivity estimate for range inequalities "A > b AND A < c" */
 #define DEFAULT_RANGE_INEQ_SEL	0.005
 
+/* default selectivity estimate for multirange inequalities "A > b AND A < c" */
+#define DEFAULT_MULTIRANGE_INEQ_SEL	0.005
+
 /* default selectivity estimate for pattern-match operators such as LIKE */
 #define DEFAULT_MATCH_SEL	0.005
 
diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h
index f27b73d76d0..e8f393520a3 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -79,6 +79,7 @@ enum SysCacheIdentifier
 	PUBLICATIONOID,
 	PUBLICATIONREL,
 	PUBLICATIONRELMAP,
+	RANGEMULTIRANGE,
 	RANGETYPE,
 	RELNAMENSP,
 	RELOID,
diff --git a/src/include/utils/typcache.h b/src/include/utils/typcache.h
index cdd20e56d70..33f332a1417 100644
--- a/src/include/utils/typcache.h
+++ b/src/include/utils/typcache.h
@@ -100,6 +100,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;	/* multirange's range underlying type */
+
 	/*
 	 * Domain's base type and typmod if it's a domain type.  Zeroes if not
 	 * domain, or if information hasn't been requested.
@@ -143,6 +148,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 6df8e14629d..d200facfa45 100644
--- a/src/pl/plpgsql/src/pl_comp.c
+++ b/src/pl/plpgsql/src/pl_comp.c
@@ -514,6 +514,8 @@ do_compile(FunctionCallInfo fcinfo,
 					else if (rettypeid == ANYRANGEOID ||
 							 rettypeid == ANYCOMPATIBLERANGEOID)
 						rettypeid = INT4RANGEOID;
+					else if (rettypeid == ANYMULTIRANGEOID)
+						rettypeid = INT4MULTIRANGEOID;
 					else		/* ANYELEMENT or ANYNONARRAY or ANYCOMPATIBLE */
 						rettypeid = INT4OID;
 					/* XXX what could we use for ANYENUM? */
@@ -2108,6 +2110,7 @@ build_datatype(HeapTuple typeTup, int32 typmod,
 		case TYPTYPE_BASE:
 		case TYPTYPE_ENUM:
 		case TYPTYPE_RANGE:
+		case TYPTYPE_MULTIRANGE:
 			typ->ttype = PLPGSQL_TTYPE_SCALAR;
 			break;
 		case TYPTYPE_COMPOSITE:
@@ -2526,6 +2529,9 @@ plpgsql_resolve_polymorphic_argtypes(int numargs,
 				case ANYCOMPATIBLERANGEOID:
 					argtypes[i] = INT4RANGEOID;
 					break;
+				case ANYMULTIRANGEOID:
+					argtypes[i] = INT4MULTIRANGEOID;
+					break;
 				default:
 					break;
 			}
diff --git a/src/test/regress/expected/dependency.out b/src/test/regress/expected/dependency.out
index b4a11b8aa9b..82327951487 100644
--- a/src/test/regress/expected/dependency.out
+++ b/src/test/regress/expected/dependency.out
@@ -140,6 +140,7 @@ owner of sequence deptest_a_seq
 owner of table deptest
 owner of function deptest_func()
 owner of type deptest_enum
+owner of type deptest_multirange
 owner of type deptest_range
 owner of table deptest2
 owner of sequence ss1
diff --git a/src/test/regress/expected/hash_func.out b/src/test/regress/expected/hash_func.out
index 827e69fc7ab..dca31365bcf 100644
--- a/src/test/regress/expected/hash_func.out
+++ b/src/test/regress/expected/hash_func.out
@@ -305,6 +305,19 @@ 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)
+
 CREATE TYPE hash_test_t1 AS (a int, b text);
 SELECT v as value, hash_record(v)::bit(32) as standard,
        hash_record_extended(v, 0)::bit(32) as extended0,
diff --git a/src/test/regress/expected/multirangetypes.out b/src/test/regress/expected/multirangetypes.out
new file mode 100644
index 00000000000..e72f99863fb
--- /dev/null
+++ b/src/test/regress/expected/multirangetypes.out
@@ -0,0 +1,2437 @@
+-- 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)
+
+--
+-- test casts, both a built-in range type and a user-defined one:
+--
+select 'empty'::int4range::int4multirange;
+ int4multirange 
+----------------
+ {}
+(1 row)
+
+select int4range(1, 3)::int4multirange;
+ int4range 
+-----------
+ {[1,3)}
+(1 row)
+
+select int4range(1, null)::int4multirange;
+ int4range 
+-----------
+ {[1,)}
+(1 row)
+
+select int4range(null, null)::int4multirange;
+ int4range 
+-----------
+ {(,)}
+(1 row)
+
+select 'empty'::textrange::textmultirange;
+ textmultirange 
+----------------
+ {}
+(1 row)
+
+select textrange('a', 'c')::textmultirange;
+ textrange 
+-----------
+ {[a,c)}
+(1 row)
+
+select textrange('a', null)::textmultirange;
+ textrange 
+-----------
+ {[a,)}
+(1 row)
+
+select textrange(null, null)::textmultirange;
+ textrange 
+-----------
+ {(,)}
+(1 row)
+
+--
+-- create some test data and test the operators
+--
+CREATE TABLE nummultirange_test (nmr NUMMULTIRANGE);
+CREATE INDEX nummultirange_test_btree ON nummultirange_test(nmr);
+INSERT INTO nummultirange_test VALUES('{}');
+INSERT INTO nummultirange_test VALUES('{[,)}');
+INSERT INTO nummultirange_test VALUES('{[3,]}');
+INSERT INTO nummultirange_test VALUES('{[,), [3,]}');
+INSERT INTO nummultirange_test VALUES('{[, 5)}');
+INSERT INTO nummultirange_test VALUES(nummultirange());
+INSERT INTO nummultirange_test VALUES(nummultirange(variadic '{}'::numrange[]));
+INSERT INTO nummultirange_test VALUES(nummultirange(numrange(1.1, 2.2)));
+INSERT INTO nummultirange_test VALUES('{empty}');
+INSERT INTO nummultirange_test VALUES(nummultirange(numrange(1.7, 1.7, '[]'), numrange(1.7, 1.9)));
+INSERT INTO nummultirange_test VALUES(nummultirange(numrange(1.7, 1.7, '[]'), numrange(1.9, 2.1)));
+SELECT nmr, isempty(nmr), lower(nmr), upper(nmr) FROM nummultirange_test ORDER BY nmr;
+          nmr          | isempty | lower | upper 
+-----------------------+---------+-------+-------
+ {}                    | t       |       |      
+ {}                    | t       |       |      
+ {}                    | t       |       |      
+ {}                    | t       |       |      
+ {(,5)}                | f       |       |     5
+ {(,)}                 | f       |       |      
+ {(,)}                 | f       |       |      
+ {[1.1,2.2)}           | f       |   1.1 |   2.2
+ {[1.7,1.7],[1.9,2.1)} | f       |   1.7 |   2.1
+ {[1.7,1.9)}           | f       |   1.7 |   1.9
+ {[3,)}                | f       |     3 |      
+(11 rows)
+
+SELECT nmr, lower_inc(nmr), lower_inf(nmr), upper_inc(nmr), upper_inf(nmr) FROM nummultirange_test ORDER BY nmr;
+          nmr          | lower_inc | lower_inf | upper_inc | upper_inf 
+-----------------------+-----------+-----------+-----------+-----------
+ {}                    | f         | f         | f         | f
+ {}                    | f         | f         | f         | f
+ {}                    | f         | f         | f         | f
+ {}                    | f         | f         | f         | f
+ {(,5)}                | f         | t         | f         | f
+ {(,)}                 | f         | t         | f         | t
+ {(,)}                 | f         | t         | f         | t
+ {[1.1,2.2)}           | t         | f         | f         | f
+ {[1.7,1.7],[1.9,2.1)} | t         | f         | f         | f
+ {[1.7,1.9)}           | t         | f         | f         | f
+ {[3,)}                | t         | f         | f         | t
+(11 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr = '{}';
+ nmr 
+-----
+ {}
+ {}
+ {}
+ {}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr = '{(,5)}';
+  nmr   
+--------
+ {(,5)}
+(1 row)
+
+SELECT * FROM nummultirange_test WHERE nmr = '{[3,)}';
+  nmr   
+--------
+ {[3,)}
+(1 row)
+
+SELECT * FROM nummultirange_test WHERE nmr = '{[1.7,1.7]}';
+ nmr 
+-----
+(0 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr = '{[1.7,1.7],[1.9,2.1)}';
+          nmr          
+-----------------------
+ {[1.7,1.7],[1.9,2.1)}
+(1 row)
+
+SELECT * FROM nummultirange_test WHERE nmr < '{}';
+ nmr 
+-----
+(0 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr < '{[-1000.0, -1000.0]}';
+  nmr   
+--------
+ {}
+ {(,)}
+ {(,)}
+ {(,5)}
+ {}
+ {}
+ {}
+(7 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr < '{[0.0, 1.0]}';
+  nmr   
+--------
+ {}
+ {(,)}
+ {(,)}
+ {(,5)}
+ {}
+ {}
+ {}
+(7 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr < '{[1000.0, 1001.0]}';
+          nmr          
+-----------------------
+ {}
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+ {}
+ {}
+ {[1.1,2.2)}
+ {}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(11 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr <= '{}';
+ nmr 
+-----
+ {}
+ {}
+ {}
+ {}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr <= '{[3,)}';
+          nmr          
+-----------------------
+ {}
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+ {}
+ {}
+ {[1.1,2.2)}
+ {}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(11 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr >= '{}';
+          nmr          
+-----------------------
+ {}
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+ {}
+ {}
+ {[1.1,2.2)}
+ {}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(11 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr >= '{[3,)}';
+  nmr   
+--------
+ {[3,)}
+(1 row)
+
+SELECT * FROM nummultirange_test WHERE nmr > '{}';
+          nmr          
+-----------------------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+ {[1.1,2.2)}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(7 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr > '{[-1000.0, -1000.0]}';
+          nmr          
+-----------------------
+ {[3,)}
+ {[1.1,2.2)}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr > '{[0.0, 1.0]}';
+          nmr          
+-----------------------
+ {[3,)}
+ {[1.1,2.2)}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr > '{[1000.0, 1001.0]}';
+ nmr 
+-----
+(0 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr <> '{}';
+          nmr          
+-----------------------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+ {[1.1,2.2)}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(7 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr <> '{(,5)}';
+          nmr          
+-----------------------
+ {}
+ {(,)}
+ {[3,)}
+ {(,)}
+ {}
+ {}
+ {[1.1,2.2)}
+ {}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(10 rows)
+
+select nummultirange(numrange(2.0, 1.0));
+ERROR:  range lower bound must be less than or equal to range upper bound
+select nummultirange(numrange(5.0, 6.0), numrange(1.0, 2.0));
+     nummultirange     
+-----------------------
+ {[1.0,2.0),[5.0,6.0)}
+(1 row)
+
+analyze nummultirange_test;
+-- overlaps
+SELECT * FROM nummultirange_test WHERE range_overlaps_multirange(numrange(4.0, 4.2), nmr);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE numrange(4.0, 4.2) && nmr;
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE multirange_overlaps_range(nmr, numrange(4.0, 4.2));
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr && numrange(4.0, 4.2);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE multirange_overlaps_multirange(nmr, nummultirange(numrange(4.0, 4.2), numrange(6.0, 7.0)));
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr && nummultirange(numrange(4.0, 4.2), numrange(6.0, 7.0));
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr && nummultirange(numrange(6.0, 7.0));
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+(3 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr && nummultirange(numrange(6.0, 7.0), numrange(8.0, 9.0));
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+(3 rows)
+
+-- mr contains x
+SELECT * FROM nummultirange_test WHERE multirange_contains_elem(nmr, 4.0);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr @> 4.0;
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE multirange_contains_range(nmr, numrange(4.0, 4.2));
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr @> numrange(4.0, 4.2);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE multirange_contains_multirange(nmr, '{[4.0,4.2), [6.0, 8.0)}');
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+(3 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr @> '{[4.0,4.2), [6.0, 8.0)}'::nummultirange;
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+(3 rows)
+
+-- x is contained by mr
+SELECT * FROM nummultirange_test WHERE elem_contained_by_multirange(4.0, nmr);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE 4.0 <@ nmr;
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE range_contained_by_multirange(numrange(4.0, 4.2), nmr);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE numrange(4.0, 4.2) <@ nmr;
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE multirange_contained_by_multirange('{[4.0,4.2), [6.0, 8.0)}', nmr);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+(3 rows)
+
+SELECT * FROM nummultirange_test WHERE '{[4.0,4.2), [6.0, 8.0)}'::nummultirange <@ nmr;
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+(3 rows)
+
+-- overlaps
+SELECT 'empty'::numrange && nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT 'empty'::numrange && nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() && 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) && 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() && nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() && nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) && nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) && nummultirange(numrange(1,2), numrange(7,8));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(7,8)) && nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) && nummultirange(numrange(1,2), numrange(3.5,8));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(3.5,8)) && numrange(3,4);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(3.5,8)) && nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+-- contains
+SELECT nummultirange() @> nummultirange();
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange() @> 'empty'::numrange;
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(null,null)) @> numrange(1,2);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(null,null)) @> numrange(null,2);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(null,null)) @> numrange(2,null);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(null,5)) @> numrange(null,3);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(null,5)) @> numrange(null,8);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(5,null)) @> numrange(8,null);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(5,null)) @> numrange(3,null);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,5)) @> numrange(8,9);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,5)) @> numrange(3,9);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,5)) @> numrange(1,4);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,5)) @> numrange(1,5);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(-4,-2), numrange(1,5)) @> numrange(1,5);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,5), numrange(8,9)) @> numrange(1,5);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,5), numrange(8,9)) @> numrange(6,7);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,5), numrange(6,9)) @> numrange(6,7);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[1,5)}'::nummultirange @> '{[1,5)}';
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[-4,-2), [1,5)}'::nummultirange @> '{[1,5)}';
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[1,5), [8,9)}'::nummultirange @> '{[1,5)}';
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[1,5), [8,9)}'::nummultirange @> '{[6,7)}';
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT '{[1,5), [6,9)}'::nummultirange @> '{[6,7)}';
+ ?column? 
+----------
+ t
+(1 row)
+
+-- is contained by
+SELECT nummultirange() <@ nummultirange();
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT 'empty'::numrange <@ nummultirange();
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,2) <@ nummultirange(numrange(null,null));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(null,2) <@ nummultirange(numrange(null,null));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(2,null) <@ nummultirange(numrange(null,null));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(null,3) <@ nummultirange(numrange(null,5));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(null,8) <@ nummultirange(numrange(null,5));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(8,null) <@ nummultirange(numrange(5,null));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(3,null) <@ nummultirange(numrange(5,null));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(8,9) <@ nummultirange(numrange(1,5));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(3,9) <@ nummultirange(numrange(1,5));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(1,4) <@ nummultirange(numrange(1,5));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,5) <@ nummultirange(numrange(1,5));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,5) <@ nummultirange(numrange(-4,-2), numrange(1,5));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,5) <@ nummultirange(numrange(1,5), numrange(8,9));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(6,7) <@ nummultirange(numrange(1,5), numrange(8,9));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(6,7) <@ nummultirange(numrange(1,5), numrange(6,9));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[1,5)}' <@ '{[1,5)}'::nummultirange;
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[1,5)}' <@ '{[-4,-2), [1,5)}'::nummultirange;
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[1,5)}' <@ '{[1,5), [8,9)}'::nummultirange;
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[6,7)}' <@ '{[1,5), [8,9)}'::nummultirange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT '{[6,7)}' <@ '{[1,5), [6,9)}'::nummultirange;
+ ?column? 
+----------
+ t
+(1 row)
+
+-- overleft
+SELECT 'empty'::numrange &< nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT 'empty'::numrange &< nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() &< 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) &< 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() &< nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) &< nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() &< nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(6,7) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(1,2) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,4) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,6) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(3.5,6) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(6,7)) &< numrange(3,4);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) &< numrange(3,4);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,4)) &< numrange(3,4);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,6)) &< numrange(3,4);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3.5,6)) &< numrange(3,4);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(6,7)) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,4)) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,6)) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3.5,6)) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+-- overright
+SELECT nummultirange() &> 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) &> 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT 'empty'::numrange &> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT 'empty'::numrange &> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() &> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() &> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) &> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> numrange(6,7);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> numrange(1,2);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> numrange(1,4);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> numrange(1,6);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> numrange(3.5,6);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(3,4) &> nummultirange(numrange(6,7));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(3,4) &> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(3,4) &> nummultirange(numrange(1,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(3,4) &> nummultirange(numrange(1,6));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(3,4) &> nummultirange(numrange(3.5,6));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(6,7));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(1,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(1,6));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(3.5,6));
+ ?column? 
+----------
+ f
+(1 row)
+
+-- meets
+SELECT 'empty'::numrange -|- nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT 'empty'::numrange -|- nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() -|- 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() -|- nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() -|- nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(1,2) -|- nummultirange(numrange(2,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,2) -|- nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- numrange(2,4);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- numrange(3,4);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- nummultirange(numrange(2,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(5,6)) -|- nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(5,6)) -|- nummultirange(numrange(6,7));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(5,6)) -|- nummultirange(numrange(8,9));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- nummultirange(numrange(2,4), numrange(6,7));
+ ?column? 
+----------
+ t
+(1 row)
+
+-- strictly left
+select 'empty'::numrange << nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(1,2) << nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(1,2) << nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+select numrange(1,2) << nummultirange(numrange(0,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(1,2) << nummultirange(numrange(0,4), numrange(7,8));
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange() << 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange() << numrange(1,2);
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(3,4)) << numrange(3,6);
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(0,2)) << numrange(3,6);
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange(numrange(0,2), numrange(7,8)) << numrange(3,6);
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(-4,-2), numrange(0,2)) << numrange(3,6);
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange() << nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange() << nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(1,2)) << nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(1,2)) << nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(1,2)) << nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange(numrange(1,2)) << nummultirange(numrange(3,4), numrange(7,8));
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange(numrange(1,2), numrange(4,5)) << nummultirange(numrange(3,4), numrange(7,8));
+ ?column? 
+----------
+ f
+(1 row)
+
+-- strictly right
+select nummultirange() >> 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange() >> numrange(1,2);
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(3,4)) >> numrange(1,2);
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange(numrange(0,4)) >> numrange(1,2);
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(0,4), numrange(7,8)) >> numrange(1,2);
+ ?column? 
+----------
+ f
+(1 row)
+
+select 'empty'::numrange >> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(1,2) >> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(3,6) >> nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(3,6) >> nummultirange(numrange(0,2));
+ ?column? 
+----------
+ t
+(1 row)
+
+select numrange(3,6) >> nummultirange(numrange(0,2), numrange(7,8));
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(3,6) >> nummultirange(numrange(-4,-2), numrange(0,2));
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange() >> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(1,2)) >> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange() >> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(1,2)) >> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(3,4)) >> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange(numrange(3,4), numrange(7,8)) >> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange(numrange(3,4), numrange(7,8)) >> nummultirange(numrange(1,2), numrange(4,5));
+ ?column? 
+----------
+ f
+(1 row)
+
+-- union
+SELECT nummultirange() + nummultirange();
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange() + nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) + nummultirange();
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) + nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) + nummultirange(numrange(2,4));
+ ?column? 
+----------
+ {[1,4)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) + nummultirange(numrange(3,4));
+   ?column?    
+---------------
+ {[1,2),[3,4)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) + nummultirange(numrange(2,4));
+ ?column? 
+----------
+ {[1,5)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) + nummultirange(numrange(3,4));
+   ?column?    
+---------------
+ {[1,2),[3,5)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) + nummultirange(numrange(0,9));
+ ?column? 
+----------
+ {[0,9)}
+(1 row)
+
+-- merge
+SELECT range_merge(nummultirange());
+ range_merge 
+-------------
+ empty
+(1 row)
+
+SELECT range_merge(nummultirange(numrange(1,2)));
+ range_merge 
+-------------
+ [1,2)
+(1 row)
+
+SELECT range_merge(nummultirange(numrange(1,2), numrange(7,8)));
+ range_merge 
+-------------
+ [1,8)
+(1 row)
+
+-- minus
+SELECT nummultirange() - nummultirange();
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange() - nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) - nummultirange();
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(3,4)) - nummultirange();
+   ?column?    
+---------------
+ {[1,2),[3,4)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) - nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) - nummultirange(numrange(2,4));
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) - nummultirange(numrange(3,4));
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {[2,4)}
+(1 row)
+
+SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(2,3));
+   ?column?    
+---------------
+ {[1,2),[3,4)}
+(1 row)
+
+SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(0,8));
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(0,2));
+ ?column? 
+----------
+ {[2,4)}
+(1 row)
+
+SELECT nummultirange(numrange(1,8)) - nummultirange(numrange(0,2), numrange(3,4));
+   ?column?    
+---------------
+ {[2,3),[4,8)}
+(1 row)
+
+SELECT nummultirange(numrange(1,8)) - nummultirange(numrange(2,3), numrange(5,null));
+   ?column?    
+---------------
+ {[1,2),[3,5)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(-2,0));
+   ?column?    
+---------------
+ {[1,2),[4,5)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(2,4));
+   ?column?    
+---------------
+ {[1,2),[4,5)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(3,5));
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(0,9));
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange(numrange(1,3), numrange(4,5)) - nummultirange(numrange(2,9));
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(8,9));
+   ?column?    
+---------------
+ {[1,2),[4,5)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(-2,0), numrange(8,9));
+   ?column?    
+---------------
+ {[1,2),[4,5)}
+(1 row)
+
+-- intersection
+SELECT nummultirange() * nummultirange();
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange() * nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) * nummultirange();
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT '{[1,3)}'::nummultirange * '{[1,5)}'::nummultirange;
+ ?column? 
+----------
+ {[1,3)}
+(1 row)
+
+SELECT '{[1,3)}'::nummultirange * '{[0,5)}'::nummultirange;
+ ?column? 
+----------
+ {[1,3)}
+(1 row)
+
+SELECT '{[1,3)}'::nummultirange * '{[0,2)}'::nummultirange;
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT '{[1,3)}'::nummultirange * '{[2,5)}'::nummultirange;
+ ?column? 
+----------
+ {[2,3)}
+(1 row)
+
+SELECT '{[1,4)}'::nummultirange * '{[2,3)}'::nummultirange;
+ ?column? 
+----------
+ {[2,3)}
+(1 row)
+
+SELECT '{[1,4)}'::nummultirange * '{[0,2), [3,5)}'::nummultirange;
+   ?column?    
+---------------
+ {[1,2),[3,4)}
+(1 row)
+
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[0,8), [9,12)}'::nummultirange;
+       ?column?       
+----------------------
+ {[1,4),[7,8),[9,10)}
+(1 row)
+
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[9,12)}'::nummultirange;
+ ?column? 
+----------
+ {[9,10)}
+(1 row)
+
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[-5,-4), [5,6), [9,12)}'::nummultirange;
+ ?column? 
+----------
+ {[9,10)}
+(1 row)
+
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[0,2), [3,8), [9,12)}'::nummultirange;
+          ?column?          
+----------------------------
+ {[1,2),[3,4),[7,8),[9,10)}
+(1 row)
+
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[0,2), [3,8), [9,12)}'::nummultirange;
+          ?column?          
+----------------------------
+ {[1,2),[3,4),[7,8),[9,10)}
+(1 row)
+
+--
+-- range_agg function
+--
+create table reservations ( room_id integer not null, booked_during daterange );
+insert into reservations values
+-- 1: has a meets and a gap
+(1, daterange('2018-07-01', '2018-07-07')),
+(1, daterange('2018-07-07', '2018-07-14')),
+(1, daterange('2018-07-20', '2018-07-22')),
+-- 2: just a single row
+(2, daterange('2018-07-01', '2018-07-03')),
+-- 3: one null range
+(3, NULL),
+-- 4: two null ranges
+(4, NULL),
+(4, NULL),
+-- 5: a null range and a non-null range
+(5, NULL),
+(5, daterange('2018-07-01', '2018-07-03')),
+-- 6: has overlap
+(6, daterange('2018-07-01', '2018-07-07')),
+(6, daterange('2018-07-05', '2018-07-10')),
+-- 7: two ranges that meet: no gap or overlap
+(7, daterange('2018-07-01', '2018-07-07')),
+(7, daterange('2018-07-07', '2018-07-14')),
+-- 8: an empty range
+(8, 'empty'::daterange)
+;
+SELECT   room_id, range_agg(booked_during)
+FROM     reservations
+GROUP BY room_id
+ORDER BY room_id;
+ room_id |                     range_agg                     
+---------+---------------------------------------------------
+       1 | {[07-01-2018,07-14-2018),[07-20-2018,07-22-2018)}
+       2 | {[07-01-2018,07-03-2018)}
+       3 | 
+       4 | 
+       5 | {[07-01-2018,07-03-2018)}
+       6 | {[07-01-2018,07-10-2018)}
+       7 | {[07-01-2018,07-14-2018)}
+       8 | {}
+(8 rows)
+
+-- range_agg on a custom range type too
+SELECT  range_agg(r)
+FROM    (VALUES
+          ('[a,c]'::textrange),
+          ('[b,b]'::textrange),
+          ('[c,f]'::textrange),
+          ('[g,h)'::textrange),
+          ('[h,j)'::textrange)
+        ) t(r);
+   range_agg   
+---------------
+ {[a,f],[g,j)}
+(1 row)
+
+select range_intersect_agg(nmr) from nummultirange_test;
+ range_intersect_agg 
+---------------------
+ {}
+(1 row)
+
+select range_intersect_agg(nmr) from nummultirange_test where false;
+ range_intersect_agg 
+---------------------
+ 
+(1 row)
+
+-- test with just one input:
+select range_intersect_agg(nmr) from (values ('{[1,2]}'::nummultirange)) t(nmr);
+ range_intersect_agg 
+---------------------
+ {[1,2]}
+(1 row)
+
+select range_intersect_agg(nmr) from nummultirange_test where nmr @> 4.0;
+ range_intersect_agg 
+---------------------
+ {[3,5)}
+(1 row)
+
+create table nummultirange_test2(nmr nummultirange);
+create index nummultirange_test2_hash_idx on nummultirange_test2 using hash (nmr);
+INSERT INTO nummultirange_test2 VALUES('{[, 5)}');
+INSERT INTO nummultirange_test2 VALUES(nummultirange(numrange(1.1, 2.2)));
+INSERT INTO nummultirange_test2 VALUES(nummultirange(numrange(1.1, 2.2)));
+INSERT INTO nummultirange_test2 VALUES(nummultirange(numrange(1.1, 2.2,'()')));
+INSERT INTO nummultirange_test2 VALUES('{}');
+select * from nummultirange_test2 where nmr = '{}';
+ nmr 
+-----
+ {}
+(1 row)
+
+select * from nummultirange_test2 where nmr = nummultirange(numrange(1.1, 2.2));
+     nmr     
+-------------
+ {[1.1,2.2)}
+ {[1.1,2.2)}
+(2 rows)
+
+select * from nummultirange_test2 where nmr = nummultirange(numrange(1.1, 2.3));
+ nmr 
+-----
+(0 rows)
+
+set enable_nestloop=t;
+set enable_hashjoin=f;
+set enable_mergejoin=f;
+select * from nummultirange_test natural join nummultirange_test2 order by nmr;
+     nmr     
+-------------
+ {}
+ {}
+ {}
+ {}
+ {(,5)}
+ {[1.1,2.2)}
+ {[1.1,2.2)}
+(7 rows)
+
+set enable_nestloop=f;
+set enable_hashjoin=t;
+set enable_mergejoin=f;
+select * from nummultirange_test natural join nummultirange_test2 order by nmr;
+     nmr     
+-------------
+ {}
+ {}
+ {}
+ {}
+ {(,5)}
+ {[1.1,2.2)}
+ {[1.1,2.2)}
+(7 rows)
+
+set enable_nestloop=f;
+set enable_hashjoin=f;
+set enable_mergejoin=t;
+select * from nummultirange_test natural join nummultirange_test2 order by nmr;
+     nmr     
+-------------
+ {}
+ {}
+ {}
+ {}
+ {(,5)}
+ {[1.1,2.2)}
+ {[1.1,2.2)}
+(7 rows)
+
+set enable_nestloop to default;
+set enable_hashjoin to default;
+set enable_mergejoin to default;
+DROP TABLE nummultirange_test2;
+--
+-- Test user-defined multirange of floats
+--
+select '{[123.001, 5.e9)}'::float8multirange @> 888.882::float8;
+ ?column? 
+----------
+ t
+(1 row)
+
+create table float8multirange_test(f8mr float8multirange, i int);
+insert into float8multirange_test values(float8multirange(float8range(-100.00007, '1.111113e9')), 42);
+select * from float8multirange_test;
+           f8mr            | i  
+---------------------------+----
+ {[-100.00007,1111113000)} | 42
+(1 row)
+
+drop table float8multirange_test;
+--
+-- Test multirange types over domains
+--
+create domain mydomain as int4;
+create type mydomainrange as range(subtype=mydomain);
+select '{[4,50)}'::mydomainmultirange @> 7::mydomain;
+ ?column? 
+----------
+ t
+(1 row)
+
+drop domain mydomain cascade;
+NOTICE:  drop cascades to type mydomainrange
+--
+-- Test domains over multirange types
+--
+create domain restrictedmultirange as int4multirange check (upper(value) < 10);
+select '{[4,5)}'::restrictedmultirange @> 7;
+ ?column? 
+----------
+ f
+(1 row)
+
+select '{[4,50)}'::restrictedmultirange @> 7; -- should fail
+ERROR:  value for domain restrictedmultirange violates check constraint "restrictedmultirange_check"
+drop domain restrictedmultirange;
+--
+-- Test multiple multirange types over the same subtype
+--
+create type textrange1 as range(subtype=text, collation="C");
+create type textrange2 as range(subtype=text, collation="C");
+select textmultirange1(textrange2('a','Z'));  -- should fail
+ERROR:  function textmultirange1(textrange2) does not exist
+LINE 1: select textmultirange1(textrange2('a','Z'));
+               ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+select textmultirange1(textrange1('a','Z')) @> 'b'::text;
+ERROR:  range lower bound must be less than or equal to range upper bound
+select textmultirange2(textrange2('a','z')) @> 'b'::text;
+ ?column? 
+----------
+ t
+(1 row)
+
+drop type textrange1;
+drop type textrange2;
+--
+-- Test polymorphic type system
+--
+create function anyarray_anymultirange_func(a anyarray, r anymultirange)
+  returns anyelement as 'select $1[1] + lower($2);' language sql;
+select anyarray_anymultirange_func(ARRAY[1,2], int4multirange(int4range(10,20)));
+ anyarray_anymultirange_func 
+-----------------------------
+                          11
+(1 row)
+
+-- should fail
+select anyarray_anymultirange_func(ARRAY[1,2], nummultirange(numrange(10,20)));
+ERROR:  function anyarray_anymultirange_func(integer[], nummultirange) does not exist
+LINE 1: select anyarray_anymultirange_func(ARRAY[1,2], nummultirange...
+               ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+drop function anyarray_anymultirange_func(anyarray, anymultirange);
+-- should fail
+create function bogus_func(anyelement)
+  returns anymultirange as 'select int4multirange(int4range(1,10))' language sql;
+ERROR:  cannot determine result data type
+DETAIL:  A result of type anymultirange requires at least one input of type anyrange or anymultirange.
+-- should fail
+create function bogus_func(int)
+  returns anymultirange as 'select int4multirange(int4range(1,10))' language sql;
+ERROR:  cannot determine result data type
+DETAIL:  A result of type anymultirange requires at least one input of type anyrange or anymultirange.
+create function range_add_bounds(anymultirange)
+  returns anyelement as 'select lower($1) + upper($1)' language sql;
+select range_add_bounds(int4multirange(int4range(1, 17)));
+ range_add_bounds 
+------------------
+               18
+(1 row)
+
+select range_add_bounds(nummultirange(numrange(1.0001, 123.123)));
+ range_add_bounds 
+------------------
+         124.1231
+(1 row)
+
+create function multirangetypes_sql(q anymultirange, b anyarray, out c anyelement)
+  as $$ select upper($1) + $2[1] $$
+  language sql;
+select multirangetypes_sql(int4multirange(int4range(1,10)), ARRAY[2,20]);
+ multirangetypes_sql 
+---------------------
+                  12
+(1 row)
+
+select multirangetypes_sql(nummultirange(numrange(1,10)), ARRAY[2,20]);  -- match failure
+ERROR:  function multirangetypes_sql(nummultirange, integer[]) does not exist
+LINE 1: select multirangetypes_sql(nummultirange(numrange(1,10)), AR...
+               ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+create function anycompatiblearray_anycompatiblemultirange_func(a anycompatiblearray, mr anycompatiblemultirange)
+  returns anycompatible as 'select $1[1] + lower($2);' language sql;
+select anycompatiblearray_anycompatiblemultirange_func(ARRAY[1,2], multirange(int4range(10,20)));
+ anycompatiblearray_anycompatiblemultirange_func 
+-------------------------------------------------
+                                              11
+(1 row)
+
+select anycompatiblearray_anycompatiblemultirange_func(ARRAY[1,2], multirange(numrange(10,20)));
+ anycompatiblearray_anycompatiblemultirange_func 
+-------------------------------------------------
+                                              11
+(1 row)
+
+-- should fail
+select anycompatiblearray_anycompatiblemultirange_func(ARRAY[1.1,2], multirange(int4range(10,20)));
+ERROR:  function anycompatiblearray_anycompatiblemultirange_func(numeric[], int4multirange) does not exist
+LINE 1: select anycompatiblearray_anycompatiblemultirange_func(ARRAY...
+               ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+drop function anycompatiblearray_anycompatiblemultirange_func(anycompatiblearray, anycompatiblemultirange);
+create function anycompatiblerange_anycompatiblemultirange_func(r anycompatiblerange, mr anycompatiblemultirange)
+  returns anycompatible as 'select lower($1) + lower($2);' language sql;
+select anycompatiblerange_anycompatiblemultirange_func(int4range(1,2), multirange(int4range(10,20)));
+ anycompatiblerange_anycompatiblemultirange_func 
+-------------------------------------------------
+                                              11
+(1 row)
+
+-- should fail
+select anycompatiblerange_anycompatiblemultirange_func(numrange(1,2), multirange(int4range(10,20)));
+ERROR:  function anycompatiblerange_anycompatiblemultirange_func(numrange, int4multirange) does not exist
+LINE 1: select anycompatiblerange_anycompatiblemultirange_func(numra...
+               ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+drop function anycompatiblerange_anycompatiblemultirange_func(anycompatiblerange, anycompatiblemultirange);
+-- should fail
+create function bogus_func(anycompatible)
+  returns anycompatiblerange as 'select int4range(1,10)' language sql;
+ERROR:  cannot determine result data type
+DETAIL:  A result of type anycompatiblerange requires at least one input of type anycompatiblerange or anycompatiblemultirange.
+--
+-- Arrays of multiranges
+--
+select ARRAY[nummultirange(numrange(1.1, 1.2)), nummultirange(numrange(12.3, 155.5))];
+              array               
+----------------------------------
+ {"{[1.1,1.2)}","{[12.3,155.5)}"}
+(1 row)
+
+create table i8mr_array (f1 int, f2 int8multirange[]);
+insert into i8mr_array values (42, array[int8multirange(int8range(1,10)), int8multirange(int8range(2,20))]);
+select * from i8mr_array;
+ f1 |           f2            
+----+-------------------------
+ 42 | {"{[1,10)}","{[2,20)}"}
+(1 row)
+
+drop table i8mr_array;
+--
+-- Multiranges of arrays
+--
+select arraymultirange(arrayrange(ARRAY[1,2], ARRAY[2,1]));
+   arraymultirange   
+---------------------
+ {["{1,2}","{2,1}")}
+(1 row)
+
+select arraymultirange(arrayrange(ARRAY[2,1], ARRAY[1,2]));  -- fail
+ERROR:  range lower bound must be less than or equal to range upper bound
+select array[1,1] <@ arraymultirange(arrayrange(array[1,2], array[2,1]));
+ ?column? 
+----------
+ f
+(1 row)
+
+select array[1,3] <@ arraymultirange(arrayrange(array[1,2], array[2,1]));
+ ?column? 
+----------
+ t
+(1 row)
+
+--
+-- Ranges of composites
+--
+create type two_ints as (a int, b int);
+create type two_ints_range as range (subtype = two_ints);
+-- with force_parallel_mode on, this exercises tqueue.c's range remapping
+select *, row_to_json(upper(t)) as u from
+  (values (two_ints_multirange(two_ints_range(row(1,2), row(3,4)))),
+          (two_ints_multirange(two_ints_range(row(5,6), row(7,8))))) v(t);
+          t          |       u       
+---------------------+---------------
+ {["(1,2)","(3,4)")} | {"a":3,"b":4}
+ {["(5,6)","(7,8)")} | {"a":7,"b":8}
+(2 rows)
+
+drop type two_ints cascade;
+NOTICE:  drop cascades to type two_ints_range
+--
+-- Check behavior when subtype lacks a hash function
+--
+set enable_sort = off;  -- try to make it pick a hash setop implementation
+select '{(2,5)}'::cashmultirange except select '{(5,6)}'::cashmultirange;
+ cashmultirange  
+-----------------
+ {($2.00,$5.00)}
+(1 row)
+
+reset enable_sort;
+--
+-- OUT/INOUT/TABLE functions
+--
+-- infer anymultirange from anymultirange
+create function mr_outparam_succeed(i anymultirange, out r anymultirange, out t text)
+  as $$ select $1, 'foo'::text $$ language sql;
+select * from mr_outparam_succeed(int4multirange(int4range(1,2)));
+    r    |  t  
+---------+-----
+ {[1,2)} | foo
+(1 row)
+
+-- infer anyarray from anymultirange
+create function mr_outparam_succeed2(i anymultirange, out r anyarray, out t text)
+  as $$ select ARRAY[upper($1)], 'foo'::text $$ language sql;
+select * from mr_outparam_succeed2(int4multirange(int4range(1,2)));
+  r  |  t  
+-----+-----
+ {2} | foo
+(1 row)
+
+-- infer anyrange from anymultirange
+create function mr_outparam_succeed3(i anymultirange, out r anyrange, out t text)
+  as $$ select range_merge($1), 'foo'::text $$ language sql;
+select * from mr_outparam_succeed3(int4multirange(int4range(1,2)));
+   r   |  t  
+-------+-----
+ [1,2) | foo
+(1 row)
+
+-- infer anymultirange from anyrange
+create function mr_outparam_succeed4(i anyrange, out r anymultirange, out t text)
+  as $$ select multirange($1), 'foo'::text $$ language sql;
+select * from mr_outparam_succeed4(int4range(1,2));
+    r    |  t  
+---------+-----
+ {[1,2)} | foo
+(1 row)
+
+-- infer anyelement from anymultirange
+create function mr_inoutparam_succeed(out i anyelement, inout r anymultirange)
+  as $$ select upper($1), $1 $$ language sql;
+select * from mr_inoutparam_succeed(int4multirange(int4range(1,2)));
+ i |    r    
+---+---------
+ 2 | {[1,2)}
+(1 row)
+
+-- infer anyelement+anymultirange from anyelement+anymultirange
+create function mr_table_succeed(i anyelement, r anymultirange) returns table(i anyelement, r anymultirange)
+  as $$ select $1, $2 $$ language sql;
+select * from mr_table_succeed(123, int4multirange(int4range(1,11)));
+  i  |    r     
+-----+----------
+ 123 | {[1,11)}
+(1 row)
+
+-- use anymultirange in plpgsql
+create function mr_polymorphic(i anyrange) returns anymultirange
+  as $$ begin return multirange($1); end; $$ language plpgsql;
+select mr_polymorphic(int4range(1, 4));
+ mr_polymorphic 
+----------------
+ {[1,4)}
+(1 row)
+
+-- should fail
+create function mr_outparam_fail(i anyelement, out r anymultirange, out t text)
+  as $$ select '[1,10]', 'foo' $$ language sql;
+ERROR:  cannot determine result data type
+DETAIL:  A result of type anymultirange requires at least one input of type anyrange or anymultirange.
+--should fail
+create function mr_inoutparam_fail(inout i anyelement, out r anymultirange)
+  as $$ select $1, '[1,10]' $$ language sql;
+ERROR:  cannot determine result data type
+DETAIL:  A result of type anymultirange requires at least one input of type anyrange or anymultirange.
+--should fail
+create function mr_table_fail(i anyelement) returns table(i anyelement, r anymultirange)
+  as $$ select $1, '[1,10]' $$ language sql;
+ERROR:  cannot determine result data type
+DETAIL:  A result of type anymultirange requires at least one input of type anyrange or anymultirange.
diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out
index 3b39137400f..e596ef090dd 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::regtype, p2.prorettype::regtype
 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        
@@ -208,6 +215,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        
@@ -228,6 +237,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        
@@ -340,7 +351,8 @@ WHERE p1.prorettype IN
      '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     
 ------+----------------
@@ -355,20 +367,25 @@ ORDER BY 2;
  3532 | enum_recv
 (9 rows)
 
--- anyrange is tighter than the rest, can only resolve from anyrange input
+-- anyrange and anymultirange are tighter than the rest, can only resolve
+-- from each other
 SELECT p1.oid, p1.proname
 FROM pg_proc as p1
-WHERE p1.prorettype = 'anyrange'::regtype
+WHERE p1.prorettype IN ('anyrange'::regtype, 'anymultirange'::regtype)
   AND NOT
-     'anyrange'::regtype = ANY (p1.proargtypes)
+    ('anyrange'::regtype = ANY (p1.proargtypes) OR
+      'anymultirange'::regtype = ANY (p1.proargtypes))
 ORDER BY 2;
  oid  |     proname      
 ------+------------------
+ 8002 | anymultirange_in
  3832 | anyrange_in
+ 8004 | multirange_in
+ 8006 | multirange_recv
  3876 | range_gist_union
  3834 | range_in
  3836 | range_recv
-(4 rows)
+(7 rows)
 
 -- similarly for the anycompatible family
 SELECT p1.oid, p1.proname
@@ -2202,13 +2219,14 @@ ORDER BY 1, 2, 3;
                     | float_ops        | float4_ops       | real
                     | float_ops        | float8_ops       | double precision
                     | jsonb_ops        | jsonb_ops        | jsonb
+                    | multirange_ops   | multirange_ops   | anymultirange
                     | numeric_ops      | numeric_ops      | numeric
                     | range_ops        | range_ops        | anyrange
                     | record_image_ops | record_image_ops | record
                     | record_ops       | record_ops       | record
                     | tsquery_ops      | tsquery_ops      | tsquery
                     | tsvector_ops     | tsvector_ops     | tsvector
-(14 rows)
+(15 rows)
 
 -- **************** pg_index ****************
 -- Look for illegal values in pg_index fields.
diff --git a/src/test/regress/expected/plpgsql.out b/src/test/regress/expected/plpgsql.out
index d0a6b630b8f..d55006d8c95 100644
--- a/src/test/regress/expected/plpgsql.out
+++ b/src/test/regress/expected/plpgsql.out
@@ -1811,7 +1811,7 @@ begin
   return array[x + 1, x + 2];
 end$$ language plpgsql;
 ERROR:  cannot determine result data type
-DETAIL:  A result of type anyrange requires at least one input of type anyrange.
+DETAIL:  A result of type anyrange requires at least one input of type anyrange or anymultirange.
 create function f1(x anyrange) returns anyarray as $$
 begin
   return array[lower(x), upper(x)];
@@ -1856,7 +1856,7 @@ begin
   return array[x + 1, x + 2];
 end$$ language plpgsql;
 ERROR:  cannot determine result data type
-DETAIL:  A result of type anycompatiblerange requires at least one input of type anycompatiblerange.
+DETAIL:  A result of type anycompatiblerange requires at least one input of type anycompatiblerange or anycompatiblemultirange.
 create function f1(x anycompatiblerange, y anycompatiblearray) returns anycompatiblerange as $$
 begin
   return x;
diff --git a/src/test/regress/expected/polymorphism.out b/src/test/regress/expected/polymorphism.out
index f5dfdf617fd..772345584f0 100644
--- a/src/test/regress/expected/polymorphism.out
+++ b/src/test/regress/expected/polymorphism.out
@@ -61,7 +61,7 @@ create function polyf(x anyelement) returns anyrange as $$
   select array[x + 1, x + 2]
 $$ language sql;
 ERROR:  cannot determine result data type
-DETAIL:  A result of type anyrange requires at least one input of type anyrange.
+DETAIL:  A result of type anyrange requires at least one input of type anyrange or anymultirange.
 create function polyf(x anyrange) returns anyarray as $$
   select array[lower(x), upper(x)]
 $$ language sql;
@@ -97,12 +97,27 @@ LINE 1: select polyf(int4range(42, 49), 11, 4.5) as fail;
                ^
 HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
 drop function polyf(x anycompatiblerange, y anycompatible, z anycompatible);
+create function polyf(x anycompatiblemultirange, y anycompatible, z anycompatible) returns anycompatiblearray as $$
+  select array[lower(x), upper(x), y, z]
+$$ language sql;
+select polyf(multirange(int4range(42, 49)), 11, 2::smallint) as int, polyf(multirange(float8range(4.5, 7.8)), 7.8, 11::real) as num;
+     int      |       num        
+--------------+------------------
+ {42,49,11,2} | {4.5,7.8,7.8,11}
+(1 row)
+
+select polyf(multirange(int4range(42, 49)), 11, 4.5) as fail;  -- range type doesn't fit
+ERROR:  function polyf(int4multirange, integer, numeric) does not exist
+LINE 1: select polyf(multirange(int4range(42, 49)), 11, 4.5) as fail...
+               ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+drop function polyf(x anycompatiblemultirange, y anycompatible, z anycompatible);
 -- fail, can't infer type:
 create function polyf(x anycompatible) returns anycompatiblerange as $$
   select array[x + 1, x + 2]
 $$ language sql;
 ERROR:  cannot determine result data type
-DETAIL:  A result of type anycompatiblerange requires at least one input of type anycompatiblerange.
+DETAIL:  A result of type anycompatiblerange requires at least one input of type anycompatiblerange or anycompatiblemultirange.
 create function polyf(x anycompatiblerange, y anycompatiblearray) returns anycompatiblerange as $$
   select x
 $$ language sql;
@@ -113,6 +128,22 @@ select polyf(int4range(42, 49), array[11]) as int, polyf(float8range(4.5, 7.8),
 (1 row)
 
 drop function polyf(x anycompatiblerange, y anycompatiblearray);
+-- fail, can't infer type:
+create function polyf(x anycompatible) returns anycompatiblemultirange as $$
+  select array[x + 1, x + 2]
+$$ language sql;
+ERROR:  cannot determine result data type
+DETAIL:  A result of type anycompatiblemultirange requires at least one input of type anycompatiblerange or anycompatiblemultirange.
+create function polyf(x anycompatiblemultirange, y anycompatiblearray) returns anycompatiblemultirange as $$
+  select x
+$$ language sql;
+select polyf(multirange(int4range(42, 49)), array[11]) as int, polyf(multirange(float8range(4.5, 7.8)), array[7]) as num;
+    int    |     num     
+-----------+-------------
+ {[42,49)} | {[4.5,7.8)}
+(1 row)
+
+drop function polyf(x anycompatiblemultirange, y anycompatiblearray);
 create function polyf(a anyelement, b anyarray,
                       c anycompatible, d anycompatible,
                       OUT x anyarray, OUT y anycompatiblearray)
@@ -231,7 +262,7 @@ CREATE AGGREGATE myaggp01a(*) (SFUNC = stfnp, STYPE = int4[],
 CREATE AGGREGATE myaggp02a(*) (SFUNC = stfnp, STYPE = anyarray,
   FINALFUNC = ffp, INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 --     N    P
 -- should CREATE
 CREATE AGGREGATE myaggp03a(*) (SFUNC = stfp, STYPE = int4[],
@@ -243,11 +274,11 @@ CREATE AGGREGATE myaggp03b(*) (SFUNC = stfp, STYPE = int4[],
 CREATE AGGREGATE myaggp04a(*) (SFUNC = stfp, STYPE = anyarray,
   FINALFUNC = ffp, INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 CREATE AGGREGATE myaggp04b(*) (SFUNC = stfp, STYPE = anyarray,
   INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 --    Case2 (R = P) && ((B = P) || (B = N))
 --    -------------------------------------
 --    S    tf1      B    tf2
@@ -302,13 +333,13 @@ ERROR:  function tfp(integer[], anyelement) does not exist
 CREATE AGGREGATE myaggp13a(BASETYPE = int, SFUNC = tfnp, STYPE = anyarray,
   FINALFUNC = ffp, INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 --    P    N        N    P
 -- should ERROR: tf2p(anyarray, int) not matched by tf2p(int[],anyelement)
 CREATE AGGREGATE myaggp14a(BASETYPE = int, SFUNC = tf2p, STYPE = anyarray,
   FINALFUNC = ffp, INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 --    P    N        P    N
 -- should ERROR: tfnp(anyarray, anyelement) not matched by tfnp(int[],int)
 CREATE AGGREGATE myaggp15a(BASETYPE = anyelement, SFUNC = tfnp,
@@ -324,21 +355,21 @@ ERROR:  function tf2p(anyarray, anyelement) does not exist
 CREATE AGGREGATE myaggp17a(BASETYPE = int, SFUNC = tf1p, STYPE = anyarray,
   FINALFUNC = ffp, INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 CREATE AGGREGATE myaggp17b(BASETYPE = int, SFUNC = tf1p, STYPE = anyarray,
   INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 --    P    P        N    P
 -- should ERROR: tfp(anyarray, int) not matched by tfp(anyarray, anyelement)
 CREATE AGGREGATE myaggp18a(BASETYPE = int, SFUNC = tfp, STYPE = anyarray,
   FINALFUNC = ffp, INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 CREATE AGGREGATE myaggp18b(BASETYPE = int, SFUNC = tfp, STYPE = anyarray,
   INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 --    P    P        P    N
 -- should ERROR: tf1p(anyarray, anyelement) not matched by tf1p(anyarray, int)
 CREATE AGGREGATE myaggp19a(BASETYPE = anyelement, SFUNC = tf1p,
@@ -368,11 +399,11 @@ CREATE AGGREGATE myaggn01b(*) (SFUNC = stfnp, STYPE = int4[],
 CREATE AGGREGATE myaggn02a(*) (SFUNC = stfnp, STYPE = anyarray,
   FINALFUNC = ffnp, INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 CREATE AGGREGATE myaggn02b(*) (SFUNC = stfnp, STYPE = anyarray,
   INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 --     N    P
 -- should CREATE
 CREATE AGGREGATE myaggn03a(*) (SFUNC = stfp, STYPE = int4[],
@@ -382,7 +413,7 @@ CREATE AGGREGATE myaggn03a(*) (SFUNC = stfp, STYPE = int4[],
 CREATE AGGREGATE myaggn04a(*) (SFUNC = stfp, STYPE = anyarray,
   FINALFUNC = ffnp, INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 --    Case4 (R = N) && ((B = P) || (B = N))
 --    -------------------------------------
 --    S    tf1      B    tf2
@@ -436,21 +467,21 @@ ERROR:  function tfp(integer[], anyelement) does not exist
 CREATE AGGREGATE myaggn13a(BASETYPE = int, SFUNC = tfnp, STYPE = anyarray,
   FINALFUNC = ffnp, INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 CREATE AGGREGATE myaggn13b(BASETYPE = int, SFUNC = tfnp, STYPE = anyarray,
   INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 --    P    N        N    P
 -- should ERROR: tf2p(anyarray, int) not matched by tf2p(int[],anyelement)
 CREATE AGGREGATE myaggn14a(BASETYPE = int, SFUNC = tf2p, STYPE = anyarray,
   FINALFUNC = ffnp, INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 CREATE AGGREGATE myaggn14b(BASETYPE = int, SFUNC = tf2p, STYPE = anyarray,
   INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 --    P    N        P    N
 -- should ERROR: tfnp(anyarray, anyelement) not matched by tfnp(int[],int)
 CREATE AGGREGATE myaggn15a(BASETYPE = anyelement, SFUNC = tfnp,
@@ -472,13 +503,13 @@ ERROR:  function tf2p(anyarray, anyelement) does not exist
 CREATE AGGREGATE myaggn17a(BASETYPE = int, SFUNC = tf1p, STYPE = anyarray,
   FINALFUNC = ffnp, INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 --    P    P        N    P
 -- should ERROR: tfp(anyarray, int) not matched by tfp(anyarray, anyelement)
 CREATE AGGREGATE myaggn18a(BASETYPE = int, SFUNC = tfp, STYPE = anyarray,
   FINALFUNC = ffnp, INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 --    P    P        P    N
 -- should ERROR: tf1p(anyarray, anyelement) not matched by tf1p(anyarray, int)
 CREATE AGGREGATE myaggn19a(BASETYPE = anyelement, SFUNC = tf1p,
@@ -1855,7 +1886,59 @@ returns anycompatiblerange as $$
   select $1
 $$ language sql;
 ERROR:  cannot determine result data type
-DETAIL:  A result of type anycompatiblerange requires at least one input of type anycompatiblerange.
+DETAIL:  A result of type anycompatiblerange requires at least one input of type anycompatiblerange or anycompatiblemultirange.
+create function anyctest(anycompatible, anycompatiblemultirange)
+returns anycompatiblemultirange as $$
+  select $2
+$$ language sql;
+select x, pg_typeof(x) from anyctest(11, multirange(int4range(4,7))) x;
+    x    |   pg_typeof    
+---------+----------------
+ {[4,7)} | int4multirange
+(1 row)
+
+select x, pg_typeof(x) from anyctest(11, multirange(numrange(4,7))) x;
+    x    |   pg_typeof   
+---------+---------------
+ {[4,7)} | nummultirange
+(1 row)
+
+select x, pg_typeof(x) from anyctest(11, 12) x;  -- fail
+ERROR:  function anyctest(integer, integer) does not exist
+LINE 1: select x, pg_typeof(x) from anyctest(11, 12) x;
+                                    ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+select x, pg_typeof(x) from anyctest(11.2, multirange(int4range(4,7))) x;  -- fail
+ERROR:  function anyctest(numeric, int4multirange) does not exist
+LINE 1: select x, pg_typeof(x) from anyctest(11.2, multirange(int4ra...
+                                    ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+select x, pg_typeof(x) from anyctest(11.2, '{[4,7)}') x;  -- fail
+ERROR:  could not identify anycompatiblemultirange type
+drop function anyctest(anycompatible, anycompatiblemultirange);
+create function anyctest(anycompatiblemultirange, anycompatiblemultirange)
+returns anycompatible as $$
+  select lower($1) + upper($2)
+$$ language sql;
+select x, pg_typeof(x) from anyctest(multirange(int4range(11,12)), multirange(int4range(4,7))) x;
+ x  | pg_typeof 
+----+-----------
+ 18 | integer
+(1 row)
+
+select x, pg_typeof(x) from anyctest(multirange(int4range(11,12)), multirange(numrange(4,7))) x; -- fail
+ERROR:  function anyctest(int4multirange, nummultirange) does not exist
+LINE 1: select x, pg_typeof(x) from anyctest(multirange(int4range(11...
+                                    ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+drop function anyctest(anycompatiblemultirange, anycompatiblemultirange);
+-- fail, can't infer result type:
+create function anyctest(anycompatible)
+returns anycompatiblemultirange as $$
+  select $1
+$$ language sql;
+ERROR:  cannot determine result data type
+DETAIL:  A result of type anycompatiblemultirange requires at least one input of type anycompatiblerange or anycompatiblemultirange.
 create function anyctest(anycompatiblenonarray, anycompatiblenonarray)
 returns anycompatiblearray as $$
   select array[$1, $2]
diff --git a/src/test/regress/expected/rangefuncs.out b/src/test/regress/expected/rangefuncs.out
index 06bd129fd22..5bbd5f6c0bd 100644
--- a/src/test/regress/expected/rangefuncs.out
+++ b/src/test/regress/expected/rangefuncs.out
@@ -1556,7 +1556,7 @@ DROP FUNCTION dup(anyelement);
 CREATE FUNCTION bad (f1 int, out f2 anyelement, out f3 anyarray)
 AS 'select $1, array[$1,$1]' LANGUAGE sql;
 ERROR:  cannot determine result data type
-DETAIL:  A result of type anyelement requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyelement requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 CREATE FUNCTION dup (f1 anycompatible, f2 anycompatiblearray, f3 out anycompatible, f4 out anycompatiblearray)
 AS 'select $1, $2' LANGUAGE sql;
 SELECT dup(22, array[44]);
diff --git a/src/test/regress/expected/rangetypes.out b/src/test/regress/expected/rangetypes.out
index c421f5394fe..6a1bbadc91a 100644
--- a/src/test/regress/expected/rangetypes.out
+++ b/src/test/regress/expected/rangetypes.out
@@ -518,6 +518,25 @@ select numrange(1.0, 2.0) * numrange(2.5, 3.0);
  empty
 (1 row)
 
+select range_intersect_agg(nr) from numrange_test;
+ range_intersect_agg 
+---------------------
+ empty
+(1 row)
+
+select range_intersect_agg(nr) from numrange_test where false;
+ range_intersect_agg 
+---------------------
+ 
+(1 row)
+
+select range_intersect_agg(nr) from numrange_test where nr @> 4.0;
+ range_intersect_agg 
+---------------------
+ [3,5)
+(1 row)
+
+analyze numrange_test;
 create table numrange_test2(nr numrange);
 create index numrange_test2_hash_idx on numrange_test2 using hash (nr);
 INSERT INTO numrange_test2 VALUES('[, 5)');
@@ -1371,12 +1390,12 @@ drop function anyarray_anyrange_func(anyarray, anyrange);
 create function bogus_func(anyelement)
   returns anyrange as 'select int4range(1,10)' language sql;
 ERROR:  cannot determine result data type
-DETAIL:  A result of type anyrange requires at least one input of type anyrange.
+DETAIL:  A result of type anyrange requires at least one input of type anyrange or anymultirange.
 -- should fail
 create function bogus_func(int)
   returns anyrange as 'select int4range(1,10)' language sql;
 ERROR:  cannot determine result data type
-DETAIL:  A result of type anyrange requires at least one input of type anyrange.
+DETAIL:  A result of type anyrange requires at least one input of type anyrange or anymultirange.
 create function range_add_bounds(anyrange)
   returns anyelement as 'select lower($1) + upper($1)' language sql;
 select range_add_bounds(int4range(1, 17));
@@ -1430,7 +1449,7 @@ drop function anycompatiblearray_anycompatiblerange_func(anycompatiblearray, any
 create function bogus_func(anycompatible)
   returns anycompatiblerange as 'select int4range(1,10)' language sql;
 ERROR:  cannot determine result data type
-DETAIL:  A result of type anycompatiblerange requires at least one input of type anycompatiblerange.
+DETAIL:  A result of type anycompatiblerange requires at least one input of type anycompatiblerange or anycompatiblemultirange.
 --
 -- Arrays of ranges
 --
@@ -1508,6 +1527,7 @@ reset enable_sort;
 --
 -- OUT/INOUT/TABLE functions
 --
+-- infer anyrange from anyrange
 create function outparam_succeed(i anyrange, out r anyrange, out t text)
   as $$ select $1, 'foo'::text $$ language sql;
 select * from outparam_succeed(int4range(1,2));
@@ -1525,6 +1545,16 @@ select * from outparam2_succeed(int4range(1,11));
  {1,11} | {11,1}
 (1 row)
 
+-- infer anyarray from anyrange
+create function outparam_succeed2(i anyrange, out r anyarray, out t text)
+  as $$ select ARRAY[upper($1)], 'foo'::text $$ language sql;
+select * from outparam_succeed2(int4range(int4range(1,2)));
+  r  |  t  
+-----+-----
+ {2} | foo
+(1 row)
+
+-- infer anyelement from anyrange
 create function inoutparam_succeed(out i anyelement, inout r anyrange)
   as $$ select upper($1), $1 $$ language sql;
 select * from inoutparam_succeed(int4range(1,2));
@@ -1547,14 +1577,14 @@ select * from table_succeed(int4range(1,11));
 create function outparam_fail(i anyelement, out r anyrange, out t text)
   as $$ select '[1,10]', 'foo' $$ language sql;
 ERROR:  cannot determine result data type
-DETAIL:  A result of type anyrange requires at least one input of type anyrange.
+DETAIL:  A result of type anyrange requires at least one input of type anyrange or anymultirange.
 --should fail
 create function inoutparam_fail(inout i anyelement, out r anyrange)
   as $$ select $1, '[1,10]' $$ language sql;
 ERROR:  cannot determine result data type
-DETAIL:  A result of type anyrange requires at least one input of type anyrange.
+DETAIL:  A result of type anyrange requires at least one input of type anyrange or anymultirange.
 --should fail
 create function table_fail(i anyelement) returns table(i anyelement, r anyrange)
   as $$ select $1, '[1,10]' $$ language sql;
 ERROR:  cannot determine result data type
-DETAIL:  A result of type anyrange requires at least one input of type anyrange.
+DETAIL:  A result of type anyrange requires at least one input of type anyrange or anymultirange.
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index 192445878d2..d9ce961be2b 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -92,6 +92,7 @@ num_exp_sqrt|t
 num_exp_sub|t
 num_input_test|f
 num_result|f
+nummultirange_test|t
 numrange_test|t
 onek|t
 onek2|t
@@ -172,6 +173,7 @@ quad_poly_tbl|t
 radix_text_tbl|t
 ramp|f
 real_city|f
+reservations|f
 road|t
 shighway|t
 slow_emp4000|f
diff --git a/src/test/regress/expected/type_sanity.out b/src/test/regress/expected/type_sanity.out
index 274130e7062..8df1ae47e48 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'));
@@ -195,18 +195,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
@@ -240,17 +241,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
@@ -318,18 +320,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
@@ -363,17 +366,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
@@ -631,3 +635,11 @@ WHERE pronargs != 2
 ----------+------------+---------
 (0 rows)
 
+-- every range should have a valid multirange
+SELECT p1.rngtypid, p1.rngsubtype, p1.rngmultitypid
+FROM pg_range p1
+WHERE p1.rngmultitypid IS NULL OR p1.rngmultitypid = 0;
+ rngtypid | rngsubtype | rngmultitypid 
+----------+------------+---------------
+(0 rows)
+
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index ae89ed7f0b4..c91b38884ff 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -19,6 +19,7 @@ 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 xid
 
@@ -27,7 +28,7 @@ test: strings numerology point lseg line box path polygon circle date time timet
 # geometry depends on point, lseg, box, path, polygon and circle
 # horology depends on interval, timetz, timestamp, timestamptz
 # ----------
-test: geometry horology regex oidjoins type_sanity opr_sanity misc_sanity comments expressions unicode
+test: geometry horology regex oidjoins type_sanity opr_sanity misc_sanity comments expressions unicode multirangetypes
 
 # ----------
 # These four each depend on the previous one
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index 525bdc804f6..081fce32e75 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -20,6 +20,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 681cc92ac20..0292dedd15a 100644
--- a/src/test/regress/sql/hash_func.sql
+++ b/src/test/regress/sql/hash_func.sql
@@ -227,6 +227,16 @@ FROM   (VALUES (int4range(10, 20)), (int4range(23, 43)),
 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);
+
 CREATE TYPE hash_test_t1 AS (a int, b text);
 SELECT v as value, hash_record(v)::bit(32) as standard,
        hash_record_extended(v, 0)::bit(32) as extended0,
diff --git a/src/test/regress/sql/multirangetypes.sql b/src/test/regress/sql/multirangetypes.sql
new file mode 100644
index 00000000000..2a6b4a0a291
--- /dev/null
+++ b/src/test/regress/sql/multirangetypes.sql
@@ -0,0 +1,658 @@
+-- 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'));
+
+--
+-- test casts, both a built-in range type and a user-defined one:
+--
+select 'empty'::int4range::int4multirange;
+select int4range(1, 3)::int4multirange;
+select int4range(1, null)::int4multirange;
+select int4range(null, null)::int4multirange;
+select 'empty'::textrange::textmultirange;
+select textrange('a', 'c')::textmultirange;
+select textrange('a', null)::textmultirange;
+select textrange(null, null)::textmultirange;
+
+--
+-- create some test data and test the operators
+--
+
+CREATE TABLE nummultirange_test (nmr NUMMULTIRANGE);
+CREATE INDEX nummultirange_test_btree ON nummultirange_test(nmr);
+
+INSERT INTO nummultirange_test VALUES('{}');
+INSERT INTO nummultirange_test VALUES('{[,)}');
+INSERT INTO nummultirange_test VALUES('{[3,]}');
+INSERT INTO nummultirange_test VALUES('{[,), [3,]}');
+INSERT INTO nummultirange_test VALUES('{[, 5)}');
+INSERT INTO nummultirange_test VALUES(nummultirange());
+INSERT INTO nummultirange_test VALUES(nummultirange(variadic '{}'::numrange[]));
+INSERT INTO nummultirange_test VALUES(nummultirange(numrange(1.1, 2.2)));
+INSERT INTO nummultirange_test VALUES('{empty}');
+INSERT INTO nummultirange_test VALUES(nummultirange(numrange(1.7, 1.7, '[]'), numrange(1.7, 1.9)));
+INSERT INTO nummultirange_test VALUES(nummultirange(numrange(1.7, 1.7, '[]'), numrange(1.9, 2.1)));
+
+SELECT nmr, isempty(nmr), lower(nmr), upper(nmr) FROM nummultirange_test ORDER BY nmr;
+SELECT nmr, lower_inc(nmr), lower_inf(nmr), upper_inc(nmr), upper_inf(nmr) FROM nummultirange_test ORDER BY nmr;
+
+SELECT * FROM nummultirange_test WHERE nmr = '{}';
+SELECT * FROM nummultirange_test WHERE nmr = '{(,5)}';
+SELECT * FROM nummultirange_test WHERE nmr = '{[3,)}';
+SELECT * FROM nummultirange_test WHERE nmr = '{[1.7,1.7]}';
+SELECT * FROM nummultirange_test WHERE nmr = '{[1.7,1.7],[1.9,2.1)}';
+SELECT * FROM nummultirange_test WHERE nmr < '{}';
+SELECT * FROM nummultirange_test WHERE nmr < '{[-1000.0, -1000.0]}';
+SELECT * FROM nummultirange_test WHERE nmr < '{[0.0, 1.0]}';
+SELECT * FROM nummultirange_test WHERE nmr < '{[1000.0, 1001.0]}';
+SELECT * FROM nummultirange_test WHERE nmr <= '{}';
+SELECT * FROM nummultirange_test WHERE nmr <= '{[3,)}';
+SELECT * FROM nummultirange_test WHERE nmr >= '{}';
+SELECT * FROM nummultirange_test WHERE nmr >= '{[3,)}';
+SELECT * FROM nummultirange_test WHERE nmr > '{}';
+SELECT * FROM nummultirange_test WHERE nmr > '{[-1000.0, -1000.0]}';
+SELECT * FROM nummultirange_test WHERE nmr > '{[0.0, 1.0]}';
+SELECT * FROM nummultirange_test WHERE nmr > '{[1000.0, 1001.0]}';
+SELECT * FROM nummultirange_test WHERE nmr <> '{}';
+SELECT * FROM nummultirange_test WHERE nmr <> '{(,5)}';
+
+select nummultirange(numrange(2.0, 1.0));
+select nummultirange(numrange(5.0, 6.0), numrange(1.0, 2.0));
+
+analyze nummultirange_test;
+
+-- overlaps
+SELECT * FROM nummultirange_test WHERE range_overlaps_multirange(numrange(4.0, 4.2), nmr);
+SELECT * FROM nummultirange_test WHERE numrange(4.0, 4.2) && nmr;
+SELECT * FROM nummultirange_test WHERE multirange_overlaps_range(nmr, numrange(4.0, 4.2));
+SELECT * FROM nummultirange_test WHERE nmr && numrange(4.0, 4.2);
+SELECT * FROM nummultirange_test WHERE multirange_overlaps_multirange(nmr, nummultirange(numrange(4.0, 4.2), numrange(6.0, 7.0)));
+SELECT * FROM nummultirange_test WHERE nmr && nummultirange(numrange(4.0, 4.2), numrange(6.0, 7.0));
+SELECT * FROM nummultirange_test WHERE nmr && nummultirange(numrange(6.0, 7.0));
+SELECT * FROM nummultirange_test WHERE nmr && nummultirange(numrange(6.0, 7.0), numrange(8.0, 9.0));
+
+-- mr contains x
+SELECT * FROM nummultirange_test WHERE multirange_contains_elem(nmr, 4.0);
+SELECT * FROM nummultirange_test WHERE nmr @> 4.0;
+SELECT * FROM nummultirange_test WHERE multirange_contains_range(nmr, numrange(4.0, 4.2));
+SELECT * FROM nummultirange_test WHERE nmr @> numrange(4.0, 4.2);
+SELECT * FROM nummultirange_test WHERE multirange_contains_multirange(nmr, '{[4.0,4.2), [6.0, 8.0)}');
+SELECT * FROM nummultirange_test WHERE nmr @> '{[4.0,4.2), [6.0, 8.0)}'::nummultirange;
+
+-- x is contained by mr
+SELECT * FROM nummultirange_test WHERE elem_contained_by_multirange(4.0, nmr);
+SELECT * FROM nummultirange_test WHERE 4.0 <@ nmr;
+SELECT * FROM nummultirange_test WHERE range_contained_by_multirange(numrange(4.0, 4.2), nmr);
+SELECT * FROM nummultirange_test WHERE numrange(4.0, 4.2) <@ nmr;
+SELECT * FROM nummultirange_test WHERE multirange_contained_by_multirange('{[4.0,4.2), [6.0, 8.0)}', nmr);
+SELECT * FROM nummultirange_test WHERE '{[4.0,4.2), [6.0, 8.0)}'::nummultirange <@ nmr;
+
+-- overlaps
+SELECT 'empty'::numrange && nummultirange();
+SELECT 'empty'::numrange && nummultirange(numrange(1,2));
+SELECT nummultirange() && 'empty'::numrange;
+SELECT nummultirange(numrange(1,2)) && 'empty'::numrange;
+SELECT nummultirange() && nummultirange();
+SELECT nummultirange() && nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) && nummultirange();
+SELECT nummultirange(numrange(3,4)) && nummultirange(numrange(1,2), numrange(7,8));
+SELECT nummultirange(numrange(1,2), numrange(7,8)) && nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(3,4)) && nummultirange(numrange(1,2), numrange(3.5,8));
+SELECT nummultirange(numrange(1,2), numrange(3.5,8)) && numrange(3,4);
+SELECT nummultirange(numrange(1,2), numrange(3.5,8)) && nummultirange(numrange(3,4));
+
+-- contains
+SELECT nummultirange() @> nummultirange();
+SELECT nummultirange() @> 'empty'::numrange;
+SELECT nummultirange(numrange(null,null)) @> numrange(1,2);
+SELECT nummultirange(numrange(null,null)) @> numrange(null,2);
+SELECT nummultirange(numrange(null,null)) @> numrange(2,null);
+SELECT nummultirange(numrange(null,5)) @> numrange(null,3);
+SELECT nummultirange(numrange(null,5)) @> numrange(null,8);
+SELECT nummultirange(numrange(5,null)) @> numrange(8,null);
+SELECT nummultirange(numrange(5,null)) @> numrange(3,null);
+SELECT nummultirange(numrange(1,5)) @> numrange(8,9);
+SELECT nummultirange(numrange(1,5)) @> numrange(3,9);
+SELECT nummultirange(numrange(1,5)) @> numrange(1,4);
+SELECT nummultirange(numrange(1,5)) @> numrange(1,5);
+SELECT nummultirange(numrange(-4,-2), numrange(1,5)) @> numrange(1,5);
+SELECT nummultirange(numrange(1,5), numrange(8,9)) @> numrange(1,5);
+SELECT nummultirange(numrange(1,5), numrange(8,9)) @> numrange(6,7);
+SELECT nummultirange(numrange(1,5), numrange(6,9)) @> numrange(6,7);
+SELECT '{[1,5)}'::nummultirange @> '{[1,5)}';
+SELECT '{[-4,-2), [1,5)}'::nummultirange @> '{[1,5)}';
+SELECT '{[1,5), [8,9)}'::nummultirange @> '{[1,5)}';
+SELECT '{[1,5), [8,9)}'::nummultirange @> '{[6,7)}';
+SELECT '{[1,5), [6,9)}'::nummultirange @> '{[6,7)}';
+
+-- is contained by
+SELECT nummultirange() <@ nummultirange();
+SELECT 'empty'::numrange <@ nummultirange();
+SELECT numrange(1,2) <@ nummultirange(numrange(null,null));
+SELECT numrange(null,2) <@ nummultirange(numrange(null,null));
+SELECT numrange(2,null) <@ nummultirange(numrange(null,null));
+SELECT numrange(null,3) <@ nummultirange(numrange(null,5));
+SELECT numrange(null,8) <@ nummultirange(numrange(null,5));
+SELECT numrange(8,null) <@ nummultirange(numrange(5,null));
+SELECT numrange(3,null) <@ nummultirange(numrange(5,null));
+SELECT numrange(8,9) <@ nummultirange(numrange(1,5));
+SELECT numrange(3,9) <@ nummultirange(numrange(1,5));
+SELECT numrange(1,4) <@ nummultirange(numrange(1,5));
+SELECT numrange(1,5) <@ nummultirange(numrange(1,5));
+SELECT numrange(1,5) <@ nummultirange(numrange(-4,-2), numrange(1,5));
+SELECT numrange(1,5) <@ nummultirange(numrange(1,5), numrange(8,9));
+SELECT numrange(6,7) <@ nummultirange(numrange(1,5), numrange(8,9));
+SELECT numrange(6,7) <@ nummultirange(numrange(1,5), numrange(6,9));
+SELECT '{[1,5)}' <@ '{[1,5)}'::nummultirange;
+SELECT '{[1,5)}' <@ '{[-4,-2), [1,5)}'::nummultirange;
+SELECT '{[1,5)}' <@ '{[1,5), [8,9)}'::nummultirange;
+SELECT '{[6,7)}' <@ '{[1,5), [8,9)}'::nummultirange;
+SELECT '{[6,7)}' <@ '{[1,5), [6,9)}'::nummultirange;
+
+-- overleft
+SELECT 'empty'::numrange &< nummultirange();
+SELECT 'empty'::numrange &< nummultirange(numrange(1,2));
+SELECT nummultirange() &< 'empty'::numrange;
+SELECT nummultirange(numrange(1,2)) &< 'empty'::numrange;
+SELECT nummultirange() &< nummultirange();
+SELECT nummultirange(numrange(1,2)) &< nummultirange();
+SELECT nummultirange() &< nummultirange(numrange(1,2));
+SELECT numrange(6,7) &< nummultirange(numrange(3,4));
+SELECT numrange(1,2) &< nummultirange(numrange(3,4));
+SELECT numrange(1,4) &< nummultirange(numrange(3,4));
+SELECT numrange(1,6) &< nummultirange(numrange(3,4));
+SELECT numrange(3.5,6) &< nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(6,7)) &< numrange(3,4);
+SELECT nummultirange(numrange(1,2)) &< numrange(3,4);
+SELECT nummultirange(numrange(1,4)) &< numrange(3,4);
+SELECT nummultirange(numrange(1,6)) &< numrange(3,4);
+SELECT nummultirange(numrange(3.5,6)) &< numrange(3,4);
+SELECT nummultirange(numrange(6,7)) &< nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,2)) &< nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,4)) &< nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,6)) &< nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(3.5,6)) &< nummultirange(numrange(3,4));
+
+-- overright
+SELECT nummultirange() &> 'empty'::numrange;
+SELECT nummultirange(numrange(1,2)) &> 'empty'::numrange;
+SELECT 'empty'::numrange &> nummultirange();
+SELECT 'empty'::numrange &> nummultirange(numrange(1,2));
+SELECT nummultirange() &> nummultirange();
+SELECT nummultirange() &> nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) &> nummultirange();
+SELECT nummultirange(numrange(3,4)) &> numrange(6,7);
+SELECT nummultirange(numrange(3,4)) &> numrange(1,2);
+SELECT nummultirange(numrange(3,4)) &> numrange(1,4);
+SELECT nummultirange(numrange(3,4)) &> numrange(1,6);
+SELECT nummultirange(numrange(3,4)) &> numrange(3.5,6);
+SELECT numrange(3,4) &> nummultirange(numrange(6,7));
+SELECT numrange(3,4) &> nummultirange(numrange(1,2));
+SELECT numrange(3,4) &> nummultirange(numrange(1,4));
+SELECT numrange(3,4) &> nummultirange(numrange(1,6));
+SELECT numrange(3,4) &> nummultirange(numrange(3.5,6));
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(6,7));
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(1,4));
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(1,6));
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(3.5,6));
+
+-- meets
+SELECT 'empty'::numrange -|- nummultirange();
+SELECT 'empty'::numrange -|- nummultirange(numrange(1,2));
+SELECT nummultirange() -|- 'empty'::numrange;
+SELECT nummultirange(numrange(1,2)) -|- 'empty'::numrange;
+SELECT nummultirange() -|- nummultirange();
+SELECT nummultirange(numrange(1,2)) -|- nummultirange();
+SELECT nummultirange() -|- nummultirange(numrange(1,2));
+SELECT numrange(1,2) -|- nummultirange(numrange(2,4));
+SELECT numrange(1,2) -|- nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,2)) -|- numrange(2,4);
+SELECT nummultirange(numrange(1,2)) -|- numrange(3,4);
+SELECT nummultirange(numrange(1,2)) -|- nummultirange(numrange(2,4));
+SELECT nummultirange(numrange(1,2)) -|- nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,2), numrange(5,6)) -|- nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,2), numrange(5,6)) -|- nummultirange(numrange(6,7));
+SELECT nummultirange(numrange(1,2), numrange(5,6)) -|- nummultirange(numrange(8,9));
+SELECT nummultirange(numrange(1,2)) -|- nummultirange(numrange(2,4), numrange(6,7));
+
+-- strictly left
+select 'empty'::numrange << nummultirange();
+select numrange(1,2) << nummultirange();
+select numrange(1,2) << nummultirange(numrange(3,4));
+select numrange(1,2) << nummultirange(numrange(0,4));
+select numrange(1,2) << nummultirange(numrange(0,4), numrange(7,8));
+select nummultirange() << 'empty'::numrange;
+select nummultirange() << numrange(1,2);
+select nummultirange(numrange(3,4)) << numrange(3,6);
+select nummultirange(numrange(0,2)) << numrange(3,6);
+select nummultirange(numrange(0,2), numrange(7,8)) << numrange(3,6);
+select nummultirange(numrange(-4,-2), numrange(0,2)) << numrange(3,6);
+select nummultirange() << nummultirange();
+select nummultirange() << nummultirange(numrange(1,2));
+select nummultirange(numrange(1,2)) << nummultirange();
+select nummultirange(numrange(1,2)) << nummultirange(numrange(1,2));
+select nummultirange(numrange(1,2)) << nummultirange(numrange(3,4));
+select nummultirange(numrange(1,2)) << nummultirange(numrange(3,4), numrange(7,8));
+select nummultirange(numrange(1,2), numrange(4,5)) << nummultirange(numrange(3,4), numrange(7,8));
+
+-- strictly right
+select nummultirange() >> 'empty'::numrange;
+select nummultirange() >> numrange(1,2);
+select nummultirange(numrange(3,4)) >> numrange(1,2);
+select nummultirange(numrange(0,4)) >> numrange(1,2);
+select nummultirange(numrange(0,4), numrange(7,8)) >> numrange(1,2);
+select 'empty'::numrange >> nummultirange();
+select numrange(1,2) >> nummultirange();
+select numrange(3,6) >> nummultirange(numrange(3,4));
+select numrange(3,6) >> nummultirange(numrange(0,2));
+select numrange(3,6) >> nummultirange(numrange(0,2), numrange(7,8));
+select numrange(3,6) >> nummultirange(numrange(-4,-2), numrange(0,2));
+select nummultirange() >> nummultirange();
+select nummultirange(numrange(1,2)) >> nummultirange();
+select nummultirange() >> nummultirange(numrange(1,2));
+select nummultirange(numrange(1,2)) >> nummultirange(numrange(1,2));
+select nummultirange(numrange(3,4)) >> nummultirange(numrange(1,2));
+select nummultirange(numrange(3,4), numrange(7,8)) >> nummultirange(numrange(1,2));
+select nummultirange(numrange(3,4), numrange(7,8)) >> nummultirange(numrange(1,2), numrange(4,5));
+
+-- union
+SELECT nummultirange() + nummultirange();
+SELECT nummultirange() + nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) + nummultirange();
+SELECT nummultirange(numrange(1,2)) + nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) + nummultirange(numrange(2,4));
+SELECT nummultirange(numrange(1,2)) + nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) + nummultirange(numrange(2,4));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) + nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) + nummultirange(numrange(0,9));
+
+-- merge
+SELECT range_merge(nummultirange());
+SELECT range_merge(nummultirange(numrange(1,2)));
+SELECT range_merge(nummultirange(numrange(1,2), numrange(7,8)));
+
+-- minus
+SELECT nummultirange() - nummultirange();
+SELECT nummultirange() - nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) - nummultirange();
+SELECT nummultirange(numrange(1,2), numrange(3,4)) - nummultirange();
+SELECT nummultirange(numrange(1,2)) - nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) - nummultirange(numrange(2,4));
+SELECT nummultirange(numrange(1,2)) - nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(2,3));
+SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(0,8));
+SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(0,2));
+SELECT nummultirange(numrange(1,8)) - nummultirange(numrange(0,2), numrange(3,4));
+SELECT nummultirange(numrange(1,8)) - nummultirange(numrange(2,3), numrange(5,null));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(-2,0));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(2,4));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(3,5));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(0,9));
+SELECT nummultirange(numrange(1,3), numrange(4,5)) - nummultirange(numrange(2,9));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(8,9));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(-2,0), numrange(8,9));
+
+-- intersection
+SELECT nummultirange() * nummultirange();
+SELECT nummultirange() * nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) * nummultirange();
+SELECT '{[1,3)}'::nummultirange * '{[1,5)}'::nummultirange;
+SELECT '{[1,3)}'::nummultirange * '{[0,5)}'::nummultirange;
+SELECT '{[1,3)}'::nummultirange * '{[0,2)}'::nummultirange;
+SELECT '{[1,3)}'::nummultirange * '{[2,5)}'::nummultirange;
+SELECT '{[1,4)}'::nummultirange * '{[2,3)}'::nummultirange;
+SELECT '{[1,4)}'::nummultirange * '{[0,2), [3,5)}'::nummultirange;
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[0,8), [9,12)}'::nummultirange;
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[9,12)}'::nummultirange;
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[-5,-4), [5,6), [9,12)}'::nummultirange;
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[0,2), [3,8), [9,12)}'::nummultirange;
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[0,2), [3,8), [9,12)}'::nummultirange;
+
+--
+-- range_agg function
+--
+create table reservations ( room_id integer not null, booked_during daterange );
+insert into reservations values
+-- 1: has a meets and a gap
+(1, daterange('2018-07-01', '2018-07-07')),
+(1, daterange('2018-07-07', '2018-07-14')),
+(1, daterange('2018-07-20', '2018-07-22')),
+-- 2: just a single row
+(2, daterange('2018-07-01', '2018-07-03')),
+-- 3: one null range
+(3, NULL),
+-- 4: two null ranges
+(4, NULL),
+(4, NULL),
+-- 5: a null range and a non-null range
+(5, NULL),
+(5, daterange('2018-07-01', '2018-07-03')),
+-- 6: has overlap
+(6, daterange('2018-07-01', '2018-07-07')),
+(6, daterange('2018-07-05', '2018-07-10')),
+-- 7: two ranges that meet: no gap or overlap
+(7, daterange('2018-07-01', '2018-07-07')),
+(7, daterange('2018-07-07', '2018-07-14')),
+-- 8: an empty range
+(8, 'empty'::daterange)
+;
+SELECT   room_id, range_agg(booked_during)
+FROM     reservations
+GROUP BY room_id
+ORDER BY room_id;
+
+-- range_agg on a custom range type too
+SELECT  range_agg(r)
+FROM    (VALUES
+          ('[a,c]'::textrange),
+          ('[b,b]'::textrange),
+          ('[c,f]'::textrange),
+          ('[g,h)'::textrange),
+          ('[h,j)'::textrange)
+        ) t(r);
+
+select range_intersect_agg(nmr) from nummultirange_test;
+select range_intersect_agg(nmr) from nummultirange_test where false;
+-- test with just one input:
+select range_intersect_agg(nmr) from (values ('{[1,2]}'::nummultirange)) t(nmr);
+select range_intersect_agg(nmr) from nummultirange_test where nmr @> 4.0;
+
+create table nummultirange_test2(nmr nummultirange);
+create index nummultirange_test2_hash_idx on nummultirange_test2 using hash (nmr);
+
+INSERT INTO nummultirange_test2 VALUES('{[, 5)}');
+INSERT INTO nummultirange_test2 VALUES(nummultirange(numrange(1.1, 2.2)));
+INSERT INTO nummultirange_test2 VALUES(nummultirange(numrange(1.1, 2.2)));
+INSERT INTO nummultirange_test2 VALUES(nummultirange(numrange(1.1, 2.2,'()')));
+INSERT INTO nummultirange_test2 VALUES('{}');
+
+select * from nummultirange_test2 where nmr = '{}';
+select * from nummultirange_test2 where nmr = nummultirange(numrange(1.1, 2.2));
+select * from nummultirange_test2 where nmr = nummultirange(numrange(1.1, 2.3));
+
+set enable_nestloop=t;
+set enable_hashjoin=f;
+set enable_mergejoin=f;
+select * from nummultirange_test natural join nummultirange_test2 order by nmr;
+set enable_nestloop=f;
+set enable_hashjoin=t;
+set enable_mergejoin=f;
+select * from nummultirange_test natural join nummultirange_test2 order by nmr;
+set enable_nestloop=f;
+set enable_hashjoin=f;
+set enable_mergejoin=t;
+select * from nummultirange_test natural join nummultirange_test2 order by nmr;
+
+set enable_nestloop to default;
+set enable_hashjoin to default;
+set enable_mergejoin to default;
+
+DROP TABLE nummultirange_test2;
+
+--
+-- Test user-defined multirange of floats
+--
+
+select '{[123.001, 5.e9)}'::float8multirange @> 888.882::float8;
+create table float8multirange_test(f8mr float8multirange, i int);
+insert into float8multirange_test values(float8multirange(float8range(-100.00007, '1.111113e9')), 42);
+select * from float8multirange_test;
+drop table float8multirange_test;
+
+--
+-- Test multirange types over domains
+--
+
+create domain mydomain as int4;
+create type mydomainrange as range(subtype=mydomain);
+select '{[4,50)}'::mydomainmultirange @> 7::mydomain;
+drop domain mydomain cascade;
+
+--
+-- Test domains over multirange types
+--
+
+create domain restrictedmultirange as int4multirange check (upper(value) < 10);
+select '{[4,5)}'::restrictedmultirange @> 7;
+select '{[4,50)}'::restrictedmultirange @> 7; -- should fail
+drop domain restrictedmultirange;
+
+--
+-- Test multiple multirange types over the same subtype
+--
+
+create type textrange1 as range(subtype=text, collation="C");
+create type textrange2 as range(subtype=text, collation="C");
+
+select textmultirange1(textrange2('a','Z'));  -- should fail
+select textmultirange1(textrange1('a','Z')) @> 'b'::text;
+select textmultirange2(textrange2('a','z')) @> 'b'::text;
+
+drop type textrange1;
+drop type textrange2;
+
+--
+-- Test polymorphic type system
+--
+
+create function anyarray_anymultirange_func(a anyarray, r anymultirange)
+  returns anyelement as 'select $1[1] + lower($2);' language sql;
+
+select anyarray_anymultirange_func(ARRAY[1,2], int4multirange(int4range(10,20)));
+
+-- should fail
+select anyarray_anymultirange_func(ARRAY[1,2], nummultirange(numrange(10,20)));
+
+drop function anyarray_anymultirange_func(anyarray, anymultirange);
+
+-- should fail
+create function bogus_func(anyelement)
+  returns anymultirange as 'select int4multirange(int4range(1,10))' language sql;
+
+-- should fail
+create function bogus_func(int)
+  returns anymultirange as 'select int4multirange(int4range(1,10))' language sql;
+
+create function range_add_bounds(anymultirange)
+  returns anyelement as 'select lower($1) + upper($1)' language sql;
+
+select range_add_bounds(int4multirange(int4range(1, 17)));
+select range_add_bounds(nummultirange(numrange(1.0001, 123.123)));
+
+create function multirangetypes_sql(q anymultirange, b anyarray, out c anyelement)
+  as $$ select upper($1) + $2[1] $$
+  language sql;
+
+select multirangetypes_sql(int4multirange(int4range(1,10)), ARRAY[2,20]);
+select multirangetypes_sql(nummultirange(numrange(1,10)), ARRAY[2,20]);  -- match failure
+
+create function anycompatiblearray_anycompatiblemultirange_func(a anycompatiblearray, mr anycompatiblemultirange)
+  returns anycompatible as 'select $1[1] + lower($2);' language sql;
+
+select anycompatiblearray_anycompatiblemultirange_func(ARRAY[1,2], multirange(int4range(10,20)));
+
+select anycompatiblearray_anycompatiblemultirange_func(ARRAY[1,2], multirange(numrange(10,20)));
+
+-- should fail
+select anycompatiblearray_anycompatiblemultirange_func(ARRAY[1.1,2], multirange(int4range(10,20)));
+
+drop function anycompatiblearray_anycompatiblemultirange_func(anycompatiblearray, anycompatiblemultirange);
+
+create function anycompatiblerange_anycompatiblemultirange_func(r anycompatiblerange, mr anycompatiblemultirange)
+  returns anycompatible as 'select lower($1) + lower($2);' language sql;
+
+select anycompatiblerange_anycompatiblemultirange_func(int4range(1,2), multirange(int4range(10,20)));
+
+-- should fail
+select anycompatiblerange_anycompatiblemultirange_func(numrange(1,2), multirange(int4range(10,20)));
+
+drop function anycompatiblerange_anycompatiblemultirange_func(anycompatiblerange, anycompatiblemultirange);
+
+-- should fail
+create function bogus_func(anycompatible)
+  returns anycompatiblerange as 'select int4range(1,10)' language sql;
+
+--
+-- Arrays of multiranges
+--
+
+select ARRAY[nummultirange(numrange(1.1, 1.2)), nummultirange(numrange(12.3, 155.5))];
+
+create table i8mr_array (f1 int, f2 int8multirange[]);
+insert into i8mr_array values (42, array[int8multirange(int8range(1,10)), int8multirange(int8range(2,20))]);
+select * from i8mr_array;
+drop table i8mr_array;
+
+--
+-- Multiranges of arrays
+--
+
+select arraymultirange(arrayrange(ARRAY[1,2], ARRAY[2,1]));
+select arraymultirange(arrayrange(ARRAY[2,1], ARRAY[1,2]));  -- fail
+
+select array[1,1] <@ arraymultirange(arrayrange(array[1,2], array[2,1]));
+select array[1,3] <@ arraymultirange(arrayrange(array[1,2], array[2,1]));
+
+--
+-- Ranges of composites
+--
+
+create type two_ints as (a int, b int);
+create type two_ints_range as range (subtype = two_ints);
+
+-- with force_parallel_mode on, this exercises tqueue.c's range remapping
+select *, row_to_json(upper(t)) as u from
+  (values (two_ints_multirange(two_ints_range(row(1,2), row(3,4)))),
+          (two_ints_multirange(two_ints_range(row(5,6), row(7,8))))) v(t);
+
+drop type two_ints cascade;
+
+--
+-- Check behavior when subtype lacks a hash function
+--
+
+set enable_sort = off;  -- try to make it pick a hash setop implementation
+
+select '{(2,5)}'::cashmultirange except select '{(5,6)}'::cashmultirange;
+
+reset enable_sort;
+
+--
+-- OUT/INOUT/TABLE functions
+--
+
+-- infer anymultirange from anymultirange
+create function mr_outparam_succeed(i anymultirange, out r anymultirange, out t text)
+  as $$ select $1, 'foo'::text $$ language sql;
+
+select * from mr_outparam_succeed(int4multirange(int4range(1,2)));
+
+-- infer anyarray from anymultirange
+create function mr_outparam_succeed2(i anymultirange, out r anyarray, out t text)
+  as $$ select ARRAY[upper($1)], 'foo'::text $$ language sql;
+
+select * from mr_outparam_succeed2(int4multirange(int4range(1,2)));
+
+-- infer anyrange from anymultirange
+create function mr_outparam_succeed3(i anymultirange, out r anyrange, out t text)
+  as $$ select range_merge($1), 'foo'::text $$ language sql;
+select * from mr_outparam_succeed3(int4multirange(int4range(1,2)));
+
+-- infer anymultirange from anyrange
+create function mr_outparam_succeed4(i anyrange, out r anymultirange, out t text)
+  as $$ select multirange($1), 'foo'::text $$ language sql;
+
+select * from mr_outparam_succeed4(int4range(1,2));
+
+-- infer anyelement from anymultirange
+create function mr_inoutparam_succeed(out i anyelement, inout r anymultirange)
+  as $$ select upper($1), $1 $$ language sql;
+
+select * from mr_inoutparam_succeed(int4multirange(int4range(1,2)));
+
+-- infer anyelement+anymultirange from anyelement+anymultirange
+create function mr_table_succeed(i anyelement, r anymultirange) returns table(i anyelement, r anymultirange)
+  as $$ select $1, $2 $$ language sql;
+
+select * from mr_table_succeed(123, int4multirange(int4range(1,11)));
+
+-- use anymultirange in plpgsql
+create function mr_polymorphic(i anyrange) returns anymultirange
+  as $$ begin return multirange($1); end; $$ language plpgsql;
+select mr_polymorphic(int4range(1, 4));
+
+-- should fail
+create function mr_outparam_fail(i anyelement, out r anymultirange, out t text)
+  as $$ select '[1,10]', 'foo' $$ language sql;
+
+--should fail
+create function mr_inoutparam_fail(inout i anyelement, out r anymultirange)
+  as $$ select $1, '[1,10]' $$ language sql;
+
+--should fail
+create function mr_table_fail(i anyelement) returns table(i anyelement, r anymultirange)
+  as $$ select $1, '[1,10]' $$ language sql;
diff --git a/src/test/regress/sql/opr_sanity.sql b/src/test/regress/sql/opr_sanity.sql
index 307aab1deb7..3d6b60179bc 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::regtype, p2.prorettype::regtype
 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;
 
@@ -279,16 +290,19 @@ WHERE p1.prorettype IN
      '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;
 
--- anyrange is tighter than the rest, can only resolve from anyrange input
+-- anyrange and anymultirange are tighter than the rest, can only resolve
+-- from each other
 
 SELECT p1.oid, p1.proname
 FROM pg_proc as p1
-WHERE p1.prorettype = 'anyrange'::regtype
+WHERE p1.prorettype IN ('anyrange'::regtype, 'anymultirange'::regtype)
   AND NOT
-     'anyrange'::regtype = ANY (p1.proargtypes)
+    ('anyrange'::regtype = ANY (p1.proargtypes) OR
+      'anymultirange'::regtype = ANY (p1.proargtypes))
 ORDER BY 2;
 
 -- similarly for the anycompatible family
diff --git a/src/test/regress/sql/polymorphism.sql b/src/test/regress/sql/polymorphism.sql
index ff517fea41a..891b023ada0 100644
--- a/src/test/regress/sql/polymorphism.sql
+++ b/src/test/regress/sql/polymorphism.sql
@@ -71,6 +71,16 @@ select polyf(int4range(42, 49), 11, 4.5) as fail;  -- range type doesn't fit
 
 drop function polyf(x anycompatiblerange, y anycompatible, z anycompatible);
 
+create function polyf(x anycompatiblemultirange, y anycompatible, z anycompatible) returns anycompatiblearray as $$
+  select array[lower(x), upper(x), y, z]
+$$ language sql;
+
+select polyf(multirange(int4range(42, 49)), 11, 2::smallint) as int, polyf(multirange(float8range(4.5, 7.8)), 7.8, 11::real) as num;
+
+select polyf(multirange(int4range(42, 49)), 11, 4.5) as fail;  -- range type doesn't fit
+
+drop function polyf(x anycompatiblemultirange, y anycompatible, z anycompatible);
+
 -- fail, can't infer type:
 create function polyf(x anycompatible) returns anycompatiblerange as $$
   select array[x + 1, x + 2]
@@ -84,6 +94,19 @@ select polyf(int4range(42, 49), array[11]) as int, polyf(float8range(4.5, 7.8),
 
 drop function polyf(x anycompatiblerange, y anycompatiblearray);
 
+-- fail, can't infer type:
+create function polyf(x anycompatible) returns anycompatiblemultirange as $$
+  select array[x + 1, x + 2]
+$$ language sql;
+
+create function polyf(x anycompatiblemultirange, y anycompatiblearray) returns anycompatiblemultirange as $$
+  select x
+$$ language sql;
+
+select polyf(multirange(int4range(42, 49)), array[11]) as int, polyf(multirange(float8range(4.5, 7.8)), array[7]) as num;
+
+drop function polyf(x anycompatiblemultirange, y anycompatiblearray);
+
 create function polyf(a anyelement, b anyarray,
                       c anycompatible, d anycompatible,
                       OUT x anyarray, OUT y anycompatiblearray)
@@ -997,6 +1020,35 @@ returns anycompatiblerange as $$
   select $1
 $$ language sql;
 
+create function anyctest(anycompatible, anycompatiblemultirange)
+returns anycompatiblemultirange as $$
+  select $2
+$$ language sql;
+
+select x, pg_typeof(x) from anyctest(11, multirange(int4range(4,7))) x;
+select x, pg_typeof(x) from anyctest(11, multirange(numrange(4,7))) x;
+select x, pg_typeof(x) from anyctest(11, 12) x;  -- fail
+select x, pg_typeof(x) from anyctest(11.2, multirange(int4range(4,7))) x;  -- fail
+select x, pg_typeof(x) from anyctest(11.2, '{[4,7)}') x;  -- fail
+
+drop function anyctest(anycompatible, anycompatiblemultirange);
+
+create function anyctest(anycompatiblemultirange, anycompatiblemultirange)
+returns anycompatible as $$
+  select lower($1) + upper($2)
+$$ language sql;
+
+select x, pg_typeof(x) from anyctest(multirange(int4range(11,12)), multirange(int4range(4,7))) x;
+select x, pg_typeof(x) from anyctest(multirange(int4range(11,12)), multirange(numrange(4,7))) x; -- fail
+
+drop function anyctest(anycompatiblemultirange, anycompatiblemultirange);
+
+-- fail, can't infer result type:
+create function anyctest(anycompatible)
+returns anycompatiblemultirange as $$
+  select $1
+$$ language sql;
+
 create function anyctest(anycompatiblenonarray, anycompatiblenonarray)
 returns anycompatiblearray as $$
   select array[$1, $2]
diff --git a/src/test/regress/sql/rangetypes.sql b/src/test/regress/sql/rangetypes.sql
index 4048b1d1854..b69efede3ae 100644
--- a/src/test/regress/sql/rangetypes.sql
+++ b/src/test/regress/sql/rangetypes.sql
@@ -118,6 +118,12 @@ select numrange(1.0, 2.0) * numrange(2.0, 3.0);
 select numrange(1.0, 2.0) * numrange(1.5, 3.0);
 select numrange(1.0, 2.0) * numrange(2.5, 3.0);
 
+select range_intersect_agg(nr) from numrange_test;
+select range_intersect_agg(nr) from numrange_test where false;
+select range_intersect_agg(nr) from numrange_test where nr @> 4.0;
+
+analyze numrange_test;
+
 create table numrange_test2(nr numrange);
 create index numrange_test2_hash_idx on numrange_test2 using hash (nr);
 
@@ -528,6 +534,7 @@ reset enable_sort;
 -- OUT/INOUT/TABLE functions
 --
 
+-- infer anyrange from anyrange
 create function outparam_succeed(i anyrange, out r anyrange, out t text)
   as $$ select $1, 'foo'::text $$ language sql;
 
@@ -539,6 +546,13 @@ create function outparam2_succeed(r anyrange, out lu anyarray, out ul anyarray)
 
 select * from outparam2_succeed(int4range(1,11));
 
+-- infer anyarray from anyrange
+create function outparam_succeed2(i anyrange, out r anyarray, out t text)
+  as $$ select ARRAY[upper($1)], 'foo'::text $$ language sql;
+
+select * from outparam_succeed2(int4range(int4range(1,2)));
+
+-- infer anyelement from anyrange
 create function inoutparam_succeed(out i anyelement, inout r anyrange)
   as $$ select upper($1), $1 $$ language sql;
 
diff --git a/src/test/regress/sql/type_sanity.sql b/src/test/regress/sql/type_sanity.sql
index 4b492ce0625..07879d9a574 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'));
@@ -153,7 +153,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')
@@ -182,7 +182,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')
@@ -234,7 +234,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')
@@ -263,7 +263,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')
@@ -467,3 +467,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.rngmultitypid
+FROM pg_range p1
+WHERE p1.rngmultitypid IS NULL OR p1.rngmultitypid = 0;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index b8ca8cffd91..a570651dee4 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1395,6 +1395,9 @@ MultiXactMember
 MultiXactOffset
 MultiXactStateData
 MultiXactStatus
+MultirangeIOData
+MultirangeParseState
+MultirangeType
 NDBOX
 NODE
 NUMCacheEntry
@@ -2276,6 +2279,7 @@ ShellTypeInfo
 ShippableCacheEntry
 ShippableCacheKey
 ShmemIndexEnt
+ShortRangeType
 ShutdownForeignScan_function
 ShutdownInformation
 ShutdownMode
#133Alexander Korotkov
aekorotkov@gmail.com
In reply to: Paul A Jungwirth (#132)
Re: range_agg

On Sun, Nov 29, 2020 at 8:11 PM Paul A Jungwirth
<pj@illuminatedcomputing.com> wrote:

On Fri, Nov 27, 2020 at 12:35 AM Alexander Korotkov
<aekorotkov@gmail.com> wrote:

I'd like to review this patch. Could you please rebase it once again? Thanks.

Thanks! Here is a rebased version. It also includes one more cleanup
commit from Alvaro since the last one.

Thank you. Could you please, update doc/src/sgml/catalogs.sgml,
because pg_type and pg_range catalogs are updated.

------
Regards,
Alexander Korotkov

#134Paul A Jungwirth
pj@illuminatedcomputing.com
In reply to: Alexander Korotkov (#133)
1 attachment(s)
Re: range_agg

On Sun, Nov 29, 2020 at 11:43 AM Alexander Korotkov
<aekorotkov@gmail.com> wrote:

Thank you. Could you please, update doc/src/sgml/catalogs.sgml,
because pg_type and pg_range catalogs are updated.

Attached! :-)

Paul

Attachments:

v24-multirange.patchapplication/octet-stream; name=v24-multirange.patchDownload
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 569841398b4..ae637382aec 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -6237,6 +6237,16 @@ SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</replaceable>:<replaceable>&l
       </para></entry>
      </row>
 
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>rngmultitypid</structfield> <type>oid</type>
+       (references <link linkend="catalog-pg-type"><structname>pg_type</structname></link>.<structfield>oid</structfield>)
+      </para>
+      <para>
+       OID of the multirange type for this range type
+      </para></entry>
+     </row>
+
      <row>
       <entry role="catalog_table_entry"><para role="column_definition">
        <structfield>rngcollation</structfield> <type>oid</type>
@@ -8671,8 +8681,9 @@ SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</replaceable>:<replaceable>&l
        <literal>c</literal> for a composite type (e.g., a table's row type),
        <literal>d</literal> for a domain,
        <literal>e</literal> for an enum type,
-       <literal>p</literal> for a pseudo-type, or
-       <literal>r</literal> for a range type.
+       <literal>p</literal> for a pseudo-type,
+       <literal>r</literal> for a range type, or
+       <literal>m</literal> for a multirange type.
        See also <structfield>typrelid</structfield> and
        <structfield>typbasetype</structfield>.
       </para></entry>
@@ -9087,7 +9098,7 @@ SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</replaceable>:<replaceable>&l
      </row>
      <row>
       <entry><literal>R</literal></entry>
-      <entry>Range types</entry>
+      <entry>Range and multirange types</entry>
      </row>
      <row>
       <entry><literal>S</literal></entry>
diff --git a/doc/src/sgml/datatype.sgml b/doc/src/sgml/datatype.sgml
index c2951854b5c..e4038783e95 100644
--- a/doc/src/sgml/datatype.sgml
+++ b/doc/src/sgml/datatype.sgml
@@ -4900,6 +4900,10 @@ SELECT * FROM pg_attribute
     <primary>anyrange</primary>
    </indexterm>
 
+   <indexterm zone="datatype-pseudo">
+    <primary>anymultirange</primary>
+   </indexterm>
+
    <indexterm zone="datatype-pseudo">
     <primary>anycompatible</primary>
    </indexterm>
@@ -4916,6 +4920,10 @@ SELECT * FROM pg_attribute
     <primary>anycompatiblerange</primary>
    </indexterm>
 
+   <indexterm zone="datatype-pseudo">
+    <primary>anycompatiblemultirange</primary>
+   </indexterm>
+
    <indexterm zone="datatype-pseudo">
     <primary>void</primary>
    </indexterm>
@@ -5027,6 +5035,13 @@ SELECT * FROM pg_attribute
         <xref linkend="rangetypes"/>).</entry>
        </row>
 
+       <row>
+        <entry><type>anymultirange</type></entry>
+        <entry>Indicates that a function accepts any multirange data type
+        (see <xref linkend="extend-types-polymorphic"/> and
+        <xref linkend="rangetypes"/>).</entry>
+       </row>
+
        <row>
         <entry><type>anycompatible</type></entry>
         <entry>Indicates that a function accepts any data type,
@@ -5056,6 +5071,14 @@ SELECT * FROM pg_attribute
         <xref linkend="rangetypes"/>).</entry>
        </row>
 
+       <row>
+        <entry><type>anycompatiblemultirange</type></entry>
+        <entry>Indicates that a function accepts any multirange data type,
+        with automatic promotion of multiple arguments to a common data type
+        (see <xref linkend="extend-types-polymorphic"/> and
+        <xref linkend="rangetypes"/>).</entry>
+       </row>
+
        <row>
         <entry><type>cstring</type></entry>
         <entry>Indicates that a function accepts or returns a null-terminated C string.</entry>
diff --git a/doc/src/sgml/extend.sgml b/doc/src/sgml/extend.sgml
index 1c37026bb05..6e3d82b85b8 100644
--- a/doc/src/sgml/extend.sgml
+++ b/doc/src/sgml/extend.sgml
@@ -288,6 +288,14 @@
         </entry>
        </row>
 
+       <row>
+        <entry><type>anymultirange</type></entry>
+        <entry>Simple</entry>
+        <entry>Indicates that a function accepts any multirange data type
+        (see <xref linkend="rangetypes"/>)
+        </entry>
+       </row>
+
        <row>
         <entry><type>anycompatible</type></entry>
         <entry>Common</entry>
@@ -319,6 +327,14 @@
         with automatic promotion of multiple arguments to a common data type
         </entry>
        </row>
+
+       <row>
+        <entry><type>anycompatiblemultirange</type></entry>
+        <entry>Common</entry>
+        <entry>Indicates that a function accepts any multirange data type,
+        with automatic promotion of multiple arguments to a common data type
+        </entry>
+       </row>
       </tbody>
      </tgroup>
     </table>
@@ -346,17 +362,15 @@
      position declared as <type>anyarray</type> can have any array data type,
      but similarly they must all be the same type.  And similarly,
      positions declared as <type>anyrange</type> must all be the same range
-     type.  Furthermore, if there are
+     type.  Likewise for <type>anymultirange</type>.
+    </para>
+
+    <para>
+     Furthermore, if there are
      positions declared <type>anyarray</type> and others declared
      <type>anyelement</type>, the actual array type in the
      <type>anyarray</type> positions must be an array whose elements are
      the same type appearing in the <type>anyelement</type> positions.
-     Similarly, if there are positions declared <type>anyrange</type>
-     and others declared <type>anyelement</type> or <type>anyarray</type>,
-     the actual range type in the <type>anyrange</type> positions must be a
-     range whose subtype is the same type appearing in
-     the <type>anyelement</type> positions and the same as the element type
-     of the <type>anyarray</type> positions.
      <type>anynonarray</type> is treated exactly the same as <type>anyelement</type>,
      but adds the additional constraint that the actual type must not be
      an array type.
@@ -365,6 +379,19 @@
      be an enum type.
     </para>
 
+    <para>
+     Similarly, if there are positions declared <type>anyrange</type>
+     and others declared <type>anyelement</type> or <type>anyarray</type>,
+     the actual range type in the <type>anyrange</type> positions must be a
+     range whose subtype is the same type appearing in
+     the <type>anyelement</type> positions and the same as the element type
+     of the <type>anyarray</type> positions.
+     If there are positions declared <type>anymultirange</type>,
+     their actual multirange type must contain ranges matching parameters declared
+     <type>anyrange</type> and base elements matching parameters declared
+     <type>anyelement</type> and <type>anyarray</type>.
+    </para>
+
     <para>
      Thus, when more than one argument position is declared with a polymorphic
      type, the net effect is that only certain combinations of actual argument
@@ -420,7 +447,8 @@
      Selection of the common type considers the actual types
      of <type>anycompatible</type> and <type>anycompatiblenonarray</type>
      inputs, the array element types of <type>anycompatiblearray</type>
-     inputs, and the range subtypes of <type>anycompatiblerange</type>
+     inputs, the range subtypes of <type>anycompatiblerange</type> inputs,
+     and the multirange subtypes of <type>anycompatiablemultirange</type>
      inputs.  If <type>anycompatiblenonarray</type> is present then the
      common type is required to be a non-array type.  Once a common type is
      identified, arguments in <type>anycompatible</type>
@@ -431,12 +459,15 @@
 
     <para>
      Since there is no way to select a range type knowing only its subtype,
-     use of <type>anycompatiblerange</type> requires that all arguments
-     declared with that type have the same actual range type, and that that
-     type's subtype agree with the selected common type, so that no casting
-     of the range values is required.  As with <type>anyrange</type>, use
-     of <type>anycompatiblerange</type> as a function result type requires
-     that there be an <type>anycompatiblerange</type> argument.
+     use of <type>anycompatiblerange</type> and/or
+     <type>anycompatiblemultirange</type> requires that all arguments declared
+     with that type have the same actual range and/or multirange type, and that
+     that type's subtype agree with the selected common type, so that no casting
+     of the range values is required.  As with <type>anyrange</type> and
+     <type>anymultirange</type>, use of <type>anycompatiblerange</type> and
+     <type>anymultirange</type> as a function result type requires that there be
+     an <type>anycompatiblerange</type> or <type>anycompatiblemultirange</type>
+     argument.
     </para>
 
     <para>
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 507bc1a6683..73da2644263 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -17891,12 +17891,15 @@ SELECT NULLIF(value, '(none)') ...
   <para>
    <xref linkend="range-operators-table"/> shows the specialized operators
    available for range types.
+   <xref linkend="multirange-operators-table"/> shows the specialized operators
+   available for multirange types.
    In addition to those, the usual comparison operators shown in
    <xref linkend="functions-comparison-op-table"/> are available for range
-   types.  The comparison operators order first by the range lower bounds, and
-   only if those are equal do they compare the upper bounds.  This does not
-   usually result in a useful overall ordering, but the operators are provided
-   to allow unique indexes to be constructed on ranges.
+   and multirange types.  The comparison operators order first by the range lower
+   bounds, and only if those are equal do they compare the upper bounds.  The
+   multirange operators compare each range until one is unequal. This
+   does not usually result in a useful overall ordering, but the operators are
+   provided to allow unique indexes to be constructed on ranges.
   </para>
 
    <table id="range-operators-table">
@@ -18106,15 +18109,449 @@ SELECT NULLIF(value, '(none)') ...
     </tgroup>
    </table>
 
+   <table id="multirange-operators-table">
+    <title>Multirange Operators</title>
+    <tgroup cols="1">
+     <thead>
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        Operator
+       </para>
+       <para>
+        Description
+       </para>
+       <para>
+        Example(s)
+       </para></entry>
+      </row>
+     </thead>
+
+     <tbody>
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>@&gt;</literal> <type>anymultirange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Does the first multirange contain the second?
+       </para>
+       <para>
+        <literal>'{[2,4)}'::int4multirange @&gt; '{[2,3)}'::int4multirange</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>@&gt;</literal> <type>anyrange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Does the multirange contain the range?
+       </para>
+       <para>
+        <literal>'{[2,4)}'::int4multirange @&gt; int4range(2,3)</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>@&gt;</literal> <type>anyelement</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Does the multirange contain the element?
+       </para>
+       <para>
+        <literal>'{[2011-01-01,2011-03-01)}'::tsmultirange @&gt; '2011-01-10'::timestamp</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>&lt;@</literal> <type>anymultirange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the first multirange contained by the second?
+       </para>
+       <para>
+        <literal>'{[2,4)}'::int4multirange &lt;@ '{[1,7)}'::int4multirange</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>&lt;@</literal> <type>anyrange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the multirange contained by the range?
+       </para>
+       <para>
+        <literal>'{[2,4)}'::int4multirange &lt;@ int4range(1,7)</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anyrange</type> <literal>&lt;@</literal> <type>anymultirange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the range contained by the multirange?
+       </para>
+       <para>
+        <literal>int4range(2,4) &lt;@ '{[1,7)}'::int4multirange</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anyelement</type> <literal>&lt;@</literal> <type>anymultirange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the element contained by the multirange?
+       </para>
+       <para>
+        <literal>42 &lt;@ '{[1,7)}'::int4multirange</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>&amp;&amp;</literal> <type>anymultirange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Do the multiranges overlap, that is, have any elements in common?
+       </para>
+       <para>
+        <literal>'{[3,7)}'::int8multirange &amp;&amp; '{[4,12)}'::int8multirange</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>&amp;&amp;</literal> <type>anyrange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Does the multirange overlap the range?
+       </para>
+       <para>
+        <literal>'{[3,7)}'::int8multirange &amp;&amp; int8range(4,12)</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anyrange</type> <literal>&amp;&amp;</literal> <type>anymultirange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Does the range overlap the multirange?
+       </para>
+       <para>
+        <literal>int8range(3,7) &amp;&amp; '{[4,12)}'::int8multirange</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>&lt;&lt;</literal> <type>anymultirange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the first multirange strictly left of the second?
+       </para>
+       <para>
+        <literal>'{[1,10)}'::int8multirange &lt;&lt; '{[100,110)}'::int8multirange</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>&lt;&lt;</literal> <type>anyrange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the multirange strictly left of the range?
+       </para>
+       <para>
+        <literal>'{[1,10)}'::int8multirange &lt;&lt; int8range(100,110)</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anyrange</type> <literal>&lt;&lt;</literal> <type>anymultirange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the range strictly left of the multirange?
+       </para>
+       <para>
+        <literal>int8range(1,10) &lt;&lt; '{[100,110)}'::int8multirange</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>&gt;&gt;</literal> <type>anymultirange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the first multirange strictly right of the second?
+       </para>
+       <para>
+        <literal>'{[50,60)}'::int8multirange &gt;&gt; '{[20,30)}'::int8multirange</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>&gt;&gt;</literal> <type>anyrange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the multirange strictly right of the range?
+       </para>
+       <para>
+        <literal>'{[50,60)}'::int8multirange &gt;&gt; int8range(20,30)</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anyrange</type> <literal>&gt;&gt;</literal> <type>anymultirange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the range strictly right of the multirange?
+       </para>
+       <para>
+        <literal>int8range(50,60) &gt;&gt; '{[20,30)}'::int8multirange</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>&amp;&lt;</literal> <type>anymultirange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Does the first multirange not extend to the right of the second?
+       </para>
+       <para>
+        <literal>'{[1,20)}'::int8multirange &amp;&lt; '{[18,20)}'::int8multirange</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>&amp;&lt;</literal> <type>anyrange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Does the multirange not extend to the right of the range?
+       </para>
+       <para>
+        <literal>'{[1,20)}'::int8multirange &amp;&lt; int8range(18,20)</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anyrange</type> <literal>&amp;&lt;</literal> <type>anymultirange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Does the range not extend to the right of the multirange?
+       </para>
+       <para>
+        <literal>int8range(1,20) &amp;&lt; '{[18,20)}'::int8multirange</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>&amp;&gt;</literal> <type>anymultirange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Does the first multirange not extend to the left of the second?
+       </para>
+       <para>
+        <literal>'{[7,20)}'::int8multirange &amp;&gt; '{[5,10)}'::int8multirange</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>&amp;&gt;</literal> <type>anyrange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Does the multirange not extend to the left of the range?
+       </para>
+       <para>
+        <literal>'{[7,20)}'::int8multirange &amp;&gt; int8range(5,10)</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anyrange</type> <literal>&amp;&gt;</literal> <type>anymultirange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Does the range not extend to the left of the multirange?
+       </para>
+       <para>
+        <literal>int8range(7,20) &amp;&gt; '{[5,10)}'::int8multirange</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>-|-</literal> <type>anymultirange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Are the multiranges adjacent?
+       </para>
+       <para>
+        <literal>'{[1.1,2.2)}'::nummultirange -|- '{[2.2,3.3)}'::nummultirange</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>-|-</literal> <type>anyrange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the multirange adjacent to the range?
+       </para>
+       <para>
+        <literal>'{[1.1,2.2)}'::nummultirange -|- numrange(2.2,3.3)</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anyrange</type> <literal>-|-</literal> <type>anymultirange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the range adjacent to the multirange?
+       </para>
+       <para>
+        <literal>numrange(1.1,2.2) -|- '{[2.2,3.3)}'::nummultirange</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>+</literal> <type>anymultirange</type>
+        <returnvalue>anymultirange</returnvalue>
+       </para>
+       <para>
+        Computes the union of the multiranges.  The multiranges need not overlap
+        or be adjacent.
+       </para>
+       <para>
+        <literal>'{[5,10)}'::nummultirange + '{[15,20)}'::nummultirange</literal>
+        <returnvalue>{[5,10), [15,20)}</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>*</literal> <type>anymultirange</type>
+        <returnvalue>anymultirange</returnvalue>
+       </para>
+       <para>
+        Computes the intersection of the multiranges.
+       </para>
+       <para>
+        <literal>'{[5,15)}'::int8multirange * '{[10,20)}'::int8multirange</literal>
+        <returnvalue>{[10,15)}</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>-</literal> <type>anymultirange</type>
+        <returnvalue>anymultirange</returnvalue>
+       </para>
+       <para>
+        Computes the difference of the multiranges.
+       </para>
+       <para>
+        <literal>'{[5,20)}'::int8multirange - '{[10,15)}'::int8multirange</literal>
+        <returnvalue>{[5,10), [15,20)}</returnvalue>
+       </para></entry>
+      </row>
+     </tbody>
+    </tgroup>
+   </table>
+
   <para>
    The left-of/right-of/adjacent operators always return false when an empty
-   range is involved; that is, an empty range is not considered to be either
-   before or after any other range.
+   range or multirange is involved; that is, an empty range is not considered to
+   be either before or after any other range.
+  </para>
+
+  <para>
+   Elsewhere empty ranges and multiranges are treated as the additive identity:
+   anything unioned with an empty value is itself. Anything minus an empty
+   value is itself. An empty multirange has exactly the same points as an empty
+   range. Every range contains the empty range. Every multirange contains as many
+   empty ranges as you like.
+  </para>
+
+  <para>
+   The range union and difference operators will fail if the resulting range would
+   need to contain two disjoint sub-ranges, as such a range cannot be
+   represented. There are separate operators for union and difference that take
+   multirange parameters and return a multirange, and they do not fail even if
+   their arguments are disjoint. So if you need a union or difference operation
+   for ranges that may be disjoint, you can avoid errors by first casting your
+   ranges to multiranges.
   </para>
 
   <para>
    <xref linkend="range-functions-table"/> shows the functions
    available for use with range types.
+   <xref linkend="multirange-functions-table"/> shows the functions
+   available for use with multirange types.
   </para>
 
    <table id="range-functions-table">
@@ -18276,10 +18713,185 @@ SELECT NULLIF(value, '(none)') ...
     </tgroup>
    </table>
 
+   <table id="multirange-functions-table">
+    <title>Multirange Functions</title>
+    <tgroup cols="1">
+     <thead>
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        Function
+       </para>
+       <para>
+        Description
+       </para>
+       <para>
+        Example(s)
+       </para></entry>
+      </row>
+     </thead>
+     <tbody>
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>lower</primary>
+        </indexterm>
+        <function>lower</function> ( <type>anymultirange</type> )
+        <returnvalue>anyelement</returnvalue>
+       </para>
+       <para>
+        Extracts the lower bound of the multirange (<literal>NULL</literal> if the
+        multirange is empty or the lower bound is infinite).
+       </para>
+       <para>
+        <literal>lower('{[1.1,2.2)}'::nummultirange)</literal>
+        <returnvalue>1.1</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>upper</primary>
+        </indexterm>
+        <function>upper</function> ( <type>anymultirange</type> )
+        <returnvalue>anyelement</returnvalue>
+       </para>
+       <para>
+        Extracts the upper bound of the multirange (<literal>NULL</literal> if the
+        multirange is empty or the upper bound is infinite).
+       </para>
+       <para>
+        <literal>upper('{[1.1,2.2)}'::nummultirange)</literal>
+        <returnvalue>2.2</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>isempty</primary>
+        </indexterm>
+        <function>isempty</function> ( <type>anymultirange</type> )
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the multirange empty?
+       </para>
+       <para>
+        <literal>isempty('{[1.1,2.2)}'::nummultirange)</literal>
+        <returnvalue>f</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>lower_inc</primary>
+        </indexterm>
+        <function>lower_inc</function> ( <type>anymultirange</type> )
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the multirange's lower bound inclusive?
+       </para>
+       <para>
+        <literal>lower_inc('{[1.1,2.2)}'::nummultirange)</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>upper_inc</primary>
+        </indexterm>
+        <function>upper_inc</function> ( <type>anymultirange</type> )
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the multirange's upper bound inclusive?
+       </para>
+       <para>
+        <literal>upper_inc('{[1.1,2.2)}'::nummultirange)</literal>
+        <returnvalue>f</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>lower_inf</primary>
+        </indexterm>
+        <function>lower_inf</function> ( <type>anymultirange</type> )
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the multirange's lower bound infinite?
+       </para>
+       <para>
+        <literal>lower_inf('{(,)}'::datemultirange)</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>upper_inf</primary>
+        </indexterm>
+        <function>upper_inf</function> ( <type>anymultirange</type> )
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the multirange's upper bound infinite?
+       </para>
+       <para>
+        <literal>upper_inf('{(,)}'::datemultirange)</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>range_merge</primary>
+        </indexterm>
+        <function>range_merge</function> ( <type>anymultirange</type> )
+        <returnvalue>anyrange</returnvalue>
+       </para>
+       <para>
+        Computes the smallest range that includes the entire multirange.
+       </para>
+       <para>
+        <literal>range_merge('{[1,2), [3,4)}'::int4multirange)</literal>
+        <returnvalue>[1,4)</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>multirange</primary>
+        </indexterm>
+        <function>multirange</function> ( <type>anyrange</type> )
+        <returnvalue>anymultirange</returnvalue>
+       </para>
+       <para>
+        Returns a multirange containing just the given range.
+       </para>
+       <para>
+        <literal>multirange('[1,2)'::int4range)</literal>
+        <returnvalue>{[1,2)}</returnvalue>
+       </para></entry>
+      </row>
+     </tbody>
+    </tgroup>
+   </table>
+
   <para>
    The <function>lower_inc</function>, <function>upper_inc</function>,
    <function>lower_inf</function>, and <function>upper_inf</function>
-   functions all return false for an empty range.
+   functions all return false for an empty range or multirange.
   </para>
   </sect1>
 
@@ -18611,6 +19223,36 @@ SELECT NULLIF(value, '(none)') ...
        <entry>Yes</entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>range_agg</primary>
+        </indexterm>
+        <function>range_agg</function> ( <parameter>value</parameter>
+         <type>anyrange</type> )
+        <returnvalue>anymultirange</returnvalue>
+       </para>
+       <para>
+        Computes the union of the non-null input values.
+       </para></entry>
+       <entry>No</entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>range_intersect_agg</primary>
+        </indexterm>
+        <function>range_intersect_agg</function> ( <parameter>value</parameter>
+         <type>anyrange</type> )
+        <returnvalue>anymultirange</returnvalue>
+       </para>
+       <para>
+        Computes the intersection of the non-null input values.
+       </para></entry>
+       <entry>No</entry>
+      </row>
+
       <row>
        <entry role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/doc/src/sgml/rangetypes.sgml b/doc/src/sgml/rangetypes.sgml
index b75fb3a3929..c8784bdce38 100644
--- a/doc/src/sgml/rangetypes.sgml
+++ b/doc/src/sgml/rangetypes.sgml
@@ -27,6 +27,13 @@
   ranges from an instrument, and so forth can also be useful.
  </para>
 
+ <para>
+  Every range type has a corresponding multirange type. A multirange is
+  an ordered list of non-continguous, non-empty, non-null ranges. Most
+  range operators also work on multiranges, and they have a few functions
+  of their own.
+ </para>
+
  <sect2 id="rangetypes-builtin">
   <title>Built-in Range Types</title>
 
@@ -232,10 +239,30 @@ SELECT '[4,4]'::int4range;
 SELECT '[4,4)'::int4range;
 </programlisting>
   </para>
+
+  <para>
+   The input for a multirange is curly brackets (<literal>{</literal> and
+   <literal>}</literal>) containing zero or more valid ranges,
+   separated by commas. Whitespace is permitted around the brackets and
+   commas. This is intended to be reminiscent of array syntax, although
+   multiranges are much simpler: they have just one dimension and there is
+   no need to quote their contents. (The bounds of their ranges may be
+   quoted as above however.)
+  </para>
+
+  <para>
+  Examples:
+<programlisting>
+SELECT '{}'::int4multirange;
+SELECT '{[3,7)}'::int4multirange;
+SELECT '{[3,7), [8,9)}'::int4multirange;
+</programlisting>
+  </para>
+
  </sect2>
 
  <sect2 id="rangetypes-construct">
-  <title>Constructing Ranges</title>
+  <title>Constructing Ranges and Multiranges</title>
 
   <para>
    Each range type has a constructor function with the same name as the range
@@ -267,6 +294,19 @@ SELECT int8range(1, 14, '(]');
 
 -- Using NULL for either bound causes the range to be unbounded on that side.
 SELECT numrange(NULL, 2.2);
+</programlisting>
+  </para>
+
+  <para>
+   Each range type also has a multirange constructor with the same name as the
+   multirange type.  The constructor function takes zero or more arguments
+   which are all ranges of the appropriate type.
+   For example:
+
+<programlisting>
+SELECT nummultirange();
+SELECT nummultirange(numrange(1.0, 14.0));
+SELECT nummultirange(numrange(1.0, 14.0), numrange(20.0, 25.0));
 </programlisting>
   </para>
  </sect2>
@@ -341,6 +381,11 @@ SELECT '[1.234, 5.678]'::floatrange;
    function in this example.
   </para>
 
+  <para>
+   When you define your own range you automatically get a corresponding
+   multirange type.
+  </para>
+
   <para>
    Defining your own range type also allows you to specify a different
    subtype B-tree operator class or collation to use, so as to change the sort
diff --git a/src/backend/catalog/pg_range.c b/src/backend/catalog/pg_range.c
index a606d8c3adb..91b0fb0611a 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;
 	ObjectAddresses *addrs;
 
 	pg_range = table_open(RangeRelationId, RowExclusiveLock);
@@ -55,6 +56,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_rngmultitypid - 1] = ObjectIdGetDatum(multirangeTypeOid);
 
 	tup = heap_form_tuple(RelationGetDescr(pg_range), values, nulls);
 
@@ -93,6 +95,12 @@ RangeCreate(Oid rangeTypeOid, Oid rangeSubType, Oid rangeCollation,
 	record_object_address_dependencies(&myself, addrs, DEPENDENCY_NORMAL);
 	free_object_addresses(addrs);
 
+	/* 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 aeb4a54f635..44df297dda0 100644
--- a/src/backend/catalog/pg_type.c
+++ b/src/backend/catalog/pg_type.c
@@ -28,6 +28,7 @@
 #include "catalog/pg_proc.h"
 #include "catalog/pg_type.h"
 #include "commands/typecmds.h"
+#include "mb/pg_wchar.h"
 #include "miscadmin.h"
 #include "parser/scansup.h"
 #include "utils/acl.h"
@@ -37,6 +38,9 @@
 #include "utils/rel.h"
 #include "utils/syscache.h"
 
+static char *makeUniqueTypeName(const char *typeName, Oid typeNamespace,
+								bool tryOriginal);
+
 /* Potentially set by pg_upgrade_support functions */
 Oid			binary_upgrade_next_pg_type_oid = InvalidOid;
 
@@ -854,31 +858,10 @@ RenameTypeInternal(Oid typeOid, const char *newTypeName, Oid typeNamespace)
 char *
 makeArrayTypeName(const char *typeName, Oid typeNamespace)
 {
-	char	   *arr = (char *) palloc(NAMEDATALEN);
-	int			namelen = strlen(typeName);
-	int			i;
-
-	/*
-	 * 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;
-	}
+	char	   *arr;
 
-	if (i >= NAMEDATALEN - 1)
+	arr = makeUniqueTypeName(typeName, typeNamespace, false);
+	if (arr == NULL)
 		ereport(ERROR,
 				(errcode(ERRCODE_DUPLICATE_OBJECT),
 				 errmsg("could not form array type name for type \"%s\"",
@@ -949,3 +932,91 @@ moveArrayTypeName(Oid typeOid, const char *typeName, Oid typeNamespace)
 
 	return true;
 }
+
+
+/*
+ * makeMultirangeTypeName
+ *	  - given a range type name, make a multirange type name for it
+ *
+ * caller is responsible for pfreeing the result
+ */
+char *
+makeMultirangeTypeName(const char *rangeTypeName, Oid typeNamespace)
+{
+	char	   *buf;
+	char	   *mrname;
+	char	   *rangestr;
+
+	/*
+	 * 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...
+	 */
+	rangestr = strstr(rangeTypeName, "range");
+	if (rangestr)
+	{
+		char	*prefix = pnstrdup(rangeTypeName, rangestr - rangeTypeName);
+
+		buf = psprintf("%s%s%s", prefix, "multi", rangestr);
+	}
+	else
+		buf = psprintf("%s_multirange", pnstrdup(rangeTypeName, NAMEDATALEN - 12));
+
+	/* clip it at NAMEDATALEN-1 bytes */
+	buf[pg_mbcliplen(buf, strlen(buf), NAMEDATALEN - 1)] = '\0';
+
+	mrname = makeUniqueTypeName(buf, typeNamespace, true);
+	if (mrname == NULL)
+		ereport(ERROR,
+				(errcode(ERRCODE_DUPLICATE_OBJECT),
+				 errmsg("could not form multirange type name for type \"%s\"",
+						rangeTypeName)));
+
+	return mrname;
+}
+
+/*
+ * makeUniqueTypeName
+ *		Generate a unique name for a prospective new type
+ *
+ * Given a typeName, return a new palloc'ed name by preprending underscores
+ * until a non-conflicting name results.
+ *
+ * If tryOriginal, first try with zero underscores.
+ */
+static char *
+makeUniqueTypeName(const char *typeName, Oid typeNamespace, bool tryOriginal)
+{
+	int			i;
+	int			namelen;
+	char		dest[NAMEDATALEN];
+
+	Assert(strlen(typeName) <= NAMEDATALEN - 1);
+
+	if (tryOriginal &&
+		!SearchSysCacheExists2(TYPENAMENSP,
+							   CStringGetDatum(typeName),
+							   ObjectIdGetDatum(typeNamespace)))
+		return pstrdup(typeName);
+
+	/*
+	 * The idea is to prepend underscores as needed until we make a name that
+	 * doesn't collide with anything ...
+	 */
+	namelen = strlen(typeName);
+	for (i = 1; i < NAMEDATALEN - 1; i++)
+	{
+		dest[i - 1] = '_';
+		strlcpy(dest + i, typeName, NAMEDATALEN - i);
+		if (namelen + i >= NAMEDATALEN)
+			truncate_identifier(dest, NAMEDATALEN, false);
+
+		if (!SearchSysCacheExists2(TYPENAMENSP,
+								   CStringGetDatum(dest),
+								   ObjectIdGetDatum(typeNamespace)))
+			return pstrdup(dest);
+	}
+
+	return NULL;
+}
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index 483bb65ddc8..b24fc1f5660 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -105,9 +105,14 @@ typedef struct
 
 /* Potentially set by pg_upgrade_support functions */
 Oid			binary_upgrade_next_array_pg_type_oid = InvalidOid;
+Oid			binary_upgrade_next_mrng_pg_type_oid = InvalidOid;
+Oid			binary_upgrade_next_mrng_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 rangeOid,
+									   Oid rangeArrayOid, Oid *castFuncOid);
 static Oid	findTypeInputFunction(List *procname, Oid typeOid);
 static Oid	findTypeOutputFunction(List *procname, Oid typeOid);
 static Oid	findTypeReceiveFunction(List *procname, Oid typeOid);
@@ -739,7 +744,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",
@@ -1280,6 +1286,11 @@ checkEnumOwner(HeapTuple tup)
 /*
  * DefineRange
  *		Registers a new range type.
+ *
+ * Perhaps it might be worthwhile to set pg_type.typelem to the base type,
+ * and likewise on multiranges to set it to the range type. But having a
+ * non-zero typelem is treated elsewhere as a synonym for being an array,
+ * and users might have queries with that same assumption.
  */
 ObjectAddress
 DefineRange(CreateRangeStmt *stmt)
@@ -1288,7 +1299,11 @@ DefineRange(CreateRangeStmt *stmt)
 	Oid			typeNamespace;
 	Oid			typoid;
 	char	   *rangeArrayName;
+	char	   *multirangeTypeName;
+	char	   *multirangeArrayName;
 	Oid			rangeArrayOid;
+	Oid			multirangeOid;
+	Oid			multirangeArrayOid;
 	Oid			rangeSubtype = InvalidOid;
 	List	   *rangeSubOpclassName = NIL;
 	List	   *rangeCollationName = NIL;
@@ -1305,6 +1320,8 @@ DefineRange(CreateRangeStmt *stmt)
 	AclResult	aclresult;
 	ListCell   *lc;
 	ObjectAddress address;
+	ObjectAddress mltrngaddress;
+	Oid			castFuncOid;
 
 	/* Convert list of names to a name and namespace */
 	typeNamespace = QualifiedNameGetCreationNamespace(stmt->typeName,
@@ -1453,8 +1470,10 @@ DefineRange(CreateRangeStmt *stmt)
 	/* alignment must be TYPALIGN_INT or TYPALIGN_DOUBLE for ranges */
 	alignment = (subtypalign == TYPALIGN_DOUBLE) ? TYPALIGN_DOUBLE : TYPALIGN_INT;
 
-	/* Allocate OID for array type */
+	/* Allocate OID for array type, its multirange, and its multirange array */
 	rangeArrayOid = AssignTypeArrayOid();
+	multirangeOid = AssignTypeMultirangeOid();
+	multirangeArrayOid = AssignTypeMultirangeArrayOid();
 
 	/* Create the pg_type entry */
 	address =
@@ -1492,9 +1511,46 @@ DefineRange(CreateRangeStmt *stmt)
 	Assert(typoid == InvalidOid || typoid == address.objectId);
 	typoid = address.objectId;
 
+	/* Create the multirange that goes with it */
+	multirangeTypeName = makeMultirangeTypeName(typeName, typeNamespace);
+
+	mltrngaddress =
+		TypeCreate(multirangeOid,	/* force assignment of this 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_RANGE,	/* type-category (range 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) */
+	Assert(multirangeOid == mltrngaddress.objectId);
+
 	/* Create the entry in pg_range */
 	RangeCreate(typoid, rangeSubtype, rangeCollation, rangeSubOpclass,
-				rangeCanonical, rangeSubtypeDiff);
+				rangeCanonical, rangeSubtypeDiff, multirangeOid);
 
 	/*
 	 * Create the array type that goes with it.
@@ -1535,8 +1591,53 @@ 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 */
+			   multirangeOid,	/* 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,
+							   multirangeOid, typoid, rangeArrayOid,
+							   &castFuncOid);
+
+	/* Create cast from the range type to its multirange type */
+	CastCreate(typoid, multirangeOid, castFuncOid, 'e', 'f', DEPENDENCY_INTERNAL);
+
+	pfree(multirangeTypeName);
+	pfree(multirangeArrayName);
 
 	return address;
 }
@@ -1614,6 +1715,147 @@ 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.
+ *
+ * Sets castFuncOid to the oid of the new constructor that can be used
+ * to cast from a range to a multirange.
+ */
+static void
+makeMultirangeConstructors(const char *name, Oid namespace,
+						   Oid multirangeOid, Oid rangeOid, Oid rangeArrayOid,
+						   Oid *castFuncOid)
+{
+	ObjectAddress myself,
+				referenced;
+	oidvector  *argtypes;
+	Datum		allParamTypes;
+	ArrayType  *allParameterTypes;
+	Datum		paramModes;
+	ArrayType  *parameterModes;
+
+	referenced.classId = TypeRelationId;
+	referenced.objectId = multirangeOid;
+	referenced.objectSubId = 0;
+
+	/* 0-arg constructor - for empty multiranges */
+	argtypes = buildoidvector(NULL, 0);
+	myself = ProcedureCreate(name,	/* name: same as multirange type */
+							 namespace,
+							 false,	/* replace */
+							 false,	/* returns set */
+							 multirangeOid,	/* return type */
+							 BOOTSTRAP_SUPERUSERID,	/* proowner */
+							 INTERNALlanguageId,	/* language */
+							 F_FMGR_INTERNAL_VALIDATOR,
+							 "multirange_constructor0",	/* prosrc */
+							 NULL,	/* probin */
+							 PROKIND_FUNCTION,
+							 false,	/* security_definer */
+							 false,	/* leakproof */
+							 false, /* isStrict */
+							 PROVOLATILE_IMMUTABLE,	/* volatility */
+							 PROPARALLEL_SAFE,	/* parallel safety */
+							 argtypes,	/* parameterTypes */
+							 PointerGetDatum(NULL),	/* allParameterTypes */
+							 PointerGetDatum(NULL),	/* parameterModes */
+							 PointerGetDatum(NULL),	/* parameterNames */
+							 NIL,	/* parameterDefaults */
+							 PointerGetDatum(NULL),	/* trftypes */
+							 PointerGetDatum(NULL),	/* proconfig */
+							 InvalidOid,	/* prosupport */
+							 1.0,	/* procost */
+							 0.0);	/* prorows */
+	/*
+	 * Make the constructor 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);
+	pfree(argtypes);
+
+	/*
+	 * 1-arg constructor - for casts
+	 *
+	 * In theory we shouldn't need both this and the vararg (n-arg) constructor,
+	 * but having a separate 1-arg function lets us define casts against it.
+	 */
+	argtypes = buildoidvector(&rangeOid, 1);
+	myself = ProcedureCreate(name,	/* name: same as multirange type */
+							 namespace,
+							 false,	/* replace */
+							 false,	/* returns set */
+							 multirangeOid,	/* return type */
+							 BOOTSTRAP_SUPERUSERID,	/* proowner */
+							 INTERNALlanguageId,	/* language */
+							 F_FMGR_INTERNAL_VALIDATOR,
+							 "multirange_constructor1",	/* prosrc */
+							 NULL,	/* probin */
+							 PROKIND_FUNCTION,
+							 false,	/* security_definer */
+							 false,	/* leakproof */
+							 true, /* isStrict */
+							 PROVOLATILE_IMMUTABLE,	/* volatility */
+							 PROPARALLEL_SAFE,	/* parallel safety */
+							 argtypes,	/* parameterTypes */
+							 PointerGetDatum(NULL),	/* allParameterTypes */
+							 PointerGetDatum(NULL),	/* parameterModes */
+							 PointerGetDatum(NULL),	/* parameterNames */
+							 NIL,	/* parameterDefaults */
+							 PointerGetDatum(NULL),	/* trftypes */
+							 PointerGetDatum(NULL),	/* proconfig */
+							 InvalidOid,	/* prosupport */
+							 1.0,	/* procost */
+							 0.0);	/* prorows */
+	/* ditto */
+	recordDependencyOn(&myself, &referenced, DEPENDENCY_INTERNAL);
+	pfree(argtypes);
+	*castFuncOid = myself.objectId;
+
+	/* n-arg constructor - vararg */
+	argtypes = buildoidvector(&rangeArrayOid, 1);
+	allParamTypes = ObjectIdGetDatum(rangeArrayOid);
+	allParameterTypes = construct_array(&allParamTypes,
+										1, OIDOID,
+										sizeof(Oid), true, 'i');
+	paramModes = CharGetDatum(FUNC_PARAM_VARIADIC);
+	parameterModes = construct_array(&paramModes, 1, CHAROID,
+									 1, true, 'c');
+	myself = ProcedureCreate(name,	/* name: same as multirange type */
+							 namespace,
+							 false,	/* replace */
+							 false,	/* returns set */
+							 multirangeOid,	/* return type */
+							 BOOTSTRAP_SUPERUSERID,	/* proowner */
+							 INTERNALlanguageId,	/* language */
+							 F_FMGR_INTERNAL_VALIDATOR,
+							 "multirange_constructor2",	/* prosrc */
+							 NULL,	/* probin */
+							 PROKIND_FUNCTION,
+							 false,	/* security_definer */
+							 false,	/* leakproof */
+							 false, /* isStrict */
+							 PROVOLATILE_IMMUTABLE,	/* volatility */
+							 PROPARALLEL_SAFE,	/* parallel safety */
+							 argtypes,	/* parameterTypes */
+							 PointerGetDatum(allParameterTypes),	/* allParameterTypes */
+							 PointerGetDatum(parameterModes),	/* parameterModes */
+							 PointerGetDatum(NULL),	/* parameterNames */
+							 NIL,	/* parameterDefaults */
+							 PointerGetDatum(NULL),	/* trftypes */
+							 PointerGetDatum(NULL),	/* proconfig */
+							 InvalidOid,	/* prosupport */
+							 1.0,	/* procost */
+							 0.0);	/* prorows */
+	/* ditto */
+	recordDependencyOn(&myself, &referenced, DEPENDENCY_INTERNAL);
+	pfree(argtypes);
+	pfree(allParameterTypes);
+	pfree(parameterModes);
+}
 
 /*
  * Find suitable I/O functions for a type.
@@ -2068,6 +2310,72 @@ AssignTypeArrayOid(void)
 	return type_array_oid;
 }
 
+/*
+ *	AssignTypeMultirangeOid
+ *
+ *	Pre-assign the range type's multirange OID for use in pg_type.oid
+ */
+Oid
+AssignTypeMultirangeOid(void)
+{
+	Oid			type_multirange_oid;
+
+	/* Use binary-upgrade override for pg_type.oid? */
+	if (IsBinaryUpgrade)
+	{
+		if (!OidIsValid(binary_upgrade_next_mrng_pg_type_oid))
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+					 errmsg("pg_type multirange OID value not set when in binary upgrade mode")));
+
+		type_multirange_oid = binary_upgrade_next_mrng_pg_type_oid;
+		binary_upgrade_next_mrng_pg_type_oid = InvalidOid;
+	}
+	else
+	{
+		Relation	pg_type = table_open(TypeRelationId, AccessShareLock);
+
+		type_multirange_oid = GetNewOidWithIndex(pg_type, TypeOidIndexId,
+												 Anum_pg_type_oid);
+		table_close(pg_type, AccessShareLock);
+	}
+
+	return type_multirange_oid;
+}
+
+/*
+ *	AssignTypeMultirangeArrayOid
+ *
+ *	Pre-assign the range type's multirange array OID for use in pg_type.typarray
+ */
+Oid
+AssignTypeMultirangeArrayOid(void)
+{
+	Oid			type_multirange_array_oid;
+
+	/* Use binary-upgrade override for pg_type.oid? */
+	if (IsBinaryUpgrade)
+	{
+		if (!OidIsValid(binary_upgrade_next_mrng_array_pg_type_oid))
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+					 errmsg("pg_type multirange array OID value not set when in binary upgrade mode")));
+
+		type_multirange_array_oid = binary_upgrade_next_mrng_array_pg_type_oid;
+		binary_upgrade_next_mrng_array_pg_type_oid = InvalidOid;
+	}
+	else
+	{
+		Relation	pg_type = table_open(TypeRelationId, AccessShareLock);
+
+		type_multirange_array_oid = GetNewOidWithIndex(pg_type, TypeOidIndexId,
+													   Anum_pg_type_oid);
+		table_close(pg_type, AccessShareLock);
+	}
+
+	return type_multirange_array_oid;
+}
+
 
 /*-------------------------------------------------------------------
  * DefineCompositeType
diff --git a/src/backend/executor/functions.c b/src/backend/executor/functions.c
index 459a33375b1..ca8d637e73a 100644
--- a/src/backend/executor/functions.c
+++ b/src/backend/executor/functions.c
@@ -1711,7 +1711,8 @@ check_sql_fn_retval(List *queryTreeLists,
 	if (fn_typtype == TYPTYPE_BASE ||
 		fn_typtype == TYPTYPE_DOMAIN ||
 		fn_typtype == TYPTYPE_ENUM ||
-		fn_typtype == TYPTYPE_RANGE)
+		fn_typtype == TYPTYPE_RANGE ||
+		fn_typtype == TYPTYPE_MULTIRANGE)
 	{
 		/*
 		 * For scalar-type returns, the target list must have exactly one
diff --git a/src/backend/parser/parse_coerce.c b/src/backend/parser/parse_coerce.c
index a2924e3d1ce..fb33b30e369 100644
--- a/src/backend/parser/parse_coerce.c
+++ b/src/backend/parser/parse_coerce.c
@@ -189,19 +189,21 @@ coerce_type(ParseState *pstate, Node *node,
 	if (targetTypeId == ANYARRAYOID ||
 		targetTypeId == ANYENUMOID ||
 		targetTypeId == ANYRANGEOID ||
+		targetTypeId == ANYMULTIRANGEOID ||
 		targetTypeId == ANYCOMPATIBLEARRAYOID ||
-		targetTypeId == ANYCOMPATIBLERANGEOID)
+		targetTypeId == ANYCOMPATIBLERANGEOID ||
+		targetTypeId == ANYCOMPATIBLEMULTIRANGEOID)
 	{
 		/*
 		 * 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 the pseudotype's input
-		 * function, 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
+		 * the pseudotype's input function, 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
@@ -1569,8 +1571,8 @@ select_common_typmod(ParseState *pstate, List *exprs, Oid common_type)
  * 1) All arguments declared ANYELEMENT must have the same datatype.
  * 2) All arguments declared ANYARRAY must have the same datatype,
  *	  which must be a varlena array type.
- * 3) All arguments declared ANYRANGE must have the same datatype,
- *	  which must be a range type.
+ * 3) All arguments declared ANYRANGE or ANYMULTIRANGE must be a range or
+ *	  multirange type, all derived from the same base datatype.
  * 4) If there are arguments of more than one of these polymorphic types,
  *	  the array element type and/or range subtype must be the same as each
  *	  other and the same as the ANYELEMENT type.
@@ -1585,8 +1587,8 @@ select_common_typmod(ParseState *pstate, List *exprs, Oid common_type)
  *	  to a common supertype (chosen as per select_common_type's rules).
  *	  ANYCOMPATIBLENONARRAY works like ANYCOMPATIBLE but also requires the
  *	  common supertype to not be an array.  If there are ANYCOMPATIBLEARRAY
- *	  or ANYCOMPATIBLERANGE arguments, their element types or subtypes are
- *	  included while making the choice of common supertype.
+ *	  or ANYCOMPATIBLERANGE or ANYCOMPATIBLEMULTIRANGE arguments, their element
+ *	  types or subtypes are included while making the choice of common supertype.
  * 8) The resolved type of ANYCOMPATIBLEARRAY arguments will be the array
  *	  type over the common supertype (which might not be the same array type
  *	  as any of the original arrays).
@@ -1594,6 +1596,10 @@ select_common_typmod(ParseState *pstate, List *exprs, Oid common_type)
  *	  (after domain flattening), since we have no preference rule that would
  *	  let us choose one over another.  Furthermore, that range's subtype
  *	  must exactly match the common supertype chosen by rule 7.
+ * 10) All ANYCOMPATIBLEMULTIRANGE arguments must be the exact same multirange
+ *	  type (after domain flattening), since we have no preference rule that would
+ *	  let us choose one over another.  Furthermore, that multirange's range's
+ *	  subtype must exactly match the common supertype chosen by rule 7.
  *
  * Domains over arrays match ANYARRAY, and are immediately flattened to their
  * base type.  (Thus, for example, we will consider it a match if one ANYARRAY
@@ -1602,7 +1608,9 @@ select_common_typmod(ParseState *pstate, List *exprs, Oid common_type)
  * for ANYCOMPATIBLEARRAY and ANYCOMPATIBLENONARRAY.
  *
  * Similarly, domains over ranges match ANYRANGE or ANYCOMPATIBLERANGE,
- * and are immediately flattened to their base type.
+ * and are immediately flattened to their base type, and domains over
+ * multiranges match ANYMULTIRANGE or ANYCOMPATIBLEMULTIRANGE and are immediately
+ * flattened to their base type.
  *
  * Note that domains aren't currently considered to match ANYENUM,
  * even if their base type would match.
@@ -1620,8 +1628,12 @@ check_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			anycompatible_range_typeid = InvalidOid;
 	Oid			anycompatible_range_typelem = InvalidOid;
+	Oid			anycompatible_multirange_typeid = InvalidOid;
+	Oid			anycompatible_multirange_typelem = InvalidOid;
+	Oid			range_typelem = InvalidOid;
 	bool		have_anynonarray = false;
 	bool		have_anyenum = false;
 	bool		have_anycompatible_nonarray = false;
@@ -1670,6 +1682,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;
+		}
 		else if (decl_type == ANYCOMPATIBLEOID ||
 				 decl_type == ANYCOMPATIBLENONARRAYOID)
 		{
@@ -1714,6 +1735,42 @@ check_generic_type_consistency(const Oid *actual_arg_types,
 				anycompatible_actual_types[n_anycompatible_args++] = anycompatible_range_typelem;
 			}
 		}
+		else if (decl_type == ANYCOMPATIBLEMULTIRANGEOID)
+		{
+			if (actual_type == UNKNOWNOID)
+				continue;
+			actual_type = getBaseType(actual_type); /* flatten domains */
+			if (OidIsValid(anycompatible_multirange_typeid))
+			{
+				/* All ANYCOMPATIBLEMULTIRANGE arguments must be the same type */
+				if (anycompatible_multirange_typeid != actual_type)
+					return false;
+			}
+			else
+			{
+				anycompatible_multirange_typeid = actual_type;
+				anycompatible_multirange_typelem = get_multirange_range(actual_type);
+				if (!OidIsValid(anycompatible_multirange_typelem))
+					return false;	/* not a multirange type */
+
+				if (OidIsValid(anycompatible_range_typeid))
+				{
+					/* ANYCOMPATIBLEMULTIRANGE and ANYCOMPATIBLERANGE arguments must match */
+					if (anycompatible_range_typeid != anycompatible_multirange_typelem)
+						return false;
+				}
+				else
+				{
+					anycompatible_range_typeid = anycompatible_multirange_typelem;
+					anycompatible_range_typelem = get_range_subtype(anycompatible_range_typeid);
+					if (!OidIsValid(anycompatible_range_typelem))
+						return false;	/* not a range type */
+				}
+				/* collect the subtype for common-supertype choice */
+				anycompatible_actual_types[n_anycompatible_args++] =
+					anycompatible_range_typelem;
+			}
+		}
 	}
 
 	/* Get the element type based on the array type, if we have one */
@@ -1760,8 +1817,6 @@ check_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))
 	{
-		Oid			range_typelem;
-
 		range_typelem = get_range_subtype(range_typeid);
 		if (!OidIsValid(range_typelem))
 			return false;		/* should be a range, but isn't */
@@ -1780,6 +1835,45 @@ 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))
+	{
+		Oid			multirange_typelem;
+
+		multirange_typelem = get_multirange_range(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 */
+		}
+		else if (multirange_typelem != range_typeid)
+		{
+			/* otherwise, they better match */
+			return false;
+		}
+
+		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 */
@@ -1818,8 +1912,10 @@ check_generic_type_consistency(const Oid *actual_arg_types,
 		}
 
 		/*
-		 * the anycompatible type must exactly match the range element type,
-		 * if we were able to identify one
+		 * The anycompatible type must exactly match the range element type,
+		 * if we were able to identify one. This checks compatibility for
+		 * anycompatiblemultirange too since that also sets
+		 * anycompatible_range_typelem above.
 		 */
 		if (OidIsValid(anycompatible_range_typelem) &&
 			anycompatible_range_typelem != anycompatible_typeid)
@@ -1858,21 +1954,27 @@ check_generic_type_consistency(const Oid *actual_arg_types,
  *	  argument's actual type as the function's return type.
  * 2) If return type is ANYARRAY, and any argument is ANYARRAY, use the
  *	  argument's actual type as the function's return type.
- * 3) Similarly, if return type is ANYRANGE, and any argument is ANYRANGE,
- *	  use the argument's actual type as the function's return type.
- * 4) Otherwise, if return type is ANYELEMENT or ANYARRAY, and there is
+ * 3) Similarly, if return type is ANYRANGE or ANYMULTIRANGE, and any
+ *	  argument is ANYRANGE or ANYMULTIRANGE, use that argument's
+ *	  actual type, range type or multirange type as the function's return
+ *	  type.
+ * 4) Otherwise, if return type is ANYMULTIRANGE, and any argument is
+ *	  ANYMULTIRANGE, use the argument's actual type as the function's return
+ *	  type. Or if any argument is ANYRANGE, use its multirange type as the
+ *	  function's return type.
+ * 5) Otherwise, if return type is ANYELEMENT or ANYARRAY, and there is
  *	  at least one ANYELEMENT, ANYARRAY, or ANYRANGE input, deduce the
  *	  return type from those inputs, or throw error if we can't.
- * 5) Otherwise, if return type is ANYRANGE, throw error.  (We have no way to
- *	  select a specific range type if the arguments don't include ANYRANGE.)
- * 6) ANYENUM is treated the same as ANYELEMENT except that if it is used
+ * 6) Otherwise, if return type is ANYRANGE or ANYMULTIRANGE, throw error.
+ *	  (We have no way to select a specific range type if the arguments don't
+ *	  include ANYRANGE.)
  *	  (alone or in combination with plain ANYELEMENT), we add the extra
  *	  condition that the ANYELEMENT type must be an enum.
- * 7) ANYNONARRAY is treated the same as ANYELEMENT except that if it is used,
+ * 8) ANYNONARRAY is treated the same as ANYELEMENT except that if it is used,
  *	  we add the extra condition that the ANYELEMENT type must not be an array.
  *	  (This is a no-op if used in combination with ANYARRAY or ANYENUM, but
  *	  is an extra restriction if not.)
- * 8) ANYCOMPATIBLE, ANYCOMPATIBLEARRAY, ANYCOMPATIBLENONARRAY, and
+ * 9) ANYCOMPATIBLE, ANYCOMPATIBLEARRAY, ANYCOMPATIBLENONARRAY, and
  *	  ANYCOMPATIBLERANGE are handled by resolving the common supertype
  *	  of those arguments (or their element types/subtypes, for array and range
  *	  inputs), and then coercing all those arguments to the common supertype,
@@ -1926,10 +2028,15 @@ 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			anycompatible_typeid = InvalidOid;
 	Oid			anycompatible_array_typeid = InvalidOid;
 	Oid			anycompatible_range_typeid = InvalidOid;
 	Oid			anycompatible_range_typelem = InvalidOid;
+	Oid			anycompatible_multirange_typeid = InvalidOid;
+	Oid			anycompatible_multirange_typelem = InvalidOid;
+	Oid			range_typelem;
+	Oid			multirange_typelem;
 	bool		have_anynonarray = (rettype == ANYNONARRAYOID);
 	bool		have_anyenum = (rettype == ANYENUMOID);
 	bool		have_anycompatible_nonarray = (rettype == ANYCOMPATIBLENONARRAYOID);
@@ -2014,6 +2121,26 @@ enforce_generic_type_consistency(const Oid *actual_arg_types,
 								   format_type_be(actual_type))));
 			range_typeid = actual_type;
 		}
+		else if (decl_type == ANYMULTIRANGEOID)
+		{
+			n_poly_args++;
+			if (actual_type == UNKNOWNOID)
+			{
+				have_poly_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;
+		}
 		else if (decl_type == ANYCOMPATIBLEOID ||
 				 decl_type == ANYCOMPATIBLENONARRAYOID)
 		{
@@ -2082,6 +2209,40 @@ enforce_generic_type_consistency(const Oid *actual_arg_types,
 				anycompatible_actual_types[n_anycompatible_args++] = anycompatible_range_typelem;
 			}
 		}
+		else if (decl_type == ANYCOMPATIBLEMULTIRANGEOID)
+		{
+			have_poly_anycompatible = true;
+			if (actual_type == UNKNOWNOID)
+				continue;
+			if (allow_poly && decl_type == actual_type)
+				continue;		/* no new information here */
+			actual_type = getBaseType(actual_type); /* flatten domains */
+			if (OidIsValid(anycompatible_multirange_typeid))
+			{
+				/* All ANYCOMPATIBLEMULTIRANGE arguments must be the same type */
+				if (anycompatible_multirange_typeid != actual_type)
+					ereport(ERROR,
+							(errcode(ERRCODE_DATATYPE_MISMATCH),
+							 errmsg("arguments declared \"anycompatiblemultirange\" are not all alike"),
+							 errdetail("%s versus %s",
+									   format_type_be(anycompatible_multirange_typeid),
+									   format_type_be(actual_type))));
+			}
+			else
+			{
+				anycompatible_multirange_typeid = actual_type;
+				anycompatible_multirange_typelem = get_multirange_range(actual_type);
+				anycompatible_range_typelem = get_range_subtype(anycompatible_multirange_typelem);
+				if (!OidIsValid(anycompatible_multirange_typelem))
+					ereport(ERROR,
+							(errcode(ERRCODE_DATATYPE_MISMATCH),
+							 errmsg("argument declared %s is not a multirange type but type %s",
+									"anycompatiblemultirange",
+									format_type_be(actual_type))));
+				/* collect the subtype for common-supertype choice */
+				anycompatible_actual_types[n_anycompatible_args++] = anycompatible_range_typelem;
+			}
+		}
 	}
 
 	/*
@@ -2150,8 +2311,6 @@ 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))
 		{
-			Oid			range_typelem;
-
 			range_typelem = get_range_subtype(range_typeid);
 			if (!OidIsValid(range_typelem))
 				ereport(ERROR,
@@ -2180,6 +2339,61 @@ enforce_generic_type_consistency(const Oid *actual_arg_types,
 								   format_type_be(elem_typeid))));
 			}
 		}
+		else
+			range_typelem = InvalidOid;
+
+		/* Get the element type based on the multirange type, if we have one */
+		if (OidIsValid(multirange_typeid))
+		{
+			multirange_typelem = get_multirange_range(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))));
+			}
+
+			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 */
+				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))));
+			}
+		}
+		else
+			multirange_typelem = InvalidOid;
 
 		if (!OidIsValid(elem_typeid))
 		{
@@ -2188,6 +2402,7 @@ enforce_generic_type_consistency(const Oid *actual_arg_types,
 				elem_typeid = ANYELEMENTOID;
 				array_typeid = ANYARRAYOID;
 				range_typeid = ANYRANGEOID;
+				multirange_typeid = ANYMULTIRANGEOID;
 			}
 			else
 			{
@@ -2287,6 +2502,7 @@ enforce_generic_type_consistency(const Oid *actual_arg_types,
 				anycompatible_typeid = ANYCOMPATIBLEOID;
 				anycompatible_array_typeid = ANYCOMPATIBLEARRAYOID;
 				anycompatible_range_typeid = ANYCOMPATIBLERANGEOID;
+				anycompatible_multirange_typeid = ANYCOMPATIBLEMULTIRANGEOID;
 			}
 			else
 			{
@@ -2318,6 +2534,8 @@ enforce_generic_type_consistency(const Oid *actual_arg_types,
 				declared_arg_types[j] = anycompatible_array_typeid;
 			else if (decl_type == ANYCOMPATIBLERANGEOID)
 				declared_arg_types[j] = anycompatible_range_typeid;
+			else if (decl_type == ANYCOMPATIBLEMULTIRANGEOID)
+				declared_arg_types[j] = anycompatible_multirange_typeid;
 		}
 	}
 
@@ -2368,6 +2586,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;
+			}
 		}
 	}
 
@@ -2404,6 +2633,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 ANYCOMPATIBLE use the appropriate type */
 	if (rettype == ANYCOMPATIBLEOID ||
 		rettype == ANYCOMPATIBLENONARRAYOID)
@@ -2438,6 +2683,17 @@ enforce_generic_type_consistency(const Oid *actual_arg_types,
 		return anycompatible_range_typeid;
 	}
 
+	/* if we return ANYCOMPATIBLEMULTIRANGE use the appropriate argument type */
+	if (rettype == ANYCOMPATIBLEMULTIRANGEOID)
+	{
+		/* this error is unreachable if the function signature is valid: */
+		if (!OidIsValid(anycompatible_multirange_typeid))
+			ereport(ERROR,
+					(errcode(ERRCODE_DATATYPE_MISMATCH),
+					 errmsg_internal("could not identify anycompatiblemultirange type")));
+		return anycompatible_multirange_typeid;
+	}
+
 	/* we don't return a generic type; send back the original return type */
 	return rettype;
 }
@@ -2455,20 +2711,37 @@ check_valid_polymorphic_signature(Oid ret_type,
 								  const Oid *declared_arg_types,
 								  int nargs)
 {
-	if (ret_type == ANYRANGEOID || ret_type == ANYCOMPATIBLERANGEOID)
+	if (ret_type == ANYRANGEOID || ret_type == ANYMULTIRANGEOID)
 	{
 		/*
-		 * ANYRANGE requires an ANYRANGE input, else we can't tell which of
-		 * several range types with the same element type to use.  Likewise
-		 * for ANYCOMPATIBLERANGE.
+		 * ANYRANGE and ANYMULTIRANGE require an ANYRANGE or ANYMULTIRANGE input,
+		 * else we can't tell which of several range types with the same element
+		 * type to use.
 		 */
 		for (int i = 0; i < nargs; i++)
 		{
-			if (declared_arg_types[i] == ret_type)
+			if (declared_arg_types[i] == ANYRANGEOID ||
+				declared_arg_types[i] == ANYMULTIRANGEOID)
 				return NULL;	/* OK */
 		}
-		return psprintf(_("A result of type %s requires at least one input of type %s."),
-						format_type_be(ret_type), format_type_be(ret_type));
+		return psprintf(_("A result of type %s requires at least one input of type anyrange or anymultirange."),
+						format_type_be(ret_type));
+	}
+	else if (ret_type == ANYCOMPATIBLERANGEOID || ret_type == ANYCOMPATIBLEMULTIRANGEOID)
+	{
+		/*
+		 * ANYCOMPATIBLERANGE and ANYCOMPATIBLEMULTIRANGE require an ANYCOMPATIBLERANGE or
+		 * ANYCOMPATIBLEMULTIRANGE input, else we can't tell which of several range types
+		 * with the same element type to use.
+		 */
+		for (int i = 0; i < nargs; i++)
+		{
+			if (declared_arg_types[i] == ANYCOMPATIBLERANGEOID ||
+				declared_arg_types[i] == ANYCOMPATIBLEMULTIRANGEOID)
+				return NULL;	/* OK */
+		}
+		return psprintf(_("A result of type %s requires at least one input of type anycompatiblerange or anycompatiblemultirange."),
+						format_type_be(ret_type));
 	}
 	else if (IsPolymorphicTypeFamily1(ret_type))
 	{
@@ -2479,7 +2752,7 @@ check_valid_polymorphic_signature(Oid ret_type,
 				return NULL;	/* OK */
 		}
 		/* Keep this list in sync with IsPolymorphicTypeFamily1! */
-		return psprintf(_("A result of type %s requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange."),
+		return psprintf(_("A result of type %s requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange."),
 						format_type_be(ret_type));
 	}
 	else if (IsPolymorphicTypeFamily2(ret_type))
@@ -2631,6 +2904,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 || targettype == ANYCOMPATIBLEMULTIRANGEOID)
+		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 b4d55e849b3..22a412d4379 100644
--- a/src/backend/utils/adt/Makefile
+++ b/src/backend/utils/adt/Makefile
@@ -59,6 +59,8 @@ OBJS = \
 	mac8.o \
 	mcxtfuncs.o \
 	misc.o \
+	multirangetypes.o \
+	multirangetypes_selfuncs.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 00000000000..1240400b0e6
--- /dev/null
+++ b/src/backend/utils/adt/multirangetypes.c
@@ -0,0 +1,2536 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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/int8.h"
+#include "utils/lsyscache.h"
+#include "utils/rangetypes.h"
+#include "utils/multirangetypes.h"
+#include "utils/timestamp.h"
+#include "utils/array.h"
+#include "utils/memutils.h"
+
+/* fn_extra cache entry for one of the range I/O functions */
+typedef struct MultirangeIOData
+{
+	TypeCacheEntry *typcache;	/* multirange type's typcache entry */
+	FmgrInfo	typioproc;		/* range type's I/O proc */
+	Oid			typioparam;		/* range type's I/O parameter */
+} 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 mltrngtypid,
+												IOFuncSelector func);
+static int32 multirange_canonicalize(TypeCacheEntry *rangetyp, int32 input_range_count,
+									 RangeType **ranges);
+static void shortrange_deserialize(TypeCacheEntry *typcache,
+								   const ShortRangeType *range,
+								   RangeBound *lower, RangeBound *upper, bool *empty);
+static void shortrange_serialize(ShortRangeType *range, TypeCacheEntry *typcache,
+								 RangeBound *lower, RangeBound *upper, bool empty);
+
+/* "Methods" required for an expanded object */
+static Size EMR_get_flat_size(ExpandedObjectHeader *eohptr);
+static void EMR_flatten_into(ExpandedObjectHeader *eohptr,
+							 void *result, Size allocated_size);
+
+static const ExpandedObjectMethods EMR_methods =
+{
+	EMR_get_flat_size,
+	EMR_flatten_into
+};
+
+/*
+ *----------------------------------------------------------
+ * 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 with 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;
+	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 = NULL;
+	int32		range_str_len;
+	char	   *range_str_copy;
+
+	cache = get_multirange_io_data(fcinfo, mltrngtypoid, IOFunc_input);
+	rangetyp = cache->typcache->rngtype;
+
+	/* 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 + 1;
+					range_str_copy = pnstrdup(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->typioproc,
+																 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;
+	int32		range_count;
+	int32		i;
+	RangeType **ranges;
+
+	cache = get_multirange_io_data(fcinfo, mltrngtypoid, IOFunc_output);
+
+	initStringInfo(&buf);
+
+	appendStringInfoChar(&buf, '{');
+
+	multirange_deserialize(cache->typcache->rngtype, multirange, &range_count, &ranges);
+	for (i = 0; i < range_count; i++)
+	{
+		if (i > 0)
+			appendStringInfoChar(&buf, ',');
+		range = ranges[i];
+		rangeStr = OutputFunctionCall(&cache->typioproc, RangeTypePGetDatum(range));
+		appendStringInfoString(&buf, rangeStr);
+	}
+
+	appendStringInfoChar(&buf, '}');
+
+	PG_RETURN_CSTRING(buf.data);
+}
+
+/*
+ * Binary representation: First a int32-sized count of ranges, followed by
+ * ranges in their native binary representation.
+ */
+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;
+	uint32		range_count;
+	RangeType **ranges;
+	MultirangeType *ret;
+	StringInfoData tmpbuf;
+
+	cache = get_multirange_io_data(fcinfo, mltrngtypoid, IOFunc_receive);
+
+	range_count = pq_getmsgint(buf, 4);
+	ranges = palloc(range_count * sizeof(RangeType *));
+
+	initStringInfo(&tmpbuf);
+	for (int i = 0; i < range_count; i++)
+	{
+		uint32		range_len = pq_getmsgint(buf, 4);
+		const char *range_data = pq_getmsgbytes(buf, range_len);
+
+		resetStringInfo(&tmpbuf);
+		appendBinaryStringInfo(&tmpbuf, range_data, range_len);
+
+		ranges[i] = DatumGetRangeTypeP(ReceiveFunctionCall(&cache->typioproc,
+														   &tmpbuf,
+														   cache->typioparam,
+														   typmod));
+	}
+	pfree(tmpbuf.data);
+
+	pq_getmsgend(buf);
+
+	ret = make_multirange(mltrngtypoid, cache->typcache->rngtype,
+						  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;
+	MultirangeIOData *cache;
+
+	cache = get_multirange_io_data(fcinfo, mltrngtypoid, IOFunc_send);
+
+	/* construct output */
+	pq_begintypsend(buf);
+
+	pq_sendint32(buf, multirange->rangeCount);
+
+	multirange_deserialize(cache->typcache->rngtype, multirange, &range_count, &ranges);
+	for (int i = 0; i < range_count; i++)
+	{
+		Datum		range;
+
+		range = RangeTypePGetDatum(ranges[i]);
+		range = PointerGetDatum(SendFunctionCall(&cache->typioproc, range));
+
+		pq_sendint32(buf, VARSIZE(range) - VARHDRSZ);
+		pq_sendbytes(buf, VARDATA(range), VARSIZE(range) - VARHDRSZ);
+	}
+
+	PG_RETURN_BYTEA_P(pq_endtypsend(buf));
+}
+
+/*
+ * 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)
+	{
+		Oid			typiofunc;
+		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,
+						 &typiofunc);
+
+		if (!OidIsValid(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(typiofunc, &cache->typioproc,
+					  fcinfo->flinfo->fn_mcxt);
+
+		fcinfo->flinfo->fn_extra = (void *) cache;
+	}
+
+	return cache;
+}
+
+/*
+ * Converts a list of arbitrary ranges 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;
+}
+
+/*
+ *----------------------------------------------------------
+ * 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;
+	int			i;
+	int32		bytelen;
+	Pointer		ptr;
+	RangeBound	lower;
+	RangeBound	upper;
+	bool		empty;
+
+	/* 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 ShortRangeTypes start aligned.
+	 */
+	bytelen = MAXALIGN(sizeof(MultirangeType));
+
+	/* Count space for all ranges */
+	for (i = 0; i < range_count; i++)
+		bytelen += MAXALIGN(VARSIZE(ranges[i]) - sizeof(char));
+
+	/* 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_deserialize(rangetyp, ranges[i], &lower, &upper, &empty);
+		shortrange_serialize((ShortRangeType *) ptr, rangetyp, &lower, &upper, empty);
+		ptr += MAXALIGN(VARSIZE(ptr));
+	}
+
+	return multirange;
+}
+
+/*
+ * 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(TypeCacheEntry *rangetyp,
+					   const MultirangeType *multirange, int32 *range_count,
+					   RangeType ***ranges)
+{
+	ExpandedMultirangeHeader *emrh = (ExpandedMultirangeHeader *)
+	DatumGetEOHP(expand_multirange(MultirangeTypePGetDatum(multirange),
+								   CurrentMemoryContext, rangetyp));
+
+	*range_count = emrh->rangeCount;
+	if (*range_count == 0)
+	{
+		*ranges = NULL;
+		return;
+	}
+
+	*ranges = emrh->ranges;
+}
+
+MultirangeType *
+make_empty_multirange(Oid mltrngtypoid, TypeCacheEntry *rangetyp)
+{
+	return make_multirange(mltrngtypoid, rangetyp, 0, NULL);
+}
+
+/*
+ *----------------------------------------------------------
+ * EXPANDED TOAST FUNCTIONS
+ *----------------------------------------------------------
+ */
+
+/*
+ * expand_multirange: convert a multirange Datum into an expanded multirange
+ *
+ * The expanded object will be a child of parentcontext.
+ */
+Datum
+expand_multirange(Datum multirangedatum, MemoryContext parentcontext, TypeCacheEntry *rangetyp)
+{
+	MultirangeType *multirange;
+	ExpandedMultirangeHeader *emrh;
+	MemoryContext objcxt;
+	MemoryContext oldcxt;
+
+	/*
+	 * Allocate private context for expanded object.  We start by assuming
+	 * that the multirange won't be very large; but if it does grow a lot,
+	 * don't constrain aset.c's large-context behavior.
+	 */
+	objcxt = AllocSetContextCreate(parentcontext,
+								   "expanded multirange",
+								   ALLOCSET_START_SMALL_SIZES);
+
+	/* Set up expanded multirange header */
+	emrh = (ExpandedMultirangeHeader *)
+		MemoryContextAlloc(objcxt, sizeof(ExpandedMultirangeHeader));
+
+	EOH_init_header(&emrh->hdr, &EMR_methods, objcxt);
+	emrh->emr_magic = EMR_MAGIC;
+
+	/*
+	 * Detoast and copy source multirange into private context. We need to do
+	 * this so that we get our own copies of the upper/lower bounds.
+	 *
+	 * Note that this coding risks leaking some memory in the private context
+	 * if we have to fetch data from a TOAST table; however, experimentation
+	 * says that the leak is minimal.  Doing it this way saves a copy step,
+	 * which seems worthwhile, especially if the multirange is large enough to
+	 * need external storage.
+	 */
+	oldcxt = MemoryContextSwitchTo(objcxt);
+	multirange = DatumGetMultirangeTypePCopy(multirangedatum);
+
+	emrh->multirangetypid = multirange->multirangetypid;
+	emrh->rangeCount = multirange->rangeCount;
+
+	/* Convert each ShortRangeType into a RangeType */
+	if (emrh->rangeCount > 0)
+	{
+		Pointer		ptr;
+		Pointer		end;
+		int32		i;
+
+		emrh->ranges = palloc(emrh->rangeCount * sizeof(RangeType *));
+
+		ptr = (char *) multirange;
+		end = ptr + VARSIZE(multirange);
+		ptr = (char *) MAXALIGN(multirange + 1);
+		i = 0;
+		while (ptr < end)
+		{
+			ShortRangeType *shortrange;
+			RangeBound	lower;
+			RangeBound	upper;
+			bool		empty;
+
+			shortrange = (ShortRangeType *) ptr;
+			shortrange_deserialize(rangetyp, shortrange, &lower, &upper, &empty);
+			emrh->ranges[i++] = make_range(rangetyp, &lower, &upper, empty);
+
+			ptr += MAXALIGN(VARSIZE(shortrange));
+		}
+	}
+	else
+	{
+		emrh->ranges = NULL;
+	}
+	MemoryContextSwitchTo(oldcxt);
+
+	/* return a R/W pointer to the expanded multirange */
+	return EOHPGetRWDatum(&emrh->hdr);
+}
+
+/*
+ * shortrange_deserialize: Extract bounds and empty flag from a ShortRangeType
+ */
+void
+shortrange_deserialize(TypeCacheEntry *typcache, const ShortRangeType *range,
+					   RangeBound *lower, RangeBound *upper, bool *empty)
+{
+	char		flags;
+	int16		typlen;
+	bool		typbyval;
+	char		typalign;
+	Pointer		ptr;
+	Datum		lbound;
+	Datum		ubound;
+
+	/* fetch the flag byte */
+	flags = range->flags;
+
+	/* fetch information about range's element type */
+	typlen = typcache->rngelemtype->typlen;
+	typbyval = typcache->rngelemtype->typbyval;
+	typalign = typcache->rngelemtype->typalign;
+
+	/* initialize data pointer just after the range struct fields */
+	ptr = (Pointer) MAXALIGN(range + 1);
+
+	/* fetch lower bound, if any */
+	if (RANGE_HAS_LBOUND(flags))
+	{
+		/* att_align_pointer cannot be necessary here */
+		lbound = fetch_att(ptr, typbyval, typlen);
+		ptr = (Pointer) att_addlength_pointer(ptr, typlen, ptr);
+	}
+	else
+		lbound = (Datum) 0;
+
+	/* fetch upper bound, if any */
+	if (RANGE_HAS_UBOUND(flags))
+	{
+		ptr = (Pointer) att_align_pointer(ptr, typalign, typlen, ptr);
+		ubound = fetch_att(ptr, typbyval, typlen);
+		/* no need for att_addlength_pointer */
+	}
+	else
+		ubound = (Datum) 0;
+
+	/* emit results */
+
+	*empty = (flags & RANGE_EMPTY) != 0;
+
+	lower->val = lbound;
+	lower->infinite = (flags & RANGE_LB_INF) != 0;
+	lower->inclusive = (flags & RANGE_LB_INC) != 0;
+	lower->lower = true;
+
+	upper->val = ubound;
+	upper->infinite = (flags & RANGE_UB_INF) != 0;
+	upper->inclusive = (flags & RANGE_UB_INC) != 0;
+	upper->lower = false;
+}
+
+/*
+ * get_flat_size method for expanded multirange
+ */
+static Size
+EMR_get_flat_size(ExpandedObjectHeader *eohptr)
+{
+	ExpandedMultirangeHeader *emrh = (ExpandedMultirangeHeader *) eohptr;
+	RangeType  *range;
+	Size		nbytes;
+	int			i;
+
+	Assert(emrh->emr_magic == EMR_MAGIC);
+
+	/* If we have a cached size value, believe that */
+	if (emrh->flat_size)
+		return emrh->flat_size;
+
+	/*
+	 * Compute space needed by examining ranges.
+	 */
+	nbytes = MAXALIGN(sizeof(MultirangeType));
+	for (i = 0; i < emrh->rangeCount; i++)
+	{
+		range = emrh->ranges[i];
+		nbytes += MAXALIGN(VARSIZE(range) - sizeof(char));
+		/* check for overflow of total request */
+		if (!AllocSizeIsValid(nbytes))
+			ereport(ERROR,
+					(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
+					 errmsg("multirange size exceeds the maximum allowed (%d)",
+							(int) MaxAllocSize)));
+	}
+
+	/* cache for next time */
+	emrh->flat_size = nbytes;
+
+	return nbytes;
+}
+
+/*
+ * flatten_into method for expanded multiranges
+ */
+static void
+EMR_flatten_into(ExpandedObjectHeader *eohptr,
+				 void *result, Size allocated_size)
+{
+	ExpandedMultirangeHeader *emrh = (ExpandedMultirangeHeader *) eohptr;
+	MultirangeType *mrresult = (MultirangeType *) result;
+	TypeCacheEntry *typcache;
+	int			rangeCount;
+	RangeType **ranges;
+	RangeBound	lower;
+	RangeBound	upper;
+	bool		empty;
+	Pointer		ptr;
+	int			i;
+
+	Assert(emrh->emr_magic == EMR_MAGIC);
+
+	typcache = lookup_type_cache(emrh->multirangetypid, TYPECACHE_MULTIRANGE_INFO);
+	if (typcache->rngtype == NULL)
+		elog(ERROR, "type %u is not a multirange type", emrh->multirangetypid);
+
+	/* Fill result multirange from RangeTypes */
+	rangeCount = emrh->rangeCount;
+	ranges = emrh->ranges;
+
+	/* We must ensure that any pad space is zero-filled */
+	memset(mrresult, 0, allocated_size);
+
+	SET_VARSIZE(mrresult, allocated_size);
+
+	/* Now fill in the datum with ShortRangeTypes */
+	mrresult->multirangetypid = emrh->multirangetypid;
+	mrresult->rangeCount = rangeCount;
+
+	ptr = (char *) MAXALIGN(mrresult + 1);
+	for (i = 0; i < rangeCount; i++)
+	{
+		range_deserialize(typcache->rngtype, ranges[i], &lower, &upper, &empty);
+		shortrange_serialize((ShortRangeType *) ptr, typcache->rngtype, &lower, &upper, empty);
+		ptr += MAXALIGN(VARSIZE(ptr));
+	}
+}
+
+/*
+ * shortrange_serialize: fill in pre-allocationed range param based on the
+ * given bounds and flags.
+ */
+static void
+shortrange_serialize(ShortRangeType *range, TypeCacheEntry *typcache,
+					 RangeBound *lower, RangeBound *upper, bool empty)
+{
+	int			cmp;
+	Size		msize;
+	Pointer		ptr;
+	int16		typlen;
+	bool		typbyval;
+	char		typalign;
+	char		typstorage;
+	char		flags = 0;
+
+	/*
+	 * Verify range is not invalid on its face, and construct flags value,
+	 * preventing any non-canonical combinations such as infinite+inclusive.
+	 */
+	Assert(lower->lower);
+	Assert(!upper->lower);
+
+	if (empty)
+		flags |= RANGE_EMPTY;
+	else
+	{
+		cmp = range_cmp_bound_values(typcache, lower, upper);
+
+		/* error check: if lower bound value is above upper, it's wrong */
+		if (cmp > 0)
+			ereport(ERROR,
+					(errcode(ERRCODE_DATA_EXCEPTION),
+					 errmsg("range lower bound must be less than or equal to range upper bound")));
+
+		/* if bounds are equal, and not both inclusive, range is empty */
+		if (cmp == 0 && !(lower->inclusive && upper->inclusive))
+			flags |= RANGE_EMPTY;
+		else
+		{
+			/* infinite boundaries are never inclusive */
+			if (lower->infinite)
+				flags |= RANGE_LB_INF;
+			else if (lower->inclusive)
+				flags |= RANGE_LB_INC;
+			if (upper->infinite)
+				flags |= RANGE_UB_INF;
+			else if (upper->inclusive)
+				flags |= RANGE_UB_INC;
+		}
+	}
+
+	/* Fetch information about range's element type */
+	typlen = typcache->rngelemtype->typlen;
+	typbyval = typcache->rngelemtype->typbyval;
+	typalign = typcache->rngelemtype->typalign;
+	typstorage = typcache->rngelemtype->typstorage;
+
+	/* Count space for varlena header */
+	msize = sizeof(ShortRangeType);
+	Assert(msize == MAXALIGN(msize));
+
+	/* Count space for bounds */
+	if (RANGE_HAS_LBOUND(flags))
+	{
+		/*
+		 * Make sure item to be inserted is not toasted.  It is essential that
+		 * we not insert an out-of-line toast value pointer into a range
+		 * object, for the same reasons that arrays and records can't contain
+		 * them.  It would work to store a compressed-in-line value, but we
+		 * prefer to decompress and then let compression be applied to the
+		 * whole range object if necessary.  But, unlike arrays, we do allow
+		 * short-header varlena objects to stay as-is.
+		 */
+		if (typlen == -1)
+			lower->val = PointerGetDatum(PG_DETOAST_DATUM_PACKED(lower->val));
+
+		msize = datum_compute_size(msize, lower->val, typbyval, typalign,
+								   typlen, typstorage);
+	}
+
+	if (RANGE_HAS_UBOUND(flags))
+	{
+		/* Make sure item to be inserted is not toasted */
+		if (typlen == -1)
+			upper->val = PointerGetDatum(PG_DETOAST_DATUM_PACKED(upper->val));
+
+		msize = datum_compute_size(msize, upper->val, typbyval, typalign,
+								   typlen, typstorage);
+	}
+
+	SET_VARSIZE(range, msize);
+
+	ptr = (char *) (range + 1);
+
+	if (RANGE_HAS_LBOUND(flags))
+	{
+		Assert(lower->lower);
+		ptr = datum_write(ptr, lower->val, typbyval, typalign, typlen,
+						  typstorage);
+	}
+
+	if (RANGE_HAS_UBOUND(flags))
+	{
+		Assert(!upper->lower);
+		ptr = datum_write(ptr, upper->val, typbyval, typalign, typlen,
+						  typstorage);
+	}
+
+	range->flags = flags;
+}
+
+
+/*
+ *----------------------------------------------------------
+ * 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_constructor2(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,
+				(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+				 errmsg("multirange values cannot contain NULL members")));
+
+	rangeArray = PG_GETARG_ARRAYTYPE_P(0);
+
+	dims = ARR_NDIM(rangeArray);
+	if (dims > 1)
+		ereport(ERROR,
+				(errcode(ERRCODE_CARDINALITY_VIOLATION),
+				 errmsg("multiranges cannot be constructed from multi-dimensional arrays")));
+
+	rngtypid = ARR_ELEMTYPE(rangeArray);
+	if (rngtypid != rangetyp->type_id)
+		ereport(ERROR,
+				(errcode(ERRCODE_DATATYPE_MISMATCH),
+				 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,
+						(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+						 errmsg("multirange values cannot contain NULL members")));
+
+			/* make_multirange will do its own copy */
+			ranges[i] = DatumGetRangeTypeP(elements[i]);
+		}
+	}
+
+	PG_RETURN_MULTIRANGE_P(make_multirange(mltrngtypid, rangetyp, range_count, ranges));
+}
+
+/*
+ * Construct multirange value from a single range.
+ * It'd be nice if we could just use multirange_constructor2
+ * for this case, but we need a non-variadic single-arg function
+ * to let us define a CAST from a range to its multirange.
+ */
+Datum
+multirange_constructor1(PG_FUNCTION_ARGS)
+{
+	Oid			mltrngtypid = get_fn_expr_rettype(fcinfo->flinfo);
+	Oid			rngtypid;
+	TypeCacheEntry *typcache;
+	TypeCacheEntry *rangetyp;
+	RangeType  *range;
+
+	typcache = multirange_get_typcache(fcinfo, mltrngtypid);
+	rangetyp = typcache->rngtype;
+
+	/*
+	 * These checks should be guaranteed by our signature, but let's do them
+	 * just in case.
+	 */
+
+	if (PG_ARGISNULL(0))
+		ereport(ERROR,
+				(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+				 errmsg("multirange values cannot contain NULL members")));
+
+	range = PG_GETARG_RANGE_P(0);
+
+	/* Make sure the range type matches. */
+	rngtypid = RangeTypeGetOid(range);
+	if (rngtypid != rangetyp->type_id)
+		ereport(ERROR,
+				(errcode(ERRCODE_DATATYPE_MISMATCH),
+				 errmsg("type %u does not match constructor type", rngtypid)));
+
+	PG_RETURN_MULTIRANGE_P(make_multirange(mltrngtypid, rangetyp, 1, &range));
+}
+
+/*
+ * 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
+		elog(ERROR,				/* can't happen */
+			 "niladic multirange constructor must not receive arguments");
+}
+
+
+/* multirange, multirange -> multirange type functions */
+
+/* multirange union */
+Datum
+multirange_union(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
+	MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count1;
+	int32		range_count2;
+	int32		range_count3;
+	RangeType **ranges1;
+	RangeType **ranges2;
+	RangeType **ranges3;
+
+	if (MultirangeIsEmpty(mr1))
+		PG_RETURN_MULTIRANGE_P(mr2);
+	if (MultirangeIsEmpty(mr2))
+		PG_RETURN_MULTIRANGE_P(mr1);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr1));
+
+	multirange_deserialize(typcache->rngtype, mr1, &range_count1, &ranges1);
+	multirange_deserialize(typcache->rngtype, mr2, &range_count2, &ranges2);
+
+	range_count3 = range_count1 + range_count2;
+	ranges3 = palloc0(range_count3 * sizeof(RangeType *));
+	memcpy(ranges3, ranges1, range_count1 * sizeof(RangeType *));
+	memcpy(ranges3 + range_count1, ranges2, range_count2 * sizeof(RangeType *));
+	PG_RETURN_MULTIRANGE_P(make_multirange(typcache->type_id, typcache->rngtype,
+										   range_count3, ranges3));
+}
+
+/* multirange minus */
+Datum
+multirange_minus(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
+	MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
+	Oid			mltrngtypoid = MultirangeTypeGetOid(mr1);
+	TypeCacheEntry *typcache;
+	TypeCacheEntry *rangetyp;
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+
+	typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
+	rangetyp = typcache->rngtype;
+
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		PG_RETURN_MULTIRANGE_P(mr1);
+
+	multirange_deserialize(typcache->rngtype, mr1, &range_count1, &ranges1);
+	multirange_deserialize(typcache->rngtype, mr2, &range_count2, &ranges2);
+
+	PG_RETURN_MULTIRANGE_P(multirange_minus_internal(mltrngtypoid,
+													 rangetyp,
+													 range_count1,
+													 ranges1,
+													 range_count2,
+													 ranges2));
+}
+
+MultirangeType *
+multirange_minus_internal(Oid mltrngtypoid, TypeCacheEntry *rangetyp,
+						  int32 range_count1, RangeType **ranges1,
+						  int32 range_count2, RangeType **ranges2)
+{
+	RangeType  *r1;
+	RangeType  *r2;
+	RangeType **ranges3;
+	int32		range_count3;
+	int32		i1;
+	int32		i2;
+
+	/*
+	 * Worst case: every range in ranges1 makes a different cut to some range
+	 * in ranges2.
+	 */
+	ranges3 = palloc0((range_count1 + range_count2) * sizeof(RangeType *));
+	range_count3 = 0;
+
+	/*
+	 * For each range in mr1, keep subtracting until it's gone or the ranges
+	 * in mr2 have passed it. After a subtraction we assign what's left back
+	 * to r1. The parallel progress through mr1 and mr2 is similar to
+	 * multirange_overlaps_multirange_internal.
+	 */
+	r2 = ranges2[0];
+	for (i1 = 0, i2 = 0; i1 < range_count1; i1++)
+	{
+		r1 = ranges1[i1];
+
+		/* Discard r2s while r2 << r1 */
+		while (r2 != NULL && range_before_internal(rangetyp, r2, r1))
+		{
+			r2 = ++i2 >= range_count2 ? NULL : ranges2[i2];
+		}
+
+		while (r2 != NULL)
+		{
+			if (range_split_internal(rangetyp, r1, r2, &ranges3[range_count3], &r1))
+			{
+				/*
+				 * If r2 takes a bite out of the middle of r1, we need two
+				 * outputs
+				 */
+				range_count3++;
+				r2 = ++i2 >= range_count2 ? NULL : ranges2[i2];
+
+			}
+			else if (range_overlaps_internal(rangetyp, r1, r2))
+			{
+				/*
+				 * If r2 overlaps r1, replace r1 with r1 - r2.
+				 */
+				r1 = range_minus_internal(rangetyp, r1, r2);
+
+				/*
+				 * If r2 goes past r1, then we need to stay with it, in case
+				 * it hits future r1s. Otherwise we need to keep r1, in case
+				 * future r2s hit it. Since we already subtracted, there's no
+				 * point in using the overright/overleft calls.
+				 */
+				if (RangeIsEmpty(r1) || range_before_internal(rangetyp, r1, r2))
+					break;
+				else
+					r2 = ++i2 >= range_count2 ? NULL : ranges2[i2];
+
+			}
+			else
+			{
+				/*
+				 * This and all future r2s are past r1, so keep them. Also
+				 * assign whatever is left of r1 to the result.
+				 */
+				break;
+			}
+		}
+
+		/*
+		 * Nothing else can remove anything from r1, so keep it. Even if r1 is
+		 * empty here, make_multirange will remove it.
+		 */
+		ranges3[range_count3++] = r1;
+	}
+
+	return make_multirange(mltrngtypoid, rangetyp, range_count3, ranges3);
+}
+
+/* multirange intersection */
+Datum
+multirange_intersect(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
+	MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
+	Oid			mltrngtypoid = MultirangeTypeGetOid(mr1);
+	TypeCacheEntry *typcache;
+	TypeCacheEntry *rangetyp;
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+
+	typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
+	rangetyp = typcache->rngtype;
+
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		PG_RETURN_MULTIRANGE_P(make_empty_multirange(mltrngtypoid, rangetyp));
+
+	multirange_deserialize(rangetyp, mr1, &range_count1, &ranges1);
+	multirange_deserialize(rangetyp, mr2, &range_count2, &ranges2);
+
+	PG_RETURN_MULTIRANGE_P(multirange_intersect_internal(mltrngtypoid,
+														 rangetyp,
+														 range_count1,
+														 ranges1,
+														 range_count2,
+														 ranges2));
+}
+
+MultirangeType *
+multirange_intersect_internal(Oid mltrngtypoid, TypeCacheEntry *rangetyp,
+							  int32 range_count1, RangeType **ranges1,
+							  int32 range_count2, RangeType **ranges2)
+{
+	RangeType  *r1;
+	RangeType  *r2;
+	RangeType **ranges3;
+	int32		range_count3;
+	int32		i1;
+	int32		i2;
+
+	if (range_count1 == 0 || range_count2 == 0)
+		return make_multirange(mltrngtypoid, rangetyp, 0, NULL);
+
+	/*-----------------------------------------------
+	 * Worst case is a stitching pattern like this:
+	 *
+	 * mr1: --- --- --- ---
+	 * mr2:   --- --- ---
+	 * mr3:   - - - - - -
+	 *
+	 * That seems to be range_count1 + range_count2 - 1,
+	 * but one extra won't hurt.
+	 *-----------------------------------------------
+	 */
+	ranges3 = palloc0((range_count1 + range_count2) * sizeof(RangeType *));
+	range_count3 = 0;
+
+	/*
+	 * For each range in mr1, keep intersecting until the ranges in mr2 have
+	 * passed it. The parallel progress through mr1 and mr2 is similar to
+	 * multirange_minus_multirange_internal, but we don't have to assign back
+	 * to r1.
+	 */
+	r2 = ranges2[0];
+	for (i1 = 0, i2 = 0; i1 < range_count1; i1++)
+	{
+		r1 = ranges1[i1];
+
+		/* Discard r2s while r2 << r1 */
+		while (r2 != NULL && range_before_internal(rangetyp, r2, r1))
+		{
+			r2 = ++i2 >= range_count2 ? NULL : ranges2[i2];
+		}
+
+		while (r2 != NULL)
+		{
+			if (range_overlaps_internal(rangetyp, r1, r2))
+			{
+				/* Keep the overlapping part */
+				ranges3[range_count3++] = range_intersect_internal(rangetyp, r1, r2);
+
+				/* If we "used up" all of r2, go to the next one... */
+				if (range_overleft_internal(rangetyp, r2, r1))
+					r2 = ++i2 >= range_count2 ? NULL : ranges2[i2];
+
+				/* ...otherwise go to the next r1 */
+				else
+					break;
+			}
+			else
+				/* We're past r1, so move to the next one */
+				break;
+		}
+
+		/* If we're out of r2s, there can be no more intersections */
+		if (r2 == NULL)
+			break;
+	}
+
+	return make_multirange(mltrngtypoid, rangetyp, range_count3, ranges3);
+}
+
+/*
+ * range_agg_transfn: combine adjacent/overlapping ranges.
+ *
+ * All we do here is gather the input ranges into an array
+ * so that the finalfn can sort and combine them.
+ */
+Datum
+range_agg_transfn(PG_FUNCTION_ARGS)
+{
+	MemoryContext aggContext;
+	Oid			rngtypoid;
+	ArrayBuildState *state;
+
+	if (!AggCheckCallContext(fcinfo, &aggContext))
+		elog(ERROR, "range_agg_transfn called in non-aggregate context");
+
+	rngtypoid = get_fn_expr_argtype(fcinfo->flinfo, 1);
+	if (!type_is_range(rngtypoid))
+		ereport(ERROR,
+				(errcode(ERRCODE_DATATYPE_MISMATCH),
+				 errmsg("range_agg must be called with a range")));
+
+	if (PG_ARGISNULL(0))
+		state = initArrayResult(rngtypoid, aggContext, false);
+	else
+		state = (ArrayBuildState *) PG_GETARG_POINTER(0);
+
+	/* skip NULLs */
+	if (!PG_ARGISNULL(1))
+		accumArrayResult(state, PG_GETARG_DATUM(1), false, rngtypoid, aggContext);
+
+	PG_RETURN_POINTER(state);
+}
+
+/*
+ * range_agg_finalfn: use our internal array to merge touching ranges.
+ */
+Datum
+range_agg_finalfn(PG_FUNCTION_ARGS)
+{
+	MemoryContext aggContext;
+	Oid			mltrngtypoid;
+	TypeCacheEntry *typcache;
+	ArrayBuildState *state;
+	int32		range_count;
+	RangeType **ranges;
+	int			i;
+
+	if (!AggCheckCallContext(fcinfo, &aggContext))
+		elog(ERROR, "range_agg_finalfn called in non-aggregate context");
+
+	state = PG_ARGISNULL(0) ? NULL : (ArrayBuildState *) PG_GETARG_POINTER(0);
+	if (state == NULL)
+		/* This shouldn't be possible, but just in case.... */
+		PG_RETURN_NULL();
+
+	/* Also return NULL if we had zero inputs, like other aggregates */
+	range_count = state->nelems;
+	if (range_count == 0)
+		PG_RETURN_NULL();
+
+	mltrngtypoid = get_fn_expr_rettype(fcinfo->flinfo);
+	typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
+
+	ranges = palloc0(range_count * sizeof(RangeType *));
+	for (i = 0; i < range_count; i++)
+		ranges[i] = DatumGetRangeTypeP(state->dvalues[i]);
+
+	PG_RETURN_MULTIRANGE_P(make_multirange(mltrngtypoid, typcache->rngtype, range_count, ranges));
+}
+
+Datum
+multirange_intersect_agg_transfn(PG_FUNCTION_ARGS)
+{
+	MemoryContext aggContext;
+	Oid			mltrngtypoid;
+	TypeCacheEntry *typcache;
+	MultirangeType *result;
+	MultirangeType *current;
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+
+	if (!AggCheckCallContext(fcinfo, &aggContext))
+		elog(ERROR, "multirange_intersect_agg_transfn called in non-aggregate context");
+
+	mltrngtypoid = get_fn_expr_argtype(fcinfo->flinfo, 1);
+	if (!type_is_multirange(mltrngtypoid))
+		ereport(ERROR,
+				(errcode(ERRCODE_DATATYPE_MISMATCH),
+				 errmsg("range_intersect_agg must be called with a multirange")));
+
+	typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
+
+	/* strictness ensures these are non-null */
+	result = PG_GETARG_MULTIRANGE_P(0);
+	current = PG_GETARG_MULTIRANGE_P(1);
+
+	multirange_deserialize(typcache->rngtype, result, &range_count1, &ranges1);
+	multirange_deserialize(typcache->rngtype, current, &range_count2, &ranges2);
+
+	result = multirange_intersect_internal(mltrngtypoid,
+										   typcache->rngtype,
+										   range_count1,
+										   ranges1,
+										   range_count2,
+										   ranges2);
+	PG_RETURN_RANGE_P(result);
+}
+
+
+/* multirange -> element type functions */
+
+/* extract lower bound value */
+Datum
+multirange_lower(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+	bool		isnull;
+	Datum		result;
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_NULL();
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(typcache->rngtype, mr, &range_count, &ranges);
+
+	result = range_lower_internal(typcache->rngtype, ranges[0], &isnull);
+
+	if (isnull)
+		PG_RETURN_NULL();
+	else
+		PG_RETURN_DATUM(result);
+}
+
+/* extract upper bound value */
+Datum
+multirange_upper(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+	bool		isnull;
+	Datum		result;
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_NULL();
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(typcache->rngtype, mr, &range_count, &ranges);
+
+	result = range_upper_internal(typcache->rngtype, ranges[range_count - 1], &isnull);
+
+	if (isnull)
+		PG_RETURN_NULL();
+	else
+		PG_RETURN_DATUM(result);
+}
+
+
+/* multirange -> bool functions */
+
+/* is multirange empty? */
+Datum
+multirange_empty(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+
+	PG_RETURN_BOOL(mr->rangeCount == 0);
+}
+
+/* is lower bound inclusive? */
+Datum
+multirange_lower_inc(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+	multirange_deserialize(typcache->rngtype, mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_has_flag(ranges[0], RANGE_LB_INC));
+}
+
+/* is upper bound inclusive? */
+Datum
+multirange_upper_inc(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+	multirange_deserialize(typcache->rngtype, mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_has_flag(ranges[range_count - 1], RANGE_UB_INC));
+}
+
+/* is lower bound infinite? */
+Datum
+multirange_lower_inf(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+	multirange_deserialize(typcache->rngtype, mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_has_flag(ranges[0], RANGE_LB_INF));
+}
+
+/* is upper bound infinite? */
+Datum
+multirange_upper_inf(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+	multirange_deserialize(typcache->rngtype, mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_has_flag(ranges[range_count - 1], RANGE_UB_INF));
+}
+
+
+
+/* multirange, element -> bool functions */
+
+/* contains? */
+Datum
+multirange_contains_elem(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	Datum		val = PG_GETARG_DATUM(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(multirange_contains_elem_internal(typcache, mr, val));
+}
+
+/* contained by? */
+Datum
+elem_contained_by_multirange(PG_FUNCTION_ARGS)
+{
+	Datum		val = PG_GETARG_DATUM(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(multirange_contains_elem_internal(typcache, mr, val));
+}
+
+/*
+ * Test whether multirange mr contains a specific element value.
+ */
+bool
+multirange_contains_elem_internal(TypeCacheEntry *typcache, MultirangeType *mr, Datum val)
+{
+	TypeCacheEntry *rangetyp;
+	int32		range_count;
+	RangeType **ranges;
+	RangeType  *r;
+	int			i;
+
+	rangetyp = typcache->rngtype;
+
+	multirange_deserialize(rangetyp, mr, &range_count, &ranges);
+
+	for (i = 0; i < range_count; i++)
+	{
+		r = ranges[i];
+		if (range_contains_elem_internal(rangetyp, r, val))
+			return true;
+	}
+
+	return false;
+}
+
+/* multirange, range -> bool functions */
+
+/* contains? */
+Datum
+multirange_contains_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(multirange_contains_range_internal(typcache, mr, r));
+}
+
+/* contained by? */
+Datum
+range_contained_by_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(multirange_contains_range_internal(typcache, mr, r));
+}
+
+/*
+ * Test whether multirange mr contains a specific range r.
+ */
+bool
+multirange_contains_range_internal(TypeCacheEntry *typcache, MultirangeType *mr, RangeType *r)
+{
+	TypeCacheEntry *rangetyp;
+	int32		range_count;
+	RangeType **ranges;
+	RangeType  *mrr;
+	int			i;
+
+	rangetyp = typcache->rngtype;
+
+	/*
+	 * Every multirange contains an infinite number of empty ranges, even an
+	 * empty one.
+	 */
+	if (RangeIsEmpty(r))
+		return true;
+
+	multirange_deserialize(rangetyp, mr, &range_count, &ranges);
+
+	for (i = 0; i < range_count; i++)
+	{
+		mrr = ranges[i];
+		if (range_contains_internal(rangetyp, mrr, r))
+			return true;
+	}
+
+	return false;
+}
+
+
+/* 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(typcache->rngtype, mr1, &range_count_1, &ranges1);
+	multirange_deserialize(typcache->rngtype, 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));
+}
+
+/* overlaps? */
+Datum
+range_overlaps_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(range_overlaps_multirange_internal(typcache, r, mr));
+}
+
+Datum
+multirange_overlaps_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(range_overlaps_multirange_internal(typcache, r, mr));
+}
+
+Datum
+multirange_overlaps_multirange(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_overlaps_multirange_internal(typcache, mr1, mr2));
+}
+
+bool
+range_overlaps_multirange_internal(TypeCacheEntry *typcache, RangeType *r, MultirangeType *mr)
+{
+	TypeCacheEntry *rangetyp;
+	int32		range_count;
+	int32		i;
+	RangeType **ranges;
+	RangeType  *mrr;
+
+	/*
+	 * Empties never overlap, even with empties. (This seems strange since
+	 * they *do* contain each other, but we want to follow how ranges work.)
+	 */
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		return false;
+
+	rangetyp = typcache->rngtype;
+
+	multirange_deserialize(rangetyp, mr, &range_count, &ranges);
+
+	/* Scan through mrr and once it gets to r they either overlap or not */
+	mrr = ranges[0];
+
+	/* Discard mrrs while mrr << r */
+	i = 0;
+	while (range_before_internal(rangetyp, mrr, r))
+	{
+		if (++i >= range_count)
+			return false;
+		mrr = ranges[i];
+	}
+
+	/* Now either we overlap or we passed r */
+	return range_overlaps_internal(rangetyp, mrr, r);
+}
+
+bool
+multirange_overlaps_multirange_internal(TypeCacheEntry *typcache, MultirangeType *mr1,
+										MultirangeType *mr2)
+{
+	TypeCacheEntry *rangetyp;
+	int32		range_count1;
+	int32		range_count2;
+	int32		i1;
+	int32		i2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+	RangeType  *r1;
+	RangeType  *r2;
+
+	/*
+	 * Empties never overlap, even with empties. (This seems strange since
+	 * they *do* contain each other, but we want to follow how ranges work.)
+	 */
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		return false;
+
+	rangetyp = typcache->rngtype;
+
+	multirange_deserialize(rangetyp, mr1, &range_count1, &ranges1);
+	multirange_deserialize(rangetyp, mr2, &range_count2, &ranges2);
+
+	/*
+	 * Every range in mr1 gets a chance to overlap with the ranges in mr2, but
+	 * we can use their ordering to avoid O(n^2). This is similar to
+	 * range_overlaps_multirange where r1 : r2 :: mrr : r, but there if we
+	 * don't find an overlap with r we're done, and here if we don't find an
+	 * overlap with r2 we try the next r2.
+	 */
+	r1 = ranges1[0];
+	for (i1 = 0, i2 = 0; i2 < range_count2; i2++)
+	{
+		r2 = ranges2[i2];
+
+		/* Discard r1s while r1 << r2 */
+		while (range_before_internal(rangetyp, r1, r2))
+		{
+			if (++i1 >= range_count1)
+				return false;
+			r1 = ranges1[i1];
+		}
+
+		/*
+		 * If r1 && r2, we're done, otherwise we failed to find an overlap for
+		 * r2, so go to the next one.
+		 */
+		if (range_overlaps_internal(rangetyp, r1, r2))
+			return true;
+	}
+
+	/* We looked through all of mr2 without finding an overlap */
+	return false;
+}
+
+/* does not extend to right of? */
+Datum
+range_overleft_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(typcache->rngtype, mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_overleft_internal(typcache->rngtype, r, ranges[range_count - 1]));
+}
+
+Datum
+multirange_overleft_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(typcache->rngtype, mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_overleft_internal(typcache->rngtype,
+										   ranges[range_count - 1], r));
+}
+
+Datum
+multirange_overleft_multirange(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
+	MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr1));
+
+	multirange_deserialize(typcache->rngtype, mr1, &range_count1, &ranges1);
+	multirange_deserialize(typcache->rngtype, mr2, &range_count2, &ranges2);
+
+	PG_RETURN_BOOL(range_overleft_internal(typcache->rngtype,
+										   ranges1[range_count1 - 1],
+										   ranges2[range_count2 - 1]));
+}
+
+/* does not extend to left of? */
+Datum
+range_overright_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(typcache->rngtype, mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_overright_internal(typcache->rngtype, r, ranges[0]));
+}
+
+Datum
+multirange_overright_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(typcache->rngtype, mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_overright_internal(typcache->rngtype, ranges[0], r));
+}
+
+Datum
+multirange_overright_multirange(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
+	MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr1));
+
+	multirange_deserialize(typcache->rngtype, mr1, &range_count1, &ranges1);
+	multirange_deserialize(typcache->rngtype, mr2, &range_count2, &ranges2);
+
+	PG_RETURN_BOOL(range_overright_internal(typcache->rngtype,
+											ranges1[0],
+											ranges2[0]));
+}
+
+/* contains? */
+Datum
+multirange_contains_multirange(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_contains_multirange_internal(typcache, mr1, mr2));
+}
+
+/* contained by? */
+Datum
+multirange_contained_by_multirange(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_contains_multirange_internal(typcache, mr2, mr1));
+}
+
+/*
+ * Test whether multirange mr1 contains every range from another multirange mr2.
+ */
+bool
+multirange_contains_multirange_internal(TypeCacheEntry *typcache,
+										MultirangeType *mr1, MultirangeType *mr2)
+{
+	TypeCacheEntry *rangetyp;
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+	RangeType  *r1;
+	RangeType  *r2;
+	int			i1,
+				i2;
+
+	rangetyp = typcache->rngtype;
+
+	multirange_deserialize(typcache->rngtype, mr1, &range_count1, &ranges1);
+	multirange_deserialize(typcache->rngtype, mr2, &range_count2, &ranges2);
+
+	/*
+	 * We follow the same logic for empties as ranges: - an empty multirange
+	 * contains an empty range/multirange. - an empty multirange can't contain
+	 * any other range/multirange. - an empty multirange is contained by any
+	 * other range/multirange.
+	 */
+
+	if (range_count2 == 0)
+		return true;
+	if (range_count1 == 0)
+		return false;
+
+	/*
+	 * Every range in mr2 must be contained by some range in mr1. To avoid
+	 * O(n^2) we walk through both ranges in tandem.
+	 */
+	r1 = ranges1[0];
+	for (i1 = 0, i2 = 0; i2 < range_count2; i2++)
+	{
+		r2 = ranges2[i2];
+
+		/* Discard r1s while r1 << r2 */
+		while (range_before_internal(rangetyp, r1, r2))
+		{
+			if (++i1 >= range_count1)
+				return false;
+			r1 = ranges1[i1];
+		}
+
+		/*
+		 * If r1 @> r2, go to the next r2, otherwise return false (since every
+		 * r1[n] and r1[n+1] must have a gap). Note this will give weird
+		 * answers if you don't canonicalize, e.g. with a custom
+		 * int2multirange {[1,1], [2,2]} there is a "gap". But that is
+		 * consistent with other range operators, e.g. '[1,1]'::int2range -|-
+		 * '[2,2]'::int2range is false.
+		 */
+		if (!range_contains_internal(rangetyp, r1, r2))
+			return false;
+	}
+
+	/* All ranges in mr2 are satisfied */
+	return true;
+}
+
+/* strictly left of? */
+Datum
+range_before_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(range_before_multirange_internal(typcache, r, mr));
+}
+
+Datum
+multirange_before_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(range_after_multirange_internal(typcache, r, mr));
+}
+
+Datum
+multirange_before_multirange(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_before_multirange_internal(typcache, mr1, mr2));
+}
+
+/* strictly right of? */
+Datum
+range_after_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(range_after_multirange_internal(typcache, r, mr));
+}
+
+Datum
+multirange_after_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(range_before_multirange_internal(typcache, r, mr));
+}
+
+Datum
+multirange_after_multirange(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_before_multirange_internal(typcache, mr2, mr1));
+}
+
+/* strictly left of? (internal version) */
+bool
+range_before_multirange_internal(TypeCacheEntry *typcache, RangeType *r,
+								 MultirangeType *mr)
+{
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		return false;
+
+	multirange_deserialize(typcache->rngtype, mr, &range_count, &ranges);
+
+	return range_before_internal(typcache->rngtype, r, ranges[0]);
+}
+
+bool
+multirange_before_multirange_internal(TypeCacheEntry *typcache, MultirangeType *mr1,
+									  MultirangeType *mr2)
+{
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		return false;
+
+	multirange_deserialize(typcache->rngtype, mr1, &range_count1, &ranges1);
+	multirange_deserialize(typcache->rngtype, mr2, &range_count2, &ranges2);
+
+	return range_before_internal(typcache->rngtype, ranges1[range_count1 - 1],
+								 ranges2[0]);
+}
+
+/* strictly right of? (internal version) */
+bool
+range_after_multirange_internal(TypeCacheEntry *typcache, RangeType *r,
+								MultirangeType *mr)
+{
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		return false;
+
+	multirange_deserialize(typcache->rngtype, mr, &range_count, &ranges);
+
+	return range_after_internal(typcache->rngtype, r, ranges[range_count - 1]);
+}
+
+/* adjacent to? */
+Datum
+range_adjacent_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		return false;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(typcache->rngtype, mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_adjacent_internal(typcache->rngtype, r, ranges[0]));
+}
+
+Datum
+multirange_adjacent_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		return false;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(typcache->rngtype, mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_adjacent_internal(typcache->rngtype, ranges[range_count - 1], r));
+}
+
+Datum
+multirange_adjacent_multirange(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
+	MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		return false;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr1));
+
+	multirange_deserialize(typcache->rngtype, mr1, &range_count1, &ranges1);
+	multirange_deserialize(typcache->rngtype, mr2, &range_count2, &ranges2);
+
+	PG_RETURN_BOOL(range_adjacent_internal(typcache->rngtype, ranges1[range_count1 - 1], ranges2[0]));
+}
+
+/* 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(typcache->rngtype, mr1, &range_count_1, &ranges1);
+	multirange_deserialize(typcache->rngtype, 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);
+}
+
+/* multirange -> range functions */
+
+/* Find the smallest range that includes everything in the multirange */
+Datum
+range_merge_from_multirange(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	Oid			mltrngtypoid = MultirangeTypeGetOid(mr);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_RANGE_P(make_empty_range(typcache->rngtype));
+
+	multirange_deserialize(typcache->rngtype, mr, &range_count, &ranges);
+
+	PG_RETURN_RANGE_P(range_union_internal(typcache->rngtype, ranges[0],
+										   ranges[range_count - 1], false));
+}
+
+/* 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(typcache->rngtype, 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(typcache->rngtype, 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/multirangetypes_selfuncs.c b/src/backend/utils/adt/multirangetypes_selfuncs.c
new file mode 100644
index 00000000000..ff0a81a49ad
--- /dev/null
+++ b/src/backend/utils/adt/multirangetypes_selfuncs.c
@@ -0,0 +1,1297 @@
+/*-------------------------------------------------------------------------
+ *
+ * multirangetypes_selfuncs.c
+ *	  Functions for selectivity estimation of multirange operators
+ *
+ * Estimates are based on histograms of lower and upper bounds, and the
+ * fraction of empty multiranges.
+ *
+ * Portions Copyright (c) 1996-2020, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ *	  src/backend/utils/adt/multirangetypes_selfuncs.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include <math.h>
+
+#include "access/htup_details.h"
+#include "catalog/pg_operator.h"
+#include "catalog/pg_statistic.h"
+#include "catalog/pg_type.h"
+#include "utils/float.h"
+#include "utils/fmgrprotos.h"
+#include "utils/lsyscache.h"
+#include "utils/rangetypes.h"
+#include "utils/multirangetypes.h"
+#include "utils/selfuncs.h"
+#include "utils/typcache.h"
+
+static double calc_multirangesel(TypeCacheEntry *typcache, VariableStatData *vardata,
+							const MultirangeType *constval, Oid operator);
+static double default_multirange_selectivity(Oid operator);
+static double default_multirange_selectivity(Oid operator);
+static double calc_hist_selectivity(TypeCacheEntry *typcache,
+									VariableStatData *vardata, const MultirangeType *constval,
+									Oid operator);
+static double calc_hist_selectivity_scalar(TypeCacheEntry *typcache,
+										   const RangeBound *constbound,
+										   const RangeBound *hist, int hist_nvalues,
+										   bool equal);
+static int	rbound_bsearch(TypeCacheEntry *typcache, const RangeBound *value,
+						   const RangeBound *hist, int hist_length, bool equal);
+static float8 get_position(TypeCacheEntry *typcache, const RangeBound *value,
+						   const RangeBound *hist1, const RangeBound *hist2);
+static float8 get_len_position(double value, double hist1, double hist2);
+static float8 get_distance(TypeCacheEntry *typcache, const RangeBound *bound1,
+						   const RangeBound *bound2);
+static int	length_hist_bsearch(Datum *length_hist_values,
+								int length_hist_nvalues, double value, bool equal);
+static double calc_length_hist_frac(Datum *length_hist_values,
+									int length_hist_nvalues, double length1, double length2, bool equal);
+static double calc_hist_selectivity_contained(TypeCacheEntry *typcache,
+											  const RangeBound *lower, RangeBound *upper,
+											  const RangeBound *hist_lower, int hist_nvalues,
+											  Datum *length_hist_values, int length_hist_nvalues);
+static double calc_hist_selectivity_contains(TypeCacheEntry *typcache,
+											 const RangeBound *lower, const RangeBound *upper,
+											 const RangeBound *hist_lower, int hist_nvalues,
+											 Datum *length_hist_values, int length_hist_nvalues);
+
+/*
+ * Returns a default selectivity estimate for given operator, when we don't
+ * have statistics or cannot use them for some reason.
+ */
+static double
+default_multirange_selectivity(Oid operator)
+{
+	switch (operator)
+	{
+		case OID_MULTIRANGE_OVERLAPS_MULTIRANGE_OP:
+		case OID_MULTIRANGE_OVERLAPS_RANGE_OP:
+		case OID_RANGE_OVERLAPS_MULTIRANGE_OP:
+			return 0.01;
+
+		case OID_MULTIRANGE_CONTAINS_RANGE_OP:
+		case OID_MULTIRANGE_CONTAINS_MULTIRANGE_OP:
+		case OID_MULTIRANGE_RANGE_CONTAINED_OP:
+		case OID_MULTIRANGE_MULTIRANGE_CONTAINED_OP:
+			return 0.005;
+
+		case OID_MULTIRANGE_CONTAINS_ELEM_OP:
+		case OID_MULTIRANGE_ELEM_CONTAINED_OP:
+
+			/*
+			 * "multirange @> elem" is more or less identical to a scalar
+			 * inequality "A >= b AND A <= c".
+			 */
+			return DEFAULT_MULTIRANGE_INEQ_SEL;
+
+		case OID_MULTIRANGE_LESS_OP:
+		case OID_MULTIRANGE_LESS_EQUAL_OP:
+		case OID_MULTIRANGE_GREATER_OP:
+		case OID_MULTIRANGE_GREATER_EQUAL_OP:
+		case OID_MULTIRANGE_LEFT_RANGE_OP:
+		case OID_MULTIRANGE_LEFT_MULTIRANGE_OP:
+		case OID_RANGE_LEFT_MULTIRANGE_OP:
+		case OID_MULTIRANGE_RIGHT_RANGE_OP:
+		case OID_MULTIRANGE_RIGHT_MULTIRANGE_OP:
+		case OID_RANGE_RIGHT_MULTIRANGE_OP:
+		case OID_MULTIRANGE_OVERLAPS_LEFT_RANGE_OP:
+		case OID_RANGE_OVERLAPS_LEFT_MULTIRANGE_OP:
+		case OID_MULTIRANGE_OVERLAPS_LEFT_MULTIRANGE_OP:
+		case OID_MULTIRANGE_OVERLAPS_RIGHT_RANGE_OP:
+		case OID_RANGE_OVERLAPS_RIGHT_MULTIRANGE_OP:
+		case OID_MULTIRANGE_OVERLAPS_RIGHT_MULTIRANGE_OP:
+			/* these are similar to regular scalar inequalities */
+			return DEFAULT_INEQ_SEL;
+
+		default:
+			/* all multirange operators should be handled above, but just in case */
+			return 0.01;
+	}
+}
+
+/*
+ * multirangesel -- restriction selectivity for multirange operators
+ */
+Datum
+multirangesel(PG_FUNCTION_ARGS)
+{
+	PlannerInfo *root = (PlannerInfo *) PG_GETARG_POINTER(0);
+	Oid			operator = PG_GETARG_OID(1);
+	List	   *args = (List *) PG_GETARG_POINTER(2);
+	int			varRelid = PG_GETARG_INT32(3);
+	VariableStatData vardata;
+	Node	   *other;
+	bool		varonleft;
+	Selectivity selec;
+	TypeCacheEntry *typcache = NULL;
+	MultirangeType *constmultirange = NULL;
+	RangeType  *constrange = NULL;
+
+	/*
+	 * If expression is not (variable op something) or (something op
+	 * variable), then punt and return a default estimate.
+	 */
+	if (!get_restriction_variable(root, args, varRelid,
+								  &vardata, &other, &varonleft))
+		PG_RETURN_FLOAT8(default_multirange_selectivity(operator));
+
+	/*
+	 * Can't do anything useful if the something is not a constant, either.
+	 */
+	if (!IsA(other, Const))
+	{
+		ReleaseVariableStats(vardata);
+		PG_RETURN_FLOAT8(default_multirange_selectivity(operator));
+	}
+
+	/*
+	 * All the multirange operators are strict, so we can cope with a NULL constant
+	 * right away.
+	 */
+	if (((Const *) other)->constisnull)
+	{
+		ReleaseVariableStats(vardata);
+		PG_RETURN_FLOAT8(0.0);
+	}
+
+	/*
+	 * If var is on the right, commute the operator, so that we can assume the
+	 * var is on the left in what follows.
+	 */
+	if (!varonleft)
+	{
+		/* we have other Op var, commute to make var Op other */
+		operator = get_commutator(operator);
+		if (!operator)
+		{
+			/* Use default selectivity (should we raise an error instead?) */
+			ReleaseVariableStats(vardata);
+			PG_RETURN_FLOAT8(default_multirange_selectivity(operator));
+		}
+	}
+
+	/*
+	 * OK, there's a Var and a Const we're dealing with here.  We need the
+	 * Const to be of same multirange type as the column, else we can't do anything
+	 * useful. (Such cases will likely fail at runtime, but here we'd rather
+	 * just return a default estimate.)
+	 *
+	 * If the operator is "multirange @> element", the constant should be of the
+	 * element type of the multirange column. Convert it to a multirange that includes
+	 * only that single point, so that we don't need special handling for that
+	 * in what follows.
+	 */
+	if (operator == OID_MULTIRANGE_CONTAINS_ELEM_OP)
+	{
+		typcache = multirange_get_typcache(fcinfo, vardata.vartype);
+
+		if (((Const *) other)->consttype == typcache->rngtype->rngelemtype->type_id)
+		{
+			RangeBound	lower,
+						upper;
+
+			lower.inclusive = true;
+			lower.val = ((Const *) other)->constvalue;
+			lower.infinite = false;
+			lower.lower = true;
+			upper.inclusive = true;
+			upper.val = ((Const *) other)->constvalue;
+			upper.infinite = false;
+			upper.lower = false;
+			constrange = range_serialize(typcache->rngtype, &lower, &upper, false);
+			constmultirange = make_multirange(typcache->type_id, typcache->rngtype,
+					1, &constrange);
+		}
+	}
+	else if (operator == OID_MULTIRANGE_CONTAINS_RANGE_OP ||
+			 operator == OID_MULTIRANGE_OVERLAPS_RANGE_OP ||
+			 operator == OID_MULTIRANGE_OVERLAPS_LEFT_RANGE_OP ||
+			 operator == OID_MULTIRANGE_OVERLAPS_RIGHT_RANGE_OP ||
+			 operator == OID_MULTIRANGE_LEFT_RANGE_OP ||
+			 operator == OID_MULTIRANGE_RIGHT_RANGE_OP)
+	{
+		/*
+		 * Promote a range in "multirange OP range" just like
+		 * we do an element in "multirange OP element".
+		 */
+		typcache = multirange_get_typcache(fcinfo, vardata.vartype);
+		if (((Const *) other)->consttype == typcache->rngtype->type_id)
+		{
+			constrange = DatumGetRangeTypeP(((Const *) other)->constvalue);
+			constmultirange = make_multirange(typcache->type_id, typcache->rngtype,
+					1, &constrange);
+		}
+	}
+	else if (operator == OID_RANGE_OVERLAPS_MULTIRANGE_OP ||
+			 operator == OID_RANGE_OVERLAPS_LEFT_MULTIRANGE_OP ||
+			 operator == OID_RANGE_OVERLAPS_RIGHT_MULTIRANGE_OP ||
+			 operator == OID_RANGE_LEFT_MULTIRANGE_OP ||
+			 operator == OID_RANGE_RIGHT_MULTIRANGE_OP ||
+			 operator == OID_MULTIRANGE_ELEM_CONTAINED_OP ||
+			 operator == OID_MULTIRANGE_RANGE_CONTAINED_OP)
+	{
+		/*
+		 * Here, the Var is the elem/range, not the multirange.  For now we just punt and
+		 * return the default estimate.  In future we could disassemble the
+		 * multirange constant to do something more intelligent.
+		 */
+	}
+	else if (((Const *) other)->consttype == vardata.vartype)
+	{
+		/* Both sides are the same multirange type */
+		typcache = multirange_get_typcache(fcinfo, vardata.vartype);
+
+		constmultirange = DatumGetMultirangeTypeP(((Const *) other)->constvalue);
+	}
+
+	/*
+	 * If we got a valid constant on one side of the operator, proceed to
+	 * estimate using statistics. Otherwise punt and return a default constant
+	 * estimate.  Note that calc_multirangesel need not handle
+	 * OID_MULTIRANGE_*_CONTAINED_OP.
+	 */
+	if (constmultirange)
+		selec = calc_multirangesel(typcache, &vardata, constmultirange, operator);
+	else
+		selec = default_multirange_selectivity(operator);
+
+	ReleaseVariableStats(vardata);
+
+	CLAMP_PROBABILITY(selec);
+
+	PG_RETURN_FLOAT8((float8) selec);
+}
+
+static double
+calc_multirangesel(TypeCacheEntry *typcache, VariableStatData *vardata,
+			  const MultirangeType *constval, Oid operator)
+{
+	double		hist_selec;
+	double		selec;
+	float4		empty_frac,
+				null_frac;
+
+	/*
+	 * First look up the fraction of NULLs and empty multiranges from pg_statistic.
+	 */
+	if (HeapTupleIsValid(vardata->statsTuple))
+	{
+		Form_pg_statistic stats;
+		AttStatsSlot sslot;
+
+		stats = (Form_pg_statistic) GETSTRUCT(vardata->statsTuple);
+		null_frac = stats->stanullfrac;
+
+		/* Try to get fraction of empty multiranges */
+		if (get_attstatsslot(&sslot, vardata->statsTuple,
+							 STATISTIC_KIND_RANGE_LENGTH_HISTOGRAM,
+							 InvalidOid,
+							 ATTSTATSSLOT_NUMBERS))
+		{
+			if (sslot.nnumbers != 1)
+				elog(ERROR, "invalid empty fraction statistic");	/* shouldn't happen */
+			empty_frac = sslot.numbers[0];
+			free_attstatsslot(&sslot);
+		}
+		else
+		{
+			/* No empty fraction statistic. Assume no empty ranges. */
+			empty_frac = 0.0;
+		}
+	}
+	else
+	{
+		/*
+		 * No stats are available. Follow through the calculations below
+		 * anyway, assuming no NULLs and no empty multiranges. This still allows us
+		 * to give a better-than-nothing estimate based on whether the
+		 * constant is an empty multirange or not.
+		 */
+		null_frac = 0.0;
+		empty_frac = 0.0;
+	}
+
+	if (MultirangeIsEmpty(constval))
+	{
+		/*
+		 * An empty multirange matches all multiranges, all empty multiranges, or
+		 * nothing, depending on the operator
+		 */
+		switch (operator)
+		{
+				/* these return false if either argument is empty */
+			case OID_RANGE_OVERLAPS_MULTIRANGE_OP:
+			case OID_MULTIRANGE_OVERLAPS_RANGE_OP:
+			case OID_MULTIRANGE_OVERLAPS_MULTIRANGE_OP:
+			case OID_RANGE_OVERLAPS_LEFT_MULTIRANGE_OP:
+			case OID_MULTIRANGE_OVERLAPS_LEFT_RANGE_OP:
+			case OID_MULTIRANGE_OVERLAPS_LEFT_MULTIRANGE_OP:
+			case OID_RANGE_OVERLAPS_RIGHT_MULTIRANGE_OP:
+			case OID_MULTIRANGE_OVERLAPS_RIGHT_RANGE_OP:
+			case OID_MULTIRANGE_OVERLAPS_RIGHT_MULTIRANGE_OP:
+			case OID_MULTIRANGE_LEFT_MULTIRANGE_OP:
+			case OID_MULTIRANGE_RIGHT_MULTIRANGE_OP:
+				/* nothing is less than an empty multirange */
+			case OID_MULTIRANGE_LESS_OP:
+				selec = 0.0;
+				break;
+
+				/* only empty multiranges can be contained by an empty multirange */
+			case OID_MULTIRANGE_RANGE_CONTAINED_OP:
+			case OID_MULTIRANGE_MULTIRANGE_CONTAINED_OP:
+				/* only empty ranges are <= an empty multirange */
+			case OID_MULTIRANGE_LESS_EQUAL_OP:
+				selec = empty_frac;
+				break;
+
+				/* everything contains an empty multirange */
+			case OID_MULTIRANGE_CONTAINS_RANGE_OP:
+			case OID_MULTIRANGE_CONTAINS_MULTIRANGE_OP:
+				/* everything is >= an empty multirange */
+			case OID_MULTIRANGE_GREATER_EQUAL_OP:
+				selec = 1.0;
+				break;
+
+				/* all non-empty multiranges are > an empty multirange */
+			case OID_MULTIRANGE_GREATER_OP:
+				selec = 1.0 - empty_frac;
+				break;
+
+				/* an element cannot be empty */
+			case OID_MULTIRANGE_ELEM_CONTAINED_OP:
+			case OID_MULTIRANGE_CONTAINS_ELEM_OP:
+			default:
+				elog(ERROR, "unexpected operator %u", operator);
+				selec = 0.0;	/* keep compiler quiet */
+				break;
+		}
+	}
+	else
+	{
+		/*
+		 * Calculate selectivity using bound histograms. If that fails for
+		 * some reason, e.g no histogram in pg_statistic, use the default
+		 * constant estimate for the fraction of non-empty values. This is
+		 * still somewhat better than just returning the default estimate,
+		 * because this still takes into account the fraction of empty and
+		 * NULL tuples, if we had statistics for them.
+		 */
+		hist_selec = calc_hist_selectivity(typcache, vardata, constval,
+										   operator);
+		if (hist_selec < 0.0)
+			hist_selec = default_multirange_selectivity(operator);
+
+		/*
+		 * Now merge the results for the empty multiranges and histogram
+		 * calculations, realizing that the histogram covers only the
+		 * non-null, non-empty values.
+		 */
+		if (operator == OID_MULTIRANGE_ELEM_CONTAINED_OP ||
+			operator == OID_MULTIRANGE_RANGE_CONTAINED_OP ||
+			operator == OID_MULTIRANGE_MULTIRANGE_CONTAINED_OP)
+		{
+			/* empty is contained by anything non-empty */
+			selec = (1.0 - empty_frac) * hist_selec + empty_frac;
+		}
+		else
+		{
+			/* with any other operator, empty Op non-empty matches nothing */
+			selec = (1.0 - empty_frac) * hist_selec;
+		}
+	}
+
+	/* all multirange operators are strict */
+	selec *= (1.0 - null_frac);
+
+	/* result should be in range, but make sure... */
+	CLAMP_PROBABILITY(selec);
+
+	return selec;
+}
+
+/*
+ * Calculate multirange operator selectivity using histograms of multirange bounds.
+ *
+ * This estimate is for the portion of values that are not empty and not
+ * NULL.
+ */
+static double
+calc_hist_selectivity(TypeCacheEntry *typcache, VariableStatData *vardata,
+					  const MultirangeType *constval, Oid operator)
+{
+	TypeCacheEntry *rng_typcache = typcache->rngtype;
+	AttStatsSlot	hslot;
+	AttStatsSlot	lslot;
+	int			nhist;
+	RangeBound *hist_lower;
+	RangeBound *hist_upper;
+	int			i;
+	RangeBound	const_lower;
+	RangeBound	const_upper;
+	bool		empty;
+	double		hist_selec;
+	int			range_count;
+	RangeType **ranges;
+	RangeType  *constrange;
+
+	/* Can't use the histogram with insecure multirange support functions */
+	if (!statistic_proc_security_check(vardata,
+									   rng_typcache->rng_cmp_proc_finfo.fn_oid))
+		return -1;
+	if (OidIsValid(rng_typcache->rng_subdiff_finfo.fn_oid) &&
+		!statistic_proc_security_check(vardata,
+									   rng_typcache->rng_subdiff_finfo.fn_oid))
+		return -1;
+
+	/* Try to get histogram of ranges */
+	if (!(HeapTupleIsValid(vardata->statsTuple) &&
+		  get_attstatsslot(&hslot, vardata->statsTuple,
+						   STATISTIC_KIND_BOUNDS_HISTOGRAM, InvalidOid,
+						   ATTSTATSSLOT_VALUES)))
+		return -1.0;
+
+	/* check that it's a histogram, not just a dummy entry */
+	if (hslot.nvalues < 2)
+	{
+		free_attstatsslot(&hslot);
+		return -1.0;
+	}
+
+	/*
+	 * Convert histogram of ranges into histograms of its lower and upper
+	 * bounds.
+	 */
+	nhist = hslot.nvalues;
+	hist_lower = (RangeBound *) palloc(sizeof(RangeBound) * nhist);
+	hist_upper = (RangeBound *) palloc(sizeof(RangeBound) * nhist);
+	for (i = 0; i < nhist; i++)
+	{
+		range_deserialize(rng_typcache, DatumGetRangeTypeP(hslot.values[i]),
+						  &hist_lower[i], &hist_upper[i], &empty);
+		/* The histogram should not contain any empty ranges */
+		if (empty)
+			elog(ERROR, "bounds histogram contains an empty range");
+	}
+
+	/* @> and @< also need a histogram of range lengths */
+	if (operator == OID_MULTIRANGE_CONTAINS_RANGE_OP ||
+		operator == OID_MULTIRANGE_CONTAINS_MULTIRANGE_OP ||
+		operator == OID_MULTIRANGE_RANGE_CONTAINED_OP ||
+		operator == OID_MULTIRANGE_MULTIRANGE_CONTAINED_OP)
+	{
+		if (!(HeapTupleIsValid(vardata->statsTuple) &&
+			  get_attstatsslot(&lslot, vardata->statsTuple,
+							   STATISTIC_KIND_RANGE_LENGTH_HISTOGRAM,
+							   InvalidOid,
+							   ATTSTATSSLOT_VALUES)))
+		{
+			free_attstatsslot(&hslot);
+			return -1.0;
+		}
+
+		/* check that it's a histogram, not just a dummy entry */
+		if (lslot.nvalues < 2)
+		{
+			free_attstatsslot(&lslot);
+			free_attstatsslot(&hslot);
+			return -1.0;
+		}
+	}
+	else
+		memset(&lslot, 0, sizeof(lslot));
+
+	/* Extract the bounds of the constant value. */
+	multirange_deserialize(rng_typcache, constval, &range_count, &ranges);
+	Assert(range_count > 0);
+	constrange = range_union_internal(rng_typcache, ranges[0],
+			ranges[range_count - 1], false);
+	range_deserialize(rng_typcache, constrange, &const_lower, &const_upper, &empty);
+
+	/*
+	 * Calculate selectivity comparing the lower or upper bound of the
+	 * constant with the histogram of lower or upper bounds.
+	 */
+	switch (operator)
+	{
+		case OID_MULTIRANGE_LESS_OP:
+
+			/*
+			 * The regular b-tree comparison operators (<, <=, >, >=) compare
+			 * the lower bounds first, and the upper bounds for values with
+			 * equal lower bounds. Estimate that by comparing the lower bounds
+			 * only. This gives a fairly accurate estimate assuming there
+			 * aren't many rows with a lower bound equal to the constant's
+			 * lower bound.
+			 */
+			hist_selec =
+				calc_hist_selectivity_scalar(rng_typcache, &const_lower,
+											 hist_lower, nhist, false);
+			break;
+
+		case OID_MULTIRANGE_LESS_EQUAL_OP:
+			hist_selec =
+				calc_hist_selectivity_scalar(rng_typcache, &const_lower,
+											 hist_lower, nhist, true);
+			break;
+
+		case OID_MULTIRANGE_GREATER_OP:
+			hist_selec =
+				1 - calc_hist_selectivity_scalar(rng_typcache, &const_lower,
+												 hist_lower, nhist, false);
+			break;
+
+		case OID_MULTIRANGE_GREATER_EQUAL_OP:
+			hist_selec =
+				1 - calc_hist_selectivity_scalar(rng_typcache, &const_lower,
+												 hist_lower, nhist, true);
+			break;
+
+		case OID_RANGE_LEFT_MULTIRANGE_OP:
+		case OID_MULTIRANGE_LEFT_RANGE_OP:
+		case OID_MULTIRANGE_LEFT_MULTIRANGE_OP:
+			/* var << const when upper(var) < lower(const) */
+			hist_selec =
+				calc_hist_selectivity_scalar(rng_typcache, &const_lower,
+											 hist_upper, nhist, false);
+			break;
+
+		case OID_RANGE_RIGHT_MULTIRANGE_OP:
+		case OID_MULTIRANGE_RIGHT_RANGE_OP:
+		case OID_MULTIRANGE_RIGHT_MULTIRANGE_OP:
+			/* var >> const when lower(var) > upper(const) */
+			hist_selec =
+				1 - calc_hist_selectivity_scalar(rng_typcache, &const_upper,
+												 hist_lower, nhist, true);
+			break;
+
+		case OID_RANGE_OVERLAPS_RIGHT_MULTIRANGE_OP:
+		case OID_MULTIRANGE_OVERLAPS_RIGHT_RANGE_OP:
+		case OID_MULTIRANGE_OVERLAPS_RIGHT_MULTIRANGE_OP:
+			/* compare lower bounds */
+			hist_selec =
+				1 - calc_hist_selectivity_scalar(rng_typcache, &const_lower,
+												 hist_lower, nhist, false);
+			break;
+
+		case OID_RANGE_OVERLAPS_LEFT_MULTIRANGE_OP:
+		case OID_MULTIRANGE_OVERLAPS_LEFT_RANGE_OP:
+		case OID_MULTIRANGE_OVERLAPS_LEFT_MULTIRANGE_OP:
+			/* compare upper bounds */
+			hist_selec =
+				calc_hist_selectivity_scalar(rng_typcache, &const_upper,
+											 hist_upper, nhist, true);
+			break;
+
+		case OID_RANGE_OVERLAPS_MULTIRANGE_OP:
+		case OID_MULTIRANGE_OVERLAPS_RANGE_OP:
+		case OID_MULTIRANGE_OVERLAPS_MULTIRANGE_OP:
+		case OID_MULTIRANGE_CONTAINS_ELEM_OP:
+
+			/*
+			 * A && B <=> NOT (A << B OR A >> B).
+			 *
+			 * Since A << B and A >> B are mutually exclusive events we can
+			 * sum their probabilities to find probability of (A << B OR A >>
+			 * B).
+			 *
+			 * "multirange @> elem" is equivalent to "multirange && {[elem,elem]}".
+			 * The caller already constructed the singular range from the element
+			 * constant, so just treat it the same as &&.
+			 */
+			hist_selec =
+				calc_hist_selectivity_scalar(rng_typcache, &const_lower, hist_upper,
+											 nhist, false);
+			hist_selec +=
+				(1.0 - calc_hist_selectivity_scalar(rng_typcache, &const_upper, hist_lower,
+													nhist, true));
+			hist_selec = 1.0 - hist_selec;
+			break;
+
+		case OID_MULTIRANGE_CONTAINS_RANGE_OP:
+		case OID_MULTIRANGE_CONTAINS_MULTIRANGE_OP:
+			hist_selec =
+				calc_hist_selectivity_contains(rng_typcache, &const_lower,
+											   &const_upper, hist_lower, nhist,
+											   lslot.values, lslot.nvalues);
+			break;
+
+		case OID_MULTIRANGE_RANGE_CONTAINED_OP:
+		case OID_MULTIRANGE_MULTIRANGE_CONTAINED_OP:
+			if (const_lower.infinite)
+			{
+				/*
+				 * Lower bound no longer matters. Just estimate the fraction
+				 * with an upper bound <= const upper bound
+				 */
+				hist_selec =
+					calc_hist_selectivity_scalar(rng_typcache, &const_upper,
+												 hist_upper, nhist, true);
+			}
+			else if (const_upper.infinite)
+			{
+				hist_selec =
+					1.0 - calc_hist_selectivity_scalar(rng_typcache, &const_lower,
+													   hist_lower, nhist, false);
+			}
+			else
+			{
+				hist_selec =
+					calc_hist_selectivity_contained(rng_typcache, &const_lower,
+													&const_upper, hist_lower, nhist,
+													lslot.values, lslot.nvalues);
+			}
+			break;
+
+		default:
+			elog(ERROR, "unknown multirange operator %u", operator);
+			hist_selec = -1.0;	/* keep compiler quiet */
+			break;
+	}
+
+	free_attstatsslot(&lslot);
+	free_attstatsslot(&hslot);
+
+	return hist_selec;
+}
+
+
+/*
+ * Look up the fraction of values less than (or equal, if 'equal' argument
+ * is true) a given const in a histogram of range bounds.
+ */
+static double
+calc_hist_selectivity_scalar(TypeCacheEntry *typcache, const RangeBound *constbound,
+							 const RangeBound *hist, int hist_nvalues, bool equal)
+{
+	Selectivity selec;
+	int			index;
+
+	/*
+	 * Find the histogram bin the given constant falls into. Estimate
+	 * selectivity as the number of preceding whole bins.
+	 */
+	index = rbound_bsearch(typcache, constbound, hist, hist_nvalues, equal);
+	selec = (Selectivity) (Max(index, 0)) / (Selectivity) (hist_nvalues - 1);
+
+	/* Adjust using linear interpolation within the bin */
+	if (index >= 0 && index < hist_nvalues - 1)
+		selec += get_position(typcache, constbound, &hist[index],
+							  &hist[index + 1]) / (Selectivity) (hist_nvalues - 1);
+
+	return selec;
+}
+
+/*
+ * Binary search on an array of range bounds. Returns greatest index of range
+ * bound in array which is less(less or equal) than given range bound. If all
+ * range bounds in array are greater or equal(greater) than given range bound,
+ * return -1. When "equal" flag is set conditions in brackets are used.
+ *
+ * This function is used in scalar operator selectivity estimation. Another
+ * goal of this function is to find a histogram bin where to stop
+ * interpolation of portion of bounds which are less or equal to given bound.
+ */
+static int
+rbound_bsearch(TypeCacheEntry *typcache, const RangeBound *value, const RangeBound *hist,
+			   int hist_length, bool equal)
+{
+	int			lower = -1,
+				upper = hist_length - 1,
+				cmp,
+				middle;
+
+	while (lower < upper)
+	{
+		middle = (lower + upper + 1) / 2;
+		cmp = range_cmp_bounds(typcache, &hist[middle], value);
+
+		if (cmp < 0 || (equal && cmp == 0))
+			lower = middle;
+		else
+			upper = middle - 1;
+	}
+	return lower;
+}
+
+
+/*
+ * Binary search on length histogram. Returns greatest index of range length in
+ * histogram which is less than (less than or equal) the given length value. If
+ * all lengths in the histogram are greater than (greater than or equal) the
+ * given length, returns -1.
+ */
+static int
+length_hist_bsearch(Datum *length_hist_values, int length_hist_nvalues,
+					double value, bool equal)
+{
+	int			lower = -1,
+				upper = length_hist_nvalues - 1,
+				middle;
+
+	while (lower < upper)
+	{
+		double		middleval;
+
+		middle = (lower + upper + 1) / 2;
+
+		middleval = DatumGetFloat8(length_hist_values[middle]);
+		if (middleval < value || (equal && middleval <= value))
+			lower = middle;
+		else
+			upper = middle - 1;
+	}
+	return lower;
+}
+
+/*
+ * Get relative position of value in histogram bin in [0,1] range.
+ */
+static float8
+get_position(TypeCacheEntry *typcache, const RangeBound *value, const RangeBound *hist1,
+			 const RangeBound *hist2)
+{
+	bool		has_subdiff = OidIsValid(typcache->rng_subdiff_finfo.fn_oid);
+	float8		position;
+
+	if (!hist1->infinite && !hist2->infinite)
+	{
+		float8		bin_width;
+
+		/*
+		 * Both bounds are finite. Assuming the subtype's comparison function
+		 * works sanely, the value must be finite, too, because it lies
+		 * somewhere between the bounds.  If it doesn't, arbitrarily return
+		 * 0.5.
+		 */
+		if (value->infinite)
+			return 0.5;
+
+		/* Can't interpolate without subdiff function */
+		if (!has_subdiff)
+			return 0.5;
+
+		/* Calculate relative position using subdiff function. */
+		bin_width = DatumGetFloat8(FunctionCall2Coll(&typcache->rng_subdiff_finfo,
+													 typcache->rng_collation,
+													 hist2->val,
+													 hist1->val));
+		if (isnan(bin_width) || bin_width <= 0.0)
+			return 0.5;			/* punt for NaN or zero-width bin */
+
+		position = DatumGetFloat8(FunctionCall2Coll(&typcache->rng_subdiff_finfo,
+													typcache->rng_collation,
+													value->val,
+													hist1->val))
+			/ bin_width;
+
+		if (isnan(position))
+			return 0.5;			/* punt for NaN from subdiff, Inf/Inf, etc */
+
+		/* Relative position must be in [0,1] range */
+		position = Max(position, 0.0);
+		position = Min(position, 1.0);
+		return position;
+	}
+	else if (hist1->infinite && !hist2->infinite)
+	{
+		/*
+		 * Lower bin boundary is -infinite, upper is finite. If the value is
+		 * -infinite, return 0.0 to indicate it's equal to the lower bound.
+		 * Otherwise return 1.0 to indicate it's infinitely far from the lower
+		 * bound.
+		 */
+		return ((value->infinite && value->lower) ? 0.0 : 1.0);
+	}
+	else if (!hist1->infinite && hist2->infinite)
+	{
+		/* same as above, but in reverse */
+		return ((value->infinite && !value->lower) ? 1.0 : 0.0);
+	}
+	else
+	{
+		/*
+		 * If both bin boundaries are infinite, they should be equal to each
+		 * other, and the value should also be infinite and equal to both
+		 * bounds. (But don't Assert that, to avoid crashing if a user creates
+		 * a datatype with a broken comparison function).
+		 *
+		 * Assume the value to lie in the middle of the infinite bounds.
+		 */
+		return 0.5;
+	}
+}
+
+
+/*
+ * Get relative position of value in a length histogram bin in [0,1] range.
+ */
+static double
+get_len_position(double value, double hist1, double hist2)
+{
+	if (!isinf(hist1) && !isinf(hist2))
+	{
+		/*
+		 * Both bounds are finite. The value should be finite too, because it
+		 * lies somewhere between the bounds. If it doesn't, just return
+		 * something.
+		 */
+		if (isinf(value))
+			return 0.5;
+
+		return 1.0 - (hist2 - value) / (hist2 - hist1);
+	}
+	else if (isinf(hist1) && !isinf(hist2))
+	{
+		/*
+		 * Lower bin boundary is -infinite, upper is finite. Return 1.0 to
+		 * indicate the value is infinitely far from the lower bound.
+		 */
+		return 1.0;
+	}
+	else if (isinf(hist1) && isinf(hist2))
+	{
+		/* same as above, but in reverse */
+		return 0.0;
+	}
+	else
+	{
+		/*
+		 * If both bin boundaries are infinite, they should be equal to each
+		 * other, and the value should also be infinite and equal to both
+		 * bounds. (But don't Assert that, to avoid crashing unnecessarily if
+		 * the caller messes up)
+		 *
+		 * Assume the value to lie in the middle of the infinite bounds.
+		 */
+		return 0.5;
+	}
+}
+
+/*
+ * Measure distance between two range bounds.
+ */
+static float8
+get_distance(TypeCacheEntry *typcache, const RangeBound *bound1, const RangeBound *bound2)
+{
+	bool		has_subdiff = OidIsValid(typcache->rng_subdiff_finfo.fn_oid);
+
+	if (!bound1->infinite && !bound2->infinite)
+	{
+		/*
+		 * Neither bound is infinite, use subdiff function or return default
+		 * value of 1.0 if no subdiff is available.
+		 */
+		if (has_subdiff)
+		{
+			float8		res;
+
+			res = DatumGetFloat8(FunctionCall2Coll(&typcache->rng_subdiff_finfo,
+												   typcache->rng_collation,
+												   bound2->val,
+												   bound1->val));
+			/* Reject possible NaN result, also negative result */
+			if (isnan(res) || res < 0.0)
+				return 1.0;
+			else
+				return res;
+		}
+		else
+			return 1.0;
+	}
+	else if (bound1->infinite && bound2->infinite)
+	{
+		/* Both bounds are infinite */
+		if (bound1->lower == bound2->lower)
+			return 0.0;
+		else
+			return get_float8_infinity();
+	}
+	else
+	{
+		/* One bound is infinite, the other is not */
+		return get_float8_infinity();
+	}
+}
+
+/*
+ * Calculate the average of function P(x), in the interval [length1, length2],
+ * where P(x) is the fraction of tuples with length < x (or length <= x if
+ * 'equal' is true).
+ */
+static double
+calc_length_hist_frac(Datum *length_hist_values, int length_hist_nvalues,
+					  double length1, double length2, bool equal)
+{
+	double		frac;
+	double		A,
+				B,
+				PA,
+				PB;
+	double		pos;
+	int			i;
+	double		area;
+
+	Assert(length2 >= length1);
+
+	if (length2 < 0.0)
+		return 0.0;				/* shouldn't happen, but doesn't hurt to check */
+
+	/* All lengths in the table are <= infinite. */
+	if (isinf(length2) && equal)
+		return 1.0;
+
+	/*----------
+	 * The average of a function between A and B can be calculated by the
+	 * formula:
+	 *
+	 *			B
+	 *	  1		/
+	 * -------	| P(x)dx
+	 *	B - A	/
+	 *			A
+	 *
+	 * The geometrical interpretation of the integral is the area under the
+	 * graph of P(x). P(x) is defined by the length histogram. We calculate
+	 * the area in a piecewise fashion, iterating through the length histogram
+	 * bins. Each bin is a trapezoid:
+	 *
+	 *		 P(x2)
+	 *		  /|
+	 *		 / |
+	 * P(x1)/  |
+	 *	   |   |
+	 *	   |   |
+	 *	---+---+--
+	 *	   x1  x2
+	 *
+	 * where x1 and x2 are the boundaries of the current histogram, and P(x1)
+	 * and P(x1) are the cumulative fraction of tuples at the boundaries.
+	 *
+	 * The area of each trapezoid is 1/2 * (P(x2) + P(x1)) * (x2 - x1)
+	 *
+	 * The first bin contains the lower bound passed by the caller, so we
+	 * use linear interpolation between the previous and next histogram bin
+	 * boundary to calculate P(x1). Likewise for the last bin: we use linear
+	 * interpolation to calculate P(x2). For the bins in between, x1 and x2
+	 * lie on histogram bin boundaries, so P(x1) and P(x2) are simply:
+	 * P(x1) =	  (bin index) / (number of bins)
+	 * P(x2) = (bin index + 1 / (number of bins)
+	 */
+
+	/* First bin, the one that contains lower bound */
+	i = length_hist_bsearch(length_hist_values, length_hist_nvalues, length1, equal);
+	if (i >= length_hist_nvalues - 1)
+		return 1.0;
+
+	if (i < 0)
+	{
+		i = 0;
+		pos = 0.0;
+	}
+	else
+	{
+		/* interpolate length1's position in the bin */
+		pos = get_len_position(length1,
+							   DatumGetFloat8(length_hist_values[i]),
+							   DatumGetFloat8(length_hist_values[i + 1]));
+	}
+	PB = (((double) i) + pos) / (double) (length_hist_nvalues - 1);
+	B = length1;
+
+	/*
+	 * In the degenerate case that length1 == length2, simply return
+	 * P(length1). This is not merely an optimization: if length1 == length2,
+	 * we'd divide by zero later on.
+	 */
+	if (length2 == length1)
+		return PB;
+
+	/*
+	 * Loop through all the bins, until we hit the last bin, the one that
+	 * contains the upper bound. (if lower and upper bounds are in the same
+	 * bin, this falls out immediately)
+	 */
+	area = 0.0;
+	for (; i < length_hist_nvalues - 1; i++)
+	{
+		double		bin_upper = DatumGetFloat8(length_hist_values[i + 1]);
+
+		/* check if we've reached the last bin */
+		if (!(bin_upper < length2 || (equal && bin_upper <= length2)))
+			break;
+
+		/* the upper bound of previous bin is the lower bound of this bin */
+		A = B;
+		PA = PB;
+
+		B = bin_upper;
+		PB = (double) i / (double) (length_hist_nvalues - 1);
+
+		/*
+		 * Add the area of this trapezoid to the total. The point of the
+		 * if-check is to avoid NaN, in the corner case that PA == PB == 0,
+		 * and B - A == Inf. The area of a zero-height trapezoid (PA == PB ==
+		 * 0) is zero, regardless of the width (B - A).
+		 */
+		if (PA > 0 || PB > 0)
+			area += 0.5 * (PB + PA) * (B - A);
+	}
+
+	/* Last bin */
+	A = B;
+	PA = PB;
+
+	B = length2;				/* last bin ends at the query upper bound */
+	if (i >= length_hist_nvalues - 1)
+		pos = 0.0;
+	else
+	{
+		if (DatumGetFloat8(length_hist_values[i]) == DatumGetFloat8(length_hist_values[i + 1]))
+			pos = 0.0;
+		else
+			pos = get_len_position(length2, DatumGetFloat8(length_hist_values[i]), DatumGetFloat8(length_hist_values[i + 1]));
+	}
+	PB = (((double) i) + pos) / (double) (length_hist_nvalues - 1);
+
+	if (PA > 0 || PB > 0)
+		area += 0.5 * (PB + PA) * (B - A);
+
+	/*
+	 * Ok, we have calculated the area, ie. the integral. Divide by width to
+	 * get the requested average.
+	 *
+	 * Avoid NaN arising from infinite / infinite. This happens at least if
+	 * length2 is infinite. It's not clear what the correct value would be in
+	 * that case, so 0.5 seems as good as any value.
+	 */
+	if (isinf(area) && isinf(length2))
+		frac = 0.5;
+	else
+		frac = area / (length2 - length1);
+
+	return frac;
+}
+
+/*
+ * Calculate selectivity of "var <@ const" operator, ie. estimate the fraction
+ * of multiranges that fall within the constant lower and upper bounds. This uses
+ * the histograms of range lower bounds and range lengths, on the assumption
+ * that the range lengths are independent of the lower bounds.
+ *
+ * The caller has already checked that constant lower and upper bounds are
+ * finite.
+ */
+static double
+calc_hist_selectivity_contained(TypeCacheEntry *typcache,
+								const RangeBound *lower, RangeBound *upper,
+								const RangeBound *hist_lower, int hist_nvalues,
+								Datum *length_hist_values, int length_hist_nvalues)
+{
+	int			i,
+				upper_index;
+	float8		prev_dist;
+	double		bin_width;
+	double		upper_bin_width;
+	double		sum_frac;
+
+	/*
+	 * Begin by finding the bin containing the upper bound, in the lower bound
+	 * histogram. Any range with a lower bound > constant upper bound can't
+	 * match, ie. there are no matches in bins greater than upper_index.
+	 */
+	upper->inclusive = !upper->inclusive;
+	upper->lower = true;
+	upper_index = rbound_bsearch(typcache, upper, hist_lower, hist_nvalues,
+								 false);
+
+	/*
+	 * If the upper bound value is below the histogram's lower limit, there
+	 * are no matches.
+	 */
+	if (upper_index < 0)
+		return 0.0;
+
+	/*
+	 * If the upper bound value is at or beyond the histogram's upper limit,
+	 * start our loop at the last actual bin, as though the upper bound were
+	 * within that bin; get_position will clamp its result to 1.0 anyway.
+	 * (This corresponds to assuming that the data population above the
+	 * histogram's upper limit is empty, exactly like what we just assumed for
+	 * the lower limit.)
+	 */
+	upper_index = Min(upper_index, hist_nvalues - 2);
+
+	/*
+	 * Calculate upper_bin_width, ie. the fraction of the (upper_index,
+	 * upper_index + 1) bin which is greater than upper bound of query range
+	 * using linear interpolation of subdiff function.
+	 */
+	upper_bin_width = get_position(typcache, upper,
+								   &hist_lower[upper_index],
+								   &hist_lower[upper_index + 1]);
+
+	/*
+	 * In the loop, dist and prev_dist are the distance of the "current" bin's
+	 * lower and upper bounds from the constant upper bound.
+	 *
+	 * bin_width represents the width of the current bin. Normally it is 1.0,
+	 * meaning a full width bin, but can be less in the corner cases: start
+	 * and end of the loop. We start with bin_width = upper_bin_width, because
+	 * we begin at the bin containing the upper bound.
+	 */
+	prev_dist = 0.0;
+	bin_width = upper_bin_width;
+
+	sum_frac = 0.0;
+	for (i = upper_index; i >= 0; i--)
+	{
+		double		dist;
+		double		length_hist_frac;
+		bool		final_bin = false;
+
+		/*
+		 * dist -- distance from upper bound of query range to lower bound of
+		 * the current bin in the lower bound histogram. Or to the lower bound
+		 * of the constant range, if this is the final bin, containing the
+		 * constant lower bound.
+		 */
+		if (range_cmp_bounds(typcache, &hist_lower[i], lower) < 0)
+		{
+			dist = get_distance(typcache, lower, upper);
+
+			/*
+			 * Subtract from bin_width the portion of this bin that we want to
+			 * ignore.
+			 */
+			bin_width -= get_position(typcache, lower, &hist_lower[i],
+									  &hist_lower[i + 1]);
+			if (bin_width < 0.0)
+				bin_width = 0.0;
+			final_bin = true;
+		}
+		else
+			dist = get_distance(typcache, &hist_lower[i], upper);
+
+		/*
+		 * Estimate the fraction of tuples in this bin that are narrow enough
+		 * to not exceed the distance to the upper bound of the query range.
+		 */
+		length_hist_frac = calc_length_hist_frac(length_hist_values,
+												 length_hist_nvalues,
+												 prev_dist, dist, true);
+
+		/*
+		 * Add the fraction of tuples in this bin, with a suitable length, to
+		 * the total.
+		 */
+		sum_frac += length_hist_frac * bin_width / (double) (hist_nvalues - 1);
+
+		if (final_bin)
+			break;
+
+		bin_width = 1.0;
+		prev_dist = dist;
+	}
+
+	return sum_frac;
+}
+
+/*
+ * Calculate selectivity of "var @> const" operator, ie. estimate the fraction
+ * of multiranges that contain the constant lower and upper bounds. This uses
+ * the histograms of range lower bounds and range lengths, on the assumption
+ * that the range lengths are independent of the lower bounds.
+ */
+static double
+calc_hist_selectivity_contains(TypeCacheEntry *typcache,
+							   const RangeBound *lower, const RangeBound *upper,
+							   const RangeBound *hist_lower, int hist_nvalues,
+							   Datum *length_hist_values, int length_hist_nvalues)
+{
+	int			i,
+				lower_index;
+	double		bin_width,
+				lower_bin_width;
+	double		sum_frac;
+	float8		prev_dist;
+
+	/* Find the bin containing the lower bound of query range. */
+	lower_index = rbound_bsearch(typcache, lower, hist_lower, hist_nvalues,
+								 true);
+
+	/*
+	 * If the lower bound value is below the histogram's lower limit, there
+	 * are no matches.
+	 */
+	if (lower_index < 0)
+		return 0.0;
+
+	/*
+	 * If the lower bound value is at or beyond the histogram's upper limit,
+	 * start our loop at the last actual bin, as though the upper bound were
+	 * within that bin; get_position will clamp its result to 1.0 anyway.
+	 * (This corresponds to assuming that the data population above the
+	 * histogram's upper limit is empty, exactly like what we just assumed for
+	 * the lower limit.)
+	 */
+	lower_index = Min(lower_index, hist_nvalues - 2);
+
+	/*
+	 * Calculate lower_bin_width, ie. the fraction of the of (lower_index,
+	 * lower_index + 1) bin which is greater than lower bound of query range
+	 * using linear interpolation of subdiff function.
+	 */
+	lower_bin_width = get_position(typcache, lower, &hist_lower[lower_index],
+								   &hist_lower[lower_index + 1]);
+
+	/*
+	 * Loop through all the lower bound bins, smaller than the query lower
+	 * bound. In the loop, dist and prev_dist are the distance of the
+	 * "current" bin's lower and upper bounds from the constant upper bound.
+	 * We begin from query lower bound, and walk backwards, so the first bin's
+	 * upper bound is the query lower bound, and its distance to the query
+	 * upper bound is the length of the query range.
+	 *
+	 * bin_width represents the width of the current bin. Normally it is 1.0,
+	 * meaning a full width bin, except for the first bin, which is only
+	 * counted up to the constant lower bound.
+	 */
+	prev_dist = get_distance(typcache, lower, upper);
+	sum_frac = 0.0;
+	bin_width = lower_bin_width;
+	for (i = lower_index; i >= 0; i--)
+	{
+		float8		dist;
+		double		length_hist_frac;
+
+		/*
+		 * dist -- distance from upper bound of query range to current value
+		 * of lower bound histogram or lower bound of query range (if we've
+		 * reach it).
+		 */
+		dist = get_distance(typcache, &hist_lower[i], upper);
+
+		/*
+		 * Get average fraction of length histogram which covers intervals
+		 * longer than (or equal to) distance to upper bound of query range.
+		 */
+		length_hist_frac =
+			1.0 - calc_length_hist_frac(length_hist_values,
+										length_hist_nvalues,
+										prev_dist, dist, false);
+
+		sum_frac += length_hist_frac * bin_width / (double) (hist_nvalues - 1);
+
+		bin_width = 1.0;
+		prev_dist = dist;
+	}
+
+	return sum_frac;
+}
diff --git a/src/backend/utils/adt/pg_upgrade_support.c b/src/backend/utils/adt/pg_upgrade_support.c
index 8a5953881ee..521ebaaab6d 100644
--- a/src/backend/utils/adt/pg_upgrade_support.c
+++ b/src/backend/utils/adt/pg_upgrade_support.c
@@ -52,6 +52,28 @@ binary_upgrade_set_next_array_pg_type_oid(PG_FUNCTION_ARGS)
 	PG_RETURN_VOID();
 }
 
+Datum
+binary_upgrade_set_next_multirange_pg_type_oid(PG_FUNCTION_ARGS)
+{
+	Oid			typoid = PG_GETARG_OID(0);
+
+	CHECK_IS_BINARY_UPGRADE;
+	binary_upgrade_next_mrng_pg_type_oid = typoid;
+
+	PG_RETURN_VOID();
+}
+
+Datum
+binary_upgrade_set_next_multirange_array_pg_type_oid(PG_FUNCTION_ARGS)
+{
+	Oid			typoid = PG_GETARG_OID(0);
+
+	CHECK_IS_BINARY_UPGRADE;
+	binary_upgrade_next_mrng_array_pg_type_oid = typoid;
+
+	PG_RETURN_VOID();
+}
+
 Datum
 binary_upgrade_set_next_heap_pg_class_oid(PG_FUNCTION_ARGS)
 {
diff --git a/src/backend/utils/adt/pseudotypes.c b/src/backend/utils/adt/pseudotypes.c
index 3d6b2f90935..99a93271fe1 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"
 
 
 /*
@@ -227,6 +228,43 @@ anycompatiblerange_out(PG_FUNCTION_ARGS)
 	return range_out(fcinfo);
 }
 
+/*
+ * anycompatiblemultirange
+ *
+ * We may as well allow output, since multirange_out will in fact work.
+ */
+PSEUDOTYPE_DUMMY_INPUT_FUNC(anycompatiblemultirange);
+
+Datum
+anycompatiblemultirange_out(PG_FUNCTION_ARGS)
+{
+	return multirange_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
  *
diff --git a/src/backend/utils/adt/rangetypes.c b/src/backend/utils/adt/rangetypes.c
index 01ad8bc240b..ba4caa2d36a 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
 {
@@ -64,10 +62,6 @@ static const char *range_parse_bound(const char *string, const char *ptr,
 static char *range_deparse(char flags, const char *lbound_str,
 						   const char *ubound_str);
 static char *range_bound_escape(const char *value);
-static Size datum_compute_size(Size sz, Datum datum, bool typbyval,
-							   char typalign, int16 typlen, char typstorage);
-static Pointer datum_write(Pointer ptr, Datum datum, bool typbyval,
-						   char typalign, int16 typlen, char typstorage);
 
 
 /*
@@ -431,19 +425,37 @@ range_lower(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
 	TypeCacheEntry *typcache;
+	bool		isnull;
+	Datum		result;
+
+	typcache = range_get_typcache(fcinfo, RangeTypeGetOid(r1));
+
+	result = range_lower_internal(typcache, r1, &isnull);
+
+	if (isnull)
+		PG_RETURN_NULL();
+	else
+		PG_RETURN_DATUM(result);
+}
+
+Datum
+range_lower_internal(TypeCacheEntry *typcache, const RangeType *r1, bool *isnull)
+{
 	RangeBound	lower;
 	RangeBound	upper;
 	bool		empty;
 
-	typcache = range_get_typcache(fcinfo, RangeTypeGetOid(r1));
-
 	range_deserialize(typcache, r1, &lower, &upper, &empty);
 
 	/* Return NULL if there's no finite lower bound */
 	if (empty || lower.infinite)
-		PG_RETURN_NULL();
+	{
+		*isnull = true;
+		return 0;
+	}
 
-	PG_RETURN_DATUM(lower.val);
+	*isnull = false;
+	return lower.val;
 }
 
 /* extract upper bound value */
@@ -452,19 +464,37 @@ range_upper(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
 	TypeCacheEntry *typcache;
+	bool		isnull;
+	Datum		result;
+
+	typcache = range_get_typcache(fcinfo, RangeTypeGetOid(r1));
+
+	result = range_upper_internal(typcache, r1, &isnull);
+
+	if (isnull)
+		PG_RETURN_NULL();
+	else
+		PG_RETURN_DATUM(result);
+}
+
+Datum
+range_upper_internal(TypeCacheEntry *typcache, const RangeType *r1, bool *isnull)
+{
 	RangeBound	lower;
 	RangeBound	upper;
 	bool		empty;
 
-	typcache = range_get_typcache(fcinfo, RangeTypeGetOid(r1));
-
 	range_deserialize(typcache, r1, &lower, &upper, &empty);
 
 	/* Return NULL if there's no finite upper bound */
 	if (empty || upper.infinite)
-		PG_RETURN_NULL();
+	{
+		*isnull = true;
+		return 0;
+	}
 
-	PG_RETURN_DATUM(upper.val);
+	*isnull = false;
+	return upper.val;
 }
 
 
@@ -475,9 +505,8 @@ Datum
 range_empty(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
-	char		flags = range_get_flags(r1);
 
-	PG_RETURN_BOOL(flags & RANGE_EMPTY);
+	PG_RETURN_BOOL(range_has_flag(r1, RANGE_EMPTY));
 }
 
 /* is lower bound inclusive? */
@@ -485,9 +514,8 @@ Datum
 range_lower_inc(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
-	char		flags = range_get_flags(r1);
 
-	PG_RETURN_BOOL(flags & RANGE_LB_INC);
+	PG_RETURN_BOOL(range_has_flag(r1, RANGE_LB_INC));
 }
 
 /* is upper bound inclusive? */
@@ -495,9 +523,8 @@ Datum
 range_upper_inc(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
-	char		flags = range_get_flags(r1);
 
-	PG_RETURN_BOOL(flags & RANGE_UB_INC);
+	PG_RETURN_BOOL(range_has_flag(r1, RANGE_UB_INC));
 }
 
 /* is lower bound infinite? */
@@ -505,9 +532,8 @@ Datum
 range_lower_inf(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
-	char		flags = range_get_flags(r1);
 
-	PG_RETURN_BOOL(flags & RANGE_LB_INF);
+	PG_RETURN_BOOL(range_has_flag(r1, RANGE_LB_INF));
 }
 
 /* is upper bound infinite? */
@@ -515,9 +541,8 @@ Datum
 range_upper_inf(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
-	char		flags = range_get_flags(r1);
 
-	PG_RETURN_BOOL(flags & RANGE_UB_INF);
+	PG_RETURN_BOOL(range_has_flag(r1, RANGE_UB_INF));
 }
 
 
@@ -957,7 +982,25 @@ range_minus(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
 	RangeType  *r2 = PG_GETARG_RANGE_P(1);
+	RangeType  *ret;
 	TypeCacheEntry *typcache;
+
+	/* 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));
+
+	ret = range_minus_internal(typcache, r1, r2);
+	if (ret)
+		PG_RETURN_RANGE_P(ret);
+	else
+		PG_RETURN_NULL();
+}
+
+RangeType *
+range_minus_internal(TypeCacheEntry *typcache, RangeType *r1, RangeType *r2)
+{
 	RangeBound	lower1,
 				lower2;
 	RangeBound	upper1,
@@ -969,18 +1012,12 @@ range_minus(PG_FUNCTION_ARGS)
 				cmp_u1l2,
 				cmp_u1u2;
 
-	/* 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));
-
 	range_deserialize(typcache, r1, &lower1, &upper1, &empty1);
 	range_deserialize(typcache, r2, &lower2, &upper2, &empty2);
 
 	/* if either is empty, r1 is the correct answer */
 	if (empty1 || empty2)
-		PG_RETURN_RANGE_P(r1);
+		return r1;
 
 	cmp_l1l2 = range_cmp_bounds(typcache, &lower1, &lower2);
 	cmp_l1u2 = range_cmp_bounds(typcache, &lower1, &upper2);
@@ -993,34 +1030,34 @@ range_minus(PG_FUNCTION_ARGS)
 				 errmsg("result of range difference would not be contiguous")));
 
 	if (cmp_l1u2 > 0 || cmp_u1l2 < 0)
-		PG_RETURN_RANGE_P(r1);
+		return r1;
 
 	if (cmp_l1l2 >= 0 && cmp_u1u2 <= 0)
-		PG_RETURN_RANGE_P(make_empty_range(typcache));
+		return make_empty_range(typcache);
 
 	if (cmp_l1l2 <= 0 && cmp_u1l2 >= 0 && cmp_u1u2 <= 0)
 	{
 		lower2.inclusive = !lower2.inclusive;
 		lower2.lower = false;	/* it will become the upper bound */
-		PG_RETURN_RANGE_P(make_range(typcache, &lower1, &lower2, false));
+		return make_range(typcache, &lower1, &lower2, false);
 	}
 
 	if (cmp_l1l2 >= 0 && cmp_u1u2 >= 0 && cmp_l1u2 <= 0)
 	{
 		upper2.inclusive = !upper2.inclusive;
 		upper2.lower = true;	/* it will become the lower bound */
-		PG_RETURN_RANGE_P(make_range(typcache, &upper2, &upper1, false));
+		return make_range(typcache, &upper2, &upper1, false);
 	}
 
 	elog(ERROR, "unexpected case in range_minus");
-	PG_RETURN_NULL();
+	return NULL;
 }
 
 /*
  * Set union.  If strict is true, it is an error that the two input ranges
  * are not adjacent or overlapping.
  */
-static RangeType *
+RangeType *
 range_union_internal(TypeCacheEntry *typcache, RangeType *r1, RangeType *r2,
 					 bool strict)
 {
@@ -1101,6 +1138,19 @@ range_intersect(PG_FUNCTION_ARGS)
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
 	RangeType  *r2 = PG_GETARG_RANGE_P(1);
 	TypeCacheEntry *typcache;
+
+	/* 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));
+
+	PG_RETURN_RANGE_P(range_intersect_internal(typcache, r1, r2));
+}
+
+RangeType *
+range_intersect_internal(TypeCacheEntry *typcache, const RangeType *r1, const RangeType *r2)
+{
 	RangeBound	lower1,
 				lower2;
 	RangeBound	upper1,
@@ -1110,17 +1160,11 @@ range_intersect(PG_FUNCTION_ARGS)
 	RangeBound *result_lower;
 	RangeBound *result_upper;
 
-	/* 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));
-
 	range_deserialize(typcache, r1, &lower1, &upper1, &empty1);
 	range_deserialize(typcache, r2, &lower2, &upper2, &empty2);
 
-	if (empty1 || empty2 || !DatumGetBool(range_overlaps(fcinfo)))
-		PG_RETURN_RANGE_P(make_empty_range(typcache));
+	if (empty1 || empty2 || !range_overlaps_internal(typcache, r1, r2))
+		return make_empty_range(typcache);
 
 	if (range_cmp_bounds(typcache, &lower1, &lower2) >= 0)
 		result_lower = &lower1;
@@ -1132,9 +1176,81 @@ range_intersect(PG_FUNCTION_ARGS)
 	else
 		result_upper = &upper2;
 
-	PG_RETURN_RANGE_P(make_range(typcache, result_lower, result_upper, false));
+	return make_range(typcache, result_lower, result_upper, false);
+}
+
+/* range, range -> range, range functions */
+
+/*
+ * range_split_internal - if r2 intersects the middle of r1, leaving non-empty
+ * ranges on both sides, then return true and set output1 and output2 to the
+ * results of r1 - r2 (in order). Otherwise return false and don't set output1
+ * or output2. Neither input range should be empty.
+ */
+bool
+range_split_internal(TypeCacheEntry *typcache, const RangeType *r1, const RangeType *r2,
+					 RangeType **output1, RangeType **output2)
+{
+	RangeBound	lower1,
+				lower2;
+	RangeBound	upper1,
+				upper2;
+	bool		empty1,
+				empty2;
+
+	range_deserialize(typcache, r1, &lower1, &upper1, &empty1);
+	range_deserialize(typcache, r2, &lower2, &upper2, &empty2);
+
+	if (range_cmp_bounds(typcache, &lower1, &lower2) < 0 &&
+		range_cmp_bounds(typcache, &upper1, &upper2) > 0)
+	{
+		/*
+		 * Need to invert inclusive/exclusive for the lower2 and upper2
+		 * points. They can't be infinite though. We're allowed to overwrite
+		 * these RangeBounds since they only exist locally.
+		 */
+		lower2.inclusive = !lower2.inclusive;
+		lower2.lower = false;
+		upper2.inclusive = !upper2.inclusive;
+		upper2.lower = true;
+
+		*output1 = make_range(typcache, &lower1, &lower2, false);
+		*output2 = make_range(typcache, &upper2, &upper1, false);
+		return true;
+	}
+
+	return false;
 }
 
+/* range -> range aggregate functions */
+
+Datum
+range_intersect_agg_transfn(PG_FUNCTION_ARGS)
+{
+	MemoryContext aggContext;
+	Oid			rngtypoid;
+	TypeCacheEntry *typcache;
+	RangeType  *result;
+	RangeType  *current;
+
+	if (!AggCheckCallContext(fcinfo, &aggContext))
+		elog(ERROR, "range_intersect_agg_transfn called in non-aggregate context");
+
+	rngtypoid = get_fn_expr_argtype(fcinfo->flinfo, 1);
+	if (!type_is_range(rngtypoid))
+		ereport(ERROR, (errmsg("range_intersect_agg must be called with a range")));
+
+	typcache = range_get_typcache(fcinfo, rngtypoid);
+
+	/* strictness ensures these are non-null */
+	result = PG_GETARG_RANGE_P(0);
+	current = PG_GETARG_RANGE_P(1);
+
+	result = range_intersect_internal(typcache, result, current);
+	PG_RETURN_RANGE_P(result);
+}
+
+
 /* Btree support */
 
 /* btree comparator */
@@ -1144,12 +1260,6 @@ range_cmp(PG_FUNCTION_ARGS)
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
 	RangeType  *r2 = PG_GETARG_RANGE_P(1);
 	TypeCacheEntry *typcache;
-	RangeBound	lower1,
-				lower2;
-	RangeBound	upper1,
-				upper2;
-	bool		empty1,
-				empty2;
 	int			cmp;
 
 	check_stack_depth();		/* recurses when subtype is a range type */
@@ -1160,6 +1270,28 @@ range_cmp(PG_FUNCTION_ARGS)
 
 	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, const RangeType *r1, const 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);
 
@@ -1177,10 +1309,7 @@ range_cmp(PG_FUNCTION_ARGS)
 			cmp = range_cmp_bounds(typcache, &upper1, &upper2);
 	}
 
-	PG_FREE_IF_COPY(r1, 0);
-	PG_FREE_IF_COPY(r2, 1);
-
-	PG_RETURN_INT32(cmp);
+	return cmp;
 }
 
 /* inequality operators using the range_cmp function */
@@ -1218,13 +1347,10 @@ range_gt(PG_FUNCTION_ARGS)
 
 /* Hash support */
 
-/* hash a range value */
-Datum
-hash_range(PG_FUNCTION_ARGS)
+uint32
+hash_range_internal(TypeCacheEntry *typcache, const RangeType *r)
 {
-	RangeType  *r = PG_GETARG_RANGE_P(0);
 	uint32		result;
-	TypeCacheEntry *typcache;
 	TypeCacheEntry *scache;
 	RangeBound	lower;
 	RangeBound	upper;
@@ -1233,10 +1359,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 +1400,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, const RangeType *r, Datum seed)
+{
+	uint64		result;
 	TypeCacheEntry *scache;
 	RangeBound	lower;
 	RangeBound	upper;
@@ -1300,10 +1429,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 +1467,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));
 }
 
 /*
@@ -1769,6 +1912,21 @@ range_get_flags(const RangeType *range)
 	return *((char *) range + VARSIZE(range) - 1);
 }
 
+/*
+ * range_has_flag: set whether a range has a specific flag.
+ *
+ * This lets expose some of our functions that just check flags
+ * to the rest of the code base (like to multiranges)
+ * without writing full-fledged *_internal versions.
+ */
+bool
+range_has_flag(const RangeType *r1, char flag)
+{
+	char		flags = range_get_flags(r1);
+
+	return flags & flag;
+}
+
 /*
  * range_set_contain_empty: set the RANGE_CONTAIN_EMPTY bit in the value.
  *
@@ -1937,6 +2095,46 @@ range_cmp_bound_values(TypeCacheEntry *typcache, const RangeBound *b1,
 										   b1->val, b2->val));
 }
 
+/*
+ * qsort callback for sorting ranges.
+ *
+ * Two empty ranges compare equal; an empty range sorts to the left of any
+ * non-empty range.  Two non-empty ranges are sorted by lower bound first
+ * and by upper bound next.
+ */
+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.
  */
@@ -2395,7 +2593,7 @@ range_contains_elem_internal(TypeCacheEntry *typcache, const RangeType *r, Datum
  * Increment data_length by the space needed by the datum, including any
  * preceding alignment padding.
  */
-static Size
+Size
 datum_compute_size(Size data_length, Datum val, bool typbyval, char typalign,
 				   int16 typlen, char typstorage)
 {
@@ -2421,7 +2619,7 @@ datum_compute_size(Size data_length, Datum val, bool typbyval, char typalign,
  * Write the given datum beginning at ptr (after advancing to correct
  * alignment, if needed).  Return the pointer incremented by space used.
  */
-static Pointer
+Pointer
 datum_write(Pointer ptr, Datum datum, bool typbyval, char typalign,
 			int16 typlen, char typstorage)
 {
diff --git a/src/backend/utils/adt/rangetypes_typanalyze.c b/src/backend/utils/adt/rangetypes_typanalyze.c
index 57413b07201..94679cce69b 100644
--- a/src/backend/utils/adt/rangetypes_typanalyze.c
+++ b/src/backend/utils/adt/rangetypes_typanalyze.c
@@ -30,6 +30,7 @@
 #include "utils/fmgrprotos.h"
 #include "utils/lsyscache.h"
 #include "utils/rangetypes.h"
+#include "utils/multirangetypes.h"
 
 static int	float8_qsort_cmp(const void *a1, const void *a2);
 static int	range_bound_qsort_cmp(const void *a1, const void *a2, void *arg);
@@ -60,6 +61,33 @@ range_typanalyze(PG_FUNCTION_ARGS)
 	PG_RETURN_BOOL(true);
 }
 
+/*
+ * multirange_typanalyze -- typanalyze function for multirange columns
+ *
+ * We do the same analysis as for ranges, but on the smallest range that
+ * completely includes the multirange.
+ */
+Datum
+multirange_typanalyze(PG_FUNCTION_ARGS)
+{
+	VacAttrStats *stats = (VacAttrStats *) PG_GETARG_POINTER(0);
+	TypeCacheEntry *typcache;
+	Form_pg_attribute attr = stats->attr;
+
+	/* Get information about multirange type; note column might be a domain */
+	typcache = multirange_get_typcache(fcinfo, getBaseType(stats->attrtypid));
+
+	if (attr->attstattarget < 0)
+		attr->attstattarget = default_statistics_target;
+
+	stats->compute_stats = compute_range_stats;
+	stats->extra_data = typcache;
+	/* same as in std_typanalyze */
+	stats->minrows = 300 * attr->attstattarget;
+
+	PG_RETURN_BOOL(true);
+}
+
 /*
  * Comparison function for sorting float8s, used for range lengths.
  */
@@ -98,7 +126,8 @@ compute_range_stats(VacAttrStats *stats, AnalyzeAttrFetchFunc fetchfunc,
 					int samplerows, double totalrows)
 {
 	TypeCacheEntry *typcache = (TypeCacheEntry *) stats->extra_data;
-	bool		has_subdiff = OidIsValid(typcache->rng_subdiff_finfo.fn_oid);
+	TypeCacheEntry *mltrng_typcache = NULL;
+	bool		has_subdiff;
 	int			null_cnt = 0;
 	int			non_null_cnt = 0;
 	int			non_empty_cnt = 0;
@@ -112,6 +141,15 @@ compute_range_stats(VacAttrStats *stats, AnalyzeAttrFetchFunc fetchfunc,
 			   *uppers;
 	double		total_width = 0;
 
+	if (typcache->typtype == TYPTYPE_MULTIRANGE)
+	{
+		mltrng_typcache = typcache;
+		typcache = typcache->rngtype;
+	}
+	else
+		Assert(typcache->typtype == TYPTYPE_RANGE);
+	has_subdiff = OidIsValid(typcache->rng_subdiff_finfo.fn_oid);
+
 	/* Allocate memory to hold range bounds and lengths of the sample ranges. */
 	lowers = (RangeBound *) palloc(sizeof(RangeBound) * samplerows);
 	uppers = (RangeBound *) palloc(sizeof(RangeBound) * samplerows);
@@ -123,6 +161,7 @@ compute_range_stats(VacAttrStats *stats, AnalyzeAttrFetchFunc fetchfunc,
 		Datum		value;
 		bool		isnull,
 					empty;
+		MultirangeType *multirange;
 		RangeType  *range;
 		RangeBound	lower,
 					upper;
@@ -145,7 +184,23 @@ compute_range_stats(VacAttrStats *stats, AnalyzeAttrFetchFunc fetchfunc,
 		total_width += VARSIZE_ANY(DatumGetPointer(value));
 
 		/* Get range and deserialize it for further analysis. */
-		range = DatumGetRangeTypeP(value);
+		if (mltrng_typcache != NULL)
+		{
+			/* Treat multiranges like a big range without gaps. */
+			multirange = DatumGetMultirangeTypeP(value);
+			if (MultirangeIsEmpty(multirange))
+				range = make_empty_range(typcache);
+			else
+			{
+				int	range_count;
+				RangeType **ranges;
+				multirange_deserialize(typcache, multirange, &range_count, &ranges);
+				range = range_union_internal(typcache, ranges[0],
+						ranges[range_count - 1], false);
+			}
+		}
+		else
+			range = DatumGetRangeTypeP(value);
 		range_deserialize(typcache, range, &lower, &upper, &empty);
 
 		if (!empty)
@@ -262,6 +317,13 @@ compute_range_stats(VacAttrStats *stats, AnalyzeAttrFetchFunc fetchfunc,
 			stats->stakind[slot_idx] = STATISTIC_KIND_BOUNDS_HISTOGRAM;
 			stats->stavalues[slot_idx] = bound_hist_values;
 			stats->numvalues[slot_idx] = num_hist;
+
+			/* Store ranges even if we're analyzing a multirange column */
+			stats->statypid[slot_idx] = typcache->type_id;
+			stats->statyplen[slot_idx] = typcache->typlen;
+			stats->statypbyval[slot_idx] = typcache->typbyval;
+			stats->statypalign[slot_idx] = 'd';
+
 			slot_idx++;
 		}
 
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index ae232991623..2138af7079a 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -2583,6 +2583,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
  *
@@ -3225,7 +3235,7 @@ get_namespace_name_or_temp(Oid nspid)
 		return get_namespace_name(nspid);
 }
 
-/*				---------- PG_RANGE CACHE ----------				 */
+/*				---------- PG_RANGE CACHES ----------				 */
 
 /*
  * get_range_subtype
@@ -3278,6 +3288,56 @@ get_range_collation(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->rngmultitypid;
+		ReleaseSysCache(tp);
+		return result;
+	}
+	else
+		return InvalidOid;
+}
+
+/*
+ * get_multirange_range
+ *		Returns the range type of a given multirange
+ *
+ * Returns InvalidOid if the type is not a multirange.
+ */
+Oid
+get_multirange_range(Oid multirangeOid)
+{
+	HeapTuple	tp;
+
+	tp = SearchSysCache1(RANGEMULTIRANGE, 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 809b27a038f..89f08a4f43c 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -650,6 +650,18 @@ static const struct cachedesc cacheinfo[] = {
 		},
 		64
 	},
+	{RangeRelationId,			/* RANGEMULTIRANGE */
+		RangeMultirangeTypidIndexId,
+		1,
+		{
+			Anum_pg_range_rngmultitypid,
+			0,
+			0,
+			0
+		},
+		4
+	},
+
 	{RangeRelationId,			/* RANGETYPE */
 		RangeTypidIndexId,
 		1,
diff --git a/src/backend/utils/cache/typcache.c b/src/backend/utils/cache/typcache.c
index dca1d48e895..7ea5744060c 100644
--- a/src/backend/utils/cache/typcache.c
+++ b/src/backend/utils/cache/typcache.c
@@ -287,6 +287,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);
@@ -305,6 +306,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 TypeCacheTypCallback(Datum arg, int cacheid, uint32 hashvalue);
 static void TypeCacheOpcCallback(Datum arg, int cacheid, uint32 hashvalue);
@@ -556,8 +560,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))
@@ -594,7 +598,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))
@@ -619,7 +623,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))
@@ -644,7 +648,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))
@@ -694,6 +698,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;
@@ -736,6 +747,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;
@@ -815,6 +833,16 @@ lookup_type_cache(Oid type_id, int flags)
 			(void) lookup_type_cache(typentry->rngelemtype->type_id, 0);
 	}
 
+	/*
+	 * 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
 	 */
@@ -926,6 +954,22 @@ load_rangetype_info(TypeCacheEntry *typentry)
 	typentry->rngelemtype = lookup_type_cache(subtypeOid, 0);
 }
 
+/*
+ * load_multirangetype_info --- helper routine to set up multirange type
+ * information
+ */
+static void
+load_multirangetype_info(TypeCacheEntry *typentry)
+{
+	Oid			rangetypeOid;
+
+	rangetypeOid = get_multirange_range(typentry->type_id);
+	if (!OidIsValid(rangetypeOid))
+		elog(ERROR, "cache lookup failed for multirange type %u",
+			 typentry->type_id);
+
+	typentry->rngtype = lookup_type_cache(rangetypeOid, TYPECACHE_RANGE_INFO);
+}
 
 /*
  * load_domaintype_info --- helper routine to set up domain constraint info
@@ -1558,11 +1602,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
@@ -1605,6 +1649,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 9696c88f241..843a2d81071 100644
--- a/src/backend/utils/fmgr/funcapi.c
+++ b/src/backend/utils/fmgr/funcapi.c
@@ -35,6 +35,7 @@ typedef struct polymorphic_actuals
 	Oid			anyelement_type;	/* anyelement mapping, if known */
 	Oid			anyarray_type;	/* anyarray mapping, if known */
 	Oid			anyrange_type;	/* anyrange mapping, if known */
+	Oid			anymultirange_type;	/* anymultirange mapping, if known */
 } polymorphic_actuals;
 
 static void shutdown_MultiFuncCall(Datum arg);
@@ -46,6 +47,7 @@ static TypeFuncClass internal_get_result_type(Oid funcid,
 static void resolve_anyelement_from_others(polymorphic_actuals *actuals);
 static void resolve_anyarray_from_others(polymorphic_actuals *actuals);
 static void resolve_anyrange_from_others(polymorphic_actuals *actuals);
+static void resolve_anymultirange_from_others(polymorphic_actuals *actuals);
 static bool resolve_polymorphic_tupdesc(TupleDesc tupdesc,
 										oidvector *declared_args,
 										Node *call_expr);
@@ -503,6 +505,34 @@ resolve_anyelement_from_others(polymorphic_actuals *actuals)
 							format_type_be(range_base_type))));
 		actuals->anyelement_type = range_typelem;
 	}
+	else if (OidIsValid(actuals->anymultirange_type))
+	{
+		/* Use the element type based on the multirange type */
+		Oid			multirange_base_type;
+		Oid			multirange_typelem;
+		Oid			range_base_type;
+		Oid			range_typelem;
+
+		multirange_base_type = getBaseType(actuals->anymultirange_type);
+		multirange_typelem = get_multirange_range(multirange_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(multirange_base_type))));
+
+		range_base_type = getBaseType(multirange_typelem);
+		range_typelem = get_range_subtype(range_base_type);
+
+		if (!OidIsValid(range_typelem))
+			ereport(ERROR,
+					(errcode(ERRCODE_DATATYPE_MISMATCH),
+					 errmsg("argument declared %s does not contain a range type but type %s",
+							"anymultirange",
+							format_type_be(range_base_type))));
+		actuals->anyelement_type = range_typelem;
+	}
 	else
 		elog(ERROR, "could not determine polymorphic type");
 }
@@ -540,10 +570,54 @@ static void
 resolve_anyrange_from_others(polymorphic_actuals *actuals)
 {
 	/*
-	 * We can't deduce a range type from other polymorphic inputs, because
-	 * there may be multiple range types with the same subtype.
+	 * We can't deduce a range type from other polymorphic array or base types,
+	 * because there may be multiple range types with the same subtype, but we
+	 * can deduce it from a polymorphic multirange type.
+	 */
+	if (OidIsValid(actuals->anymultirange_type))
+	{
+		/* Use the element type based on the multirange type */
+		Oid			multirange_base_type = getBaseType(actuals->anymultirange_type);
+		Oid			multirange_typelem =
+			get_multirange_range(multirange_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(multirange_base_type))));
+		actuals->anyrange_type = multirange_typelem;
+	}
+	else
+		elog(ERROR, "could not determine polymorphic type");
+}
+
+/*
+ * Resolve actual type of ANYMULTIRANGE from other polymorphic inputs
+ */
+static void
+resolve_anymultirange_from_others(polymorphic_actuals *actuals)
+{
+	/*
+	 * We can't deduce a multirange type from polymorphic array or base types,
+	 * because there may be multiple range types with the same subtype, but we
+	 * can deduce it from a polymorphic range type.
 	 */
-	elog(ERROR, "could not determine polymorphic type");
+	if (OidIsValid(actuals->anyrange_type))
+	{
+		Oid			range_base_type = getBaseType(actuals->anyrange_type);
+		Oid			multirange_typeid = get_range_multirange(range_base_type);
+
+		if (!OidIsValid(multirange_typeid))
+			ereport(ERROR,
+					(errcode(ERRCODE_UNDEFINED_OBJECT),
+					 errmsg("could not find multirange type for data type %s",
+							format_type_be(actuals->anyrange_type))));
+		actuals->anymultirange_type = multirange_typeid;
+	}
+	else
+		elog(ERROR, "could not determine polymorphic type");
 }
 
 /*
@@ -566,9 +640,11 @@ 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_anycompatible_result = false;
 	bool		have_anycompatible_array_result = false;
 	bool		have_anycompatible_range_result = false;
+	bool		have_anycompatible_multirange_result = false;
 	polymorphic_actuals poly_actuals;
 	polymorphic_actuals anyc_actuals;
 	Oid			anycollation = InvalidOid;
@@ -594,6 +670,10 @@ resolve_polymorphic_tupdesc(TupleDesc tupdesc, oidvector *declared_args,
 				have_polymorphic_result = true;
 				have_anyrange_result = true;
 				break;
+			case ANYMULTIRANGEOID:
+				have_polymorphic_result = true;
+				have_anymultirange_result = true;
+				break;
 			case ANYCOMPATIBLEOID:
 			case ANYCOMPATIBLENONARRAYOID:
 				have_polymorphic_result = true;
@@ -607,6 +687,10 @@ resolve_polymorphic_tupdesc(TupleDesc tupdesc, oidvector *declared_args,
 				have_polymorphic_result = true;
 				have_anycompatible_range_result = true;
 				break;
+			case ANYCOMPATIBLEMULTIRANGEOID:
+				have_polymorphic_result = true;
+				have_anycompatible_multirange_result = true;
+				break;
 			default:
 				break;
 		}
@@ -660,6 +744,15 @@ resolve_polymorphic_tupdesc(TupleDesc tupdesc, oidvector *declared_args,
 						return false;
 				}
 				break;
+			case ANYMULTIRANGEOID:
+				if (!OidIsValid(poly_actuals.anymultirange_type))
+				{
+					poly_actuals.anymultirange_type =
+						get_call_expr_argtype(call_expr, i);
+					if (!OidIsValid(poly_actuals.anymultirange_type))
+						return false;
+				}
+				break;
 			case ANYCOMPATIBLEOID:
 			case ANYCOMPATIBLENONARRAYOID:
 				if (!OidIsValid(anyc_actuals.anyelement_type))
@@ -688,6 +781,15 @@ resolve_polymorphic_tupdesc(TupleDesc tupdesc, oidvector *declared_args,
 						return false;
 				}
 				break;
+			case ANYCOMPATIBLEMULTIRANGEOID:
+				if (!OidIsValid(anyc_actuals.anymultirange_type))
+				{
+					anyc_actuals.anymultirange_type =
+						get_call_expr_argtype(call_expr, i);
+					if (!OidIsValid(anyc_actuals.anymultirange_type))
+						return false;
+				}
+				break;
 			default:
 				break;
 		}
@@ -703,6 +805,9 @@ resolve_polymorphic_tupdesc(TupleDesc tupdesc, oidvector *declared_args,
 	if (have_anyrange_result && !OidIsValid(poly_actuals.anyrange_type))
 		resolve_anyrange_from_others(&poly_actuals);
 
+	if (have_anymultirange_result && !OidIsValid(poly_actuals.anymultirange_type))
+		resolve_anymultirange_from_others(&poly_actuals);
+
 	if (have_anycompatible_result && !OidIsValid(anyc_actuals.anyelement_type))
 		resolve_anyelement_from_others(&anyc_actuals);
 
@@ -712,6 +817,9 @@ resolve_polymorphic_tupdesc(TupleDesc tupdesc, oidvector *declared_args,
 	if (have_anycompatible_range_result && !OidIsValid(anyc_actuals.anyrange_type))
 		resolve_anyrange_from_others(&anyc_actuals);
 
+	if (have_anycompatible_multirange_result && !OidIsValid(anyc_actuals.anymultirange_type))
+		resolve_anymultirange_from_others(&anyc_actuals);
+
 	/*
 	 * Identify the collation to use for polymorphic OUT parameters. (It'll
 	 * necessarily be the same for both anyelement and anyarray, likewise for
@@ -780,6 +888,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),
+								   poly_actuals.anymultirange_type,
+								   -1,
+								   0);
+				/* no collation should be attached to a multirange type */
+				break;
 			case ANYCOMPATIBLEOID:
 			case ANYCOMPATIBLENONARRAYOID:
 				TupleDescInitEntry(tupdesc, i + 1,
@@ -805,6 +921,14 @@ resolve_polymorphic_tupdesc(TupleDesc tupdesc, oidvector *declared_args,
 								   0);
 				/* no collation should be attached to a range type */
 				break;
+			case ANYCOMPATIBLEMULTIRANGEOID:
+				TupleDescInitEntry(tupdesc, i + 1,
+								   NameStr(att->attname),
+								   anyc_actuals.anymultirange_type,
+								   -1,
+								   0);
+				/* no collation should be attached to a multirange type */
+				break;
 			default:
 				break;
 		}
@@ -834,9 +958,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;
 	bool		have_anycompatible_result = false;
 	bool		have_anycompatible_array_result = false;
 	bool		have_anycompatible_range_result = false;
+	bool		have_anycompatible_multirange_result = false;
 	polymorphic_actuals poly_actuals;
 	polymorphic_actuals anyc_actuals;
 	int			inargno;
@@ -912,6 +1038,24 @@ resolve_polymorphic_argtypes(int numargs, Oid *argtypes, char *argmodes,
 					argtypes[i] = poly_actuals.anyrange_type;
 				}
 				break;
+			case ANYMULTIRANGEOID:
+				if (argmode == PROARGMODE_OUT || argmode == PROARGMODE_TABLE)
+				{
+					have_polymorphic_result = true;
+					have_anymultirange_result = true;
+				}
+				else
+				{
+					if (!OidIsValid(poly_actuals.anymultirange_type))
+					{
+						poly_actuals.anymultirange_type =
+							get_call_expr_argtype(call_expr, inargno);
+						if (!OidIsValid(poly_actuals.anymultirange_type))
+							return false;
+					}
+					argtypes[i] = poly_actuals.anymultirange_type;
+				}
+				break;
 			case ANYCOMPATIBLEOID:
 			case ANYCOMPATIBLENONARRAYOID:
 				if (argmode == PROARGMODE_OUT || argmode == PROARGMODE_TABLE)
@@ -967,6 +1111,24 @@ resolve_polymorphic_argtypes(int numargs, Oid *argtypes, char *argmodes,
 					argtypes[i] = anyc_actuals.anyrange_type;
 				}
 				break;
+			case ANYCOMPATIBLEMULTIRANGEOID:
+				if (argmode == PROARGMODE_OUT || argmode == PROARGMODE_TABLE)
+				{
+					have_polymorphic_result = true;
+					have_anycompatible_multirange_result = true;
+				}
+				else
+				{
+					if (!OidIsValid(anyc_actuals.anymultirange_type))
+					{
+						anyc_actuals.anymultirange_type =
+							get_call_expr_argtype(call_expr, inargno);
+						if (!OidIsValid(anyc_actuals.anymultirange_type))
+							return false;
+					}
+					argtypes[i] = anyc_actuals.anymultirange_type;
+				}
+				break;
 			default:
 				break;
 		}
@@ -988,6 +1150,9 @@ resolve_polymorphic_argtypes(int numargs, Oid *argtypes, char *argmodes,
 	if (have_anyrange_result && !OidIsValid(poly_actuals.anyrange_type))
 		resolve_anyrange_from_others(&poly_actuals);
 
+	if (have_anymultirange_result && !OidIsValid(poly_actuals.anymultirange_type))
+		resolve_anymultirange_from_others(&poly_actuals);
+
 	if (have_anycompatible_result && !OidIsValid(anyc_actuals.anyelement_type))
 		resolve_anyelement_from_others(&anyc_actuals);
 
@@ -997,6 +1162,9 @@ resolve_polymorphic_argtypes(int numargs, Oid *argtypes, char *argmodes,
 	if (have_anycompatible_range_result && !OidIsValid(anyc_actuals.anyrange_type))
 		resolve_anyrange_from_others(&anyc_actuals);
 
+	if (have_anycompatible_multirange_result && !OidIsValid(anyc_actuals.anymultirange_type))
+		resolve_anymultirange_from_others(&anyc_actuals);
+
 	/* And finally replace the output column types as needed */
 	for (i = 0; i < numargs; i++)
 	{
@@ -1013,6 +1181,9 @@ resolve_polymorphic_argtypes(int numargs, Oid *argtypes, char *argmodes,
 			case ANYRANGEOID:
 				argtypes[i] = poly_actuals.anyrange_type;
 				break;
+			case ANYMULTIRANGEOID:
+				argtypes[i] = poly_actuals.anymultirange_type;
+				break;
 			case ANYCOMPATIBLEOID:
 			case ANYCOMPATIBLENONARRAYOID:
 				argtypes[i] = anyc_actuals.anyelement_type;
@@ -1023,6 +1194,9 @@ resolve_polymorphic_argtypes(int numargs, Oid *argtypes, char *argmodes,
 			case ANYCOMPATIBLERANGEOID:
 				argtypes[i] = anyc_actuals.anyrange_type;
 				break;
+			case ANYCOMPATIBLEMULTIRANGEOID:
+				argtypes[i] = anyc_actuals.anymultirange_type;
+				break;
 			default:
 				break;
 		}
@@ -1052,6 +1226,7 @@ get_type_func_class(Oid typid, Oid *base_typeid)
 		case TYPTYPE_BASE:
 		case TYPTYPE_ENUM:
 		case TYPTYPE_RANGE:
+		case TYPTYPE_MULTIRANGE:
 			return TYPEFUNC_SCALAR;
 		case TYPTYPE_DOMAIN:
 			*base_typeid = typid = getBaseType(typid);
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index dc1d41dd8d2..02d377e5acd 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -272,7 +272,8 @@ static void dumpSearchPath(Archive *AH);
 static void binary_upgrade_set_type_oids_by_type_oid(Archive *fout,
 													 PQExpBuffer upgrade_buffer,
 													 Oid pg_type_oid,
-													 bool force_array_type);
+													 bool force_array_type,
+													 bool include_multirange_type);
 static void binary_upgrade_set_type_oids_by_rel_oid(Archive *fout,
 													PQExpBuffer upgrade_buffer, Oid pg_rel_oid);
 static void binary_upgrade_set_pg_class_oids(Archive *fout,
@@ -1653,7 +1654,7 @@ selectDumpableType(TypeInfo *tyinfo, Archive *fout)
 	}
 
 	/* skip auto-generated array types */
-	if (tyinfo->isArray)
+	if (tyinfo->isArray || tyinfo->isMultirange)
 	{
 		tyinfo->dobj.objType = DO_DUMMY_TYPE;
 
@@ -4461,16 +4462,49 @@ append_depends_on_extension(Archive *fout,
 	}
 }
 
+static Oid
+get_next_possible_free_pg_type_oid(Archive *fout, PQExpBuffer upgrade_query)
+{
+	/*
+	 * If the old version didn't assign an array type, but the new version
+	 * does, we must select an unused type OID to assign.  This currently only
+	 * happens for domains, when upgrading pre-v11 to v11 and up.
+	 *
+	 * Note: local state here is kind of ugly, but we must have some, since we
+	 * mustn't choose the same unused OID more than once.
+	 */
+	static Oid	next_possible_free_oid = FirstNormalObjectId;
+	PGresult   *res;
+	bool		is_dup;
+
+	do
+	{
+		++next_possible_free_oid;
+		printfPQExpBuffer(upgrade_query,
+						  "SELECT EXISTS(SELECT 1 "
+						  "FROM pg_catalog.pg_type "
+						  "WHERE oid = '%u'::pg_catalog.oid);",
+						  next_possible_free_oid);
+		res = ExecuteSqlQueryForSingleRow(fout, upgrade_query->data);
+		is_dup = (PQgetvalue(res, 0, 0)[0] == 't');
+		PQclear(res);
+	} while (is_dup);
+
+	return next_possible_free_oid;
+}
 
 static void
 binary_upgrade_set_type_oids_by_type_oid(Archive *fout,
 										 PQExpBuffer upgrade_buffer,
 										 Oid pg_type_oid,
-										 bool force_array_type)
+										 bool force_array_type,
+										 bool include_multirange_type)
 {
 	PQExpBuffer upgrade_query = createPQExpBuffer();
 	PGresult   *res;
 	Oid			pg_type_array_oid;
+	Oid			pg_type_multirange_oid;
+	Oid			pg_type_multirange_array_oid;
 
 	appendPQExpBufferStr(upgrade_buffer, "\n-- For binary upgrade, must preserve pg_type oid\n");
 	appendPQExpBuffer(upgrade_buffer,
@@ -4491,33 +4525,7 @@ binary_upgrade_set_type_oids_by_type_oid(Archive *fout,
 	PQclear(res);
 
 	if (!OidIsValid(pg_type_array_oid) && force_array_type)
-	{
-		/*
-		 * If the old version didn't assign an array type, but the new version
-		 * does, we must select an unused type OID to assign.  This currently
-		 * only happens for domains, when upgrading pre-v11 to v11 and up.
-		 *
-		 * Note: local state here is kind of ugly, but we must have some,
-		 * since we mustn't choose the same unused OID more than once.
-		 */
-		static Oid	next_possible_free_oid = FirstNormalObjectId;
-		bool		is_dup;
-
-		do
-		{
-			++next_possible_free_oid;
-			printfPQExpBuffer(upgrade_query,
-							  "SELECT EXISTS(SELECT 1 "
-							  "FROM pg_catalog.pg_type "
-							  "WHERE oid = '%u'::pg_catalog.oid);",
-							  next_possible_free_oid);
-			res = ExecuteSqlQueryForSingleRow(fout, upgrade_query->data);
-			is_dup = (PQgetvalue(res, 0, 0)[0] == 't');
-			PQclear(res);
-		} while (is_dup);
-
-		pg_type_array_oid = next_possible_free_oid;
-	}
+		pg_type_array_oid = get_next_possible_free_pg_type_oid(fout, upgrade_query);
 
 	if (OidIsValid(pg_type_array_oid))
 	{
@@ -4528,6 +4536,46 @@ binary_upgrade_set_type_oids_by_type_oid(Archive *fout,
 						  pg_type_array_oid);
 	}
 
+	/*
+	 * Pre-set the multirange type oid and its own array type oid.
+	 */
+	if (include_multirange_type)
+	{
+		if (fout->remoteVersion >= 130000)
+		{
+			appendPQExpBuffer(upgrade_query,
+							  "SELECT t.oid, t.typarray "
+							  "FROM pg_catalog.pg_type t "
+							  "JOIN pg_catalog.pg_range r "
+							  "ON t.oid = r.rngmultitypid "
+							  "WHERE r.rngtypid = '%u'::pg_catalog.oid;",
+							  pg_type_oid);
+
+			res = ExecuteSqlQueryForSingleRow(fout, upgrade_query->data);
+
+			pg_type_multirange_oid = atooid(PQgetvalue(res, 0, PQfnumber(res, "oid")));
+			pg_type_multirange_array_oid = atooid(PQgetvalue(res, 0, PQfnumber(res, "typarray")));
+
+			PQclear(res);
+		}
+		else
+		{
+			pg_type_multirange_oid = get_next_possible_free_pg_type_oid(fout, upgrade_query);
+			pg_type_multirange_array_oid = get_next_possible_free_pg_type_oid(fout, upgrade_query);
+		}
+
+		appendPQExpBufferStr(upgrade_buffer,
+							 "\n-- For binary upgrade, must preserve multirange pg_type oid\n");
+		appendPQExpBuffer(upgrade_buffer,
+						  "SELECT pg_catalog.binary_upgrade_set_next_multirange_pg_type_oid('%u'::pg_catalog.oid);\n\n",
+						  pg_type_multirange_oid);
+		appendPQExpBufferStr(upgrade_buffer,
+							 "\n-- For binary upgrade, must preserve multirange pg_type array oid\n");
+		appendPQExpBuffer(upgrade_buffer,
+						  "SELECT pg_catalog.binary_upgrade_set_next_multirange_array_pg_type_oid('%u'::pg_catalog.oid);\n\n",
+						  pg_type_multirange_array_oid);
+	}
+
 	destroyPQExpBuffer(upgrade_query);
 }
 
@@ -4552,7 +4600,7 @@ binary_upgrade_set_type_oids_by_rel_oid(Archive *fout,
 
 	if (OidIsValid(pg_type_oid))
 		binary_upgrade_set_type_oids_by_type_oid(fout, upgrade_buffer,
-												 pg_type_oid, false);
+												 pg_type_oid, false, false);
 
 	PQclear(upgrade_res);
 	destroyPQExpBuffer(upgrade_query);
@@ -5097,6 +5145,11 @@ getTypes(Archive *fout, int *numTypes)
 		else
 			tyinfo[i].isArray = false;
 
+		if (tyinfo[i].typtype == 'm')
+			tyinfo[i].isMultirange = true;
+		else
+			tyinfo[i].isMultirange = false;
+
 		/* Decide whether we want to dump it */
 		selectDumpableType(&tyinfo[i], fout);
 
@@ -8326,9 +8379,12 @@ getProcLangs(Archive *fout, int *numProcLangs)
 
 /*
  * getCasts
- *	  get basic information about every cast in the system
+ *	  get basic information about most casts in the system
  *
  * numCasts is set to the number of casts read in
+ *
+ * Skip casts from a range to its multirange, since we'll create those
+ * automatically.
  */
 CastInfo *
 getCasts(Archive *fout, int *numCasts)
@@ -8346,7 +8402,20 @@ getCasts(Archive *fout, int *numCasts)
 	int			i_castcontext;
 	int			i_castmethod;
 
-	if (fout->remoteVersion >= 80400)
+	if (fout->remoteVersion >= 130000)
+	{
+		appendPQExpBufferStr(query, "SELECT tableoid, oid, "
+							 "castsource, casttarget, castfunc, castcontext, "
+							 "castmethod "
+							 "FROM pg_cast c "
+							 "WHERE NOT EXISTS ( "
+							 "SELECT 1 FROM pg_range r "
+							 "WHERE c.castsource = r.rngtypid "
+							 "AND c.casttarget = r.rngmultitypid "
+							 ") "
+							 "ORDER BY 3,4");
+	}
+	else if (fout->remoteVersion >= 80400)
 	{
 		appendPQExpBufferStr(query, "SELECT tableoid, oid, "
 							 "castsource, casttarget, castfunc, castcontext, "
@@ -10496,7 +10565,7 @@ dumpEnumType(Archive *fout, TypeInfo *tyinfo)
 	if (dopt->binary_upgrade)
 		binary_upgrade_set_type_oids_by_type_oid(fout, q,
 												 tyinfo->dobj.catId.oid,
-												 false);
+												 false, false);
 
 	appendPQExpBuffer(q, "CREATE TYPE %s AS ENUM (",
 					  qualtypname);
@@ -10622,7 +10691,7 @@ dumpRangeType(Archive *fout, TypeInfo *tyinfo)
 	if (dopt->binary_upgrade)
 		binary_upgrade_set_type_oids_by_type_oid(fout, q,
 												 tyinfo->dobj.catId.oid,
-												 false);
+												 false, true);
 
 	appendPQExpBuffer(q, "CREATE TYPE %s AS RANGE (",
 					  qualtypname);
@@ -10728,7 +10797,7 @@ dumpUndefinedType(Archive *fout, TypeInfo *tyinfo)
 	if (dopt->binary_upgrade)
 		binary_upgrade_set_type_oids_by_type_oid(fout, q,
 												 tyinfo->dobj.catId.oid,
-												 false);
+												 false, false);
 
 	appendPQExpBuffer(q, "CREATE TYPE %s;\n",
 					  qualtypname);
@@ -10933,7 +11002,7 @@ dumpBaseType(Archive *fout, TypeInfo *tyinfo)
 	if (dopt->binary_upgrade)
 		binary_upgrade_set_type_oids_by_type_oid(fout, q,
 												 tyinfo->dobj.catId.oid,
-												 false);
+												 false, false);
 
 	appendPQExpBuffer(q,
 					  "CREATE TYPE %s (\n"
@@ -11120,7 +11189,8 @@ dumpDomain(Archive *fout, TypeInfo *tyinfo)
 	if (dopt->binary_upgrade)
 		binary_upgrade_set_type_oids_by_type_oid(fout, q,
 												 tyinfo->dobj.catId.oid,
-												 true); /* force array type */
+												 true,		/* force array type */
+												 false);	/* force multirange type */
 
 	qtypname = pg_strdup(fmtId(tyinfo->dobj.name));
 	qualtypname = pg_strdup(fmtQualifiedDumpable(tyinfo));
@@ -11308,7 +11378,7 @@ dumpCompositeType(Archive *fout, TypeInfo *tyinfo)
 	{
 		binary_upgrade_set_type_oids_by_type_oid(fout, q,
 												 tyinfo->dobj.catId.oid,
-												 false);
+												 false, false);
 		binary_upgrade_set_pg_class_oids(fout, q, tyinfo->typrelid, false);
 	}
 
@@ -11582,7 +11652,7 @@ dumpShellType(Archive *fout, ShellTypeInfo *stinfo)
 	if (dopt->binary_upgrade)
 		binary_upgrade_set_type_oids_by_type_oid(fout, q,
 												 stinfo->baseType->dobj.catId.oid,
-												 false);
+												 false, false);
 
 	appendPQExpBuffer(q, "CREATE TYPE %s;\n",
 					  fmtQualifiedDumpable(stinfo));
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 317bb839702..d7f77f1d3e0 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -176,6 +176,7 @@ typedef struct _typeInfo
 	char		typrelkind;		/* 'r', 'v', 'c', etc */
 	char		typtype;		/* 'b', 'c', etc */
 	bool		isArray;		/* true if auto-generated array type */
+	bool		isMultirange;	/* true if auto-generated multirange type */
 	bool		isDefined;		/* true if typisdefined */
 	/* If needed, we'll create a "shell type" entry for it; link that here: */
 	struct _shellTypeInfo *shellType;	/* shell-type entry, or NULL */
diff --git a/src/include/access/tupmacs.h b/src/include/access/tupmacs.h
index 70157cf90ab..c262265b951 100644
--- a/src/include/access/tupmacs.h
+++ b/src/include/access/tupmacs.h
@@ -139,8 +139,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/binary_upgrade.h b/src/include/catalog/binary_upgrade.h
index 02fecb90f79..1ff62e9f8e6 100644
--- a/src/include/catalog/binary_upgrade.h
+++ b/src/include/catalog/binary_upgrade.h
@@ -16,6 +16,8 @@
 
 extern PGDLLIMPORT Oid binary_upgrade_next_pg_type_oid;
 extern PGDLLIMPORT Oid binary_upgrade_next_array_pg_type_oid;
+extern PGDLLIMPORT Oid binary_upgrade_next_mrng_pg_type_oid;
+extern PGDLLIMPORT Oid binary_upgrade_next_mrng_array_pg_type_oid;
 
 extern PGDLLIMPORT Oid binary_upgrade_next_heap_pg_class_oid;
 extern PGDLLIMPORT Oid binary_upgrade_next_index_pg_class_oid;
diff --git a/src/include/catalog/pg_aggregate.dat b/src/include/catalog/pg_aggregate.dat
index ffabe275c00..03bab74253d 100644
--- a/src/include/catalog/pg_aggregate.dat
+++ b/src/include/catalog/pg_aggregate.dat
@@ -538,6 +538,17 @@
   aggtransfn => 'bytea_string_agg_transfn',
   aggfinalfn => 'bytea_string_agg_finalfn', aggtranstype => 'internal' },
 
+# range
+{ aggfnoid => 'range_intersect_agg(anyrange)',
+  aggtransfn => 'range_intersect_agg_transfn',
+  aggcombinefn => 'range_intersect_agg_transfn', aggtranstype => 'anyrange' },
+{ aggfnoid => 'range_intersect_agg(anymultirange)',
+  aggtransfn => 'multirange_intersect_agg_transfn',
+  aggcombinefn => 'multirange_intersect_agg_transfn', aggtranstype => 'anymultirange' },
+{ aggfnoid => 'range_agg(anyrange)', aggtransfn => 'range_agg_transfn',
+  aggfinalfn => 'range_agg_finalfn', aggfinalextra => 't',
+  aggtranstype => 'internal' },
+
 # json
 { aggfnoid => 'json_agg', aggtransfn => 'json_agg_transfn',
   aggfinalfn => 'json_agg_finalfn', aggtranstype => 'internal' },
diff --git a/src/include/catalog/pg_amop.dat b/src/include/catalog/pg_amop.dat
index 2c899f19d92..78d7d2c5232 100644
--- a/src/include/catalog/pg_amop.dat
+++ b/src/include/catalog/pg_amop.dat
@@ -1374,6 +1374,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 db3e8c2d01a..9d423d535cd 100644
--- a/src/include/catalog/pg_amproc.dat
+++ b/src/include/catalog/pg_amproc.dat
@@ -285,6 +285,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' },
 { amprocfamily => 'btree/xid8_ops', amproclefttype => 'xid8',
@@ -457,6 +459,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',
@@ -1280,12 +1287,13 @@
   amprocrighttype => 'anyrange', amprocnum => '4',
   amproc => 'brin_inclusion_union' },
 { amprocfamily => 'brin/range_inclusion_ops', amproclefttype => 'anyrange',
-  amprocrighttype => 'anyrange', amprocnum => '11', amproc => 'range_merge' },
+  amprocrighttype => 'anyrange', amprocnum => '11',
+  amproc => 'range_merge(anyrange,anyrange)' },
 { amprocfamily => 'brin/range_inclusion_ops', amproclefttype => 'anyrange',
   amprocrighttype => 'anyrange', amprocnum => '13',
   amproc => 'range_contains' },
 { amprocfamily => 'brin/range_inclusion_ops', amproclefttype => 'anyrange',
-  amprocrighttype => 'anyrange', amprocnum => '14', amproc => 'isempty' },
+  amprocrighttype => 'anyrange', amprocnum => '14', amproc => 'isempty(anyrange)' },
 
 # minmax pg_lsn
 { amprocfamily => 'brin/pg_lsn_minmax_ops', amproclefttype => 'pg_lsn',
diff --git a/src/include/catalog/pg_cast.dat b/src/include/catalog/pg_cast.dat
index 5a58f50fbb2..d6ca624addc 100644
--- a/src/include/catalog/pg_cast.dat
+++ b/src/include/catalog/pg_cast.dat
@@ -530,4 +530,17 @@
 { castsource => 'jsonb', casttarget => 'float8', castfunc => 'float8(jsonb)',
   castcontext => 'e', castmethod => 'f' },
 
+# range to multirange
+{ castsource => 'int4range', casttarget => 'int4multirange', castfunc => 'int4multirange(int4range)',
+  castcontext => 'e', castmethod => 'f' },
+{ castsource => 'int8range', casttarget => 'int8multirange', castfunc => 'int8multirange(int8range)',
+  castcontext => 'e', castmethod => 'f' },
+{ castsource => 'numrange', casttarget => 'nummultirange', castfunc => 'nummultirange(numrange)',
+  castcontext => 'e', castmethod => 'f' },
+{ castsource => 'daterange', casttarget => 'datemultirange', castfunc => 'datemultirange(daterange)',
+  castcontext => 'e', castmethod => 'f' },
+{ castsource => 'tsrange', casttarget => 'tsmultirange', castfunc => 'tsmultirange(tsrange)',
+  castcontext => 'e', castmethod => 'f' },
+{ castsource => 'tstzrange', casttarget => 'tstzmultirange', castfunc => 'tstzmultirange(tstzrange)',
+  castcontext => 'e', castmethod => 'f' },
 ]
diff --git a/src/include/catalog/pg_opclass.dat b/src/include/catalog/pg_opclass.dat
index be5712692fe..4c5e475ff7b 100644
--- a/src/include/catalog/pg_opclass.dat
+++ b/src/include/catalog/pg_opclass.dat
@@ -232,6 +232,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 7ae19235ee2..f44a826e2e9 100644
--- a/src/include/catalog/pg_operator.dat
+++ b/src/include/catalog/pg_operator.dat
@@ -3277,5 +3277,174 @@
   oprname => '@@', oprleft => 'jsonb', oprright => 'jsonpath',
   oprresult => 'bool', oprcode => 'jsonb_path_match_opr(jsonb,jsonpath)',
   oprrest => 'matchingsel', oprjoin => 'matchingjoinsel' },
+{ 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 => 'multirangesel', 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 => 'multirangesel', 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 => 'multirangesel', 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 => 'multirangesel', oprjoin => 'scalargtjoinsel' },
+{ oid => '8073', oid_symbol => 'OID_RANGE_OVERLAPS_MULTIRANGE_OP',
+  descr => 'overlaps',
+  oprname => '&&', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '&&(anymultirange,anyrange)',
+  oprcode => 'range_overlaps_multirange', oprrest => 'multirangesel',
+  oprjoin => 'areajoinsel' },
+{ oid => '8074', oid_symbol => 'OID_MULTIRANGE_OVERLAPS_RANGE_OP', descr => 'contains',
+  oprname => '&&', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcom => '&&(anyrange,anymultirange)',
+  oprcode => 'multirange_overlaps_range', oprrest => 'multirangesel',
+  oprjoin => 'areajoinsel' },
+{ oid => '8075', oid_symbol => 'OID_MULTIRANGE_OVERLAPS_MULTIRANGE_OP', descr => 'contains',
+  oprname => '&&', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '&&(anymultirange,anymultirange)',
+  oprcode => 'multirange_overlaps_multirange', oprrest => 'multirangesel',
+  oprjoin => 'areajoinsel' },
+{ oid => '8064', oid_symbol => 'OID_MULTIRANGE_CONTAINS_ELEM_OP',
+  descr => 'contains',
+  oprname => '@>', oprleft => 'anymultirange', oprright => 'anyelement',
+  oprresult => 'bool', oprcom => '<@(anyelement,anymultirange)',
+  oprcode => 'multirange_contains_elem', oprrest => 'multirangesel',
+  oprjoin => 'contjoinsel' },
+{ oid => '8065', oid_symbol => 'OID_MULTIRANGE_CONTAINS_RANGE_OP', descr => 'contains',
+  oprname => '@>', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcom => '<@(anyrange,anymultirange)',
+  oprcode => 'multirange_contains_range', oprrest => 'multirangesel',
+  oprjoin => 'contjoinsel' },
+{ oid => '8066', oid_symbol => 'OID_MULTIRANGE_CONTAINS_MULTIRANGE_OP', descr => 'contains',
+  oprname => '@>', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '<@(anymultirange,anymultirange)',
+  oprcode => 'multirange_contains_multirange', oprrest => 'multirangesel',
+  oprjoin => 'contjoinsel' },
+{ oid => '8067', oid_symbol => 'OID_MULTIRANGE_ELEM_CONTAINED_OP',
+  descr => 'is contained by',
+  oprname => '<@', oprleft => 'anyelement', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '@>(anymultirange,anyelement)',
+  oprcode => 'elem_contained_by_multirange', oprrest => 'multirangesel',
+  oprjoin => 'contjoinsel' },
+{ oid => '8068', oid_symbol => 'OID_MULTIRANGE_RANGE_CONTAINED_OP',
+  descr => 'is contained by',
+  oprname => '<@', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '@>(anymultirange,anyrange)',
+  oprcode => 'range_contained_by_multirange', oprrest => 'multirangesel',
+  oprjoin => 'contjoinsel' },
+{ oid => '8069', oid_symbol => 'OID_MULTIRANGE_MULTIRANGE_CONTAINED_OP',
+  descr => 'is contained by',
+  oprname => '<@', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '@>(anymultirange,anymultirange)',
+  oprcode => 'multirange_contained_by_multirange', oprrest => 'multirangesel',
+  oprjoin => 'contjoinsel' },
+{ oid => '8106', oid_symbol => 'OID_RANGE_OVERLAPS_LEFT_MULTIRANGE_OP',
+  descr => 'overlaps or is left of',
+  oprname => '&<', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcode => 'range_overleft_multirange', oprrest => 'multirangesel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8107', oid_symbol => 'OID_MULTIRANGE_OVERLAPS_LEFT_RANGE_OP',
+  descr => 'overlaps or is left of',
+  oprname => '&<', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcode => 'multirange_overleft_range', oprrest => 'multirangesel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8108', oid_symbol => 'OID_MULTIRANGE_OVERLAPS_LEFT_MULTIRANGE_OP',
+  descr => 'overlaps or is left of',
+  oprname => '&<', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcode => 'multirange_overleft_multirange', oprrest => 'multirangesel',
+  oprjoin => 'scalargtjoinsel' },
+{ oid => '8109', oid_symbol => 'OID_RANGE_OVERLAPS_RIGHT_MULTIRANGE_OP',
+  descr => 'overlaps or is right of',
+  oprname => '&>', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcode => 'range_overright_multirange', oprrest => 'multirangesel',
+  oprjoin => 'scalargtjoinsel' },
+{ oid => '8110', oid_symbol => 'OID_MULTIRANGE_OVERLAPS_RIGHT_RANGE_OP',
+  descr => 'overlaps or is right of',
+  oprname => '&>', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcode => 'multirange_overright_range', oprrest => 'multirangesel',
+  oprjoin => 'scalargtjoinsel' },
+{ oid => '8111', oid_symbol => 'OID_MULTIRANGE_OVERLAPS_RIGHT_MULTIRANGE_OP',
+  descr => 'overlaps or is right of',
+  oprname => '&>', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcode => 'multirange_overright_multirange', oprrest => 'multirangesel',
+  oprjoin => 'scalargtjoinsel' },
+{ oid => '8091', oid_symbol => 'OID_RANGE_ADJACENT_MULTIRANGE_OP', descr => 'is adjacent to',
+  oprname => '-|-', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '-|-(anymultirange,anyrange)',
+  oprcode => 'range_adjacent_multirange', oprrest => 'matchingsel',
+  oprjoin => 'matchingjoinsel' },
+{ oid => '8092', oid_symbol => 'OID_MULTIRANGE_ADJACENT_RANGE_OP', descr => 'is adjacent to',
+  oprname => '-|-', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcom => '-|-(anyrange,anymultirange)',
+  oprcode => 'multirange_adjacent_range', oprrest => 'matchingsel',
+  oprjoin => 'matchingjoinsel' },
+{ oid => '8093', oid_symbol => 'OID_MULTIRANGE_ADJACENT_MULTIRANGE_OP', descr => 'is adjacent to',
+  oprname => '-|-', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '-|-(anymultirange,anymultirange)',
+  oprcode => 'multirange_adjacent_multirange', oprrest => 'matchingsel',
+  oprjoin => 'matchingjoinsel' },
+{ oid => '8117', descr => 'multirange union',
+  oprname => '+', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'anymultirange', oprcom => '+(anymultirange,anymultirange)',
+  oprcode => 'multirange_union' },
+{ oid => '8123', descr => 'multirange minus',
+  oprname => '-', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'anymultirange', oprcode => 'multirange_minus' },
+{ oid => '8129', descr => 'multirange intersect',
+  oprname => '*', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'anymultirange', oprcom => '*(anymultirange,anymultirange)',
+  oprcode => 'multirange_intersect' },
+{ oid => '8082', oid_symbol => 'OID_RANGE_LEFT_MULTIRANGE_OP', descr => 'is left of',
+  oprname => '<<', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '>>(anymultirange,anyrange)',
+  oprcode => 'range_before_multirange', oprrest => 'multirangesel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8083', oid_symbol => 'OID_MULTIRANGE_LEFT_RANGE_OP', descr => 'is left of',
+  oprname => '<<', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcom => '>>(anyrange,anymultirange)',
+  oprcode => 'multirange_before_range', oprrest => 'multirangesel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8084', oid_symbol => 'OID_MULTIRANGE_LEFT_MULTIRANGE_OP', descr => 'is left of',
+  oprname => '<<', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '>>(anymultirange,anymultirange)',
+  oprcode => 'multirange_before_multirange', oprrest => 'multirangesel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8085', oid_symbol => 'OID_RANGE_RIGHT_MULTIRANGE_OP', descr => 'is right of',
+  oprname => '>>', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '<<(anymultirange,anyrange)',
+  oprcode => 'range_after_multirange', oprrest => 'multirangesel',
+  oprjoin => 'scalargtjoinsel' },
+{ oid => '8086', oid_symbol => 'OID_MULTIRANGE_RIGHT_RANGE_OP', descr => 'is right of',
+  oprname => '>>', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcom => '<<(anyrange,anymultirange)',
+  oprcode => 'multirange_after_range', oprrest => 'multirangesel',
+  oprjoin => 'scalargtjoinsel' },
+{ oid => '8087', oid_symbol => 'OID_MULTIRANGE_RIGHT_MULTIRANGE_OP', descr => 'is right of',
+  oprname => '>>', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '<<(anymultirange,anymultirange)',
+  oprcode => 'multirange_after_multirange', oprrest => 'multirangesel',
+  oprjoin => 'scalargtjoinsel' },
 
 ]
diff --git a/src/include/catalog/pg_opfamily.dat b/src/include/catalog/pg_opfamily.dat
index 11c7ad2c145..3ff81026fc7 100644
--- a/src/include/catalog/pg_opfamily.dat
+++ b/src/include/catalog/pg_opfamily.dat
@@ -232,5 +232,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 e7fbda9f81a..c771d12dba2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -7252,6 +7252,14 @@
   proname => 'anycompatiblerange_out', provolatile => 's',
   prorettype => 'cstring', proargtypes => 'anycompatiblerange',
   prosrc => 'anycompatiblerange_out' },
+{ oid => '8154', descr => 'I/O',
+  proname => 'anycompatiblemultirange_in', provolatile => 's',
+  prorettype => 'anycompatiblemultirange', proargtypes => 'cstring oid int4',
+  prosrc => 'anycompatiblemultirange_in' },
+{ oid => '8155', descr => 'I/O',
+  proname => 'anycompatiblemultirange_out', provolatile => 's',
+  prorettype => 'cstring', proargtypes => 'anycompatiblemultirange',
+  prosrc => 'anycompatiblemultirange_out' },
 
 # tablesample method handlers
 { oid => '3313', descr => 'BERNOULLI tablesample method handler',
@@ -9872,6 +9880,10 @@
   descr => 'the smallest range which includes both of the given ranges',
   proname => 'range_merge', prorettype => 'anyrange',
   proargtypes => 'anyrange anyrange', prosrc => 'range_merge' },
+{ oid => '8143',
+  descr => 'the smallest range which includes the whole multirange',
+  proname => 'range_merge', prorettype => 'anyrange',
+  proargtypes => 'anymultirange', prosrc => 'range_merge_from_multirange' },
 { oid => '3868',
   proname => 'range_intersect', prorettype => 'anyrange',
   proargtypes => 'anyrange anyrange', prosrc => 'range_intersect' },
@@ -9921,6 +9933,13 @@
 { oid => '3169', descr => 'restriction selectivity for range operators',
   proname => 'rangesel', provolatile => 's', prorettype => 'float8',
   proargtypes => 'internal oid internal int4', prosrc => 'rangesel' },
+{ oid => '8133', descr => 'range aggregate by intersecting',
+  proname => 'range_intersect_agg_transfn', prorettype => 'anyrange',
+  proargtypes => 'anyrange anyrange', prosrc => 'range_intersect_agg_transfn'},
+{ oid => '8134', descr => 'range aggregate by intersecting',
+  proname => 'range_intersect_agg', prokind => 'a', proisstrict => 'f',
+  prorettype => 'anyrange', proargtypes => 'anyrange',
+  prosrc => 'aggregate_dummy'},
 
 { oid => '3914', descr => 'convert an int4 range to canonical form',
   proname => 'int4range_canonical', prorettype => 'int4range',
@@ -9989,6 +10008,261 @@
   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 => '8094', descr => 'lower bound of multirange',
+  proname => 'lower', prorettype => 'anyelement', proargtypes => 'anymultirange',
+  prosrc => 'multirange_lower' },
+{ oid => '8095', descr => 'upper bound of multirange',
+  proname => 'upper', prorettype => 'anyelement', proargtypes => 'anymultirange',
+  prosrc => 'multirange_upper' },
+{ oid => '8057', descr => 'is the multirange empty?',
+  proname => 'isempty', prorettype => 'bool',
+  proargtypes => 'anymultirange', prosrc => 'multirange_empty' },
+{ oid => '8096', descr => 'is the multirange\'s lower bound inclusive?',
+  proname => 'lower_inc', prorettype => 'bool', proargtypes => 'anymultirange',
+  prosrc => 'multirange_lower_inc' },
+{ oid => '8097', descr => 'is the multirange\'s upper bound inclusive?',
+  proname => 'upper_inc', prorettype => 'bool', proargtypes => 'anymultirange',
+  prosrc => 'multirange_upper_inc' },
+{ oid => '8098', descr => 'is the multirange\'s lower bound infinite?',
+  proname => 'lower_inf', prorettype => 'bool', proargtypes => 'anymultirange',
+  prosrc => 'multirange_lower_inf' },
+{ oid => '8099', descr => 'is the multirange\'s upper bound infinite?',
+  proname => 'upper_inf', prorettype => 'bool', proargtypes => 'anymultirange',
+  prosrc => 'multirange_upper_inf' },
+{ oid => '8008', descr => 'multirange typanalyze',
+  proname => 'multirange_typanalyze', provolatile => 's', prorettype => 'bool',
+  proargtypes => 'internal', prosrc => 'multirange_typanalyze' },
+{ oid => '8156', descr => 'restriction selectivity for multirange operators',
+  proname => 'multirangesel', provolatile => 's', prorettype => 'float8',
+  proargtypes => 'internal oid internal int4', prosrc => 'multirangesel' },
+{ oid => '8033',
+  proname => 'multirange_eq', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_eq' },
+{ oid => '8034',
+  proname => 'multirange_ne', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_ne' },
+{ oid => '8070',
+  proname => 'range_overlaps_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_overlaps_multirange' },
+{ oid => '8071',
+  proname => 'multirange_overlaps_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_overlaps_range' },
+{ oid => '8072',
+  proname => 'multirange_overlaps_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_overlaps_multirange' },
+{ oid => '8058',
+  proname => 'multirange_contains_elem', prorettype => 'bool',
+  proargtypes => 'anymultirange anyelement', prosrc => 'multirange_contains_elem' },
+{ oid => '8059',
+  proname => 'multirange_contains_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_contains_range' },
+{ oid => '8060',
+  proname => 'multirange_contains_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_contains_multirange' },
+{ oid => '8061',
+  proname => 'elem_contained_by_multirange', prorettype => 'bool',
+  proargtypes => 'anyelement anymultirange', prosrc => 'elem_contained_by_multirange' },
+{ oid => '8062',
+  proname => 'range_contained_by_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_contained_by_multirange' },
+{ oid => '8063',
+  proname => 'multirange_contained_by_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_contained_by_multirange' },
+{ oid => '8088',
+  proname => 'range_adjacent_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_adjacent_multirange' },
+{ oid => '8089',
+  proname => 'multirange_adjacent_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_adjacent_multirange' },
+{ oid => '8090',
+  proname => 'multirange_adjacent_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_adjacent_range' },
+{ oid => '8076',
+  proname => 'range_before_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_before_multirange' },
+{ oid => '8077',
+  proname => 'multirange_before_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_before_range' },
+{ oid => '8078',
+  proname => 'multirange_before_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_before_multirange' },
+{ oid => '8079',
+  proname => 'range_after_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_after_multirange' },
+{ oid => '8080',
+  proname => 'multirange_after_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_after_range' },
+{ oid => '8081',
+  proname => 'multirange_after_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_after_multirange' },
+{ oid => '8100',
+  proname => 'range_overleft_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_overleft_multirange' },
+{ oid => '8101',
+  proname => 'multirange_overleft_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_overleft_range' },
+{ oid => '8102',
+  proname => 'multirange_overleft_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_overleft_multirange' },
+{ oid => '8103',
+  proname => 'range_overright_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_overright_multirange' },
+{ oid => '8104',
+  proname => 'multirange_overright_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_overright_range' },
+{ oid => '8105',
+  proname => 'multirange_overright_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_overright_multirange' },
+{ oid => '8114',
+  proname => 'multirange_union', prorettype => 'anymultirange',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_union' },
+{ oid => '8120',
+  proname => 'multirange_minus', prorettype => 'anymultirange',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_minus' },
+{ oid => '8126',
+  proname => 'multirange_intersect', prorettype => 'anymultirange',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_intersect' },
+{ oid => '8022', descr => 'less-equal-greater',
+  proname => 'multirange_cmp', prorettype => 'int4',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_cmp' },
+{ oid => '8023',
+  proname => 'multirange_lt', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_lt' },
+{ oid => '8024',
+  proname => 'multirange_le', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_le' },
+{ oid => '8025',
+  proname => 'multirange_ge', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_ge' },
+{ oid => '8026',
+  proname => 'multirange_gt', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_gt' },
+{ oid => '8038', descr => 'hash a multirange',
+  proname => 'hash_multirange', prorettype => 'int4', proargtypes => 'anymultirange',
+  prosrc => 'hash_multirange' },
+{ oid => '8039', descr => 'hash a multirange',
+  proname => 'hash_multirange_extended', prorettype => 'int8',
+  proargtypes => 'anymultirange int8', prosrc => 'hash_multirange_extended' },
+
+{ oid => '8045', descr => 'int4multirange constructor',
+  proname => 'int4multirange', proisstrict => 'f',
+  prorettype => 'int4multirange', proargtypes => '',
+  prosrc => 'multirange_constructor0' },
+{ oid => '8146', descr => 'int4multirange constructor',
+  proname => 'int4multirange', proisstrict => 't',
+  prorettype => 'int4multirange', proargtypes => 'int4range',
+  prosrc => 'multirange_constructor1' },
+{ oid => '8046', descr => 'int4multirange constructor',
+  proname => 'int4multirange', provariadic => 'int4range', proisstrict => 'f',
+  prorettype => 'int4multirange', proargtypes => '_int4range',
+  proallargtypes => '{_int4range}', proargmodes => '{v}',
+  prosrc => 'multirange_constructor2' },
+{ oid => '8047', descr => 'nummultirange constructor',
+  proname => 'nummultirange', proisstrict => 'f',
+  prorettype => 'nummultirange', proargtypes => '',
+  prosrc => 'multirange_constructor0' },
+{ oid => '8147', descr => 'nummultirange constructor',
+  proname => 'nummultirange', proisstrict => 't',
+  prorettype => 'nummultirange', proargtypes => 'numrange',
+  prosrc => 'multirange_constructor1' },
+{ oid => '8048', descr => 'nummultirange constructor',
+  proname => 'nummultirange', provariadic => 'numrange', proisstrict => 'f',
+  prorettype => 'nummultirange', proargtypes => '_numrange',
+  proallargtypes => '{_numrange}', proargmodes => '{v}',
+  prosrc => 'multirange_constructor2' },
+{ oid => '8049', descr => 'tsmultirange constructor',
+  proname => 'tsmultirange', proisstrict => 'f',
+  prorettype => 'tsmultirange', proargtypes => '',
+  prosrc => 'multirange_constructor0' },
+{ oid => '8148', descr => 'tsmultirange constructor',
+  proname => 'tsmultirange', proisstrict => 't',
+  prorettype => 'tsmultirange', proargtypes => 'tsrange',
+  prosrc => 'multirange_constructor1' },
+{ oid => '8050', descr => 'tsmultirange constructor',
+  proname => 'tsmultirange', provariadic => 'tsrange', proisstrict => 'f',
+  prorettype => 'tsmultirange', proargtypes => '_tsrange',
+  proallargtypes => '{_tsrange}', proargmodes => '{v}',
+  prosrc => 'multirange_constructor2' },
+{ oid => '8051', descr => 'tstzmultirange constructor',
+  proname => 'tstzmultirange', proisstrict => 'f',
+  prorettype => 'tstzmultirange', proargtypes => '',
+  prosrc => 'multirange_constructor0' },
+{ oid => '8149', descr => 'tstzmultirange constructor',
+  proname => 'tstzmultirange', proisstrict => 't',
+  prorettype => 'tstzmultirange', proargtypes => 'tstzrange',
+  prosrc => 'multirange_constructor1' },
+{ oid => '8052', descr => 'tstzmultirange constructor',
+  proname => 'tstzmultirange', provariadic => 'tstzrange', proisstrict => 'f',
+  prorettype => 'tstzmultirange', proargtypes => '_tstzrange',
+  proallargtypes => '{_tstzrange}', proargmodes => '{v}',
+  prosrc => 'multirange_constructor2' },
+{ oid => '8053', descr => 'datemultirange constructor',
+  proname => 'datemultirange', proisstrict => 'f',
+  prorettype => 'datemultirange', proargtypes => '',
+  prosrc => 'multirange_constructor0' },
+{ oid => '8150', descr => 'datemultirange constructor',
+  proname => 'datemultirange', proisstrict => 't',
+  prorettype => 'datemultirange', proargtypes => 'daterange',
+  prosrc => 'multirange_constructor1' },
+{ oid => '8054', descr => 'datemultirange constructor',
+  proname => 'datemultirange', provariadic => 'daterange', proisstrict => 'f',
+  prorettype => 'datemultirange', proargtypes => '_daterange',
+  proallargtypes => '{_daterange}', proargmodes => '{v}',
+  prosrc => 'multirange_constructor2' },
+{ oid => '8055', descr => 'int8multirange constructor',
+  proname => 'int8multirange', proisstrict => 'f',
+  prorettype => 'int8multirange', proargtypes => '',
+  prosrc => 'multirange_constructor0' },
+{ oid => '8151', descr => 'int8multirange constructor',
+  proname => 'int8multirange', proisstrict => 't',
+  prorettype => 'int8multirange', proargtypes => 'int8range',
+  prosrc => 'multirange_constructor1' },
+{ oid => '8056', descr => 'int8multirange constructor',
+  proname => 'int8multirange', provariadic => 'int8range', proisstrict => 'f',
+  prorettype => 'int8multirange', proargtypes => '_int8range',
+  proallargtypes => '{_int8range}', proargmodes => '{v}',
+  prosrc => 'multirange_constructor2' },
+{ oid => '8152', descr => 'anymultirange cast',
+  proname => 'multirange', proisstrict => 't',
+  prorettype => 'anymultirange', proargtypes => 'anyrange',
+  prosrc => 'multirange_constructor1' },
+{ oid => '8130', descr => 'aggregate transition function',
+  proname => 'range_agg_transfn', proisstrict => 'f', prorettype => 'internal',
+  proargtypes => 'internal anyrange', prosrc => 'range_agg_transfn' },
+{ oid => '8131', descr => 'aggregate final function',
+  proname => 'range_agg_finalfn', proisstrict => 'f', prorettype => 'anymultirange',
+  proargtypes => 'internal anyrange', prosrc => 'range_agg_finalfn' },
+{ oid => '8132', descr => 'combine aggregate input into a multirange',
+  proname => 'range_agg', prokind => 'a', proisstrict => 'f',
+  prorettype => 'anymultirange', proargtypes => 'anyrange',
+  prosrc => 'aggregate_dummy' },
+{ oid => '8135', descr => 'range aggregate by intersecting',
+  proname => 'multirange_intersect_agg_transfn', prorettype => 'anymultirange',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_intersect_agg_transfn'},
+{ oid => '8136', descr => 'range aggregate by intersecting',
+  proname => 'range_intersect_agg', prokind => 'a', proisstrict => 'f',
+  prorettype => 'anymultirange', proargtypes => 'anymultirange',
+  prosrc => 'aggregate_dummy'},
+
 # date, time, timestamp constructors
 { oid => '3846', descr => 'construct date',
   proname => 'make_date', prorettype => 'date', proargtypes => 'int4 int4 int4',
@@ -10371,6 +10645,14 @@
   proname => 'binary_upgrade_set_next_array_pg_type_oid', provolatile => 'v',
   proparallel => 'r', prorettype => 'void', proargtypes => 'oid',
   prosrc => 'binary_upgrade_set_next_array_pg_type_oid' },
+{ oid => '8144', descr => 'for use by pg_upgrade',
+  proname => 'binary_upgrade_set_next_multirange_pg_type_oid', provolatile => 'v',
+  proparallel => 'r', prorettype => 'void', proargtypes => 'oid',
+  prosrc => 'binary_upgrade_set_next_multirange_pg_type_oid' },
+{ oid => '8145', descr => 'for use by pg_upgrade',
+  proname => 'binary_upgrade_set_next_multirange_array_pg_type_oid', provolatile => 'v',
+  proparallel => 'r', prorettype => 'void', proargtypes => 'oid',
+  prosrc => 'binary_upgrade_set_next_multirange_array_pg_type_oid' },
 { oid => '3586', descr => 'for use by pg_upgrade',
   proname => 'binary_upgrade_set_next_heap_pg_class_oid', provolatile => 'v',
   proparallel => 'r', prorettype => 'void', proargtypes => 'oid',
diff --git a/src/include/catalog/pg_range.dat b/src/include/catalog/pg_range.dat
index 479754c2455..10060255c95 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',
+  rngmultitypid => 'int4multirange' },
 { rngtypid => 'numrange', rngsubtype => 'numeric',
   rngsubopc => 'btree/numeric_ops', rngcanonical => '-',
-  rngsubdiff => 'numrange_subdiff' },
+  rngsubdiff => 'numrange_subdiff', rngmultitypid => 'nummultirange' },
 { rngtypid => 'tsrange', rngsubtype => 'timestamp',
   rngsubopc => 'btree/timestamp_ops', rngcanonical => '-',
-  rngsubdiff => 'tsrange_subdiff' },
+  rngsubdiff => 'tsrange_subdiff', rngmultitypid => 'tsmultirange' },
 { rngtypid => 'tstzrange', rngsubtype => 'timestamptz',
   rngsubopc => 'btree/timestamptz_ops', rngcanonical => '-',
-  rngsubdiff => 'tstzrange_subdiff' },
+  rngsubdiff => 'tstzrange_subdiff', rngmultitypid => 'tstzmultirange' },
 { rngtypid => 'daterange', rngsubtype => 'date', rngsubopc => 'btree/date_ops',
-  rngcanonical => 'daterange_canonical', rngsubdiff => 'daterange_subdiff' },
+  rngcanonical => 'daterange_canonical', rngsubdiff => 'daterange_subdiff',
+  rngmultitypid => 'datemultirange' },
 { rngtypid => 'int8range', rngsubtype => 'int8', rngsubopc => 'btree/int8_ops',
-  rngcanonical => 'int8range_canonical', rngsubdiff => 'int8range_subdiff' },
+  rngcanonical => 'int8range_canonical', rngsubdiff => 'int8range_subdiff',
+  rngmultitypid => 'int8multirange' },
 
 ]
diff --git a/src/include/catalog/pg_range.h b/src/include/catalog/pg_range.h
index c28bb57b6c9..07e6dbba667 100644
--- a/src/include/catalog/pg_range.h
+++ b/src/include/catalog/pg_range.h
@@ -34,6 +34,9 @@ CATALOG(pg_range,3541,RangeRelationId)
 	/* OID of range's element type (subtype) */
 	Oid			rngsubtype BKI_LOOKUP(pg_type);
 
+	/* OID of the range's multirange type */
+	Oid			rngmultitypid BKI_LOOKUP(pg_type);
+
 	/* collation for this range type, or 0 */
 	Oid			rngcollation BKI_DEFAULT(0);
 
@@ -57,13 +60,17 @@ typedef FormData_pg_range *Form_pg_range;
 DECLARE_UNIQUE_INDEX(pg_range_rngtypid_index, 3542, on pg_range using btree(rngtypid oid_ops));
 #define RangeTypidIndexId					3542
 
+DECLARE_UNIQUE_INDEX(pg_range_rngmultitypid_index, 8001, on pg_range using btree(rngmultitypid oid_ops));
+#define RangeMultirangeTypidIndexId			8001
+
+
 /*
  * prototypes for functions in pg_range.c
  */
 
 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 21a467a7a7a..35953b51f9f 100644
--- a/src/include/catalog/pg_type.dat
+++ b/src/include/catalog/pg_type.dat
@@ -490,6 +490,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 => 'R', 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 => 'R', 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 => 'R', 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 => 'R', 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 => 'R', 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 => 'R', 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
@@ -619,5 +653,16 @@
   typtype => 'p', typcategory => 'P', typinput => 'anycompatiblerange_in',
   typoutput => 'anycompatiblerange_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' },
+{ oid => '8153',
+  descr => 'pseudo-type representing a multirange over a polymorphic common type',
+  typname => 'anycompatiblemultirange', typlen => '-1', typbyval => 'f',
+  typtype => 'p', typcategory => 'P', typinput => 'anycompatiblemultirange_in',
+  typoutput => 'anycompatiblemultirange_out', typreceive => '-', typsend => '-',
+  typalign => 'd', typstorage => 'x' },
 
 ]
diff --git a/src/include/catalog/pg_type.h b/src/include/catalog/pg_type.h
index 6099e5f57ca..c5dd43a0abe 100644
--- a/src/include/catalog/pg_type.h
+++ b/src/include/catalog/pg_type.h
@@ -270,6 +270,7 @@ DECLARE_UNIQUE_INDEX(pg_type_typname_nsp_index, 2704, on pg_type using btree(typ
 #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 */
 
@@ -311,13 +312,15 @@ DECLARE_UNIQUE_INDEX(pg_type_typname_nsp_index, 2704, on pg_type using btree(typ
 	 (typid) == ANYARRAYOID || \
 	 (typid) == ANYNONARRAYOID || \
 	 (typid) == ANYENUMOID || \
-	 (typid) == ANYRANGEOID)
+	 (typid) == ANYRANGEOID || \
+	 (typid) == ANYMULTIRANGEOID)
 
 #define IsPolymorphicTypeFamily2(typid)  \
 	((typid) == ANYCOMPATIBLEOID || \
 	 (typid) == ANYCOMPATIBLEARRAYOID || \
 	 (typid) == ANYCOMPATIBLENONARRAYOID || \
-	 (typid) == ANYCOMPATIBLERANGEOID)
+	 (typid) == ANYCOMPATIBLERANGEOID || \
+	 (typid) == ANYCOMPATIBLEMULTIRANGEOID)
 
 /*
  * Backwards compatibility for ancient random spellings of pg_type OID macros.
@@ -385,4 +388,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/commands/typecmds.h b/src/include/commands/typecmds.h
index 23130895af4..989914dfe11 100644
--- a/src/include/commands/typecmds.h
+++ b/src/include/commands/typecmds.h
@@ -29,6 +29,8 @@ extern ObjectAddress DefineRange(CreateRangeStmt *stmt);
 extern ObjectAddress AlterEnum(AlterEnumStmt *stmt);
 extern ObjectAddress DefineCompositeType(RangeVar *typevar, List *coldeflist);
 extern Oid	AssignTypeArrayOid(void);
+extern Oid	AssignTypeMultirangeOid(void);
+extern Oid	AssignTypeMultirangeArrayOid(void);
 
 extern ObjectAddress AlterDomainDefault(List *names, Node *defaultRaw);
 extern ObjectAddress AlterDomainNotNull(List *names, bool notNull);
diff --git a/src/include/utils/lsyscache.h b/src/include/utils/lsyscache.h
index fecfe1f4f6e..ed84908d727 100644
--- a/src/include/utils/lsyscache.h
+++ b/src/include/utils/lsyscache.h
@@ -157,6 +157,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);
@@ -183,6 +184,8 @@ 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_collation(Oid rangeOid);
+extern Oid	get_range_multirange(Oid rangeOid);
+extern Oid	get_multirange_range(Oid multirangeOid);
 extern Oid	get_index_column_opclass(Oid index_oid, int attno);
 extern bool get_index_isreplident(Oid index_oid);
 extern bool get_index_isvalid(Oid index_oid);
diff --git a/src/include/utils/multirangetypes.h b/src/include/utils/multirangetypes.h
new file mode 100644
index 00000000000..e0961e5fe14
--- /dev/null
+++ b/src/include/utils/multirangetypes.h
@@ -0,0 +1,144 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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"
+#include "utils/expandeddatum.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
+{
+	int32		vl_len_;		/* varlena header (do not touch directly!) */
+	Oid			multirangetypid;	/* multirange type's own OID */
+	uint32		rangeCount;		/* the number of ranges */
+
+	/*
+	 * Following the count are the range objects themselves, as ShortRangeType
+	 * structs. 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;
+
+/*
+ * Since each RangeType includes its type oid, it seems wasteful to store them
+ * in multiranges like that on-disk. Instead we store ShortRangeType structs
+ * that have everything but the type varlena header and oid. But we do want the
+ * type oid in memory, so that we can call ordinary range functions. We use the
+ * EXPANDED TOAST mechansim to convert between them.
+ */
+typedef struct
+{
+	int32		vl_len_;		/* varlena header */
+	char		flags;			/* range flags */
+	char		_padding;		/* Bounds must be aligned */
+	/* Following the header are zero to two bound values. */
+} ShortRangeType;
+
+#define EMR_MAGIC 689375834		/* ID for debugging crosschecks */
+
+typedef struct ExpandedMultirangeHeader
+{
+	/* Standard header for expanded objects */
+	ExpandedObjectHeader hdr;
+
+	/* Magic value identifying an expanded array (for debugging only) */
+	int			emr_magic;
+
+	Oid			multirangetypid;	/* multirange type's own OID */
+	uint32		rangeCount;			/* the number of ranges */
+	RangeType   **ranges;			/* array of ranges */
+
+	/*
+	 * flat_size is the current space requirement for the flat equivalent of
+	 * the expanded multirange, if known; otherwise it's 0.  We store this to make
+	 * consecutive calls of get_flat_size cheap.
+	 */
+	Size		flat_size;
+} ExpandedMultirangeHeader;
+
+/* Use these macros in preference to accessing these fields 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);
+extern bool multirange_contains_elem_internal(TypeCacheEntry *typcache, MultirangeType *mr,
+											  Datum elem);
+extern bool multirange_contains_range_internal(TypeCacheEntry *typcache, MultirangeType *mr,
+											   RangeType *r);
+extern bool multirange_contains_multirange_internal(TypeCacheEntry *typcache,
+													MultirangeType *mr1,
+													MultirangeType *mr2);
+extern bool range_overlaps_multirange_internal(TypeCacheEntry *typcache, RangeType *r,
+											   MultirangeType *mr);
+extern bool multirange_overlaps_multirange_internal(TypeCacheEntry *typcache,
+													MultirangeType *mr1,
+													MultirangeType *mr2);
+extern bool range_before_multirange_internal(TypeCacheEntry *typcache, RangeType *r,
+											 MultirangeType *mr);
+extern bool range_after_multirange_internal(TypeCacheEntry *typcache, RangeType *r,
+											MultirangeType *mr);
+extern bool multirange_before_multirange_internal(TypeCacheEntry *typcache,
+												  MultirangeType *mr1,
+												  MultirangeType *mr2);
+extern MultirangeType *multirange_minus_internal(Oid mltrngtypoid,
+												 TypeCacheEntry *rangetyp,
+												 int32 range_count1,
+												 RangeType **ranges1,
+												 int32 range_count2,
+												 RangeType **ranges2);
+extern MultirangeType *multirange_intersect_internal(Oid mltrngtypoid,
+													 TypeCacheEntry *rangetyp,
+													 int32 range_count1,
+													 RangeType **ranges1,
+													 int32 range_count2,
+													 RangeType **ranges2);
+
+/* assorted support functions */
+extern TypeCacheEntry *multirange_get_typcache(FunctionCallInfo fcinfo,
+											   Oid mltrngtypid);
+extern void multirange_deserialize(TypeCacheEntry *rangetyp,
+								   const 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);
+
+extern Datum expand_multirange(Datum multirangedatum, MemoryContext parentcontext, TypeCacheEntry *rangetyp);
+
+#endif							/* MULTIRANGETYPES_H */
diff --git a/src/include/utils/rangetypes.h b/src/include/utils/rangetypes.h
index b77c41cf1b8..139e19c7d66 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)
 
@@ -92,13 +94,21 @@ typedef struct
  * prototypes for functions defined in rangetypes.c
  */
 
-extern bool range_contains_elem_internal(TypeCacheEntry *typcache, const RangeType *r, Datum val);
-
 /* internal versions of the above */
+extern int	range_cmp_internal(TypeCacheEntry *typcache, const RangeType *r1,
+							   const RangeType *r2);
+extern uint32 hash_range_internal(TypeCacheEntry *typcache, const RangeType *r);
+extern uint64 hash_range_extended_internal(TypeCacheEntry *typcache, const RangeType *r,
+										   Datum seed);
+extern Datum range_lower_internal(TypeCacheEntry *typcache, const RangeType *r1,
+								  bool *isnull);
+extern Datum range_upper_internal(TypeCacheEntry *typcache, const RangeType *r1,
+								  bool *isnull);
 extern bool range_eq_internal(TypeCacheEntry *typcache, const RangeType *r1,
 							  const RangeType *r2);
 extern bool range_ne_internal(TypeCacheEntry *typcache, const RangeType *r1,
 							  const RangeType *r2);
+extern bool range_contains_elem_internal(TypeCacheEntry *typcache, const RangeType *r, Datum val);
 extern bool range_contains_internal(TypeCacheEntry *typcache, const RangeType *r1,
 									const RangeType *r2);
 extern bool range_contained_by_internal(TypeCacheEntry *typcache, const RangeType *r1,
@@ -115,8 +125,18 @@ extern bool range_overleft_internal(TypeCacheEntry *typcache, const RangeType *r
 									const RangeType *r2);
 extern bool range_overright_internal(TypeCacheEntry *typcache, const RangeType *r1,
 									 const RangeType *r2);
+extern RangeType *range_union_internal(TypeCacheEntry *typcache, RangeType *r1,
+									   RangeType *r2, bool strict);
+extern RangeType *range_minus_internal(TypeCacheEntry *typcache, RangeType *r1,
+									   RangeType *r2);
+extern RangeType *range_intersect_internal(TypeCacheEntry *typcache, const RangeType *r1,
+										   const RangeType *r2);
 
 /* assorted support functions */
+extern Size datum_compute_size(Size sz, Datum datum, bool typbyval,
+							   char typalign, int16 typlen, char typstorage);
+extern Pointer datum_write(Pointer ptr, Datum datum, bool typbyval,
+						   char typalign, int16 typlen, char typstorage);
 extern TypeCacheEntry *range_get_typcache(FunctionCallInfo fcinfo,
 										  Oid rngtypid);
 extern RangeType *range_serialize(TypeCacheEntry *typcache, RangeBound *lower,
@@ -125,6 +145,7 @@ extern void range_deserialize(TypeCacheEntry *typcache, const RangeType *range,
 							  RangeBound *lower, RangeBound *upper,
 							  bool *empty);
 extern char range_get_flags(const RangeType *range);
+extern bool range_has_flag(const RangeType *range, char flag);
 extern void range_set_contain_empty(RangeType *range);
 extern RangeType *make_range(TypeCacheEntry *typcache, RangeBound *lower,
 							 RangeBound *upper, bool empty);
@@ -132,8 +153,12 @@ extern int	range_cmp_bounds(TypeCacheEntry *typcache, const RangeBound *b1,
 							 const RangeBound *b2);
 extern int	range_cmp_bound_values(TypeCacheEntry *typcache, const RangeBound *b1,
 								   const RangeBound *b2);
+extern int	range_compare(const void *key1, const void *key2, void *arg);
 extern bool bounds_adjacent(TypeCacheEntry *typcache, RangeBound bound1,
 							RangeBound bound2);
 extern RangeType *make_empty_range(TypeCacheEntry *typcache);
+extern bool range_split_internal(TypeCacheEntry *typcache, const RangeType *r1,
+								 const RangeType *r2, RangeType **output1,
+								 RangeType **output2);
 
 #endif							/* RANGETYPES_H */
diff --git a/src/include/utils/selfuncs.h b/src/include/utils/selfuncs.h
index 3a2cfb7efa6..68ffd7761f1 100644
--- a/src/include/utils/selfuncs.h
+++ b/src/include/utils/selfuncs.h
@@ -39,6 +39,9 @@
 /* default selectivity estimate for range inequalities "A > b AND A < c" */
 #define DEFAULT_RANGE_INEQ_SEL	0.005
 
+/* default selectivity estimate for multirange inequalities "A > b AND A < c" */
+#define DEFAULT_MULTIRANGE_INEQ_SEL	0.005
+
 /* default selectivity estimate for pattern-match operators such as LIKE */
 #define DEFAULT_MATCH_SEL	0.005
 
diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h
index f27b73d76d0..e8f393520a3 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -79,6 +79,7 @@ enum SysCacheIdentifier
 	PUBLICATIONOID,
 	PUBLICATIONREL,
 	PUBLICATIONRELMAP,
+	RANGEMULTIRANGE,
 	RANGETYPE,
 	RELNAMENSP,
 	RELOID,
diff --git a/src/include/utils/typcache.h b/src/include/utils/typcache.h
index cdd20e56d70..33f332a1417 100644
--- a/src/include/utils/typcache.h
+++ b/src/include/utils/typcache.h
@@ -100,6 +100,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;	/* multirange's range underlying type */
+
 	/*
 	 * Domain's base type and typmod if it's a domain type.  Zeroes if not
 	 * domain, or if information hasn't been requested.
@@ -143,6 +148,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 6df8e14629d..d200facfa45 100644
--- a/src/pl/plpgsql/src/pl_comp.c
+++ b/src/pl/plpgsql/src/pl_comp.c
@@ -514,6 +514,8 @@ do_compile(FunctionCallInfo fcinfo,
 					else if (rettypeid == ANYRANGEOID ||
 							 rettypeid == ANYCOMPATIBLERANGEOID)
 						rettypeid = INT4RANGEOID;
+					else if (rettypeid == ANYMULTIRANGEOID)
+						rettypeid = INT4MULTIRANGEOID;
 					else		/* ANYELEMENT or ANYNONARRAY or ANYCOMPATIBLE */
 						rettypeid = INT4OID;
 					/* XXX what could we use for ANYENUM? */
@@ -2108,6 +2110,7 @@ build_datatype(HeapTuple typeTup, int32 typmod,
 		case TYPTYPE_BASE:
 		case TYPTYPE_ENUM:
 		case TYPTYPE_RANGE:
+		case TYPTYPE_MULTIRANGE:
 			typ->ttype = PLPGSQL_TTYPE_SCALAR;
 			break;
 		case TYPTYPE_COMPOSITE:
@@ -2526,6 +2529,9 @@ plpgsql_resolve_polymorphic_argtypes(int numargs,
 				case ANYCOMPATIBLERANGEOID:
 					argtypes[i] = INT4RANGEOID;
 					break;
+				case ANYMULTIRANGEOID:
+					argtypes[i] = INT4MULTIRANGEOID;
+					break;
 				default:
 					break;
 			}
diff --git a/src/test/regress/expected/dependency.out b/src/test/regress/expected/dependency.out
index b4a11b8aa9b..82327951487 100644
--- a/src/test/regress/expected/dependency.out
+++ b/src/test/regress/expected/dependency.out
@@ -140,6 +140,7 @@ owner of sequence deptest_a_seq
 owner of table deptest
 owner of function deptest_func()
 owner of type deptest_enum
+owner of type deptest_multirange
 owner of type deptest_range
 owner of table deptest2
 owner of sequence ss1
diff --git a/src/test/regress/expected/hash_func.out b/src/test/regress/expected/hash_func.out
index 827e69fc7ab..dca31365bcf 100644
--- a/src/test/regress/expected/hash_func.out
+++ b/src/test/regress/expected/hash_func.out
@@ -305,6 +305,19 @@ 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)
+
 CREATE TYPE hash_test_t1 AS (a int, b text);
 SELECT v as value, hash_record(v)::bit(32) as standard,
        hash_record_extended(v, 0)::bit(32) as extended0,
diff --git a/src/test/regress/expected/multirangetypes.out b/src/test/regress/expected/multirangetypes.out
new file mode 100644
index 00000000000..e72f99863fb
--- /dev/null
+++ b/src/test/regress/expected/multirangetypes.out
@@ -0,0 +1,2437 @@
+-- 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)
+
+--
+-- test casts, both a built-in range type and a user-defined one:
+--
+select 'empty'::int4range::int4multirange;
+ int4multirange 
+----------------
+ {}
+(1 row)
+
+select int4range(1, 3)::int4multirange;
+ int4range 
+-----------
+ {[1,3)}
+(1 row)
+
+select int4range(1, null)::int4multirange;
+ int4range 
+-----------
+ {[1,)}
+(1 row)
+
+select int4range(null, null)::int4multirange;
+ int4range 
+-----------
+ {(,)}
+(1 row)
+
+select 'empty'::textrange::textmultirange;
+ textmultirange 
+----------------
+ {}
+(1 row)
+
+select textrange('a', 'c')::textmultirange;
+ textrange 
+-----------
+ {[a,c)}
+(1 row)
+
+select textrange('a', null)::textmultirange;
+ textrange 
+-----------
+ {[a,)}
+(1 row)
+
+select textrange(null, null)::textmultirange;
+ textrange 
+-----------
+ {(,)}
+(1 row)
+
+--
+-- create some test data and test the operators
+--
+CREATE TABLE nummultirange_test (nmr NUMMULTIRANGE);
+CREATE INDEX nummultirange_test_btree ON nummultirange_test(nmr);
+INSERT INTO nummultirange_test VALUES('{}');
+INSERT INTO nummultirange_test VALUES('{[,)}');
+INSERT INTO nummultirange_test VALUES('{[3,]}');
+INSERT INTO nummultirange_test VALUES('{[,), [3,]}');
+INSERT INTO nummultirange_test VALUES('{[, 5)}');
+INSERT INTO nummultirange_test VALUES(nummultirange());
+INSERT INTO nummultirange_test VALUES(nummultirange(variadic '{}'::numrange[]));
+INSERT INTO nummultirange_test VALUES(nummultirange(numrange(1.1, 2.2)));
+INSERT INTO nummultirange_test VALUES('{empty}');
+INSERT INTO nummultirange_test VALUES(nummultirange(numrange(1.7, 1.7, '[]'), numrange(1.7, 1.9)));
+INSERT INTO nummultirange_test VALUES(nummultirange(numrange(1.7, 1.7, '[]'), numrange(1.9, 2.1)));
+SELECT nmr, isempty(nmr), lower(nmr), upper(nmr) FROM nummultirange_test ORDER BY nmr;
+          nmr          | isempty | lower | upper 
+-----------------------+---------+-------+-------
+ {}                    | t       |       |      
+ {}                    | t       |       |      
+ {}                    | t       |       |      
+ {}                    | t       |       |      
+ {(,5)}                | f       |       |     5
+ {(,)}                 | f       |       |      
+ {(,)}                 | f       |       |      
+ {[1.1,2.2)}           | f       |   1.1 |   2.2
+ {[1.7,1.7],[1.9,2.1)} | f       |   1.7 |   2.1
+ {[1.7,1.9)}           | f       |   1.7 |   1.9
+ {[3,)}                | f       |     3 |      
+(11 rows)
+
+SELECT nmr, lower_inc(nmr), lower_inf(nmr), upper_inc(nmr), upper_inf(nmr) FROM nummultirange_test ORDER BY nmr;
+          nmr          | lower_inc | lower_inf | upper_inc | upper_inf 
+-----------------------+-----------+-----------+-----------+-----------
+ {}                    | f         | f         | f         | f
+ {}                    | f         | f         | f         | f
+ {}                    | f         | f         | f         | f
+ {}                    | f         | f         | f         | f
+ {(,5)}                | f         | t         | f         | f
+ {(,)}                 | f         | t         | f         | t
+ {(,)}                 | f         | t         | f         | t
+ {[1.1,2.2)}           | t         | f         | f         | f
+ {[1.7,1.7],[1.9,2.1)} | t         | f         | f         | f
+ {[1.7,1.9)}           | t         | f         | f         | f
+ {[3,)}                | t         | f         | f         | t
+(11 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr = '{}';
+ nmr 
+-----
+ {}
+ {}
+ {}
+ {}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr = '{(,5)}';
+  nmr   
+--------
+ {(,5)}
+(1 row)
+
+SELECT * FROM nummultirange_test WHERE nmr = '{[3,)}';
+  nmr   
+--------
+ {[3,)}
+(1 row)
+
+SELECT * FROM nummultirange_test WHERE nmr = '{[1.7,1.7]}';
+ nmr 
+-----
+(0 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr = '{[1.7,1.7],[1.9,2.1)}';
+          nmr          
+-----------------------
+ {[1.7,1.7],[1.9,2.1)}
+(1 row)
+
+SELECT * FROM nummultirange_test WHERE nmr < '{}';
+ nmr 
+-----
+(0 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr < '{[-1000.0, -1000.0]}';
+  nmr   
+--------
+ {}
+ {(,)}
+ {(,)}
+ {(,5)}
+ {}
+ {}
+ {}
+(7 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr < '{[0.0, 1.0]}';
+  nmr   
+--------
+ {}
+ {(,)}
+ {(,)}
+ {(,5)}
+ {}
+ {}
+ {}
+(7 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr < '{[1000.0, 1001.0]}';
+          nmr          
+-----------------------
+ {}
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+ {}
+ {}
+ {[1.1,2.2)}
+ {}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(11 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr <= '{}';
+ nmr 
+-----
+ {}
+ {}
+ {}
+ {}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr <= '{[3,)}';
+          nmr          
+-----------------------
+ {}
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+ {}
+ {}
+ {[1.1,2.2)}
+ {}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(11 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr >= '{}';
+          nmr          
+-----------------------
+ {}
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+ {}
+ {}
+ {[1.1,2.2)}
+ {}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(11 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr >= '{[3,)}';
+  nmr   
+--------
+ {[3,)}
+(1 row)
+
+SELECT * FROM nummultirange_test WHERE nmr > '{}';
+          nmr          
+-----------------------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+ {[1.1,2.2)}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(7 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr > '{[-1000.0, -1000.0]}';
+          nmr          
+-----------------------
+ {[3,)}
+ {[1.1,2.2)}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr > '{[0.0, 1.0]}';
+          nmr          
+-----------------------
+ {[3,)}
+ {[1.1,2.2)}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr > '{[1000.0, 1001.0]}';
+ nmr 
+-----
+(0 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr <> '{}';
+          nmr          
+-----------------------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+ {[1.1,2.2)}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(7 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr <> '{(,5)}';
+          nmr          
+-----------------------
+ {}
+ {(,)}
+ {[3,)}
+ {(,)}
+ {}
+ {}
+ {[1.1,2.2)}
+ {}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(10 rows)
+
+select nummultirange(numrange(2.0, 1.0));
+ERROR:  range lower bound must be less than or equal to range upper bound
+select nummultirange(numrange(5.0, 6.0), numrange(1.0, 2.0));
+     nummultirange     
+-----------------------
+ {[1.0,2.0),[5.0,6.0)}
+(1 row)
+
+analyze nummultirange_test;
+-- overlaps
+SELECT * FROM nummultirange_test WHERE range_overlaps_multirange(numrange(4.0, 4.2), nmr);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE numrange(4.0, 4.2) && nmr;
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE multirange_overlaps_range(nmr, numrange(4.0, 4.2));
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr && numrange(4.0, 4.2);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE multirange_overlaps_multirange(nmr, nummultirange(numrange(4.0, 4.2), numrange(6.0, 7.0)));
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr && nummultirange(numrange(4.0, 4.2), numrange(6.0, 7.0));
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr && nummultirange(numrange(6.0, 7.0));
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+(3 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr && nummultirange(numrange(6.0, 7.0), numrange(8.0, 9.0));
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+(3 rows)
+
+-- mr contains x
+SELECT * FROM nummultirange_test WHERE multirange_contains_elem(nmr, 4.0);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr @> 4.0;
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE multirange_contains_range(nmr, numrange(4.0, 4.2));
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr @> numrange(4.0, 4.2);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE multirange_contains_multirange(nmr, '{[4.0,4.2), [6.0, 8.0)}');
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+(3 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr @> '{[4.0,4.2), [6.0, 8.0)}'::nummultirange;
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+(3 rows)
+
+-- x is contained by mr
+SELECT * FROM nummultirange_test WHERE elem_contained_by_multirange(4.0, nmr);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE 4.0 <@ nmr;
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE range_contained_by_multirange(numrange(4.0, 4.2), nmr);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE numrange(4.0, 4.2) <@ nmr;
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE multirange_contained_by_multirange('{[4.0,4.2), [6.0, 8.0)}', nmr);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+(3 rows)
+
+SELECT * FROM nummultirange_test WHERE '{[4.0,4.2), [6.0, 8.0)}'::nummultirange <@ nmr;
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+(3 rows)
+
+-- overlaps
+SELECT 'empty'::numrange && nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT 'empty'::numrange && nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() && 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) && 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() && nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() && nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) && nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) && nummultirange(numrange(1,2), numrange(7,8));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(7,8)) && nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) && nummultirange(numrange(1,2), numrange(3.5,8));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(3.5,8)) && numrange(3,4);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(3.5,8)) && nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+-- contains
+SELECT nummultirange() @> nummultirange();
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange() @> 'empty'::numrange;
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(null,null)) @> numrange(1,2);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(null,null)) @> numrange(null,2);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(null,null)) @> numrange(2,null);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(null,5)) @> numrange(null,3);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(null,5)) @> numrange(null,8);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(5,null)) @> numrange(8,null);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(5,null)) @> numrange(3,null);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,5)) @> numrange(8,9);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,5)) @> numrange(3,9);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,5)) @> numrange(1,4);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,5)) @> numrange(1,5);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(-4,-2), numrange(1,5)) @> numrange(1,5);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,5), numrange(8,9)) @> numrange(1,5);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,5), numrange(8,9)) @> numrange(6,7);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,5), numrange(6,9)) @> numrange(6,7);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[1,5)}'::nummultirange @> '{[1,5)}';
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[-4,-2), [1,5)}'::nummultirange @> '{[1,5)}';
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[1,5), [8,9)}'::nummultirange @> '{[1,5)}';
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[1,5), [8,9)}'::nummultirange @> '{[6,7)}';
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT '{[1,5), [6,9)}'::nummultirange @> '{[6,7)}';
+ ?column? 
+----------
+ t
+(1 row)
+
+-- is contained by
+SELECT nummultirange() <@ nummultirange();
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT 'empty'::numrange <@ nummultirange();
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,2) <@ nummultirange(numrange(null,null));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(null,2) <@ nummultirange(numrange(null,null));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(2,null) <@ nummultirange(numrange(null,null));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(null,3) <@ nummultirange(numrange(null,5));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(null,8) <@ nummultirange(numrange(null,5));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(8,null) <@ nummultirange(numrange(5,null));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(3,null) <@ nummultirange(numrange(5,null));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(8,9) <@ nummultirange(numrange(1,5));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(3,9) <@ nummultirange(numrange(1,5));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(1,4) <@ nummultirange(numrange(1,5));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,5) <@ nummultirange(numrange(1,5));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,5) <@ nummultirange(numrange(-4,-2), numrange(1,5));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,5) <@ nummultirange(numrange(1,5), numrange(8,9));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(6,7) <@ nummultirange(numrange(1,5), numrange(8,9));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(6,7) <@ nummultirange(numrange(1,5), numrange(6,9));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[1,5)}' <@ '{[1,5)}'::nummultirange;
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[1,5)}' <@ '{[-4,-2), [1,5)}'::nummultirange;
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[1,5)}' <@ '{[1,5), [8,9)}'::nummultirange;
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[6,7)}' <@ '{[1,5), [8,9)}'::nummultirange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT '{[6,7)}' <@ '{[1,5), [6,9)}'::nummultirange;
+ ?column? 
+----------
+ t
+(1 row)
+
+-- overleft
+SELECT 'empty'::numrange &< nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT 'empty'::numrange &< nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() &< 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) &< 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() &< nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) &< nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() &< nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(6,7) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(1,2) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,4) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,6) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(3.5,6) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(6,7)) &< numrange(3,4);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) &< numrange(3,4);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,4)) &< numrange(3,4);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,6)) &< numrange(3,4);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3.5,6)) &< numrange(3,4);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(6,7)) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,4)) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,6)) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3.5,6)) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+-- overright
+SELECT nummultirange() &> 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) &> 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT 'empty'::numrange &> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT 'empty'::numrange &> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() &> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() &> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) &> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> numrange(6,7);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> numrange(1,2);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> numrange(1,4);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> numrange(1,6);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> numrange(3.5,6);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(3,4) &> nummultirange(numrange(6,7));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(3,4) &> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(3,4) &> nummultirange(numrange(1,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(3,4) &> nummultirange(numrange(1,6));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(3,4) &> nummultirange(numrange(3.5,6));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(6,7));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(1,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(1,6));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(3.5,6));
+ ?column? 
+----------
+ f
+(1 row)
+
+-- meets
+SELECT 'empty'::numrange -|- nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT 'empty'::numrange -|- nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() -|- 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() -|- nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() -|- nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(1,2) -|- nummultirange(numrange(2,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,2) -|- nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- numrange(2,4);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- numrange(3,4);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- nummultirange(numrange(2,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(5,6)) -|- nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(5,6)) -|- nummultirange(numrange(6,7));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(5,6)) -|- nummultirange(numrange(8,9));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- nummultirange(numrange(2,4), numrange(6,7));
+ ?column? 
+----------
+ t
+(1 row)
+
+-- strictly left
+select 'empty'::numrange << nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(1,2) << nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(1,2) << nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+select numrange(1,2) << nummultirange(numrange(0,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(1,2) << nummultirange(numrange(0,4), numrange(7,8));
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange() << 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange() << numrange(1,2);
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(3,4)) << numrange(3,6);
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(0,2)) << numrange(3,6);
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange(numrange(0,2), numrange(7,8)) << numrange(3,6);
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(-4,-2), numrange(0,2)) << numrange(3,6);
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange() << nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange() << nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(1,2)) << nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(1,2)) << nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(1,2)) << nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange(numrange(1,2)) << nummultirange(numrange(3,4), numrange(7,8));
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange(numrange(1,2), numrange(4,5)) << nummultirange(numrange(3,4), numrange(7,8));
+ ?column? 
+----------
+ f
+(1 row)
+
+-- strictly right
+select nummultirange() >> 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange() >> numrange(1,2);
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(3,4)) >> numrange(1,2);
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange(numrange(0,4)) >> numrange(1,2);
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(0,4), numrange(7,8)) >> numrange(1,2);
+ ?column? 
+----------
+ f
+(1 row)
+
+select 'empty'::numrange >> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(1,2) >> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(3,6) >> nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(3,6) >> nummultirange(numrange(0,2));
+ ?column? 
+----------
+ t
+(1 row)
+
+select numrange(3,6) >> nummultirange(numrange(0,2), numrange(7,8));
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(3,6) >> nummultirange(numrange(-4,-2), numrange(0,2));
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange() >> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(1,2)) >> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange() >> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(1,2)) >> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(3,4)) >> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange(numrange(3,4), numrange(7,8)) >> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange(numrange(3,4), numrange(7,8)) >> nummultirange(numrange(1,2), numrange(4,5));
+ ?column? 
+----------
+ f
+(1 row)
+
+-- union
+SELECT nummultirange() + nummultirange();
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange() + nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) + nummultirange();
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) + nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) + nummultirange(numrange(2,4));
+ ?column? 
+----------
+ {[1,4)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) + nummultirange(numrange(3,4));
+   ?column?    
+---------------
+ {[1,2),[3,4)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) + nummultirange(numrange(2,4));
+ ?column? 
+----------
+ {[1,5)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) + nummultirange(numrange(3,4));
+   ?column?    
+---------------
+ {[1,2),[3,5)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) + nummultirange(numrange(0,9));
+ ?column? 
+----------
+ {[0,9)}
+(1 row)
+
+-- merge
+SELECT range_merge(nummultirange());
+ range_merge 
+-------------
+ empty
+(1 row)
+
+SELECT range_merge(nummultirange(numrange(1,2)));
+ range_merge 
+-------------
+ [1,2)
+(1 row)
+
+SELECT range_merge(nummultirange(numrange(1,2), numrange(7,8)));
+ range_merge 
+-------------
+ [1,8)
+(1 row)
+
+-- minus
+SELECT nummultirange() - nummultirange();
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange() - nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) - nummultirange();
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(3,4)) - nummultirange();
+   ?column?    
+---------------
+ {[1,2),[3,4)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) - nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) - nummultirange(numrange(2,4));
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) - nummultirange(numrange(3,4));
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {[2,4)}
+(1 row)
+
+SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(2,3));
+   ?column?    
+---------------
+ {[1,2),[3,4)}
+(1 row)
+
+SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(0,8));
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(0,2));
+ ?column? 
+----------
+ {[2,4)}
+(1 row)
+
+SELECT nummultirange(numrange(1,8)) - nummultirange(numrange(0,2), numrange(3,4));
+   ?column?    
+---------------
+ {[2,3),[4,8)}
+(1 row)
+
+SELECT nummultirange(numrange(1,8)) - nummultirange(numrange(2,3), numrange(5,null));
+   ?column?    
+---------------
+ {[1,2),[3,5)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(-2,0));
+   ?column?    
+---------------
+ {[1,2),[4,5)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(2,4));
+   ?column?    
+---------------
+ {[1,2),[4,5)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(3,5));
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(0,9));
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange(numrange(1,3), numrange(4,5)) - nummultirange(numrange(2,9));
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(8,9));
+   ?column?    
+---------------
+ {[1,2),[4,5)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(-2,0), numrange(8,9));
+   ?column?    
+---------------
+ {[1,2),[4,5)}
+(1 row)
+
+-- intersection
+SELECT nummultirange() * nummultirange();
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange() * nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) * nummultirange();
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT '{[1,3)}'::nummultirange * '{[1,5)}'::nummultirange;
+ ?column? 
+----------
+ {[1,3)}
+(1 row)
+
+SELECT '{[1,3)}'::nummultirange * '{[0,5)}'::nummultirange;
+ ?column? 
+----------
+ {[1,3)}
+(1 row)
+
+SELECT '{[1,3)}'::nummultirange * '{[0,2)}'::nummultirange;
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT '{[1,3)}'::nummultirange * '{[2,5)}'::nummultirange;
+ ?column? 
+----------
+ {[2,3)}
+(1 row)
+
+SELECT '{[1,4)}'::nummultirange * '{[2,3)}'::nummultirange;
+ ?column? 
+----------
+ {[2,3)}
+(1 row)
+
+SELECT '{[1,4)}'::nummultirange * '{[0,2), [3,5)}'::nummultirange;
+   ?column?    
+---------------
+ {[1,2),[3,4)}
+(1 row)
+
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[0,8), [9,12)}'::nummultirange;
+       ?column?       
+----------------------
+ {[1,4),[7,8),[9,10)}
+(1 row)
+
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[9,12)}'::nummultirange;
+ ?column? 
+----------
+ {[9,10)}
+(1 row)
+
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[-5,-4), [5,6), [9,12)}'::nummultirange;
+ ?column? 
+----------
+ {[9,10)}
+(1 row)
+
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[0,2), [3,8), [9,12)}'::nummultirange;
+          ?column?          
+----------------------------
+ {[1,2),[3,4),[7,8),[9,10)}
+(1 row)
+
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[0,2), [3,8), [9,12)}'::nummultirange;
+          ?column?          
+----------------------------
+ {[1,2),[3,4),[7,8),[9,10)}
+(1 row)
+
+--
+-- range_agg function
+--
+create table reservations ( room_id integer not null, booked_during daterange );
+insert into reservations values
+-- 1: has a meets and a gap
+(1, daterange('2018-07-01', '2018-07-07')),
+(1, daterange('2018-07-07', '2018-07-14')),
+(1, daterange('2018-07-20', '2018-07-22')),
+-- 2: just a single row
+(2, daterange('2018-07-01', '2018-07-03')),
+-- 3: one null range
+(3, NULL),
+-- 4: two null ranges
+(4, NULL),
+(4, NULL),
+-- 5: a null range and a non-null range
+(5, NULL),
+(5, daterange('2018-07-01', '2018-07-03')),
+-- 6: has overlap
+(6, daterange('2018-07-01', '2018-07-07')),
+(6, daterange('2018-07-05', '2018-07-10')),
+-- 7: two ranges that meet: no gap or overlap
+(7, daterange('2018-07-01', '2018-07-07')),
+(7, daterange('2018-07-07', '2018-07-14')),
+-- 8: an empty range
+(8, 'empty'::daterange)
+;
+SELECT   room_id, range_agg(booked_during)
+FROM     reservations
+GROUP BY room_id
+ORDER BY room_id;
+ room_id |                     range_agg                     
+---------+---------------------------------------------------
+       1 | {[07-01-2018,07-14-2018),[07-20-2018,07-22-2018)}
+       2 | {[07-01-2018,07-03-2018)}
+       3 | 
+       4 | 
+       5 | {[07-01-2018,07-03-2018)}
+       6 | {[07-01-2018,07-10-2018)}
+       7 | {[07-01-2018,07-14-2018)}
+       8 | {}
+(8 rows)
+
+-- range_agg on a custom range type too
+SELECT  range_agg(r)
+FROM    (VALUES
+          ('[a,c]'::textrange),
+          ('[b,b]'::textrange),
+          ('[c,f]'::textrange),
+          ('[g,h)'::textrange),
+          ('[h,j)'::textrange)
+        ) t(r);
+   range_agg   
+---------------
+ {[a,f],[g,j)}
+(1 row)
+
+select range_intersect_agg(nmr) from nummultirange_test;
+ range_intersect_agg 
+---------------------
+ {}
+(1 row)
+
+select range_intersect_agg(nmr) from nummultirange_test where false;
+ range_intersect_agg 
+---------------------
+ 
+(1 row)
+
+-- test with just one input:
+select range_intersect_agg(nmr) from (values ('{[1,2]}'::nummultirange)) t(nmr);
+ range_intersect_agg 
+---------------------
+ {[1,2]}
+(1 row)
+
+select range_intersect_agg(nmr) from nummultirange_test where nmr @> 4.0;
+ range_intersect_agg 
+---------------------
+ {[3,5)}
+(1 row)
+
+create table nummultirange_test2(nmr nummultirange);
+create index nummultirange_test2_hash_idx on nummultirange_test2 using hash (nmr);
+INSERT INTO nummultirange_test2 VALUES('{[, 5)}');
+INSERT INTO nummultirange_test2 VALUES(nummultirange(numrange(1.1, 2.2)));
+INSERT INTO nummultirange_test2 VALUES(nummultirange(numrange(1.1, 2.2)));
+INSERT INTO nummultirange_test2 VALUES(nummultirange(numrange(1.1, 2.2,'()')));
+INSERT INTO nummultirange_test2 VALUES('{}');
+select * from nummultirange_test2 where nmr = '{}';
+ nmr 
+-----
+ {}
+(1 row)
+
+select * from nummultirange_test2 where nmr = nummultirange(numrange(1.1, 2.2));
+     nmr     
+-------------
+ {[1.1,2.2)}
+ {[1.1,2.2)}
+(2 rows)
+
+select * from nummultirange_test2 where nmr = nummultirange(numrange(1.1, 2.3));
+ nmr 
+-----
+(0 rows)
+
+set enable_nestloop=t;
+set enable_hashjoin=f;
+set enable_mergejoin=f;
+select * from nummultirange_test natural join nummultirange_test2 order by nmr;
+     nmr     
+-------------
+ {}
+ {}
+ {}
+ {}
+ {(,5)}
+ {[1.1,2.2)}
+ {[1.1,2.2)}
+(7 rows)
+
+set enable_nestloop=f;
+set enable_hashjoin=t;
+set enable_mergejoin=f;
+select * from nummultirange_test natural join nummultirange_test2 order by nmr;
+     nmr     
+-------------
+ {}
+ {}
+ {}
+ {}
+ {(,5)}
+ {[1.1,2.2)}
+ {[1.1,2.2)}
+(7 rows)
+
+set enable_nestloop=f;
+set enable_hashjoin=f;
+set enable_mergejoin=t;
+select * from nummultirange_test natural join nummultirange_test2 order by nmr;
+     nmr     
+-------------
+ {}
+ {}
+ {}
+ {}
+ {(,5)}
+ {[1.1,2.2)}
+ {[1.1,2.2)}
+(7 rows)
+
+set enable_nestloop to default;
+set enable_hashjoin to default;
+set enable_mergejoin to default;
+DROP TABLE nummultirange_test2;
+--
+-- Test user-defined multirange of floats
+--
+select '{[123.001, 5.e9)}'::float8multirange @> 888.882::float8;
+ ?column? 
+----------
+ t
+(1 row)
+
+create table float8multirange_test(f8mr float8multirange, i int);
+insert into float8multirange_test values(float8multirange(float8range(-100.00007, '1.111113e9')), 42);
+select * from float8multirange_test;
+           f8mr            | i  
+---------------------------+----
+ {[-100.00007,1111113000)} | 42
+(1 row)
+
+drop table float8multirange_test;
+--
+-- Test multirange types over domains
+--
+create domain mydomain as int4;
+create type mydomainrange as range(subtype=mydomain);
+select '{[4,50)}'::mydomainmultirange @> 7::mydomain;
+ ?column? 
+----------
+ t
+(1 row)
+
+drop domain mydomain cascade;
+NOTICE:  drop cascades to type mydomainrange
+--
+-- Test domains over multirange types
+--
+create domain restrictedmultirange as int4multirange check (upper(value) < 10);
+select '{[4,5)}'::restrictedmultirange @> 7;
+ ?column? 
+----------
+ f
+(1 row)
+
+select '{[4,50)}'::restrictedmultirange @> 7; -- should fail
+ERROR:  value for domain restrictedmultirange violates check constraint "restrictedmultirange_check"
+drop domain restrictedmultirange;
+--
+-- Test multiple multirange types over the same subtype
+--
+create type textrange1 as range(subtype=text, collation="C");
+create type textrange2 as range(subtype=text, collation="C");
+select textmultirange1(textrange2('a','Z'));  -- should fail
+ERROR:  function textmultirange1(textrange2) does not exist
+LINE 1: select textmultirange1(textrange2('a','Z'));
+               ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+select textmultirange1(textrange1('a','Z')) @> 'b'::text;
+ERROR:  range lower bound must be less than or equal to range upper bound
+select textmultirange2(textrange2('a','z')) @> 'b'::text;
+ ?column? 
+----------
+ t
+(1 row)
+
+drop type textrange1;
+drop type textrange2;
+--
+-- Test polymorphic type system
+--
+create function anyarray_anymultirange_func(a anyarray, r anymultirange)
+  returns anyelement as 'select $1[1] + lower($2);' language sql;
+select anyarray_anymultirange_func(ARRAY[1,2], int4multirange(int4range(10,20)));
+ anyarray_anymultirange_func 
+-----------------------------
+                          11
+(1 row)
+
+-- should fail
+select anyarray_anymultirange_func(ARRAY[1,2], nummultirange(numrange(10,20)));
+ERROR:  function anyarray_anymultirange_func(integer[], nummultirange) does not exist
+LINE 1: select anyarray_anymultirange_func(ARRAY[1,2], nummultirange...
+               ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+drop function anyarray_anymultirange_func(anyarray, anymultirange);
+-- should fail
+create function bogus_func(anyelement)
+  returns anymultirange as 'select int4multirange(int4range(1,10))' language sql;
+ERROR:  cannot determine result data type
+DETAIL:  A result of type anymultirange requires at least one input of type anyrange or anymultirange.
+-- should fail
+create function bogus_func(int)
+  returns anymultirange as 'select int4multirange(int4range(1,10))' language sql;
+ERROR:  cannot determine result data type
+DETAIL:  A result of type anymultirange requires at least one input of type anyrange or anymultirange.
+create function range_add_bounds(anymultirange)
+  returns anyelement as 'select lower($1) + upper($1)' language sql;
+select range_add_bounds(int4multirange(int4range(1, 17)));
+ range_add_bounds 
+------------------
+               18
+(1 row)
+
+select range_add_bounds(nummultirange(numrange(1.0001, 123.123)));
+ range_add_bounds 
+------------------
+         124.1231
+(1 row)
+
+create function multirangetypes_sql(q anymultirange, b anyarray, out c anyelement)
+  as $$ select upper($1) + $2[1] $$
+  language sql;
+select multirangetypes_sql(int4multirange(int4range(1,10)), ARRAY[2,20]);
+ multirangetypes_sql 
+---------------------
+                  12
+(1 row)
+
+select multirangetypes_sql(nummultirange(numrange(1,10)), ARRAY[2,20]);  -- match failure
+ERROR:  function multirangetypes_sql(nummultirange, integer[]) does not exist
+LINE 1: select multirangetypes_sql(nummultirange(numrange(1,10)), AR...
+               ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+create function anycompatiblearray_anycompatiblemultirange_func(a anycompatiblearray, mr anycompatiblemultirange)
+  returns anycompatible as 'select $1[1] + lower($2);' language sql;
+select anycompatiblearray_anycompatiblemultirange_func(ARRAY[1,2], multirange(int4range(10,20)));
+ anycompatiblearray_anycompatiblemultirange_func 
+-------------------------------------------------
+                                              11
+(1 row)
+
+select anycompatiblearray_anycompatiblemultirange_func(ARRAY[1,2], multirange(numrange(10,20)));
+ anycompatiblearray_anycompatiblemultirange_func 
+-------------------------------------------------
+                                              11
+(1 row)
+
+-- should fail
+select anycompatiblearray_anycompatiblemultirange_func(ARRAY[1.1,2], multirange(int4range(10,20)));
+ERROR:  function anycompatiblearray_anycompatiblemultirange_func(numeric[], int4multirange) does not exist
+LINE 1: select anycompatiblearray_anycompatiblemultirange_func(ARRAY...
+               ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+drop function anycompatiblearray_anycompatiblemultirange_func(anycompatiblearray, anycompatiblemultirange);
+create function anycompatiblerange_anycompatiblemultirange_func(r anycompatiblerange, mr anycompatiblemultirange)
+  returns anycompatible as 'select lower($1) + lower($2);' language sql;
+select anycompatiblerange_anycompatiblemultirange_func(int4range(1,2), multirange(int4range(10,20)));
+ anycompatiblerange_anycompatiblemultirange_func 
+-------------------------------------------------
+                                              11
+(1 row)
+
+-- should fail
+select anycompatiblerange_anycompatiblemultirange_func(numrange(1,2), multirange(int4range(10,20)));
+ERROR:  function anycompatiblerange_anycompatiblemultirange_func(numrange, int4multirange) does not exist
+LINE 1: select anycompatiblerange_anycompatiblemultirange_func(numra...
+               ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+drop function anycompatiblerange_anycompatiblemultirange_func(anycompatiblerange, anycompatiblemultirange);
+-- should fail
+create function bogus_func(anycompatible)
+  returns anycompatiblerange as 'select int4range(1,10)' language sql;
+ERROR:  cannot determine result data type
+DETAIL:  A result of type anycompatiblerange requires at least one input of type anycompatiblerange or anycompatiblemultirange.
+--
+-- Arrays of multiranges
+--
+select ARRAY[nummultirange(numrange(1.1, 1.2)), nummultirange(numrange(12.3, 155.5))];
+              array               
+----------------------------------
+ {"{[1.1,1.2)}","{[12.3,155.5)}"}
+(1 row)
+
+create table i8mr_array (f1 int, f2 int8multirange[]);
+insert into i8mr_array values (42, array[int8multirange(int8range(1,10)), int8multirange(int8range(2,20))]);
+select * from i8mr_array;
+ f1 |           f2            
+----+-------------------------
+ 42 | {"{[1,10)}","{[2,20)}"}
+(1 row)
+
+drop table i8mr_array;
+--
+-- Multiranges of arrays
+--
+select arraymultirange(arrayrange(ARRAY[1,2], ARRAY[2,1]));
+   arraymultirange   
+---------------------
+ {["{1,2}","{2,1}")}
+(1 row)
+
+select arraymultirange(arrayrange(ARRAY[2,1], ARRAY[1,2]));  -- fail
+ERROR:  range lower bound must be less than or equal to range upper bound
+select array[1,1] <@ arraymultirange(arrayrange(array[1,2], array[2,1]));
+ ?column? 
+----------
+ f
+(1 row)
+
+select array[1,3] <@ arraymultirange(arrayrange(array[1,2], array[2,1]));
+ ?column? 
+----------
+ t
+(1 row)
+
+--
+-- Ranges of composites
+--
+create type two_ints as (a int, b int);
+create type two_ints_range as range (subtype = two_ints);
+-- with force_parallel_mode on, this exercises tqueue.c's range remapping
+select *, row_to_json(upper(t)) as u from
+  (values (two_ints_multirange(two_ints_range(row(1,2), row(3,4)))),
+          (two_ints_multirange(two_ints_range(row(5,6), row(7,8))))) v(t);
+          t          |       u       
+---------------------+---------------
+ {["(1,2)","(3,4)")} | {"a":3,"b":4}
+ {["(5,6)","(7,8)")} | {"a":7,"b":8}
+(2 rows)
+
+drop type two_ints cascade;
+NOTICE:  drop cascades to type two_ints_range
+--
+-- Check behavior when subtype lacks a hash function
+--
+set enable_sort = off;  -- try to make it pick a hash setop implementation
+select '{(2,5)}'::cashmultirange except select '{(5,6)}'::cashmultirange;
+ cashmultirange  
+-----------------
+ {($2.00,$5.00)}
+(1 row)
+
+reset enable_sort;
+--
+-- OUT/INOUT/TABLE functions
+--
+-- infer anymultirange from anymultirange
+create function mr_outparam_succeed(i anymultirange, out r anymultirange, out t text)
+  as $$ select $1, 'foo'::text $$ language sql;
+select * from mr_outparam_succeed(int4multirange(int4range(1,2)));
+    r    |  t  
+---------+-----
+ {[1,2)} | foo
+(1 row)
+
+-- infer anyarray from anymultirange
+create function mr_outparam_succeed2(i anymultirange, out r anyarray, out t text)
+  as $$ select ARRAY[upper($1)], 'foo'::text $$ language sql;
+select * from mr_outparam_succeed2(int4multirange(int4range(1,2)));
+  r  |  t  
+-----+-----
+ {2} | foo
+(1 row)
+
+-- infer anyrange from anymultirange
+create function mr_outparam_succeed3(i anymultirange, out r anyrange, out t text)
+  as $$ select range_merge($1), 'foo'::text $$ language sql;
+select * from mr_outparam_succeed3(int4multirange(int4range(1,2)));
+   r   |  t  
+-------+-----
+ [1,2) | foo
+(1 row)
+
+-- infer anymultirange from anyrange
+create function mr_outparam_succeed4(i anyrange, out r anymultirange, out t text)
+  as $$ select multirange($1), 'foo'::text $$ language sql;
+select * from mr_outparam_succeed4(int4range(1,2));
+    r    |  t  
+---------+-----
+ {[1,2)} | foo
+(1 row)
+
+-- infer anyelement from anymultirange
+create function mr_inoutparam_succeed(out i anyelement, inout r anymultirange)
+  as $$ select upper($1), $1 $$ language sql;
+select * from mr_inoutparam_succeed(int4multirange(int4range(1,2)));
+ i |    r    
+---+---------
+ 2 | {[1,2)}
+(1 row)
+
+-- infer anyelement+anymultirange from anyelement+anymultirange
+create function mr_table_succeed(i anyelement, r anymultirange) returns table(i anyelement, r anymultirange)
+  as $$ select $1, $2 $$ language sql;
+select * from mr_table_succeed(123, int4multirange(int4range(1,11)));
+  i  |    r     
+-----+----------
+ 123 | {[1,11)}
+(1 row)
+
+-- use anymultirange in plpgsql
+create function mr_polymorphic(i anyrange) returns anymultirange
+  as $$ begin return multirange($1); end; $$ language plpgsql;
+select mr_polymorphic(int4range(1, 4));
+ mr_polymorphic 
+----------------
+ {[1,4)}
+(1 row)
+
+-- should fail
+create function mr_outparam_fail(i anyelement, out r anymultirange, out t text)
+  as $$ select '[1,10]', 'foo' $$ language sql;
+ERROR:  cannot determine result data type
+DETAIL:  A result of type anymultirange requires at least one input of type anyrange or anymultirange.
+--should fail
+create function mr_inoutparam_fail(inout i anyelement, out r anymultirange)
+  as $$ select $1, '[1,10]' $$ language sql;
+ERROR:  cannot determine result data type
+DETAIL:  A result of type anymultirange requires at least one input of type anyrange or anymultirange.
+--should fail
+create function mr_table_fail(i anyelement) returns table(i anyelement, r anymultirange)
+  as $$ select $1, '[1,10]' $$ language sql;
+ERROR:  cannot determine result data type
+DETAIL:  A result of type anymultirange requires at least one input of type anyrange or anymultirange.
diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out
index 3b39137400f..e596ef090dd 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::regtype, p2.prorettype::regtype
 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        
@@ -208,6 +215,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        
@@ -228,6 +237,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        
@@ -340,7 +351,8 @@ WHERE p1.prorettype IN
      '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     
 ------+----------------
@@ -355,20 +367,25 @@ ORDER BY 2;
  3532 | enum_recv
 (9 rows)
 
--- anyrange is tighter than the rest, can only resolve from anyrange input
+-- anyrange and anymultirange are tighter than the rest, can only resolve
+-- from each other
 SELECT p1.oid, p1.proname
 FROM pg_proc as p1
-WHERE p1.prorettype = 'anyrange'::regtype
+WHERE p1.prorettype IN ('anyrange'::regtype, 'anymultirange'::regtype)
   AND NOT
-     'anyrange'::regtype = ANY (p1.proargtypes)
+    ('anyrange'::regtype = ANY (p1.proargtypes) OR
+      'anymultirange'::regtype = ANY (p1.proargtypes))
 ORDER BY 2;
  oid  |     proname      
 ------+------------------
+ 8002 | anymultirange_in
  3832 | anyrange_in
+ 8004 | multirange_in
+ 8006 | multirange_recv
  3876 | range_gist_union
  3834 | range_in
  3836 | range_recv
-(4 rows)
+(7 rows)
 
 -- similarly for the anycompatible family
 SELECT p1.oid, p1.proname
@@ -2202,13 +2219,14 @@ ORDER BY 1, 2, 3;
                     | float_ops        | float4_ops       | real
                     | float_ops        | float8_ops       | double precision
                     | jsonb_ops        | jsonb_ops        | jsonb
+                    | multirange_ops   | multirange_ops   | anymultirange
                     | numeric_ops      | numeric_ops      | numeric
                     | range_ops        | range_ops        | anyrange
                     | record_image_ops | record_image_ops | record
                     | record_ops       | record_ops       | record
                     | tsquery_ops      | tsquery_ops      | tsquery
                     | tsvector_ops     | tsvector_ops     | tsvector
-(14 rows)
+(15 rows)
 
 -- **************** pg_index ****************
 -- Look for illegal values in pg_index fields.
diff --git a/src/test/regress/expected/plpgsql.out b/src/test/regress/expected/plpgsql.out
index d0a6b630b8f..d55006d8c95 100644
--- a/src/test/regress/expected/plpgsql.out
+++ b/src/test/regress/expected/plpgsql.out
@@ -1811,7 +1811,7 @@ begin
   return array[x + 1, x + 2];
 end$$ language plpgsql;
 ERROR:  cannot determine result data type
-DETAIL:  A result of type anyrange requires at least one input of type anyrange.
+DETAIL:  A result of type anyrange requires at least one input of type anyrange or anymultirange.
 create function f1(x anyrange) returns anyarray as $$
 begin
   return array[lower(x), upper(x)];
@@ -1856,7 +1856,7 @@ begin
   return array[x + 1, x + 2];
 end$$ language plpgsql;
 ERROR:  cannot determine result data type
-DETAIL:  A result of type anycompatiblerange requires at least one input of type anycompatiblerange.
+DETAIL:  A result of type anycompatiblerange requires at least one input of type anycompatiblerange or anycompatiblemultirange.
 create function f1(x anycompatiblerange, y anycompatiblearray) returns anycompatiblerange as $$
 begin
   return x;
diff --git a/src/test/regress/expected/polymorphism.out b/src/test/regress/expected/polymorphism.out
index f5dfdf617fd..772345584f0 100644
--- a/src/test/regress/expected/polymorphism.out
+++ b/src/test/regress/expected/polymorphism.out
@@ -61,7 +61,7 @@ create function polyf(x anyelement) returns anyrange as $$
   select array[x + 1, x + 2]
 $$ language sql;
 ERROR:  cannot determine result data type
-DETAIL:  A result of type anyrange requires at least one input of type anyrange.
+DETAIL:  A result of type anyrange requires at least one input of type anyrange or anymultirange.
 create function polyf(x anyrange) returns anyarray as $$
   select array[lower(x), upper(x)]
 $$ language sql;
@@ -97,12 +97,27 @@ LINE 1: select polyf(int4range(42, 49), 11, 4.5) as fail;
                ^
 HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
 drop function polyf(x anycompatiblerange, y anycompatible, z anycompatible);
+create function polyf(x anycompatiblemultirange, y anycompatible, z anycompatible) returns anycompatiblearray as $$
+  select array[lower(x), upper(x), y, z]
+$$ language sql;
+select polyf(multirange(int4range(42, 49)), 11, 2::smallint) as int, polyf(multirange(float8range(4.5, 7.8)), 7.8, 11::real) as num;
+     int      |       num        
+--------------+------------------
+ {42,49,11,2} | {4.5,7.8,7.8,11}
+(1 row)
+
+select polyf(multirange(int4range(42, 49)), 11, 4.5) as fail;  -- range type doesn't fit
+ERROR:  function polyf(int4multirange, integer, numeric) does not exist
+LINE 1: select polyf(multirange(int4range(42, 49)), 11, 4.5) as fail...
+               ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+drop function polyf(x anycompatiblemultirange, y anycompatible, z anycompatible);
 -- fail, can't infer type:
 create function polyf(x anycompatible) returns anycompatiblerange as $$
   select array[x + 1, x + 2]
 $$ language sql;
 ERROR:  cannot determine result data type
-DETAIL:  A result of type anycompatiblerange requires at least one input of type anycompatiblerange.
+DETAIL:  A result of type anycompatiblerange requires at least one input of type anycompatiblerange or anycompatiblemultirange.
 create function polyf(x anycompatiblerange, y anycompatiblearray) returns anycompatiblerange as $$
   select x
 $$ language sql;
@@ -113,6 +128,22 @@ select polyf(int4range(42, 49), array[11]) as int, polyf(float8range(4.5, 7.8),
 (1 row)
 
 drop function polyf(x anycompatiblerange, y anycompatiblearray);
+-- fail, can't infer type:
+create function polyf(x anycompatible) returns anycompatiblemultirange as $$
+  select array[x + 1, x + 2]
+$$ language sql;
+ERROR:  cannot determine result data type
+DETAIL:  A result of type anycompatiblemultirange requires at least one input of type anycompatiblerange or anycompatiblemultirange.
+create function polyf(x anycompatiblemultirange, y anycompatiblearray) returns anycompatiblemultirange as $$
+  select x
+$$ language sql;
+select polyf(multirange(int4range(42, 49)), array[11]) as int, polyf(multirange(float8range(4.5, 7.8)), array[7]) as num;
+    int    |     num     
+-----------+-------------
+ {[42,49)} | {[4.5,7.8)}
+(1 row)
+
+drop function polyf(x anycompatiblemultirange, y anycompatiblearray);
 create function polyf(a anyelement, b anyarray,
                       c anycompatible, d anycompatible,
                       OUT x anyarray, OUT y anycompatiblearray)
@@ -231,7 +262,7 @@ CREATE AGGREGATE myaggp01a(*) (SFUNC = stfnp, STYPE = int4[],
 CREATE AGGREGATE myaggp02a(*) (SFUNC = stfnp, STYPE = anyarray,
   FINALFUNC = ffp, INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 --     N    P
 -- should CREATE
 CREATE AGGREGATE myaggp03a(*) (SFUNC = stfp, STYPE = int4[],
@@ -243,11 +274,11 @@ CREATE AGGREGATE myaggp03b(*) (SFUNC = stfp, STYPE = int4[],
 CREATE AGGREGATE myaggp04a(*) (SFUNC = stfp, STYPE = anyarray,
   FINALFUNC = ffp, INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 CREATE AGGREGATE myaggp04b(*) (SFUNC = stfp, STYPE = anyarray,
   INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 --    Case2 (R = P) && ((B = P) || (B = N))
 --    -------------------------------------
 --    S    tf1      B    tf2
@@ -302,13 +333,13 @@ ERROR:  function tfp(integer[], anyelement) does not exist
 CREATE AGGREGATE myaggp13a(BASETYPE = int, SFUNC = tfnp, STYPE = anyarray,
   FINALFUNC = ffp, INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 --    P    N        N    P
 -- should ERROR: tf2p(anyarray, int) not matched by tf2p(int[],anyelement)
 CREATE AGGREGATE myaggp14a(BASETYPE = int, SFUNC = tf2p, STYPE = anyarray,
   FINALFUNC = ffp, INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 --    P    N        P    N
 -- should ERROR: tfnp(anyarray, anyelement) not matched by tfnp(int[],int)
 CREATE AGGREGATE myaggp15a(BASETYPE = anyelement, SFUNC = tfnp,
@@ -324,21 +355,21 @@ ERROR:  function tf2p(anyarray, anyelement) does not exist
 CREATE AGGREGATE myaggp17a(BASETYPE = int, SFUNC = tf1p, STYPE = anyarray,
   FINALFUNC = ffp, INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 CREATE AGGREGATE myaggp17b(BASETYPE = int, SFUNC = tf1p, STYPE = anyarray,
   INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 --    P    P        N    P
 -- should ERROR: tfp(anyarray, int) not matched by tfp(anyarray, anyelement)
 CREATE AGGREGATE myaggp18a(BASETYPE = int, SFUNC = tfp, STYPE = anyarray,
   FINALFUNC = ffp, INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 CREATE AGGREGATE myaggp18b(BASETYPE = int, SFUNC = tfp, STYPE = anyarray,
   INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 --    P    P        P    N
 -- should ERROR: tf1p(anyarray, anyelement) not matched by tf1p(anyarray, int)
 CREATE AGGREGATE myaggp19a(BASETYPE = anyelement, SFUNC = tf1p,
@@ -368,11 +399,11 @@ CREATE AGGREGATE myaggn01b(*) (SFUNC = stfnp, STYPE = int4[],
 CREATE AGGREGATE myaggn02a(*) (SFUNC = stfnp, STYPE = anyarray,
   FINALFUNC = ffnp, INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 CREATE AGGREGATE myaggn02b(*) (SFUNC = stfnp, STYPE = anyarray,
   INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 --     N    P
 -- should CREATE
 CREATE AGGREGATE myaggn03a(*) (SFUNC = stfp, STYPE = int4[],
@@ -382,7 +413,7 @@ CREATE AGGREGATE myaggn03a(*) (SFUNC = stfp, STYPE = int4[],
 CREATE AGGREGATE myaggn04a(*) (SFUNC = stfp, STYPE = anyarray,
   FINALFUNC = ffnp, INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 --    Case4 (R = N) && ((B = P) || (B = N))
 --    -------------------------------------
 --    S    tf1      B    tf2
@@ -436,21 +467,21 @@ ERROR:  function tfp(integer[], anyelement) does not exist
 CREATE AGGREGATE myaggn13a(BASETYPE = int, SFUNC = tfnp, STYPE = anyarray,
   FINALFUNC = ffnp, INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 CREATE AGGREGATE myaggn13b(BASETYPE = int, SFUNC = tfnp, STYPE = anyarray,
   INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 --    P    N        N    P
 -- should ERROR: tf2p(anyarray, int) not matched by tf2p(int[],anyelement)
 CREATE AGGREGATE myaggn14a(BASETYPE = int, SFUNC = tf2p, STYPE = anyarray,
   FINALFUNC = ffnp, INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 CREATE AGGREGATE myaggn14b(BASETYPE = int, SFUNC = tf2p, STYPE = anyarray,
   INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 --    P    N        P    N
 -- should ERROR: tfnp(anyarray, anyelement) not matched by tfnp(int[],int)
 CREATE AGGREGATE myaggn15a(BASETYPE = anyelement, SFUNC = tfnp,
@@ -472,13 +503,13 @@ ERROR:  function tf2p(anyarray, anyelement) does not exist
 CREATE AGGREGATE myaggn17a(BASETYPE = int, SFUNC = tf1p, STYPE = anyarray,
   FINALFUNC = ffnp, INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 --    P    P        N    P
 -- should ERROR: tfp(anyarray, int) not matched by tfp(anyarray, anyelement)
 CREATE AGGREGATE myaggn18a(BASETYPE = int, SFUNC = tfp, STYPE = anyarray,
   FINALFUNC = ffnp, INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 --    P    P        P    N
 -- should ERROR: tf1p(anyarray, anyelement) not matched by tf1p(anyarray, int)
 CREATE AGGREGATE myaggn19a(BASETYPE = anyelement, SFUNC = tf1p,
@@ -1855,7 +1886,59 @@ returns anycompatiblerange as $$
   select $1
 $$ language sql;
 ERROR:  cannot determine result data type
-DETAIL:  A result of type anycompatiblerange requires at least one input of type anycompatiblerange.
+DETAIL:  A result of type anycompatiblerange requires at least one input of type anycompatiblerange or anycompatiblemultirange.
+create function anyctest(anycompatible, anycompatiblemultirange)
+returns anycompatiblemultirange as $$
+  select $2
+$$ language sql;
+select x, pg_typeof(x) from anyctest(11, multirange(int4range(4,7))) x;
+    x    |   pg_typeof    
+---------+----------------
+ {[4,7)} | int4multirange
+(1 row)
+
+select x, pg_typeof(x) from anyctest(11, multirange(numrange(4,7))) x;
+    x    |   pg_typeof   
+---------+---------------
+ {[4,7)} | nummultirange
+(1 row)
+
+select x, pg_typeof(x) from anyctest(11, 12) x;  -- fail
+ERROR:  function anyctest(integer, integer) does not exist
+LINE 1: select x, pg_typeof(x) from anyctest(11, 12) x;
+                                    ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+select x, pg_typeof(x) from anyctest(11.2, multirange(int4range(4,7))) x;  -- fail
+ERROR:  function anyctest(numeric, int4multirange) does not exist
+LINE 1: select x, pg_typeof(x) from anyctest(11.2, multirange(int4ra...
+                                    ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+select x, pg_typeof(x) from anyctest(11.2, '{[4,7)}') x;  -- fail
+ERROR:  could not identify anycompatiblemultirange type
+drop function anyctest(anycompatible, anycompatiblemultirange);
+create function anyctest(anycompatiblemultirange, anycompatiblemultirange)
+returns anycompatible as $$
+  select lower($1) + upper($2)
+$$ language sql;
+select x, pg_typeof(x) from anyctest(multirange(int4range(11,12)), multirange(int4range(4,7))) x;
+ x  | pg_typeof 
+----+-----------
+ 18 | integer
+(1 row)
+
+select x, pg_typeof(x) from anyctest(multirange(int4range(11,12)), multirange(numrange(4,7))) x; -- fail
+ERROR:  function anyctest(int4multirange, nummultirange) does not exist
+LINE 1: select x, pg_typeof(x) from anyctest(multirange(int4range(11...
+                                    ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+drop function anyctest(anycompatiblemultirange, anycompatiblemultirange);
+-- fail, can't infer result type:
+create function anyctest(anycompatible)
+returns anycompatiblemultirange as $$
+  select $1
+$$ language sql;
+ERROR:  cannot determine result data type
+DETAIL:  A result of type anycompatiblemultirange requires at least one input of type anycompatiblerange or anycompatiblemultirange.
 create function anyctest(anycompatiblenonarray, anycompatiblenonarray)
 returns anycompatiblearray as $$
   select array[$1, $2]
diff --git a/src/test/regress/expected/rangefuncs.out b/src/test/regress/expected/rangefuncs.out
index 06bd129fd22..5bbd5f6c0bd 100644
--- a/src/test/regress/expected/rangefuncs.out
+++ b/src/test/regress/expected/rangefuncs.out
@@ -1556,7 +1556,7 @@ DROP FUNCTION dup(anyelement);
 CREATE FUNCTION bad (f1 int, out f2 anyelement, out f3 anyarray)
 AS 'select $1, array[$1,$1]' LANGUAGE sql;
 ERROR:  cannot determine result data type
-DETAIL:  A result of type anyelement requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyelement requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 CREATE FUNCTION dup (f1 anycompatible, f2 anycompatiblearray, f3 out anycompatible, f4 out anycompatiblearray)
 AS 'select $1, $2' LANGUAGE sql;
 SELECT dup(22, array[44]);
diff --git a/src/test/regress/expected/rangetypes.out b/src/test/regress/expected/rangetypes.out
index c421f5394fe..6a1bbadc91a 100644
--- a/src/test/regress/expected/rangetypes.out
+++ b/src/test/regress/expected/rangetypes.out
@@ -518,6 +518,25 @@ select numrange(1.0, 2.0) * numrange(2.5, 3.0);
  empty
 (1 row)
 
+select range_intersect_agg(nr) from numrange_test;
+ range_intersect_agg 
+---------------------
+ empty
+(1 row)
+
+select range_intersect_agg(nr) from numrange_test where false;
+ range_intersect_agg 
+---------------------
+ 
+(1 row)
+
+select range_intersect_agg(nr) from numrange_test where nr @> 4.0;
+ range_intersect_agg 
+---------------------
+ [3,5)
+(1 row)
+
+analyze numrange_test;
 create table numrange_test2(nr numrange);
 create index numrange_test2_hash_idx on numrange_test2 using hash (nr);
 INSERT INTO numrange_test2 VALUES('[, 5)');
@@ -1371,12 +1390,12 @@ drop function anyarray_anyrange_func(anyarray, anyrange);
 create function bogus_func(anyelement)
   returns anyrange as 'select int4range(1,10)' language sql;
 ERROR:  cannot determine result data type
-DETAIL:  A result of type anyrange requires at least one input of type anyrange.
+DETAIL:  A result of type anyrange requires at least one input of type anyrange or anymultirange.
 -- should fail
 create function bogus_func(int)
   returns anyrange as 'select int4range(1,10)' language sql;
 ERROR:  cannot determine result data type
-DETAIL:  A result of type anyrange requires at least one input of type anyrange.
+DETAIL:  A result of type anyrange requires at least one input of type anyrange or anymultirange.
 create function range_add_bounds(anyrange)
   returns anyelement as 'select lower($1) + upper($1)' language sql;
 select range_add_bounds(int4range(1, 17));
@@ -1430,7 +1449,7 @@ drop function anycompatiblearray_anycompatiblerange_func(anycompatiblearray, any
 create function bogus_func(anycompatible)
   returns anycompatiblerange as 'select int4range(1,10)' language sql;
 ERROR:  cannot determine result data type
-DETAIL:  A result of type anycompatiblerange requires at least one input of type anycompatiblerange.
+DETAIL:  A result of type anycompatiblerange requires at least one input of type anycompatiblerange or anycompatiblemultirange.
 --
 -- Arrays of ranges
 --
@@ -1508,6 +1527,7 @@ reset enable_sort;
 --
 -- OUT/INOUT/TABLE functions
 --
+-- infer anyrange from anyrange
 create function outparam_succeed(i anyrange, out r anyrange, out t text)
   as $$ select $1, 'foo'::text $$ language sql;
 select * from outparam_succeed(int4range(1,2));
@@ -1525,6 +1545,16 @@ select * from outparam2_succeed(int4range(1,11));
  {1,11} | {11,1}
 (1 row)
 
+-- infer anyarray from anyrange
+create function outparam_succeed2(i anyrange, out r anyarray, out t text)
+  as $$ select ARRAY[upper($1)], 'foo'::text $$ language sql;
+select * from outparam_succeed2(int4range(int4range(1,2)));
+  r  |  t  
+-----+-----
+ {2} | foo
+(1 row)
+
+-- infer anyelement from anyrange
 create function inoutparam_succeed(out i anyelement, inout r anyrange)
   as $$ select upper($1), $1 $$ language sql;
 select * from inoutparam_succeed(int4range(1,2));
@@ -1547,14 +1577,14 @@ select * from table_succeed(int4range(1,11));
 create function outparam_fail(i anyelement, out r anyrange, out t text)
   as $$ select '[1,10]', 'foo' $$ language sql;
 ERROR:  cannot determine result data type
-DETAIL:  A result of type anyrange requires at least one input of type anyrange.
+DETAIL:  A result of type anyrange requires at least one input of type anyrange or anymultirange.
 --should fail
 create function inoutparam_fail(inout i anyelement, out r anyrange)
   as $$ select $1, '[1,10]' $$ language sql;
 ERROR:  cannot determine result data type
-DETAIL:  A result of type anyrange requires at least one input of type anyrange.
+DETAIL:  A result of type anyrange requires at least one input of type anyrange or anymultirange.
 --should fail
 create function table_fail(i anyelement) returns table(i anyelement, r anyrange)
   as $$ select $1, '[1,10]' $$ language sql;
 ERROR:  cannot determine result data type
-DETAIL:  A result of type anyrange requires at least one input of type anyrange.
+DETAIL:  A result of type anyrange requires at least one input of type anyrange or anymultirange.
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index 192445878d2..d9ce961be2b 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -92,6 +92,7 @@ num_exp_sqrt|t
 num_exp_sub|t
 num_input_test|f
 num_result|f
+nummultirange_test|t
 numrange_test|t
 onek|t
 onek2|t
@@ -172,6 +173,7 @@ quad_poly_tbl|t
 radix_text_tbl|t
 ramp|f
 real_city|f
+reservations|f
 road|t
 shighway|t
 slow_emp4000|f
diff --git a/src/test/regress/expected/type_sanity.out b/src/test/regress/expected/type_sanity.out
index 274130e7062..8df1ae47e48 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'));
@@ -195,18 +195,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
@@ -240,17 +241,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
@@ -318,18 +320,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
@@ -363,17 +366,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
@@ -631,3 +635,11 @@ WHERE pronargs != 2
 ----------+------------+---------
 (0 rows)
 
+-- every range should have a valid multirange
+SELECT p1.rngtypid, p1.rngsubtype, p1.rngmultitypid
+FROM pg_range p1
+WHERE p1.rngmultitypid IS NULL OR p1.rngmultitypid = 0;
+ rngtypid | rngsubtype | rngmultitypid 
+----------+------------+---------------
+(0 rows)
+
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index ae89ed7f0b4..c91b38884ff 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -19,6 +19,7 @@ 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 xid
 
@@ -27,7 +28,7 @@ test: strings numerology point lseg line box path polygon circle date time timet
 # geometry depends on point, lseg, box, path, polygon and circle
 # horology depends on interval, timetz, timestamp, timestamptz
 # ----------
-test: geometry horology regex oidjoins type_sanity opr_sanity misc_sanity comments expressions unicode
+test: geometry horology regex oidjoins type_sanity opr_sanity misc_sanity comments expressions unicode multirangetypes
 
 # ----------
 # These four each depend on the previous one
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index 525bdc804f6..081fce32e75 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -20,6 +20,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 681cc92ac20..0292dedd15a 100644
--- a/src/test/regress/sql/hash_func.sql
+++ b/src/test/regress/sql/hash_func.sql
@@ -227,6 +227,16 @@ FROM   (VALUES (int4range(10, 20)), (int4range(23, 43)),
 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);
+
 CREATE TYPE hash_test_t1 AS (a int, b text);
 SELECT v as value, hash_record(v)::bit(32) as standard,
        hash_record_extended(v, 0)::bit(32) as extended0,
diff --git a/src/test/regress/sql/multirangetypes.sql b/src/test/regress/sql/multirangetypes.sql
new file mode 100644
index 00000000000..2a6b4a0a291
--- /dev/null
+++ b/src/test/regress/sql/multirangetypes.sql
@@ -0,0 +1,658 @@
+-- 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'));
+
+--
+-- test casts, both a built-in range type and a user-defined one:
+--
+select 'empty'::int4range::int4multirange;
+select int4range(1, 3)::int4multirange;
+select int4range(1, null)::int4multirange;
+select int4range(null, null)::int4multirange;
+select 'empty'::textrange::textmultirange;
+select textrange('a', 'c')::textmultirange;
+select textrange('a', null)::textmultirange;
+select textrange(null, null)::textmultirange;
+
+--
+-- create some test data and test the operators
+--
+
+CREATE TABLE nummultirange_test (nmr NUMMULTIRANGE);
+CREATE INDEX nummultirange_test_btree ON nummultirange_test(nmr);
+
+INSERT INTO nummultirange_test VALUES('{}');
+INSERT INTO nummultirange_test VALUES('{[,)}');
+INSERT INTO nummultirange_test VALUES('{[3,]}');
+INSERT INTO nummultirange_test VALUES('{[,), [3,]}');
+INSERT INTO nummultirange_test VALUES('{[, 5)}');
+INSERT INTO nummultirange_test VALUES(nummultirange());
+INSERT INTO nummultirange_test VALUES(nummultirange(variadic '{}'::numrange[]));
+INSERT INTO nummultirange_test VALUES(nummultirange(numrange(1.1, 2.2)));
+INSERT INTO nummultirange_test VALUES('{empty}');
+INSERT INTO nummultirange_test VALUES(nummultirange(numrange(1.7, 1.7, '[]'), numrange(1.7, 1.9)));
+INSERT INTO nummultirange_test VALUES(nummultirange(numrange(1.7, 1.7, '[]'), numrange(1.9, 2.1)));
+
+SELECT nmr, isempty(nmr), lower(nmr), upper(nmr) FROM nummultirange_test ORDER BY nmr;
+SELECT nmr, lower_inc(nmr), lower_inf(nmr), upper_inc(nmr), upper_inf(nmr) FROM nummultirange_test ORDER BY nmr;
+
+SELECT * FROM nummultirange_test WHERE nmr = '{}';
+SELECT * FROM nummultirange_test WHERE nmr = '{(,5)}';
+SELECT * FROM nummultirange_test WHERE nmr = '{[3,)}';
+SELECT * FROM nummultirange_test WHERE nmr = '{[1.7,1.7]}';
+SELECT * FROM nummultirange_test WHERE nmr = '{[1.7,1.7],[1.9,2.1)}';
+SELECT * FROM nummultirange_test WHERE nmr < '{}';
+SELECT * FROM nummultirange_test WHERE nmr < '{[-1000.0, -1000.0]}';
+SELECT * FROM nummultirange_test WHERE nmr < '{[0.0, 1.0]}';
+SELECT * FROM nummultirange_test WHERE nmr < '{[1000.0, 1001.0]}';
+SELECT * FROM nummultirange_test WHERE nmr <= '{}';
+SELECT * FROM nummultirange_test WHERE nmr <= '{[3,)}';
+SELECT * FROM nummultirange_test WHERE nmr >= '{}';
+SELECT * FROM nummultirange_test WHERE nmr >= '{[3,)}';
+SELECT * FROM nummultirange_test WHERE nmr > '{}';
+SELECT * FROM nummultirange_test WHERE nmr > '{[-1000.0, -1000.0]}';
+SELECT * FROM nummultirange_test WHERE nmr > '{[0.0, 1.0]}';
+SELECT * FROM nummultirange_test WHERE nmr > '{[1000.0, 1001.0]}';
+SELECT * FROM nummultirange_test WHERE nmr <> '{}';
+SELECT * FROM nummultirange_test WHERE nmr <> '{(,5)}';
+
+select nummultirange(numrange(2.0, 1.0));
+select nummultirange(numrange(5.0, 6.0), numrange(1.0, 2.0));
+
+analyze nummultirange_test;
+
+-- overlaps
+SELECT * FROM nummultirange_test WHERE range_overlaps_multirange(numrange(4.0, 4.2), nmr);
+SELECT * FROM nummultirange_test WHERE numrange(4.0, 4.2) && nmr;
+SELECT * FROM nummultirange_test WHERE multirange_overlaps_range(nmr, numrange(4.0, 4.2));
+SELECT * FROM nummultirange_test WHERE nmr && numrange(4.0, 4.2);
+SELECT * FROM nummultirange_test WHERE multirange_overlaps_multirange(nmr, nummultirange(numrange(4.0, 4.2), numrange(6.0, 7.0)));
+SELECT * FROM nummultirange_test WHERE nmr && nummultirange(numrange(4.0, 4.2), numrange(6.0, 7.0));
+SELECT * FROM nummultirange_test WHERE nmr && nummultirange(numrange(6.0, 7.0));
+SELECT * FROM nummultirange_test WHERE nmr && nummultirange(numrange(6.0, 7.0), numrange(8.0, 9.0));
+
+-- mr contains x
+SELECT * FROM nummultirange_test WHERE multirange_contains_elem(nmr, 4.0);
+SELECT * FROM nummultirange_test WHERE nmr @> 4.0;
+SELECT * FROM nummultirange_test WHERE multirange_contains_range(nmr, numrange(4.0, 4.2));
+SELECT * FROM nummultirange_test WHERE nmr @> numrange(4.0, 4.2);
+SELECT * FROM nummultirange_test WHERE multirange_contains_multirange(nmr, '{[4.0,4.2), [6.0, 8.0)}');
+SELECT * FROM nummultirange_test WHERE nmr @> '{[4.0,4.2), [6.0, 8.0)}'::nummultirange;
+
+-- x is contained by mr
+SELECT * FROM nummultirange_test WHERE elem_contained_by_multirange(4.0, nmr);
+SELECT * FROM nummultirange_test WHERE 4.0 <@ nmr;
+SELECT * FROM nummultirange_test WHERE range_contained_by_multirange(numrange(4.0, 4.2), nmr);
+SELECT * FROM nummultirange_test WHERE numrange(4.0, 4.2) <@ nmr;
+SELECT * FROM nummultirange_test WHERE multirange_contained_by_multirange('{[4.0,4.2), [6.0, 8.0)}', nmr);
+SELECT * FROM nummultirange_test WHERE '{[4.0,4.2), [6.0, 8.0)}'::nummultirange <@ nmr;
+
+-- overlaps
+SELECT 'empty'::numrange && nummultirange();
+SELECT 'empty'::numrange && nummultirange(numrange(1,2));
+SELECT nummultirange() && 'empty'::numrange;
+SELECT nummultirange(numrange(1,2)) && 'empty'::numrange;
+SELECT nummultirange() && nummultirange();
+SELECT nummultirange() && nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) && nummultirange();
+SELECT nummultirange(numrange(3,4)) && nummultirange(numrange(1,2), numrange(7,8));
+SELECT nummultirange(numrange(1,2), numrange(7,8)) && nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(3,4)) && nummultirange(numrange(1,2), numrange(3.5,8));
+SELECT nummultirange(numrange(1,2), numrange(3.5,8)) && numrange(3,4);
+SELECT nummultirange(numrange(1,2), numrange(3.5,8)) && nummultirange(numrange(3,4));
+
+-- contains
+SELECT nummultirange() @> nummultirange();
+SELECT nummultirange() @> 'empty'::numrange;
+SELECT nummultirange(numrange(null,null)) @> numrange(1,2);
+SELECT nummultirange(numrange(null,null)) @> numrange(null,2);
+SELECT nummultirange(numrange(null,null)) @> numrange(2,null);
+SELECT nummultirange(numrange(null,5)) @> numrange(null,3);
+SELECT nummultirange(numrange(null,5)) @> numrange(null,8);
+SELECT nummultirange(numrange(5,null)) @> numrange(8,null);
+SELECT nummultirange(numrange(5,null)) @> numrange(3,null);
+SELECT nummultirange(numrange(1,5)) @> numrange(8,9);
+SELECT nummultirange(numrange(1,5)) @> numrange(3,9);
+SELECT nummultirange(numrange(1,5)) @> numrange(1,4);
+SELECT nummultirange(numrange(1,5)) @> numrange(1,5);
+SELECT nummultirange(numrange(-4,-2), numrange(1,5)) @> numrange(1,5);
+SELECT nummultirange(numrange(1,5), numrange(8,9)) @> numrange(1,5);
+SELECT nummultirange(numrange(1,5), numrange(8,9)) @> numrange(6,7);
+SELECT nummultirange(numrange(1,5), numrange(6,9)) @> numrange(6,7);
+SELECT '{[1,5)}'::nummultirange @> '{[1,5)}';
+SELECT '{[-4,-2), [1,5)}'::nummultirange @> '{[1,5)}';
+SELECT '{[1,5), [8,9)}'::nummultirange @> '{[1,5)}';
+SELECT '{[1,5), [8,9)}'::nummultirange @> '{[6,7)}';
+SELECT '{[1,5), [6,9)}'::nummultirange @> '{[6,7)}';
+
+-- is contained by
+SELECT nummultirange() <@ nummultirange();
+SELECT 'empty'::numrange <@ nummultirange();
+SELECT numrange(1,2) <@ nummultirange(numrange(null,null));
+SELECT numrange(null,2) <@ nummultirange(numrange(null,null));
+SELECT numrange(2,null) <@ nummultirange(numrange(null,null));
+SELECT numrange(null,3) <@ nummultirange(numrange(null,5));
+SELECT numrange(null,8) <@ nummultirange(numrange(null,5));
+SELECT numrange(8,null) <@ nummultirange(numrange(5,null));
+SELECT numrange(3,null) <@ nummultirange(numrange(5,null));
+SELECT numrange(8,9) <@ nummultirange(numrange(1,5));
+SELECT numrange(3,9) <@ nummultirange(numrange(1,5));
+SELECT numrange(1,4) <@ nummultirange(numrange(1,5));
+SELECT numrange(1,5) <@ nummultirange(numrange(1,5));
+SELECT numrange(1,5) <@ nummultirange(numrange(-4,-2), numrange(1,5));
+SELECT numrange(1,5) <@ nummultirange(numrange(1,5), numrange(8,9));
+SELECT numrange(6,7) <@ nummultirange(numrange(1,5), numrange(8,9));
+SELECT numrange(6,7) <@ nummultirange(numrange(1,5), numrange(6,9));
+SELECT '{[1,5)}' <@ '{[1,5)}'::nummultirange;
+SELECT '{[1,5)}' <@ '{[-4,-2), [1,5)}'::nummultirange;
+SELECT '{[1,5)}' <@ '{[1,5), [8,9)}'::nummultirange;
+SELECT '{[6,7)}' <@ '{[1,5), [8,9)}'::nummultirange;
+SELECT '{[6,7)}' <@ '{[1,5), [6,9)}'::nummultirange;
+
+-- overleft
+SELECT 'empty'::numrange &< nummultirange();
+SELECT 'empty'::numrange &< nummultirange(numrange(1,2));
+SELECT nummultirange() &< 'empty'::numrange;
+SELECT nummultirange(numrange(1,2)) &< 'empty'::numrange;
+SELECT nummultirange() &< nummultirange();
+SELECT nummultirange(numrange(1,2)) &< nummultirange();
+SELECT nummultirange() &< nummultirange(numrange(1,2));
+SELECT numrange(6,7) &< nummultirange(numrange(3,4));
+SELECT numrange(1,2) &< nummultirange(numrange(3,4));
+SELECT numrange(1,4) &< nummultirange(numrange(3,4));
+SELECT numrange(1,6) &< nummultirange(numrange(3,4));
+SELECT numrange(3.5,6) &< nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(6,7)) &< numrange(3,4);
+SELECT nummultirange(numrange(1,2)) &< numrange(3,4);
+SELECT nummultirange(numrange(1,4)) &< numrange(3,4);
+SELECT nummultirange(numrange(1,6)) &< numrange(3,4);
+SELECT nummultirange(numrange(3.5,6)) &< numrange(3,4);
+SELECT nummultirange(numrange(6,7)) &< nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,2)) &< nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,4)) &< nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,6)) &< nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(3.5,6)) &< nummultirange(numrange(3,4));
+
+-- overright
+SELECT nummultirange() &> 'empty'::numrange;
+SELECT nummultirange(numrange(1,2)) &> 'empty'::numrange;
+SELECT 'empty'::numrange &> nummultirange();
+SELECT 'empty'::numrange &> nummultirange(numrange(1,2));
+SELECT nummultirange() &> nummultirange();
+SELECT nummultirange() &> nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) &> nummultirange();
+SELECT nummultirange(numrange(3,4)) &> numrange(6,7);
+SELECT nummultirange(numrange(3,4)) &> numrange(1,2);
+SELECT nummultirange(numrange(3,4)) &> numrange(1,4);
+SELECT nummultirange(numrange(3,4)) &> numrange(1,6);
+SELECT nummultirange(numrange(3,4)) &> numrange(3.5,6);
+SELECT numrange(3,4) &> nummultirange(numrange(6,7));
+SELECT numrange(3,4) &> nummultirange(numrange(1,2));
+SELECT numrange(3,4) &> nummultirange(numrange(1,4));
+SELECT numrange(3,4) &> nummultirange(numrange(1,6));
+SELECT numrange(3,4) &> nummultirange(numrange(3.5,6));
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(6,7));
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(1,4));
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(1,6));
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(3.5,6));
+
+-- meets
+SELECT 'empty'::numrange -|- nummultirange();
+SELECT 'empty'::numrange -|- nummultirange(numrange(1,2));
+SELECT nummultirange() -|- 'empty'::numrange;
+SELECT nummultirange(numrange(1,2)) -|- 'empty'::numrange;
+SELECT nummultirange() -|- nummultirange();
+SELECT nummultirange(numrange(1,2)) -|- nummultirange();
+SELECT nummultirange() -|- nummultirange(numrange(1,2));
+SELECT numrange(1,2) -|- nummultirange(numrange(2,4));
+SELECT numrange(1,2) -|- nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,2)) -|- numrange(2,4);
+SELECT nummultirange(numrange(1,2)) -|- numrange(3,4);
+SELECT nummultirange(numrange(1,2)) -|- nummultirange(numrange(2,4));
+SELECT nummultirange(numrange(1,2)) -|- nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,2), numrange(5,6)) -|- nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,2), numrange(5,6)) -|- nummultirange(numrange(6,7));
+SELECT nummultirange(numrange(1,2), numrange(5,6)) -|- nummultirange(numrange(8,9));
+SELECT nummultirange(numrange(1,2)) -|- nummultirange(numrange(2,4), numrange(6,7));
+
+-- strictly left
+select 'empty'::numrange << nummultirange();
+select numrange(1,2) << nummultirange();
+select numrange(1,2) << nummultirange(numrange(3,4));
+select numrange(1,2) << nummultirange(numrange(0,4));
+select numrange(1,2) << nummultirange(numrange(0,4), numrange(7,8));
+select nummultirange() << 'empty'::numrange;
+select nummultirange() << numrange(1,2);
+select nummultirange(numrange(3,4)) << numrange(3,6);
+select nummultirange(numrange(0,2)) << numrange(3,6);
+select nummultirange(numrange(0,2), numrange(7,8)) << numrange(3,6);
+select nummultirange(numrange(-4,-2), numrange(0,2)) << numrange(3,6);
+select nummultirange() << nummultirange();
+select nummultirange() << nummultirange(numrange(1,2));
+select nummultirange(numrange(1,2)) << nummultirange();
+select nummultirange(numrange(1,2)) << nummultirange(numrange(1,2));
+select nummultirange(numrange(1,2)) << nummultirange(numrange(3,4));
+select nummultirange(numrange(1,2)) << nummultirange(numrange(3,4), numrange(7,8));
+select nummultirange(numrange(1,2), numrange(4,5)) << nummultirange(numrange(3,4), numrange(7,8));
+
+-- strictly right
+select nummultirange() >> 'empty'::numrange;
+select nummultirange() >> numrange(1,2);
+select nummultirange(numrange(3,4)) >> numrange(1,2);
+select nummultirange(numrange(0,4)) >> numrange(1,2);
+select nummultirange(numrange(0,4), numrange(7,8)) >> numrange(1,2);
+select 'empty'::numrange >> nummultirange();
+select numrange(1,2) >> nummultirange();
+select numrange(3,6) >> nummultirange(numrange(3,4));
+select numrange(3,6) >> nummultirange(numrange(0,2));
+select numrange(3,6) >> nummultirange(numrange(0,2), numrange(7,8));
+select numrange(3,6) >> nummultirange(numrange(-4,-2), numrange(0,2));
+select nummultirange() >> nummultirange();
+select nummultirange(numrange(1,2)) >> nummultirange();
+select nummultirange() >> nummultirange(numrange(1,2));
+select nummultirange(numrange(1,2)) >> nummultirange(numrange(1,2));
+select nummultirange(numrange(3,4)) >> nummultirange(numrange(1,2));
+select nummultirange(numrange(3,4), numrange(7,8)) >> nummultirange(numrange(1,2));
+select nummultirange(numrange(3,4), numrange(7,8)) >> nummultirange(numrange(1,2), numrange(4,5));
+
+-- union
+SELECT nummultirange() + nummultirange();
+SELECT nummultirange() + nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) + nummultirange();
+SELECT nummultirange(numrange(1,2)) + nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) + nummultirange(numrange(2,4));
+SELECT nummultirange(numrange(1,2)) + nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) + nummultirange(numrange(2,4));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) + nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) + nummultirange(numrange(0,9));
+
+-- merge
+SELECT range_merge(nummultirange());
+SELECT range_merge(nummultirange(numrange(1,2)));
+SELECT range_merge(nummultirange(numrange(1,2), numrange(7,8)));
+
+-- minus
+SELECT nummultirange() - nummultirange();
+SELECT nummultirange() - nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) - nummultirange();
+SELECT nummultirange(numrange(1,2), numrange(3,4)) - nummultirange();
+SELECT nummultirange(numrange(1,2)) - nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) - nummultirange(numrange(2,4));
+SELECT nummultirange(numrange(1,2)) - nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(2,3));
+SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(0,8));
+SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(0,2));
+SELECT nummultirange(numrange(1,8)) - nummultirange(numrange(0,2), numrange(3,4));
+SELECT nummultirange(numrange(1,8)) - nummultirange(numrange(2,3), numrange(5,null));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(-2,0));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(2,4));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(3,5));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(0,9));
+SELECT nummultirange(numrange(1,3), numrange(4,5)) - nummultirange(numrange(2,9));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(8,9));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(-2,0), numrange(8,9));
+
+-- intersection
+SELECT nummultirange() * nummultirange();
+SELECT nummultirange() * nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) * nummultirange();
+SELECT '{[1,3)}'::nummultirange * '{[1,5)}'::nummultirange;
+SELECT '{[1,3)}'::nummultirange * '{[0,5)}'::nummultirange;
+SELECT '{[1,3)}'::nummultirange * '{[0,2)}'::nummultirange;
+SELECT '{[1,3)}'::nummultirange * '{[2,5)}'::nummultirange;
+SELECT '{[1,4)}'::nummultirange * '{[2,3)}'::nummultirange;
+SELECT '{[1,4)}'::nummultirange * '{[0,2), [3,5)}'::nummultirange;
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[0,8), [9,12)}'::nummultirange;
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[9,12)}'::nummultirange;
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[-5,-4), [5,6), [9,12)}'::nummultirange;
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[0,2), [3,8), [9,12)}'::nummultirange;
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[0,2), [3,8), [9,12)}'::nummultirange;
+
+--
+-- range_agg function
+--
+create table reservations ( room_id integer not null, booked_during daterange );
+insert into reservations values
+-- 1: has a meets and a gap
+(1, daterange('2018-07-01', '2018-07-07')),
+(1, daterange('2018-07-07', '2018-07-14')),
+(1, daterange('2018-07-20', '2018-07-22')),
+-- 2: just a single row
+(2, daterange('2018-07-01', '2018-07-03')),
+-- 3: one null range
+(3, NULL),
+-- 4: two null ranges
+(4, NULL),
+(4, NULL),
+-- 5: a null range and a non-null range
+(5, NULL),
+(5, daterange('2018-07-01', '2018-07-03')),
+-- 6: has overlap
+(6, daterange('2018-07-01', '2018-07-07')),
+(6, daterange('2018-07-05', '2018-07-10')),
+-- 7: two ranges that meet: no gap or overlap
+(7, daterange('2018-07-01', '2018-07-07')),
+(7, daterange('2018-07-07', '2018-07-14')),
+-- 8: an empty range
+(8, 'empty'::daterange)
+;
+SELECT   room_id, range_agg(booked_during)
+FROM     reservations
+GROUP BY room_id
+ORDER BY room_id;
+
+-- range_agg on a custom range type too
+SELECT  range_agg(r)
+FROM    (VALUES
+          ('[a,c]'::textrange),
+          ('[b,b]'::textrange),
+          ('[c,f]'::textrange),
+          ('[g,h)'::textrange),
+          ('[h,j)'::textrange)
+        ) t(r);
+
+select range_intersect_agg(nmr) from nummultirange_test;
+select range_intersect_agg(nmr) from nummultirange_test where false;
+-- test with just one input:
+select range_intersect_agg(nmr) from (values ('{[1,2]}'::nummultirange)) t(nmr);
+select range_intersect_agg(nmr) from nummultirange_test where nmr @> 4.0;
+
+create table nummultirange_test2(nmr nummultirange);
+create index nummultirange_test2_hash_idx on nummultirange_test2 using hash (nmr);
+
+INSERT INTO nummultirange_test2 VALUES('{[, 5)}');
+INSERT INTO nummultirange_test2 VALUES(nummultirange(numrange(1.1, 2.2)));
+INSERT INTO nummultirange_test2 VALUES(nummultirange(numrange(1.1, 2.2)));
+INSERT INTO nummultirange_test2 VALUES(nummultirange(numrange(1.1, 2.2,'()')));
+INSERT INTO nummultirange_test2 VALUES('{}');
+
+select * from nummultirange_test2 where nmr = '{}';
+select * from nummultirange_test2 where nmr = nummultirange(numrange(1.1, 2.2));
+select * from nummultirange_test2 where nmr = nummultirange(numrange(1.1, 2.3));
+
+set enable_nestloop=t;
+set enable_hashjoin=f;
+set enable_mergejoin=f;
+select * from nummultirange_test natural join nummultirange_test2 order by nmr;
+set enable_nestloop=f;
+set enable_hashjoin=t;
+set enable_mergejoin=f;
+select * from nummultirange_test natural join nummultirange_test2 order by nmr;
+set enable_nestloop=f;
+set enable_hashjoin=f;
+set enable_mergejoin=t;
+select * from nummultirange_test natural join nummultirange_test2 order by nmr;
+
+set enable_nestloop to default;
+set enable_hashjoin to default;
+set enable_mergejoin to default;
+
+DROP TABLE nummultirange_test2;
+
+--
+-- Test user-defined multirange of floats
+--
+
+select '{[123.001, 5.e9)}'::float8multirange @> 888.882::float8;
+create table float8multirange_test(f8mr float8multirange, i int);
+insert into float8multirange_test values(float8multirange(float8range(-100.00007, '1.111113e9')), 42);
+select * from float8multirange_test;
+drop table float8multirange_test;
+
+--
+-- Test multirange types over domains
+--
+
+create domain mydomain as int4;
+create type mydomainrange as range(subtype=mydomain);
+select '{[4,50)}'::mydomainmultirange @> 7::mydomain;
+drop domain mydomain cascade;
+
+--
+-- Test domains over multirange types
+--
+
+create domain restrictedmultirange as int4multirange check (upper(value) < 10);
+select '{[4,5)}'::restrictedmultirange @> 7;
+select '{[4,50)}'::restrictedmultirange @> 7; -- should fail
+drop domain restrictedmultirange;
+
+--
+-- Test multiple multirange types over the same subtype
+--
+
+create type textrange1 as range(subtype=text, collation="C");
+create type textrange2 as range(subtype=text, collation="C");
+
+select textmultirange1(textrange2('a','Z'));  -- should fail
+select textmultirange1(textrange1('a','Z')) @> 'b'::text;
+select textmultirange2(textrange2('a','z')) @> 'b'::text;
+
+drop type textrange1;
+drop type textrange2;
+
+--
+-- Test polymorphic type system
+--
+
+create function anyarray_anymultirange_func(a anyarray, r anymultirange)
+  returns anyelement as 'select $1[1] + lower($2);' language sql;
+
+select anyarray_anymultirange_func(ARRAY[1,2], int4multirange(int4range(10,20)));
+
+-- should fail
+select anyarray_anymultirange_func(ARRAY[1,2], nummultirange(numrange(10,20)));
+
+drop function anyarray_anymultirange_func(anyarray, anymultirange);
+
+-- should fail
+create function bogus_func(anyelement)
+  returns anymultirange as 'select int4multirange(int4range(1,10))' language sql;
+
+-- should fail
+create function bogus_func(int)
+  returns anymultirange as 'select int4multirange(int4range(1,10))' language sql;
+
+create function range_add_bounds(anymultirange)
+  returns anyelement as 'select lower($1) + upper($1)' language sql;
+
+select range_add_bounds(int4multirange(int4range(1, 17)));
+select range_add_bounds(nummultirange(numrange(1.0001, 123.123)));
+
+create function multirangetypes_sql(q anymultirange, b anyarray, out c anyelement)
+  as $$ select upper($1) + $2[1] $$
+  language sql;
+
+select multirangetypes_sql(int4multirange(int4range(1,10)), ARRAY[2,20]);
+select multirangetypes_sql(nummultirange(numrange(1,10)), ARRAY[2,20]);  -- match failure
+
+create function anycompatiblearray_anycompatiblemultirange_func(a anycompatiblearray, mr anycompatiblemultirange)
+  returns anycompatible as 'select $1[1] + lower($2);' language sql;
+
+select anycompatiblearray_anycompatiblemultirange_func(ARRAY[1,2], multirange(int4range(10,20)));
+
+select anycompatiblearray_anycompatiblemultirange_func(ARRAY[1,2], multirange(numrange(10,20)));
+
+-- should fail
+select anycompatiblearray_anycompatiblemultirange_func(ARRAY[1.1,2], multirange(int4range(10,20)));
+
+drop function anycompatiblearray_anycompatiblemultirange_func(anycompatiblearray, anycompatiblemultirange);
+
+create function anycompatiblerange_anycompatiblemultirange_func(r anycompatiblerange, mr anycompatiblemultirange)
+  returns anycompatible as 'select lower($1) + lower($2);' language sql;
+
+select anycompatiblerange_anycompatiblemultirange_func(int4range(1,2), multirange(int4range(10,20)));
+
+-- should fail
+select anycompatiblerange_anycompatiblemultirange_func(numrange(1,2), multirange(int4range(10,20)));
+
+drop function anycompatiblerange_anycompatiblemultirange_func(anycompatiblerange, anycompatiblemultirange);
+
+-- should fail
+create function bogus_func(anycompatible)
+  returns anycompatiblerange as 'select int4range(1,10)' language sql;
+
+--
+-- Arrays of multiranges
+--
+
+select ARRAY[nummultirange(numrange(1.1, 1.2)), nummultirange(numrange(12.3, 155.5))];
+
+create table i8mr_array (f1 int, f2 int8multirange[]);
+insert into i8mr_array values (42, array[int8multirange(int8range(1,10)), int8multirange(int8range(2,20))]);
+select * from i8mr_array;
+drop table i8mr_array;
+
+--
+-- Multiranges of arrays
+--
+
+select arraymultirange(arrayrange(ARRAY[1,2], ARRAY[2,1]));
+select arraymultirange(arrayrange(ARRAY[2,1], ARRAY[1,2]));  -- fail
+
+select array[1,1] <@ arraymultirange(arrayrange(array[1,2], array[2,1]));
+select array[1,3] <@ arraymultirange(arrayrange(array[1,2], array[2,1]));
+
+--
+-- Ranges of composites
+--
+
+create type two_ints as (a int, b int);
+create type two_ints_range as range (subtype = two_ints);
+
+-- with force_parallel_mode on, this exercises tqueue.c's range remapping
+select *, row_to_json(upper(t)) as u from
+  (values (two_ints_multirange(two_ints_range(row(1,2), row(3,4)))),
+          (two_ints_multirange(two_ints_range(row(5,6), row(7,8))))) v(t);
+
+drop type two_ints cascade;
+
+--
+-- Check behavior when subtype lacks a hash function
+--
+
+set enable_sort = off;  -- try to make it pick a hash setop implementation
+
+select '{(2,5)}'::cashmultirange except select '{(5,6)}'::cashmultirange;
+
+reset enable_sort;
+
+--
+-- OUT/INOUT/TABLE functions
+--
+
+-- infer anymultirange from anymultirange
+create function mr_outparam_succeed(i anymultirange, out r anymultirange, out t text)
+  as $$ select $1, 'foo'::text $$ language sql;
+
+select * from mr_outparam_succeed(int4multirange(int4range(1,2)));
+
+-- infer anyarray from anymultirange
+create function mr_outparam_succeed2(i anymultirange, out r anyarray, out t text)
+  as $$ select ARRAY[upper($1)], 'foo'::text $$ language sql;
+
+select * from mr_outparam_succeed2(int4multirange(int4range(1,2)));
+
+-- infer anyrange from anymultirange
+create function mr_outparam_succeed3(i anymultirange, out r anyrange, out t text)
+  as $$ select range_merge($1), 'foo'::text $$ language sql;
+select * from mr_outparam_succeed3(int4multirange(int4range(1,2)));
+
+-- infer anymultirange from anyrange
+create function mr_outparam_succeed4(i anyrange, out r anymultirange, out t text)
+  as $$ select multirange($1), 'foo'::text $$ language sql;
+
+select * from mr_outparam_succeed4(int4range(1,2));
+
+-- infer anyelement from anymultirange
+create function mr_inoutparam_succeed(out i anyelement, inout r anymultirange)
+  as $$ select upper($1), $1 $$ language sql;
+
+select * from mr_inoutparam_succeed(int4multirange(int4range(1,2)));
+
+-- infer anyelement+anymultirange from anyelement+anymultirange
+create function mr_table_succeed(i anyelement, r anymultirange) returns table(i anyelement, r anymultirange)
+  as $$ select $1, $2 $$ language sql;
+
+select * from mr_table_succeed(123, int4multirange(int4range(1,11)));
+
+-- use anymultirange in plpgsql
+create function mr_polymorphic(i anyrange) returns anymultirange
+  as $$ begin return multirange($1); end; $$ language plpgsql;
+select mr_polymorphic(int4range(1, 4));
+
+-- should fail
+create function mr_outparam_fail(i anyelement, out r anymultirange, out t text)
+  as $$ select '[1,10]', 'foo' $$ language sql;
+
+--should fail
+create function mr_inoutparam_fail(inout i anyelement, out r anymultirange)
+  as $$ select $1, '[1,10]' $$ language sql;
+
+--should fail
+create function mr_table_fail(i anyelement) returns table(i anyelement, r anymultirange)
+  as $$ select $1, '[1,10]' $$ language sql;
diff --git a/src/test/regress/sql/opr_sanity.sql b/src/test/regress/sql/opr_sanity.sql
index 307aab1deb7..3d6b60179bc 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::regtype, p2.prorettype::regtype
 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;
 
@@ -279,16 +290,19 @@ WHERE p1.prorettype IN
      '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;
 
--- anyrange is tighter than the rest, can only resolve from anyrange input
+-- anyrange and anymultirange are tighter than the rest, can only resolve
+-- from each other
 
 SELECT p1.oid, p1.proname
 FROM pg_proc as p1
-WHERE p1.prorettype = 'anyrange'::regtype
+WHERE p1.prorettype IN ('anyrange'::regtype, 'anymultirange'::regtype)
   AND NOT
-     'anyrange'::regtype = ANY (p1.proargtypes)
+    ('anyrange'::regtype = ANY (p1.proargtypes) OR
+      'anymultirange'::regtype = ANY (p1.proargtypes))
 ORDER BY 2;
 
 -- similarly for the anycompatible family
diff --git a/src/test/regress/sql/polymorphism.sql b/src/test/regress/sql/polymorphism.sql
index ff517fea41a..891b023ada0 100644
--- a/src/test/regress/sql/polymorphism.sql
+++ b/src/test/regress/sql/polymorphism.sql
@@ -71,6 +71,16 @@ select polyf(int4range(42, 49), 11, 4.5) as fail;  -- range type doesn't fit
 
 drop function polyf(x anycompatiblerange, y anycompatible, z anycompatible);
 
+create function polyf(x anycompatiblemultirange, y anycompatible, z anycompatible) returns anycompatiblearray as $$
+  select array[lower(x), upper(x), y, z]
+$$ language sql;
+
+select polyf(multirange(int4range(42, 49)), 11, 2::smallint) as int, polyf(multirange(float8range(4.5, 7.8)), 7.8, 11::real) as num;
+
+select polyf(multirange(int4range(42, 49)), 11, 4.5) as fail;  -- range type doesn't fit
+
+drop function polyf(x anycompatiblemultirange, y anycompatible, z anycompatible);
+
 -- fail, can't infer type:
 create function polyf(x anycompatible) returns anycompatiblerange as $$
   select array[x + 1, x + 2]
@@ -84,6 +94,19 @@ select polyf(int4range(42, 49), array[11]) as int, polyf(float8range(4.5, 7.8),
 
 drop function polyf(x anycompatiblerange, y anycompatiblearray);
 
+-- fail, can't infer type:
+create function polyf(x anycompatible) returns anycompatiblemultirange as $$
+  select array[x + 1, x + 2]
+$$ language sql;
+
+create function polyf(x anycompatiblemultirange, y anycompatiblearray) returns anycompatiblemultirange as $$
+  select x
+$$ language sql;
+
+select polyf(multirange(int4range(42, 49)), array[11]) as int, polyf(multirange(float8range(4.5, 7.8)), array[7]) as num;
+
+drop function polyf(x anycompatiblemultirange, y anycompatiblearray);
+
 create function polyf(a anyelement, b anyarray,
                       c anycompatible, d anycompatible,
                       OUT x anyarray, OUT y anycompatiblearray)
@@ -997,6 +1020,35 @@ returns anycompatiblerange as $$
   select $1
 $$ language sql;
 
+create function anyctest(anycompatible, anycompatiblemultirange)
+returns anycompatiblemultirange as $$
+  select $2
+$$ language sql;
+
+select x, pg_typeof(x) from anyctest(11, multirange(int4range(4,7))) x;
+select x, pg_typeof(x) from anyctest(11, multirange(numrange(4,7))) x;
+select x, pg_typeof(x) from anyctest(11, 12) x;  -- fail
+select x, pg_typeof(x) from anyctest(11.2, multirange(int4range(4,7))) x;  -- fail
+select x, pg_typeof(x) from anyctest(11.2, '{[4,7)}') x;  -- fail
+
+drop function anyctest(anycompatible, anycompatiblemultirange);
+
+create function anyctest(anycompatiblemultirange, anycompatiblemultirange)
+returns anycompatible as $$
+  select lower($1) + upper($2)
+$$ language sql;
+
+select x, pg_typeof(x) from anyctest(multirange(int4range(11,12)), multirange(int4range(4,7))) x;
+select x, pg_typeof(x) from anyctest(multirange(int4range(11,12)), multirange(numrange(4,7))) x; -- fail
+
+drop function anyctest(anycompatiblemultirange, anycompatiblemultirange);
+
+-- fail, can't infer result type:
+create function anyctest(anycompatible)
+returns anycompatiblemultirange as $$
+  select $1
+$$ language sql;
+
 create function anyctest(anycompatiblenonarray, anycompatiblenonarray)
 returns anycompatiblearray as $$
   select array[$1, $2]
diff --git a/src/test/regress/sql/rangetypes.sql b/src/test/regress/sql/rangetypes.sql
index 4048b1d1854..b69efede3ae 100644
--- a/src/test/regress/sql/rangetypes.sql
+++ b/src/test/regress/sql/rangetypes.sql
@@ -118,6 +118,12 @@ select numrange(1.0, 2.0) * numrange(2.0, 3.0);
 select numrange(1.0, 2.0) * numrange(1.5, 3.0);
 select numrange(1.0, 2.0) * numrange(2.5, 3.0);
 
+select range_intersect_agg(nr) from numrange_test;
+select range_intersect_agg(nr) from numrange_test where false;
+select range_intersect_agg(nr) from numrange_test where nr @> 4.0;
+
+analyze numrange_test;
+
 create table numrange_test2(nr numrange);
 create index numrange_test2_hash_idx on numrange_test2 using hash (nr);
 
@@ -528,6 +534,7 @@ reset enable_sort;
 -- OUT/INOUT/TABLE functions
 --
 
+-- infer anyrange from anyrange
 create function outparam_succeed(i anyrange, out r anyrange, out t text)
   as $$ select $1, 'foo'::text $$ language sql;
 
@@ -539,6 +546,13 @@ create function outparam2_succeed(r anyrange, out lu anyarray, out ul anyarray)
 
 select * from outparam2_succeed(int4range(1,11));
 
+-- infer anyarray from anyrange
+create function outparam_succeed2(i anyrange, out r anyarray, out t text)
+  as $$ select ARRAY[upper($1)], 'foo'::text $$ language sql;
+
+select * from outparam_succeed2(int4range(int4range(1,2)));
+
+-- infer anyelement from anyrange
 create function inoutparam_succeed(out i anyelement, inout r anyrange)
   as $$ select upper($1), $1 $$ language sql;
 
diff --git a/src/test/regress/sql/type_sanity.sql b/src/test/regress/sql/type_sanity.sql
index 4b492ce0625..07879d9a574 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'));
@@ -153,7 +153,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')
@@ -182,7 +182,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')
@@ -234,7 +234,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')
@@ -263,7 +263,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')
@@ -467,3 +467,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.rngmultitypid
+FROM pg_range p1
+WHERE p1.rngmultitypid IS NULL OR p1.rngmultitypid = 0;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index b8ca8cffd91..a570651dee4 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1395,6 +1395,9 @@ MultiXactMember
 MultiXactOffset
 MultiXactStateData
 MultiXactStatus
+MultirangeIOData
+MultirangeParseState
+MultirangeType
 NDBOX
 NODE
 NUMCacheEntry
@@ -2276,6 +2279,7 @@ ShellTypeInfo
 ShippableCacheEntry
 ShippableCacheKey
 ShmemIndexEnt
+ShortRangeType
 ShutdownForeignScan_function
 ShutdownInformation
 ShutdownMode
#135Alexander Korotkov
aekorotkov@gmail.com
In reply to: Paul A Jungwirth (#134)
Re: range_agg

On Sun, Nov 29, 2020 at 11:53 PM Paul A Jungwirth
<pj@illuminatedcomputing.com> wrote:

On Sun, Nov 29, 2020 at 11:43 AM Alexander Korotkov
<aekorotkov@gmail.com> wrote:

Thank you. Could you please, update doc/src/sgml/catalogs.sgml,
because pg_type and pg_range catalogs are updated.

Attached! :-)

You're quick, thank you. Please, also take a look at cfbot failure
https://travis-ci.org/github/postgresql-cfbot/postgresql/builds/746623942
I've tried to reproduce it, but didn't manage yet.

------
Regards,
Alexander Korotkov

#136Alexander Korotkov
aekorotkov@gmail.com
In reply to: Alexander Korotkov (#135)
Re: range_agg

On Mon, Nov 30, 2020 at 10:35 PM Alexander Korotkov
<aekorotkov@gmail.com> wrote:

On Sun, Nov 29, 2020 at 11:53 PM Paul A Jungwirth
<pj@illuminatedcomputing.com> wrote:

On Sun, Nov 29, 2020 at 11:43 AM Alexander Korotkov
<aekorotkov@gmail.com> wrote:

Thank you. Could you please, update doc/src/sgml/catalogs.sgml,
because pg_type and pg_range catalogs are updated.

Attached! :-)

You're quick, thank you. Please, also take a look at cfbot failure
https://travis-ci.org/github/postgresql-cfbot/postgresql/builds/746623942
I've tried to reproduce it, but didn't manage yet.

Got it. type_sanity test fails on any platform, you just need to
repeat "make check" till it fails.

The failed query checked consistency of range types, but it didn't
take into account ranges of domains and ranges of records, which are
exercised by multirangetypes test running in parallel. We could teach
this query about such kinds of ranges, but I think that would be
overkill, because we're not going to introduce such builtin ranges
yet. So, I'm going to just move multirangetypes test into another
group of parallel tests.

------
Regards,
Alexander Korotkov

#137Alexander Korotkov
aekorotkov@gmail.com
In reply to: Alexander Korotkov (#136)
Re: range_agg

On Mon, Nov 30, 2020 at 11:39 PM Alexander Korotkov
<aekorotkov@gmail.com> wrote:

On Mon, Nov 30, 2020 at 10:35 PM Alexander Korotkov
<aekorotkov@gmail.com> wrote:

On Sun, Nov 29, 2020 at 11:53 PM Paul A Jungwirth
<pj@illuminatedcomputing.com> wrote:

On Sun, Nov 29, 2020 at 11:43 AM Alexander Korotkov
<aekorotkov@gmail.com> wrote:

Thank you. Could you please, update doc/src/sgml/catalogs.sgml,
because pg_type and pg_range catalogs are updated.

Attached! :-)

You're quick, thank you. Please, also take a look at cfbot failure
https://travis-ci.org/github/postgresql-cfbot/postgresql/builds/746623942
I've tried to reproduce it, but didn't manage yet.

Got it. type_sanity test fails on any platform, you just need to
repeat "make check" till it fails.

The failed query checked consistency of range types, but it didn't
take into account ranges of domains and ranges of records, which are
exercised by multirangetypes test running in parallel. We could teach
this query about such kinds of ranges, but I think that would be
overkill, because we're not going to introduce such builtin ranges
yet. So, I'm going to just move multirangetypes test into another
group of parallel tests.

I also found a problem in multirange types naming logic. Consider the
following example.

create type a_multirange AS (x float, y float);
create type a as range(subtype=text, collation="C");
create table tbl (x __a_multirange);
drop type a_multirange;

If you dump this database, the dump couldn't be restored. The
multirange type is named __a_multirange, because the type named
a_multirange already exists. However, it might appear that
a_multirange type is already deleted. When the dump is restored, a
multirange type is named a_multirange, and the corresponding table
fails to be created. The same thing doesn't happen with arrays,
because arrays are not referenced in dumps by their internal names.

I think we probably should add an option to specify multirange type
names while creating a range type. Then dump can contain exact type
names used in the database, and restore wouldn't have a names
collision.

Another thing that worries me is the multirange serialization format.

typedef struct
{
int32 vl_len_; /* varlena header */
char flags; /* range flags */
char _padding; /* Bounds must be aligned */
/* Following the header are zero to two bound values. */
} ShortRangeType;

Comment says this structure doesn't contain a varlena header, while
structure obviously has it.

In general, I wonder if we can make the binary format of multiranges
more efficient. It seems that every function involving multiranges
from multirange_deserialize(). I think we can make functions like
multirange_contains_elem() much more efficient. Multirange is
basically an array of ranges. So we can pack it as follows.
1. Typeid and rangecount
2. Tightly packed array of flags (1-byte for each range)
3. Array of indexes of boundaries (4-byte for each range). Or even
better we can combine offsets and lengths to be compression-friendly
like jsonb JEntry's do.
4. Boundary values
Using this format, we can implement multirange_contains_elem(),
multirange_contains_range() without deserialization and using binary
search. That would be much more efficient. What do you think?

------
Regards,
Alexander Korotkov

#138Alvaro Herrera
alvherre@alvh.no-ip.org
In reply to: Alexander Korotkov (#137)
Re: range_agg

On 2020-Dec-08, Alexander Korotkov wrote:

I also found a problem in multirange types naming logic. Consider the
following example.

create type a_multirange AS (x float, y float);
create type a as range(subtype=text, collation="C");
create table tbl (x __a_multirange);
drop type a_multirange;

If you dump this database, the dump couldn't be restored. The
multirange type is named __a_multirange, because the type named
a_multirange already exists. However, it might appear that
a_multirange type is already deleted. When the dump is restored, a
multirange type is named a_multirange, and the corresponding table
fails to be created. The same thing doesn't happen with arrays,
because arrays are not referenced in dumps by their internal names.

I think we probably should add an option to specify multirange type
names while creating a range type. Then dump can contain exact type
names used in the database, and restore wouldn't have a names
collision.

Hmm, good point. I agree that a dump must preserve the name, since once
created it is user-visible. I had not noticed this problem, but it's
obvious in retrospect.

In general, I wonder if we can make the binary format of multiranges
more efficient. It seems that every function involving multiranges
from multirange_deserialize(). I think we can make functions like
multirange_contains_elem() much more efficient. Multirange is
basically an array of ranges. So we can pack it as follows.
1. Typeid and rangecount
2. Tightly packed array of flags (1-byte for each range)
3. Array of indexes of boundaries (4-byte for each range). Or even
better we can combine offsets and lengths to be compression-friendly
like jsonb JEntry's do.
4. Boundary values
Using this format, we can implement multirange_contains_elem(),
multirange_contains_range() without deserialization and using binary
search. That would be much more efficient. What do you think?

I also agree. I spent some time staring at the I/O code a couple of
months back but was unable to focus on it for long enough. I don't know
JEntry's format, but I do remember that the storage format for JSONB was
widely discussed back then; it seems wise to apply similar logic or at
least similar reasoning.

#139Alexander Korotkov
aekorotkov@gmail.com
In reply to: Alvaro Herrera (#138)
1 attachment(s)
Re: range_agg

On Tue, Dec 8, 2020 at 3:00 AM Alvaro Herrera <alvherre@alvh.no-ip.org> wrote:

On 2020-Dec-08, Alexander Korotkov wrote:

I also found a problem in multirange types naming logic. Consider the
following example.

create type a_multirange AS (x float, y float);
create type a as range(subtype=text, collation="C");
create table tbl (x __a_multirange);
drop type a_multirange;

If you dump this database, the dump couldn't be restored. The
multirange type is named __a_multirange, because the type named
a_multirange already exists. However, it might appear that
a_multirange type is already deleted. When the dump is restored, a
multirange type is named a_multirange, and the corresponding table
fails to be created. The same thing doesn't happen with arrays,
because arrays are not referenced in dumps by their internal names.

I think we probably should add an option to specify multirange type
names while creating a range type. Then dump can contain exact type
names used in the database, and restore wouldn't have a names
collision.

Hmm, good point. I agree that a dump must preserve the name, since once
created it is user-visible. I had not noticed this problem, but it's
obvious in retrospect.

In general, I wonder if we can make the binary format of multiranges
more efficient. It seems that every function involving multiranges
from multirange_deserialize(). I think we can make functions like
multirange_contains_elem() much more efficient. Multirange is
basically an array of ranges. So we can pack it as follows.
1. Typeid and rangecount
2. Tightly packed array of flags (1-byte for each range)
3. Array of indexes of boundaries (4-byte for each range). Or even
better we can combine offsets and lengths to be compression-friendly
like jsonb JEntry's do.
4. Boundary values
Using this format, we can implement multirange_contains_elem(),
multirange_contains_range() without deserialization and using binary
search. That would be much more efficient. What do you think?

I also agree. I spent some time staring at the I/O code a couple of
months back but was unable to focus on it for long enough. I don't know
JEntry's format, but I do remember that the storage format for JSONB was
widely discussed back then; it seems wise to apply similar logic or at
least similar reasoning.

Thank you for your feedback!

I'd like to publish my revision of the patch. So Paul could start
from it. The changes I made are minor
1. Add missing types to typedefs.list
2. Run pg_indent run over the changed files and some other formatting changes
3. Reorder the regression tests to evade the error spotted by
commitfest.cputube.org

I'm switching this patch to WOA.

------
Regards,
Alexander Korotkov

Attachments:

v25-multirange.patchapplication/octet-stream; name=v25-multirange.patchDownload
commit b2c6f34127eb24c436a074dd655fcca19bef860e
Author: Alexander Korotkov <akorotkov@postgresql.org>
Date:   Sun Nov 29 22:32:30 2020 +0300

    Multiranges v25

diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 569841398b4..e2e52a0567a 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -6237,6 +6237,16 @@ SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</replaceable>:<replaceable>&l
       </para></entry>
      </row>
 
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>rngmultitypid</structfield> <type>oid</type>
+       (references <link linkend="catalog-pg-type"><structname>pg_type</structname></link>.<structfield>oid</structfield>)
+      </para>
+      <para>
+       OID of the multirange type for this range type
+      </para></entry>
+     </row>
+
      <row>
       <entry role="catalog_table_entry"><para role="column_definition">
        <structfield>rngcollation</structfield> <type>oid</type>
@@ -8671,8 +8681,9 @@ SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</replaceable>:<replaceable>&l
        <literal>c</literal> for a composite type (e.g., a table's row type),
        <literal>d</literal> for a domain,
        <literal>e</literal> for an enum type,
-       <literal>p</literal> for a pseudo-type, or
-       <literal>r</literal> for a range type.
+       <literal>p</literal> for a pseudo-type,
+       <literal>r</literal> for a range type, or
+       <literal>m</literal> for a multirange type.
        See also <structfield>typrelid</structfield> and
        <structfield>typbasetype</structfield>.
       </para></entry>
diff --git a/doc/src/sgml/datatype.sgml b/doc/src/sgml/datatype.sgml
index c2951854b5c..e4038783e95 100644
--- a/doc/src/sgml/datatype.sgml
+++ b/doc/src/sgml/datatype.sgml
@@ -4900,6 +4900,10 @@ SELECT * FROM pg_attribute
     <primary>anyrange</primary>
    </indexterm>
 
+   <indexterm zone="datatype-pseudo">
+    <primary>anymultirange</primary>
+   </indexterm>
+
    <indexterm zone="datatype-pseudo">
     <primary>anycompatible</primary>
    </indexterm>
@@ -4916,6 +4920,10 @@ SELECT * FROM pg_attribute
     <primary>anycompatiblerange</primary>
    </indexterm>
 
+   <indexterm zone="datatype-pseudo">
+    <primary>anycompatiblemultirange</primary>
+   </indexterm>
+
    <indexterm zone="datatype-pseudo">
     <primary>void</primary>
    </indexterm>
@@ -5027,6 +5035,13 @@ SELECT * FROM pg_attribute
         <xref linkend="rangetypes"/>).</entry>
        </row>
 
+       <row>
+        <entry><type>anymultirange</type></entry>
+        <entry>Indicates that a function accepts any multirange data type
+        (see <xref linkend="extend-types-polymorphic"/> and
+        <xref linkend="rangetypes"/>).</entry>
+       </row>
+
        <row>
         <entry><type>anycompatible</type></entry>
         <entry>Indicates that a function accepts any data type,
@@ -5056,6 +5071,14 @@ SELECT * FROM pg_attribute
         <xref linkend="rangetypes"/>).</entry>
        </row>
 
+       <row>
+        <entry><type>anycompatiblemultirange</type></entry>
+        <entry>Indicates that a function accepts any multirange data type,
+        with automatic promotion of multiple arguments to a common data type
+        (see <xref linkend="extend-types-polymorphic"/> and
+        <xref linkend="rangetypes"/>).</entry>
+       </row>
+
        <row>
         <entry><type>cstring</type></entry>
         <entry>Indicates that a function accepts or returns a null-terminated C string.</entry>
diff --git a/doc/src/sgml/extend.sgml b/doc/src/sgml/extend.sgml
index 1c37026bb05..6e3d82b85b8 100644
--- a/doc/src/sgml/extend.sgml
+++ b/doc/src/sgml/extend.sgml
@@ -288,6 +288,14 @@
         </entry>
        </row>
 
+       <row>
+        <entry><type>anymultirange</type></entry>
+        <entry>Simple</entry>
+        <entry>Indicates that a function accepts any multirange data type
+        (see <xref linkend="rangetypes"/>)
+        </entry>
+       </row>
+
        <row>
         <entry><type>anycompatible</type></entry>
         <entry>Common</entry>
@@ -319,6 +327,14 @@
         with automatic promotion of multiple arguments to a common data type
         </entry>
        </row>
+
+       <row>
+        <entry><type>anycompatiblemultirange</type></entry>
+        <entry>Common</entry>
+        <entry>Indicates that a function accepts any multirange data type,
+        with automatic promotion of multiple arguments to a common data type
+        </entry>
+       </row>
       </tbody>
      </tgroup>
     </table>
@@ -346,17 +362,15 @@
      position declared as <type>anyarray</type> can have any array data type,
      but similarly they must all be the same type.  And similarly,
      positions declared as <type>anyrange</type> must all be the same range
-     type.  Furthermore, if there are
+     type.  Likewise for <type>anymultirange</type>.
+    </para>
+
+    <para>
+     Furthermore, if there are
      positions declared <type>anyarray</type> and others declared
      <type>anyelement</type>, the actual array type in the
      <type>anyarray</type> positions must be an array whose elements are
      the same type appearing in the <type>anyelement</type> positions.
-     Similarly, if there are positions declared <type>anyrange</type>
-     and others declared <type>anyelement</type> or <type>anyarray</type>,
-     the actual range type in the <type>anyrange</type> positions must be a
-     range whose subtype is the same type appearing in
-     the <type>anyelement</type> positions and the same as the element type
-     of the <type>anyarray</type> positions.
      <type>anynonarray</type> is treated exactly the same as <type>anyelement</type>,
      but adds the additional constraint that the actual type must not be
      an array type.
@@ -365,6 +379,19 @@
      be an enum type.
     </para>
 
+    <para>
+     Similarly, if there are positions declared <type>anyrange</type>
+     and others declared <type>anyelement</type> or <type>anyarray</type>,
+     the actual range type in the <type>anyrange</type> positions must be a
+     range whose subtype is the same type appearing in
+     the <type>anyelement</type> positions and the same as the element type
+     of the <type>anyarray</type> positions.
+     If there are positions declared <type>anymultirange</type>,
+     their actual multirange type must contain ranges matching parameters declared
+     <type>anyrange</type> and base elements matching parameters declared
+     <type>anyelement</type> and <type>anyarray</type>.
+    </para>
+
     <para>
      Thus, when more than one argument position is declared with a polymorphic
      type, the net effect is that only certain combinations of actual argument
@@ -420,7 +447,8 @@
      Selection of the common type considers the actual types
      of <type>anycompatible</type> and <type>anycompatiblenonarray</type>
      inputs, the array element types of <type>anycompatiblearray</type>
-     inputs, and the range subtypes of <type>anycompatiblerange</type>
+     inputs, the range subtypes of <type>anycompatiblerange</type> inputs,
+     and the multirange subtypes of <type>anycompatiablemultirange</type>
      inputs.  If <type>anycompatiblenonarray</type> is present then the
      common type is required to be a non-array type.  Once a common type is
      identified, arguments in <type>anycompatible</type>
@@ -431,12 +459,15 @@
 
     <para>
      Since there is no way to select a range type knowing only its subtype,
-     use of <type>anycompatiblerange</type> requires that all arguments
-     declared with that type have the same actual range type, and that that
-     type's subtype agree with the selected common type, so that no casting
-     of the range values is required.  As with <type>anyrange</type>, use
-     of <type>anycompatiblerange</type> as a function result type requires
-     that there be an <type>anycompatiblerange</type> argument.
+     use of <type>anycompatiblerange</type> and/or
+     <type>anycompatiblemultirange</type> requires that all arguments declared
+     with that type have the same actual range and/or multirange type, and that
+     that type's subtype agree with the selected common type, so that no casting
+     of the range values is required.  As with <type>anyrange</type> and
+     <type>anymultirange</type>, use of <type>anycompatiblerange</type> and
+     <type>anymultirange</type> as a function result type requires that there be
+     an <type>anycompatiblerange</type> or <type>anycompatiblemultirange</type>
+     argument.
     </para>
 
     <para>
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 507bc1a6683..73da2644263 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -17891,12 +17891,15 @@ SELECT NULLIF(value, '(none)') ...
   <para>
    <xref linkend="range-operators-table"/> shows the specialized operators
    available for range types.
+   <xref linkend="multirange-operators-table"/> shows the specialized operators
+   available for multirange types.
    In addition to those, the usual comparison operators shown in
    <xref linkend="functions-comparison-op-table"/> are available for range
-   types.  The comparison operators order first by the range lower bounds, and
-   only if those are equal do they compare the upper bounds.  This does not
-   usually result in a useful overall ordering, but the operators are provided
-   to allow unique indexes to be constructed on ranges.
+   and multirange types.  The comparison operators order first by the range lower
+   bounds, and only if those are equal do they compare the upper bounds.  The
+   multirange operators compare each range until one is unequal. This
+   does not usually result in a useful overall ordering, but the operators are
+   provided to allow unique indexes to be constructed on ranges.
   </para>
 
    <table id="range-operators-table">
@@ -18106,15 +18109,449 @@ SELECT NULLIF(value, '(none)') ...
     </tgroup>
    </table>
 
+   <table id="multirange-operators-table">
+    <title>Multirange Operators</title>
+    <tgroup cols="1">
+     <thead>
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        Operator
+       </para>
+       <para>
+        Description
+       </para>
+       <para>
+        Example(s)
+       </para></entry>
+      </row>
+     </thead>
+
+     <tbody>
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>@&gt;</literal> <type>anymultirange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Does the first multirange contain the second?
+       </para>
+       <para>
+        <literal>'{[2,4)}'::int4multirange @&gt; '{[2,3)}'::int4multirange</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>@&gt;</literal> <type>anyrange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Does the multirange contain the range?
+       </para>
+       <para>
+        <literal>'{[2,4)}'::int4multirange @&gt; int4range(2,3)</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>@&gt;</literal> <type>anyelement</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Does the multirange contain the element?
+       </para>
+       <para>
+        <literal>'{[2011-01-01,2011-03-01)}'::tsmultirange @&gt; '2011-01-10'::timestamp</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>&lt;@</literal> <type>anymultirange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the first multirange contained by the second?
+       </para>
+       <para>
+        <literal>'{[2,4)}'::int4multirange &lt;@ '{[1,7)}'::int4multirange</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>&lt;@</literal> <type>anyrange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the multirange contained by the range?
+       </para>
+       <para>
+        <literal>'{[2,4)}'::int4multirange &lt;@ int4range(1,7)</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anyrange</type> <literal>&lt;@</literal> <type>anymultirange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the range contained by the multirange?
+       </para>
+       <para>
+        <literal>int4range(2,4) &lt;@ '{[1,7)}'::int4multirange</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anyelement</type> <literal>&lt;@</literal> <type>anymultirange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the element contained by the multirange?
+       </para>
+       <para>
+        <literal>42 &lt;@ '{[1,7)}'::int4multirange</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>&amp;&amp;</literal> <type>anymultirange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Do the multiranges overlap, that is, have any elements in common?
+       </para>
+       <para>
+        <literal>'{[3,7)}'::int8multirange &amp;&amp; '{[4,12)}'::int8multirange</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>&amp;&amp;</literal> <type>anyrange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Does the multirange overlap the range?
+       </para>
+       <para>
+        <literal>'{[3,7)}'::int8multirange &amp;&amp; int8range(4,12)</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anyrange</type> <literal>&amp;&amp;</literal> <type>anymultirange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Does the range overlap the multirange?
+       </para>
+       <para>
+        <literal>int8range(3,7) &amp;&amp; '{[4,12)}'::int8multirange</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>&lt;&lt;</literal> <type>anymultirange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the first multirange strictly left of the second?
+       </para>
+       <para>
+        <literal>'{[1,10)}'::int8multirange &lt;&lt; '{[100,110)}'::int8multirange</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>&lt;&lt;</literal> <type>anyrange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the multirange strictly left of the range?
+       </para>
+       <para>
+        <literal>'{[1,10)}'::int8multirange &lt;&lt; int8range(100,110)</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anyrange</type> <literal>&lt;&lt;</literal> <type>anymultirange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the range strictly left of the multirange?
+       </para>
+       <para>
+        <literal>int8range(1,10) &lt;&lt; '{[100,110)}'::int8multirange</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>&gt;&gt;</literal> <type>anymultirange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the first multirange strictly right of the second?
+       </para>
+       <para>
+        <literal>'{[50,60)}'::int8multirange &gt;&gt; '{[20,30)}'::int8multirange</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>&gt;&gt;</literal> <type>anyrange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the multirange strictly right of the range?
+       </para>
+       <para>
+        <literal>'{[50,60)}'::int8multirange &gt;&gt; int8range(20,30)</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anyrange</type> <literal>&gt;&gt;</literal> <type>anymultirange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the range strictly right of the multirange?
+       </para>
+       <para>
+        <literal>int8range(50,60) &gt;&gt; '{[20,30)}'::int8multirange</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>&amp;&lt;</literal> <type>anymultirange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Does the first multirange not extend to the right of the second?
+       </para>
+       <para>
+        <literal>'{[1,20)}'::int8multirange &amp;&lt; '{[18,20)}'::int8multirange</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>&amp;&lt;</literal> <type>anyrange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Does the multirange not extend to the right of the range?
+       </para>
+       <para>
+        <literal>'{[1,20)}'::int8multirange &amp;&lt; int8range(18,20)</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anyrange</type> <literal>&amp;&lt;</literal> <type>anymultirange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Does the range not extend to the right of the multirange?
+       </para>
+       <para>
+        <literal>int8range(1,20) &amp;&lt; '{[18,20)}'::int8multirange</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>&amp;&gt;</literal> <type>anymultirange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Does the first multirange not extend to the left of the second?
+       </para>
+       <para>
+        <literal>'{[7,20)}'::int8multirange &amp;&gt; '{[5,10)}'::int8multirange</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>&amp;&gt;</literal> <type>anyrange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Does the multirange not extend to the left of the range?
+       </para>
+       <para>
+        <literal>'{[7,20)}'::int8multirange &amp;&gt; int8range(5,10)</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anyrange</type> <literal>&amp;&gt;</literal> <type>anymultirange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Does the range not extend to the left of the multirange?
+       </para>
+       <para>
+        <literal>int8range(7,20) &amp;&gt; '{[5,10)}'::int8multirange</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>-|-</literal> <type>anymultirange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Are the multiranges adjacent?
+       </para>
+       <para>
+        <literal>'{[1.1,2.2)}'::nummultirange -|- '{[2.2,3.3)}'::nummultirange</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>-|-</literal> <type>anyrange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the multirange adjacent to the range?
+       </para>
+       <para>
+        <literal>'{[1.1,2.2)}'::nummultirange -|- numrange(2.2,3.3)</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anyrange</type> <literal>-|-</literal> <type>anymultirange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the range adjacent to the multirange?
+       </para>
+       <para>
+        <literal>numrange(1.1,2.2) -|- '{[2.2,3.3)}'::nummultirange</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>+</literal> <type>anymultirange</type>
+        <returnvalue>anymultirange</returnvalue>
+       </para>
+       <para>
+        Computes the union of the multiranges.  The multiranges need not overlap
+        or be adjacent.
+       </para>
+       <para>
+        <literal>'{[5,10)}'::nummultirange + '{[15,20)}'::nummultirange</literal>
+        <returnvalue>{[5,10), [15,20)}</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>*</literal> <type>anymultirange</type>
+        <returnvalue>anymultirange</returnvalue>
+       </para>
+       <para>
+        Computes the intersection of the multiranges.
+       </para>
+       <para>
+        <literal>'{[5,15)}'::int8multirange * '{[10,20)}'::int8multirange</literal>
+        <returnvalue>{[10,15)}</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>-</literal> <type>anymultirange</type>
+        <returnvalue>anymultirange</returnvalue>
+       </para>
+       <para>
+        Computes the difference of the multiranges.
+       </para>
+       <para>
+        <literal>'{[5,20)}'::int8multirange - '{[10,15)}'::int8multirange</literal>
+        <returnvalue>{[5,10), [15,20)}</returnvalue>
+       </para></entry>
+      </row>
+     </tbody>
+    </tgroup>
+   </table>
+
   <para>
    The left-of/right-of/adjacent operators always return false when an empty
-   range is involved; that is, an empty range is not considered to be either
-   before or after any other range.
+   range or multirange is involved; that is, an empty range is not considered to
+   be either before or after any other range.
+  </para>
+
+  <para>
+   Elsewhere empty ranges and multiranges are treated as the additive identity:
+   anything unioned with an empty value is itself. Anything minus an empty
+   value is itself. An empty multirange has exactly the same points as an empty
+   range. Every range contains the empty range. Every multirange contains as many
+   empty ranges as you like.
+  </para>
+
+  <para>
+   The range union and difference operators will fail if the resulting range would
+   need to contain two disjoint sub-ranges, as such a range cannot be
+   represented. There are separate operators for union and difference that take
+   multirange parameters and return a multirange, and they do not fail even if
+   their arguments are disjoint. So if you need a union or difference operation
+   for ranges that may be disjoint, you can avoid errors by first casting your
+   ranges to multiranges.
   </para>
 
   <para>
    <xref linkend="range-functions-table"/> shows the functions
    available for use with range types.
+   <xref linkend="multirange-functions-table"/> shows the functions
+   available for use with multirange types.
   </para>
 
    <table id="range-functions-table">
@@ -18276,10 +18713,185 @@ SELECT NULLIF(value, '(none)') ...
     </tgroup>
    </table>
 
+   <table id="multirange-functions-table">
+    <title>Multirange Functions</title>
+    <tgroup cols="1">
+     <thead>
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        Function
+       </para>
+       <para>
+        Description
+       </para>
+       <para>
+        Example(s)
+       </para></entry>
+      </row>
+     </thead>
+     <tbody>
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>lower</primary>
+        </indexterm>
+        <function>lower</function> ( <type>anymultirange</type> )
+        <returnvalue>anyelement</returnvalue>
+       </para>
+       <para>
+        Extracts the lower bound of the multirange (<literal>NULL</literal> if the
+        multirange is empty or the lower bound is infinite).
+       </para>
+       <para>
+        <literal>lower('{[1.1,2.2)}'::nummultirange)</literal>
+        <returnvalue>1.1</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>upper</primary>
+        </indexterm>
+        <function>upper</function> ( <type>anymultirange</type> )
+        <returnvalue>anyelement</returnvalue>
+       </para>
+       <para>
+        Extracts the upper bound of the multirange (<literal>NULL</literal> if the
+        multirange is empty or the upper bound is infinite).
+       </para>
+       <para>
+        <literal>upper('{[1.1,2.2)}'::nummultirange)</literal>
+        <returnvalue>2.2</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>isempty</primary>
+        </indexterm>
+        <function>isempty</function> ( <type>anymultirange</type> )
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the multirange empty?
+       </para>
+       <para>
+        <literal>isempty('{[1.1,2.2)}'::nummultirange)</literal>
+        <returnvalue>f</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>lower_inc</primary>
+        </indexterm>
+        <function>lower_inc</function> ( <type>anymultirange</type> )
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the multirange's lower bound inclusive?
+       </para>
+       <para>
+        <literal>lower_inc('{[1.1,2.2)}'::nummultirange)</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>upper_inc</primary>
+        </indexterm>
+        <function>upper_inc</function> ( <type>anymultirange</type> )
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the multirange's upper bound inclusive?
+       </para>
+       <para>
+        <literal>upper_inc('{[1.1,2.2)}'::nummultirange)</literal>
+        <returnvalue>f</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>lower_inf</primary>
+        </indexterm>
+        <function>lower_inf</function> ( <type>anymultirange</type> )
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the multirange's lower bound infinite?
+       </para>
+       <para>
+        <literal>lower_inf('{(,)}'::datemultirange)</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>upper_inf</primary>
+        </indexterm>
+        <function>upper_inf</function> ( <type>anymultirange</type> )
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the multirange's upper bound infinite?
+       </para>
+       <para>
+        <literal>upper_inf('{(,)}'::datemultirange)</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>range_merge</primary>
+        </indexterm>
+        <function>range_merge</function> ( <type>anymultirange</type> )
+        <returnvalue>anyrange</returnvalue>
+       </para>
+       <para>
+        Computes the smallest range that includes the entire multirange.
+       </para>
+       <para>
+        <literal>range_merge('{[1,2), [3,4)}'::int4multirange)</literal>
+        <returnvalue>[1,4)</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>multirange</primary>
+        </indexterm>
+        <function>multirange</function> ( <type>anyrange</type> )
+        <returnvalue>anymultirange</returnvalue>
+       </para>
+       <para>
+        Returns a multirange containing just the given range.
+       </para>
+       <para>
+        <literal>multirange('[1,2)'::int4range)</literal>
+        <returnvalue>{[1,2)}</returnvalue>
+       </para></entry>
+      </row>
+     </tbody>
+    </tgroup>
+   </table>
+
   <para>
    The <function>lower_inc</function>, <function>upper_inc</function>,
    <function>lower_inf</function>, and <function>upper_inf</function>
-   functions all return false for an empty range.
+   functions all return false for an empty range or multirange.
   </para>
   </sect1>
 
@@ -18611,6 +19223,36 @@ SELECT NULLIF(value, '(none)') ...
        <entry>Yes</entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>range_agg</primary>
+        </indexterm>
+        <function>range_agg</function> ( <parameter>value</parameter>
+         <type>anyrange</type> )
+        <returnvalue>anymultirange</returnvalue>
+       </para>
+       <para>
+        Computes the union of the non-null input values.
+       </para></entry>
+       <entry>No</entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>range_intersect_agg</primary>
+        </indexterm>
+        <function>range_intersect_agg</function> ( <parameter>value</parameter>
+         <type>anyrange</type> )
+        <returnvalue>anymultirange</returnvalue>
+       </para>
+       <para>
+        Computes the intersection of the non-null input values.
+       </para></entry>
+       <entry>No</entry>
+      </row>
+
       <row>
        <entry role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/doc/src/sgml/rangetypes.sgml b/doc/src/sgml/rangetypes.sgml
index b75fb3a3929..c8784bdce38 100644
--- a/doc/src/sgml/rangetypes.sgml
+++ b/doc/src/sgml/rangetypes.sgml
@@ -27,6 +27,13 @@
   ranges from an instrument, and so forth can also be useful.
  </para>
 
+ <para>
+  Every range type has a corresponding multirange type. A multirange is
+  an ordered list of non-continguous, non-empty, non-null ranges. Most
+  range operators also work on multiranges, and they have a few functions
+  of their own.
+ </para>
+
  <sect2 id="rangetypes-builtin">
   <title>Built-in Range Types</title>
 
@@ -232,10 +239,30 @@ SELECT '[4,4]'::int4range;
 SELECT '[4,4)'::int4range;
 </programlisting>
   </para>
+
+  <para>
+   The input for a multirange is curly brackets (<literal>{</literal> and
+   <literal>}</literal>) containing zero or more valid ranges,
+   separated by commas. Whitespace is permitted around the brackets and
+   commas. This is intended to be reminiscent of array syntax, although
+   multiranges are much simpler: they have just one dimension and there is
+   no need to quote their contents. (The bounds of their ranges may be
+   quoted as above however.)
+  </para>
+
+  <para>
+  Examples:
+<programlisting>
+SELECT '{}'::int4multirange;
+SELECT '{[3,7)}'::int4multirange;
+SELECT '{[3,7), [8,9)}'::int4multirange;
+</programlisting>
+  </para>
+
  </sect2>
 
  <sect2 id="rangetypes-construct">
-  <title>Constructing Ranges</title>
+  <title>Constructing Ranges and Multiranges</title>
 
   <para>
    Each range type has a constructor function with the same name as the range
@@ -267,6 +294,19 @@ SELECT int8range(1, 14, '(]');
 
 -- Using NULL for either bound causes the range to be unbounded on that side.
 SELECT numrange(NULL, 2.2);
+</programlisting>
+  </para>
+
+  <para>
+   Each range type also has a multirange constructor with the same name as the
+   multirange type.  The constructor function takes zero or more arguments
+   which are all ranges of the appropriate type.
+   For example:
+
+<programlisting>
+SELECT nummultirange();
+SELECT nummultirange(numrange(1.0, 14.0));
+SELECT nummultirange(numrange(1.0, 14.0), numrange(20.0, 25.0));
 </programlisting>
   </para>
  </sect2>
@@ -341,6 +381,11 @@ SELECT '[1.234, 5.678]'::floatrange;
    function in this example.
   </para>
 
+  <para>
+   When you define your own range you automatically get a corresponding
+   multirange type.
+  </para>
+
   <para>
    Defining your own range type also allows you to specify a different
    subtype B-tree operator class or collation to use, so as to change the sort
diff --git a/src/backend/catalog/pg_range.c b/src/backend/catalog/pg_range.c
index a606d8c3adb..91b0fb0611a 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;
 	ObjectAddresses *addrs;
 
 	pg_range = table_open(RangeRelationId, RowExclusiveLock);
@@ -55,6 +56,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_rngmultitypid - 1] = ObjectIdGetDatum(multirangeTypeOid);
 
 	tup = heap_form_tuple(RelationGetDescr(pg_range), values, nulls);
 
@@ -93,6 +95,12 @@ RangeCreate(Oid rangeTypeOid, Oid rangeSubType, Oid rangeCollation,
 	record_object_address_dependencies(&myself, addrs, DEPENDENCY_NORMAL);
 	free_object_addresses(addrs);
 
+	/* 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 aeb4a54f635..a00613c8d4d 100644
--- a/src/backend/catalog/pg_type.c
+++ b/src/backend/catalog/pg_type.c
@@ -28,6 +28,7 @@
 #include "catalog/pg_proc.h"
 #include "catalog/pg_type.h"
 #include "commands/typecmds.h"
+#include "mb/pg_wchar.h"
 #include "miscadmin.h"
 #include "parser/scansup.h"
 #include "utils/acl.h"
@@ -37,6 +38,9 @@
 #include "utils/rel.h"
 #include "utils/syscache.h"
 
+static char *makeUniqueTypeName(const char *typeName, Oid typeNamespace,
+								bool tryOriginal);
+
 /* Potentially set by pg_upgrade_support functions */
 Oid			binary_upgrade_next_pg_type_oid = InvalidOid;
 
@@ -854,31 +858,10 @@ RenameTypeInternal(Oid typeOid, const char *newTypeName, Oid typeNamespace)
 char *
 makeArrayTypeName(const char *typeName, Oid typeNamespace)
 {
-	char	   *arr = (char *) palloc(NAMEDATALEN);
-	int			namelen = strlen(typeName);
-	int			i;
-
-	/*
-	 * 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;
-	}
+	char	   *arr;
 
-	if (i >= NAMEDATALEN - 1)
+	arr = makeUniqueTypeName(typeName, typeNamespace, false);
+	if (arr == NULL)
 		ereport(ERROR,
 				(errcode(ERRCODE_DUPLICATE_OBJECT),
 				 errmsg("could not form array type name for type \"%s\"",
@@ -949,3 +932,91 @@ moveArrayTypeName(Oid typeOid, const char *typeName, Oid typeNamespace)
 
 	return true;
 }
+
+
+/*
+ * makeMultirangeTypeName
+ *	  - given a range type name, make a multirange type name for it
+ *
+ * caller is responsible for pfreeing the result
+ */
+char *
+makeMultirangeTypeName(const char *rangeTypeName, Oid typeNamespace)
+{
+	char	   *buf;
+	char	   *mrname;
+	char	   *rangestr;
+
+	/*
+	 * 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...
+	 */
+	rangestr = strstr(rangeTypeName, "range");
+	if (rangestr)
+	{
+		char	   *prefix = pnstrdup(rangeTypeName, rangestr - rangeTypeName);
+
+		buf = psprintf("%s%s%s", prefix, "multi", rangestr);
+	}
+	else
+		buf = psprintf("%s_multirange", pnstrdup(rangeTypeName, NAMEDATALEN - 12));
+
+	/* clip it at NAMEDATALEN-1 bytes */
+	buf[pg_mbcliplen(buf, strlen(buf), NAMEDATALEN - 1)] = '\0';
+
+	mrname = makeUniqueTypeName(buf, typeNamespace, true);
+	if (mrname == NULL)
+		ereport(ERROR,
+				(errcode(ERRCODE_DUPLICATE_OBJECT),
+				 errmsg("could not form multirange type name for type \"%s\"",
+						rangeTypeName)));
+
+	return mrname;
+}
+
+/*
+ * makeUniqueTypeName
+ *		Generate a unique name for a prospective new type
+ *
+ * Given a typeName, return a new palloc'ed name by preprending underscores
+ * until a non-conflicting name results.
+ *
+ * If tryOriginal, first try with zero underscores.
+ */
+static char *
+makeUniqueTypeName(const char *typeName, Oid typeNamespace, bool tryOriginal)
+{
+	int			i;
+	int			namelen;
+	char		dest[NAMEDATALEN];
+
+	Assert(strlen(typeName) <= NAMEDATALEN - 1);
+
+	if (tryOriginal &&
+		!SearchSysCacheExists2(TYPENAMENSP,
+							   CStringGetDatum(typeName),
+							   ObjectIdGetDatum(typeNamespace)))
+		return pstrdup(typeName);
+
+	/*
+	 * The idea is to prepend underscores as needed until we make a name that
+	 * doesn't collide with anything ...
+	 */
+	namelen = strlen(typeName);
+	for (i = 1; i < NAMEDATALEN - 1; i++)
+	{
+		dest[i - 1] = '_';
+		strlcpy(dest + i, typeName, NAMEDATALEN - i);
+		if (namelen + i >= NAMEDATALEN)
+			truncate_identifier(dest, NAMEDATALEN, false);
+
+		if (!SearchSysCacheExists2(TYPENAMENSP,
+								   CStringGetDatum(dest),
+								   ObjectIdGetDatum(typeNamespace)))
+			return pstrdup(dest);
+	}
+
+	return NULL;
+}
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index 483bb65ddc8..41b59e7ddfb 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -105,9 +105,14 @@ typedef struct
 
 /* Potentially set by pg_upgrade_support functions */
 Oid			binary_upgrade_next_array_pg_type_oid = InvalidOid;
+Oid			binary_upgrade_next_mrng_pg_type_oid = InvalidOid;
+Oid			binary_upgrade_next_mrng_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 rangeOid,
+									   Oid rangeArrayOid, Oid *castFuncOid);
 static Oid	findTypeInputFunction(List *procname, Oid typeOid);
 static Oid	findTypeOutputFunction(List *procname, Oid typeOid);
 static Oid	findTypeReceiveFunction(List *procname, Oid typeOid);
@@ -739,7 +744,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",
@@ -1280,6 +1286,11 @@ checkEnumOwner(HeapTuple tup)
 /*
  * DefineRange
  *		Registers a new range type.
+ *
+ * Perhaps it might be worthwhile to set pg_type.typelem to the base type,
+ * and likewise on multiranges to set it to the range type. But having a
+ * non-zero typelem is treated elsewhere as a synonym for being an array,
+ * and users might have queries with that same assumption.
  */
 ObjectAddress
 DefineRange(CreateRangeStmt *stmt)
@@ -1288,7 +1299,11 @@ DefineRange(CreateRangeStmt *stmt)
 	Oid			typeNamespace;
 	Oid			typoid;
 	char	   *rangeArrayName;
+	char	   *multirangeTypeName;
+	char	   *multirangeArrayName;
 	Oid			rangeArrayOid;
+	Oid			multirangeOid;
+	Oid			multirangeArrayOid;
 	Oid			rangeSubtype = InvalidOid;
 	List	   *rangeSubOpclassName = NIL;
 	List	   *rangeCollationName = NIL;
@@ -1305,6 +1320,8 @@ DefineRange(CreateRangeStmt *stmt)
 	AclResult	aclresult;
 	ListCell   *lc;
 	ObjectAddress address;
+	ObjectAddress mltrngaddress;
+	Oid			castFuncOid;
 
 	/* Convert list of names to a name and namespace */
 	typeNamespace = QualifiedNameGetCreationNamespace(stmt->typeName,
@@ -1453,8 +1470,10 @@ DefineRange(CreateRangeStmt *stmt)
 	/* alignment must be TYPALIGN_INT or TYPALIGN_DOUBLE for ranges */
 	alignment = (subtypalign == TYPALIGN_DOUBLE) ? TYPALIGN_DOUBLE : TYPALIGN_INT;
 
-	/* Allocate OID for array type */
+	/* Allocate OID for array type, its multirange, and its multirange array */
 	rangeArrayOid = AssignTypeArrayOid();
+	multirangeOid = AssignTypeMultirangeOid();
+	multirangeArrayOid = AssignTypeMultirangeArrayOid();
 
 	/* Create the pg_type entry */
 	address =
@@ -1492,9 +1511,46 @@ DefineRange(CreateRangeStmt *stmt)
 	Assert(typoid == InvalidOid || typoid == address.objectId);
 	typoid = address.objectId;
 
+	/* Create the multirange that goes with it */
+	multirangeTypeName = makeMultirangeTypeName(typeName, typeNamespace);
+
+	mltrngaddress =
+		TypeCreate(multirangeOid,	/* force assignment of this 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_RANGE,	/* type-category (range 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) */
+	Assert(multirangeOid == mltrngaddress.objectId);
+
 	/* Create the entry in pg_range */
 	RangeCreate(typoid, rangeSubtype, rangeCollation, rangeSubOpclass,
-				rangeCanonical, rangeSubtypeDiff);
+				rangeCanonical, rangeSubtypeDiff, multirangeOid);
 
 	/*
 	 * Create the array type that goes with it.
@@ -1535,8 +1591,53 @@ 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 */
+			   multirangeOid,	/* 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,
+							   multirangeOid, typoid, rangeArrayOid,
+							   &castFuncOid);
+
+	/* Create cast from the range type to its multirange type */
+	CastCreate(typoid, multirangeOid, castFuncOid, 'e', 'f', DEPENDENCY_INTERNAL);
+
+	pfree(multirangeTypeName);
+	pfree(multirangeArrayName);
 
 	return address;
 }
@@ -1614,6 +1715,149 @@ 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.
+ *
+ * Sets castFuncOid to the oid of the new constructor that can be used
+ * to cast from a range to a multirange.
+ */
+static void
+makeMultirangeConstructors(const char *name, Oid namespace,
+						   Oid multirangeOid, Oid rangeOid, Oid rangeArrayOid,
+						   Oid *castFuncOid)
+{
+	ObjectAddress myself,
+				referenced;
+	oidvector  *argtypes;
+	Datum		allParamTypes;
+	ArrayType  *allParameterTypes;
+	Datum		paramModes;
+	ArrayType  *parameterModes;
+
+	referenced.classId = TypeRelationId;
+	referenced.objectId = multirangeOid;
+	referenced.objectSubId = 0;
+
+	/* 0-arg constructor - for empty multiranges */
+	argtypes = buildoidvector(NULL, 0);
+	myself = ProcedureCreate(name,	/* name: same as multirange type */
+							 namespace,
+							 false, /* replace */
+							 false, /* returns set */
+							 multirangeOid, /* return type */
+							 BOOTSTRAP_SUPERUSERID, /* proowner */
+							 INTERNALlanguageId,	/* language */
+							 F_FMGR_INTERNAL_VALIDATOR,
+							 "multirange_constructor0", /* prosrc */
+							 NULL,	/* probin */
+							 PROKIND_FUNCTION,
+							 false, /* security_definer */
+							 false, /* leakproof */
+							 false, /* isStrict */
+							 PROVOLATILE_IMMUTABLE, /* volatility */
+							 PROPARALLEL_SAFE,	/* parallel safety */
+							 argtypes,	/* parameterTypes */
+							 PointerGetDatum(NULL), /* allParameterTypes */
+							 PointerGetDatum(NULL), /* parameterModes */
+							 PointerGetDatum(NULL), /* parameterNames */
+							 NIL,	/* parameterDefaults */
+							 PointerGetDatum(NULL), /* trftypes */
+							 PointerGetDatum(NULL), /* proconfig */
+							 InvalidOid,	/* prosupport */
+							 1.0,	/* procost */
+							 0.0);	/* prorows */
+
+	/*
+	 * Make the constructor 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);
+	pfree(argtypes);
+
+	/*
+	 * 1-arg constructor - for casts
+	 *
+	 * In theory we shouldn't need both this and the vararg (n-arg)
+	 * constructor, but having a separate 1-arg function lets us define casts
+	 * against it.
+	 */
+	argtypes = buildoidvector(&rangeOid, 1);
+	myself = ProcedureCreate(name,	/* name: same as multirange type */
+							 namespace,
+							 false, /* replace */
+							 false, /* returns set */
+							 multirangeOid, /* return type */
+							 BOOTSTRAP_SUPERUSERID, /* proowner */
+							 INTERNALlanguageId,	/* language */
+							 F_FMGR_INTERNAL_VALIDATOR,
+							 "multirange_constructor1", /* prosrc */
+							 NULL,	/* probin */
+							 PROKIND_FUNCTION,
+							 false, /* security_definer */
+							 false, /* leakproof */
+							 true,	/* isStrict */
+							 PROVOLATILE_IMMUTABLE, /* volatility */
+							 PROPARALLEL_SAFE,	/* parallel safety */
+							 argtypes,	/* parameterTypes */
+							 PointerGetDatum(NULL), /* allParameterTypes */
+							 PointerGetDatum(NULL), /* parameterModes */
+							 PointerGetDatum(NULL), /* parameterNames */
+							 NIL,	/* parameterDefaults */
+							 PointerGetDatum(NULL), /* trftypes */
+							 PointerGetDatum(NULL), /* proconfig */
+							 InvalidOid,	/* prosupport */
+							 1.0,	/* procost */
+							 0.0);	/* prorows */
+	/* ditto */
+	recordDependencyOn(&myself, &referenced, DEPENDENCY_INTERNAL);
+	pfree(argtypes);
+	*castFuncOid = myself.objectId;
+
+	/* n-arg constructor - vararg */
+	argtypes = buildoidvector(&rangeArrayOid, 1);
+	allParamTypes = ObjectIdGetDatum(rangeArrayOid);
+	allParameterTypes = construct_array(&allParamTypes,
+										1, OIDOID,
+										sizeof(Oid), true, 'i');
+	paramModes = CharGetDatum(FUNC_PARAM_VARIADIC);
+	parameterModes = construct_array(&paramModes, 1, CHAROID,
+									 1, true, 'c');
+	myself = ProcedureCreate(name,	/* name: same as multirange type */
+							 namespace,
+							 false, /* replace */
+							 false, /* returns set */
+							 multirangeOid, /* return type */
+							 BOOTSTRAP_SUPERUSERID, /* proowner */
+							 INTERNALlanguageId,	/* language */
+							 F_FMGR_INTERNAL_VALIDATOR,
+							 "multirange_constructor2", /* prosrc */
+							 NULL,	/* probin */
+							 PROKIND_FUNCTION,
+							 false, /* security_definer */
+							 false, /* leakproof */
+							 false, /* isStrict */
+							 PROVOLATILE_IMMUTABLE, /* volatility */
+							 PROPARALLEL_SAFE,	/* parallel safety */
+							 argtypes,	/* parameterTypes */
+							 PointerGetDatum(allParameterTypes),	/* allParameterTypes */
+							 PointerGetDatum(parameterModes),	/* parameterModes */
+							 PointerGetDatum(NULL), /* parameterNames */
+							 NIL,	/* parameterDefaults */
+							 PointerGetDatum(NULL), /* trftypes */
+							 PointerGetDatum(NULL), /* proconfig */
+							 InvalidOid,	/* prosupport */
+							 1.0,	/* procost */
+							 0.0);	/* prorows */
+	/* ditto */
+	recordDependencyOn(&myself, &referenced, DEPENDENCY_INTERNAL);
+	pfree(argtypes);
+	pfree(allParameterTypes);
+	pfree(parameterModes);
+}
 
 /*
  * Find suitable I/O functions for a type.
@@ -2068,6 +2312,72 @@ AssignTypeArrayOid(void)
 	return type_array_oid;
 }
 
+/*
+ *	AssignTypeMultirangeOid
+ *
+ *	Pre-assign the range type's multirange OID for use in pg_type.oid
+ */
+Oid
+AssignTypeMultirangeOid(void)
+{
+	Oid			type_multirange_oid;
+
+	/* Use binary-upgrade override for pg_type.oid? */
+	if (IsBinaryUpgrade)
+	{
+		if (!OidIsValid(binary_upgrade_next_mrng_pg_type_oid))
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+					 errmsg("pg_type multirange OID value not set when in binary upgrade mode")));
+
+		type_multirange_oid = binary_upgrade_next_mrng_pg_type_oid;
+		binary_upgrade_next_mrng_pg_type_oid = InvalidOid;
+	}
+	else
+	{
+		Relation	pg_type = table_open(TypeRelationId, AccessShareLock);
+
+		type_multirange_oid = GetNewOidWithIndex(pg_type, TypeOidIndexId,
+												 Anum_pg_type_oid);
+		table_close(pg_type, AccessShareLock);
+	}
+
+	return type_multirange_oid;
+}
+
+/*
+ *	AssignTypeMultirangeArrayOid
+ *
+ *	Pre-assign the range type's multirange array OID for use in pg_type.typarray
+ */
+Oid
+AssignTypeMultirangeArrayOid(void)
+{
+	Oid			type_multirange_array_oid;
+
+	/* Use binary-upgrade override for pg_type.oid? */
+	if (IsBinaryUpgrade)
+	{
+		if (!OidIsValid(binary_upgrade_next_mrng_array_pg_type_oid))
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+					 errmsg("pg_type multirange array OID value not set when in binary upgrade mode")));
+
+		type_multirange_array_oid = binary_upgrade_next_mrng_array_pg_type_oid;
+		binary_upgrade_next_mrng_array_pg_type_oid = InvalidOid;
+	}
+	else
+	{
+		Relation	pg_type = table_open(TypeRelationId, AccessShareLock);
+
+		type_multirange_array_oid = GetNewOidWithIndex(pg_type, TypeOidIndexId,
+													   Anum_pg_type_oid);
+		table_close(pg_type, AccessShareLock);
+	}
+
+	return type_multirange_array_oid;
+}
+
 
 /*-------------------------------------------------------------------
  * DefineCompositeType
diff --git a/src/backend/executor/functions.c b/src/backend/executor/functions.c
index 459a33375b1..ca8d637e73a 100644
--- a/src/backend/executor/functions.c
+++ b/src/backend/executor/functions.c
@@ -1711,7 +1711,8 @@ check_sql_fn_retval(List *queryTreeLists,
 	if (fn_typtype == TYPTYPE_BASE ||
 		fn_typtype == TYPTYPE_DOMAIN ||
 		fn_typtype == TYPTYPE_ENUM ||
-		fn_typtype == TYPTYPE_RANGE)
+		fn_typtype == TYPTYPE_RANGE ||
+		fn_typtype == TYPTYPE_MULTIRANGE)
 	{
 		/*
 		 * For scalar-type returns, the target list must have exactly one
diff --git a/src/backend/parser/parse_coerce.c b/src/backend/parser/parse_coerce.c
index a2924e3d1ce..9e723b20180 100644
--- a/src/backend/parser/parse_coerce.c
+++ b/src/backend/parser/parse_coerce.c
@@ -189,19 +189,21 @@ coerce_type(ParseState *pstate, Node *node,
 	if (targetTypeId == ANYARRAYOID ||
 		targetTypeId == ANYENUMOID ||
 		targetTypeId == ANYRANGEOID ||
+		targetTypeId == ANYMULTIRANGEOID ||
 		targetTypeId == ANYCOMPATIBLEARRAYOID ||
-		targetTypeId == ANYCOMPATIBLERANGEOID)
+		targetTypeId == ANYCOMPATIBLERANGEOID ||
+		targetTypeId == ANYCOMPATIBLEMULTIRANGEOID)
 	{
 		/*
 		 * 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 the pseudotype's input
-		 * function, 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 the
+		 * pseudotype's input function, 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
@@ -1569,8 +1571,8 @@ select_common_typmod(ParseState *pstate, List *exprs, Oid common_type)
  * 1) All arguments declared ANYELEMENT must have the same datatype.
  * 2) All arguments declared ANYARRAY must have the same datatype,
  *	  which must be a varlena array type.
- * 3) All arguments declared ANYRANGE must have the same datatype,
- *	  which must be a range type.
+ * 3) All arguments declared ANYRANGE or ANYMULTIRANGE must be a range or
+ *	  multirange type, all derived from the same base datatype.
  * 4) If there are arguments of more than one of these polymorphic types,
  *	  the array element type and/or range subtype must be the same as each
  *	  other and the same as the ANYELEMENT type.
@@ -1585,8 +1587,8 @@ select_common_typmod(ParseState *pstate, List *exprs, Oid common_type)
  *	  to a common supertype (chosen as per select_common_type's rules).
  *	  ANYCOMPATIBLENONARRAY works like ANYCOMPATIBLE but also requires the
  *	  common supertype to not be an array.  If there are ANYCOMPATIBLEARRAY
- *	  or ANYCOMPATIBLERANGE arguments, their element types or subtypes are
- *	  included while making the choice of common supertype.
+ *	  or ANYCOMPATIBLERANGE or ANYCOMPATIBLEMULTIRANGE arguments, their element
+ *	  types or subtypes are included while making the choice of common supertype.
  * 8) The resolved type of ANYCOMPATIBLEARRAY arguments will be the array
  *	  type over the common supertype (which might not be the same array type
  *	  as any of the original arrays).
@@ -1594,6 +1596,10 @@ select_common_typmod(ParseState *pstate, List *exprs, Oid common_type)
  *	  (after domain flattening), since we have no preference rule that would
  *	  let us choose one over another.  Furthermore, that range's subtype
  *	  must exactly match the common supertype chosen by rule 7.
+ * 10) All ANYCOMPATIBLEMULTIRANGE arguments must be the exact same multirange
+ *	  type (after domain flattening), since we have no preference rule that would
+ *	  let us choose one over another.  Furthermore, that multirange's range's
+ *	  subtype must exactly match the common supertype chosen by rule 7.
  *
  * Domains over arrays match ANYARRAY, and are immediately flattened to their
  * base type.  (Thus, for example, we will consider it a match if one ANYARRAY
@@ -1602,7 +1608,9 @@ select_common_typmod(ParseState *pstate, List *exprs, Oid common_type)
  * for ANYCOMPATIBLEARRAY and ANYCOMPATIBLENONARRAY.
  *
  * Similarly, domains over ranges match ANYRANGE or ANYCOMPATIBLERANGE,
- * and are immediately flattened to their base type.
+ * and are immediately flattened to their base type, and domains over
+ * multiranges match ANYMULTIRANGE or ANYCOMPATIBLEMULTIRANGE and are immediately
+ * flattened to their base type.
  *
  * Note that domains aren't currently considered to match ANYENUM,
  * even if their base type would match.
@@ -1620,8 +1628,12 @@ check_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			anycompatible_range_typeid = InvalidOid;
 	Oid			anycompatible_range_typelem = InvalidOid;
+	Oid			anycompatible_multirange_typeid = InvalidOid;
+	Oid			anycompatible_multirange_typelem = InvalidOid;
+	Oid			range_typelem = InvalidOid;
 	bool		have_anynonarray = false;
 	bool		have_anyenum = false;
 	bool		have_anycompatible_nonarray = false;
@@ -1670,6 +1682,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;
+		}
 		else if (decl_type == ANYCOMPATIBLEOID ||
 				 decl_type == ANYCOMPATIBLENONARRAYOID)
 		{
@@ -1714,6 +1735,45 @@ check_generic_type_consistency(const Oid *actual_arg_types,
 				anycompatible_actual_types[n_anycompatible_args++] = anycompatible_range_typelem;
 			}
 		}
+		else if (decl_type == ANYCOMPATIBLEMULTIRANGEOID)
+		{
+			if (actual_type == UNKNOWNOID)
+				continue;
+			actual_type = getBaseType(actual_type); /* flatten domains */
+			if (OidIsValid(anycompatible_multirange_typeid))
+			{
+				/* All ANYCOMPATIBLEMULTIRANGE arguments must be the same type */
+				if (anycompatible_multirange_typeid != actual_type)
+					return false;
+			}
+			else
+			{
+				anycompatible_multirange_typeid = actual_type;
+				anycompatible_multirange_typelem = get_multirange_range(actual_type);
+				if (!OidIsValid(anycompatible_multirange_typelem))
+					return false;	/* not a multirange type */
+
+				if (OidIsValid(anycompatible_range_typeid))
+				{
+					/*
+					 * ANYCOMPATIBLEMULTIRANGE and ANYCOMPATIBLERANGE
+					 * arguments must match
+					 */
+					if (anycompatible_range_typeid != anycompatible_multirange_typelem)
+						return false;
+				}
+				else
+				{
+					anycompatible_range_typeid = anycompatible_multirange_typelem;
+					anycompatible_range_typelem = get_range_subtype(anycompatible_range_typeid);
+					if (!OidIsValid(anycompatible_range_typelem))
+						return false;	/* not a range type */
+				}
+				/* collect the subtype for common-supertype choice */
+				anycompatible_actual_types[n_anycompatible_args++] =
+					anycompatible_range_typelem;
+			}
+		}
 	}
 
 	/* Get the element type based on the array type, if we have one */
@@ -1760,8 +1820,6 @@ check_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))
 	{
-		Oid			range_typelem;
-
 		range_typelem = get_range_subtype(range_typeid);
 		if (!OidIsValid(range_typelem))
 			return false;		/* should be a range, but isn't */
@@ -1780,6 +1838,45 @@ 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))
+	{
+		Oid			multirange_typelem;
+
+		multirange_typelem = get_multirange_range(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 */
+		}
+		else if (multirange_typelem != range_typeid)
+		{
+			/* otherwise, they better match */
+			return false;
+		}
+
+		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 */
@@ -1818,8 +1915,10 @@ check_generic_type_consistency(const Oid *actual_arg_types,
 		}
 
 		/*
-		 * the anycompatible type must exactly match the range element type,
-		 * if we were able to identify one
+		 * The anycompatible type must exactly match the range element type,
+		 * if we were able to identify one. This checks compatibility for
+		 * anycompatiblemultirange too since that also sets
+		 * anycompatible_range_typelem above.
 		 */
 		if (OidIsValid(anycompatible_range_typelem) &&
 			anycompatible_range_typelem != anycompatible_typeid)
@@ -1858,21 +1957,27 @@ check_generic_type_consistency(const Oid *actual_arg_types,
  *	  argument's actual type as the function's return type.
  * 2) If return type is ANYARRAY, and any argument is ANYARRAY, use the
  *	  argument's actual type as the function's return type.
- * 3) Similarly, if return type is ANYRANGE, and any argument is ANYRANGE,
- *	  use the argument's actual type as the function's return type.
- * 4) Otherwise, if return type is ANYELEMENT or ANYARRAY, and there is
+ * 3) Similarly, if return type is ANYRANGE or ANYMULTIRANGE, and any
+ *	  argument is ANYRANGE or ANYMULTIRANGE, use that argument's
+ *	  actual type, range type or multirange type as the function's return
+ *	  type.
+ * 4) Otherwise, if return type is ANYMULTIRANGE, and any argument is
+ *	  ANYMULTIRANGE, use the argument's actual type as the function's return
+ *	  type. Or if any argument is ANYRANGE, use its multirange type as the
+ *	  function's return type.
+ * 5) Otherwise, if return type is ANYELEMENT or ANYARRAY, and there is
  *	  at least one ANYELEMENT, ANYARRAY, or ANYRANGE input, deduce the
  *	  return type from those inputs, or throw error if we can't.
- * 5) Otherwise, if return type is ANYRANGE, throw error.  (We have no way to
- *	  select a specific range type if the arguments don't include ANYRANGE.)
- * 6) ANYENUM is treated the same as ANYELEMENT except that if it is used
+ * 6) Otherwise, if return type is ANYRANGE or ANYMULTIRANGE, throw error.
+ *	  (We have no way to select a specific range type if the arguments don't
+ *	  include ANYRANGE.)
  *	  (alone or in combination with plain ANYELEMENT), we add the extra
  *	  condition that the ANYELEMENT type must be an enum.
- * 7) ANYNONARRAY is treated the same as ANYELEMENT except that if it is used,
+ * 8) ANYNONARRAY is treated the same as ANYELEMENT except that if it is used,
  *	  we add the extra condition that the ANYELEMENT type must not be an array.
  *	  (This is a no-op if used in combination with ANYARRAY or ANYENUM, but
  *	  is an extra restriction if not.)
- * 8) ANYCOMPATIBLE, ANYCOMPATIBLEARRAY, ANYCOMPATIBLENONARRAY, and
+ * 9) ANYCOMPATIBLE, ANYCOMPATIBLEARRAY, ANYCOMPATIBLENONARRAY, and
  *	  ANYCOMPATIBLERANGE are handled by resolving the common supertype
  *	  of those arguments (or their element types/subtypes, for array and range
  *	  inputs), and then coercing all those arguments to the common supertype,
@@ -1926,10 +2031,15 @@ 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			anycompatible_typeid = InvalidOid;
 	Oid			anycompatible_array_typeid = InvalidOid;
 	Oid			anycompatible_range_typeid = InvalidOid;
 	Oid			anycompatible_range_typelem = InvalidOid;
+	Oid			anycompatible_multirange_typeid = InvalidOid;
+	Oid			anycompatible_multirange_typelem = InvalidOid;
+	Oid			range_typelem;
+	Oid			multirange_typelem;
 	bool		have_anynonarray = (rettype == ANYNONARRAYOID);
 	bool		have_anyenum = (rettype == ANYENUMOID);
 	bool		have_anycompatible_nonarray = (rettype == ANYCOMPATIBLENONARRAYOID);
@@ -2014,6 +2124,26 @@ enforce_generic_type_consistency(const Oid *actual_arg_types,
 								   format_type_be(actual_type))));
 			range_typeid = actual_type;
 		}
+		else if (decl_type == ANYMULTIRANGEOID)
+		{
+			n_poly_args++;
+			if (actual_type == UNKNOWNOID)
+			{
+				have_poly_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;
+		}
 		else if (decl_type == ANYCOMPATIBLEOID ||
 				 decl_type == ANYCOMPATIBLENONARRAYOID)
 		{
@@ -2082,6 +2212,40 @@ enforce_generic_type_consistency(const Oid *actual_arg_types,
 				anycompatible_actual_types[n_anycompatible_args++] = anycompatible_range_typelem;
 			}
 		}
+		else if (decl_type == ANYCOMPATIBLEMULTIRANGEOID)
+		{
+			have_poly_anycompatible = true;
+			if (actual_type == UNKNOWNOID)
+				continue;
+			if (allow_poly && decl_type == actual_type)
+				continue;		/* no new information here */
+			actual_type = getBaseType(actual_type); /* flatten domains */
+			if (OidIsValid(anycompatible_multirange_typeid))
+			{
+				/* All ANYCOMPATIBLEMULTIRANGE arguments must be the same type */
+				if (anycompatible_multirange_typeid != actual_type)
+					ereport(ERROR,
+							(errcode(ERRCODE_DATATYPE_MISMATCH),
+							 errmsg("arguments declared \"anycompatiblemultirange\" are not all alike"),
+							 errdetail("%s versus %s",
+									   format_type_be(anycompatible_multirange_typeid),
+									   format_type_be(actual_type))));
+			}
+			else
+			{
+				anycompatible_multirange_typeid = actual_type;
+				anycompatible_multirange_typelem = get_multirange_range(actual_type);
+				anycompatible_range_typelem = get_range_subtype(anycompatible_multirange_typelem);
+				if (!OidIsValid(anycompatible_multirange_typelem))
+					ereport(ERROR,
+							(errcode(ERRCODE_DATATYPE_MISMATCH),
+							 errmsg("argument declared %s is not a multirange type but type %s",
+									"anycompatiblemultirange",
+									format_type_be(actual_type))));
+				/* collect the subtype for common-supertype choice */
+				anycompatible_actual_types[n_anycompatible_args++] = anycompatible_range_typelem;
+			}
+		}
 	}
 
 	/*
@@ -2150,8 +2314,6 @@ 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))
 		{
-			Oid			range_typelem;
-
 			range_typelem = get_range_subtype(range_typeid);
 			if (!OidIsValid(range_typelem))
 				ereport(ERROR,
@@ -2180,6 +2342,61 @@ enforce_generic_type_consistency(const Oid *actual_arg_types,
 								   format_type_be(elem_typeid))));
 			}
 		}
+		else
+			range_typelem = InvalidOid;
+
+		/* Get the element type based on the multirange type, if we have one */
+		if (OidIsValid(multirange_typeid))
+		{
+			multirange_typelem = get_multirange_range(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))));
+			}
+
+			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 */
+				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))));
+			}
+		}
+		else
+			multirange_typelem = InvalidOid;
 
 		if (!OidIsValid(elem_typeid))
 		{
@@ -2188,6 +2405,7 @@ enforce_generic_type_consistency(const Oid *actual_arg_types,
 				elem_typeid = ANYELEMENTOID;
 				array_typeid = ANYARRAYOID;
 				range_typeid = ANYRANGEOID;
+				multirange_typeid = ANYMULTIRANGEOID;
 			}
 			else
 			{
@@ -2287,6 +2505,7 @@ enforce_generic_type_consistency(const Oid *actual_arg_types,
 				anycompatible_typeid = ANYCOMPATIBLEOID;
 				anycompatible_array_typeid = ANYCOMPATIBLEARRAYOID;
 				anycompatible_range_typeid = ANYCOMPATIBLERANGEOID;
+				anycompatible_multirange_typeid = ANYCOMPATIBLEMULTIRANGEOID;
 			}
 			else
 			{
@@ -2318,6 +2537,8 @@ enforce_generic_type_consistency(const Oid *actual_arg_types,
 				declared_arg_types[j] = anycompatible_array_typeid;
 			else if (decl_type == ANYCOMPATIBLERANGEOID)
 				declared_arg_types[j] = anycompatible_range_typeid;
+			else if (decl_type == ANYCOMPATIBLEMULTIRANGEOID)
+				declared_arg_types[j] = anycompatible_multirange_typeid;
 		}
 	}
 
@@ -2368,6 +2589,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;
+			}
 		}
 	}
 
@@ -2404,6 +2636,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 ANYCOMPATIBLE use the appropriate type */
 	if (rettype == ANYCOMPATIBLEOID ||
 		rettype == ANYCOMPATIBLENONARRAYOID)
@@ -2438,6 +2686,17 @@ enforce_generic_type_consistency(const Oid *actual_arg_types,
 		return anycompatible_range_typeid;
 	}
 
+	/* if we return ANYCOMPATIBLEMULTIRANGE use the appropriate argument type */
+	if (rettype == ANYCOMPATIBLEMULTIRANGEOID)
+	{
+		/* this error is unreachable if the function signature is valid: */
+		if (!OidIsValid(anycompatible_multirange_typeid))
+			ereport(ERROR,
+					(errcode(ERRCODE_DATATYPE_MISMATCH),
+					 errmsg_internal("could not identify anycompatiblemultirange type")));
+		return anycompatible_multirange_typeid;
+	}
+
 	/* we don't return a generic type; send back the original return type */
 	return rettype;
 }
@@ -2455,20 +2714,38 @@ check_valid_polymorphic_signature(Oid ret_type,
 								  const Oid *declared_arg_types,
 								  int nargs)
 {
-	if (ret_type == ANYRANGEOID || ret_type == ANYCOMPATIBLERANGEOID)
+	if (ret_type == ANYRANGEOID || ret_type == ANYMULTIRANGEOID)
 	{
 		/*
-		 * ANYRANGE requires an ANYRANGE input, else we can't tell which of
-		 * several range types with the same element type to use.  Likewise
-		 * for ANYCOMPATIBLERANGE.
+		 * ANYRANGE and ANYMULTIRANGE require an ANYRANGE or ANYMULTIRANGE
+		 * input, else we can't tell which of several range types with the
+		 * same element type to use.
 		 */
 		for (int i = 0; i < nargs; i++)
 		{
-			if (declared_arg_types[i] == ret_type)
+			if (declared_arg_types[i] == ANYRANGEOID ||
+				declared_arg_types[i] == ANYMULTIRANGEOID)
 				return NULL;	/* OK */
 		}
-		return psprintf(_("A result of type %s requires at least one input of type %s."),
-						format_type_be(ret_type), format_type_be(ret_type));
+		return psprintf(_("A result of type %s requires at least one input of type anyrange or anymultirange."),
+						format_type_be(ret_type));
+	}
+	else if (ret_type == ANYCOMPATIBLERANGEOID || ret_type == ANYCOMPATIBLEMULTIRANGEOID)
+	{
+		/*
+		 * ANYCOMPATIBLERANGE and ANYCOMPATIBLEMULTIRANGE require an
+		 * ANYCOMPATIBLERANGE or ANYCOMPATIBLEMULTIRANGE input, else we can't
+		 * tell which of several range types with the same element type to
+		 * use.
+		 */
+		for (int i = 0; i < nargs; i++)
+		{
+			if (declared_arg_types[i] == ANYCOMPATIBLERANGEOID ||
+				declared_arg_types[i] == ANYCOMPATIBLEMULTIRANGEOID)
+				return NULL;	/* OK */
+		}
+		return psprintf(_("A result of type %s requires at least one input of type anycompatiblerange or anycompatiblemultirange."),
+						format_type_be(ret_type));
 	}
 	else if (IsPolymorphicTypeFamily1(ret_type))
 	{
@@ -2479,7 +2756,7 @@ check_valid_polymorphic_signature(Oid ret_type,
 				return NULL;	/* OK */
 		}
 		/* Keep this list in sync with IsPolymorphicTypeFamily1! */
-		return psprintf(_("A result of type %s requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange."),
+		return psprintf(_("A result of type %s requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange."),
 						format_type_be(ret_type));
 	}
 	else if (IsPolymorphicTypeFamily2(ret_type))
@@ -2631,6 +2908,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 || targettype == ANYCOMPATIBLEMULTIRANGEOID)
+		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 b4d55e849b3..22a412d4379 100644
--- a/src/backend/utils/adt/Makefile
+++ b/src/backend/utils/adt/Makefile
@@ -59,6 +59,8 @@ OBJS = \
 	mac8.o \
 	mcxtfuncs.o \
 	misc.o \
+	multirangetypes.o \
+	multirangetypes_selfuncs.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 00000000000..d5bc44a7bf5
--- /dev/null
+++ b/src/backend/utils/adt/multirangetypes.c
@@ -0,0 +1,2531 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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/int8.h"
+#include "utils/lsyscache.h"
+#include "utils/rangetypes.h"
+#include "utils/multirangetypes.h"
+#include "utils/timestamp.h"
+#include "utils/array.h"
+#include "utils/memutils.h"
+
+/* fn_extra cache entry for one of the range I/O functions */
+typedef struct MultirangeIOData
+{
+	TypeCacheEntry *typcache;	/* multirange type's typcache entry */
+	FmgrInfo	typioproc;		/* range type's I/O proc */
+	Oid			typioparam;		/* range type's I/O parameter */
+} 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 mltrngtypid,
+												IOFuncSelector func);
+static int32 multirange_canonicalize(TypeCacheEntry *rangetyp, int32 input_range_count,
+									 RangeType **ranges);
+static void shortrange_deserialize(TypeCacheEntry *typcache,
+								   const ShortRangeType *range,
+								   RangeBound *lower, RangeBound *upper, bool *empty);
+static void shortrange_serialize(ShortRangeType *range, TypeCacheEntry *typcache,
+								 RangeBound *lower, RangeBound *upper, bool empty);
+
+/* "Methods" required for an expanded object */
+static Size EMR_get_flat_size(ExpandedObjectHeader *eohptr);
+static void EMR_flatten_into(ExpandedObjectHeader *eohptr,
+							 void *result, Size allocated_size);
+
+static const ExpandedObjectMethods EMR_methods =
+{
+	EMR_get_flat_size,
+	EMR_flatten_into
+};
+
+/*
+ *----------------------------------------------------------
+ * 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 with 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;
+	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 = NULL;
+	int32		range_str_len;
+	char	   *range_str_copy;
+
+	cache = get_multirange_io_data(fcinfo, mltrngtypoid, IOFunc_input);
+	rangetyp = cache->typcache->rngtype;
+
+	/* 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 + 1;
+					range_str_copy = pnstrdup(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->typioproc,
+																 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;
+	int32		range_count;
+	int32		i;
+	RangeType **ranges;
+
+	cache = get_multirange_io_data(fcinfo, mltrngtypoid, IOFunc_output);
+
+	initStringInfo(&buf);
+
+	appendStringInfoChar(&buf, '{');
+
+	multirange_deserialize(cache->typcache->rngtype, multirange, &range_count, &ranges);
+	for (i = 0; i < range_count; i++)
+	{
+		if (i > 0)
+			appendStringInfoChar(&buf, ',');
+		range = ranges[i];
+		rangeStr = OutputFunctionCall(&cache->typioproc, RangeTypePGetDatum(range));
+		appendStringInfoString(&buf, rangeStr);
+	}
+
+	appendStringInfoChar(&buf, '}');
+
+	PG_RETURN_CSTRING(buf.data);
+}
+
+/*
+ * Binary representation: First a int32-sized count of ranges, followed by
+ * ranges in their native binary representation.
+ */
+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;
+	uint32		range_count;
+	RangeType **ranges;
+	MultirangeType *ret;
+	StringInfoData tmpbuf;
+
+	cache = get_multirange_io_data(fcinfo, mltrngtypoid, IOFunc_receive);
+
+	range_count = pq_getmsgint(buf, 4);
+	ranges = palloc(range_count * sizeof(RangeType *));
+
+	initStringInfo(&tmpbuf);
+	for (int i = 0; i < range_count; i++)
+	{
+		uint32		range_len = pq_getmsgint(buf, 4);
+		const char *range_data = pq_getmsgbytes(buf, range_len);
+
+		resetStringInfo(&tmpbuf);
+		appendBinaryStringInfo(&tmpbuf, range_data, range_len);
+
+		ranges[i] = DatumGetRangeTypeP(ReceiveFunctionCall(&cache->typioproc,
+														   &tmpbuf,
+														   cache->typioparam,
+														   typmod));
+	}
+	pfree(tmpbuf.data);
+
+	pq_getmsgend(buf);
+
+	ret = make_multirange(mltrngtypoid, cache->typcache->rngtype,
+						  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;
+	MultirangeIOData *cache;
+
+	cache = get_multirange_io_data(fcinfo, mltrngtypoid, IOFunc_send);
+
+	/* construct output */
+	pq_begintypsend(buf);
+
+	pq_sendint32(buf, multirange->rangeCount);
+
+	multirange_deserialize(cache->typcache->rngtype, multirange, &range_count, &ranges);
+	for (int i = 0; i < range_count; i++)
+	{
+		Datum		range;
+
+		range = RangeTypePGetDatum(ranges[i]);
+		range = PointerGetDatum(SendFunctionCall(&cache->typioproc, range));
+
+		pq_sendint32(buf, VARSIZE(range) - VARHDRSZ);
+		pq_sendbytes(buf, VARDATA(range), VARSIZE(range) - VARHDRSZ);
+	}
+
+	PG_RETURN_BYTEA_P(pq_endtypsend(buf));
+}
+
+/*
+ * 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)
+	{
+		Oid			typiofunc;
+		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,
+						 &typiofunc);
+
+		if (!OidIsValid(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(typiofunc, &cache->typioproc,
+					  fcinfo->flinfo->fn_mcxt);
+
+		fcinfo->flinfo->fn_extra = (void *) cache;
+	}
+
+	return cache;
+}
+
+/*
+ * Converts a list of arbitrary ranges 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;
+}
+
+/*
+ *----------------------------------------------------------
+ * 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;
+	int			i;
+	int32		bytelen;
+	Pointer		ptr;
+	RangeBound	lower;
+	RangeBound	upper;
+	bool		empty;
+
+	/* 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 ShortRangeTypes start aligned.
+	 */
+	bytelen = MAXALIGN(sizeof(MultirangeType));
+
+	/* Count space for all ranges */
+	for (i = 0; i < range_count; i++)
+		bytelen += MAXALIGN(VARSIZE(ranges[i]) - sizeof(char));
+
+	/* 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_deserialize(rangetyp, ranges[i], &lower, &upper, &empty);
+		shortrange_serialize((ShortRangeType *) ptr, rangetyp, &lower, &upper, empty);
+		ptr += MAXALIGN(VARSIZE(ptr));
+	}
+
+	return multirange;
+}
+
+/*
+ * 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(TypeCacheEntry *rangetyp,
+					   const MultirangeType *multirange, int32 *range_count,
+					   RangeType ***ranges)
+{
+	ExpandedMultirangeHeader *emrh = (ExpandedMultirangeHeader *)
+	DatumGetEOHP(expand_multirange(MultirangeTypePGetDatum(multirange),
+								   CurrentMemoryContext, rangetyp));
+
+	*range_count = emrh->rangeCount;
+	if (*range_count == 0)
+	{
+		*ranges = NULL;
+		return;
+	}
+
+	*ranges = emrh->ranges;
+}
+
+MultirangeType *
+make_empty_multirange(Oid mltrngtypoid, TypeCacheEntry *rangetyp)
+{
+	return make_multirange(mltrngtypoid, rangetyp, 0, NULL);
+}
+
+/*
+ *----------------------------------------------------------
+ * EXPANDED TOAST FUNCTIONS
+ *----------------------------------------------------------
+ */
+
+/*
+ * expand_multirange: convert a multirange Datum into an expanded multirange
+ *
+ * The expanded object will be a child of parentcontext.
+ */
+Datum
+expand_multirange(Datum multirangedatum, MemoryContext parentcontext, TypeCacheEntry *rangetyp)
+{
+	MultirangeType *multirange;
+	ExpandedMultirangeHeader *emrh;
+	MemoryContext objcxt;
+	MemoryContext oldcxt;
+
+	/*
+	 * Allocate private context for expanded object.  We start by assuming
+	 * that the multirange won't be very large; but if it does grow a lot,
+	 * don't constrain aset.c's large-context behavior.
+	 */
+	objcxt = AllocSetContextCreate(parentcontext,
+								   "expanded multirange",
+								   ALLOCSET_START_SMALL_SIZES);
+
+	/* Set up expanded multirange header */
+	emrh = (ExpandedMultirangeHeader *)
+		MemoryContextAlloc(objcxt, sizeof(ExpandedMultirangeHeader));
+
+	EOH_init_header(&emrh->hdr, &EMR_methods, objcxt);
+	emrh->emr_magic = EMR_MAGIC;
+
+	/*
+	 * Detoast and copy source multirange into private context. We need to do
+	 * this so that we get our own copies of the upper/lower bounds.
+	 *
+	 * Note that this coding risks leaking some memory in the private context
+	 * if we have to fetch data from a TOAST table; however, experimentation
+	 * says that the leak is minimal.  Doing it this way saves a copy step,
+	 * which seems worthwhile, especially if the multirange is large enough to
+	 * need external storage.
+	 */
+	oldcxt = MemoryContextSwitchTo(objcxt);
+	multirange = DatumGetMultirangeTypePCopy(multirangedatum);
+
+	emrh->multirangetypid = multirange->multirangetypid;
+	emrh->rangeCount = multirange->rangeCount;
+
+	/* Convert each ShortRangeType into a RangeType */
+	if (emrh->rangeCount > 0)
+	{
+		Pointer		ptr;
+		Pointer		end;
+		int32		i;
+
+		emrh->ranges = palloc(emrh->rangeCount * sizeof(RangeType *));
+
+		ptr = (char *) multirange;
+		end = ptr + VARSIZE(multirange);
+		ptr = (char *) MAXALIGN(multirange + 1);
+		i = 0;
+		while (ptr < end)
+		{
+			ShortRangeType *shortrange;
+			RangeBound	lower;
+			RangeBound	upper;
+			bool		empty;
+
+			shortrange = (ShortRangeType *) ptr;
+			shortrange_deserialize(rangetyp, shortrange, &lower, &upper, &empty);
+			emrh->ranges[i++] = make_range(rangetyp, &lower, &upper, empty);
+
+			ptr += MAXALIGN(VARSIZE(shortrange));
+		}
+	}
+	else
+	{
+		emrh->ranges = NULL;
+	}
+	MemoryContextSwitchTo(oldcxt);
+
+	/* return a R/W pointer to the expanded multirange */
+	return EOHPGetRWDatum(&emrh->hdr);
+}
+
+/*
+ * shortrange_deserialize: Extract bounds and empty flag from a ShortRangeType
+ */
+void
+shortrange_deserialize(TypeCacheEntry *typcache, const ShortRangeType *range,
+					   RangeBound *lower, RangeBound *upper, bool *empty)
+{
+	char		flags;
+	int16		typlen;
+	bool		typbyval;
+	char		typalign;
+	Pointer		ptr;
+	Datum		lbound;
+	Datum		ubound;
+
+	/* fetch the flag byte */
+	flags = range->flags;
+
+	/* fetch information about range's element type */
+	typlen = typcache->rngelemtype->typlen;
+	typbyval = typcache->rngelemtype->typbyval;
+	typalign = typcache->rngelemtype->typalign;
+
+	/* initialize data pointer just after the range struct fields */
+	ptr = (Pointer) MAXALIGN(range + 1);
+
+	/* fetch lower bound, if any */
+	if (RANGE_HAS_LBOUND(flags))
+	{
+		/* att_align_pointer cannot be necessary here */
+		lbound = fetch_att(ptr, typbyval, typlen);
+		ptr = (Pointer) att_addlength_pointer(ptr, typlen, ptr);
+	}
+	else
+		lbound = (Datum) 0;
+
+	/* fetch upper bound, if any */
+	if (RANGE_HAS_UBOUND(flags))
+	{
+		ptr = (Pointer) att_align_pointer(ptr, typalign, typlen, ptr);
+		ubound = fetch_att(ptr, typbyval, typlen);
+		/* no need for att_addlength_pointer */
+	}
+	else
+		ubound = (Datum) 0;
+
+	/* emit results */
+
+	*empty = (flags & RANGE_EMPTY) != 0;
+
+	lower->val = lbound;
+	lower->infinite = (flags & RANGE_LB_INF) != 0;
+	lower->inclusive = (flags & RANGE_LB_INC) != 0;
+	lower->lower = true;
+
+	upper->val = ubound;
+	upper->infinite = (flags & RANGE_UB_INF) != 0;
+	upper->inclusive = (flags & RANGE_UB_INC) != 0;
+	upper->lower = false;
+}
+
+/*
+ * get_flat_size method for expanded multirange
+ */
+static Size
+EMR_get_flat_size(ExpandedObjectHeader *eohptr)
+{
+	ExpandedMultirangeHeader *emrh = (ExpandedMultirangeHeader *) eohptr;
+	RangeType  *range;
+	Size		nbytes;
+	int			i;
+
+	Assert(emrh->emr_magic == EMR_MAGIC);
+
+	/* If we have a cached size value, believe that */
+	if (emrh->flat_size)
+		return emrh->flat_size;
+
+	/*
+	 * Compute space needed by examining ranges.
+	 */
+	nbytes = MAXALIGN(sizeof(MultirangeType));
+	for (i = 0; i < emrh->rangeCount; i++)
+	{
+		range = emrh->ranges[i];
+		nbytes += MAXALIGN(VARSIZE(range) - sizeof(char));
+		/* check for overflow of total request */
+		if (!AllocSizeIsValid(nbytes))
+			ereport(ERROR,
+					(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
+					 errmsg("multirange size exceeds the maximum allowed (%d)",
+							(int) MaxAllocSize)));
+	}
+
+	/* cache for next time */
+	emrh->flat_size = nbytes;
+
+	return nbytes;
+}
+
+/*
+ * flatten_into method for expanded multiranges
+ */
+static void
+EMR_flatten_into(ExpandedObjectHeader *eohptr,
+				 void *result, Size allocated_size)
+{
+	ExpandedMultirangeHeader *emrh = (ExpandedMultirangeHeader *) eohptr;
+	MultirangeType *mrresult = (MultirangeType *) result;
+	TypeCacheEntry *typcache;
+	int			rangeCount;
+	RangeType **ranges;
+	RangeBound	lower;
+	RangeBound	upper;
+	bool		empty;
+	Pointer		ptr;
+	int			i;
+
+	Assert(emrh->emr_magic == EMR_MAGIC);
+
+	typcache = lookup_type_cache(emrh->multirangetypid, TYPECACHE_MULTIRANGE_INFO);
+	if (typcache->rngtype == NULL)
+		elog(ERROR, "type %u is not a multirange type", emrh->multirangetypid);
+
+	/* Fill result multirange from RangeTypes */
+	rangeCount = emrh->rangeCount;
+	ranges = emrh->ranges;
+
+	/* We must ensure that any pad space is zero-filled */
+	memset(mrresult, 0, allocated_size);
+
+	SET_VARSIZE(mrresult, allocated_size);
+
+	/* Now fill in the datum with ShortRangeTypes */
+	mrresult->multirangetypid = emrh->multirangetypid;
+	mrresult->rangeCount = rangeCount;
+
+	ptr = (char *) MAXALIGN(mrresult + 1);
+	for (i = 0; i < rangeCount; i++)
+	{
+		range_deserialize(typcache->rngtype, ranges[i], &lower, &upper, &empty);
+		shortrange_serialize((ShortRangeType *) ptr, typcache->rngtype, &lower, &upper, empty);
+		ptr += MAXALIGN(VARSIZE(ptr));
+	}
+}
+
+/*
+ * shortrange_serialize: fill in pre-allocationed range param based on the
+ * given bounds and flags.
+ */
+static void
+shortrange_serialize(ShortRangeType *range, TypeCacheEntry *typcache,
+					 RangeBound *lower, RangeBound *upper, bool empty)
+{
+	int			cmp;
+	Size		msize;
+	Pointer		ptr;
+	int16		typlen;
+	bool		typbyval;
+	char		typalign;
+	char		typstorage;
+	char		flags = 0;
+
+	/*
+	 * Verify range is not invalid on its face, and construct flags value,
+	 * preventing any non-canonical combinations such as infinite+inclusive.
+	 */
+	Assert(lower->lower);
+	Assert(!upper->lower);
+
+	if (empty)
+		flags |= RANGE_EMPTY;
+	else
+	{
+		cmp = range_cmp_bound_values(typcache, lower, upper);
+
+		/* error check: if lower bound value is above upper, it's wrong */
+		if (cmp > 0)
+			ereport(ERROR,
+					(errcode(ERRCODE_DATA_EXCEPTION),
+					 errmsg("range lower bound must be less than or equal to range upper bound")));
+
+		/* if bounds are equal, and not both inclusive, range is empty */
+		if (cmp == 0 && !(lower->inclusive && upper->inclusive))
+			flags |= RANGE_EMPTY;
+		else
+		{
+			/* infinite boundaries are never inclusive */
+			if (lower->infinite)
+				flags |= RANGE_LB_INF;
+			else if (lower->inclusive)
+				flags |= RANGE_LB_INC;
+			if (upper->infinite)
+				flags |= RANGE_UB_INF;
+			else if (upper->inclusive)
+				flags |= RANGE_UB_INC;
+		}
+	}
+
+	/* Fetch information about range's element type */
+	typlen = typcache->rngelemtype->typlen;
+	typbyval = typcache->rngelemtype->typbyval;
+	typalign = typcache->rngelemtype->typalign;
+	typstorage = typcache->rngelemtype->typstorage;
+
+	/* Count space for varlena header */
+	msize = sizeof(ShortRangeType);
+	Assert(msize == MAXALIGN(msize));
+
+	/* Count space for bounds */
+	if (RANGE_HAS_LBOUND(flags))
+	{
+		/*
+		 * Make sure item to be inserted is not toasted.  It is essential that
+		 * we not insert an out-of-line toast value pointer into a range
+		 * object, for the same reasons that arrays and records can't contain
+		 * them.  It would work to store a compressed-in-line value, but we
+		 * prefer to decompress and then let compression be applied to the
+		 * whole range object if necessary.  But, unlike arrays, we do allow
+		 * short-header varlena objects to stay as-is.
+		 */
+		if (typlen == -1)
+			lower->val = PointerGetDatum(PG_DETOAST_DATUM_PACKED(lower->val));
+
+		msize = datum_compute_size(msize, lower->val, typbyval, typalign,
+								   typlen, typstorage);
+	}
+
+	if (RANGE_HAS_UBOUND(flags))
+	{
+		/* Make sure item to be inserted is not toasted */
+		if (typlen == -1)
+			upper->val = PointerGetDatum(PG_DETOAST_DATUM_PACKED(upper->val));
+
+		msize = datum_compute_size(msize, upper->val, typbyval, typalign,
+								   typlen, typstorage);
+	}
+
+	SET_VARSIZE(range, msize);
+
+	ptr = (char *) (range + 1);
+
+	if (RANGE_HAS_LBOUND(flags))
+	{
+		Assert(lower->lower);
+		ptr = datum_write(ptr, lower->val, typbyval, typalign, typlen,
+						  typstorage);
+	}
+
+	if (RANGE_HAS_UBOUND(flags))
+	{
+		Assert(!upper->lower);
+		ptr = datum_write(ptr, upper->val, typbyval, typalign, typlen,
+						  typstorage);
+	}
+
+	range->flags = flags;
+}
+
+
+/*
+ *----------------------------------------------------------
+ * 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 ranges
+ * that match our return value, and there must be no NULLs.
+ */
+Datum
+multirange_constructor2(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,
+				(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+				 errmsg("multirange values cannot contain NULL members")));
+
+	rangeArray = PG_GETARG_ARRAYTYPE_P(0);
+
+	dims = ARR_NDIM(rangeArray);
+	if (dims > 1)
+		ereport(ERROR,
+				(errcode(ERRCODE_CARDINALITY_VIOLATION),
+				 errmsg("multiranges cannot be constructed from multi-dimensional arrays")));
+
+	rngtypid = ARR_ELEMTYPE(rangeArray);
+	if (rngtypid != rangetyp->type_id)
+		ereport(ERROR,
+				(errcode(ERRCODE_DATATYPE_MISMATCH),
+				 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,
+						(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+						 errmsg("multirange values cannot contain NULL members")));
+
+			/* make_multirange will do its own copy */
+			ranges[i] = DatumGetRangeTypeP(elements[i]);
+		}
+	}
+
+	PG_RETURN_MULTIRANGE_P(make_multirange(mltrngtypid, rangetyp, range_count, ranges));
+}
+
+/*
+ * Construct multirange value from a single range.  It'd be nice if we could
+ * just use multirange_constructor2 for this case, but we need a non-variadic
+ * single-arg function to let us define a CAST from a range to its multirange.
+ */
+Datum
+multirange_constructor1(PG_FUNCTION_ARGS)
+{
+	Oid			mltrngtypid = get_fn_expr_rettype(fcinfo->flinfo);
+	Oid			rngtypid;
+	TypeCacheEntry *typcache;
+	TypeCacheEntry *rangetyp;
+	RangeType  *range;
+
+	typcache = multirange_get_typcache(fcinfo, mltrngtypid);
+	rangetyp = typcache->rngtype;
+
+	/*
+	 * These checks should be guaranteed by our signature, but let's do them
+	 * just in case.
+	 */
+
+	if (PG_ARGISNULL(0))
+		ereport(ERROR,
+				(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+				 errmsg("multirange values cannot contain NULL members")));
+
+	range = PG_GETARG_RANGE_P(0);
+
+	/* Make sure the range type matches. */
+	rngtypid = RangeTypeGetOid(range);
+	if (rngtypid != rangetyp->type_id)
+		ereport(ERROR,
+				(errcode(ERRCODE_DATATYPE_MISMATCH),
+				 errmsg("type %u does not match constructor type", rngtypid)));
+
+	PG_RETURN_MULTIRANGE_P(make_multirange(mltrngtypid, rangetyp, 1, &range));
+}
+
+/*
+ * 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
+		elog(ERROR,				/* can't happen */
+			 "niladic multirange constructor must not receive arguments");
+}
+
+
+/* multirange, multirange -> multirange type functions */
+
+/* multirange union */
+Datum
+multirange_union(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
+	MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count1;
+	int32		range_count2;
+	int32		range_count3;
+	RangeType **ranges1;
+	RangeType **ranges2;
+	RangeType **ranges3;
+
+	if (MultirangeIsEmpty(mr1))
+		PG_RETURN_MULTIRANGE_P(mr2);
+	if (MultirangeIsEmpty(mr2))
+		PG_RETURN_MULTIRANGE_P(mr1);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr1));
+
+	multirange_deserialize(typcache->rngtype, mr1, &range_count1, &ranges1);
+	multirange_deserialize(typcache->rngtype, mr2, &range_count2, &ranges2);
+
+	range_count3 = range_count1 + range_count2;
+	ranges3 = palloc0(range_count3 * sizeof(RangeType *));
+	memcpy(ranges3, ranges1, range_count1 * sizeof(RangeType *));
+	memcpy(ranges3 + range_count1, ranges2, range_count2 * sizeof(RangeType *));
+	PG_RETURN_MULTIRANGE_P(make_multirange(typcache->type_id, typcache->rngtype,
+										   range_count3, ranges3));
+}
+
+/* multirange minus */
+Datum
+multirange_minus(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
+	MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
+	Oid			mltrngtypoid = MultirangeTypeGetOid(mr1);
+	TypeCacheEntry *typcache;
+	TypeCacheEntry *rangetyp;
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+
+	typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
+	rangetyp = typcache->rngtype;
+
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		PG_RETURN_MULTIRANGE_P(mr1);
+
+	multirange_deserialize(typcache->rngtype, mr1, &range_count1, &ranges1);
+	multirange_deserialize(typcache->rngtype, mr2, &range_count2, &ranges2);
+
+	PG_RETURN_MULTIRANGE_P(multirange_minus_internal(mltrngtypoid,
+													 rangetyp,
+													 range_count1,
+													 ranges1,
+													 range_count2,
+													 ranges2));
+}
+
+MultirangeType *
+multirange_minus_internal(Oid mltrngtypoid, TypeCacheEntry *rangetyp,
+						  int32 range_count1, RangeType **ranges1,
+						  int32 range_count2, RangeType **ranges2)
+{
+	RangeType  *r1;
+	RangeType  *r2;
+	RangeType **ranges3;
+	int32		range_count3;
+	int32		i1;
+	int32		i2;
+
+	/*
+	 * Worst case: every range in ranges1 makes a different cut to some range
+	 * in ranges2.
+	 */
+	ranges3 = palloc0((range_count1 + range_count2) * sizeof(RangeType *));
+	range_count3 = 0;
+
+	/*
+	 * For each range in mr1, keep subtracting until it's gone or the ranges
+	 * in mr2 have passed it. After a subtraction we assign what's left back
+	 * to r1. The parallel progress through mr1 and mr2 is similar to
+	 * multirange_overlaps_multirange_internal.
+	 */
+	r2 = ranges2[0];
+	for (i1 = 0, i2 = 0; i1 < range_count1; i1++)
+	{
+		r1 = ranges1[i1];
+
+		/* Discard r2s while r2 << r1 */
+		while (r2 != NULL && range_before_internal(rangetyp, r2, r1))
+		{
+			r2 = ++i2 >= range_count2 ? NULL : ranges2[i2];
+		}
+
+		while (r2 != NULL)
+		{
+			if (range_split_internal(rangetyp, r1, r2, &ranges3[range_count3], &r1))
+			{
+				/*
+				 * If r2 takes a bite out of the middle of r1, we need two
+				 * outputs
+				 */
+				range_count3++;
+				r2 = ++i2 >= range_count2 ? NULL : ranges2[i2];
+
+			}
+			else if (range_overlaps_internal(rangetyp, r1, r2))
+			{
+				/*
+				 * If r2 overlaps r1, replace r1 with r1 - r2.
+				 */
+				r1 = range_minus_internal(rangetyp, r1, r2);
+
+				/*
+				 * If r2 goes past r1, then we need to stay with it, in case
+				 * it hits future r1s. Otherwise we need to keep r1, in case
+				 * future r2s hit it. Since we already subtracted, there's no
+				 * point in using the overright/overleft calls.
+				 */
+				if (RangeIsEmpty(r1) || range_before_internal(rangetyp, r1, r2))
+					break;
+				else
+					r2 = ++i2 >= range_count2 ? NULL : ranges2[i2];
+
+			}
+			else
+			{
+				/*
+				 * This and all future r2s are past r1, so keep them. Also
+				 * assign whatever is left of r1 to the result.
+				 */
+				break;
+			}
+		}
+
+		/*
+		 * Nothing else can remove anything from r1, so keep it. Even if r1 is
+		 * empty here, make_multirange will remove it.
+		 */
+		ranges3[range_count3++] = r1;
+	}
+
+	return make_multirange(mltrngtypoid, rangetyp, range_count3, ranges3);
+}
+
+/* multirange intersection */
+Datum
+multirange_intersect(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
+	MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
+	Oid			mltrngtypoid = MultirangeTypeGetOid(mr1);
+	TypeCacheEntry *typcache;
+	TypeCacheEntry *rangetyp;
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+
+	typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
+	rangetyp = typcache->rngtype;
+
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		PG_RETURN_MULTIRANGE_P(make_empty_multirange(mltrngtypoid, rangetyp));
+
+	multirange_deserialize(rangetyp, mr1, &range_count1, &ranges1);
+	multirange_deserialize(rangetyp, mr2, &range_count2, &ranges2);
+
+	PG_RETURN_MULTIRANGE_P(multirange_intersect_internal(mltrngtypoid,
+														 rangetyp,
+														 range_count1,
+														 ranges1,
+														 range_count2,
+														 ranges2));
+}
+
+MultirangeType *
+multirange_intersect_internal(Oid mltrngtypoid, TypeCacheEntry *rangetyp,
+							  int32 range_count1, RangeType **ranges1,
+							  int32 range_count2, RangeType **ranges2)
+{
+	RangeType  *r1;
+	RangeType  *r2;
+	RangeType **ranges3;
+	int32		range_count3;
+	int32		i1;
+	int32		i2;
+
+	if (range_count1 == 0 || range_count2 == 0)
+		return make_multirange(mltrngtypoid, rangetyp, 0, NULL);
+
+	/*-----------------------------------------------
+	 * Worst case is a stitching pattern like this:
+	 *
+	 * mr1: --- --- --- ---
+	 * mr2:   --- --- ---
+	 * mr3:   - - - - - -
+	 *
+	 * That seems to be range_count1 + range_count2 - 1,
+	 * but one extra won't hurt.
+	 *-----------------------------------------------
+	 */
+	ranges3 = palloc0((range_count1 + range_count2) * sizeof(RangeType *));
+	range_count3 = 0;
+
+	/*
+	 * For each range in mr1, keep intersecting until the ranges in mr2 have
+	 * passed it. The parallel progress through mr1 and mr2 is similar to
+	 * multirange_minus_multirange_internal, but we don't have to assign back
+	 * to r1.
+	 */
+	r2 = ranges2[0];
+	for (i1 = 0, i2 = 0; i1 < range_count1; i1++)
+	{
+		r1 = ranges1[i1];
+
+		/* Discard r2s while r2 << r1 */
+		while (r2 != NULL && range_before_internal(rangetyp, r2, r1))
+		{
+			r2 = ++i2 >= range_count2 ? NULL : ranges2[i2];
+		}
+
+		while (r2 != NULL)
+		{
+			if (range_overlaps_internal(rangetyp, r1, r2))
+			{
+				/* Keep the overlapping part */
+				ranges3[range_count3++] = range_intersect_internal(rangetyp, r1, r2);
+
+				/* If we "used up" all of r2, go to the next one... */
+				if (range_overleft_internal(rangetyp, r2, r1))
+					r2 = ++i2 >= range_count2 ? NULL : ranges2[i2];
+
+				/* ...otherwise go to the next r1 */
+				else
+					break;
+			}
+			else
+				/* We're past r1, so move to the next one */
+				break;
+		}
+
+		/* If we're out of r2s, there can be no more intersections */
+		if (r2 == NULL)
+			break;
+	}
+
+	return make_multirange(mltrngtypoid, rangetyp, range_count3, ranges3);
+}
+
+/*
+ * range_agg_transfn: combine adjacent/overlapping ranges.
+ *
+ * All we do here is gather the input ranges into an array
+ * so that the finalfn can sort and combine them.
+ */
+Datum
+range_agg_transfn(PG_FUNCTION_ARGS)
+{
+	MemoryContext aggContext;
+	Oid			rngtypoid;
+	ArrayBuildState *state;
+
+	if (!AggCheckCallContext(fcinfo, &aggContext))
+		elog(ERROR, "range_agg_transfn called in non-aggregate context");
+
+	rngtypoid = get_fn_expr_argtype(fcinfo->flinfo, 1);
+	if (!type_is_range(rngtypoid))
+		ereport(ERROR,
+				(errcode(ERRCODE_DATATYPE_MISMATCH),
+				 errmsg("range_agg must be called with a range")));
+
+	if (PG_ARGISNULL(0))
+		state = initArrayResult(rngtypoid, aggContext, false);
+	else
+		state = (ArrayBuildState *) PG_GETARG_POINTER(0);
+
+	/* skip NULLs */
+	if (!PG_ARGISNULL(1))
+		accumArrayResult(state, PG_GETARG_DATUM(1), false, rngtypoid, aggContext);
+
+	PG_RETURN_POINTER(state);
+}
+
+/*
+ * range_agg_finalfn: use our internal array to merge touching ranges.
+ */
+Datum
+range_agg_finalfn(PG_FUNCTION_ARGS)
+{
+	MemoryContext aggContext;
+	Oid			mltrngtypoid;
+	TypeCacheEntry *typcache;
+	ArrayBuildState *state;
+	int32		range_count;
+	RangeType **ranges;
+	int			i;
+
+	if (!AggCheckCallContext(fcinfo, &aggContext))
+		elog(ERROR, "range_agg_finalfn called in non-aggregate context");
+
+	state = PG_ARGISNULL(0) ? NULL : (ArrayBuildState *) PG_GETARG_POINTER(0);
+	if (state == NULL)
+		/* This shouldn't be possible, but just in case.... */
+		PG_RETURN_NULL();
+
+	/* Also return NULL if we had zero inputs, like other aggregates */
+	range_count = state->nelems;
+	if (range_count == 0)
+		PG_RETURN_NULL();
+
+	mltrngtypoid = get_fn_expr_rettype(fcinfo->flinfo);
+	typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
+
+	ranges = palloc0(range_count * sizeof(RangeType *));
+	for (i = 0; i < range_count; i++)
+		ranges[i] = DatumGetRangeTypeP(state->dvalues[i]);
+
+	PG_RETURN_MULTIRANGE_P(make_multirange(mltrngtypoid, typcache->rngtype, range_count, ranges));
+}
+
+Datum
+multirange_intersect_agg_transfn(PG_FUNCTION_ARGS)
+{
+	MemoryContext aggContext;
+	Oid			mltrngtypoid;
+	TypeCacheEntry *typcache;
+	MultirangeType *result;
+	MultirangeType *current;
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+
+	if (!AggCheckCallContext(fcinfo, &aggContext))
+		elog(ERROR, "multirange_intersect_agg_transfn called in non-aggregate context");
+
+	mltrngtypoid = get_fn_expr_argtype(fcinfo->flinfo, 1);
+	if (!type_is_multirange(mltrngtypoid))
+		ereport(ERROR,
+				(errcode(ERRCODE_DATATYPE_MISMATCH),
+				 errmsg("range_intersect_agg must be called with a multirange")));
+
+	typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
+
+	/* strictness ensures these are non-null */
+	result = PG_GETARG_MULTIRANGE_P(0);
+	current = PG_GETARG_MULTIRANGE_P(1);
+
+	multirange_deserialize(typcache->rngtype, result, &range_count1, &ranges1);
+	multirange_deserialize(typcache->rngtype, current, &range_count2, &ranges2);
+
+	result = multirange_intersect_internal(mltrngtypoid,
+										   typcache->rngtype,
+										   range_count1,
+										   ranges1,
+										   range_count2,
+										   ranges2);
+	PG_RETURN_RANGE_P(result);
+}
+
+
+/* multirange -> element type functions */
+
+/* extract lower bound value */
+Datum
+multirange_lower(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+	bool		isnull;
+	Datum		result;
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_NULL();
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(typcache->rngtype, mr, &range_count, &ranges);
+
+	result = range_lower_internal(typcache->rngtype, ranges[0], &isnull);
+
+	if (isnull)
+		PG_RETURN_NULL();
+	else
+		PG_RETURN_DATUM(result);
+}
+
+/* extract upper bound value */
+Datum
+multirange_upper(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+	bool		isnull;
+	Datum		result;
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_NULL();
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(typcache->rngtype, mr, &range_count, &ranges);
+
+	result = range_upper_internal(typcache->rngtype, ranges[range_count - 1], &isnull);
+
+	if (isnull)
+		PG_RETURN_NULL();
+	else
+		PG_RETURN_DATUM(result);
+}
+
+
+/* multirange -> bool functions */
+
+/* is multirange empty? */
+Datum
+multirange_empty(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+
+	PG_RETURN_BOOL(mr->rangeCount == 0);
+}
+
+/* is lower bound inclusive? */
+Datum
+multirange_lower_inc(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+	multirange_deserialize(typcache->rngtype, mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_has_flag(ranges[0], RANGE_LB_INC));
+}
+
+/* is upper bound inclusive? */
+Datum
+multirange_upper_inc(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+	multirange_deserialize(typcache->rngtype, mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_has_flag(ranges[range_count - 1], RANGE_UB_INC));
+}
+
+/* is lower bound infinite? */
+Datum
+multirange_lower_inf(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+	multirange_deserialize(typcache->rngtype, mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_has_flag(ranges[0], RANGE_LB_INF));
+}
+
+/* is upper bound infinite? */
+Datum
+multirange_upper_inf(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+	multirange_deserialize(typcache->rngtype, mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_has_flag(ranges[range_count - 1], RANGE_UB_INF));
+}
+
+
+
+/* multirange, element -> bool functions */
+
+/* contains? */
+Datum
+multirange_contains_elem(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	Datum		val = PG_GETARG_DATUM(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(multirange_contains_elem_internal(typcache, mr, val));
+}
+
+/* contained by? */
+Datum
+elem_contained_by_multirange(PG_FUNCTION_ARGS)
+{
+	Datum		val = PG_GETARG_DATUM(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(multirange_contains_elem_internal(typcache, mr, val));
+}
+
+/*
+ * Test whether multirange mr contains a specific element value.
+ */
+bool
+multirange_contains_elem_internal(TypeCacheEntry *typcache, MultirangeType *mr, Datum val)
+{
+	TypeCacheEntry *rangetyp;
+	int32		range_count;
+	RangeType **ranges;
+	RangeType  *r;
+	int			i;
+
+	rangetyp = typcache->rngtype;
+
+	multirange_deserialize(rangetyp, mr, &range_count, &ranges);
+
+	for (i = 0; i < range_count; i++)
+	{
+		r = ranges[i];
+		if (range_contains_elem_internal(rangetyp, r, val))
+			return true;
+	}
+
+	return false;
+}
+
+/* multirange, range -> bool functions */
+
+/* contains? */
+Datum
+multirange_contains_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(multirange_contains_range_internal(typcache, mr, r));
+}
+
+/* contained by? */
+Datum
+range_contained_by_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(multirange_contains_range_internal(typcache, mr, r));
+}
+
+/*
+ * Test whether multirange mr contains a specific range r.
+ */
+bool
+multirange_contains_range_internal(TypeCacheEntry *typcache, MultirangeType *mr, RangeType *r)
+{
+	TypeCacheEntry *rangetyp;
+	int32		range_count;
+	RangeType **ranges;
+	RangeType  *mrr;
+	int			i;
+
+	rangetyp = typcache->rngtype;
+
+	/*
+	 * Every multirange contains an infinite number of empty ranges, even an
+	 * empty one.
+	 */
+	if (RangeIsEmpty(r))
+		return true;
+
+	multirange_deserialize(rangetyp, mr, &range_count, &ranges);
+
+	for (i = 0; i < range_count; i++)
+	{
+		mrr = ranges[i];
+		if (range_contains_internal(rangetyp, mrr, r))
+			return true;
+	}
+
+	return false;
+}
+
+
+/* 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(typcache->rngtype, mr1, &range_count_1, &ranges1);
+	multirange_deserialize(typcache->rngtype, 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));
+}
+
+/* overlaps? */
+Datum
+range_overlaps_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(range_overlaps_multirange_internal(typcache, r, mr));
+}
+
+Datum
+multirange_overlaps_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(range_overlaps_multirange_internal(typcache, r, mr));
+}
+
+Datum
+multirange_overlaps_multirange(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_overlaps_multirange_internal(typcache, mr1, mr2));
+}
+
+bool
+range_overlaps_multirange_internal(TypeCacheEntry *typcache, RangeType *r, MultirangeType *mr)
+{
+	TypeCacheEntry *rangetyp;
+	int32		range_count;
+	int32		i;
+	RangeType **ranges;
+	RangeType  *mrr;
+
+	/*
+	 * Empties never overlap, even with empties. (This seems strange since
+	 * they *do* contain each other, but we want to follow how ranges work.)
+	 */
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		return false;
+
+	rangetyp = typcache->rngtype;
+
+	multirange_deserialize(rangetyp, mr, &range_count, &ranges);
+
+	/* Scan through mrr and once it gets to r they either overlap or not */
+	mrr = ranges[0];
+
+	/* Discard mrrs while mrr << r */
+	i = 0;
+	while (range_before_internal(rangetyp, mrr, r))
+	{
+		if (++i >= range_count)
+			return false;
+		mrr = ranges[i];
+	}
+
+	/* Now either we overlap or we passed r */
+	return range_overlaps_internal(rangetyp, mrr, r);
+}
+
+bool
+multirange_overlaps_multirange_internal(TypeCacheEntry *typcache, MultirangeType *mr1,
+										MultirangeType *mr2)
+{
+	TypeCacheEntry *rangetyp;
+	int32		range_count1;
+	int32		range_count2;
+	int32		i1;
+	int32		i2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+	RangeType  *r1;
+	RangeType  *r2;
+
+	/*
+	 * Empties never overlap, even with empties. (This seems strange since
+	 * they *do* contain each other, but we want to follow how ranges work.)
+	 */
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		return false;
+
+	rangetyp = typcache->rngtype;
+
+	multirange_deserialize(rangetyp, mr1, &range_count1, &ranges1);
+	multirange_deserialize(rangetyp, mr2, &range_count2, &ranges2);
+
+	/*
+	 * Every range in mr1 gets a chance to overlap with the ranges in mr2, but
+	 * we can use their ordering to avoid O(n^2). This is similar to
+	 * range_overlaps_multirange where r1 : r2 :: mrr : r, but there if we
+	 * don't find an overlap with r we're done, and here if we don't find an
+	 * overlap with r2 we try the next r2.
+	 */
+	r1 = ranges1[0];
+	for (i1 = 0, i2 = 0; i2 < range_count2; i2++)
+	{
+		r2 = ranges2[i2];
+
+		/* Discard r1s while r1 << r2 */
+		while (range_before_internal(rangetyp, r1, r2))
+		{
+			if (++i1 >= range_count1)
+				return false;
+			r1 = ranges1[i1];
+		}
+
+		/*
+		 * If r1 && r2, we're done, otherwise we failed to find an overlap for
+		 * r2, so go to the next one.
+		 */
+		if (range_overlaps_internal(rangetyp, r1, r2))
+			return true;
+	}
+
+	/* We looked through all of mr2 without finding an overlap */
+	return false;
+}
+
+/* does not extend to right of? */
+Datum
+range_overleft_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(typcache->rngtype, mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_overleft_internal(typcache->rngtype, r, ranges[range_count - 1]));
+}
+
+Datum
+multirange_overleft_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(typcache->rngtype, mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_overleft_internal(typcache->rngtype,
+										   ranges[range_count - 1], r));
+}
+
+Datum
+multirange_overleft_multirange(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
+	MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr1));
+
+	multirange_deserialize(typcache->rngtype, mr1, &range_count1, &ranges1);
+	multirange_deserialize(typcache->rngtype, mr2, &range_count2, &ranges2);
+
+	PG_RETURN_BOOL(range_overleft_internal(typcache->rngtype,
+										   ranges1[range_count1 - 1],
+										   ranges2[range_count2 - 1]));
+}
+
+/* does not extend to left of? */
+Datum
+range_overright_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(typcache->rngtype, mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_overright_internal(typcache->rngtype, r, ranges[0]));
+}
+
+Datum
+multirange_overright_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(typcache->rngtype, mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_overright_internal(typcache->rngtype, ranges[0], r));
+}
+
+Datum
+multirange_overright_multirange(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
+	MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr1));
+
+	multirange_deserialize(typcache->rngtype, mr1, &range_count1, &ranges1);
+	multirange_deserialize(typcache->rngtype, mr2, &range_count2, &ranges2);
+
+	PG_RETURN_BOOL(range_overright_internal(typcache->rngtype,
+											ranges1[0],
+											ranges2[0]));
+}
+
+/* contains? */
+Datum
+multirange_contains_multirange(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_contains_multirange_internal(typcache, mr1, mr2));
+}
+
+/* contained by? */
+Datum
+multirange_contained_by_multirange(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_contains_multirange_internal(typcache, mr2, mr1));
+}
+
+/*
+ * Test whether multirange mr1 contains every range from another multirange mr2.
+ */
+bool
+multirange_contains_multirange_internal(TypeCacheEntry *typcache,
+										MultirangeType *mr1, MultirangeType *mr2)
+{
+	TypeCacheEntry *rangetyp;
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+	RangeType  *r1;
+	RangeType  *r2;
+	int			i1,
+				i2;
+
+	rangetyp = typcache->rngtype;
+
+	multirange_deserialize(typcache->rngtype, mr1, &range_count1, &ranges1);
+	multirange_deserialize(typcache->rngtype, mr2, &range_count2, &ranges2);
+
+	/*
+	 * We follow the same logic for empties as ranges: - an empty multirange
+	 * contains an empty range/multirange. - an empty multirange can't contain
+	 * any other range/multirange. - an empty multirange is contained by any
+	 * other range/multirange.
+	 */
+
+	if (range_count2 == 0)
+		return true;
+	if (range_count1 == 0)
+		return false;
+
+	/*
+	 * Every range in mr2 must be contained by some range in mr1. To avoid
+	 * O(n^2) we walk through both ranges in tandem.
+	 */
+	r1 = ranges1[0];
+	for (i1 = 0, i2 = 0; i2 < range_count2; i2++)
+	{
+		r2 = ranges2[i2];
+
+		/* Discard r1s while r1 << r2 */
+		while (range_before_internal(rangetyp, r1, r2))
+		{
+			if (++i1 >= range_count1)
+				return false;
+			r1 = ranges1[i1];
+		}
+
+		/*
+		 * If r1 @> r2, go to the next r2, otherwise return false (since every
+		 * r1[n] and r1[n+1] must have a gap). Note this will give weird
+		 * answers if you don't canonicalize, e.g. with a custom
+		 * int2multirange {[1,1], [2,2]} there is a "gap". But that is
+		 * consistent with other range operators, e.g. '[1,1]'::int2range -|-
+		 * '[2,2]'::int2range is false.
+		 */
+		if (!range_contains_internal(rangetyp, r1, r2))
+			return false;
+	}
+
+	/* All ranges in mr2 are satisfied */
+	return true;
+}
+
+/* strictly left of? */
+Datum
+range_before_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(range_before_multirange_internal(typcache, r, mr));
+}
+
+Datum
+multirange_before_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(range_after_multirange_internal(typcache, r, mr));
+}
+
+Datum
+multirange_before_multirange(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_before_multirange_internal(typcache, mr1, mr2));
+}
+
+/* strictly right of? */
+Datum
+range_after_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(range_after_multirange_internal(typcache, r, mr));
+}
+
+Datum
+multirange_after_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(range_before_multirange_internal(typcache, r, mr));
+}
+
+Datum
+multirange_after_multirange(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_before_multirange_internal(typcache, mr2, mr1));
+}
+
+/* strictly left of? (internal version) */
+bool
+range_before_multirange_internal(TypeCacheEntry *typcache, RangeType *r,
+								 MultirangeType *mr)
+{
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		return false;
+
+	multirange_deserialize(typcache->rngtype, mr, &range_count, &ranges);
+
+	return range_before_internal(typcache->rngtype, r, ranges[0]);
+}
+
+bool
+multirange_before_multirange_internal(TypeCacheEntry *typcache, MultirangeType *mr1,
+									  MultirangeType *mr2)
+{
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		return false;
+
+	multirange_deserialize(typcache->rngtype, mr1, &range_count1, &ranges1);
+	multirange_deserialize(typcache->rngtype, mr2, &range_count2, &ranges2);
+
+	return range_before_internal(typcache->rngtype, ranges1[range_count1 - 1],
+								 ranges2[0]);
+}
+
+/* strictly right of? (internal version) */
+bool
+range_after_multirange_internal(TypeCacheEntry *typcache, RangeType *r,
+								MultirangeType *mr)
+{
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		return false;
+
+	multirange_deserialize(typcache->rngtype, mr, &range_count, &ranges);
+
+	return range_after_internal(typcache->rngtype, r, ranges[range_count - 1]);
+}
+
+/* adjacent to? */
+Datum
+range_adjacent_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		return false;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(typcache->rngtype, mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_adjacent_internal(typcache->rngtype, r, ranges[0]));
+}
+
+Datum
+multirange_adjacent_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		return false;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_deserialize(typcache->rngtype, mr, &range_count, &ranges);
+
+	PG_RETURN_BOOL(range_adjacent_internal(typcache->rngtype, ranges[range_count - 1], r));
+}
+
+Datum
+multirange_adjacent_multirange(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
+	MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		return false;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr1));
+
+	multirange_deserialize(typcache->rngtype, mr1, &range_count1, &ranges1);
+	multirange_deserialize(typcache->rngtype, mr2, &range_count2, &ranges2);
+
+	PG_RETURN_BOOL(range_adjacent_internal(typcache->rngtype, ranges1[range_count1 - 1], ranges2[0]));
+}
+
+/* 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(typcache->rngtype, mr1, &range_count_1, &ranges1);
+	multirange_deserialize(typcache->rngtype, 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);
+}
+
+/* multirange -> range functions */
+
+/* Find the smallest range that includes everything in the multirange */
+Datum
+range_merge_from_multirange(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	Oid			mltrngtypoid = MultirangeTypeGetOid(mr);
+	TypeCacheEntry *typcache;
+	int32		range_count;
+	RangeType **ranges;
+
+	typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_RANGE_P(make_empty_range(typcache->rngtype));
+
+	multirange_deserialize(typcache->rngtype, mr, &range_count, &ranges);
+
+	PG_RETURN_RANGE_P(range_union_internal(typcache->rngtype, ranges[0],
+										   ranges[range_count - 1], false));
+}
+
+/* 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(typcache->rngtype, 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(typcache->rngtype, 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/multirangetypes_selfuncs.c b/src/backend/utils/adt/multirangetypes_selfuncs.c
new file mode 100644
index 00000000000..b1e56841ccd
--- /dev/null
+++ b/src/backend/utils/adt/multirangetypes_selfuncs.c
@@ -0,0 +1,1307 @@
+/*-------------------------------------------------------------------------
+ *
+ * multirangetypes_selfuncs.c
+ *	  Functions for selectivity estimation of multirange operators
+ *
+ * Estimates are based on histograms of lower and upper bounds, and the
+ * fraction of empty multiranges.
+ *
+ * Portions Copyright (c) 1996-2020, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ *	  src/backend/utils/adt/multirangetypes_selfuncs.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include <math.h>
+
+#include "access/htup_details.h"
+#include "catalog/pg_operator.h"
+#include "catalog/pg_statistic.h"
+#include "catalog/pg_type.h"
+#include "utils/float.h"
+#include "utils/fmgrprotos.h"
+#include "utils/lsyscache.h"
+#include "utils/rangetypes.h"
+#include "utils/multirangetypes.h"
+#include "utils/selfuncs.h"
+#include "utils/typcache.h"
+
+static double calc_multirangesel(TypeCacheEntry *typcache, VariableStatData *vardata,
+								 const MultirangeType *constval, Oid operator);
+static double default_multirange_selectivity(Oid operator);
+static double default_multirange_selectivity(Oid operator);
+static double calc_hist_selectivity(TypeCacheEntry *typcache,
+									VariableStatData *vardata, const MultirangeType *constval,
+									Oid operator);
+static double calc_hist_selectivity_scalar(TypeCacheEntry *typcache,
+										   const RangeBound *constbound,
+										   const RangeBound *hist, int hist_nvalues,
+										   bool equal);
+static int	rbound_bsearch(TypeCacheEntry *typcache, const RangeBound *value,
+						   const RangeBound *hist, int hist_length, bool equal);
+static float8 get_position(TypeCacheEntry *typcache, const RangeBound *value,
+						   const RangeBound *hist1, const RangeBound *hist2);
+static float8 get_len_position(double value, double hist1, double hist2);
+static float8 get_distance(TypeCacheEntry *typcache, const RangeBound *bound1,
+						   const RangeBound *bound2);
+static int	length_hist_bsearch(Datum *length_hist_values,
+								int length_hist_nvalues, double value, bool equal);
+static double calc_length_hist_frac(Datum *length_hist_values,
+									int length_hist_nvalues, double length1, double length2, bool equal);
+static double calc_hist_selectivity_contained(TypeCacheEntry *typcache,
+											  const RangeBound *lower, RangeBound *upper,
+											  const RangeBound *hist_lower, int hist_nvalues,
+											  Datum *length_hist_values, int length_hist_nvalues);
+static double calc_hist_selectivity_contains(TypeCacheEntry *typcache,
+											 const RangeBound *lower, const RangeBound *upper,
+											 const RangeBound *hist_lower, int hist_nvalues,
+											 Datum *length_hist_values, int length_hist_nvalues);
+
+/*
+ * Returns a default selectivity estimate for given operator, when we don't
+ * have statistics or cannot use them for some reason.
+ */
+static double
+default_multirange_selectivity(Oid operator)
+{
+	switch (operator)
+	{
+		case OID_MULTIRANGE_OVERLAPS_MULTIRANGE_OP:
+		case OID_MULTIRANGE_OVERLAPS_RANGE_OP:
+		case OID_RANGE_OVERLAPS_MULTIRANGE_OP:
+			return 0.01;
+
+		case OID_MULTIRANGE_CONTAINS_RANGE_OP:
+		case OID_MULTIRANGE_CONTAINS_MULTIRANGE_OP:
+		case OID_MULTIRANGE_RANGE_CONTAINED_OP:
+		case OID_MULTIRANGE_MULTIRANGE_CONTAINED_OP:
+			return 0.005;
+
+		case OID_MULTIRANGE_CONTAINS_ELEM_OP:
+		case OID_MULTIRANGE_ELEM_CONTAINED_OP:
+
+			/*
+			 * "multirange @> elem" is more or less identical to a scalar
+			 * inequality "A >= b AND A <= c".
+			 */
+			return DEFAULT_MULTIRANGE_INEQ_SEL;
+
+		case OID_MULTIRANGE_LESS_OP:
+		case OID_MULTIRANGE_LESS_EQUAL_OP:
+		case OID_MULTIRANGE_GREATER_OP:
+		case OID_MULTIRANGE_GREATER_EQUAL_OP:
+		case OID_MULTIRANGE_LEFT_RANGE_OP:
+		case OID_MULTIRANGE_LEFT_MULTIRANGE_OP:
+		case OID_RANGE_LEFT_MULTIRANGE_OP:
+		case OID_MULTIRANGE_RIGHT_RANGE_OP:
+		case OID_MULTIRANGE_RIGHT_MULTIRANGE_OP:
+		case OID_RANGE_RIGHT_MULTIRANGE_OP:
+		case OID_MULTIRANGE_OVERLAPS_LEFT_RANGE_OP:
+		case OID_RANGE_OVERLAPS_LEFT_MULTIRANGE_OP:
+		case OID_MULTIRANGE_OVERLAPS_LEFT_MULTIRANGE_OP:
+		case OID_MULTIRANGE_OVERLAPS_RIGHT_RANGE_OP:
+		case OID_RANGE_OVERLAPS_RIGHT_MULTIRANGE_OP:
+		case OID_MULTIRANGE_OVERLAPS_RIGHT_MULTIRANGE_OP:
+			/* these are similar to regular scalar inequalities */
+			return DEFAULT_INEQ_SEL;
+
+		default:
+
+			/*
+			 * all multirange operators should be handled above, but just in
+			 * case
+			 */
+			return 0.01;
+	}
+}
+
+/*
+ * multirangesel -- restriction selectivity for multirange operators
+ */
+Datum
+multirangesel(PG_FUNCTION_ARGS)
+{
+	PlannerInfo *root = (PlannerInfo *) PG_GETARG_POINTER(0);
+	Oid			operator = PG_GETARG_OID(1);
+	List	   *args = (List *) PG_GETARG_POINTER(2);
+	int			varRelid = PG_GETARG_INT32(3);
+	VariableStatData vardata;
+	Node	   *other;
+	bool		varonleft;
+	Selectivity selec;
+	TypeCacheEntry *typcache = NULL;
+	MultirangeType *constmultirange = NULL;
+	RangeType  *constrange = NULL;
+
+	/*
+	 * If expression is not (variable op something) or (something op
+	 * variable), then punt and return a default estimate.
+	 */
+	if (!get_restriction_variable(root, args, varRelid,
+								  &vardata, &other, &varonleft))
+		PG_RETURN_FLOAT8(default_multirange_selectivity(operator));
+
+	/*
+	 * Can't do anything useful if the something is not a constant, either.
+	 */
+	if (!IsA(other, Const))
+	{
+		ReleaseVariableStats(vardata);
+		PG_RETURN_FLOAT8(default_multirange_selectivity(operator));
+	}
+
+	/*
+	 * All the multirange operators are strict, so we can cope with a NULL
+	 * constant right away.
+	 */
+	if (((Const *) other)->constisnull)
+	{
+		ReleaseVariableStats(vardata);
+		PG_RETURN_FLOAT8(0.0);
+	}
+
+	/*
+	 * If var is on the right, commute the operator, so that we can assume the
+	 * var is on the left in what follows.
+	 */
+	if (!varonleft)
+	{
+		/* we have other Op var, commute to make var Op other */
+		operator = get_commutator(operator);
+		if (!operator)
+		{
+			/* Use default selectivity (should we raise an error instead?) */
+			ReleaseVariableStats(vardata);
+			PG_RETURN_FLOAT8(default_multirange_selectivity(operator));
+		}
+	}
+
+	/*
+	 * OK, there's a Var and a Const we're dealing with here.  We need the
+	 * Const to be of same multirange type as the column, else we can't do
+	 * anything useful. (Such cases will likely fail at runtime, but here we'd
+	 * rather just return a default estimate.)
+	 *
+	 * If the operator is "multirange @> element", the constant should be of
+	 * the element type of the multirange column. Convert it to a multirange
+	 * that includes only that single point, so that we don't need special
+	 * handling for that in what follows.
+	 */
+	if (operator == OID_MULTIRANGE_CONTAINS_ELEM_OP)
+	{
+		typcache = multirange_get_typcache(fcinfo, vardata.vartype);
+
+		if (((Const *) other)->consttype == typcache->rngtype->rngelemtype->type_id)
+		{
+			RangeBound	lower,
+						upper;
+
+			lower.inclusive = true;
+			lower.val = ((Const *) other)->constvalue;
+			lower.infinite = false;
+			lower.lower = true;
+			upper.inclusive = true;
+			upper.val = ((Const *) other)->constvalue;
+			upper.infinite = false;
+			upper.lower = false;
+			constrange = range_serialize(typcache->rngtype, &lower, &upper, false);
+			constmultirange = make_multirange(typcache->type_id, typcache->rngtype,
+											  1, &constrange);
+		}
+	}
+	else if (operator == OID_MULTIRANGE_CONTAINS_RANGE_OP ||
+			 operator == OID_MULTIRANGE_OVERLAPS_RANGE_OP ||
+			 operator == OID_MULTIRANGE_OVERLAPS_LEFT_RANGE_OP ||
+			 operator == OID_MULTIRANGE_OVERLAPS_RIGHT_RANGE_OP ||
+			 operator == OID_MULTIRANGE_LEFT_RANGE_OP ||
+			 operator == OID_MULTIRANGE_RIGHT_RANGE_OP)
+	{
+		/*
+		 * Promote a range in "multirange OP range" just like we do an element
+		 * in "multirange OP element".
+		 */
+		typcache = multirange_get_typcache(fcinfo, vardata.vartype);
+		if (((Const *) other)->consttype == typcache->rngtype->type_id)
+		{
+			constrange = DatumGetRangeTypeP(((Const *) other)->constvalue);
+			constmultirange = make_multirange(typcache->type_id, typcache->rngtype,
+											  1, &constrange);
+		}
+	}
+	else if (operator == OID_RANGE_OVERLAPS_MULTIRANGE_OP ||
+			 operator == OID_RANGE_OVERLAPS_LEFT_MULTIRANGE_OP ||
+			 operator == OID_RANGE_OVERLAPS_RIGHT_MULTIRANGE_OP ||
+			 operator == OID_RANGE_LEFT_MULTIRANGE_OP ||
+			 operator == OID_RANGE_RIGHT_MULTIRANGE_OP ||
+			 operator == OID_MULTIRANGE_ELEM_CONTAINED_OP ||
+			 operator == OID_MULTIRANGE_RANGE_CONTAINED_OP)
+	{
+		/*
+		 * Here, the Var is the elem/range, not the multirange.  For now we
+		 * just punt and return the default estimate.  In future we could
+		 * disassemble the multirange constant to do something more
+		 * intelligent.
+		 */
+	}
+	else if (((Const *) other)->consttype == vardata.vartype)
+	{
+		/* Both sides are the same multirange type */
+		typcache = multirange_get_typcache(fcinfo, vardata.vartype);
+
+		constmultirange = DatumGetMultirangeTypeP(((Const *) other)->constvalue);
+	}
+
+	/*
+	 * If we got a valid constant on one side of the operator, proceed to
+	 * estimate using statistics. Otherwise punt and return a default constant
+	 * estimate.  Note that calc_multirangesel need not handle
+	 * OID_MULTIRANGE_*_CONTAINED_OP.
+	 */
+	if (constmultirange)
+		selec = calc_multirangesel(typcache, &vardata, constmultirange, operator);
+	else
+		selec = default_multirange_selectivity(operator);
+
+	ReleaseVariableStats(vardata);
+
+	CLAMP_PROBABILITY(selec);
+
+	PG_RETURN_FLOAT8((float8) selec);
+}
+
+static double
+calc_multirangesel(TypeCacheEntry *typcache, VariableStatData *vardata,
+				   const MultirangeType *constval, Oid operator)
+{
+	double		hist_selec;
+	double		selec;
+	float4		empty_frac,
+				null_frac;
+
+	/*
+	 * First look up the fraction of NULLs and empty multiranges from
+	 * pg_statistic.
+	 */
+	if (HeapTupleIsValid(vardata->statsTuple))
+	{
+		Form_pg_statistic stats;
+		AttStatsSlot sslot;
+
+		stats = (Form_pg_statistic) GETSTRUCT(vardata->statsTuple);
+		null_frac = stats->stanullfrac;
+
+		/* Try to get fraction of empty multiranges */
+		if (get_attstatsslot(&sslot, vardata->statsTuple,
+							 STATISTIC_KIND_RANGE_LENGTH_HISTOGRAM,
+							 InvalidOid,
+							 ATTSTATSSLOT_NUMBERS))
+		{
+			if (sslot.nnumbers != 1)
+				elog(ERROR, "invalid empty fraction statistic");	/* shouldn't happen */
+			empty_frac = sslot.numbers[0];
+			free_attstatsslot(&sslot);
+		}
+		else
+		{
+			/* No empty fraction statistic. Assume no empty ranges. */
+			empty_frac = 0.0;
+		}
+	}
+	else
+	{
+		/*
+		 * No stats are available. Follow through the calculations below
+		 * anyway, assuming no NULLs and no empty multiranges. This still
+		 * allows us to give a better-than-nothing estimate based on whether
+		 * the constant is an empty multirange or not.
+		 */
+		null_frac = 0.0;
+		empty_frac = 0.0;
+	}
+
+	if (MultirangeIsEmpty(constval))
+	{
+		/*
+		 * An empty multirange matches all multiranges, all empty multiranges,
+		 * or nothing, depending on the operator
+		 */
+		switch (operator)
+		{
+				/* these return false if either argument is empty */
+			case OID_RANGE_OVERLAPS_MULTIRANGE_OP:
+			case OID_MULTIRANGE_OVERLAPS_RANGE_OP:
+			case OID_MULTIRANGE_OVERLAPS_MULTIRANGE_OP:
+			case OID_RANGE_OVERLAPS_LEFT_MULTIRANGE_OP:
+			case OID_MULTIRANGE_OVERLAPS_LEFT_RANGE_OP:
+			case OID_MULTIRANGE_OVERLAPS_LEFT_MULTIRANGE_OP:
+			case OID_RANGE_OVERLAPS_RIGHT_MULTIRANGE_OP:
+			case OID_MULTIRANGE_OVERLAPS_RIGHT_RANGE_OP:
+			case OID_MULTIRANGE_OVERLAPS_RIGHT_MULTIRANGE_OP:
+			case OID_MULTIRANGE_LEFT_MULTIRANGE_OP:
+			case OID_MULTIRANGE_RIGHT_MULTIRANGE_OP:
+				/* nothing is less than an empty multirange */
+			case OID_MULTIRANGE_LESS_OP:
+				selec = 0.0;
+				break;
+
+				/*
+				 * only empty multiranges can be contained by an empty
+				 * multirange
+				 */
+			case OID_MULTIRANGE_RANGE_CONTAINED_OP:
+			case OID_MULTIRANGE_MULTIRANGE_CONTAINED_OP:
+				/* only empty ranges are <= an empty multirange */
+			case OID_MULTIRANGE_LESS_EQUAL_OP:
+				selec = empty_frac;
+				break;
+
+				/* everything contains an empty multirange */
+			case OID_MULTIRANGE_CONTAINS_RANGE_OP:
+			case OID_MULTIRANGE_CONTAINS_MULTIRANGE_OP:
+				/* everything is >= an empty multirange */
+			case OID_MULTIRANGE_GREATER_EQUAL_OP:
+				selec = 1.0;
+				break;
+
+				/* all non-empty multiranges are > an empty multirange */
+			case OID_MULTIRANGE_GREATER_OP:
+				selec = 1.0 - empty_frac;
+				break;
+
+				/* an element cannot be empty */
+			case OID_MULTIRANGE_ELEM_CONTAINED_OP:
+			case OID_MULTIRANGE_CONTAINS_ELEM_OP:
+			default:
+				elog(ERROR, "unexpected operator %u", operator);
+				selec = 0.0;	/* keep compiler quiet */
+				break;
+		}
+	}
+	else
+	{
+		/*
+		 * Calculate selectivity using bound histograms. If that fails for
+		 * some reason, e.g no histogram in pg_statistic, use the default
+		 * constant estimate for the fraction of non-empty values. This is
+		 * still somewhat better than just returning the default estimate,
+		 * because this still takes into account the fraction of empty and
+		 * NULL tuples, if we had statistics for them.
+		 */
+		hist_selec = calc_hist_selectivity(typcache, vardata, constval,
+										   operator);
+		if (hist_selec < 0.0)
+			hist_selec = default_multirange_selectivity(operator);
+
+		/*
+		 * Now merge the results for the empty multiranges and histogram
+		 * calculations, realizing that the histogram covers only the
+		 * non-null, non-empty values.
+		 */
+		if (operator == OID_MULTIRANGE_ELEM_CONTAINED_OP ||
+			operator == OID_MULTIRANGE_RANGE_CONTAINED_OP ||
+			operator == OID_MULTIRANGE_MULTIRANGE_CONTAINED_OP)
+		{
+			/* empty is contained by anything non-empty */
+			selec = (1.0 - empty_frac) * hist_selec + empty_frac;
+		}
+		else
+		{
+			/* with any other operator, empty Op non-empty matches nothing */
+			selec = (1.0 - empty_frac) * hist_selec;
+		}
+	}
+
+	/* all multirange operators are strict */
+	selec *= (1.0 - null_frac);
+
+	/* result should be in range, but make sure... */
+	CLAMP_PROBABILITY(selec);
+
+	return selec;
+}
+
+/*
+ * Calculate multirange operator selectivity using histograms of multirange bounds.
+ *
+ * This estimate is for the portion of values that are not empty and not
+ * NULL.
+ */
+static double
+calc_hist_selectivity(TypeCacheEntry *typcache, VariableStatData *vardata,
+					  const MultirangeType *constval, Oid operator)
+{
+	TypeCacheEntry *rng_typcache = typcache->rngtype;
+	AttStatsSlot hslot;
+	AttStatsSlot lslot;
+	int			nhist;
+	RangeBound *hist_lower;
+	RangeBound *hist_upper;
+	int			i;
+	RangeBound	const_lower;
+	RangeBound	const_upper;
+	bool		empty;
+	double		hist_selec;
+	int			range_count;
+	RangeType **ranges;
+	RangeType  *constrange;
+
+	/* Can't use the histogram with insecure multirange support functions */
+	if (!statistic_proc_security_check(vardata,
+									   rng_typcache->rng_cmp_proc_finfo.fn_oid))
+		return -1;
+	if (OidIsValid(rng_typcache->rng_subdiff_finfo.fn_oid) &&
+		!statistic_proc_security_check(vardata,
+									   rng_typcache->rng_subdiff_finfo.fn_oid))
+		return -1;
+
+	/* Try to get histogram of ranges */
+	if (!(HeapTupleIsValid(vardata->statsTuple) &&
+		  get_attstatsslot(&hslot, vardata->statsTuple,
+						   STATISTIC_KIND_BOUNDS_HISTOGRAM, InvalidOid,
+						   ATTSTATSSLOT_VALUES)))
+		return -1.0;
+
+	/* check that it's a histogram, not just a dummy entry */
+	if (hslot.nvalues < 2)
+	{
+		free_attstatsslot(&hslot);
+		return -1.0;
+	}
+
+	/*
+	 * Convert histogram of ranges into histograms of its lower and upper
+	 * bounds.
+	 */
+	nhist = hslot.nvalues;
+	hist_lower = (RangeBound *) palloc(sizeof(RangeBound) * nhist);
+	hist_upper = (RangeBound *) palloc(sizeof(RangeBound) * nhist);
+	for (i = 0; i < nhist; i++)
+	{
+		range_deserialize(rng_typcache, DatumGetRangeTypeP(hslot.values[i]),
+						  &hist_lower[i], &hist_upper[i], &empty);
+		/* The histogram should not contain any empty ranges */
+		if (empty)
+			elog(ERROR, "bounds histogram contains an empty range");
+	}
+
+	/* @> and @< also need a histogram of range lengths */
+	if (operator == OID_MULTIRANGE_CONTAINS_RANGE_OP ||
+		operator == OID_MULTIRANGE_CONTAINS_MULTIRANGE_OP ||
+		operator == OID_MULTIRANGE_RANGE_CONTAINED_OP ||
+		operator == OID_MULTIRANGE_MULTIRANGE_CONTAINED_OP)
+	{
+		if (!(HeapTupleIsValid(vardata->statsTuple) &&
+			  get_attstatsslot(&lslot, vardata->statsTuple,
+							   STATISTIC_KIND_RANGE_LENGTH_HISTOGRAM,
+							   InvalidOid,
+							   ATTSTATSSLOT_VALUES)))
+		{
+			free_attstatsslot(&hslot);
+			return -1.0;
+		}
+
+		/* check that it's a histogram, not just a dummy entry */
+		if (lslot.nvalues < 2)
+		{
+			free_attstatsslot(&lslot);
+			free_attstatsslot(&hslot);
+			return -1.0;
+		}
+	}
+	else
+		memset(&lslot, 0, sizeof(lslot));
+
+	/* Extract the bounds of the constant value. */
+	multirange_deserialize(rng_typcache, constval, &range_count, &ranges);
+	Assert(range_count > 0);
+	constrange = range_union_internal(rng_typcache, ranges[0],
+									  ranges[range_count - 1], false);
+	range_deserialize(rng_typcache, constrange, &const_lower, &const_upper, &empty);
+
+	/*
+	 * Calculate selectivity comparing the lower or upper bound of the
+	 * constant with the histogram of lower or upper bounds.
+	 */
+	switch (operator)
+	{
+		case OID_MULTIRANGE_LESS_OP:
+
+			/*
+			 * The regular b-tree comparison operators (<, <=, >, >=) compare
+			 * the lower bounds first, and the upper bounds for values with
+			 * equal lower bounds. Estimate that by comparing the lower bounds
+			 * only. This gives a fairly accurate estimate assuming there
+			 * aren't many rows with a lower bound equal to the constant's
+			 * lower bound.
+			 */
+			hist_selec =
+				calc_hist_selectivity_scalar(rng_typcache, &const_lower,
+											 hist_lower, nhist, false);
+			break;
+
+		case OID_MULTIRANGE_LESS_EQUAL_OP:
+			hist_selec =
+				calc_hist_selectivity_scalar(rng_typcache, &const_lower,
+											 hist_lower, nhist, true);
+			break;
+
+		case OID_MULTIRANGE_GREATER_OP:
+			hist_selec =
+				1 - calc_hist_selectivity_scalar(rng_typcache, &const_lower,
+												 hist_lower, nhist, false);
+			break;
+
+		case OID_MULTIRANGE_GREATER_EQUAL_OP:
+			hist_selec =
+				1 - calc_hist_selectivity_scalar(rng_typcache, &const_lower,
+												 hist_lower, nhist, true);
+			break;
+
+		case OID_RANGE_LEFT_MULTIRANGE_OP:
+		case OID_MULTIRANGE_LEFT_RANGE_OP:
+		case OID_MULTIRANGE_LEFT_MULTIRANGE_OP:
+			/* var << const when upper(var) < lower(const) */
+			hist_selec =
+				calc_hist_selectivity_scalar(rng_typcache, &const_lower,
+											 hist_upper, nhist, false);
+			break;
+
+		case OID_RANGE_RIGHT_MULTIRANGE_OP:
+		case OID_MULTIRANGE_RIGHT_RANGE_OP:
+		case OID_MULTIRANGE_RIGHT_MULTIRANGE_OP:
+			/* var >> const when lower(var) > upper(const) */
+			hist_selec =
+				1 - calc_hist_selectivity_scalar(rng_typcache, &const_upper,
+												 hist_lower, nhist, true);
+			break;
+
+		case OID_RANGE_OVERLAPS_RIGHT_MULTIRANGE_OP:
+		case OID_MULTIRANGE_OVERLAPS_RIGHT_RANGE_OP:
+		case OID_MULTIRANGE_OVERLAPS_RIGHT_MULTIRANGE_OP:
+			/* compare lower bounds */
+			hist_selec =
+				1 - calc_hist_selectivity_scalar(rng_typcache, &const_lower,
+												 hist_lower, nhist, false);
+			break;
+
+		case OID_RANGE_OVERLAPS_LEFT_MULTIRANGE_OP:
+		case OID_MULTIRANGE_OVERLAPS_LEFT_RANGE_OP:
+		case OID_MULTIRANGE_OVERLAPS_LEFT_MULTIRANGE_OP:
+			/* compare upper bounds */
+			hist_selec =
+				calc_hist_selectivity_scalar(rng_typcache, &const_upper,
+											 hist_upper, nhist, true);
+			break;
+
+		case OID_RANGE_OVERLAPS_MULTIRANGE_OP:
+		case OID_MULTIRANGE_OVERLAPS_RANGE_OP:
+		case OID_MULTIRANGE_OVERLAPS_MULTIRANGE_OP:
+		case OID_MULTIRANGE_CONTAINS_ELEM_OP:
+
+			/*
+			 * A && B <=> NOT (A << B OR A >> B).
+			 *
+			 * Since A << B and A >> B are mutually exclusive events we can
+			 * sum their probabilities to find probability of (A << B OR A >>
+			 * B).
+			 *
+			 * "multirange @> elem" is equivalent to "multirange &&
+			 * {[elem,elem]}". The caller already constructed the singular
+			 * range from the element constant, so just treat it the same as
+			 * &&.
+			 */
+			hist_selec =
+				calc_hist_selectivity_scalar(rng_typcache, &const_lower, hist_upper,
+											 nhist, false);
+			hist_selec +=
+				(1.0 - calc_hist_selectivity_scalar(rng_typcache, &const_upper, hist_lower,
+													nhist, true));
+			hist_selec = 1.0 - hist_selec;
+			break;
+
+		case OID_MULTIRANGE_CONTAINS_RANGE_OP:
+		case OID_MULTIRANGE_CONTAINS_MULTIRANGE_OP:
+			hist_selec =
+				calc_hist_selectivity_contains(rng_typcache, &const_lower,
+											   &const_upper, hist_lower, nhist,
+											   lslot.values, lslot.nvalues);
+			break;
+
+		case OID_MULTIRANGE_RANGE_CONTAINED_OP:
+		case OID_MULTIRANGE_MULTIRANGE_CONTAINED_OP:
+			if (const_lower.infinite)
+			{
+				/*
+				 * Lower bound no longer matters. Just estimate the fraction
+				 * with an upper bound <= const upper bound
+				 */
+				hist_selec =
+					calc_hist_selectivity_scalar(rng_typcache, &const_upper,
+												 hist_upper, nhist, true);
+			}
+			else if (const_upper.infinite)
+			{
+				hist_selec =
+					1.0 - calc_hist_selectivity_scalar(rng_typcache, &const_lower,
+													   hist_lower, nhist, false);
+			}
+			else
+			{
+				hist_selec =
+					calc_hist_selectivity_contained(rng_typcache, &const_lower,
+													&const_upper, hist_lower, nhist,
+													lslot.values, lslot.nvalues);
+			}
+			break;
+
+		default:
+			elog(ERROR, "unknown multirange operator %u", operator);
+			hist_selec = -1.0;	/* keep compiler quiet */
+			break;
+	}
+
+	free_attstatsslot(&lslot);
+	free_attstatsslot(&hslot);
+
+	return hist_selec;
+}
+
+
+/*
+ * Look up the fraction of values less than (or equal, if 'equal' argument
+ * is true) a given const in a histogram of range bounds.
+ */
+static double
+calc_hist_selectivity_scalar(TypeCacheEntry *typcache, const RangeBound *constbound,
+							 const RangeBound *hist, int hist_nvalues, bool equal)
+{
+	Selectivity selec;
+	int			index;
+
+	/*
+	 * Find the histogram bin the given constant falls into. Estimate
+	 * selectivity as the number of preceding whole bins.
+	 */
+	index = rbound_bsearch(typcache, constbound, hist, hist_nvalues, equal);
+	selec = (Selectivity) (Max(index, 0)) / (Selectivity) (hist_nvalues - 1);
+
+	/* Adjust using linear interpolation within the bin */
+	if (index >= 0 && index < hist_nvalues - 1)
+		selec += get_position(typcache, constbound, &hist[index],
+							  &hist[index + 1]) / (Selectivity) (hist_nvalues - 1);
+
+	return selec;
+}
+
+/*
+ * Binary search on an array of range bounds. Returns greatest index of range
+ * bound in array which is less(less or equal) than given range bound. If all
+ * range bounds in array are greater or equal(greater) than given range bound,
+ * return -1. When "equal" flag is set conditions in brackets are used.
+ *
+ * This function is used in scalar operator selectivity estimation. Another
+ * goal of this function is to find a histogram bin where to stop
+ * interpolation of portion of bounds which are less or equal to given bound.
+ */
+static int
+rbound_bsearch(TypeCacheEntry *typcache, const RangeBound *value, const RangeBound *hist,
+			   int hist_length, bool equal)
+{
+	int			lower = -1,
+				upper = hist_length - 1,
+				cmp,
+				middle;
+
+	while (lower < upper)
+	{
+		middle = (lower + upper + 1) / 2;
+		cmp = range_cmp_bounds(typcache, &hist[middle], value);
+
+		if (cmp < 0 || (equal && cmp == 0))
+			lower = middle;
+		else
+			upper = middle - 1;
+	}
+	return lower;
+}
+
+
+/*
+ * Binary search on length histogram. Returns greatest index of range length in
+ * histogram which is less than (less than or equal) the given length value. If
+ * all lengths in the histogram are greater than (greater than or equal) the
+ * given length, returns -1.
+ */
+static int
+length_hist_bsearch(Datum *length_hist_values, int length_hist_nvalues,
+					double value, bool equal)
+{
+	int			lower = -1,
+				upper = length_hist_nvalues - 1,
+				middle;
+
+	while (lower < upper)
+	{
+		double		middleval;
+
+		middle = (lower + upper + 1) / 2;
+
+		middleval = DatumGetFloat8(length_hist_values[middle]);
+		if (middleval < value || (equal && middleval <= value))
+			lower = middle;
+		else
+			upper = middle - 1;
+	}
+	return lower;
+}
+
+/*
+ * Get relative position of value in histogram bin in [0,1] range.
+ */
+static float8
+get_position(TypeCacheEntry *typcache, const RangeBound *value, const RangeBound *hist1,
+			 const RangeBound *hist2)
+{
+	bool		has_subdiff = OidIsValid(typcache->rng_subdiff_finfo.fn_oid);
+	float8		position;
+
+	if (!hist1->infinite && !hist2->infinite)
+	{
+		float8		bin_width;
+
+		/*
+		 * Both bounds are finite. Assuming the subtype's comparison function
+		 * works sanely, the value must be finite, too, because it lies
+		 * somewhere between the bounds.  If it doesn't, arbitrarily return
+		 * 0.5.
+		 */
+		if (value->infinite)
+			return 0.5;
+
+		/* Can't interpolate without subdiff function */
+		if (!has_subdiff)
+			return 0.5;
+
+		/* Calculate relative position using subdiff function. */
+		bin_width = DatumGetFloat8(FunctionCall2Coll(&typcache->rng_subdiff_finfo,
+													 typcache->rng_collation,
+													 hist2->val,
+													 hist1->val));
+		if (isnan(bin_width) || bin_width <= 0.0)
+			return 0.5;			/* punt for NaN or zero-width bin */
+
+		position = DatumGetFloat8(FunctionCall2Coll(&typcache->rng_subdiff_finfo,
+													typcache->rng_collation,
+													value->val,
+													hist1->val))
+			/ bin_width;
+
+		if (isnan(position))
+			return 0.5;			/* punt for NaN from subdiff, Inf/Inf, etc */
+
+		/* Relative position must be in [0,1] range */
+		position = Max(position, 0.0);
+		position = Min(position, 1.0);
+		return position;
+	}
+	else if (hist1->infinite && !hist2->infinite)
+	{
+		/*
+		 * Lower bin boundary is -infinite, upper is finite. If the value is
+		 * -infinite, return 0.0 to indicate it's equal to the lower bound.
+		 * Otherwise return 1.0 to indicate it's infinitely far from the lower
+		 * bound.
+		 */
+		return ((value->infinite && value->lower) ? 0.0 : 1.0);
+	}
+	else if (!hist1->infinite && hist2->infinite)
+	{
+		/* same as above, but in reverse */
+		return ((value->infinite && !value->lower) ? 1.0 : 0.0);
+	}
+	else
+	{
+		/*
+		 * If both bin boundaries are infinite, they should be equal to each
+		 * other, and the value should also be infinite and equal to both
+		 * bounds. (But don't Assert that, to avoid crashing if a user creates
+		 * a datatype with a broken comparison function).
+		 *
+		 * Assume the value to lie in the middle of the infinite bounds.
+		 */
+		return 0.5;
+	}
+}
+
+
+/*
+ * Get relative position of value in a length histogram bin in [0,1] range.
+ */
+static double
+get_len_position(double value, double hist1, double hist2)
+{
+	if (!isinf(hist1) && !isinf(hist2))
+	{
+		/*
+		 * Both bounds are finite. The value should be finite too, because it
+		 * lies somewhere between the bounds. If it doesn't, just return
+		 * something.
+		 */
+		if (isinf(value))
+			return 0.5;
+
+		return 1.0 - (hist2 - value) / (hist2 - hist1);
+	}
+	else if (isinf(hist1) && !isinf(hist2))
+	{
+		/*
+		 * Lower bin boundary is -infinite, upper is finite. Return 1.0 to
+		 * indicate the value is infinitely far from the lower bound.
+		 */
+		return 1.0;
+	}
+	else if (isinf(hist1) && isinf(hist2))
+	{
+		/* same as above, but in reverse */
+		return 0.0;
+	}
+	else
+	{
+		/*
+		 * If both bin boundaries are infinite, they should be equal to each
+		 * other, and the value should also be infinite and equal to both
+		 * bounds. (But don't Assert that, to avoid crashing unnecessarily if
+		 * the caller messes up)
+		 *
+		 * Assume the value to lie in the middle of the infinite bounds.
+		 */
+		return 0.5;
+	}
+}
+
+/*
+ * Measure distance between two range bounds.
+ */
+static float8
+get_distance(TypeCacheEntry *typcache, const RangeBound *bound1, const RangeBound *bound2)
+{
+	bool		has_subdiff = OidIsValid(typcache->rng_subdiff_finfo.fn_oid);
+
+	if (!bound1->infinite && !bound2->infinite)
+	{
+		/*
+		 * Neither bound is infinite, use subdiff function or return default
+		 * value of 1.0 if no subdiff is available.
+		 */
+		if (has_subdiff)
+		{
+			float8		res;
+
+			res = DatumGetFloat8(FunctionCall2Coll(&typcache->rng_subdiff_finfo,
+												   typcache->rng_collation,
+												   bound2->val,
+												   bound1->val));
+			/* Reject possible NaN result, also negative result */
+			if (isnan(res) || res < 0.0)
+				return 1.0;
+			else
+				return res;
+		}
+		else
+			return 1.0;
+	}
+	else if (bound1->infinite && bound2->infinite)
+	{
+		/* Both bounds are infinite */
+		if (bound1->lower == bound2->lower)
+			return 0.0;
+		else
+			return get_float8_infinity();
+	}
+	else
+	{
+		/* One bound is infinite, the other is not */
+		return get_float8_infinity();
+	}
+}
+
+/*
+ * Calculate the average of function P(x), in the interval [length1, length2],
+ * where P(x) is the fraction of tuples with length < x (or length <= x if
+ * 'equal' is true).
+ */
+static double
+calc_length_hist_frac(Datum *length_hist_values, int length_hist_nvalues,
+					  double length1, double length2, bool equal)
+{
+	double		frac;
+	double		A,
+				B,
+				PA,
+				PB;
+	double		pos;
+	int			i;
+	double		area;
+
+	Assert(length2 >= length1);
+
+	if (length2 < 0.0)
+		return 0.0;				/* shouldn't happen, but doesn't hurt to check */
+
+	/* All lengths in the table are <= infinite. */
+	if (isinf(length2) && equal)
+		return 1.0;
+
+	/*----------
+	 * The average of a function between A and B can be calculated by the
+	 * formula:
+	 *
+	 *			B
+	 *	  1		/
+	 * -------	| P(x)dx
+	 *	B - A	/
+	 *			A
+	 *
+	 * The geometrical interpretation of the integral is the area under the
+	 * graph of P(x). P(x) is defined by the length histogram. We calculate
+	 * the area in a piecewise fashion, iterating through the length histogram
+	 * bins. Each bin is a trapezoid:
+	 *
+	 *		 P(x2)
+	 *		  /|
+	 *		 / |
+	 * P(x1)/  |
+	 *	   |   |
+	 *	   |   |
+	 *	---+---+--
+	 *	   x1  x2
+	 *
+	 * where x1 and x2 are the boundaries of the current histogram, and P(x1)
+	 * and P(x1) are the cumulative fraction of tuples at the boundaries.
+	 *
+	 * The area of each trapezoid is 1/2 * (P(x2) + P(x1)) * (x2 - x1)
+	 *
+	 * The first bin contains the lower bound passed by the caller, so we
+	 * use linear interpolation between the previous and next histogram bin
+	 * boundary to calculate P(x1). Likewise for the last bin: we use linear
+	 * interpolation to calculate P(x2). For the bins in between, x1 and x2
+	 * lie on histogram bin boundaries, so P(x1) and P(x2) are simply:
+	 * P(x1) =	  (bin index) / (number of bins)
+	 * P(x2) = (bin index + 1 / (number of bins)
+	 */
+
+	/* First bin, the one that contains lower bound */
+	i = length_hist_bsearch(length_hist_values, length_hist_nvalues, length1, equal);
+	if (i >= length_hist_nvalues - 1)
+		return 1.0;
+
+	if (i < 0)
+	{
+		i = 0;
+		pos = 0.0;
+	}
+	else
+	{
+		/* interpolate length1's position in the bin */
+		pos = get_len_position(length1,
+							   DatumGetFloat8(length_hist_values[i]),
+							   DatumGetFloat8(length_hist_values[i + 1]));
+	}
+	PB = (((double) i) + pos) / (double) (length_hist_nvalues - 1);
+	B = length1;
+
+	/*
+	 * In the degenerate case that length1 == length2, simply return
+	 * P(length1). This is not merely an optimization: if length1 == length2,
+	 * we'd divide by zero later on.
+	 */
+	if (length2 == length1)
+		return PB;
+
+	/*
+	 * Loop through all the bins, until we hit the last bin, the one that
+	 * contains the upper bound. (if lower and upper bounds are in the same
+	 * bin, this falls out immediately)
+	 */
+	area = 0.0;
+	for (; i < length_hist_nvalues - 1; i++)
+	{
+		double		bin_upper = DatumGetFloat8(length_hist_values[i + 1]);
+
+		/* check if we've reached the last bin */
+		if (!(bin_upper < length2 || (equal && bin_upper <= length2)))
+			break;
+
+		/* the upper bound of previous bin is the lower bound of this bin */
+		A = B;
+		PA = PB;
+
+		B = bin_upper;
+		PB = (double) i / (double) (length_hist_nvalues - 1);
+
+		/*
+		 * Add the area of this trapezoid to the total. The point of the
+		 * if-check is to avoid NaN, in the corner case that PA == PB == 0,
+		 * and B - A == Inf. The area of a zero-height trapezoid (PA == PB ==
+		 * 0) is zero, regardless of the width (B - A).
+		 */
+		if (PA > 0 || PB > 0)
+			area += 0.5 * (PB + PA) * (B - A);
+	}
+
+	/* Last bin */
+	A = B;
+	PA = PB;
+
+	B = length2;				/* last bin ends at the query upper bound */
+	if (i >= length_hist_nvalues - 1)
+		pos = 0.0;
+	else
+	{
+		if (DatumGetFloat8(length_hist_values[i]) == DatumGetFloat8(length_hist_values[i + 1]))
+			pos = 0.0;
+		else
+			pos = get_len_position(length2, DatumGetFloat8(length_hist_values[i]), DatumGetFloat8(length_hist_values[i + 1]));
+	}
+	PB = (((double) i) + pos) / (double) (length_hist_nvalues - 1);
+
+	if (PA > 0 || PB > 0)
+		area += 0.5 * (PB + PA) * (B - A);
+
+	/*
+	 * Ok, we have calculated the area, ie. the integral. Divide by width to
+	 * get the requested average.
+	 *
+	 * Avoid NaN arising from infinite / infinite. This happens at least if
+	 * length2 is infinite. It's not clear what the correct value would be in
+	 * that case, so 0.5 seems as good as any value.
+	 */
+	if (isinf(area) && isinf(length2))
+		frac = 0.5;
+	else
+		frac = area / (length2 - length1);
+
+	return frac;
+}
+
+/*
+ * Calculate selectivity of "var <@ const" operator, ie. estimate the fraction
+ * of multiranges that fall within the constant lower and upper bounds. This uses
+ * the histograms of range lower bounds and range lengths, on the assumption
+ * that the range lengths are independent of the lower bounds.
+ *
+ * The caller has already checked that constant lower and upper bounds are
+ * finite.
+ */
+static double
+calc_hist_selectivity_contained(TypeCacheEntry *typcache,
+								const RangeBound *lower, RangeBound *upper,
+								const RangeBound *hist_lower, int hist_nvalues,
+								Datum *length_hist_values, int length_hist_nvalues)
+{
+	int			i,
+				upper_index;
+	float8		prev_dist;
+	double		bin_width;
+	double		upper_bin_width;
+	double		sum_frac;
+
+	/*
+	 * Begin by finding the bin containing the upper bound, in the lower bound
+	 * histogram. Any range with a lower bound > constant upper bound can't
+	 * match, ie. there are no matches in bins greater than upper_index.
+	 */
+	upper->inclusive = !upper->inclusive;
+	upper->lower = true;
+	upper_index = rbound_bsearch(typcache, upper, hist_lower, hist_nvalues,
+								 false);
+
+	/*
+	 * If the upper bound value is below the histogram's lower limit, there
+	 * are no matches.
+	 */
+	if (upper_index < 0)
+		return 0.0;
+
+	/*
+	 * If the upper bound value is at or beyond the histogram's upper limit,
+	 * start our loop at the last actual bin, as though the upper bound were
+	 * within that bin; get_position will clamp its result to 1.0 anyway.
+	 * (This corresponds to assuming that the data population above the
+	 * histogram's upper limit is empty, exactly like what we just assumed for
+	 * the lower limit.)
+	 */
+	upper_index = Min(upper_index, hist_nvalues - 2);
+
+	/*
+	 * Calculate upper_bin_width, ie. the fraction of the (upper_index,
+	 * upper_index + 1) bin which is greater than upper bound of query range
+	 * using linear interpolation of subdiff function.
+	 */
+	upper_bin_width = get_position(typcache, upper,
+								   &hist_lower[upper_index],
+								   &hist_lower[upper_index + 1]);
+
+	/*
+	 * In the loop, dist and prev_dist are the distance of the "current" bin's
+	 * lower and upper bounds from the constant upper bound.
+	 *
+	 * bin_width represents the width of the current bin. Normally it is 1.0,
+	 * meaning a full width bin, but can be less in the corner cases: start
+	 * and end of the loop. We start with bin_width = upper_bin_width, because
+	 * we begin at the bin containing the upper bound.
+	 */
+	prev_dist = 0.0;
+	bin_width = upper_bin_width;
+
+	sum_frac = 0.0;
+	for (i = upper_index; i >= 0; i--)
+	{
+		double		dist;
+		double		length_hist_frac;
+		bool		final_bin = false;
+
+		/*
+		 * dist -- distance from upper bound of query range to lower bound of
+		 * the current bin in the lower bound histogram. Or to the lower bound
+		 * of the constant range, if this is the final bin, containing the
+		 * constant lower bound.
+		 */
+		if (range_cmp_bounds(typcache, &hist_lower[i], lower) < 0)
+		{
+			dist = get_distance(typcache, lower, upper);
+
+			/*
+			 * Subtract from bin_width the portion of this bin that we want to
+			 * ignore.
+			 */
+			bin_width -= get_position(typcache, lower, &hist_lower[i],
+									  &hist_lower[i + 1]);
+			if (bin_width < 0.0)
+				bin_width = 0.0;
+			final_bin = true;
+		}
+		else
+			dist = get_distance(typcache, &hist_lower[i], upper);
+
+		/*
+		 * Estimate the fraction of tuples in this bin that are narrow enough
+		 * to not exceed the distance to the upper bound of the query range.
+		 */
+		length_hist_frac = calc_length_hist_frac(length_hist_values,
+												 length_hist_nvalues,
+												 prev_dist, dist, true);
+
+		/*
+		 * Add the fraction of tuples in this bin, with a suitable length, to
+		 * the total.
+		 */
+		sum_frac += length_hist_frac * bin_width / (double) (hist_nvalues - 1);
+
+		if (final_bin)
+			break;
+
+		bin_width = 1.0;
+		prev_dist = dist;
+	}
+
+	return sum_frac;
+}
+
+/*
+ * Calculate selectivity of "var @> const" operator, ie. estimate the fraction
+ * of multiranges that contain the constant lower and upper bounds. This uses
+ * the histograms of range lower bounds and range lengths, on the assumption
+ * that the range lengths are independent of the lower bounds.
+ */
+static double
+calc_hist_selectivity_contains(TypeCacheEntry *typcache,
+							   const RangeBound *lower, const RangeBound *upper,
+							   const RangeBound *hist_lower, int hist_nvalues,
+							   Datum *length_hist_values, int length_hist_nvalues)
+{
+	int			i,
+				lower_index;
+	double		bin_width,
+				lower_bin_width;
+	double		sum_frac;
+	float8		prev_dist;
+
+	/* Find the bin containing the lower bound of query range. */
+	lower_index = rbound_bsearch(typcache, lower, hist_lower, hist_nvalues,
+								 true);
+
+	/*
+	 * If the lower bound value is below the histogram's lower limit, there
+	 * are no matches.
+	 */
+	if (lower_index < 0)
+		return 0.0;
+
+	/*
+	 * If the lower bound value is at or beyond the histogram's upper limit,
+	 * start our loop at the last actual bin, as though the upper bound were
+	 * within that bin; get_position will clamp its result to 1.0 anyway.
+	 * (This corresponds to assuming that the data population above the
+	 * histogram's upper limit is empty, exactly like what we just assumed for
+	 * the lower limit.)
+	 */
+	lower_index = Min(lower_index, hist_nvalues - 2);
+
+	/*
+	 * Calculate lower_bin_width, ie. the fraction of the of (lower_index,
+	 * lower_index + 1) bin which is greater than lower bound of query range
+	 * using linear interpolation of subdiff function.
+	 */
+	lower_bin_width = get_position(typcache, lower, &hist_lower[lower_index],
+								   &hist_lower[lower_index + 1]);
+
+	/*
+	 * Loop through all the lower bound bins, smaller than the query lower
+	 * bound. In the loop, dist and prev_dist are the distance of the
+	 * "current" bin's lower and upper bounds from the constant upper bound.
+	 * We begin from query lower bound, and walk backwards, so the first bin's
+	 * upper bound is the query lower bound, and its distance to the query
+	 * upper bound is the length of the query range.
+	 *
+	 * bin_width represents the width of the current bin. Normally it is 1.0,
+	 * meaning a full width bin, except for the first bin, which is only
+	 * counted up to the constant lower bound.
+	 */
+	prev_dist = get_distance(typcache, lower, upper);
+	sum_frac = 0.0;
+	bin_width = lower_bin_width;
+	for (i = lower_index; i >= 0; i--)
+	{
+		float8		dist;
+		double		length_hist_frac;
+
+		/*
+		 * dist -- distance from upper bound of query range to current value
+		 * of lower bound histogram or lower bound of query range (if we've
+		 * reach it).
+		 */
+		dist = get_distance(typcache, &hist_lower[i], upper);
+
+		/*
+		 * Get average fraction of length histogram which covers intervals
+		 * longer than (or equal to) distance to upper bound of query range.
+		 */
+		length_hist_frac =
+			1.0 - calc_length_hist_frac(length_hist_values,
+										length_hist_nvalues,
+										prev_dist, dist, false);
+
+		sum_frac += length_hist_frac * bin_width / (double) (hist_nvalues - 1);
+
+		bin_width = 1.0;
+		prev_dist = dist;
+	}
+
+	return sum_frac;
+}
diff --git a/src/backend/utils/adt/pg_upgrade_support.c b/src/backend/utils/adt/pg_upgrade_support.c
index 8a5953881ee..521ebaaab6d 100644
--- a/src/backend/utils/adt/pg_upgrade_support.c
+++ b/src/backend/utils/adt/pg_upgrade_support.c
@@ -52,6 +52,28 @@ binary_upgrade_set_next_array_pg_type_oid(PG_FUNCTION_ARGS)
 	PG_RETURN_VOID();
 }
 
+Datum
+binary_upgrade_set_next_multirange_pg_type_oid(PG_FUNCTION_ARGS)
+{
+	Oid			typoid = PG_GETARG_OID(0);
+
+	CHECK_IS_BINARY_UPGRADE;
+	binary_upgrade_next_mrng_pg_type_oid = typoid;
+
+	PG_RETURN_VOID();
+}
+
+Datum
+binary_upgrade_set_next_multirange_array_pg_type_oid(PG_FUNCTION_ARGS)
+{
+	Oid			typoid = PG_GETARG_OID(0);
+
+	CHECK_IS_BINARY_UPGRADE;
+	binary_upgrade_next_mrng_array_pg_type_oid = typoid;
+
+	PG_RETURN_VOID();
+}
+
 Datum
 binary_upgrade_set_next_heap_pg_class_oid(PG_FUNCTION_ARGS)
 {
diff --git a/src/backend/utils/adt/pseudotypes.c b/src/backend/utils/adt/pseudotypes.c
index 3d6b2f90935..99a93271fe1 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"
 
 
 /*
@@ -227,6 +228,43 @@ anycompatiblerange_out(PG_FUNCTION_ARGS)
 	return range_out(fcinfo);
 }
 
+/*
+ * anycompatiblemultirange
+ *
+ * We may as well allow output, since multirange_out will in fact work.
+ */
+PSEUDOTYPE_DUMMY_INPUT_FUNC(anycompatiblemultirange);
+
+Datum
+anycompatiblemultirange_out(PG_FUNCTION_ARGS)
+{
+	return multirange_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
  *
diff --git a/src/backend/utils/adt/rangetypes.c b/src/backend/utils/adt/rangetypes.c
index 01ad8bc240b..ba4caa2d36a 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
 {
@@ -64,10 +62,6 @@ static const char *range_parse_bound(const char *string, const char *ptr,
 static char *range_deparse(char flags, const char *lbound_str,
 						   const char *ubound_str);
 static char *range_bound_escape(const char *value);
-static Size datum_compute_size(Size sz, Datum datum, bool typbyval,
-							   char typalign, int16 typlen, char typstorage);
-static Pointer datum_write(Pointer ptr, Datum datum, bool typbyval,
-						   char typalign, int16 typlen, char typstorage);
 
 
 /*
@@ -431,19 +425,37 @@ range_lower(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
 	TypeCacheEntry *typcache;
+	bool		isnull;
+	Datum		result;
+
+	typcache = range_get_typcache(fcinfo, RangeTypeGetOid(r1));
+
+	result = range_lower_internal(typcache, r1, &isnull);
+
+	if (isnull)
+		PG_RETURN_NULL();
+	else
+		PG_RETURN_DATUM(result);
+}
+
+Datum
+range_lower_internal(TypeCacheEntry *typcache, const RangeType *r1, bool *isnull)
+{
 	RangeBound	lower;
 	RangeBound	upper;
 	bool		empty;
 
-	typcache = range_get_typcache(fcinfo, RangeTypeGetOid(r1));
-
 	range_deserialize(typcache, r1, &lower, &upper, &empty);
 
 	/* Return NULL if there's no finite lower bound */
 	if (empty || lower.infinite)
-		PG_RETURN_NULL();
+	{
+		*isnull = true;
+		return 0;
+	}
 
-	PG_RETURN_DATUM(lower.val);
+	*isnull = false;
+	return lower.val;
 }
 
 /* extract upper bound value */
@@ -452,19 +464,37 @@ range_upper(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
 	TypeCacheEntry *typcache;
+	bool		isnull;
+	Datum		result;
+
+	typcache = range_get_typcache(fcinfo, RangeTypeGetOid(r1));
+
+	result = range_upper_internal(typcache, r1, &isnull);
+
+	if (isnull)
+		PG_RETURN_NULL();
+	else
+		PG_RETURN_DATUM(result);
+}
+
+Datum
+range_upper_internal(TypeCacheEntry *typcache, const RangeType *r1, bool *isnull)
+{
 	RangeBound	lower;
 	RangeBound	upper;
 	bool		empty;
 
-	typcache = range_get_typcache(fcinfo, RangeTypeGetOid(r1));
-
 	range_deserialize(typcache, r1, &lower, &upper, &empty);
 
 	/* Return NULL if there's no finite upper bound */
 	if (empty || upper.infinite)
-		PG_RETURN_NULL();
+	{
+		*isnull = true;
+		return 0;
+	}
 
-	PG_RETURN_DATUM(upper.val);
+	*isnull = false;
+	return upper.val;
 }
 
 
@@ -475,9 +505,8 @@ Datum
 range_empty(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
-	char		flags = range_get_flags(r1);
 
-	PG_RETURN_BOOL(flags & RANGE_EMPTY);
+	PG_RETURN_BOOL(range_has_flag(r1, RANGE_EMPTY));
 }
 
 /* is lower bound inclusive? */
@@ -485,9 +514,8 @@ Datum
 range_lower_inc(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
-	char		flags = range_get_flags(r1);
 
-	PG_RETURN_BOOL(flags & RANGE_LB_INC);
+	PG_RETURN_BOOL(range_has_flag(r1, RANGE_LB_INC));
 }
 
 /* is upper bound inclusive? */
@@ -495,9 +523,8 @@ Datum
 range_upper_inc(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
-	char		flags = range_get_flags(r1);
 
-	PG_RETURN_BOOL(flags & RANGE_UB_INC);
+	PG_RETURN_BOOL(range_has_flag(r1, RANGE_UB_INC));
 }
 
 /* is lower bound infinite? */
@@ -505,9 +532,8 @@ Datum
 range_lower_inf(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
-	char		flags = range_get_flags(r1);
 
-	PG_RETURN_BOOL(flags & RANGE_LB_INF);
+	PG_RETURN_BOOL(range_has_flag(r1, RANGE_LB_INF));
 }
 
 /* is upper bound infinite? */
@@ -515,9 +541,8 @@ Datum
 range_upper_inf(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
-	char		flags = range_get_flags(r1);
 
-	PG_RETURN_BOOL(flags & RANGE_UB_INF);
+	PG_RETURN_BOOL(range_has_flag(r1, RANGE_UB_INF));
 }
 
 
@@ -957,7 +982,25 @@ range_minus(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
 	RangeType  *r2 = PG_GETARG_RANGE_P(1);
+	RangeType  *ret;
 	TypeCacheEntry *typcache;
+
+	/* 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));
+
+	ret = range_minus_internal(typcache, r1, r2);
+	if (ret)
+		PG_RETURN_RANGE_P(ret);
+	else
+		PG_RETURN_NULL();
+}
+
+RangeType *
+range_minus_internal(TypeCacheEntry *typcache, RangeType *r1, RangeType *r2)
+{
 	RangeBound	lower1,
 				lower2;
 	RangeBound	upper1,
@@ -969,18 +1012,12 @@ range_minus(PG_FUNCTION_ARGS)
 				cmp_u1l2,
 				cmp_u1u2;
 
-	/* 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));
-
 	range_deserialize(typcache, r1, &lower1, &upper1, &empty1);
 	range_deserialize(typcache, r2, &lower2, &upper2, &empty2);
 
 	/* if either is empty, r1 is the correct answer */
 	if (empty1 || empty2)
-		PG_RETURN_RANGE_P(r1);
+		return r1;
 
 	cmp_l1l2 = range_cmp_bounds(typcache, &lower1, &lower2);
 	cmp_l1u2 = range_cmp_bounds(typcache, &lower1, &upper2);
@@ -993,34 +1030,34 @@ range_minus(PG_FUNCTION_ARGS)
 				 errmsg("result of range difference would not be contiguous")));
 
 	if (cmp_l1u2 > 0 || cmp_u1l2 < 0)
-		PG_RETURN_RANGE_P(r1);
+		return r1;
 
 	if (cmp_l1l2 >= 0 && cmp_u1u2 <= 0)
-		PG_RETURN_RANGE_P(make_empty_range(typcache));
+		return make_empty_range(typcache);
 
 	if (cmp_l1l2 <= 0 && cmp_u1l2 >= 0 && cmp_u1u2 <= 0)
 	{
 		lower2.inclusive = !lower2.inclusive;
 		lower2.lower = false;	/* it will become the upper bound */
-		PG_RETURN_RANGE_P(make_range(typcache, &lower1, &lower2, false));
+		return make_range(typcache, &lower1, &lower2, false);
 	}
 
 	if (cmp_l1l2 >= 0 && cmp_u1u2 >= 0 && cmp_l1u2 <= 0)
 	{
 		upper2.inclusive = !upper2.inclusive;
 		upper2.lower = true;	/* it will become the lower bound */
-		PG_RETURN_RANGE_P(make_range(typcache, &upper2, &upper1, false));
+		return make_range(typcache, &upper2, &upper1, false);
 	}
 
 	elog(ERROR, "unexpected case in range_minus");
-	PG_RETURN_NULL();
+	return NULL;
 }
 
 /*
  * Set union.  If strict is true, it is an error that the two input ranges
  * are not adjacent or overlapping.
  */
-static RangeType *
+RangeType *
 range_union_internal(TypeCacheEntry *typcache, RangeType *r1, RangeType *r2,
 					 bool strict)
 {
@@ -1101,6 +1138,19 @@ range_intersect(PG_FUNCTION_ARGS)
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
 	RangeType  *r2 = PG_GETARG_RANGE_P(1);
 	TypeCacheEntry *typcache;
+
+	/* 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));
+
+	PG_RETURN_RANGE_P(range_intersect_internal(typcache, r1, r2));
+}
+
+RangeType *
+range_intersect_internal(TypeCacheEntry *typcache, const RangeType *r1, const RangeType *r2)
+{
 	RangeBound	lower1,
 				lower2;
 	RangeBound	upper1,
@@ -1110,17 +1160,11 @@ range_intersect(PG_FUNCTION_ARGS)
 	RangeBound *result_lower;
 	RangeBound *result_upper;
 
-	/* 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));
-
 	range_deserialize(typcache, r1, &lower1, &upper1, &empty1);
 	range_deserialize(typcache, r2, &lower2, &upper2, &empty2);
 
-	if (empty1 || empty2 || !DatumGetBool(range_overlaps(fcinfo)))
-		PG_RETURN_RANGE_P(make_empty_range(typcache));
+	if (empty1 || empty2 || !range_overlaps_internal(typcache, r1, r2))
+		return make_empty_range(typcache);
 
 	if (range_cmp_bounds(typcache, &lower1, &lower2) >= 0)
 		result_lower = &lower1;
@@ -1132,9 +1176,81 @@ range_intersect(PG_FUNCTION_ARGS)
 	else
 		result_upper = &upper2;
 
-	PG_RETURN_RANGE_P(make_range(typcache, result_lower, result_upper, false));
+	return make_range(typcache, result_lower, result_upper, false);
+}
+
+/* range, range -> range, range functions */
+
+/*
+ * range_split_internal - if r2 intersects the middle of r1, leaving non-empty
+ * ranges on both sides, then return true and set output1 and output2 to the
+ * results of r1 - r2 (in order). Otherwise return false and don't set output1
+ * or output2. Neither input range should be empty.
+ */
+bool
+range_split_internal(TypeCacheEntry *typcache, const RangeType *r1, const RangeType *r2,
+					 RangeType **output1, RangeType **output2)
+{
+	RangeBound	lower1,
+				lower2;
+	RangeBound	upper1,
+				upper2;
+	bool		empty1,
+				empty2;
+
+	range_deserialize(typcache, r1, &lower1, &upper1, &empty1);
+	range_deserialize(typcache, r2, &lower2, &upper2, &empty2);
+
+	if (range_cmp_bounds(typcache, &lower1, &lower2) < 0 &&
+		range_cmp_bounds(typcache, &upper1, &upper2) > 0)
+	{
+		/*
+		 * Need to invert inclusive/exclusive for the lower2 and upper2
+		 * points. They can't be infinite though. We're allowed to overwrite
+		 * these RangeBounds since they only exist locally.
+		 */
+		lower2.inclusive = !lower2.inclusive;
+		lower2.lower = false;
+		upper2.inclusive = !upper2.inclusive;
+		upper2.lower = true;
+
+		*output1 = make_range(typcache, &lower1, &lower2, false);
+		*output2 = make_range(typcache, &upper2, &upper1, false);
+		return true;
+	}
+
+	return false;
 }
 
+/* range -> range aggregate functions */
+
+Datum
+range_intersect_agg_transfn(PG_FUNCTION_ARGS)
+{
+	MemoryContext aggContext;
+	Oid			rngtypoid;
+	TypeCacheEntry *typcache;
+	RangeType  *result;
+	RangeType  *current;
+
+	if (!AggCheckCallContext(fcinfo, &aggContext))
+		elog(ERROR, "range_intersect_agg_transfn called in non-aggregate context");
+
+	rngtypoid = get_fn_expr_argtype(fcinfo->flinfo, 1);
+	if (!type_is_range(rngtypoid))
+		ereport(ERROR, (errmsg("range_intersect_agg must be called with a range")));
+
+	typcache = range_get_typcache(fcinfo, rngtypoid);
+
+	/* strictness ensures these are non-null */
+	result = PG_GETARG_RANGE_P(0);
+	current = PG_GETARG_RANGE_P(1);
+
+	result = range_intersect_internal(typcache, result, current);
+	PG_RETURN_RANGE_P(result);
+}
+
+
 /* Btree support */
 
 /* btree comparator */
@@ -1144,12 +1260,6 @@ range_cmp(PG_FUNCTION_ARGS)
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
 	RangeType  *r2 = PG_GETARG_RANGE_P(1);
 	TypeCacheEntry *typcache;
-	RangeBound	lower1,
-				lower2;
-	RangeBound	upper1,
-				upper2;
-	bool		empty1,
-				empty2;
 	int			cmp;
 
 	check_stack_depth();		/* recurses when subtype is a range type */
@@ -1160,6 +1270,28 @@ range_cmp(PG_FUNCTION_ARGS)
 
 	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, const RangeType *r1, const 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);
 
@@ -1177,10 +1309,7 @@ range_cmp(PG_FUNCTION_ARGS)
 			cmp = range_cmp_bounds(typcache, &upper1, &upper2);
 	}
 
-	PG_FREE_IF_COPY(r1, 0);
-	PG_FREE_IF_COPY(r2, 1);
-
-	PG_RETURN_INT32(cmp);
+	return cmp;
 }
 
 /* inequality operators using the range_cmp function */
@@ -1218,13 +1347,10 @@ range_gt(PG_FUNCTION_ARGS)
 
 /* Hash support */
 
-/* hash a range value */
-Datum
-hash_range(PG_FUNCTION_ARGS)
+uint32
+hash_range_internal(TypeCacheEntry *typcache, const RangeType *r)
 {
-	RangeType  *r = PG_GETARG_RANGE_P(0);
 	uint32		result;
-	TypeCacheEntry *typcache;
 	TypeCacheEntry *scache;
 	RangeBound	lower;
 	RangeBound	upper;
@@ -1233,10 +1359,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 +1400,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, const RangeType *r, Datum seed)
+{
+	uint64		result;
 	TypeCacheEntry *scache;
 	RangeBound	lower;
 	RangeBound	upper;
@@ -1300,10 +1429,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 +1467,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));
 }
 
 /*
@@ -1769,6 +1912,21 @@ range_get_flags(const RangeType *range)
 	return *((char *) range + VARSIZE(range) - 1);
 }
 
+/*
+ * range_has_flag: set whether a range has a specific flag.
+ *
+ * This lets expose some of our functions that just check flags
+ * to the rest of the code base (like to multiranges)
+ * without writing full-fledged *_internal versions.
+ */
+bool
+range_has_flag(const RangeType *r1, char flag)
+{
+	char		flags = range_get_flags(r1);
+
+	return flags & flag;
+}
+
 /*
  * range_set_contain_empty: set the RANGE_CONTAIN_EMPTY bit in the value.
  *
@@ -1937,6 +2095,46 @@ range_cmp_bound_values(TypeCacheEntry *typcache, const RangeBound *b1,
 										   b1->val, b2->val));
 }
 
+/*
+ * qsort callback for sorting ranges.
+ *
+ * Two empty ranges compare equal; an empty range sorts to the left of any
+ * non-empty range.  Two non-empty ranges are sorted by lower bound first
+ * and by upper bound next.
+ */
+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.
  */
@@ -2395,7 +2593,7 @@ range_contains_elem_internal(TypeCacheEntry *typcache, const RangeType *r, Datum
  * Increment data_length by the space needed by the datum, including any
  * preceding alignment padding.
  */
-static Size
+Size
 datum_compute_size(Size data_length, Datum val, bool typbyval, char typalign,
 				   int16 typlen, char typstorage)
 {
@@ -2421,7 +2619,7 @@ datum_compute_size(Size data_length, Datum val, bool typbyval, char typalign,
  * Write the given datum beginning at ptr (after advancing to correct
  * alignment, if needed).  Return the pointer incremented by space used.
  */
-static Pointer
+Pointer
 datum_write(Pointer ptr, Datum datum, bool typbyval, char typalign,
 			int16 typlen, char typstorage)
 {
diff --git a/src/backend/utils/adt/rangetypes_typanalyze.c b/src/backend/utils/adt/rangetypes_typanalyze.c
index 57413b07201..dcef09b13b7 100644
--- a/src/backend/utils/adt/rangetypes_typanalyze.c
+++ b/src/backend/utils/adt/rangetypes_typanalyze.c
@@ -30,6 +30,7 @@
 #include "utils/fmgrprotos.h"
 #include "utils/lsyscache.h"
 #include "utils/rangetypes.h"
+#include "utils/multirangetypes.h"
 
 static int	float8_qsort_cmp(const void *a1, const void *a2);
 static int	range_bound_qsort_cmp(const void *a1, const void *a2, void *arg);
@@ -60,6 +61,33 @@ range_typanalyze(PG_FUNCTION_ARGS)
 	PG_RETURN_BOOL(true);
 }
 
+/*
+ * multirange_typanalyze -- typanalyze function for multirange columns
+ *
+ * We do the same analysis as for ranges, but on the smallest range that
+ * completely includes the multirange.
+ */
+Datum
+multirange_typanalyze(PG_FUNCTION_ARGS)
+{
+	VacAttrStats *stats = (VacAttrStats *) PG_GETARG_POINTER(0);
+	TypeCacheEntry *typcache;
+	Form_pg_attribute attr = stats->attr;
+
+	/* Get information about multirange type; note column might be a domain */
+	typcache = multirange_get_typcache(fcinfo, getBaseType(stats->attrtypid));
+
+	if (attr->attstattarget < 0)
+		attr->attstattarget = default_statistics_target;
+
+	stats->compute_stats = compute_range_stats;
+	stats->extra_data = typcache;
+	/* same as in std_typanalyze */
+	stats->minrows = 300 * attr->attstattarget;
+
+	PG_RETURN_BOOL(true);
+}
+
 /*
  * Comparison function for sorting float8s, used for range lengths.
  */
@@ -98,7 +126,8 @@ compute_range_stats(VacAttrStats *stats, AnalyzeAttrFetchFunc fetchfunc,
 					int samplerows, double totalrows)
 {
 	TypeCacheEntry *typcache = (TypeCacheEntry *) stats->extra_data;
-	bool		has_subdiff = OidIsValid(typcache->rng_subdiff_finfo.fn_oid);
+	TypeCacheEntry *mltrng_typcache = NULL;
+	bool		has_subdiff;
 	int			null_cnt = 0;
 	int			non_null_cnt = 0;
 	int			non_empty_cnt = 0;
@@ -112,6 +141,15 @@ compute_range_stats(VacAttrStats *stats, AnalyzeAttrFetchFunc fetchfunc,
 			   *uppers;
 	double		total_width = 0;
 
+	if (typcache->typtype == TYPTYPE_MULTIRANGE)
+	{
+		mltrng_typcache = typcache;
+		typcache = typcache->rngtype;
+	}
+	else
+		Assert(typcache->typtype == TYPTYPE_RANGE);
+	has_subdiff = OidIsValid(typcache->rng_subdiff_finfo.fn_oid);
+
 	/* Allocate memory to hold range bounds and lengths of the sample ranges. */
 	lowers = (RangeBound *) palloc(sizeof(RangeBound) * samplerows);
 	uppers = (RangeBound *) palloc(sizeof(RangeBound) * samplerows);
@@ -123,6 +161,7 @@ compute_range_stats(VacAttrStats *stats, AnalyzeAttrFetchFunc fetchfunc,
 		Datum		value;
 		bool		isnull,
 					empty;
+		MultirangeType *multirange;
 		RangeType  *range;
 		RangeBound	lower,
 					upper;
@@ -145,7 +184,24 @@ compute_range_stats(VacAttrStats *stats, AnalyzeAttrFetchFunc fetchfunc,
 		total_width += VARSIZE_ANY(DatumGetPointer(value));
 
 		/* Get range and deserialize it for further analysis. */
-		range = DatumGetRangeTypeP(value);
+		if (mltrng_typcache != NULL)
+		{
+			/* Treat multiranges like a big range without gaps. */
+			multirange = DatumGetMultirangeTypeP(value);
+			if (MultirangeIsEmpty(multirange))
+				range = make_empty_range(typcache);
+			else
+			{
+				int			range_count;
+				RangeType **ranges;
+
+				multirange_deserialize(typcache, multirange, &range_count, &ranges);
+				range = range_union_internal(typcache, ranges[0],
+											 ranges[range_count - 1], false);
+			}
+		}
+		else
+			range = DatumGetRangeTypeP(value);
 		range_deserialize(typcache, range, &lower, &upper, &empty);
 
 		if (!empty)
@@ -262,6 +318,13 @@ compute_range_stats(VacAttrStats *stats, AnalyzeAttrFetchFunc fetchfunc,
 			stats->stakind[slot_idx] = STATISTIC_KIND_BOUNDS_HISTOGRAM;
 			stats->stavalues[slot_idx] = bound_hist_values;
 			stats->numvalues[slot_idx] = num_hist;
+
+			/* Store ranges even if we're analyzing a multirange column */
+			stats->statypid[slot_idx] = typcache->type_id;
+			stats->statyplen[slot_idx] = typcache->typlen;
+			stats->statypbyval[slot_idx] = typcache->typbyval;
+			stats->statypalign[slot_idx] = 'd';
+
 			slot_idx++;
 		}
 
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index ae232991623..2138af7079a 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -2583,6 +2583,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
  *
@@ -3225,7 +3235,7 @@ get_namespace_name_or_temp(Oid nspid)
 		return get_namespace_name(nspid);
 }
 
-/*				---------- PG_RANGE CACHE ----------				 */
+/*				---------- PG_RANGE CACHES ----------				 */
 
 /*
  * get_range_subtype
@@ -3278,6 +3288,56 @@ get_range_collation(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->rngmultitypid;
+		ReleaseSysCache(tp);
+		return result;
+	}
+	else
+		return InvalidOid;
+}
+
+/*
+ * get_multirange_range
+ *		Returns the range type of a given multirange
+ *
+ * Returns InvalidOid if the type is not a multirange.
+ */
+Oid
+get_multirange_range(Oid multirangeOid)
+{
+	HeapTuple	tp;
+
+	tp = SearchSysCache1(RANGEMULTIRANGE, 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 809b27a038f..89f08a4f43c 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -650,6 +650,18 @@ static const struct cachedesc cacheinfo[] = {
 		},
 		64
 	},
+	{RangeRelationId,			/* RANGEMULTIRANGE */
+		RangeMultirangeTypidIndexId,
+		1,
+		{
+			Anum_pg_range_rngmultitypid,
+			0,
+			0,
+			0
+		},
+		4
+	},
+
 	{RangeRelationId,			/* RANGETYPE */
 		RangeTypidIndexId,
 		1,
diff --git a/src/backend/utils/cache/typcache.c b/src/backend/utils/cache/typcache.c
index dca1d48e895..7ea5744060c 100644
--- a/src/backend/utils/cache/typcache.c
+++ b/src/backend/utils/cache/typcache.c
@@ -287,6 +287,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);
@@ -305,6 +306,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 TypeCacheTypCallback(Datum arg, int cacheid, uint32 hashvalue);
 static void TypeCacheOpcCallback(Datum arg, int cacheid, uint32 hashvalue);
@@ -556,8 +560,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))
@@ -594,7 +598,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))
@@ -619,7 +623,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))
@@ -644,7 +648,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))
@@ -694,6 +698,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;
@@ -736,6 +747,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;
@@ -815,6 +833,16 @@ lookup_type_cache(Oid type_id, int flags)
 			(void) lookup_type_cache(typentry->rngelemtype->type_id, 0);
 	}
 
+	/*
+	 * 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
 	 */
@@ -926,6 +954,22 @@ load_rangetype_info(TypeCacheEntry *typentry)
 	typentry->rngelemtype = lookup_type_cache(subtypeOid, 0);
 }
 
+/*
+ * load_multirangetype_info --- helper routine to set up multirange type
+ * information
+ */
+static void
+load_multirangetype_info(TypeCacheEntry *typentry)
+{
+	Oid			rangetypeOid;
+
+	rangetypeOid = get_multirange_range(typentry->type_id);
+	if (!OidIsValid(rangetypeOid))
+		elog(ERROR, "cache lookup failed for multirange type %u",
+			 typentry->type_id);
+
+	typentry->rngtype = lookup_type_cache(rangetypeOid, TYPECACHE_RANGE_INFO);
+}
 
 /*
  * load_domaintype_info --- helper routine to set up domain constraint info
@@ -1558,11 +1602,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
@@ -1605,6 +1649,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 9696c88f241..f6fa4ab2fb2 100644
--- a/src/backend/utils/fmgr/funcapi.c
+++ b/src/backend/utils/fmgr/funcapi.c
@@ -35,6 +35,7 @@ typedef struct polymorphic_actuals
 	Oid			anyelement_type;	/* anyelement mapping, if known */
 	Oid			anyarray_type;	/* anyarray mapping, if known */
 	Oid			anyrange_type;	/* anyrange mapping, if known */
+	Oid			anymultirange_type; /* anymultirange mapping, if known */
 } polymorphic_actuals;
 
 static void shutdown_MultiFuncCall(Datum arg);
@@ -46,6 +47,7 @@ static TypeFuncClass internal_get_result_type(Oid funcid,
 static void resolve_anyelement_from_others(polymorphic_actuals *actuals);
 static void resolve_anyarray_from_others(polymorphic_actuals *actuals);
 static void resolve_anyrange_from_others(polymorphic_actuals *actuals);
+static void resolve_anymultirange_from_others(polymorphic_actuals *actuals);
 static bool resolve_polymorphic_tupdesc(TupleDesc tupdesc,
 										oidvector *declared_args,
 										Node *call_expr);
@@ -503,6 +505,34 @@ resolve_anyelement_from_others(polymorphic_actuals *actuals)
 							format_type_be(range_base_type))));
 		actuals->anyelement_type = range_typelem;
 	}
+	else if (OidIsValid(actuals->anymultirange_type))
+	{
+		/* Use the element type based on the multirange type */
+		Oid			multirange_base_type;
+		Oid			multirange_typelem;
+		Oid			range_base_type;
+		Oid			range_typelem;
+
+		multirange_base_type = getBaseType(actuals->anymultirange_type);
+		multirange_typelem = get_multirange_range(multirange_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(multirange_base_type))));
+
+		range_base_type = getBaseType(multirange_typelem);
+		range_typelem = get_range_subtype(range_base_type);
+
+		if (!OidIsValid(range_typelem))
+			ereport(ERROR,
+					(errcode(ERRCODE_DATATYPE_MISMATCH),
+					 errmsg("argument declared %s does not contain a range type but type %s",
+							"anymultirange",
+							format_type_be(range_base_type))));
+		actuals->anyelement_type = range_typelem;
+	}
 	else
 		elog(ERROR, "could not determine polymorphic type");
 }
@@ -540,10 +570,53 @@ static void
 resolve_anyrange_from_others(polymorphic_actuals *actuals)
 {
 	/*
-	 * We can't deduce a range type from other polymorphic inputs, because
-	 * there may be multiple range types with the same subtype.
+	 * We can't deduce a range type from other polymorphic array or base
+	 * types, because there may be multiple range types with the same subtype,
+	 * but we can deduce it from a polymorphic multirange type.
+	 */
+	if (OidIsValid(actuals->anymultirange_type))
+	{
+		/* Use the element type based on the multirange type */
+		Oid			multirange_base_type = getBaseType(actuals->anymultirange_type);
+		Oid			multirange_typelem = get_multirange_range(multirange_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(multirange_base_type))));
+		actuals->anyrange_type = multirange_typelem;
+	}
+	else
+		elog(ERROR, "could not determine polymorphic type");
+}
+
+/*
+ * Resolve actual type of ANYMULTIRANGE from other polymorphic inputs
+ */
+static void
+resolve_anymultirange_from_others(polymorphic_actuals *actuals)
+{
+	/*
+	 * We can't deduce a multirange type from polymorphic array or base types,
+	 * because there may be multiple range types with the same subtype, but we
+	 * can deduce it from a polymorphic range type.
 	 */
-	elog(ERROR, "could not determine polymorphic type");
+	if (OidIsValid(actuals->anyrange_type))
+	{
+		Oid			range_base_type = getBaseType(actuals->anyrange_type);
+		Oid			multirange_typeid = get_range_multirange(range_base_type);
+
+		if (!OidIsValid(multirange_typeid))
+			ereport(ERROR,
+					(errcode(ERRCODE_UNDEFINED_OBJECT),
+					 errmsg("could not find multirange type for data type %s",
+							format_type_be(actuals->anyrange_type))));
+		actuals->anymultirange_type = multirange_typeid;
+	}
+	else
+		elog(ERROR, "could not determine polymorphic type");
 }
 
 /*
@@ -566,9 +639,11 @@ 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_anycompatible_result = false;
 	bool		have_anycompatible_array_result = false;
 	bool		have_anycompatible_range_result = false;
+	bool		have_anycompatible_multirange_result = false;
 	polymorphic_actuals poly_actuals;
 	polymorphic_actuals anyc_actuals;
 	Oid			anycollation = InvalidOid;
@@ -594,6 +669,10 @@ resolve_polymorphic_tupdesc(TupleDesc tupdesc, oidvector *declared_args,
 				have_polymorphic_result = true;
 				have_anyrange_result = true;
 				break;
+			case ANYMULTIRANGEOID:
+				have_polymorphic_result = true;
+				have_anymultirange_result = true;
+				break;
 			case ANYCOMPATIBLEOID:
 			case ANYCOMPATIBLENONARRAYOID:
 				have_polymorphic_result = true;
@@ -607,6 +686,10 @@ resolve_polymorphic_tupdesc(TupleDesc tupdesc, oidvector *declared_args,
 				have_polymorphic_result = true;
 				have_anycompatible_range_result = true;
 				break;
+			case ANYCOMPATIBLEMULTIRANGEOID:
+				have_polymorphic_result = true;
+				have_anycompatible_multirange_result = true;
+				break;
 			default:
 				break;
 		}
@@ -660,6 +743,15 @@ resolve_polymorphic_tupdesc(TupleDesc tupdesc, oidvector *declared_args,
 						return false;
 				}
 				break;
+			case ANYMULTIRANGEOID:
+				if (!OidIsValid(poly_actuals.anymultirange_type))
+				{
+					poly_actuals.anymultirange_type =
+						get_call_expr_argtype(call_expr, i);
+					if (!OidIsValid(poly_actuals.anymultirange_type))
+						return false;
+				}
+				break;
 			case ANYCOMPATIBLEOID:
 			case ANYCOMPATIBLENONARRAYOID:
 				if (!OidIsValid(anyc_actuals.anyelement_type))
@@ -688,6 +780,15 @@ resolve_polymorphic_tupdesc(TupleDesc tupdesc, oidvector *declared_args,
 						return false;
 				}
 				break;
+			case ANYCOMPATIBLEMULTIRANGEOID:
+				if (!OidIsValid(anyc_actuals.anymultirange_type))
+				{
+					anyc_actuals.anymultirange_type =
+						get_call_expr_argtype(call_expr, i);
+					if (!OidIsValid(anyc_actuals.anymultirange_type))
+						return false;
+				}
+				break;
 			default:
 				break;
 		}
@@ -703,6 +804,9 @@ resolve_polymorphic_tupdesc(TupleDesc tupdesc, oidvector *declared_args,
 	if (have_anyrange_result && !OidIsValid(poly_actuals.anyrange_type))
 		resolve_anyrange_from_others(&poly_actuals);
 
+	if (have_anymultirange_result && !OidIsValid(poly_actuals.anymultirange_type))
+		resolve_anymultirange_from_others(&poly_actuals);
+
 	if (have_anycompatible_result && !OidIsValid(anyc_actuals.anyelement_type))
 		resolve_anyelement_from_others(&anyc_actuals);
 
@@ -712,6 +816,9 @@ resolve_polymorphic_tupdesc(TupleDesc tupdesc, oidvector *declared_args,
 	if (have_anycompatible_range_result && !OidIsValid(anyc_actuals.anyrange_type))
 		resolve_anyrange_from_others(&anyc_actuals);
 
+	if (have_anycompatible_multirange_result && !OidIsValid(anyc_actuals.anymultirange_type))
+		resolve_anymultirange_from_others(&anyc_actuals);
+
 	/*
 	 * Identify the collation to use for polymorphic OUT parameters. (It'll
 	 * necessarily be the same for both anyelement and anyarray, likewise for
@@ -780,6 +887,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),
+								   poly_actuals.anymultirange_type,
+								   -1,
+								   0);
+				/* no collation should be attached to a multirange type */
+				break;
 			case ANYCOMPATIBLEOID:
 			case ANYCOMPATIBLENONARRAYOID:
 				TupleDescInitEntry(tupdesc, i + 1,
@@ -805,6 +920,14 @@ resolve_polymorphic_tupdesc(TupleDesc tupdesc, oidvector *declared_args,
 								   0);
 				/* no collation should be attached to a range type */
 				break;
+			case ANYCOMPATIBLEMULTIRANGEOID:
+				TupleDescInitEntry(tupdesc, i + 1,
+								   NameStr(att->attname),
+								   anyc_actuals.anymultirange_type,
+								   -1,
+								   0);
+				/* no collation should be attached to a multirange type */
+				break;
 			default:
 				break;
 		}
@@ -834,9 +957,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;
 	bool		have_anycompatible_result = false;
 	bool		have_anycompatible_array_result = false;
 	bool		have_anycompatible_range_result = false;
+	bool		have_anycompatible_multirange_result = false;
 	polymorphic_actuals poly_actuals;
 	polymorphic_actuals anyc_actuals;
 	int			inargno;
@@ -912,6 +1037,24 @@ resolve_polymorphic_argtypes(int numargs, Oid *argtypes, char *argmodes,
 					argtypes[i] = poly_actuals.anyrange_type;
 				}
 				break;
+			case ANYMULTIRANGEOID:
+				if (argmode == PROARGMODE_OUT || argmode == PROARGMODE_TABLE)
+				{
+					have_polymorphic_result = true;
+					have_anymultirange_result = true;
+				}
+				else
+				{
+					if (!OidIsValid(poly_actuals.anymultirange_type))
+					{
+						poly_actuals.anymultirange_type =
+							get_call_expr_argtype(call_expr, inargno);
+						if (!OidIsValid(poly_actuals.anymultirange_type))
+							return false;
+					}
+					argtypes[i] = poly_actuals.anymultirange_type;
+				}
+				break;
 			case ANYCOMPATIBLEOID:
 			case ANYCOMPATIBLENONARRAYOID:
 				if (argmode == PROARGMODE_OUT || argmode == PROARGMODE_TABLE)
@@ -967,6 +1110,24 @@ resolve_polymorphic_argtypes(int numargs, Oid *argtypes, char *argmodes,
 					argtypes[i] = anyc_actuals.anyrange_type;
 				}
 				break;
+			case ANYCOMPATIBLEMULTIRANGEOID:
+				if (argmode == PROARGMODE_OUT || argmode == PROARGMODE_TABLE)
+				{
+					have_polymorphic_result = true;
+					have_anycompatible_multirange_result = true;
+				}
+				else
+				{
+					if (!OidIsValid(anyc_actuals.anymultirange_type))
+					{
+						anyc_actuals.anymultirange_type =
+							get_call_expr_argtype(call_expr, inargno);
+						if (!OidIsValid(anyc_actuals.anymultirange_type))
+							return false;
+					}
+					argtypes[i] = anyc_actuals.anymultirange_type;
+				}
+				break;
 			default:
 				break;
 		}
@@ -988,6 +1149,9 @@ resolve_polymorphic_argtypes(int numargs, Oid *argtypes, char *argmodes,
 	if (have_anyrange_result && !OidIsValid(poly_actuals.anyrange_type))
 		resolve_anyrange_from_others(&poly_actuals);
 
+	if (have_anymultirange_result && !OidIsValid(poly_actuals.anymultirange_type))
+		resolve_anymultirange_from_others(&poly_actuals);
+
 	if (have_anycompatible_result && !OidIsValid(anyc_actuals.anyelement_type))
 		resolve_anyelement_from_others(&anyc_actuals);
 
@@ -997,6 +1161,9 @@ resolve_polymorphic_argtypes(int numargs, Oid *argtypes, char *argmodes,
 	if (have_anycompatible_range_result && !OidIsValid(anyc_actuals.anyrange_type))
 		resolve_anyrange_from_others(&anyc_actuals);
 
+	if (have_anycompatible_multirange_result && !OidIsValid(anyc_actuals.anymultirange_type))
+		resolve_anymultirange_from_others(&anyc_actuals);
+
 	/* And finally replace the output column types as needed */
 	for (i = 0; i < numargs; i++)
 	{
@@ -1013,6 +1180,9 @@ resolve_polymorphic_argtypes(int numargs, Oid *argtypes, char *argmodes,
 			case ANYRANGEOID:
 				argtypes[i] = poly_actuals.anyrange_type;
 				break;
+			case ANYMULTIRANGEOID:
+				argtypes[i] = poly_actuals.anymultirange_type;
+				break;
 			case ANYCOMPATIBLEOID:
 			case ANYCOMPATIBLENONARRAYOID:
 				argtypes[i] = anyc_actuals.anyelement_type;
@@ -1023,6 +1193,9 @@ resolve_polymorphic_argtypes(int numargs, Oid *argtypes, char *argmodes,
 			case ANYCOMPATIBLERANGEOID:
 				argtypes[i] = anyc_actuals.anyrange_type;
 				break;
+			case ANYCOMPATIBLEMULTIRANGEOID:
+				argtypes[i] = anyc_actuals.anymultirange_type;
+				break;
 			default:
 				break;
 		}
@@ -1052,6 +1225,7 @@ get_type_func_class(Oid typid, Oid *base_typeid)
 		case TYPTYPE_BASE:
 		case TYPTYPE_ENUM:
 		case TYPTYPE_RANGE:
+		case TYPTYPE_MULTIRANGE:
 			return TYPEFUNC_SCALAR;
 		case TYPTYPE_DOMAIN:
 			*base_typeid = typid = getBaseType(typid);
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index dc1d41dd8d2..02d377e5acd 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -272,7 +272,8 @@ static void dumpSearchPath(Archive *AH);
 static void binary_upgrade_set_type_oids_by_type_oid(Archive *fout,
 													 PQExpBuffer upgrade_buffer,
 													 Oid pg_type_oid,
-													 bool force_array_type);
+													 bool force_array_type,
+													 bool include_multirange_type);
 static void binary_upgrade_set_type_oids_by_rel_oid(Archive *fout,
 													PQExpBuffer upgrade_buffer, Oid pg_rel_oid);
 static void binary_upgrade_set_pg_class_oids(Archive *fout,
@@ -1653,7 +1654,7 @@ selectDumpableType(TypeInfo *tyinfo, Archive *fout)
 	}
 
 	/* skip auto-generated array types */
-	if (tyinfo->isArray)
+	if (tyinfo->isArray || tyinfo->isMultirange)
 	{
 		tyinfo->dobj.objType = DO_DUMMY_TYPE;
 
@@ -4461,16 +4462,49 @@ append_depends_on_extension(Archive *fout,
 	}
 }
 
+static Oid
+get_next_possible_free_pg_type_oid(Archive *fout, PQExpBuffer upgrade_query)
+{
+	/*
+	 * If the old version didn't assign an array type, but the new version
+	 * does, we must select an unused type OID to assign.  This currently only
+	 * happens for domains, when upgrading pre-v11 to v11 and up.
+	 *
+	 * Note: local state here is kind of ugly, but we must have some, since we
+	 * mustn't choose the same unused OID more than once.
+	 */
+	static Oid	next_possible_free_oid = FirstNormalObjectId;
+	PGresult   *res;
+	bool		is_dup;
+
+	do
+	{
+		++next_possible_free_oid;
+		printfPQExpBuffer(upgrade_query,
+						  "SELECT EXISTS(SELECT 1 "
+						  "FROM pg_catalog.pg_type "
+						  "WHERE oid = '%u'::pg_catalog.oid);",
+						  next_possible_free_oid);
+		res = ExecuteSqlQueryForSingleRow(fout, upgrade_query->data);
+		is_dup = (PQgetvalue(res, 0, 0)[0] == 't');
+		PQclear(res);
+	} while (is_dup);
+
+	return next_possible_free_oid;
+}
 
 static void
 binary_upgrade_set_type_oids_by_type_oid(Archive *fout,
 										 PQExpBuffer upgrade_buffer,
 										 Oid pg_type_oid,
-										 bool force_array_type)
+										 bool force_array_type,
+										 bool include_multirange_type)
 {
 	PQExpBuffer upgrade_query = createPQExpBuffer();
 	PGresult   *res;
 	Oid			pg_type_array_oid;
+	Oid			pg_type_multirange_oid;
+	Oid			pg_type_multirange_array_oid;
 
 	appendPQExpBufferStr(upgrade_buffer, "\n-- For binary upgrade, must preserve pg_type oid\n");
 	appendPQExpBuffer(upgrade_buffer,
@@ -4491,33 +4525,7 @@ binary_upgrade_set_type_oids_by_type_oid(Archive *fout,
 	PQclear(res);
 
 	if (!OidIsValid(pg_type_array_oid) && force_array_type)
-	{
-		/*
-		 * If the old version didn't assign an array type, but the new version
-		 * does, we must select an unused type OID to assign.  This currently
-		 * only happens for domains, when upgrading pre-v11 to v11 and up.
-		 *
-		 * Note: local state here is kind of ugly, but we must have some,
-		 * since we mustn't choose the same unused OID more than once.
-		 */
-		static Oid	next_possible_free_oid = FirstNormalObjectId;
-		bool		is_dup;
-
-		do
-		{
-			++next_possible_free_oid;
-			printfPQExpBuffer(upgrade_query,
-							  "SELECT EXISTS(SELECT 1 "
-							  "FROM pg_catalog.pg_type "
-							  "WHERE oid = '%u'::pg_catalog.oid);",
-							  next_possible_free_oid);
-			res = ExecuteSqlQueryForSingleRow(fout, upgrade_query->data);
-			is_dup = (PQgetvalue(res, 0, 0)[0] == 't');
-			PQclear(res);
-		} while (is_dup);
-
-		pg_type_array_oid = next_possible_free_oid;
-	}
+		pg_type_array_oid = get_next_possible_free_pg_type_oid(fout, upgrade_query);
 
 	if (OidIsValid(pg_type_array_oid))
 	{
@@ -4528,6 +4536,46 @@ binary_upgrade_set_type_oids_by_type_oid(Archive *fout,
 						  pg_type_array_oid);
 	}
 
+	/*
+	 * Pre-set the multirange type oid and its own array type oid.
+	 */
+	if (include_multirange_type)
+	{
+		if (fout->remoteVersion >= 130000)
+		{
+			appendPQExpBuffer(upgrade_query,
+							  "SELECT t.oid, t.typarray "
+							  "FROM pg_catalog.pg_type t "
+							  "JOIN pg_catalog.pg_range r "
+							  "ON t.oid = r.rngmultitypid "
+							  "WHERE r.rngtypid = '%u'::pg_catalog.oid;",
+							  pg_type_oid);
+
+			res = ExecuteSqlQueryForSingleRow(fout, upgrade_query->data);
+
+			pg_type_multirange_oid = atooid(PQgetvalue(res, 0, PQfnumber(res, "oid")));
+			pg_type_multirange_array_oid = atooid(PQgetvalue(res, 0, PQfnumber(res, "typarray")));
+
+			PQclear(res);
+		}
+		else
+		{
+			pg_type_multirange_oid = get_next_possible_free_pg_type_oid(fout, upgrade_query);
+			pg_type_multirange_array_oid = get_next_possible_free_pg_type_oid(fout, upgrade_query);
+		}
+
+		appendPQExpBufferStr(upgrade_buffer,
+							 "\n-- For binary upgrade, must preserve multirange pg_type oid\n");
+		appendPQExpBuffer(upgrade_buffer,
+						  "SELECT pg_catalog.binary_upgrade_set_next_multirange_pg_type_oid('%u'::pg_catalog.oid);\n\n",
+						  pg_type_multirange_oid);
+		appendPQExpBufferStr(upgrade_buffer,
+							 "\n-- For binary upgrade, must preserve multirange pg_type array oid\n");
+		appendPQExpBuffer(upgrade_buffer,
+						  "SELECT pg_catalog.binary_upgrade_set_next_multirange_array_pg_type_oid('%u'::pg_catalog.oid);\n\n",
+						  pg_type_multirange_array_oid);
+	}
+
 	destroyPQExpBuffer(upgrade_query);
 }
 
@@ -4552,7 +4600,7 @@ binary_upgrade_set_type_oids_by_rel_oid(Archive *fout,
 
 	if (OidIsValid(pg_type_oid))
 		binary_upgrade_set_type_oids_by_type_oid(fout, upgrade_buffer,
-												 pg_type_oid, false);
+												 pg_type_oid, false, false);
 
 	PQclear(upgrade_res);
 	destroyPQExpBuffer(upgrade_query);
@@ -5097,6 +5145,11 @@ getTypes(Archive *fout, int *numTypes)
 		else
 			tyinfo[i].isArray = false;
 
+		if (tyinfo[i].typtype == 'm')
+			tyinfo[i].isMultirange = true;
+		else
+			tyinfo[i].isMultirange = false;
+
 		/* Decide whether we want to dump it */
 		selectDumpableType(&tyinfo[i], fout);
 
@@ -8326,9 +8379,12 @@ getProcLangs(Archive *fout, int *numProcLangs)
 
 /*
  * getCasts
- *	  get basic information about every cast in the system
+ *	  get basic information about most casts in the system
  *
  * numCasts is set to the number of casts read in
+ *
+ * Skip casts from a range to its multirange, since we'll create those
+ * automatically.
  */
 CastInfo *
 getCasts(Archive *fout, int *numCasts)
@@ -8346,7 +8402,20 @@ getCasts(Archive *fout, int *numCasts)
 	int			i_castcontext;
 	int			i_castmethod;
 
-	if (fout->remoteVersion >= 80400)
+	if (fout->remoteVersion >= 130000)
+	{
+		appendPQExpBufferStr(query, "SELECT tableoid, oid, "
+							 "castsource, casttarget, castfunc, castcontext, "
+							 "castmethod "
+							 "FROM pg_cast c "
+							 "WHERE NOT EXISTS ( "
+							 "SELECT 1 FROM pg_range r "
+							 "WHERE c.castsource = r.rngtypid "
+							 "AND c.casttarget = r.rngmultitypid "
+							 ") "
+							 "ORDER BY 3,4");
+	}
+	else if (fout->remoteVersion >= 80400)
 	{
 		appendPQExpBufferStr(query, "SELECT tableoid, oid, "
 							 "castsource, casttarget, castfunc, castcontext, "
@@ -10496,7 +10565,7 @@ dumpEnumType(Archive *fout, TypeInfo *tyinfo)
 	if (dopt->binary_upgrade)
 		binary_upgrade_set_type_oids_by_type_oid(fout, q,
 												 tyinfo->dobj.catId.oid,
-												 false);
+												 false, false);
 
 	appendPQExpBuffer(q, "CREATE TYPE %s AS ENUM (",
 					  qualtypname);
@@ -10622,7 +10691,7 @@ dumpRangeType(Archive *fout, TypeInfo *tyinfo)
 	if (dopt->binary_upgrade)
 		binary_upgrade_set_type_oids_by_type_oid(fout, q,
 												 tyinfo->dobj.catId.oid,
-												 false);
+												 false, true);
 
 	appendPQExpBuffer(q, "CREATE TYPE %s AS RANGE (",
 					  qualtypname);
@@ -10728,7 +10797,7 @@ dumpUndefinedType(Archive *fout, TypeInfo *tyinfo)
 	if (dopt->binary_upgrade)
 		binary_upgrade_set_type_oids_by_type_oid(fout, q,
 												 tyinfo->dobj.catId.oid,
-												 false);
+												 false, false);
 
 	appendPQExpBuffer(q, "CREATE TYPE %s;\n",
 					  qualtypname);
@@ -10933,7 +11002,7 @@ dumpBaseType(Archive *fout, TypeInfo *tyinfo)
 	if (dopt->binary_upgrade)
 		binary_upgrade_set_type_oids_by_type_oid(fout, q,
 												 tyinfo->dobj.catId.oid,
-												 false);
+												 false, false);
 
 	appendPQExpBuffer(q,
 					  "CREATE TYPE %s (\n"
@@ -11120,7 +11189,8 @@ dumpDomain(Archive *fout, TypeInfo *tyinfo)
 	if (dopt->binary_upgrade)
 		binary_upgrade_set_type_oids_by_type_oid(fout, q,
 												 tyinfo->dobj.catId.oid,
-												 true); /* force array type */
+												 true,		/* force array type */
+												 false);	/* force multirange type */
 
 	qtypname = pg_strdup(fmtId(tyinfo->dobj.name));
 	qualtypname = pg_strdup(fmtQualifiedDumpable(tyinfo));
@@ -11308,7 +11378,7 @@ dumpCompositeType(Archive *fout, TypeInfo *tyinfo)
 	{
 		binary_upgrade_set_type_oids_by_type_oid(fout, q,
 												 tyinfo->dobj.catId.oid,
-												 false);
+												 false, false);
 		binary_upgrade_set_pg_class_oids(fout, q, tyinfo->typrelid, false);
 	}
 
@@ -11582,7 +11652,7 @@ dumpShellType(Archive *fout, ShellTypeInfo *stinfo)
 	if (dopt->binary_upgrade)
 		binary_upgrade_set_type_oids_by_type_oid(fout, q,
 												 stinfo->baseType->dobj.catId.oid,
-												 false);
+												 false, false);
 
 	appendPQExpBuffer(q, "CREATE TYPE %s;\n",
 					  fmtQualifiedDumpable(stinfo));
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 317bb839702..d7f77f1d3e0 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -176,6 +176,7 @@ typedef struct _typeInfo
 	char		typrelkind;		/* 'r', 'v', 'c', etc */
 	char		typtype;		/* 'b', 'c', etc */
 	bool		isArray;		/* true if auto-generated array type */
+	bool		isMultirange;	/* true if auto-generated multirange type */
 	bool		isDefined;		/* true if typisdefined */
 	/* If needed, we'll create a "shell type" entry for it; link that here: */
 	struct _shellTypeInfo *shellType;	/* shell-type entry, or NULL */
diff --git a/src/include/access/tupmacs.h b/src/include/access/tupmacs.h
index 70157cf90ab..c262265b951 100644
--- a/src/include/access/tupmacs.h
+++ b/src/include/access/tupmacs.h
@@ -139,8 +139,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/binary_upgrade.h b/src/include/catalog/binary_upgrade.h
index 02fecb90f79..1ff62e9f8e6 100644
--- a/src/include/catalog/binary_upgrade.h
+++ b/src/include/catalog/binary_upgrade.h
@@ -16,6 +16,8 @@
 
 extern PGDLLIMPORT Oid binary_upgrade_next_pg_type_oid;
 extern PGDLLIMPORT Oid binary_upgrade_next_array_pg_type_oid;
+extern PGDLLIMPORT Oid binary_upgrade_next_mrng_pg_type_oid;
+extern PGDLLIMPORT Oid binary_upgrade_next_mrng_array_pg_type_oid;
 
 extern PGDLLIMPORT Oid binary_upgrade_next_heap_pg_class_oid;
 extern PGDLLIMPORT Oid binary_upgrade_next_index_pg_class_oid;
diff --git a/src/include/catalog/pg_aggregate.dat b/src/include/catalog/pg_aggregate.dat
index ffabe275c00..03bab74253d 100644
--- a/src/include/catalog/pg_aggregate.dat
+++ b/src/include/catalog/pg_aggregate.dat
@@ -538,6 +538,17 @@
   aggtransfn => 'bytea_string_agg_transfn',
   aggfinalfn => 'bytea_string_agg_finalfn', aggtranstype => 'internal' },
 
+# range
+{ aggfnoid => 'range_intersect_agg(anyrange)',
+  aggtransfn => 'range_intersect_agg_transfn',
+  aggcombinefn => 'range_intersect_agg_transfn', aggtranstype => 'anyrange' },
+{ aggfnoid => 'range_intersect_agg(anymultirange)',
+  aggtransfn => 'multirange_intersect_agg_transfn',
+  aggcombinefn => 'multirange_intersect_agg_transfn', aggtranstype => 'anymultirange' },
+{ aggfnoid => 'range_agg(anyrange)', aggtransfn => 'range_agg_transfn',
+  aggfinalfn => 'range_agg_finalfn', aggfinalextra => 't',
+  aggtranstype => 'internal' },
+
 # json
 { aggfnoid => 'json_agg', aggtransfn => 'json_agg_transfn',
   aggfinalfn => 'json_agg_finalfn', aggtranstype => 'internal' },
diff --git a/src/include/catalog/pg_amop.dat b/src/include/catalog/pg_amop.dat
index 2c899f19d92..78d7d2c5232 100644
--- a/src/include/catalog/pg_amop.dat
+++ b/src/include/catalog/pg_amop.dat
@@ -1374,6 +1374,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 db3e8c2d01a..9d423d535cd 100644
--- a/src/include/catalog/pg_amproc.dat
+++ b/src/include/catalog/pg_amproc.dat
@@ -285,6 +285,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' },
 { amprocfamily => 'btree/xid8_ops', amproclefttype => 'xid8',
@@ -457,6 +459,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',
@@ -1280,12 +1287,13 @@
   amprocrighttype => 'anyrange', amprocnum => '4',
   amproc => 'brin_inclusion_union' },
 { amprocfamily => 'brin/range_inclusion_ops', amproclefttype => 'anyrange',
-  amprocrighttype => 'anyrange', amprocnum => '11', amproc => 'range_merge' },
+  amprocrighttype => 'anyrange', amprocnum => '11',
+  amproc => 'range_merge(anyrange,anyrange)' },
 { amprocfamily => 'brin/range_inclusion_ops', amproclefttype => 'anyrange',
   amprocrighttype => 'anyrange', amprocnum => '13',
   amproc => 'range_contains' },
 { amprocfamily => 'brin/range_inclusion_ops', amproclefttype => 'anyrange',
-  amprocrighttype => 'anyrange', amprocnum => '14', amproc => 'isempty' },
+  amprocrighttype => 'anyrange', amprocnum => '14', amproc => 'isempty(anyrange)' },
 
 # minmax pg_lsn
 { amprocfamily => 'brin/pg_lsn_minmax_ops', amproclefttype => 'pg_lsn',
diff --git a/src/include/catalog/pg_cast.dat b/src/include/catalog/pg_cast.dat
index 5a58f50fbb2..d6ca624addc 100644
--- a/src/include/catalog/pg_cast.dat
+++ b/src/include/catalog/pg_cast.dat
@@ -530,4 +530,17 @@
 { castsource => 'jsonb', casttarget => 'float8', castfunc => 'float8(jsonb)',
   castcontext => 'e', castmethod => 'f' },
 
+# range to multirange
+{ castsource => 'int4range', casttarget => 'int4multirange', castfunc => 'int4multirange(int4range)',
+  castcontext => 'e', castmethod => 'f' },
+{ castsource => 'int8range', casttarget => 'int8multirange', castfunc => 'int8multirange(int8range)',
+  castcontext => 'e', castmethod => 'f' },
+{ castsource => 'numrange', casttarget => 'nummultirange', castfunc => 'nummultirange(numrange)',
+  castcontext => 'e', castmethod => 'f' },
+{ castsource => 'daterange', casttarget => 'datemultirange', castfunc => 'datemultirange(daterange)',
+  castcontext => 'e', castmethod => 'f' },
+{ castsource => 'tsrange', casttarget => 'tsmultirange', castfunc => 'tsmultirange(tsrange)',
+  castcontext => 'e', castmethod => 'f' },
+{ castsource => 'tstzrange', casttarget => 'tstzmultirange', castfunc => 'tstzmultirange(tstzrange)',
+  castcontext => 'e', castmethod => 'f' },
 ]
diff --git a/src/include/catalog/pg_opclass.dat b/src/include/catalog/pg_opclass.dat
index be5712692fe..4c5e475ff7b 100644
--- a/src/include/catalog/pg_opclass.dat
+++ b/src/include/catalog/pg_opclass.dat
@@ -232,6 +232,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 7ae19235ee2..f44a826e2e9 100644
--- a/src/include/catalog/pg_operator.dat
+++ b/src/include/catalog/pg_operator.dat
@@ -3277,5 +3277,174 @@
   oprname => '@@', oprleft => 'jsonb', oprright => 'jsonpath',
   oprresult => 'bool', oprcode => 'jsonb_path_match_opr(jsonb,jsonpath)',
   oprrest => 'matchingsel', oprjoin => 'matchingjoinsel' },
+{ 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 => 'multirangesel', 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 => 'multirangesel', 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 => 'multirangesel', 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 => 'multirangesel', oprjoin => 'scalargtjoinsel' },
+{ oid => '8073', oid_symbol => 'OID_RANGE_OVERLAPS_MULTIRANGE_OP',
+  descr => 'overlaps',
+  oprname => '&&', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '&&(anymultirange,anyrange)',
+  oprcode => 'range_overlaps_multirange', oprrest => 'multirangesel',
+  oprjoin => 'areajoinsel' },
+{ oid => '8074', oid_symbol => 'OID_MULTIRANGE_OVERLAPS_RANGE_OP', descr => 'contains',
+  oprname => '&&', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcom => '&&(anyrange,anymultirange)',
+  oprcode => 'multirange_overlaps_range', oprrest => 'multirangesel',
+  oprjoin => 'areajoinsel' },
+{ oid => '8075', oid_symbol => 'OID_MULTIRANGE_OVERLAPS_MULTIRANGE_OP', descr => 'contains',
+  oprname => '&&', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '&&(anymultirange,anymultirange)',
+  oprcode => 'multirange_overlaps_multirange', oprrest => 'multirangesel',
+  oprjoin => 'areajoinsel' },
+{ oid => '8064', oid_symbol => 'OID_MULTIRANGE_CONTAINS_ELEM_OP',
+  descr => 'contains',
+  oprname => '@>', oprleft => 'anymultirange', oprright => 'anyelement',
+  oprresult => 'bool', oprcom => '<@(anyelement,anymultirange)',
+  oprcode => 'multirange_contains_elem', oprrest => 'multirangesel',
+  oprjoin => 'contjoinsel' },
+{ oid => '8065', oid_symbol => 'OID_MULTIRANGE_CONTAINS_RANGE_OP', descr => 'contains',
+  oprname => '@>', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcom => '<@(anyrange,anymultirange)',
+  oprcode => 'multirange_contains_range', oprrest => 'multirangesel',
+  oprjoin => 'contjoinsel' },
+{ oid => '8066', oid_symbol => 'OID_MULTIRANGE_CONTAINS_MULTIRANGE_OP', descr => 'contains',
+  oprname => '@>', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '<@(anymultirange,anymultirange)',
+  oprcode => 'multirange_contains_multirange', oprrest => 'multirangesel',
+  oprjoin => 'contjoinsel' },
+{ oid => '8067', oid_symbol => 'OID_MULTIRANGE_ELEM_CONTAINED_OP',
+  descr => 'is contained by',
+  oprname => '<@', oprleft => 'anyelement', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '@>(anymultirange,anyelement)',
+  oprcode => 'elem_contained_by_multirange', oprrest => 'multirangesel',
+  oprjoin => 'contjoinsel' },
+{ oid => '8068', oid_symbol => 'OID_MULTIRANGE_RANGE_CONTAINED_OP',
+  descr => 'is contained by',
+  oprname => '<@', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '@>(anymultirange,anyrange)',
+  oprcode => 'range_contained_by_multirange', oprrest => 'multirangesel',
+  oprjoin => 'contjoinsel' },
+{ oid => '8069', oid_symbol => 'OID_MULTIRANGE_MULTIRANGE_CONTAINED_OP',
+  descr => 'is contained by',
+  oprname => '<@', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '@>(anymultirange,anymultirange)',
+  oprcode => 'multirange_contained_by_multirange', oprrest => 'multirangesel',
+  oprjoin => 'contjoinsel' },
+{ oid => '8106', oid_symbol => 'OID_RANGE_OVERLAPS_LEFT_MULTIRANGE_OP',
+  descr => 'overlaps or is left of',
+  oprname => '&<', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcode => 'range_overleft_multirange', oprrest => 'multirangesel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8107', oid_symbol => 'OID_MULTIRANGE_OVERLAPS_LEFT_RANGE_OP',
+  descr => 'overlaps or is left of',
+  oprname => '&<', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcode => 'multirange_overleft_range', oprrest => 'multirangesel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8108', oid_symbol => 'OID_MULTIRANGE_OVERLAPS_LEFT_MULTIRANGE_OP',
+  descr => 'overlaps or is left of',
+  oprname => '&<', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcode => 'multirange_overleft_multirange', oprrest => 'multirangesel',
+  oprjoin => 'scalargtjoinsel' },
+{ oid => '8109', oid_symbol => 'OID_RANGE_OVERLAPS_RIGHT_MULTIRANGE_OP',
+  descr => 'overlaps or is right of',
+  oprname => '&>', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcode => 'range_overright_multirange', oprrest => 'multirangesel',
+  oprjoin => 'scalargtjoinsel' },
+{ oid => '8110', oid_symbol => 'OID_MULTIRANGE_OVERLAPS_RIGHT_RANGE_OP',
+  descr => 'overlaps or is right of',
+  oprname => '&>', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcode => 'multirange_overright_range', oprrest => 'multirangesel',
+  oprjoin => 'scalargtjoinsel' },
+{ oid => '8111', oid_symbol => 'OID_MULTIRANGE_OVERLAPS_RIGHT_MULTIRANGE_OP',
+  descr => 'overlaps or is right of',
+  oprname => '&>', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcode => 'multirange_overright_multirange', oprrest => 'multirangesel',
+  oprjoin => 'scalargtjoinsel' },
+{ oid => '8091', oid_symbol => 'OID_RANGE_ADJACENT_MULTIRANGE_OP', descr => 'is adjacent to',
+  oprname => '-|-', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '-|-(anymultirange,anyrange)',
+  oprcode => 'range_adjacent_multirange', oprrest => 'matchingsel',
+  oprjoin => 'matchingjoinsel' },
+{ oid => '8092', oid_symbol => 'OID_MULTIRANGE_ADJACENT_RANGE_OP', descr => 'is adjacent to',
+  oprname => '-|-', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcom => '-|-(anyrange,anymultirange)',
+  oprcode => 'multirange_adjacent_range', oprrest => 'matchingsel',
+  oprjoin => 'matchingjoinsel' },
+{ oid => '8093', oid_symbol => 'OID_MULTIRANGE_ADJACENT_MULTIRANGE_OP', descr => 'is adjacent to',
+  oprname => '-|-', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '-|-(anymultirange,anymultirange)',
+  oprcode => 'multirange_adjacent_multirange', oprrest => 'matchingsel',
+  oprjoin => 'matchingjoinsel' },
+{ oid => '8117', descr => 'multirange union',
+  oprname => '+', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'anymultirange', oprcom => '+(anymultirange,anymultirange)',
+  oprcode => 'multirange_union' },
+{ oid => '8123', descr => 'multirange minus',
+  oprname => '-', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'anymultirange', oprcode => 'multirange_minus' },
+{ oid => '8129', descr => 'multirange intersect',
+  oprname => '*', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'anymultirange', oprcom => '*(anymultirange,anymultirange)',
+  oprcode => 'multirange_intersect' },
+{ oid => '8082', oid_symbol => 'OID_RANGE_LEFT_MULTIRANGE_OP', descr => 'is left of',
+  oprname => '<<', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '>>(anymultirange,anyrange)',
+  oprcode => 'range_before_multirange', oprrest => 'multirangesel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8083', oid_symbol => 'OID_MULTIRANGE_LEFT_RANGE_OP', descr => 'is left of',
+  oprname => '<<', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcom => '>>(anyrange,anymultirange)',
+  oprcode => 'multirange_before_range', oprrest => 'multirangesel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8084', oid_symbol => 'OID_MULTIRANGE_LEFT_MULTIRANGE_OP', descr => 'is left of',
+  oprname => '<<', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '>>(anymultirange,anymultirange)',
+  oprcode => 'multirange_before_multirange', oprrest => 'multirangesel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8085', oid_symbol => 'OID_RANGE_RIGHT_MULTIRANGE_OP', descr => 'is right of',
+  oprname => '>>', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '<<(anymultirange,anyrange)',
+  oprcode => 'range_after_multirange', oprrest => 'multirangesel',
+  oprjoin => 'scalargtjoinsel' },
+{ oid => '8086', oid_symbol => 'OID_MULTIRANGE_RIGHT_RANGE_OP', descr => 'is right of',
+  oprname => '>>', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcom => '<<(anyrange,anymultirange)',
+  oprcode => 'multirange_after_range', oprrest => 'multirangesel',
+  oprjoin => 'scalargtjoinsel' },
+{ oid => '8087', oid_symbol => 'OID_MULTIRANGE_RIGHT_MULTIRANGE_OP', descr => 'is right of',
+  oprname => '>>', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '<<(anymultirange,anymultirange)',
+  oprcode => 'multirange_after_multirange', oprrest => 'multirangesel',
+  oprjoin => 'scalargtjoinsel' },
 
 ]
diff --git a/src/include/catalog/pg_opfamily.dat b/src/include/catalog/pg_opfamily.dat
index 11c7ad2c145..3ff81026fc7 100644
--- a/src/include/catalog/pg_opfamily.dat
+++ b/src/include/catalog/pg_opfamily.dat
@@ -232,5 +232,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 e7fbda9f81a..c771d12dba2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -7252,6 +7252,14 @@
   proname => 'anycompatiblerange_out', provolatile => 's',
   prorettype => 'cstring', proargtypes => 'anycompatiblerange',
   prosrc => 'anycompatiblerange_out' },
+{ oid => '8154', descr => 'I/O',
+  proname => 'anycompatiblemultirange_in', provolatile => 's',
+  prorettype => 'anycompatiblemultirange', proargtypes => 'cstring oid int4',
+  prosrc => 'anycompatiblemultirange_in' },
+{ oid => '8155', descr => 'I/O',
+  proname => 'anycompatiblemultirange_out', provolatile => 's',
+  prorettype => 'cstring', proargtypes => 'anycompatiblemultirange',
+  prosrc => 'anycompatiblemultirange_out' },
 
 # tablesample method handlers
 { oid => '3313', descr => 'BERNOULLI tablesample method handler',
@@ -9872,6 +9880,10 @@
   descr => 'the smallest range which includes both of the given ranges',
   proname => 'range_merge', prorettype => 'anyrange',
   proargtypes => 'anyrange anyrange', prosrc => 'range_merge' },
+{ oid => '8143',
+  descr => 'the smallest range which includes the whole multirange',
+  proname => 'range_merge', prorettype => 'anyrange',
+  proargtypes => 'anymultirange', prosrc => 'range_merge_from_multirange' },
 { oid => '3868',
   proname => 'range_intersect', prorettype => 'anyrange',
   proargtypes => 'anyrange anyrange', prosrc => 'range_intersect' },
@@ -9921,6 +9933,13 @@
 { oid => '3169', descr => 'restriction selectivity for range operators',
   proname => 'rangesel', provolatile => 's', prorettype => 'float8',
   proargtypes => 'internal oid internal int4', prosrc => 'rangesel' },
+{ oid => '8133', descr => 'range aggregate by intersecting',
+  proname => 'range_intersect_agg_transfn', prorettype => 'anyrange',
+  proargtypes => 'anyrange anyrange', prosrc => 'range_intersect_agg_transfn'},
+{ oid => '8134', descr => 'range aggregate by intersecting',
+  proname => 'range_intersect_agg', prokind => 'a', proisstrict => 'f',
+  prorettype => 'anyrange', proargtypes => 'anyrange',
+  prosrc => 'aggregate_dummy'},
 
 { oid => '3914', descr => 'convert an int4 range to canonical form',
   proname => 'int4range_canonical', prorettype => 'int4range',
@@ -9989,6 +10008,261 @@
   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 => '8094', descr => 'lower bound of multirange',
+  proname => 'lower', prorettype => 'anyelement', proargtypes => 'anymultirange',
+  prosrc => 'multirange_lower' },
+{ oid => '8095', descr => 'upper bound of multirange',
+  proname => 'upper', prorettype => 'anyelement', proargtypes => 'anymultirange',
+  prosrc => 'multirange_upper' },
+{ oid => '8057', descr => 'is the multirange empty?',
+  proname => 'isempty', prorettype => 'bool',
+  proargtypes => 'anymultirange', prosrc => 'multirange_empty' },
+{ oid => '8096', descr => 'is the multirange\'s lower bound inclusive?',
+  proname => 'lower_inc', prorettype => 'bool', proargtypes => 'anymultirange',
+  prosrc => 'multirange_lower_inc' },
+{ oid => '8097', descr => 'is the multirange\'s upper bound inclusive?',
+  proname => 'upper_inc', prorettype => 'bool', proargtypes => 'anymultirange',
+  prosrc => 'multirange_upper_inc' },
+{ oid => '8098', descr => 'is the multirange\'s lower bound infinite?',
+  proname => 'lower_inf', prorettype => 'bool', proargtypes => 'anymultirange',
+  prosrc => 'multirange_lower_inf' },
+{ oid => '8099', descr => 'is the multirange\'s upper bound infinite?',
+  proname => 'upper_inf', prorettype => 'bool', proargtypes => 'anymultirange',
+  prosrc => 'multirange_upper_inf' },
+{ oid => '8008', descr => 'multirange typanalyze',
+  proname => 'multirange_typanalyze', provolatile => 's', prorettype => 'bool',
+  proargtypes => 'internal', prosrc => 'multirange_typanalyze' },
+{ oid => '8156', descr => 'restriction selectivity for multirange operators',
+  proname => 'multirangesel', provolatile => 's', prorettype => 'float8',
+  proargtypes => 'internal oid internal int4', prosrc => 'multirangesel' },
+{ oid => '8033',
+  proname => 'multirange_eq', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_eq' },
+{ oid => '8034',
+  proname => 'multirange_ne', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_ne' },
+{ oid => '8070',
+  proname => 'range_overlaps_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_overlaps_multirange' },
+{ oid => '8071',
+  proname => 'multirange_overlaps_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_overlaps_range' },
+{ oid => '8072',
+  proname => 'multirange_overlaps_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_overlaps_multirange' },
+{ oid => '8058',
+  proname => 'multirange_contains_elem', prorettype => 'bool',
+  proargtypes => 'anymultirange anyelement', prosrc => 'multirange_contains_elem' },
+{ oid => '8059',
+  proname => 'multirange_contains_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_contains_range' },
+{ oid => '8060',
+  proname => 'multirange_contains_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_contains_multirange' },
+{ oid => '8061',
+  proname => 'elem_contained_by_multirange', prorettype => 'bool',
+  proargtypes => 'anyelement anymultirange', prosrc => 'elem_contained_by_multirange' },
+{ oid => '8062',
+  proname => 'range_contained_by_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_contained_by_multirange' },
+{ oid => '8063',
+  proname => 'multirange_contained_by_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_contained_by_multirange' },
+{ oid => '8088',
+  proname => 'range_adjacent_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_adjacent_multirange' },
+{ oid => '8089',
+  proname => 'multirange_adjacent_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_adjacent_multirange' },
+{ oid => '8090',
+  proname => 'multirange_adjacent_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_adjacent_range' },
+{ oid => '8076',
+  proname => 'range_before_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_before_multirange' },
+{ oid => '8077',
+  proname => 'multirange_before_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_before_range' },
+{ oid => '8078',
+  proname => 'multirange_before_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_before_multirange' },
+{ oid => '8079',
+  proname => 'range_after_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_after_multirange' },
+{ oid => '8080',
+  proname => 'multirange_after_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_after_range' },
+{ oid => '8081',
+  proname => 'multirange_after_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_after_multirange' },
+{ oid => '8100',
+  proname => 'range_overleft_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_overleft_multirange' },
+{ oid => '8101',
+  proname => 'multirange_overleft_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_overleft_range' },
+{ oid => '8102',
+  proname => 'multirange_overleft_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_overleft_multirange' },
+{ oid => '8103',
+  proname => 'range_overright_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_overright_multirange' },
+{ oid => '8104',
+  proname => 'multirange_overright_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_overright_range' },
+{ oid => '8105',
+  proname => 'multirange_overright_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_overright_multirange' },
+{ oid => '8114',
+  proname => 'multirange_union', prorettype => 'anymultirange',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_union' },
+{ oid => '8120',
+  proname => 'multirange_minus', prorettype => 'anymultirange',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_minus' },
+{ oid => '8126',
+  proname => 'multirange_intersect', prorettype => 'anymultirange',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_intersect' },
+{ oid => '8022', descr => 'less-equal-greater',
+  proname => 'multirange_cmp', prorettype => 'int4',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_cmp' },
+{ oid => '8023',
+  proname => 'multirange_lt', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_lt' },
+{ oid => '8024',
+  proname => 'multirange_le', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_le' },
+{ oid => '8025',
+  proname => 'multirange_ge', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_ge' },
+{ oid => '8026',
+  proname => 'multirange_gt', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_gt' },
+{ oid => '8038', descr => 'hash a multirange',
+  proname => 'hash_multirange', prorettype => 'int4', proargtypes => 'anymultirange',
+  prosrc => 'hash_multirange' },
+{ oid => '8039', descr => 'hash a multirange',
+  proname => 'hash_multirange_extended', prorettype => 'int8',
+  proargtypes => 'anymultirange int8', prosrc => 'hash_multirange_extended' },
+
+{ oid => '8045', descr => 'int4multirange constructor',
+  proname => 'int4multirange', proisstrict => 'f',
+  prorettype => 'int4multirange', proargtypes => '',
+  prosrc => 'multirange_constructor0' },
+{ oid => '8146', descr => 'int4multirange constructor',
+  proname => 'int4multirange', proisstrict => 't',
+  prorettype => 'int4multirange', proargtypes => 'int4range',
+  prosrc => 'multirange_constructor1' },
+{ oid => '8046', descr => 'int4multirange constructor',
+  proname => 'int4multirange', provariadic => 'int4range', proisstrict => 'f',
+  prorettype => 'int4multirange', proargtypes => '_int4range',
+  proallargtypes => '{_int4range}', proargmodes => '{v}',
+  prosrc => 'multirange_constructor2' },
+{ oid => '8047', descr => 'nummultirange constructor',
+  proname => 'nummultirange', proisstrict => 'f',
+  prorettype => 'nummultirange', proargtypes => '',
+  prosrc => 'multirange_constructor0' },
+{ oid => '8147', descr => 'nummultirange constructor',
+  proname => 'nummultirange', proisstrict => 't',
+  prorettype => 'nummultirange', proargtypes => 'numrange',
+  prosrc => 'multirange_constructor1' },
+{ oid => '8048', descr => 'nummultirange constructor',
+  proname => 'nummultirange', provariadic => 'numrange', proisstrict => 'f',
+  prorettype => 'nummultirange', proargtypes => '_numrange',
+  proallargtypes => '{_numrange}', proargmodes => '{v}',
+  prosrc => 'multirange_constructor2' },
+{ oid => '8049', descr => 'tsmultirange constructor',
+  proname => 'tsmultirange', proisstrict => 'f',
+  prorettype => 'tsmultirange', proargtypes => '',
+  prosrc => 'multirange_constructor0' },
+{ oid => '8148', descr => 'tsmultirange constructor',
+  proname => 'tsmultirange', proisstrict => 't',
+  prorettype => 'tsmultirange', proargtypes => 'tsrange',
+  prosrc => 'multirange_constructor1' },
+{ oid => '8050', descr => 'tsmultirange constructor',
+  proname => 'tsmultirange', provariadic => 'tsrange', proisstrict => 'f',
+  prorettype => 'tsmultirange', proargtypes => '_tsrange',
+  proallargtypes => '{_tsrange}', proargmodes => '{v}',
+  prosrc => 'multirange_constructor2' },
+{ oid => '8051', descr => 'tstzmultirange constructor',
+  proname => 'tstzmultirange', proisstrict => 'f',
+  prorettype => 'tstzmultirange', proargtypes => '',
+  prosrc => 'multirange_constructor0' },
+{ oid => '8149', descr => 'tstzmultirange constructor',
+  proname => 'tstzmultirange', proisstrict => 't',
+  prorettype => 'tstzmultirange', proargtypes => 'tstzrange',
+  prosrc => 'multirange_constructor1' },
+{ oid => '8052', descr => 'tstzmultirange constructor',
+  proname => 'tstzmultirange', provariadic => 'tstzrange', proisstrict => 'f',
+  prorettype => 'tstzmultirange', proargtypes => '_tstzrange',
+  proallargtypes => '{_tstzrange}', proargmodes => '{v}',
+  prosrc => 'multirange_constructor2' },
+{ oid => '8053', descr => 'datemultirange constructor',
+  proname => 'datemultirange', proisstrict => 'f',
+  prorettype => 'datemultirange', proargtypes => '',
+  prosrc => 'multirange_constructor0' },
+{ oid => '8150', descr => 'datemultirange constructor',
+  proname => 'datemultirange', proisstrict => 't',
+  prorettype => 'datemultirange', proargtypes => 'daterange',
+  prosrc => 'multirange_constructor1' },
+{ oid => '8054', descr => 'datemultirange constructor',
+  proname => 'datemultirange', provariadic => 'daterange', proisstrict => 'f',
+  prorettype => 'datemultirange', proargtypes => '_daterange',
+  proallargtypes => '{_daterange}', proargmodes => '{v}',
+  prosrc => 'multirange_constructor2' },
+{ oid => '8055', descr => 'int8multirange constructor',
+  proname => 'int8multirange', proisstrict => 'f',
+  prorettype => 'int8multirange', proargtypes => '',
+  prosrc => 'multirange_constructor0' },
+{ oid => '8151', descr => 'int8multirange constructor',
+  proname => 'int8multirange', proisstrict => 't',
+  prorettype => 'int8multirange', proargtypes => 'int8range',
+  prosrc => 'multirange_constructor1' },
+{ oid => '8056', descr => 'int8multirange constructor',
+  proname => 'int8multirange', provariadic => 'int8range', proisstrict => 'f',
+  prorettype => 'int8multirange', proargtypes => '_int8range',
+  proallargtypes => '{_int8range}', proargmodes => '{v}',
+  prosrc => 'multirange_constructor2' },
+{ oid => '8152', descr => 'anymultirange cast',
+  proname => 'multirange', proisstrict => 't',
+  prorettype => 'anymultirange', proargtypes => 'anyrange',
+  prosrc => 'multirange_constructor1' },
+{ oid => '8130', descr => 'aggregate transition function',
+  proname => 'range_agg_transfn', proisstrict => 'f', prorettype => 'internal',
+  proargtypes => 'internal anyrange', prosrc => 'range_agg_transfn' },
+{ oid => '8131', descr => 'aggregate final function',
+  proname => 'range_agg_finalfn', proisstrict => 'f', prorettype => 'anymultirange',
+  proargtypes => 'internal anyrange', prosrc => 'range_agg_finalfn' },
+{ oid => '8132', descr => 'combine aggregate input into a multirange',
+  proname => 'range_agg', prokind => 'a', proisstrict => 'f',
+  prorettype => 'anymultirange', proargtypes => 'anyrange',
+  prosrc => 'aggregate_dummy' },
+{ oid => '8135', descr => 'range aggregate by intersecting',
+  proname => 'multirange_intersect_agg_transfn', prorettype => 'anymultirange',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_intersect_agg_transfn'},
+{ oid => '8136', descr => 'range aggregate by intersecting',
+  proname => 'range_intersect_agg', prokind => 'a', proisstrict => 'f',
+  prorettype => 'anymultirange', proargtypes => 'anymultirange',
+  prosrc => 'aggregate_dummy'},
+
 # date, time, timestamp constructors
 { oid => '3846', descr => 'construct date',
   proname => 'make_date', prorettype => 'date', proargtypes => 'int4 int4 int4',
@@ -10371,6 +10645,14 @@
   proname => 'binary_upgrade_set_next_array_pg_type_oid', provolatile => 'v',
   proparallel => 'r', prorettype => 'void', proargtypes => 'oid',
   prosrc => 'binary_upgrade_set_next_array_pg_type_oid' },
+{ oid => '8144', descr => 'for use by pg_upgrade',
+  proname => 'binary_upgrade_set_next_multirange_pg_type_oid', provolatile => 'v',
+  proparallel => 'r', prorettype => 'void', proargtypes => 'oid',
+  prosrc => 'binary_upgrade_set_next_multirange_pg_type_oid' },
+{ oid => '8145', descr => 'for use by pg_upgrade',
+  proname => 'binary_upgrade_set_next_multirange_array_pg_type_oid', provolatile => 'v',
+  proparallel => 'r', prorettype => 'void', proargtypes => 'oid',
+  prosrc => 'binary_upgrade_set_next_multirange_array_pg_type_oid' },
 { oid => '3586', descr => 'for use by pg_upgrade',
   proname => 'binary_upgrade_set_next_heap_pg_class_oid', provolatile => 'v',
   proparallel => 'r', prorettype => 'void', proargtypes => 'oid',
diff --git a/src/include/catalog/pg_range.dat b/src/include/catalog/pg_range.dat
index 479754c2455..10060255c95 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',
+  rngmultitypid => 'int4multirange' },
 { rngtypid => 'numrange', rngsubtype => 'numeric',
   rngsubopc => 'btree/numeric_ops', rngcanonical => '-',
-  rngsubdiff => 'numrange_subdiff' },
+  rngsubdiff => 'numrange_subdiff', rngmultitypid => 'nummultirange' },
 { rngtypid => 'tsrange', rngsubtype => 'timestamp',
   rngsubopc => 'btree/timestamp_ops', rngcanonical => '-',
-  rngsubdiff => 'tsrange_subdiff' },
+  rngsubdiff => 'tsrange_subdiff', rngmultitypid => 'tsmultirange' },
 { rngtypid => 'tstzrange', rngsubtype => 'timestamptz',
   rngsubopc => 'btree/timestamptz_ops', rngcanonical => '-',
-  rngsubdiff => 'tstzrange_subdiff' },
+  rngsubdiff => 'tstzrange_subdiff', rngmultitypid => 'tstzmultirange' },
 { rngtypid => 'daterange', rngsubtype => 'date', rngsubopc => 'btree/date_ops',
-  rngcanonical => 'daterange_canonical', rngsubdiff => 'daterange_subdiff' },
+  rngcanonical => 'daterange_canonical', rngsubdiff => 'daterange_subdiff',
+  rngmultitypid => 'datemultirange' },
 { rngtypid => 'int8range', rngsubtype => 'int8', rngsubopc => 'btree/int8_ops',
-  rngcanonical => 'int8range_canonical', rngsubdiff => 'int8range_subdiff' },
+  rngcanonical => 'int8range_canonical', rngsubdiff => 'int8range_subdiff',
+  rngmultitypid => 'int8multirange' },
 
 ]
diff --git a/src/include/catalog/pg_range.h b/src/include/catalog/pg_range.h
index c28bb57b6c9..07e6dbba667 100644
--- a/src/include/catalog/pg_range.h
+++ b/src/include/catalog/pg_range.h
@@ -34,6 +34,9 @@ CATALOG(pg_range,3541,RangeRelationId)
 	/* OID of range's element type (subtype) */
 	Oid			rngsubtype BKI_LOOKUP(pg_type);
 
+	/* OID of the range's multirange type */
+	Oid			rngmultitypid BKI_LOOKUP(pg_type);
+
 	/* collation for this range type, or 0 */
 	Oid			rngcollation BKI_DEFAULT(0);
 
@@ -57,13 +60,17 @@ typedef FormData_pg_range *Form_pg_range;
 DECLARE_UNIQUE_INDEX(pg_range_rngtypid_index, 3542, on pg_range using btree(rngtypid oid_ops));
 #define RangeTypidIndexId					3542
 
+DECLARE_UNIQUE_INDEX(pg_range_rngmultitypid_index, 8001, on pg_range using btree(rngmultitypid oid_ops));
+#define RangeMultirangeTypidIndexId			8001
+
+
 /*
  * prototypes for functions in pg_range.c
  */
 
 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 21a467a7a7a..35953b51f9f 100644
--- a/src/include/catalog/pg_type.dat
+++ b/src/include/catalog/pg_type.dat
@@ -490,6 +490,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 => 'R', 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 => 'R', 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 => 'R', 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 => 'R', 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 => 'R', 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 => 'R', 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
@@ -619,5 +653,16 @@
   typtype => 'p', typcategory => 'P', typinput => 'anycompatiblerange_in',
   typoutput => 'anycompatiblerange_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' },
+{ oid => '8153',
+  descr => 'pseudo-type representing a multirange over a polymorphic common type',
+  typname => 'anycompatiblemultirange', typlen => '-1', typbyval => 'f',
+  typtype => 'p', typcategory => 'P', typinput => 'anycompatiblemultirange_in',
+  typoutput => 'anycompatiblemultirange_out', typreceive => '-', typsend => '-',
+  typalign => 'd', typstorage => 'x' },
 
 ]
diff --git a/src/include/catalog/pg_type.h b/src/include/catalog/pg_type.h
index 6099e5f57ca..c5dd43a0abe 100644
--- a/src/include/catalog/pg_type.h
+++ b/src/include/catalog/pg_type.h
@@ -270,6 +270,7 @@ DECLARE_UNIQUE_INDEX(pg_type_typname_nsp_index, 2704, on pg_type using btree(typ
 #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 */
 
@@ -311,13 +312,15 @@ DECLARE_UNIQUE_INDEX(pg_type_typname_nsp_index, 2704, on pg_type using btree(typ
 	 (typid) == ANYARRAYOID || \
 	 (typid) == ANYNONARRAYOID || \
 	 (typid) == ANYENUMOID || \
-	 (typid) == ANYRANGEOID)
+	 (typid) == ANYRANGEOID || \
+	 (typid) == ANYMULTIRANGEOID)
 
 #define IsPolymorphicTypeFamily2(typid)  \
 	((typid) == ANYCOMPATIBLEOID || \
 	 (typid) == ANYCOMPATIBLEARRAYOID || \
 	 (typid) == ANYCOMPATIBLENONARRAYOID || \
-	 (typid) == ANYCOMPATIBLERANGEOID)
+	 (typid) == ANYCOMPATIBLERANGEOID || \
+	 (typid) == ANYCOMPATIBLEMULTIRANGEOID)
 
 /*
  * Backwards compatibility for ancient random spellings of pg_type OID macros.
@@ -385,4 +388,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/commands/typecmds.h b/src/include/commands/typecmds.h
index 23130895af4..989914dfe11 100644
--- a/src/include/commands/typecmds.h
+++ b/src/include/commands/typecmds.h
@@ -29,6 +29,8 @@ extern ObjectAddress DefineRange(CreateRangeStmt *stmt);
 extern ObjectAddress AlterEnum(AlterEnumStmt *stmt);
 extern ObjectAddress DefineCompositeType(RangeVar *typevar, List *coldeflist);
 extern Oid	AssignTypeArrayOid(void);
+extern Oid	AssignTypeMultirangeOid(void);
+extern Oid	AssignTypeMultirangeArrayOid(void);
 
 extern ObjectAddress AlterDomainDefault(List *names, Node *defaultRaw);
 extern ObjectAddress AlterDomainNotNull(List *names, bool notNull);
diff --git a/src/include/utils/lsyscache.h b/src/include/utils/lsyscache.h
index fecfe1f4f6e..ed84908d727 100644
--- a/src/include/utils/lsyscache.h
+++ b/src/include/utils/lsyscache.h
@@ -157,6 +157,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);
@@ -183,6 +184,8 @@ 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_collation(Oid rangeOid);
+extern Oid	get_range_multirange(Oid rangeOid);
+extern Oid	get_multirange_range(Oid multirangeOid);
 extern Oid	get_index_column_opclass(Oid index_oid, int attno);
 extern bool get_index_isreplident(Oid index_oid);
 extern bool get_index_isvalid(Oid index_oid);
diff --git a/src/include/utils/multirangetypes.h b/src/include/utils/multirangetypes.h
new file mode 100644
index 00000000000..1ec2e25c6b3
--- /dev/null
+++ b/src/include/utils/multirangetypes.h
@@ -0,0 +1,144 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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"
+#include "utils/expandeddatum.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
+{
+	int32		vl_len_;		/* varlena header (do not touch directly!) */
+	Oid			multirangetypid;	/* multirange type's own OID */
+	uint32		rangeCount;		/* the number of ranges */
+
+	/*
+	 * Following the count are the range objects themselves, as ShortRangeType
+	 * structs. 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;
+
+/*
+ * Since each RangeType includes its type oid, it seems wasteful to store them
+ * in multiranges like that on-disk. Instead we store ShortRangeType structs
+ * that have everything but the type varlena header and oid. But we do want the
+ * type oid in memory, so that we can call ordinary range functions. We use the
+ * EXPANDED TOAST mechansim to convert between them.
+ */
+typedef struct
+{
+	int32		vl_len_;		/* varlena header */
+	char		flags;			/* range flags */
+	char		_padding;		/* Bounds must be aligned */
+	/* Following the header are zero to two bound values. */
+} ShortRangeType;
+
+#define EMR_MAGIC 689375834		/* ID for debugging crosschecks */
+
+typedef struct ExpandedMultirangeHeader
+{
+	/* Standard header for expanded objects */
+	ExpandedObjectHeader hdr;
+
+	/* Magic value identifying an expanded array (for debugging only) */
+	int			emr_magic;
+
+	Oid			multirangetypid;	/* multirange type's own OID */
+	uint32		rangeCount;		/* the number of ranges */
+	RangeType **ranges;			/* array of ranges */
+
+	/*
+	 * flat_size is the current space requirement for the flat equivalent of
+	 * the expanded multirange, if known; otherwise it's 0.  We store this to
+	 * make consecutive calls of get_flat_size cheap.
+	 */
+	Size		flat_size;
+} ExpandedMultirangeHeader;
+
+/* Use these macros in preference to accessing these fields 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);
+extern bool multirange_contains_elem_internal(TypeCacheEntry *typcache, MultirangeType *mr,
+											  Datum elem);
+extern bool multirange_contains_range_internal(TypeCacheEntry *typcache, MultirangeType *mr,
+											   RangeType *r);
+extern bool multirange_contains_multirange_internal(TypeCacheEntry *typcache,
+													MultirangeType *mr1,
+													MultirangeType *mr2);
+extern bool range_overlaps_multirange_internal(TypeCacheEntry *typcache, RangeType *r,
+											   MultirangeType *mr);
+extern bool multirange_overlaps_multirange_internal(TypeCacheEntry *typcache,
+													MultirangeType *mr1,
+													MultirangeType *mr2);
+extern bool range_before_multirange_internal(TypeCacheEntry *typcache, RangeType *r,
+											 MultirangeType *mr);
+extern bool range_after_multirange_internal(TypeCacheEntry *typcache, RangeType *r,
+											MultirangeType *mr);
+extern bool multirange_before_multirange_internal(TypeCacheEntry *typcache,
+												  MultirangeType *mr1,
+												  MultirangeType *mr2);
+extern MultirangeType *multirange_minus_internal(Oid mltrngtypoid,
+												 TypeCacheEntry *rangetyp,
+												 int32 range_count1,
+												 RangeType **ranges1,
+												 int32 range_count2,
+												 RangeType **ranges2);
+extern MultirangeType *multirange_intersect_internal(Oid mltrngtypoid,
+													 TypeCacheEntry *rangetyp,
+													 int32 range_count1,
+													 RangeType **ranges1,
+													 int32 range_count2,
+													 RangeType **ranges2);
+
+/* assorted support functions */
+extern TypeCacheEntry *multirange_get_typcache(FunctionCallInfo fcinfo,
+											   Oid mltrngtypid);
+extern void multirange_deserialize(TypeCacheEntry *rangetyp,
+								   const 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);
+
+extern Datum expand_multirange(Datum multirangedatum, MemoryContext parentcontext, TypeCacheEntry *rangetyp);
+
+#endif							/* MULTIRANGETYPES_H */
diff --git a/src/include/utils/rangetypes.h b/src/include/utils/rangetypes.h
index b77c41cf1b8..139e19c7d66 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)
 
@@ -92,13 +94,21 @@ typedef struct
  * prototypes for functions defined in rangetypes.c
  */
 
-extern bool range_contains_elem_internal(TypeCacheEntry *typcache, const RangeType *r, Datum val);
-
 /* internal versions of the above */
+extern int	range_cmp_internal(TypeCacheEntry *typcache, const RangeType *r1,
+							   const RangeType *r2);
+extern uint32 hash_range_internal(TypeCacheEntry *typcache, const RangeType *r);
+extern uint64 hash_range_extended_internal(TypeCacheEntry *typcache, const RangeType *r,
+										   Datum seed);
+extern Datum range_lower_internal(TypeCacheEntry *typcache, const RangeType *r1,
+								  bool *isnull);
+extern Datum range_upper_internal(TypeCacheEntry *typcache, const RangeType *r1,
+								  bool *isnull);
 extern bool range_eq_internal(TypeCacheEntry *typcache, const RangeType *r1,
 							  const RangeType *r2);
 extern bool range_ne_internal(TypeCacheEntry *typcache, const RangeType *r1,
 							  const RangeType *r2);
+extern bool range_contains_elem_internal(TypeCacheEntry *typcache, const RangeType *r, Datum val);
 extern bool range_contains_internal(TypeCacheEntry *typcache, const RangeType *r1,
 									const RangeType *r2);
 extern bool range_contained_by_internal(TypeCacheEntry *typcache, const RangeType *r1,
@@ -115,8 +125,18 @@ extern bool range_overleft_internal(TypeCacheEntry *typcache, const RangeType *r
 									const RangeType *r2);
 extern bool range_overright_internal(TypeCacheEntry *typcache, const RangeType *r1,
 									 const RangeType *r2);
+extern RangeType *range_union_internal(TypeCacheEntry *typcache, RangeType *r1,
+									   RangeType *r2, bool strict);
+extern RangeType *range_minus_internal(TypeCacheEntry *typcache, RangeType *r1,
+									   RangeType *r2);
+extern RangeType *range_intersect_internal(TypeCacheEntry *typcache, const RangeType *r1,
+										   const RangeType *r2);
 
 /* assorted support functions */
+extern Size datum_compute_size(Size sz, Datum datum, bool typbyval,
+							   char typalign, int16 typlen, char typstorage);
+extern Pointer datum_write(Pointer ptr, Datum datum, bool typbyval,
+						   char typalign, int16 typlen, char typstorage);
 extern TypeCacheEntry *range_get_typcache(FunctionCallInfo fcinfo,
 										  Oid rngtypid);
 extern RangeType *range_serialize(TypeCacheEntry *typcache, RangeBound *lower,
@@ -125,6 +145,7 @@ extern void range_deserialize(TypeCacheEntry *typcache, const RangeType *range,
 							  RangeBound *lower, RangeBound *upper,
 							  bool *empty);
 extern char range_get_flags(const RangeType *range);
+extern bool range_has_flag(const RangeType *range, char flag);
 extern void range_set_contain_empty(RangeType *range);
 extern RangeType *make_range(TypeCacheEntry *typcache, RangeBound *lower,
 							 RangeBound *upper, bool empty);
@@ -132,8 +153,12 @@ extern int	range_cmp_bounds(TypeCacheEntry *typcache, const RangeBound *b1,
 							 const RangeBound *b2);
 extern int	range_cmp_bound_values(TypeCacheEntry *typcache, const RangeBound *b1,
 								   const RangeBound *b2);
+extern int	range_compare(const void *key1, const void *key2, void *arg);
 extern bool bounds_adjacent(TypeCacheEntry *typcache, RangeBound bound1,
 							RangeBound bound2);
 extern RangeType *make_empty_range(TypeCacheEntry *typcache);
+extern bool range_split_internal(TypeCacheEntry *typcache, const RangeType *r1,
+								 const RangeType *r2, RangeType **output1,
+								 RangeType **output2);
 
 #endif							/* RANGETYPES_H */
diff --git a/src/include/utils/selfuncs.h b/src/include/utils/selfuncs.h
index 3a2cfb7efa6..68ffd7761f1 100644
--- a/src/include/utils/selfuncs.h
+++ b/src/include/utils/selfuncs.h
@@ -39,6 +39,9 @@
 /* default selectivity estimate for range inequalities "A > b AND A < c" */
 #define DEFAULT_RANGE_INEQ_SEL	0.005
 
+/* default selectivity estimate for multirange inequalities "A > b AND A < c" */
+#define DEFAULT_MULTIRANGE_INEQ_SEL	0.005
+
 /* default selectivity estimate for pattern-match operators such as LIKE */
 #define DEFAULT_MATCH_SEL	0.005
 
diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h
index f27b73d76d0..e8f393520a3 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -79,6 +79,7 @@ enum SysCacheIdentifier
 	PUBLICATIONOID,
 	PUBLICATIONREL,
 	PUBLICATIONRELMAP,
+	RANGEMULTIRANGE,
 	RANGETYPE,
 	RELNAMENSP,
 	RELOID,
diff --git a/src/include/utils/typcache.h b/src/include/utils/typcache.h
index cdd20e56d70..bb497e8ee08 100644
--- a/src/include/utils/typcache.h
+++ b/src/include/utils/typcache.h
@@ -100,6 +100,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; /* multirange's range underlying type */
+
 	/*
 	 * Domain's base type and typmod if it's a domain type.  Zeroes if not
 	 * domain, or if information hasn't been requested.
@@ -127,22 +132,23 @@ typedef struct TypeCacheEntry
 } TypeCacheEntry;
 
 /* Bit flags to indicate which fields a given caller needs to have set */
-#define TYPECACHE_EQ_OPR			0x0001
-#define TYPECACHE_LT_OPR			0x0002
-#define TYPECACHE_GT_OPR			0x0004
-#define TYPECACHE_CMP_PROC			0x0008
-#define TYPECACHE_HASH_PROC			0x0010
-#define TYPECACHE_EQ_OPR_FINFO		0x0020
-#define TYPECACHE_CMP_PROC_FINFO	0x0040
-#define TYPECACHE_HASH_PROC_FINFO	0x0080
-#define TYPECACHE_TUPDESC			0x0100
-#define TYPECACHE_BTREE_OPFAMILY	0x0200
-#define TYPECACHE_HASH_OPFAMILY		0x0400
-#define TYPECACHE_RANGE_INFO		0x0800
-#define TYPECACHE_DOMAIN_BASE_INFO			0x1000
-#define TYPECACHE_DOMAIN_CONSTR_INFO		0x2000
-#define TYPECACHE_HASH_EXTENDED_PROC		0x4000
-#define TYPECACHE_HASH_EXTENDED_PROC_FINFO	0x8000
+#define TYPECACHE_EQ_OPR			0x00001
+#define TYPECACHE_LT_OPR			0x00002
+#define TYPECACHE_GT_OPR			0x00004
+#define TYPECACHE_CMP_PROC			0x00008
+#define TYPECACHE_HASH_PROC			0x00010
+#define TYPECACHE_EQ_OPR_FINFO		0x00020
+#define TYPECACHE_CMP_PROC_FINFO	0x00040
+#define TYPECACHE_HASH_PROC_FINFO	0x00080
+#define TYPECACHE_TUPDESC			0x00100
+#define TYPECACHE_BTREE_OPFAMILY	0x00200
+#define TYPECACHE_HASH_OPFAMILY		0x00400
+#define TYPECACHE_RANGE_INFO		0x00800
+#define TYPECACHE_DOMAIN_BASE_INFO			0x01000
+#define TYPECACHE_DOMAIN_CONSTR_INFO		0x02000
+#define TYPECACHE_HASH_EXTENDED_PROC		0x04000
+#define TYPECACHE_HASH_EXTENDED_PROC_FINFO	0x08000
+#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 6df8e14629d..d200facfa45 100644
--- a/src/pl/plpgsql/src/pl_comp.c
+++ b/src/pl/plpgsql/src/pl_comp.c
@@ -514,6 +514,8 @@ do_compile(FunctionCallInfo fcinfo,
 					else if (rettypeid == ANYRANGEOID ||
 							 rettypeid == ANYCOMPATIBLERANGEOID)
 						rettypeid = INT4RANGEOID;
+					else if (rettypeid == ANYMULTIRANGEOID)
+						rettypeid = INT4MULTIRANGEOID;
 					else		/* ANYELEMENT or ANYNONARRAY or ANYCOMPATIBLE */
 						rettypeid = INT4OID;
 					/* XXX what could we use for ANYENUM? */
@@ -2108,6 +2110,7 @@ build_datatype(HeapTuple typeTup, int32 typmod,
 		case TYPTYPE_BASE:
 		case TYPTYPE_ENUM:
 		case TYPTYPE_RANGE:
+		case TYPTYPE_MULTIRANGE:
 			typ->ttype = PLPGSQL_TTYPE_SCALAR;
 			break;
 		case TYPTYPE_COMPOSITE:
@@ -2526,6 +2529,9 @@ plpgsql_resolve_polymorphic_argtypes(int numargs,
 				case ANYCOMPATIBLERANGEOID:
 					argtypes[i] = INT4RANGEOID;
 					break;
+				case ANYMULTIRANGEOID:
+					argtypes[i] = INT4MULTIRANGEOID;
+					break;
 				default:
 					break;
 			}
diff --git a/src/test/regress/expected/dependency.out b/src/test/regress/expected/dependency.out
index b4a11b8aa9b..82327951487 100644
--- a/src/test/regress/expected/dependency.out
+++ b/src/test/regress/expected/dependency.out
@@ -140,6 +140,7 @@ owner of sequence deptest_a_seq
 owner of table deptest
 owner of function deptest_func()
 owner of type deptest_enum
+owner of type deptest_multirange
 owner of type deptest_range
 owner of table deptest2
 owner of sequence ss1
diff --git a/src/test/regress/expected/hash_func.out b/src/test/regress/expected/hash_func.out
index 827e69fc7ab..dca31365bcf 100644
--- a/src/test/regress/expected/hash_func.out
+++ b/src/test/regress/expected/hash_func.out
@@ -305,6 +305,19 @@ 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)
+
 CREATE TYPE hash_test_t1 AS (a int, b text);
 SELECT v as value, hash_record(v)::bit(32) as standard,
        hash_record_extended(v, 0)::bit(32) as extended0,
diff --git a/src/test/regress/expected/multirangetypes.out b/src/test/regress/expected/multirangetypes.out
new file mode 100644
index 00000000000..e72f99863fb
--- /dev/null
+++ b/src/test/regress/expected/multirangetypes.out
@@ -0,0 +1,2437 @@
+-- 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)
+
+--
+-- test casts, both a built-in range type and a user-defined one:
+--
+select 'empty'::int4range::int4multirange;
+ int4multirange 
+----------------
+ {}
+(1 row)
+
+select int4range(1, 3)::int4multirange;
+ int4range 
+-----------
+ {[1,3)}
+(1 row)
+
+select int4range(1, null)::int4multirange;
+ int4range 
+-----------
+ {[1,)}
+(1 row)
+
+select int4range(null, null)::int4multirange;
+ int4range 
+-----------
+ {(,)}
+(1 row)
+
+select 'empty'::textrange::textmultirange;
+ textmultirange 
+----------------
+ {}
+(1 row)
+
+select textrange('a', 'c')::textmultirange;
+ textrange 
+-----------
+ {[a,c)}
+(1 row)
+
+select textrange('a', null)::textmultirange;
+ textrange 
+-----------
+ {[a,)}
+(1 row)
+
+select textrange(null, null)::textmultirange;
+ textrange 
+-----------
+ {(,)}
+(1 row)
+
+--
+-- create some test data and test the operators
+--
+CREATE TABLE nummultirange_test (nmr NUMMULTIRANGE);
+CREATE INDEX nummultirange_test_btree ON nummultirange_test(nmr);
+INSERT INTO nummultirange_test VALUES('{}');
+INSERT INTO nummultirange_test VALUES('{[,)}');
+INSERT INTO nummultirange_test VALUES('{[3,]}');
+INSERT INTO nummultirange_test VALUES('{[,), [3,]}');
+INSERT INTO nummultirange_test VALUES('{[, 5)}');
+INSERT INTO nummultirange_test VALUES(nummultirange());
+INSERT INTO nummultirange_test VALUES(nummultirange(variadic '{}'::numrange[]));
+INSERT INTO nummultirange_test VALUES(nummultirange(numrange(1.1, 2.2)));
+INSERT INTO nummultirange_test VALUES('{empty}');
+INSERT INTO nummultirange_test VALUES(nummultirange(numrange(1.7, 1.7, '[]'), numrange(1.7, 1.9)));
+INSERT INTO nummultirange_test VALUES(nummultirange(numrange(1.7, 1.7, '[]'), numrange(1.9, 2.1)));
+SELECT nmr, isempty(nmr), lower(nmr), upper(nmr) FROM nummultirange_test ORDER BY nmr;
+          nmr          | isempty | lower | upper 
+-----------------------+---------+-------+-------
+ {}                    | t       |       |      
+ {}                    | t       |       |      
+ {}                    | t       |       |      
+ {}                    | t       |       |      
+ {(,5)}                | f       |       |     5
+ {(,)}                 | f       |       |      
+ {(,)}                 | f       |       |      
+ {[1.1,2.2)}           | f       |   1.1 |   2.2
+ {[1.7,1.7],[1.9,2.1)} | f       |   1.7 |   2.1
+ {[1.7,1.9)}           | f       |   1.7 |   1.9
+ {[3,)}                | f       |     3 |      
+(11 rows)
+
+SELECT nmr, lower_inc(nmr), lower_inf(nmr), upper_inc(nmr), upper_inf(nmr) FROM nummultirange_test ORDER BY nmr;
+          nmr          | lower_inc | lower_inf | upper_inc | upper_inf 
+-----------------------+-----------+-----------+-----------+-----------
+ {}                    | f         | f         | f         | f
+ {}                    | f         | f         | f         | f
+ {}                    | f         | f         | f         | f
+ {}                    | f         | f         | f         | f
+ {(,5)}                | f         | t         | f         | f
+ {(,)}                 | f         | t         | f         | t
+ {(,)}                 | f         | t         | f         | t
+ {[1.1,2.2)}           | t         | f         | f         | f
+ {[1.7,1.7],[1.9,2.1)} | t         | f         | f         | f
+ {[1.7,1.9)}           | t         | f         | f         | f
+ {[3,)}                | t         | f         | f         | t
+(11 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr = '{}';
+ nmr 
+-----
+ {}
+ {}
+ {}
+ {}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr = '{(,5)}';
+  nmr   
+--------
+ {(,5)}
+(1 row)
+
+SELECT * FROM nummultirange_test WHERE nmr = '{[3,)}';
+  nmr   
+--------
+ {[3,)}
+(1 row)
+
+SELECT * FROM nummultirange_test WHERE nmr = '{[1.7,1.7]}';
+ nmr 
+-----
+(0 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr = '{[1.7,1.7],[1.9,2.1)}';
+          nmr          
+-----------------------
+ {[1.7,1.7],[1.9,2.1)}
+(1 row)
+
+SELECT * FROM nummultirange_test WHERE nmr < '{}';
+ nmr 
+-----
+(0 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr < '{[-1000.0, -1000.0]}';
+  nmr   
+--------
+ {}
+ {(,)}
+ {(,)}
+ {(,5)}
+ {}
+ {}
+ {}
+(7 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr < '{[0.0, 1.0]}';
+  nmr   
+--------
+ {}
+ {(,)}
+ {(,)}
+ {(,5)}
+ {}
+ {}
+ {}
+(7 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr < '{[1000.0, 1001.0]}';
+          nmr          
+-----------------------
+ {}
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+ {}
+ {}
+ {[1.1,2.2)}
+ {}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(11 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr <= '{}';
+ nmr 
+-----
+ {}
+ {}
+ {}
+ {}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr <= '{[3,)}';
+          nmr          
+-----------------------
+ {}
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+ {}
+ {}
+ {[1.1,2.2)}
+ {}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(11 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr >= '{}';
+          nmr          
+-----------------------
+ {}
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+ {}
+ {}
+ {[1.1,2.2)}
+ {}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(11 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr >= '{[3,)}';
+  nmr   
+--------
+ {[3,)}
+(1 row)
+
+SELECT * FROM nummultirange_test WHERE nmr > '{}';
+          nmr          
+-----------------------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+ {[1.1,2.2)}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(7 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr > '{[-1000.0, -1000.0]}';
+          nmr          
+-----------------------
+ {[3,)}
+ {[1.1,2.2)}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr > '{[0.0, 1.0]}';
+          nmr          
+-----------------------
+ {[3,)}
+ {[1.1,2.2)}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr > '{[1000.0, 1001.0]}';
+ nmr 
+-----
+(0 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr <> '{}';
+          nmr          
+-----------------------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+ {[1.1,2.2)}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(7 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr <> '{(,5)}';
+          nmr          
+-----------------------
+ {}
+ {(,)}
+ {[3,)}
+ {(,)}
+ {}
+ {}
+ {[1.1,2.2)}
+ {}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(10 rows)
+
+select nummultirange(numrange(2.0, 1.0));
+ERROR:  range lower bound must be less than or equal to range upper bound
+select nummultirange(numrange(5.0, 6.0), numrange(1.0, 2.0));
+     nummultirange     
+-----------------------
+ {[1.0,2.0),[5.0,6.0)}
+(1 row)
+
+analyze nummultirange_test;
+-- overlaps
+SELECT * FROM nummultirange_test WHERE range_overlaps_multirange(numrange(4.0, 4.2), nmr);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE numrange(4.0, 4.2) && nmr;
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE multirange_overlaps_range(nmr, numrange(4.0, 4.2));
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr && numrange(4.0, 4.2);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE multirange_overlaps_multirange(nmr, nummultirange(numrange(4.0, 4.2), numrange(6.0, 7.0)));
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr && nummultirange(numrange(4.0, 4.2), numrange(6.0, 7.0));
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr && nummultirange(numrange(6.0, 7.0));
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+(3 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr && nummultirange(numrange(6.0, 7.0), numrange(8.0, 9.0));
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+(3 rows)
+
+-- mr contains x
+SELECT * FROM nummultirange_test WHERE multirange_contains_elem(nmr, 4.0);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr @> 4.0;
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE multirange_contains_range(nmr, numrange(4.0, 4.2));
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr @> numrange(4.0, 4.2);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE multirange_contains_multirange(nmr, '{[4.0,4.2), [6.0, 8.0)}');
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+(3 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr @> '{[4.0,4.2), [6.0, 8.0)}'::nummultirange;
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+(3 rows)
+
+-- x is contained by mr
+SELECT * FROM nummultirange_test WHERE elem_contained_by_multirange(4.0, nmr);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE 4.0 <@ nmr;
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE range_contained_by_multirange(numrange(4.0, 4.2), nmr);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE numrange(4.0, 4.2) <@ nmr;
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE multirange_contained_by_multirange('{[4.0,4.2), [6.0, 8.0)}', nmr);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+(3 rows)
+
+SELECT * FROM nummultirange_test WHERE '{[4.0,4.2), [6.0, 8.0)}'::nummultirange <@ nmr;
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+(3 rows)
+
+-- overlaps
+SELECT 'empty'::numrange && nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT 'empty'::numrange && nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() && 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) && 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() && nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() && nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) && nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) && nummultirange(numrange(1,2), numrange(7,8));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(7,8)) && nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) && nummultirange(numrange(1,2), numrange(3.5,8));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(3.5,8)) && numrange(3,4);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(3.5,8)) && nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+-- contains
+SELECT nummultirange() @> nummultirange();
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange() @> 'empty'::numrange;
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(null,null)) @> numrange(1,2);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(null,null)) @> numrange(null,2);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(null,null)) @> numrange(2,null);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(null,5)) @> numrange(null,3);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(null,5)) @> numrange(null,8);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(5,null)) @> numrange(8,null);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(5,null)) @> numrange(3,null);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,5)) @> numrange(8,9);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,5)) @> numrange(3,9);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,5)) @> numrange(1,4);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,5)) @> numrange(1,5);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(-4,-2), numrange(1,5)) @> numrange(1,5);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,5), numrange(8,9)) @> numrange(1,5);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,5), numrange(8,9)) @> numrange(6,7);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,5), numrange(6,9)) @> numrange(6,7);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[1,5)}'::nummultirange @> '{[1,5)}';
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[-4,-2), [1,5)}'::nummultirange @> '{[1,5)}';
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[1,5), [8,9)}'::nummultirange @> '{[1,5)}';
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[1,5), [8,9)}'::nummultirange @> '{[6,7)}';
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT '{[1,5), [6,9)}'::nummultirange @> '{[6,7)}';
+ ?column? 
+----------
+ t
+(1 row)
+
+-- is contained by
+SELECT nummultirange() <@ nummultirange();
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT 'empty'::numrange <@ nummultirange();
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,2) <@ nummultirange(numrange(null,null));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(null,2) <@ nummultirange(numrange(null,null));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(2,null) <@ nummultirange(numrange(null,null));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(null,3) <@ nummultirange(numrange(null,5));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(null,8) <@ nummultirange(numrange(null,5));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(8,null) <@ nummultirange(numrange(5,null));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(3,null) <@ nummultirange(numrange(5,null));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(8,9) <@ nummultirange(numrange(1,5));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(3,9) <@ nummultirange(numrange(1,5));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(1,4) <@ nummultirange(numrange(1,5));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,5) <@ nummultirange(numrange(1,5));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,5) <@ nummultirange(numrange(-4,-2), numrange(1,5));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,5) <@ nummultirange(numrange(1,5), numrange(8,9));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(6,7) <@ nummultirange(numrange(1,5), numrange(8,9));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(6,7) <@ nummultirange(numrange(1,5), numrange(6,9));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[1,5)}' <@ '{[1,5)}'::nummultirange;
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[1,5)}' <@ '{[-4,-2), [1,5)}'::nummultirange;
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[1,5)}' <@ '{[1,5), [8,9)}'::nummultirange;
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[6,7)}' <@ '{[1,5), [8,9)}'::nummultirange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT '{[6,7)}' <@ '{[1,5), [6,9)}'::nummultirange;
+ ?column? 
+----------
+ t
+(1 row)
+
+-- overleft
+SELECT 'empty'::numrange &< nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT 'empty'::numrange &< nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() &< 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) &< 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() &< nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) &< nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() &< nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(6,7) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(1,2) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,4) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,6) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(3.5,6) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(6,7)) &< numrange(3,4);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) &< numrange(3,4);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,4)) &< numrange(3,4);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,6)) &< numrange(3,4);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3.5,6)) &< numrange(3,4);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(6,7)) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,4)) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,6)) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3.5,6)) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+-- overright
+SELECT nummultirange() &> 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) &> 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT 'empty'::numrange &> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT 'empty'::numrange &> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() &> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() &> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) &> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> numrange(6,7);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> numrange(1,2);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> numrange(1,4);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> numrange(1,6);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> numrange(3.5,6);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(3,4) &> nummultirange(numrange(6,7));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(3,4) &> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(3,4) &> nummultirange(numrange(1,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(3,4) &> nummultirange(numrange(1,6));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(3,4) &> nummultirange(numrange(3.5,6));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(6,7));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(1,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(1,6));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(3.5,6));
+ ?column? 
+----------
+ f
+(1 row)
+
+-- meets
+SELECT 'empty'::numrange -|- nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT 'empty'::numrange -|- nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() -|- 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() -|- nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() -|- nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(1,2) -|- nummultirange(numrange(2,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,2) -|- nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- numrange(2,4);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- numrange(3,4);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- nummultirange(numrange(2,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(5,6)) -|- nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(5,6)) -|- nummultirange(numrange(6,7));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(5,6)) -|- nummultirange(numrange(8,9));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- nummultirange(numrange(2,4), numrange(6,7));
+ ?column? 
+----------
+ t
+(1 row)
+
+-- strictly left
+select 'empty'::numrange << nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(1,2) << nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(1,2) << nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+select numrange(1,2) << nummultirange(numrange(0,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(1,2) << nummultirange(numrange(0,4), numrange(7,8));
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange() << 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange() << numrange(1,2);
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(3,4)) << numrange(3,6);
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(0,2)) << numrange(3,6);
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange(numrange(0,2), numrange(7,8)) << numrange(3,6);
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(-4,-2), numrange(0,2)) << numrange(3,6);
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange() << nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange() << nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(1,2)) << nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(1,2)) << nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(1,2)) << nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange(numrange(1,2)) << nummultirange(numrange(3,4), numrange(7,8));
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange(numrange(1,2), numrange(4,5)) << nummultirange(numrange(3,4), numrange(7,8));
+ ?column? 
+----------
+ f
+(1 row)
+
+-- strictly right
+select nummultirange() >> 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange() >> numrange(1,2);
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(3,4)) >> numrange(1,2);
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange(numrange(0,4)) >> numrange(1,2);
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(0,4), numrange(7,8)) >> numrange(1,2);
+ ?column? 
+----------
+ f
+(1 row)
+
+select 'empty'::numrange >> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(1,2) >> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(3,6) >> nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(3,6) >> nummultirange(numrange(0,2));
+ ?column? 
+----------
+ t
+(1 row)
+
+select numrange(3,6) >> nummultirange(numrange(0,2), numrange(7,8));
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(3,6) >> nummultirange(numrange(-4,-2), numrange(0,2));
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange() >> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(1,2)) >> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange() >> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(1,2)) >> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(3,4)) >> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange(numrange(3,4), numrange(7,8)) >> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange(numrange(3,4), numrange(7,8)) >> nummultirange(numrange(1,2), numrange(4,5));
+ ?column? 
+----------
+ f
+(1 row)
+
+-- union
+SELECT nummultirange() + nummultirange();
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange() + nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) + nummultirange();
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) + nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) + nummultirange(numrange(2,4));
+ ?column? 
+----------
+ {[1,4)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) + nummultirange(numrange(3,4));
+   ?column?    
+---------------
+ {[1,2),[3,4)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) + nummultirange(numrange(2,4));
+ ?column? 
+----------
+ {[1,5)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) + nummultirange(numrange(3,4));
+   ?column?    
+---------------
+ {[1,2),[3,5)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) + nummultirange(numrange(0,9));
+ ?column? 
+----------
+ {[0,9)}
+(1 row)
+
+-- merge
+SELECT range_merge(nummultirange());
+ range_merge 
+-------------
+ empty
+(1 row)
+
+SELECT range_merge(nummultirange(numrange(1,2)));
+ range_merge 
+-------------
+ [1,2)
+(1 row)
+
+SELECT range_merge(nummultirange(numrange(1,2), numrange(7,8)));
+ range_merge 
+-------------
+ [1,8)
+(1 row)
+
+-- minus
+SELECT nummultirange() - nummultirange();
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange() - nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) - nummultirange();
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(3,4)) - nummultirange();
+   ?column?    
+---------------
+ {[1,2),[3,4)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) - nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) - nummultirange(numrange(2,4));
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) - nummultirange(numrange(3,4));
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {[2,4)}
+(1 row)
+
+SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(2,3));
+   ?column?    
+---------------
+ {[1,2),[3,4)}
+(1 row)
+
+SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(0,8));
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(0,2));
+ ?column? 
+----------
+ {[2,4)}
+(1 row)
+
+SELECT nummultirange(numrange(1,8)) - nummultirange(numrange(0,2), numrange(3,4));
+   ?column?    
+---------------
+ {[2,3),[4,8)}
+(1 row)
+
+SELECT nummultirange(numrange(1,8)) - nummultirange(numrange(2,3), numrange(5,null));
+   ?column?    
+---------------
+ {[1,2),[3,5)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(-2,0));
+   ?column?    
+---------------
+ {[1,2),[4,5)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(2,4));
+   ?column?    
+---------------
+ {[1,2),[4,5)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(3,5));
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(0,9));
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange(numrange(1,3), numrange(4,5)) - nummultirange(numrange(2,9));
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(8,9));
+   ?column?    
+---------------
+ {[1,2),[4,5)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(-2,0), numrange(8,9));
+   ?column?    
+---------------
+ {[1,2),[4,5)}
+(1 row)
+
+-- intersection
+SELECT nummultirange() * nummultirange();
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange() * nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) * nummultirange();
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT '{[1,3)}'::nummultirange * '{[1,5)}'::nummultirange;
+ ?column? 
+----------
+ {[1,3)}
+(1 row)
+
+SELECT '{[1,3)}'::nummultirange * '{[0,5)}'::nummultirange;
+ ?column? 
+----------
+ {[1,3)}
+(1 row)
+
+SELECT '{[1,3)}'::nummultirange * '{[0,2)}'::nummultirange;
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT '{[1,3)}'::nummultirange * '{[2,5)}'::nummultirange;
+ ?column? 
+----------
+ {[2,3)}
+(1 row)
+
+SELECT '{[1,4)}'::nummultirange * '{[2,3)}'::nummultirange;
+ ?column? 
+----------
+ {[2,3)}
+(1 row)
+
+SELECT '{[1,4)}'::nummultirange * '{[0,2), [3,5)}'::nummultirange;
+   ?column?    
+---------------
+ {[1,2),[3,4)}
+(1 row)
+
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[0,8), [9,12)}'::nummultirange;
+       ?column?       
+----------------------
+ {[1,4),[7,8),[9,10)}
+(1 row)
+
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[9,12)}'::nummultirange;
+ ?column? 
+----------
+ {[9,10)}
+(1 row)
+
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[-5,-4), [5,6), [9,12)}'::nummultirange;
+ ?column? 
+----------
+ {[9,10)}
+(1 row)
+
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[0,2), [3,8), [9,12)}'::nummultirange;
+          ?column?          
+----------------------------
+ {[1,2),[3,4),[7,8),[9,10)}
+(1 row)
+
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[0,2), [3,8), [9,12)}'::nummultirange;
+          ?column?          
+----------------------------
+ {[1,2),[3,4),[7,8),[9,10)}
+(1 row)
+
+--
+-- range_agg function
+--
+create table reservations ( room_id integer not null, booked_during daterange );
+insert into reservations values
+-- 1: has a meets and a gap
+(1, daterange('2018-07-01', '2018-07-07')),
+(1, daterange('2018-07-07', '2018-07-14')),
+(1, daterange('2018-07-20', '2018-07-22')),
+-- 2: just a single row
+(2, daterange('2018-07-01', '2018-07-03')),
+-- 3: one null range
+(3, NULL),
+-- 4: two null ranges
+(4, NULL),
+(4, NULL),
+-- 5: a null range and a non-null range
+(5, NULL),
+(5, daterange('2018-07-01', '2018-07-03')),
+-- 6: has overlap
+(6, daterange('2018-07-01', '2018-07-07')),
+(6, daterange('2018-07-05', '2018-07-10')),
+-- 7: two ranges that meet: no gap or overlap
+(7, daterange('2018-07-01', '2018-07-07')),
+(7, daterange('2018-07-07', '2018-07-14')),
+-- 8: an empty range
+(8, 'empty'::daterange)
+;
+SELECT   room_id, range_agg(booked_during)
+FROM     reservations
+GROUP BY room_id
+ORDER BY room_id;
+ room_id |                     range_agg                     
+---------+---------------------------------------------------
+       1 | {[07-01-2018,07-14-2018),[07-20-2018,07-22-2018)}
+       2 | {[07-01-2018,07-03-2018)}
+       3 | 
+       4 | 
+       5 | {[07-01-2018,07-03-2018)}
+       6 | {[07-01-2018,07-10-2018)}
+       7 | {[07-01-2018,07-14-2018)}
+       8 | {}
+(8 rows)
+
+-- range_agg on a custom range type too
+SELECT  range_agg(r)
+FROM    (VALUES
+          ('[a,c]'::textrange),
+          ('[b,b]'::textrange),
+          ('[c,f]'::textrange),
+          ('[g,h)'::textrange),
+          ('[h,j)'::textrange)
+        ) t(r);
+   range_agg   
+---------------
+ {[a,f],[g,j)}
+(1 row)
+
+select range_intersect_agg(nmr) from nummultirange_test;
+ range_intersect_agg 
+---------------------
+ {}
+(1 row)
+
+select range_intersect_agg(nmr) from nummultirange_test where false;
+ range_intersect_agg 
+---------------------
+ 
+(1 row)
+
+-- test with just one input:
+select range_intersect_agg(nmr) from (values ('{[1,2]}'::nummultirange)) t(nmr);
+ range_intersect_agg 
+---------------------
+ {[1,2]}
+(1 row)
+
+select range_intersect_agg(nmr) from nummultirange_test where nmr @> 4.0;
+ range_intersect_agg 
+---------------------
+ {[3,5)}
+(1 row)
+
+create table nummultirange_test2(nmr nummultirange);
+create index nummultirange_test2_hash_idx on nummultirange_test2 using hash (nmr);
+INSERT INTO nummultirange_test2 VALUES('{[, 5)}');
+INSERT INTO nummultirange_test2 VALUES(nummultirange(numrange(1.1, 2.2)));
+INSERT INTO nummultirange_test2 VALUES(nummultirange(numrange(1.1, 2.2)));
+INSERT INTO nummultirange_test2 VALUES(nummultirange(numrange(1.1, 2.2,'()')));
+INSERT INTO nummultirange_test2 VALUES('{}');
+select * from nummultirange_test2 where nmr = '{}';
+ nmr 
+-----
+ {}
+(1 row)
+
+select * from nummultirange_test2 where nmr = nummultirange(numrange(1.1, 2.2));
+     nmr     
+-------------
+ {[1.1,2.2)}
+ {[1.1,2.2)}
+(2 rows)
+
+select * from nummultirange_test2 where nmr = nummultirange(numrange(1.1, 2.3));
+ nmr 
+-----
+(0 rows)
+
+set enable_nestloop=t;
+set enable_hashjoin=f;
+set enable_mergejoin=f;
+select * from nummultirange_test natural join nummultirange_test2 order by nmr;
+     nmr     
+-------------
+ {}
+ {}
+ {}
+ {}
+ {(,5)}
+ {[1.1,2.2)}
+ {[1.1,2.2)}
+(7 rows)
+
+set enable_nestloop=f;
+set enable_hashjoin=t;
+set enable_mergejoin=f;
+select * from nummultirange_test natural join nummultirange_test2 order by nmr;
+     nmr     
+-------------
+ {}
+ {}
+ {}
+ {}
+ {(,5)}
+ {[1.1,2.2)}
+ {[1.1,2.2)}
+(7 rows)
+
+set enable_nestloop=f;
+set enable_hashjoin=f;
+set enable_mergejoin=t;
+select * from nummultirange_test natural join nummultirange_test2 order by nmr;
+     nmr     
+-------------
+ {}
+ {}
+ {}
+ {}
+ {(,5)}
+ {[1.1,2.2)}
+ {[1.1,2.2)}
+(7 rows)
+
+set enable_nestloop to default;
+set enable_hashjoin to default;
+set enable_mergejoin to default;
+DROP TABLE nummultirange_test2;
+--
+-- Test user-defined multirange of floats
+--
+select '{[123.001, 5.e9)}'::float8multirange @> 888.882::float8;
+ ?column? 
+----------
+ t
+(1 row)
+
+create table float8multirange_test(f8mr float8multirange, i int);
+insert into float8multirange_test values(float8multirange(float8range(-100.00007, '1.111113e9')), 42);
+select * from float8multirange_test;
+           f8mr            | i  
+---------------------------+----
+ {[-100.00007,1111113000)} | 42
+(1 row)
+
+drop table float8multirange_test;
+--
+-- Test multirange types over domains
+--
+create domain mydomain as int4;
+create type mydomainrange as range(subtype=mydomain);
+select '{[4,50)}'::mydomainmultirange @> 7::mydomain;
+ ?column? 
+----------
+ t
+(1 row)
+
+drop domain mydomain cascade;
+NOTICE:  drop cascades to type mydomainrange
+--
+-- Test domains over multirange types
+--
+create domain restrictedmultirange as int4multirange check (upper(value) < 10);
+select '{[4,5)}'::restrictedmultirange @> 7;
+ ?column? 
+----------
+ f
+(1 row)
+
+select '{[4,50)}'::restrictedmultirange @> 7; -- should fail
+ERROR:  value for domain restrictedmultirange violates check constraint "restrictedmultirange_check"
+drop domain restrictedmultirange;
+--
+-- Test multiple multirange types over the same subtype
+--
+create type textrange1 as range(subtype=text, collation="C");
+create type textrange2 as range(subtype=text, collation="C");
+select textmultirange1(textrange2('a','Z'));  -- should fail
+ERROR:  function textmultirange1(textrange2) does not exist
+LINE 1: select textmultirange1(textrange2('a','Z'));
+               ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+select textmultirange1(textrange1('a','Z')) @> 'b'::text;
+ERROR:  range lower bound must be less than or equal to range upper bound
+select textmultirange2(textrange2('a','z')) @> 'b'::text;
+ ?column? 
+----------
+ t
+(1 row)
+
+drop type textrange1;
+drop type textrange2;
+--
+-- Test polymorphic type system
+--
+create function anyarray_anymultirange_func(a anyarray, r anymultirange)
+  returns anyelement as 'select $1[1] + lower($2);' language sql;
+select anyarray_anymultirange_func(ARRAY[1,2], int4multirange(int4range(10,20)));
+ anyarray_anymultirange_func 
+-----------------------------
+                          11
+(1 row)
+
+-- should fail
+select anyarray_anymultirange_func(ARRAY[1,2], nummultirange(numrange(10,20)));
+ERROR:  function anyarray_anymultirange_func(integer[], nummultirange) does not exist
+LINE 1: select anyarray_anymultirange_func(ARRAY[1,2], nummultirange...
+               ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+drop function anyarray_anymultirange_func(anyarray, anymultirange);
+-- should fail
+create function bogus_func(anyelement)
+  returns anymultirange as 'select int4multirange(int4range(1,10))' language sql;
+ERROR:  cannot determine result data type
+DETAIL:  A result of type anymultirange requires at least one input of type anyrange or anymultirange.
+-- should fail
+create function bogus_func(int)
+  returns anymultirange as 'select int4multirange(int4range(1,10))' language sql;
+ERROR:  cannot determine result data type
+DETAIL:  A result of type anymultirange requires at least one input of type anyrange or anymultirange.
+create function range_add_bounds(anymultirange)
+  returns anyelement as 'select lower($1) + upper($1)' language sql;
+select range_add_bounds(int4multirange(int4range(1, 17)));
+ range_add_bounds 
+------------------
+               18
+(1 row)
+
+select range_add_bounds(nummultirange(numrange(1.0001, 123.123)));
+ range_add_bounds 
+------------------
+         124.1231
+(1 row)
+
+create function multirangetypes_sql(q anymultirange, b anyarray, out c anyelement)
+  as $$ select upper($1) + $2[1] $$
+  language sql;
+select multirangetypes_sql(int4multirange(int4range(1,10)), ARRAY[2,20]);
+ multirangetypes_sql 
+---------------------
+                  12
+(1 row)
+
+select multirangetypes_sql(nummultirange(numrange(1,10)), ARRAY[2,20]);  -- match failure
+ERROR:  function multirangetypes_sql(nummultirange, integer[]) does not exist
+LINE 1: select multirangetypes_sql(nummultirange(numrange(1,10)), AR...
+               ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+create function anycompatiblearray_anycompatiblemultirange_func(a anycompatiblearray, mr anycompatiblemultirange)
+  returns anycompatible as 'select $1[1] + lower($2);' language sql;
+select anycompatiblearray_anycompatiblemultirange_func(ARRAY[1,2], multirange(int4range(10,20)));
+ anycompatiblearray_anycompatiblemultirange_func 
+-------------------------------------------------
+                                              11
+(1 row)
+
+select anycompatiblearray_anycompatiblemultirange_func(ARRAY[1,2], multirange(numrange(10,20)));
+ anycompatiblearray_anycompatiblemultirange_func 
+-------------------------------------------------
+                                              11
+(1 row)
+
+-- should fail
+select anycompatiblearray_anycompatiblemultirange_func(ARRAY[1.1,2], multirange(int4range(10,20)));
+ERROR:  function anycompatiblearray_anycompatiblemultirange_func(numeric[], int4multirange) does not exist
+LINE 1: select anycompatiblearray_anycompatiblemultirange_func(ARRAY...
+               ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+drop function anycompatiblearray_anycompatiblemultirange_func(anycompatiblearray, anycompatiblemultirange);
+create function anycompatiblerange_anycompatiblemultirange_func(r anycompatiblerange, mr anycompatiblemultirange)
+  returns anycompatible as 'select lower($1) + lower($2);' language sql;
+select anycompatiblerange_anycompatiblemultirange_func(int4range(1,2), multirange(int4range(10,20)));
+ anycompatiblerange_anycompatiblemultirange_func 
+-------------------------------------------------
+                                              11
+(1 row)
+
+-- should fail
+select anycompatiblerange_anycompatiblemultirange_func(numrange(1,2), multirange(int4range(10,20)));
+ERROR:  function anycompatiblerange_anycompatiblemultirange_func(numrange, int4multirange) does not exist
+LINE 1: select anycompatiblerange_anycompatiblemultirange_func(numra...
+               ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+drop function anycompatiblerange_anycompatiblemultirange_func(anycompatiblerange, anycompatiblemultirange);
+-- should fail
+create function bogus_func(anycompatible)
+  returns anycompatiblerange as 'select int4range(1,10)' language sql;
+ERROR:  cannot determine result data type
+DETAIL:  A result of type anycompatiblerange requires at least one input of type anycompatiblerange or anycompatiblemultirange.
+--
+-- Arrays of multiranges
+--
+select ARRAY[nummultirange(numrange(1.1, 1.2)), nummultirange(numrange(12.3, 155.5))];
+              array               
+----------------------------------
+ {"{[1.1,1.2)}","{[12.3,155.5)}"}
+(1 row)
+
+create table i8mr_array (f1 int, f2 int8multirange[]);
+insert into i8mr_array values (42, array[int8multirange(int8range(1,10)), int8multirange(int8range(2,20))]);
+select * from i8mr_array;
+ f1 |           f2            
+----+-------------------------
+ 42 | {"{[1,10)}","{[2,20)}"}
+(1 row)
+
+drop table i8mr_array;
+--
+-- Multiranges of arrays
+--
+select arraymultirange(arrayrange(ARRAY[1,2], ARRAY[2,1]));
+   arraymultirange   
+---------------------
+ {["{1,2}","{2,1}")}
+(1 row)
+
+select arraymultirange(arrayrange(ARRAY[2,1], ARRAY[1,2]));  -- fail
+ERROR:  range lower bound must be less than or equal to range upper bound
+select array[1,1] <@ arraymultirange(arrayrange(array[1,2], array[2,1]));
+ ?column? 
+----------
+ f
+(1 row)
+
+select array[1,3] <@ arraymultirange(arrayrange(array[1,2], array[2,1]));
+ ?column? 
+----------
+ t
+(1 row)
+
+--
+-- Ranges of composites
+--
+create type two_ints as (a int, b int);
+create type two_ints_range as range (subtype = two_ints);
+-- with force_parallel_mode on, this exercises tqueue.c's range remapping
+select *, row_to_json(upper(t)) as u from
+  (values (two_ints_multirange(two_ints_range(row(1,2), row(3,4)))),
+          (two_ints_multirange(two_ints_range(row(5,6), row(7,8))))) v(t);
+          t          |       u       
+---------------------+---------------
+ {["(1,2)","(3,4)")} | {"a":3,"b":4}
+ {["(5,6)","(7,8)")} | {"a":7,"b":8}
+(2 rows)
+
+drop type two_ints cascade;
+NOTICE:  drop cascades to type two_ints_range
+--
+-- Check behavior when subtype lacks a hash function
+--
+set enable_sort = off;  -- try to make it pick a hash setop implementation
+select '{(2,5)}'::cashmultirange except select '{(5,6)}'::cashmultirange;
+ cashmultirange  
+-----------------
+ {($2.00,$5.00)}
+(1 row)
+
+reset enable_sort;
+--
+-- OUT/INOUT/TABLE functions
+--
+-- infer anymultirange from anymultirange
+create function mr_outparam_succeed(i anymultirange, out r anymultirange, out t text)
+  as $$ select $1, 'foo'::text $$ language sql;
+select * from mr_outparam_succeed(int4multirange(int4range(1,2)));
+    r    |  t  
+---------+-----
+ {[1,2)} | foo
+(1 row)
+
+-- infer anyarray from anymultirange
+create function mr_outparam_succeed2(i anymultirange, out r anyarray, out t text)
+  as $$ select ARRAY[upper($1)], 'foo'::text $$ language sql;
+select * from mr_outparam_succeed2(int4multirange(int4range(1,2)));
+  r  |  t  
+-----+-----
+ {2} | foo
+(1 row)
+
+-- infer anyrange from anymultirange
+create function mr_outparam_succeed3(i anymultirange, out r anyrange, out t text)
+  as $$ select range_merge($1), 'foo'::text $$ language sql;
+select * from mr_outparam_succeed3(int4multirange(int4range(1,2)));
+   r   |  t  
+-------+-----
+ [1,2) | foo
+(1 row)
+
+-- infer anymultirange from anyrange
+create function mr_outparam_succeed4(i anyrange, out r anymultirange, out t text)
+  as $$ select multirange($1), 'foo'::text $$ language sql;
+select * from mr_outparam_succeed4(int4range(1,2));
+    r    |  t  
+---------+-----
+ {[1,2)} | foo
+(1 row)
+
+-- infer anyelement from anymultirange
+create function mr_inoutparam_succeed(out i anyelement, inout r anymultirange)
+  as $$ select upper($1), $1 $$ language sql;
+select * from mr_inoutparam_succeed(int4multirange(int4range(1,2)));
+ i |    r    
+---+---------
+ 2 | {[1,2)}
+(1 row)
+
+-- infer anyelement+anymultirange from anyelement+anymultirange
+create function mr_table_succeed(i anyelement, r anymultirange) returns table(i anyelement, r anymultirange)
+  as $$ select $1, $2 $$ language sql;
+select * from mr_table_succeed(123, int4multirange(int4range(1,11)));
+  i  |    r     
+-----+----------
+ 123 | {[1,11)}
+(1 row)
+
+-- use anymultirange in plpgsql
+create function mr_polymorphic(i anyrange) returns anymultirange
+  as $$ begin return multirange($1); end; $$ language plpgsql;
+select mr_polymorphic(int4range(1, 4));
+ mr_polymorphic 
+----------------
+ {[1,4)}
+(1 row)
+
+-- should fail
+create function mr_outparam_fail(i anyelement, out r anymultirange, out t text)
+  as $$ select '[1,10]', 'foo' $$ language sql;
+ERROR:  cannot determine result data type
+DETAIL:  A result of type anymultirange requires at least one input of type anyrange or anymultirange.
+--should fail
+create function mr_inoutparam_fail(inout i anyelement, out r anymultirange)
+  as $$ select $1, '[1,10]' $$ language sql;
+ERROR:  cannot determine result data type
+DETAIL:  A result of type anymultirange requires at least one input of type anyrange or anymultirange.
+--should fail
+create function mr_table_fail(i anyelement) returns table(i anyelement, r anymultirange)
+  as $$ select $1, '[1,10]' $$ language sql;
+ERROR:  cannot determine result data type
+DETAIL:  A result of type anymultirange requires at least one input of type anyrange or anymultirange.
diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out
index 3b39137400f..e596ef090dd 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::regtype, p2.prorettype::regtype
 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        
@@ -208,6 +215,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        
@@ -228,6 +237,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        
@@ -340,7 +351,8 @@ WHERE p1.prorettype IN
      '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     
 ------+----------------
@@ -355,20 +367,25 @@ ORDER BY 2;
  3532 | enum_recv
 (9 rows)
 
--- anyrange is tighter than the rest, can only resolve from anyrange input
+-- anyrange and anymultirange are tighter than the rest, can only resolve
+-- from each other
 SELECT p1.oid, p1.proname
 FROM pg_proc as p1
-WHERE p1.prorettype = 'anyrange'::regtype
+WHERE p1.prorettype IN ('anyrange'::regtype, 'anymultirange'::regtype)
   AND NOT
-     'anyrange'::regtype = ANY (p1.proargtypes)
+    ('anyrange'::regtype = ANY (p1.proargtypes) OR
+      'anymultirange'::regtype = ANY (p1.proargtypes))
 ORDER BY 2;
  oid  |     proname      
 ------+------------------
+ 8002 | anymultirange_in
  3832 | anyrange_in
+ 8004 | multirange_in
+ 8006 | multirange_recv
  3876 | range_gist_union
  3834 | range_in
  3836 | range_recv
-(4 rows)
+(7 rows)
 
 -- similarly for the anycompatible family
 SELECT p1.oid, p1.proname
@@ -2202,13 +2219,14 @@ ORDER BY 1, 2, 3;
                     | float_ops        | float4_ops       | real
                     | float_ops        | float8_ops       | double precision
                     | jsonb_ops        | jsonb_ops        | jsonb
+                    | multirange_ops   | multirange_ops   | anymultirange
                     | numeric_ops      | numeric_ops      | numeric
                     | range_ops        | range_ops        | anyrange
                     | record_image_ops | record_image_ops | record
                     | record_ops       | record_ops       | record
                     | tsquery_ops      | tsquery_ops      | tsquery
                     | tsvector_ops     | tsvector_ops     | tsvector
-(14 rows)
+(15 rows)
 
 -- **************** pg_index ****************
 -- Look for illegal values in pg_index fields.
diff --git a/src/test/regress/expected/plpgsql.out b/src/test/regress/expected/plpgsql.out
index d0a6b630b8f..d55006d8c95 100644
--- a/src/test/regress/expected/plpgsql.out
+++ b/src/test/regress/expected/plpgsql.out
@@ -1811,7 +1811,7 @@ begin
   return array[x + 1, x + 2];
 end$$ language plpgsql;
 ERROR:  cannot determine result data type
-DETAIL:  A result of type anyrange requires at least one input of type anyrange.
+DETAIL:  A result of type anyrange requires at least one input of type anyrange or anymultirange.
 create function f1(x anyrange) returns anyarray as $$
 begin
   return array[lower(x), upper(x)];
@@ -1856,7 +1856,7 @@ begin
   return array[x + 1, x + 2];
 end$$ language plpgsql;
 ERROR:  cannot determine result data type
-DETAIL:  A result of type anycompatiblerange requires at least one input of type anycompatiblerange.
+DETAIL:  A result of type anycompatiblerange requires at least one input of type anycompatiblerange or anycompatiblemultirange.
 create function f1(x anycompatiblerange, y anycompatiblearray) returns anycompatiblerange as $$
 begin
   return x;
diff --git a/src/test/regress/expected/polymorphism.out b/src/test/regress/expected/polymorphism.out
index f5dfdf617fd..772345584f0 100644
--- a/src/test/regress/expected/polymorphism.out
+++ b/src/test/regress/expected/polymorphism.out
@@ -61,7 +61,7 @@ create function polyf(x anyelement) returns anyrange as $$
   select array[x + 1, x + 2]
 $$ language sql;
 ERROR:  cannot determine result data type
-DETAIL:  A result of type anyrange requires at least one input of type anyrange.
+DETAIL:  A result of type anyrange requires at least one input of type anyrange or anymultirange.
 create function polyf(x anyrange) returns anyarray as $$
   select array[lower(x), upper(x)]
 $$ language sql;
@@ -97,12 +97,27 @@ LINE 1: select polyf(int4range(42, 49), 11, 4.5) as fail;
                ^
 HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
 drop function polyf(x anycompatiblerange, y anycompatible, z anycompatible);
+create function polyf(x anycompatiblemultirange, y anycompatible, z anycompatible) returns anycompatiblearray as $$
+  select array[lower(x), upper(x), y, z]
+$$ language sql;
+select polyf(multirange(int4range(42, 49)), 11, 2::smallint) as int, polyf(multirange(float8range(4.5, 7.8)), 7.8, 11::real) as num;
+     int      |       num        
+--------------+------------------
+ {42,49,11,2} | {4.5,7.8,7.8,11}
+(1 row)
+
+select polyf(multirange(int4range(42, 49)), 11, 4.5) as fail;  -- range type doesn't fit
+ERROR:  function polyf(int4multirange, integer, numeric) does not exist
+LINE 1: select polyf(multirange(int4range(42, 49)), 11, 4.5) as fail...
+               ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+drop function polyf(x anycompatiblemultirange, y anycompatible, z anycompatible);
 -- fail, can't infer type:
 create function polyf(x anycompatible) returns anycompatiblerange as $$
   select array[x + 1, x + 2]
 $$ language sql;
 ERROR:  cannot determine result data type
-DETAIL:  A result of type anycompatiblerange requires at least one input of type anycompatiblerange.
+DETAIL:  A result of type anycompatiblerange requires at least one input of type anycompatiblerange or anycompatiblemultirange.
 create function polyf(x anycompatiblerange, y anycompatiblearray) returns anycompatiblerange as $$
   select x
 $$ language sql;
@@ -113,6 +128,22 @@ select polyf(int4range(42, 49), array[11]) as int, polyf(float8range(4.5, 7.8),
 (1 row)
 
 drop function polyf(x anycompatiblerange, y anycompatiblearray);
+-- fail, can't infer type:
+create function polyf(x anycompatible) returns anycompatiblemultirange as $$
+  select array[x + 1, x + 2]
+$$ language sql;
+ERROR:  cannot determine result data type
+DETAIL:  A result of type anycompatiblemultirange requires at least one input of type anycompatiblerange or anycompatiblemultirange.
+create function polyf(x anycompatiblemultirange, y anycompatiblearray) returns anycompatiblemultirange as $$
+  select x
+$$ language sql;
+select polyf(multirange(int4range(42, 49)), array[11]) as int, polyf(multirange(float8range(4.5, 7.8)), array[7]) as num;
+    int    |     num     
+-----------+-------------
+ {[42,49)} | {[4.5,7.8)}
+(1 row)
+
+drop function polyf(x anycompatiblemultirange, y anycompatiblearray);
 create function polyf(a anyelement, b anyarray,
                       c anycompatible, d anycompatible,
                       OUT x anyarray, OUT y anycompatiblearray)
@@ -231,7 +262,7 @@ CREATE AGGREGATE myaggp01a(*) (SFUNC = stfnp, STYPE = int4[],
 CREATE AGGREGATE myaggp02a(*) (SFUNC = stfnp, STYPE = anyarray,
   FINALFUNC = ffp, INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 --     N    P
 -- should CREATE
 CREATE AGGREGATE myaggp03a(*) (SFUNC = stfp, STYPE = int4[],
@@ -243,11 +274,11 @@ CREATE AGGREGATE myaggp03b(*) (SFUNC = stfp, STYPE = int4[],
 CREATE AGGREGATE myaggp04a(*) (SFUNC = stfp, STYPE = anyarray,
   FINALFUNC = ffp, INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 CREATE AGGREGATE myaggp04b(*) (SFUNC = stfp, STYPE = anyarray,
   INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 --    Case2 (R = P) && ((B = P) || (B = N))
 --    -------------------------------------
 --    S    tf1      B    tf2
@@ -302,13 +333,13 @@ ERROR:  function tfp(integer[], anyelement) does not exist
 CREATE AGGREGATE myaggp13a(BASETYPE = int, SFUNC = tfnp, STYPE = anyarray,
   FINALFUNC = ffp, INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 --    P    N        N    P
 -- should ERROR: tf2p(anyarray, int) not matched by tf2p(int[],anyelement)
 CREATE AGGREGATE myaggp14a(BASETYPE = int, SFUNC = tf2p, STYPE = anyarray,
   FINALFUNC = ffp, INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 --    P    N        P    N
 -- should ERROR: tfnp(anyarray, anyelement) not matched by tfnp(int[],int)
 CREATE AGGREGATE myaggp15a(BASETYPE = anyelement, SFUNC = tfnp,
@@ -324,21 +355,21 @@ ERROR:  function tf2p(anyarray, anyelement) does not exist
 CREATE AGGREGATE myaggp17a(BASETYPE = int, SFUNC = tf1p, STYPE = anyarray,
   FINALFUNC = ffp, INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 CREATE AGGREGATE myaggp17b(BASETYPE = int, SFUNC = tf1p, STYPE = anyarray,
   INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 --    P    P        N    P
 -- should ERROR: tfp(anyarray, int) not matched by tfp(anyarray, anyelement)
 CREATE AGGREGATE myaggp18a(BASETYPE = int, SFUNC = tfp, STYPE = anyarray,
   FINALFUNC = ffp, INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 CREATE AGGREGATE myaggp18b(BASETYPE = int, SFUNC = tfp, STYPE = anyarray,
   INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 --    P    P        P    N
 -- should ERROR: tf1p(anyarray, anyelement) not matched by tf1p(anyarray, int)
 CREATE AGGREGATE myaggp19a(BASETYPE = anyelement, SFUNC = tf1p,
@@ -368,11 +399,11 @@ CREATE AGGREGATE myaggn01b(*) (SFUNC = stfnp, STYPE = int4[],
 CREATE AGGREGATE myaggn02a(*) (SFUNC = stfnp, STYPE = anyarray,
   FINALFUNC = ffnp, INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 CREATE AGGREGATE myaggn02b(*) (SFUNC = stfnp, STYPE = anyarray,
   INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 --     N    P
 -- should CREATE
 CREATE AGGREGATE myaggn03a(*) (SFUNC = stfp, STYPE = int4[],
@@ -382,7 +413,7 @@ CREATE AGGREGATE myaggn03a(*) (SFUNC = stfp, STYPE = int4[],
 CREATE AGGREGATE myaggn04a(*) (SFUNC = stfp, STYPE = anyarray,
   FINALFUNC = ffnp, INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 --    Case4 (R = N) && ((B = P) || (B = N))
 --    -------------------------------------
 --    S    tf1      B    tf2
@@ -436,21 +467,21 @@ ERROR:  function tfp(integer[], anyelement) does not exist
 CREATE AGGREGATE myaggn13a(BASETYPE = int, SFUNC = tfnp, STYPE = anyarray,
   FINALFUNC = ffnp, INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 CREATE AGGREGATE myaggn13b(BASETYPE = int, SFUNC = tfnp, STYPE = anyarray,
   INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 --    P    N        N    P
 -- should ERROR: tf2p(anyarray, int) not matched by tf2p(int[],anyelement)
 CREATE AGGREGATE myaggn14a(BASETYPE = int, SFUNC = tf2p, STYPE = anyarray,
   FINALFUNC = ffnp, INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 CREATE AGGREGATE myaggn14b(BASETYPE = int, SFUNC = tf2p, STYPE = anyarray,
   INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 --    P    N        P    N
 -- should ERROR: tfnp(anyarray, anyelement) not matched by tfnp(int[],int)
 CREATE AGGREGATE myaggn15a(BASETYPE = anyelement, SFUNC = tfnp,
@@ -472,13 +503,13 @@ ERROR:  function tf2p(anyarray, anyelement) does not exist
 CREATE AGGREGATE myaggn17a(BASETYPE = int, SFUNC = tf1p, STYPE = anyarray,
   FINALFUNC = ffnp, INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 --    P    P        N    P
 -- should ERROR: tfp(anyarray, int) not matched by tfp(anyarray, anyelement)
 CREATE AGGREGATE myaggn18a(BASETYPE = int, SFUNC = tfp, STYPE = anyarray,
   FINALFUNC = ffnp, INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 --    P    P        P    N
 -- should ERROR: tf1p(anyarray, anyelement) not matched by tf1p(anyarray, int)
 CREATE AGGREGATE myaggn19a(BASETYPE = anyelement, SFUNC = tf1p,
@@ -1855,7 +1886,59 @@ returns anycompatiblerange as $$
   select $1
 $$ language sql;
 ERROR:  cannot determine result data type
-DETAIL:  A result of type anycompatiblerange requires at least one input of type anycompatiblerange.
+DETAIL:  A result of type anycompatiblerange requires at least one input of type anycompatiblerange or anycompatiblemultirange.
+create function anyctest(anycompatible, anycompatiblemultirange)
+returns anycompatiblemultirange as $$
+  select $2
+$$ language sql;
+select x, pg_typeof(x) from anyctest(11, multirange(int4range(4,7))) x;
+    x    |   pg_typeof    
+---------+----------------
+ {[4,7)} | int4multirange
+(1 row)
+
+select x, pg_typeof(x) from anyctest(11, multirange(numrange(4,7))) x;
+    x    |   pg_typeof   
+---------+---------------
+ {[4,7)} | nummultirange
+(1 row)
+
+select x, pg_typeof(x) from anyctest(11, 12) x;  -- fail
+ERROR:  function anyctest(integer, integer) does not exist
+LINE 1: select x, pg_typeof(x) from anyctest(11, 12) x;
+                                    ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+select x, pg_typeof(x) from anyctest(11.2, multirange(int4range(4,7))) x;  -- fail
+ERROR:  function anyctest(numeric, int4multirange) does not exist
+LINE 1: select x, pg_typeof(x) from anyctest(11.2, multirange(int4ra...
+                                    ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+select x, pg_typeof(x) from anyctest(11.2, '{[4,7)}') x;  -- fail
+ERROR:  could not identify anycompatiblemultirange type
+drop function anyctest(anycompatible, anycompatiblemultirange);
+create function anyctest(anycompatiblemultirange, anycompatiblemultirange)
+returns anycompatible as $$
+  select lower($1) + upper($2)
+$$ language sql;
+select x, pg_typeof(x) from anyctest(multirange(int4range(11,12)), multirange(int4range(4,7))) x;
+ x  | pg_typeof 
+----+-----------
+ 18 | integer
+(1 row)
+
+select x, pg_typeof(x) from anyctest(multirange(int4range(11,12)), multirange(numrange(4,7))) x; -- fail
+ERROR:  function anyctest(int4multirange, nummultirange) does not exist
+LINE 1: select x, pg_typeof(x) from anyctest(multirange(int4range(11...
+                                    ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+drop function anyctest(anycompatiblemultirange, anycompatiblemultirange);
+-- fail, can't infer result type:
+create function anyctest(anycompatible)
+returns anycompatiblemultirange as $$
+  select $1
+$$ language sql;
+ERROR:  cannot determine result data type
+DETAIL:  A result of type anycompatiblemultirange requires at least one input of type anycompatiblerange or anycompatiblemultirange.
 create function anyctest(anycompatiblenonarray, anycompatiblenonarray)
 returns anycompatiblearray as $$
   select array[$1, $2]
diff --git a/src/test/regress/expected/rangefuncs.out b/src/test/regress/expected/rangefuncs.out
index 06bd129fd22..5bbd5f6c0bd 100644
--- a/src/test/regress/expected/rangefuncs.out
+++ b/src/test/regress/expected/rangefuncs.out
@@ -1556,7 +1556,7 @@ DROP FUNCTION dup(anyelement);
 CREATE FUNCTION bad (f1 int, out f2 anyelement, out f3 anyarray)
 AS 'select $1, array[$1,$1]' LANGUAGE sql;
 ERROR:  cannot determine result data type
-DETAIL:  A result of type anyelement requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyelement requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 CREATE FUNCTION dup (f1 anycompatible, f2 anycompatiblearray, f3 out anycompatible, f4 out anycompatiblearray)
 AS 'select $1, $2' LANGUAGE sql;
 SELECT dup(22, array[44]);
diff --git a/src/test/regress/expected/rangetypes.out b/src/test/regress/expected/rangetypes.out
index c421f5394fe..6a1bbadc91a 100644
--- a/src/test/regress/expected/rangetypes.out
+++ b/src/test/regress/expected/rangetypes.out
@@ -518,6 +518,25 @@ select numrange(1.0, 2.0) * numrange(2.5, 3.0);
  empty
 (1 row)
 
+select range_intersect_agg(nr) from numrange_test;
+ range_intersect_agg 
+---------------------
+ empty
+(1 row)
+
+select range_intersect_agg(nr) from numrange_test where false;
+ range_intersect_agg 
+---------------------
+ 
+(1 row)
+
+select range_intersect_agg(nr) from numrange_test where nr @> 4.0;
+ range_intersect_agg 
+---------------------
+ [3,5)
+(1 row)
+
+analyze numrange_test;
 create table numrange_test2(nr numrange);
 create index numrange_test2_hash_idx on numrange_test2 using hash (nr);
 INSERT INTO numrange_test2 VALUES('[, 5)');
@@ -1371,12 +1390,12 @@ drop function anyarray_anyrange_func(anyarray, anyrange);
 create function bogus_func(anyelement)
   returns anyrange as 'select int4range(1,10)' language sql;
 ERROR:  cannot determine result data type
-DETAIL:  A result of type anyrange requires at least one input of type anyrange.
+DETAIL:  A result of type anyrange requires at least one input of type anyrange or anymultirange.
 -- should fail
 create function bogus_func(int)
   returns anyrange as 'select int4range(1,10)' language sql;
 ERROR:  cannot determine result data type
-DETAIL:  A result of type anyrange requires at least one input of type anyrange.
+DETAIL:  A result of type anyrange requires at least one input of type anyrange or anymultirange.
 create function range_add_bounds(anyrange)
   returns anyelement as 'select lower($1) + upper($1)' language sql;
 select range_add_bounds(int4range(1, 17));
@@ -1430,7 +1449,7 @@ drop function anycompatiblearray_anycompatiblerange_func(anycompatiblearray, any
 create function bogus_func(anycompatible)
   returns anycompatiblerange as 'select int4range(1,10)' language sql;
 ERROR:  cannot determine result data type
-DETAIL:  A result of type anycompatiblerange requires at least one input of type anycompatiblerange.
+DETAIL:  A result of type anycompatiblerange requires at least one input of type anycompatiblerange or anycompatiblemultirange.
 --
 -- Arrays of ranges
 --
@@ -1508,6 +1527,7 @@ reset enable_sort;
 --
 -- OUT/INOUT/TABLE functions
 --
+-- infer anyrange from anyrange
 create function outparam_succeed(i anyrange, out r anyrange, out t text)
   as $$ select $1, 'foo'::text $$ language sql;
 select * from outparam_succeed(int4range(1,2));
@@ -1525,6 +1545,16 @@ select * from outparam2_succeed(int4range(1,11));
  {1,11} | {11,1}
 (1 row)
 
+-- infer anyarray from anyrange
+create function outparam_succeed2(i anyrange, out r anyarray, out t text)
+  as $$ select ARRAY[upper($1)], 'foo'::text $$ language sql;
+select * from outparam_succeed2(int4range(int4range(1,2)));
+  r  |  t  
+-----+-----
+ {2} | foo
+(1 row)
+
+-- infer anyelement from anyrange
 create function inoutparam_succeed(out i anyelement, inout r anyrange)
   as $$ select upper($1), $1 $$ language sql;
 select * from inoutparam_succeed(int4range(1,2));
@@ -1547,14 +1577,14 @@ select * from table_succeed(int4range(1,11));
 create function outparam_fail(i anyelement, out r anyrange, out t text)
   as $$ select '[1,10]', 'foo' $$ language sql;
 ERROR:  cannot determine result data type
-DETAIL:  A result of type anyrange requires at least one input of type anyrange.
+DETAIL:  A result of type anyrange requires at least one input of type anyrange or anymultirange.
 --should fail
 create function inoutparam_fail(inout i anyelement, out r anyrange)
   as $$ select $1, '[1,10]' $$ language sql;
 ERROR:  cannot determine result data type
-DETAIL:  A result of type anyrange requires at least one input of type anyrange.
+DETAIL:  A result of type anyrange requires at least one input of type anyrange or anymultirange.
 --should fail
 create function table_fail(i anyelement) returns table(i anyelement, r anyrange)
   as $$ select $1, '[1,10]' $$ language sql;
 ERROR:  cannot determine result data type
-DETAIL:  A result of type anyrange requires at least one input of type anyrange.
+DETAIL:  A result of type anyrange requires at least one input of type anyrange or anymultirange.
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index 192445878d2..d9ce961be2b 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -92,6 +92,7 @@ num_exp_sqrt|t
 num_exp_sub|t
 num_input_test|f
 num_result|f
+nummultirange_test|t
 numrange_test|t
 onek|t
 onek2|t
@@ -172,6 +173,7 @@ quad_poly_tbl|t
 radix_text_tbl|t
 ramp|f
 real_city|f
+reservations|f
 road|t
 shighway|t
 slow_emp4000|f
diff --git a/src/test/regress/expected/type_sanity.out b/src/test/regress/expected/type_sanity.out
index 274130e7062..8df1ae47e48 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'));
@@ -195,18 +195,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
@@ -240,17 +241,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
@@ -318,18 +320,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
@@ -363,17 +366,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
@@ -631,3 +635,11 @@ WHERE pronargs != 2
 ----------+------------+---------
 (0 rows)
 
+-- every range should have a valid multirange
+SELECT p1.rngtypid, p1.rngsubtype, p1.rngmultitypid
+FROM pg_range p1
+WHERE p1.rngmultitypid IS NULL OR p1.rngmultitypid = 0;
+ rngtypid | rngsubtype | rngmultitypid 
+----------+------------+---------------
+(0 rows)
+
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index ae89ed7f0b4..432f454aa17 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -19,15 +19,16 @@ 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 xid
+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
 # geometry depends on point, lseg, box, path, polygon and circle
 # horology depends on interval, timetz, timestamp, timestamptz
 # ----------
-test: geometry horology regex oidjoins type_sanity opr_sanity misc_sanity comments expressions unicode
+test: geometry horology regex oidjoins type_sanity opr_sanity misc_sanity comments expressions unicode xid
 
 # ----------
 # These four each depend on the previous one
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index 525bdc804f6..081fce32e75 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -20,6 +20,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 681cc92ac20..0292dedd15a 100644
--- a/src/test/regress/sql/hash_func.sql
+++ b/src/test/regress/sql/hash_func.sql
@@ -227,6 +227,16 @@ FROM   (VALUES (int4range(10, 20)), (int4range(23, 43)),
 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);
+
 CREATE TYPE hash_test_t1 AS (a int, b text);
 SELECT v as value, hash_record(v)::bit(32) as standard,
        hash_record_extended(v, 0)::bit(32) as extended0,
diff --git a/src/test/regress/sql/multirangetypes.sql b/src/test/regress/sql/multirangetypes.sql
new file mode 100644
index 00000000000..2a6b4a0a291
--- /dev/null
+++ b/src/test/regress/sql/multirangetypes.sql
@@ -0,0 +1,658 @@
+-- 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'));
+
+--
+-- test casts, both a built-in range type and a user-defined one:
+--
+select 'empty'::int4range::int4multirange;
+select int4range(1, 3)::int4multirange;
+select int4range(1, null)::int4multirange;
+select int4range(null, null)::int4multirange;
+select 'empty'::textrange::textmultirange;
+select textrange('a', 'c')::textmultirange;
+select textrange('a', null)::textmultirange;
+select textrange(null, null)::textmultirange;
+
+--
+-- create some test data and test the operators
+--
+
+CREATE TABLE nummultirange_test (nmr NUMMULTIRANGE);
+CREATE INDEX nummultirange_test_btree ON nummultirange_test(nmr);
+
+INSERT INTO nummultirange_test VALUES('{}');
+INSERT INTO nummultirange_test VALUES('{[,)}');
+INSERT INTO nummultirange_test VALUES('{[3,]}');
+INSERT INTO nummultirange_test VALUES('{[,), [3,]}');
+INSERT INTO nummultirange_test VALUES('{[, 5)}');
+INSERT INTO nummultirange_test VALUES(nummultirange());
+INSERT INTO nummultirange_test VALUES(nummultirange(variadic '{}'::numrange[]));
+INSERT INTO nummultirange_test VALUES(nummultirange(numrange(1.1, 2.2)));
+INSERT INTO nummultirange_test VALUES('{empty}');
+INSERT INTO nummultirange_test VALUES(nummultirange(numrange(1.7, 1.7, '[]'), numrange(1.7, 1.9)));
+INSERT INTO nummultirange_test VALUES(nummultirange(numrange(1.7, 1.7, '[]'), numrange(1.9, 2.1)));
+
+SELECT nmr, isempty(nmr), lower(nmr), upper(nmr) FROM nummultirange_test ORDER BY nmr;
+SELECT nmr, lower_inc(nmr), lower_inf(nmr), upper_inc(nmr), upper_inf(nmr) FROM nummultirange_test ORDER BY nmr;
+
+SELECT * FROM nummultirange_test WHERE nmr = '{}';
+SELECT * FROM nummultirange_test WHERE nmr = '{(,5)}';
+SELECT * FROM nummultirange_test WHERE nmr = '{[3,)}';
+SELECT * FROM nummultirange_test WHERE nmr = '{[1.7,1.7]}';
+SELECT * FROM nummultirange_test WHERE nmr = '{[1.7,1.7],[1.9,2.1)}';
+SELECT * FROM nummultirange_test WHERE nmr < '{}';
+SELECT * FROM nummultirange_test WHERE nmr < '{[-1000.0, -1000.0]}';
+SELECT * FROM nummultirange_test WHERE nmr < '{[0.0, 1.0]}';
+SELECT * FROM nummultirange_test WHERE nmr < '{[1000.0, 1001.0]}';
+SELECT * FROM nummultirange_test WHERE nmr <= '{}';
+SELECT * FROM nummultirange_test WHERE nmr <= '{[3,)}';
+SELECT * FROM nummultirange_test WHERE nmr >= '{}';
+SELECT * FROM nummultirange_test WHERE nmr >= '{[3,)}';
+SELECT * FROM nummultirange_test WHERE nmr > '{}';
+SELECT * FROM nummultirange_test WHERE nmr > '{[-1000.0, -1000.0]}';
+SELECT * FROM nummultirange_test WHERE nmr > '{[0.0, 1.0]}';
+SELECT * FROM nummultirange_test WHERE nmr > '{[1000.0, 1001.0]}';
+SELECT * FROM nummultirange_test WHERE nmr <> '{}';
+SELECT * FROM nummultirange_test WHERE nmr <> '{(,5)}';
+
+select nummultirange(numrange(2.0, 1.0));
+select nummultirange(numrange(5.0, 6.0), numrange(1.0, 2.0));
+
+analyze nummultirange_test;
+
+-- overlaps
+SELECT * FROM nummultirange_test WHERE range_overlaps_multirange(numrange(4.0, 4.2), nmr);
+SELECT * FROM nummultirange_test WHERE numrange(4.0, 4.2) && nmr;
+SELECT * FROM nummultirange_test WHERE multirange_overlaps_range(nmr, numrange(4.0, 4.2));
+SELECT * FROM nummultirange_test WHERE nmr && numrange(4.0, 4.2);
+SELECT * FROM nummultirange_test WHERE multirange_overlaps_multirange(nmr, nummultirange(numrange(4.0, 4.2), numrange(6.0, 7.0)));
+SELECT * FROM nummultirange_test WHERE nmr && nummultirange(numrange(4.0, 4.2), numrange(6.0, 7.0));
+SELECT * FROM nummultirange_test WHERE nmr && nummultirange(numrange(6.0, 7.0));
+SELECT * FROM nummultirange_test WHERE nmr && nummultirange(numrange(6.0, 7.0), numrange(8.0, 9.0));
+
+-- mr contains x
+SELECT * FROM nummultirange_test WHERE multirange_contains_elem(nmr, 4.0);
+SELECT * FROM nummultirange_test WHERE nmr @> 4.0;
+SELECT * FROM nummultirange_test WHERE multirange_contains_range(nmr, numrange(4.0, 4.2));
+SELECT * FROM nummultirange_test WHERE nmr @> numrange(4.0, 4.2);
+SELECT * FROM nummultirange_test WHERE multirange_contains_multirange(nmr, '{[4.0,4.2), [6.0, 8.0)}');
+SELECT * FROM nummultirange_test WHERE nmr @> '{[4.0,4.2), [6.0, 8.0)}'::nummultirange;
+
+-- x is contained by mr
+SELECT * FROM nummultirange_test WHERE elem_contained_by_multirange(4.0, nmr);
+SELECT * FROM nummultirange_test WHERE 4.0 <@ nmr;
+SELECT * FROM nummultirange_test WHERE range_contained_by_multirange(numrange(4.0, 4.2), nmr);
+SELECT * FROM nummultirange_test WHERE numrange(4.0, 4.2) <@ nmr;
+SELECT * FROM nummultirange_test WHERE multirange_contained_by_multirange('{[4.0,4.2), [6.0, 8.0)}', nmr);
+SELECT * FROM nummultirange_test WHERE '{[4.0,4.2), [6.0, 8.0)}'::nummultirange <@ nmr;
+
+-- overlaps
+SELECT 'empty'::numrange && nummultirange();
+SELECT 'empty'::numrange && nummultirange(numrange(1,2));
+SELECT nummultirange() && 'empty'::numrange;
+SELECT nummultirange(numrange(1,2)) && 'empty'::numrange;
+SELECT nummultirange() && nummultirange();
+SELECT nummultirange() && nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) && nummultirange();
+SELECT nummultirange(numrange(3,4)) && nummultirange(numrange(1,2), numrange(7,8));
+SELECT nummultirange(numrange(1,2), numrange(7,8)) && nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(3,4)) && nummultirange(numrange(1,2), numrange(3.5,8));
+SELECT nummultirange(numrange(1,2), numrange(3.5,8)) && numrange(3,4);
+SELECT nummultirange(numrange(1,2), numrange(3.5,8)) && nummultirange(numrange(3,4));
+
+-- contains
+SELECT nummultirange() @> nummultirange();
+SELECT nummultirange() @> 'empty'::numrange;
+SELECT nummultirange(numrange(null,null)) @> numrange(1,2);
+SELECT nummultirange(numrange(null,null)) @> numrange(null,2);
+SELECT nummultirange(numrange(null,null)) @> numrange(2,null);
+SELECT nummultirange(numrange(null,5)) @> numrange(null,3);
+SELECT nummultirange(numrange(null,5)) @> numrange(null,8);
+SELECT nummultirange(numrange(5,null)) @> numrange(8,null);
+SELECT nummultirange(numrange(5,null)) @> numrange(3,null);
+SELECT nummultirange(numrange(1,5)) @> numrange(8,9);
+SELECT nummultirange(numrange(1,5)) @> numrange(3,9);
+SELECT nummultirange(numrange(1,5)) @> numrange(1,4);
+SELECT nummultirange(numrange(1,5)) @> numrange(1,5);
+SELECT nummultirange(numrange(-4,-2), numrange(1,5)) @> numrange(1,5);
+SELECT nummultirange(numrange(1,5), numrange(8,9)) @> numrange(1,5);
+SELECT nummultirange(numrange(1,5), numrange(8,9)) @> numrange(6,7);
+SELECT nummultirange(numrange(1,5), numrange(6,9)) @> numrange(6,7);
+SELECT '{[1,5)}'::nummultirange @> '{[1,5)}';
+SELECT '{[-4,-2), [1,5)}'::nummultirange @> '{[1,5)}';
+SELECT '{[1,5), [8,9)}'::nummultirange @> '{[1,5)}';
+SELECT '{[1,5), [8,9)}'::nummultirange @> '{[6,7)}';
+SELECT '{[1,5), [6,9)}'::nummultirange @> '{[6,7)}';
+
+-- is contained by
+SELECT nummultirange() <@ nummultirange();
+SELECT 'empty'::numrange <@ nummultirange();
+SELECT numrange(1,2) <@ nummultirange(numrange(null,null));
+SELECT numrange(null,2) <@ nummultirange(numrange(null,null));
+SELECT numrange(2,null) <@ nummultirange(numrange(null,null));
+SELECT numrange(null,3) <@ nummultirange(numrange(null,5));
+SELECT numrange(null,8) <@ nummultirange(numrange(null,5));
+SELECT numrange(8,null) <@ nummultirange(numrange(5,null));
+SELECT numrange(3,null) <@ nummultirange(numrange(5,null));
+SELECT numrange(8,9) <@ nummultirange(numrange(1,5));
+SELECT numrange(3,9) <@ nummultirange(numrange(1,5));
+SELECT numrange(1,4) <@ nummultirange(numrange(1,5));
+SELECT numrange(1,5) <@ nummultirange(numrange(1,5));
+SELECT numrange(1,5) <@ nummultirange(numrange(-4,-2), numrange(1,5));
+SELECT numrange(1,5) <@ nummultirange(numrange(1,5), numrange(8,9));
+SELECT numrange(6,7) <@ nummultirange(numrange(1,5), numrange(8,9));
+SELECT numrange(6,7) <@ nummultirange(numrange(1,5), numrange(6,9));
+SELECT '{[1,5)}' <@ '{[1,5)}'::nummultirange;
+SELECT '{[1,5)}' <@ '{[-4,-2), [1,5)}'::nummultirange;
+SELECT '{[1,5)}' <@ '{[1,5), [8,9)}'::nummultirange;
+SELECT '{[6,7)}' <@ '{[1,5), [8,9)}'::nummultirange;
+SELECT '{[6,7)}' <@ '{[1,5), [6,9)}'::nummultirange;
+
+-- overleft
+SELECT 'empty'::numrange &< nummultirange();
+SELECT 'empty'::numrange &< nummultirange(numrange(1,2));
+SELECT nummultirange() &< 'empty'::numrange;
+SELECT nummultirange(numrange(1,2)) &< 'empty'::numrange;
+SELECT nummultirange() &< nummultirange();
+SELECT nummultirange(numrange(1,2)) &< nummultirange();
+SELECT nummultirange() &< nummultirange(numrange(1,2));
+SELECT numrange(6,7) &< nummultirange(numrange(3,4));
+SELECT numrange(1,2) &< nummultirange(numrange(3,4));
+SELECT numrange(1,4) &< nummultirange(numrange(3,4));
+SELECT numrange(1,6) &< nummultirange(numrange(3,4));
+SELECT numrange(3.5,6) &< nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(6,7)) &< numrange(3,4);
+SELECT nummultirange(numrange(1,2)) &< numrange(3,4);
+SELECT nummultirange(numrange(1,4)) &< numrange(3,4);
+SELECT nummultirange(numrange(1,6)) &< numrange(3,4);
+SELECT nummultirange(numrange(3.5,6)) &< numrange(3,4);
+SELECT nummultirange(numrange(6,7)) &< nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,2)) &< nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,4)) &< nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,6)) &< nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(3.5,6)) &< nummultirange(numrange(3,4));
+
+-- overright
+SELECT nummultirange() &> 'empty'::numrange;
+SELECT nummultirange(numrange(1,2)) &> 'empty'::numrange;
+SELECT 'empty'::numrange &> nummultirange();
+SELECT 'empty'::numrange &> nummultirange(numrange(1,2));
+SELECT nummultirange() &> nummultirange();
+SELECT nummultirange() &> nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) &> nummultirange();
+SELECT nummultirange(numrange(3,4)) &> numrange(6,7);
+SELECT nummultirange(numrange(3,4)) &> numrange(1,2);
+SELECT nummultirange(numrange(3,4)) &> numrange(1,4);
+SELECT nummultirange(numrange(3,4)) &> numrange(1,6);
+SELECT nummultirange(numrange(3,4)) &> numrange(3.5,6);
+SELECT numrange(3,4) &> nummultirange(numrange(6,7));
+SELECT numrange(3,4) &> nummultirange(numrange(1,2));
+SELECT numrange(3,4) &> nummultirange(numrange(1,4));
+SELECT numrange(3,4) &> nummultirange(numrange(1,6));
+SELECT numrange(3,4) &> nummultirange(numrange(3.5,6));
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(6,7));
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(1,4));
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(1,6));
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(3.5,6));
+
+-- meets
+SELECT 'empty'::numrange -|- nummultirange();
+SELECT 'empty'::numrange -|- nummultirange(numrange(1,2));
+SELECT nummultirange() -|- 'empty'::numrange;
+SELECT nummultirange(numrange(1,2)) -|- 'empty'::numrange;
+SELECT nummultirange() -|- nummultirange();
+SELECT nummultirange(numrange(1,2)) -|- nummultirange();
+SELECT nummultirange() -|- nummultirange(numrange(1,2));
+SELECT numrange(1,2) -|- nummultirange(numrange(2,4));
+SELECT numrange(1,2) -|- nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,2)) -|- numrange(2,4);
+SELECT nummultirange(numrange(1,2)) -|- numrange(3,4);
+SELECT nummultirange(numrange(1,2)) -|- nummultirange(numrange(2,4));
+SELECT nummultirange(numrange(1,2)) -|- nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,2), numrange(5,6)) -|- nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,2), numrange(5,6)) -|- nummultirange(numrange(6,7));
+SELECT nummultirange(numrange(1,2), numrange(5,6)) -|- nummultirange(numrange(8,9));
+SELECT nummultirange(numrange(1,2)) -|- nummultirange(numrange(2,4), numrange(6,7));
+
+-- strictly left
+select 'empty'::numrange << nummultirange();
+select numrange(1,2) << nummultirange();
+select numrange(1,2) << nummultirange(numrange(3,4));
+select numrange(1,2) << nummultirange(numrange(0,4));
+select numrange(1,2) << nummultirange(numrange(0,4), numrange(7,8));
+select nummultirange() << 'empty'::numrange;
+select nummultirange() << numrange(1,2);
+select nummultirange(numrange(3,4)) << numrange(3,6);
+select nummultirange(numrange(0,2)) << numrange(3,6);
+select nummultirange(numrange(0,2), numrange(7,8)) << numrange(3,6);
+select nummultirange(numrange(-4,-2), numrange(0,2)) << numrange(3,6);
+select nummultirange() << nummultirange();
+select nummultirange() << nummultirange(numrange(1,2));
+select nummultirange(numrange(1,2)) << nummultirange();
+select nummultirange(numrange(1,2)) << nummultirange(numrange(1,2));
+select nummultirange(numrange(1,2)) << nummultirange(numrange(3,4));
+select nummultirange(numrange(1,2)) << nummultirange(numrange(3,4), numrange(7,8));
+select nummultirange(numrange(1,2), numrange(4,5)) << nummultirange(numrange(3,4), numrange(7,8));
+
+-- strictly right
+select nummultirange() >> 'empty'::numrange;
+select nummultirange() >> numrange(1,2);
+select nummultirange(numrange(3,4)) >> numrange(1,2);
+select nummultirange(numrange(0,4)) >> numrange(1,2);
+select nummultirange(numrange(0,4), numrange(7,8)) >> numrange(1,2);
+select 'empty'::numrange >> nummultirange();
+select numrange(1,2) >> nummultirange();
+select numrange(3,6) >> nummultirange(numrange(3,4));
+select numrange(3,6) >> nummultirange(numrange(0,2));
+select numrange(3,6) >> nummultirange(numrange(0,2), numrange(7,8));
+select numrange(3,6) >> nummultirange(numrange(-4,-2), numrange(0,2));
+select nummultirange() >> nummultirange();
+select nummultirange(numrange(1,2)) >> nummultirange();
+select nummultirange() >> nummultirange(numrange(1,2));
+select nummultirange(numrange(1,2)) >> nummultirange(numrange(1,2));
+select nummultirange(numrange(3,4)) >> nummultirange(numrange(1,2));
+select nummultirange(numrange(3,4), numrange(7,8)) >> nummultirange(numrange(1,2));
+select nummultirange(numrange(3,4), numrange(7,8)) >> nummultirange(numrange(1,2), numrange(4,5));
+
+-- union
+SELECT nummultirange() + nummultirange();
+SELECT nummultirange() + nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) + nummultirange();
+SELECT nummultirange(numrange(1,2)) + nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) + nummultirange(numrange(2,4));
+SELECT nummultirange(numrange(1,2)) + nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) + nummultirange(numrange(2,4));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) + nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) + nummultirange(numrange(0,9));
+
+-- merge
+SELECT range_merge(nummultirange());
+SELECT range_merge(nummultirange(numrange(1,2)));
+SELECT range_merge(nummultirange(numrange(1,2), numrange(7,8)));
+
+-- minus
+SELECT nummultirange() - nummultirange();
+SELECT nummultirange() - nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) - nummultirange();
+SELECT nummultirange(numrange(1,2), numrange(3,4)) - nummultirange();
+SELECT nummultirange(numrange(1,2)) - nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) - nummultirange(numrange(2,4));
+SELECT nummultirange(numrange(1,2)) - nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(2,3));
+SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(0,8));
+SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(0,2));
+SELECT nummultirange(numrange(1,8)) - nummultirange(numrange(0,2), numrange(3,4));
+SELECT nummultirange(numrange(1,8)) - nummultirange(numrange(2,3), numrange(5,null));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(-2,0));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(2,4));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(3,5));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(0,9));
+SELECT nummultirange(numrange(1,3), numrange(4,5)) - nummultirange(numrange(2,9));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(8,9));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(-2,0), numrange(8,9));
+
+-- intersection
+SELECT nummultirange() * nummultirange();
+SELECT nummultirange() * nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) * nummultirange();
+SELECT '{[1,3)}'::nummultirange * '{[1,5)}'::nummultirange;
+SELECT '{[1,3)}'::nummultirange * '{[0,5)}'::nummultirange;
+SELECT '{[1,3)}'::nummultirange * '{[0,2)}'::nummultirange;
+SELECT '{[1,3)}'::nummultirange * '{[2,5)}'::nummultirange;
+SELECT '{[1,4)}'::nummultirange * '{[2,3)}'::nummultirange;
+SELECT '{[1,4)}'::nummultirange * '{[0,2), [3,5)}'::nummultirange;
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[0,8), [9,12)}'::nummultirange;
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[9,12)}'::nummultirange;
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[-5,-4), [5,6), [9,12)}'::nummultirange;
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[0,2), [3,8), [9,12)}'::nummultirange;
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[0,2), [3,8), [9,12)}'::nummultirange;
+
+--
+-- range_agg function
+--
+create table reservations ( room_id integer not null, booked_during daterange );
+insert into reservations values
+-- 1: has a meets and a gap
+(1, daterange('2018-07-01', '2018-07-07')),
+(1, daterange('2018-07-07', '2018-07-14')),
+(1, daterange('2018-07-20', '2018-07-22')),
+-- 2: just a single row
+(2, daterange('2018-07-01', '2018-07-03')),
+-- 3: one null range
+(3, NULL),
+-- 4: two null ranges
+(4, NULL),
+(4, NULL),
+-- 5: a null range and a non-null range
+(5, NULL),
+(5, daterange('2018-07-01', '2018-07-03')),
+-- 6: has overlap
+(6, daterange('2018-07-01', '2018-07-07')),
+(6, daterange('2018-07-05', '2018-07-10')),
+-- 7: two ranges that meet: no gap or overlap
+(7, daterange('2018-07-01', '2018-07-07')),
+(7, daterange('2018-07-07', '2018-07-14')),
+-- 8: an empty range
+(8, 'empty'::daterange)
+;
+SELECT   room_id, range_agg(booked_during)
+FROM     reservations
+GROUP BY room_id
+ORDER BY room_id;
+
+-- range_agg on a custom range type too
+SELECT  range_agg(r)
+FROM    (VALUES
+          ('[a,c]'::textrange),
+          ('[b,b]'::textrange),
+          ('[c,f]'::textrange),
+          ('[g,h)'::textrange),
+          ('[h,j)'::textrange)
+        ) t(r);
+
+select range_intersect_agg(nmr) from nummultirange_test;
+select range_intersect_agg(nmr) from nummultirange_test where false;
+-- test with just one input:
+select range_intersect_agg(nmr) from (values ('{[1,2]}'::nummultirange)) t(nmr);
+select range_intersect_agg(nmr) from nummultirange_test where nmr @> 4.0;
+
+create table nummultirange_test2(nmr nummultirange);
+create index nummultirange_test2_hash_idx on nummultirange_test2 using hash (nmr);
+
+INSERT INTO nummultirange_test2 VALUES('{[, 5)}');
+INSERT INTO nummultirange_test2 VALUES(nummultirange(numrange(1.1, 2.2)));
+INSERT INTO nummultirange_test2 VALUES(nummultirange(numrange(1.1, 2.2)));
+INSERT INTO nummultirange_test2 VALUES(nummultirange(numrange(1.1, 2.2,'()')));
+INSERT INTO nummultirange_test2 VALUES('{}');
+
+select * from nummultirange_test2 where nmr = '{}';
+select * from nummultirange_test2 where nmr = nummultirange(numrange(1.1, 2.2));
+select * from nummultirange_test2 where nmr = nummultirange(numrange(1.1, 2.3));
+
+set enable_nestloop=t;
+set enable_hashjoin=f;
+set enable_mergejoin=f;
+select * from nummultirange_test natural join nummultirange_test2 order by nmr;
+set enable_nestloop=f;
+set enable_hashjoin=t;
+set enable_mergejoin=f;
+select * from nummultirange_test natural join nummultirange_test2 order by nmr;
+set enable_nestloop=f;
+set enable_hashjoin=f;
+set enable_mergejoin=t;
+select * from nummultirange_test natural join nummultirange_test2 order by nmr;
+
+set enable_nestloop to default;
+set enable_hashjoin to default;
+set enable_mergejoin to default;
+
+DROP TABLE nummultirange_test2;
+
+--
+-- Test user-defined multirange of floats
+--
+
+select '{[123.001, 5.e9)}'::float8multirange @> 888.882::float8;
+create table float8multirange_test(f8mr float8multirange, i int);
+insert into float8multirange_test values(float8multirange(float8range(-100.00007, '1.111113e9')), 42);
+select * from float8multirange_test;
+drop table float8multirange_test;
+
+--
+-- Test multirange types over domains
+--
+
+create domain mydomain as int4;
+create type mydomainrange as range(subtype=mydomain);
+select '{[4,50)}'::mydomainmultirange @> 7::mydomain;
+drop domain mydomain cascade;
+
+--
+-- Test domains over multirange types
+--
+
+create domain restrictedmultirange as int4multirange check (upper(value) < 10);
+select '{[4,5)}'::restrictedmultirange @> 7;
+select '{[4,50)}'::restrictedmultirange @> 7; -- should fail
+drop domain restrictedmultirange;
+
+--
+-- Test multiple multirange types over the same subtype
+--
+
+create type textrange1 as range(subtype=text, collation="C");
+create type textrange2 as range(subtype=text, collation="C");
+
+select textmultirange1(textrange2('a','Z'));  -- should fail
+select textmultirange1(textrange1('a','Z')) @> 'b'::text;
+select textmultirange2(textrange2('a','z')) @> 'b'::text;
+
+drop type textrange1;
+drop type textrange2;
+
+--
+-- Test polymorphic type system
+--
+
+create function anyarray_anymultirange_func(a anyarray, r anymultirange)
+  returns anyelement as 'select $1[1] + lower($2);' language sql;
+
+select anyarray_anymultirange_func(ARRAY[1,2], int4multirange(int4range(10,20)));
+
+-- should fail
+select anyarray_anymultirange_func(ARRAY[1,2], nummultirange(numrange(10,20)));
+
+drop function anyarray_anymultirange_func(anyarray, anymultirange);
+
+-- should fail
+create function bogus_func(anyelement)
+  returns anymultirange as 'select int4multirange(int4range(1,10))' language sql;
+
+-- should fail
+create function bogus_func(int)
+  returns anymultirange as 'select int4multirange(int4range(1,10))' language sql;
+
+create function range_add_bounds(anymultirange)
+  returns anyelement as 'select lower($1) + upper($1)' language sql;
+
+select range_add_bounds(int4multirange(int4range(1, 17)));
+select range_add_bounds(nummultirange(numrange(1.0001, 123.123)));
+
+create function multirangetypes_sql(q anymultirange, b anyarray, out c anyelement)
+  as $$ select upper($1) + $2[1] $$
+  language sql;
+
+select multirangetypes_sql(int4multirange(int4range(1,10)), ARRAY[2,20]);
+select multirangetypes_sql(nummultirange(numrange(1,10)), ARRAY[2,20]);  -- match failure
+
+create function anycompatiblearray_anycompatiblemultirange_func(a anycompatiblearray, mr anycompatiblemultirange)
+  returns anycompatible as 'select $1[1] + lower($2);' language sql;
+
+select anycompatiblearray_anycompatiblemultirange_func(ARRAY[1,2], multirange(int4range(10,20)));
+
+select anycompatiblearray_anycompatiblemultirange_func(ARRAY[1,2], multirange(numrange(10,20)));
+
+-- should fail
+select anycompatiblearray_anycompatiblemultirange_func(ARRAY[1.1,2], multirange(int4range(10,20)));
+
+drop function anycompatiblearray_anycompatiblemultirange_func(anycompatiblearray, anycompatiblemultirange);
+
+create function anycompatiblerange_anycompatiblemultirange_func(r anycompatiblerange, mr anycompatiblemultirange)
+  returns anycompatible as 'select lower($1) + lower($2);' language sql;
+
+select anycompatiblerange_anycompatiblemultirange_func(int4range(1,2), multirange(int4range(10,20)));
+
+-- should fail
+select anycompatiblerange_anycompatiblemultirange_func(numrange(1,2), multirange(int4range(10,20)));
+
+drop function anycompatiblerange_anycompatiblemultirange_func(anycompatiblerange, anycompatiblemultirange);
+
+-- should fail
+create function bogus_func(anycompatible)
+  returns anycompatiblerange as 'select int4range(1,10)' language sql;
+
+--
+-- Arrays of multiranges
+--
+
+select ARRAY[nummultirange(numrange(1.1, 1.2)), nummultirange(numrange(12.3, 155.5))];
+
+create table i8mr_array (f1 int, f2 int8multirange[]);
+insert into i8mr_array values (42, array[int8multirange(int8range(1,10)), int8multirange(int8range(2,20))]);
+select * from i8mr_array;
+drop table i8mr_array;
+
+--
+-- Multiranges of arrays
+--
+
+select arraymultirange(arrayrange(ARRAY[1,2], ARRAY[2,1]));
+select arraymultirange(arrayrange(ARRAY[2,1], ARRAY[1,2]));  -- fail
+
+select array[1,1] <@ arraymultirange(arrayrange(array[1,2], array[2,1]));
+select array[1,3] <@ arraymultirange(arrayrange(array[1,2], array[2,1]));
+
+--
+-- Ranges of composites
+--
+
+create type two_ints as (a int, b int);
+create type two_ints_range as range (subtype = two_ints);
+
+-- with force_parallel_mode on, this exercises tqueue.c's range remapping
+select *, row_to_json(upper(t)) as u from
+  (values (two_ints_multirange(two_ints_range(row(1,2), row(3,4)))),
+          (two_ints_multirange(two_ints_range(row(5,6), row(7,8))))) v(t);
+
+drop type two_ints cascade;
+
+--
+-- Check behavior when subtype lacks a hash function
+--
+
+set enable_sort = off;  -- try to make it pick a hash setop implementation
+
+select '{(2,5)}'::cashmultirange except select '{(5,6)}'::cashmultirange;
+
+reset enable_sort;
+
+--
+-- OUT/INOUT/TABLE functions
+--
+
+-- infer anymultirange from anymultirange
+create function mr_outparam_succeed(i anymultirange, out r anymultirange, out t text)
+  as $$ select $1, 'foo'::text $$ language sql;
+
+select * from mr_outparam_succeed(int4multirange(int4range(1,2)));
+
+-- infer anyarray from anymultirange
+create function mr_outparam_succeed2(i anymultirange, out r anyarray, out t text)
+  as $$ select ARRAY[upper($1)], 'foo'::text $$ language sql;
+
+select * from mr_outparam_succeed2(int4multirange(int4range(1,2)));
+
+-- infer anyrange from anymultirange
+create function mr_outparam_succeed3(i anymultirange, out r anyrange, out t text)
+  as $$ select range_merge($1), 'foo'::text $$ language sql;
+select * from mr_outparam_succeed3(int4multirange(int4range(1,2)));
+
+-- infer anymultirange from anyrange
+create function mr_outparam_succeed4(i anyrange, out r anymultirange, out t text)
+  as $$ select multirange($1), 'foo'::text $$ language sql;
+
+select * from mr_outparam_succeed4(int4range(1,2));
+
+-- infer anyelement from anymultirange
+create function mr_inoutparam_succeed(out i anyelement, inout r anymultirange)
+  as $$ select upper($1), $1 $$ language sql;
+
+select * from mr_inoutparam_succeed(int4multirange(int4range(1,2)));
+
+-- infer anyelement+anymultirange from anyelement+anymultirange
+create function mr_table_succeed(i anyelement, r anymultirange) returns table(i anyelement, r anymultirange)
+  as $$ select $1, $2 $$ language sql;
+
+select * from mr_table_succeed(123, int4multirange(int4range(1,11)));
+
+-- use anymultirange in plpgsql
+create function mr_polymorphic(i anyrange) returns anymultirange
+  as $$ begin return multirange($1); end; $$ language plpgsql;
+select mr_polymorphic(int4range(1, 4));
+
+-- should fail
+create function mr_outparam_fail(i anyelement, out r anymultirange, out t text)
+  as $$ select '[1,10]', 'foo' $$ language sql;
+
+--should fail
+create function mr_inoutparam_fail(inout i anyelement, out r anymultirange)
+  as $$ select $1, '[1,10]' $$ language sql;
+
+--should fail
+create function mr_table_fail(i anyelement) returns table(i anyelement, r anymultirange)
+  as $$ select $1, '[1,10]' $$ language sql;
diff --git a/src/test/regress/sql/opr_sanity.sql b/src/test/regress/sql/opr_sanity.sql
index 307aab1deb7..3d6b60179bc 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::regtype, p2.prorettype::regtype
 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;
 
@@ -279,16 +290,19 @@ WHERE p1.prorettype IN
      '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;
 
--- anyrange is tighter than the rest, can only resolve from anyrange input
+-- anyrange and anymultirange are tighter than the rest, can only resolve
+-- from each other
 
 SELECT p1.oid, p1.proname
 FROM pg_proc as p1
-WHERE p1.prorettype = 'anyrange'::regtype
+WHERE p1.prorettype IN ('anyrange'::regtype, 'anymultirange'::regtype)
   AND NOT
-     'anyrange'::regtype = ANY (p1.proargtypes)
+    ('anyrange'::regtype = ANY (p1.proargtypes) OR
+      'anymultirange'::regtype = ANY (p1.proargtypes))
 ORDER BY 2;
 
 -- similarly for the anycompatible family
diff --git a/src/test/regress/sql/polymorphism.sql b/src/test/regress/sql/polymorphism.sql
index ff517fea41a..891b023ada0 100644
--- a/src/test/regress/sql/polymorphism.sql
+++ b/src/test/regress/sql/polymorphism.sql
@@ -71,6 +71,16 @@ select polyf(int4range(42, 49), 11, 4.5) as fail;  -- range type doesn't fit
 
 drop function polyf(x anycompatiblerange, y anycompatible, z anycompatible);
 
+create function polyf(x anycompatiblemultirange, y anycompatible, z anycompatible) returns anycompatiblearray as $$
+  select array[lower(x), upper(x), y, z]
+$$ language sql;
+
+select polyf(multirange(int4range(42, 49)), 11, 2::smallint) as int, polyf(multirange(float8range(4.5, 7.8)), 7.8, 11::real) as num;
+
+select polyf(multirange(int4range(42, 49)), 11, 4.5) as fail;  -- range type doesn't fit
+
+drop function polyf(x anycompatiblemultirange, y anycompatible, z anycompatible);
+
 -- fail, can't infer type:
 create function polyf(x anycompatible) returns anycompatiblerange as $$
   select array[x + 1, x + 2]
@@ -84,6 +94,19 @@ select polyf(int4range(42, 49), array[11]) as int, polyf(float8range(4.5, 7.8),
 
 drop function polyf(x anycompatiblerange, y anycompatiblearray);
 
+-- fail, can't infer type:
+create function polyf(x anycompatible) returns anycompatiblemultirange as $$
+  select array[x + 1, x + 2]
+$$ language sql;
+
+create function polyf(x anycompatiblemultirange, y anycompatiblearray) returns anycompatiblemultirange as $$
+  select x
+$$ language sql;
+
+select polyf(multirange(int4range(42, 49)), array[11]) as int, polyf(multirange(float8range(4.5, 7.8)), array[7]) as num;
+
+drop function polyf(x anycompatiblemultirange, y anycompatiblearray);
+
 create function polyf(a anyelement, b anyarray,
                       c anycompatible, d anycompatible,
                       OUT x anyarray, OUT y anycompatiblearray)
@@ -997,6 +1020,35 @@ returns anycompatiblerange as $$
   select $1
 $$ language sql;
 
+create function anyctest(anycompatible, anycompatiblemultirange)
+returns anycompatiblemultirange as $$
+  select $2
+$$ language sql;
+
+select x, pg_typeof(x) from anyctest(11, multirange(int4range(4,7))) x;
+select x, pg_typeof(x) from anyctest(11, multirange(numrange(4,7))) x;
+select x, pg_typeof(x) from anyctest(11, 12) x;  -- fail
+select x, pg_typeof(x) from anyctest(11.2, multirange(int4range(4,7))) x;  -- fail
+select x, pg_typeof(x) from anyctest(11.2, '{[4,7)}') x;  -- fail
+
+drop function anyctest(anycompatible, anycompatiblemultirange);
+
+create function anyctest(anycompatiblemultirange, anycompatiblemultirange)
+returns anycompatible as $$
+  select lower($1) + upper($2)
+$$ language sql;
+
+select x, pg_typeof(x) from anyctest(multirange(int4range(11,12)), multirange(int4range(4,7))) x;
+select x, pg_typeof(x) from anyctest(multirange(int4range(11,12)), multirange(numrange(4,7))) x; -- fail
+
+drop function anyctest(anycompatiblemultirange, anycompatiblemultirange);
+
+-- fail, can't infer result type:
+create function anyctest(anycompatible)
+returns anycompatiblemultirange as $$
+  select $1
+$$ language sql;
+
 create function anyctest(anycompatiblenonarray, anycompatiblenonarray)
 returns anycompatiblearray as $$
   select array[$1, $2]
diff --git a/src/test/regress/sql/rangetypes.sql b/src/test/regress/sql/rangetypes.sql
index 4048b1d1854..b69efede3ae 100644
--- a/src/test/regress/sql/rangetypes.sql
+++ b/src/test/regress/sql/rangetypes.sql
@@ -118,6 +118,12 @@ select numrange(1.0, 2.0) * numrange(2.0, 3.0);
 select numrange(1.0, 2.0) * numrange(1.5, 3.0);
 select numrange(1.0, 2.0) * numrange(2.5, 3.0);
 
+select range_intersect_agg(nr) from numrange_test;
+select range_intersect_agg(nr) from numrange_test where false;
+select range_intersect_agg(nr) from numrange_test where nr @> 4.0;
+
+analyze numrange_test;
+
 create table numrange_test2(nr numrange);
 create index numrange_test2_hash_idx on numrange_test2 using hash (nr);
 
@@ -528,6 +534,7 @@ reset enable_sort;
 -- OUT/INOUT/TABLE functions
 --
 
+-- infer anyrange from anyrange
 create function outparam_succeed(i anyrange, out r anyrange, out t text)
   as $$ select $1, 'foo'::text $$ language sql;
 
@@ -539,6 +546,13 @@ create function outparam2_succeed(r anyrange, out lu anyarray, out ul anyarray)
 
 select * from outparam2_succeed(int4range(1,11));
 
+-- infer anyarray from anyrange
+create function outparam_succeed2(i anyrange, out r anyarray, out t text)
+  as $$ select ARRAY[upper($1)], 'foo'::text $$ language sql;
+
+select * from outparam_succeed2(int4range(int4range(1,2)));
+
+-- infer anyelement from anyrange
 create function inoutparam_succeed(out i anyelement, inout r anyrange)
   as $$ select upper($1), $1 $$ language sql;
 
diff --git a/src/test/regress/sql/type_sanity.sql b/src/test/regress/sql/type_sanity.sql
index 4b492ce0625..07879d9a574 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'));
@@ -153,7 +153,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')
@@ -182,7 +182,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')
@@ -234,7 +234,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')
@@ -263,7 +263,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')
@@ -467,3 +467,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.rngmultitypid
+FROM pg_range p1
+WHERE p1.rngmultitypid IS NULL OR p1.rngmultitypid = 0;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index b8ca8cffd91..46b0d39f586 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -626,6 +626,7 @@ ExecutorStart_hook_type
 ExpandedArrayHeader
 ExpandedObjectHeader
 ExpandedObjectMethods
+ExpandedMultirangeHeader
 ExpandedRecordFieldInfo
 ExpandedRecordHeader
 ExplainDirectModify_function
@@ -1395,6 +1396,9 @@ MultiXactMember
 MultiXactOffset
 MultiXactStateData
 MultiXactStatus
+MultirangeIOData
+MultirangeParseState
+MultirangeType
 NDBOX
 NODE
 NUMCacheEntry
@@ -2276,6 +2280,7 @@ ShellTypeInfo
 ShippableCacheEntry
 ShippableCacheKey
 ShmemIndexEnt
+ShortRangeType
 ShutdownForeignScan_function
 ShutdownInformation
 ShutdownMode
#140Alexander Korotkov
aekorotkov@gmail.com
In reply to: Alexander Korotkov (#139)
1 attachment(s)
Re: range_agg

On Tue, Dec 8, 2020 at 3:20 AM Alexander Korotkov <aekorotkov@gmail.com> wrote:

I'd like to publish my revision of the patch. So Paul could start
from it. The changes I made are minor
1. Add missing types to typedefs.list
2. Run pg_indent run over the changed files and some other formatting changes
3. Reorder the regression tests to evade the error spotted by
commitfest.cputube.org

I'm switching this patch to WOA.

I decided to work on this patch myself. The next revision is attached.

The changes are as follows.

1. CREATE TYPE ... AS RANGE command now accepts new argument
multirange_type_name. If multirange_type_name isn't specified, then
multirange type name is selected automatically. pg_dump always
specifies multirange_type_name (if dumping at least pg14). Thanks to
that dumps are always restorable.
2. Multiranges now have a new binary format. After the MultirangeType
struct, an array of offsets comes, then an array of flags and finally
bounds themselves. Offsets points to the bounds of particular range
within multirange. Thanks to that particular range could be accessed
by number without deserialization of the whole multirange. Offsets
are stored in compression-friendly format similar to jsonb (actually
only every 4th of those "offsets" is really offsets, others are
lengths).
3. Most of simple functions working with multirages now don't
deserialize the whole multirange. Instead they fetch bounds of
particular ranges, and that doesn't even require any additional memory
allocation.
4. I've removed ExpandedObject support from the patch. I don't see
much point in it assuming all the functions are returning serialized
multirage anyway. We can add ExpandedObject support in future if
needed.
5. multirange_contains_element(), multirange_contains_range(),
multirange_overlaps_range() now use binary search. Thanks to binary
format, which doesn't require full deserialization, these functions
now work with O(log N) complexity.

Comments and documentation still need revision according to these
changes. I'm going to continue with this.

------
Regards,
Alexander Korotkov

Attachments:

v26-multirange.patchapplication/octet-stream; name=v26-multirange.patchDownload
commit 632e7b85e686559934dfc7e703a77ee218069600
Author: Alexander Korotkov <akorotkov@postgresql.org>
Date:   Sun Nov 29 22:32:30 2020 +0300

    Multiranges v26

diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 62711ee83ff..34918485972 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -6237,6 +6237,16 @@ SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</replaceable>:<replaceable>&l
       </para></entry>
      </row>
 
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>rngmultitypid</structfield> <type>oid</type>
+       (references <link linkend="catalog-pg-type"><structname>pg_type</structname></link>.<structfield>oid</structfield>)
+      </para>
+      <para>
+       OID of the multirange type for this range type
+      </para></entry>
+     </row>
+
      <row>
       <entry role="catalog_table_entry"><para role="column_definition">
        <structfield>rngcollation</structfield> <type>oid</type>
@@ -8671,8 +8681,9 @@ SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</replaceable>:<replaceable>&l
        <literal>c</literal> for a composite type (e.g., a table's row type),
        <literal>d</literal> for a domain,
        <literal>e</literal> for an enum type,
-       <literal>p</literal> for a pseudo-type, or
-       <literal>r</literal> for a range type.
+       <literal>p</literal> for a pseudo-type,
+       <literal>r</literal> for a range type, or
+       <literal>m</literal> for a multirange type.
        See also <structfield>typrelid</structfield> and
        <structfield>typbasetype</structfield>.
       </para></entry>
diff --git a/doc/src/sgml/datatype.sgml b/doc/src/sgml/datatype.sgml
index 9eb19a1c616..58d168c763e 100644
--- a/doc/src/sgml/datatype.sgml
+++ b/doc/src/sgml/datatype.sgml
@@ -4907,6 +4907,10 @@ SELECT * FROM pg_attribute
     <primary>anyrange</primary>
    </indexterm>
 
+   <indexterm zone="datatype-pseudo">
+    <primary>anymultirange</primary>
+   </indexterm>
+
    <indexterm zone="datatype-pseudo">
     <primary>anycompatible</primary>
    </indexterm>
@@ -4923,6 +4927,10 @@ SELECT * FROM pg_attribute
     <primary>anycompatiblerange</primary>
    </indexterm>
 
+   <indexterm zone="datatype-pseudo">
+    <primary>anycompatiblemultirange</primary>
+   </indexterm>
+
    <indexterm zone="datatype-pseudo">
     <primary>void</primary>
    </indexterm>
@@ -5034,6 +5042,13 @@ SELECT * FROM pg_attribute
         <xref linkend="rangetypes"/>).</entry>
        </row>
 
+       <row>
+        <entry><type>anymultirange</type></entry>
+        <entry>Indicates that a function accepts any multirange data type
+        (see <xref linkend="extend-types-polymorphic"/> and
+        <xref linkend="rangetypes"/>).</entry>
+       </row>
+
        <row>
         <entry><type>anycompatible</type></entry>
         <entry>Indicates that a function accepts any data type,
@@ -5063,6 +5078,14 @@ SELECT * FROM pg_attribute
         <xref linkend="rangetypes"/>).</entry>
        </row>
 
+       <row>
+        <entry><type>anycompatiblemultirange</type></entry>
+        <entry>Indicates that a function accepts any multirange data type,
+        with automatic promotion of multiple arguments to a common data type
+        (see <xref linkend="extend-types-polymorphic"/> and
+        <xref linkend="rangetypes"/>).</entry>
+       </row>
+
        <row>
         <entry><type>cstring</type></entry>
         <entry>Indicates that a function accepts or returns a null-terminated C string.</entry>
diff --git a/doc/src/sgml/extend.sgml b/doc/src/sgml/extend.sgml
index 1c37026bb05..6e3d82b85b8 100644
--- a/doc/src/sgml/extend.sgml
+++ b/doc/src/sgml/extend.sgml
@@ -288,6 +288,14 @@
         </entry>
        </row>
 
+       <row>
+        <entry><type>anymultirange</type></entry>
+        <entry>Simple</entry>
+        <entry>Indicates that a function accepts any multirange data type
+        (see <xref linkend="rangetypes"/>)
+        </entry>
+       </row>
+
        <row>
         <entry><type>anycompatible</type></entry>
         <entry>Common</entry>
@@ -319,6 +327,14 @@
         with automatic promotion of multiple arguments to a common data type
         </entry>
        </row>
+
+       <row>
+        <entry><type>anycompatiblemultirange</type></entry>
+        <entry>Common</entry>
+        <entry>Indicates that a function accepts any multirange data type,
+        with automatic promotion of multiple arguments to a common data type
+        </entry>
+       </row>
       </tbody>
      </tgroup>
     </table>
@@ -346,17 +362,15 @@
      position declared as <type>anyarray</type> can have any array data type,
      but similarly they must all be the same type.  And similarly,
      positions declared as <type>anyrange</type> must all be the same range
-     type.  Furthermore, if there are
+     type.  Likewise for <type>anymultirange</type>.
+    </para>
+
+    <para>
+     Furthermore, if there are
      positions declared <type>anyarray</type> and others declared
      <type>anyelement</type>, the actual array type in the
      <type>anyarray</type> positions must be an array whose elements are
      the same type appearing in the <type>anyelement</type> positions.
-     Similarly, if there are positions declared <type>anyrange</type>
-     and others declared <type>anyelement</type> or <type>anyarray</type>,
-     the actual range type in the <type>anyrange</type> positions must be a
-     range whose subtype is the same type appearing in
-     the <type>anyelement</type> positions and the same as the element type
-     of the <type>anyarray</type> positions.
      <type>anynonarray</type> is treated exactly the same as <type>anyelement</type>,
      but adds the additional constraint that the actual type must not be
      an array type.
@@ -365,6 +379,19 @@
      be an enum type.
     </para>
 
+    <para>
+     Similarly, if there are positions declared <type>anyrange</type>
+     and others declared <type>anyelement</type> or <type>anyarray</type>,
+     the actual range type in the <type>anyrange</type> positions must be a
+     range whose subtype is the same type appearing in
+     the <type>anyelement</type> positions and the same as the element type
+     of the <type>anyarray</type> positions.
+     If there are positions declared <type>anymultirange</type>,
+     their actual multirange type must contain ranges matching parameters declared
+     <type>anyrange</type> and base elements matching parameters declared
+     <type>anyelement</type> and <type>anyarray</type>.
+    </para>
+
     <para>
      Thus, when more than one argument position is declared with a polymorphic
      type, the net effect is that only certain combinations of actual argument
@@ -420,7 +447,8 @@
      Selection of the common type considers the actual types
      of <type>anycompatible</type> and <type>anycompatiblenonarray</type>
      inputs, the array element types of <type>anycompatiblearray</type>
-     inputs, and the range subtypes of <type>anycompatiblerange</type>
+     inputs, the range subtypes of <type>anycompatiblerange</type> inputs,
+     and the multirange subtypes of <type>anycompatiablemultirange</type>
      inputs.  If <type>anycompatiblenonarray</type> is present then the
      common type is required to be a non-array type.  Once a common type is
      identified, arguments in <type>anycompatible</type>
@@ -431,12 +459,15 @@
 
     <para>
      Since there is no way to select a range type knowing only its subtype,
-     use of <type>anycompatiblerange</type> requires that all arguments
-     declared with that type have the same actual range type, and that that
-     type's subtype agree with the selected common type, so that no casting
-     of the range values is required.  As with <type>anyrange</type>, use
-     of <type>anycompatiblerange</type> as a function result type requires
-     that there be an <type>anycompatiblerange</type> argument.
+     use of <type>anycompatiblerange</type> and/or
+     <type>anycompatiblemultirange</type> requires that all arguments declared
+     with that type have the same actual range and/or multirange type, and that
+     that type's subtype agree with the selected common type, so that no casting
+     of the range values is required.  As with <type>anyrange</type> and
+     <type>anymultirange</type>, use of <type>anycompatiblerange</type> and
+     <type>anymultirange</type> as a function result type requires that there be
+     an <type>anycompatiblerange</type> or <type>anycompatiblemultirange</type>
+     argument.
     </para>
 
     <para>
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index df29af6371a..d5cd705eebb 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -17884,12 +17884,15 @@ SELECT NULLIF(value, '(none)') ...
   <para>
    <xref linkend="range-operators-table"/> shows the specialized operators
    available for range types.
+   <xref linkend="multirange-operators-table"/> shows the specialized operators
+   available for multirange types.
    In addition to those, the usual comparison operators shown in
    <xref linkend="functions-comparison-op-table"/> are available for range
-   types.  The comparison operators order first by the range lower bounds, and
-   only if those are equal do they compare the upper bounds.  This does not
-   usually result in a useful overall ordering, but the operators are provided
-   to allow unique indexes to be constructed on ranges.
+   and multirange types.  The comparison operators order first by the range lower
+   bounds, and only if those are equal do they compare the upper bounds.  The
+   multirange operators compare each range until one is unequal. This
+   does not usually result in a useful overall ordering, but the operators are
+   provided to allow unique indexes to be constructed on ranges.
   </para>
 
    <table id="range-operators-table">
@@ -18099,15 +18102,449 @@ SELECT NULLIF(value, '(none)') ...
     </tgroup>
    </table>
 
+   <table id="multirange-operators-table">
+    <title>Multirange Operators</title>
+    <tgroup cols="1">
+     <thead>
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        Operator
+       </para>
+       <para>
+        Description
+       </para>
+       <para>
+        Example(s)
+       </para></entry>
+      </row>
+     </thead>
+
+     <tbody>
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>@&gt;</literal> <type>anymultirange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Does the first multirange contain the second?
+       </para>
+       <para>
+        <literal>'{[2,4)}'::int4multirange @&gt; '{[2,3)}'::int4multirange</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>@&gt;</literal> <type>anyrange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Does the multirange contain the range?
+       </para>
+       <para>
+        <literal>'{[2,4)}'::int4multirange @&gt; int4range(2,3)</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>@&gt;</literal> <type>anyelement</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Does the multirange contain the element?
+       </para>
+       <para>
+        <literal>'{[2011-01-01,2011-03-01)}'::tsmultirange @&gt; '2011-01-10'::timestamp</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>&lt;@</literal> <type>anymultirange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the first multirange contained by the second?
+       </para>
+       <para>
+        <literal>'{[2,4)}'::int4multirange &lt;@ '{[1,7)}'::int4multirange</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>&lt;@</literal> <type>anyrange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the multirange contained by the range?
+       </para>
+       <para>
+        <literal>'{[2,4)}'::int4multirange &lt;@ int4range(1,7)</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anyrange</type> <literal>&lt;@</literal> <type>anymultirange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the range contained by the multirange?
+       </para>
+       <para>
+        <literal>int4range(2,4) &lt;@ '{[1,7)}'::int4multirange</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anyelement</type> <literal>&lt;@</literal> <type>anymultirange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the element contained by the multirange?
+       </para>
+       <para>
+        <literal>42 &lt;@ '{[1,7)}'::int4multirange</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>&amp;&amp;</literal> <type>anymultirange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Do the multiranges overlap, that is, have any elements in common?
+       </para>
+       <para>
+        <literal>'{[3,7)}'::int8multirange &amp;&amp; '{[4,12)}'::int8multirange</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>&amp;&amp;</literal> <type>anyrange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Does the multirange overlap the range?
+       </para>
+       <para>
+        <literal>'{[3,7)}'::int8multirange &amp;&amp; int8range(4,12)</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anyrange</type> <literal>&amp;&amp;</literal> <type>anymultirange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Does the range overlap the multirange?
+       </para>
+       <para>
+        <literal>int8range(3,7) &amp;&amp; '{[4,12)}'::int8multirange</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>&lt;&lt;</literal> <type>anymultirange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the first multirange strictly left of the second?
+       </para>
+       <para>
+        <literal>'{[1,10)}'::int8multirange &lt;&lt; '{[100,110)}'::int8multirange</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>&lt;&lt;</literal> <type>anyrange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the multirange strictly left of the range?
+       </para>
+       <para>
+        <literal>'{[1,10)}'::int8multirange &lt;&lt; int8range(100,110)</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anyrange</type> <literal>&lt;&lt;</literal> <type>anymultirange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the range strictly left of the multirange?
+       </para>
+       <para>
+        <literal>int8range(1,10) &lt;&lt; '{[100,110)}'::int8multirange</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>&gt;&gt;</literal> <type>anymultirange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the first multirange strictly right of the second?
+       </para>
+       <para>
+        <literal>'{[50,60)}'::int8multirange &gt;&gt; '{[20,30)}'::int8multirange</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>&gt;&gt;</literal> <type>anyrange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the multirange strictly right of the range?
+       </para>
+       <para>
+        <literal>'{[50,60)}'::int8multirange &gt;&gt; int8range(20,30)</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anyrange</type> <literal>&gt;&gt;</literal> <type>anymultirange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the range strictly right of the multirange?
+       </para>
+       <para>
+        <literal>int8range(50,60) &gt;&gt; '{[20,30)}'::int8multirange</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>&amp;&lt;</literal> <type>anymultirange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Does the first multirange not extend to the right of the second?
+       </para>
+       <para>
+        <literal>'{[1,20)}'::int8multirange &amp;&lt; '{[18,20)}'::int8multirange</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>&amp;&lt;</literal> <type>anyrange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Does the multirange not extend to the right of the range?
+       </para>
+       <para>
+        <literal>'{[1,20)}'::int8multirange &amp;&lt; int8range(18,20)</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anyrange</type> <literal>&amp;&lt;</literal> <type>anymultirange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Does the range not extend to the right of the multirange?
+       </para>
+       <para>
+        <literal>int8range(1,20) &amp;&lt; '{[18,20)}'::int8multirange</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>&amp;&gt;</literal> <type>anymultirange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Does the first multirange not extend to the left of the second?
+       </para>
+       <para>
+        <literal>'{[7,20)}'::int8multirange &amp;&gt; '{[5,10)}'::int8multirange</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>&amp;&gt;</literal> <type>anyrange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Does the multirange not extend to the left of the range?
+       </para>
+       <para>
+        <literal>'{[7,20)}'::int8multirange &amp;&gt; int8range(5,10)</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anyrange</type> <literal>&amp;&gt;</literal> <type>anymultirange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Does the range not extend to the left of the multirange?
+       </para>
+       <para>
+        <literal>int8range(7,20) &amp;&gt; '{[5,10)}'::int8multirange</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>-|-</literal> <type>anymultirange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Are the multiranges adjacent?
+       </para>
+       <para>
+        <literal>'{[1.1,2.2)}'::nummultirange -|- '{[2.2,3.3)}'::nummultirange</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>-|-</literal> <type>anyrange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the multirange adjacent to the range?
+       </para>
+       <para>
+        <literal>'{[1.1,2.2)}'::nummultirange -|- numrange(2.2,3.3)</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anyrange</type> <literal>-|-</literal> <type>anymultirange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the range adjacent to the multirange?
+       </para>
+       <para>
+        <literal>numrange(1.1,2.2) -|- '{[2.2,3.3)}'::nummultirange</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>+</literal> <type>anymultirange</type>
+        <returnvalue>anymultirange</returnvalue>
+       </para>
+       <para>
+        Computes the union of the multiranges.  The multiranges need not overlap
+        or be adjacent.
+       </para>
+       <para>
+        <literal>'{[5,10)}'::nummultirange + '{[15,20)}'::nummultirange</literal>
+        <returnvalue>{[5,10), [15,20)}</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>*</literal> <type>anymultirange</type>
+        <returnvalue>anymultirange</returnvalue>
+       </para>
+       <para>
+        Computes the intersection of the multiranges.
+       </para>
+       <para>
+        <literal>'{[5,15)}'::int8multirange * '{[10,20)}'::int8multirange</literal>
+        <returnvalue>{[10,15)}</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>-</literal> <type>anymultirange</type>
+        <returnvalue>anymultirange</returnvalue>
+       </para>
+       <para>
+        Computes the difference of the multiranges.
+       </para>
+       <para>
+        <literal>'{[5,20)}'::int8multirange - '{[10,15)}'::int8multirange</literal>
+        <returnvalue>{[5,10), [15,20)}</returnvalue>
+       </para></entry>
+      </row>
+     </tbody>
+    </tgroup>
+   </table>
+
   <para>
    The left-of/right-of/adjacent operators always return false when an empty
-   range is involved; that is, an empty range is not considered to be either
-   before or after any other range.
+   range or multirange is involved; that is, an empty range is not considered to
+   be either before or after any other range.
+  </para>
+
+  <para>
+   Elsewhere empty ranges and multiranges are treated as the additive identity:
+   anything unioned with an empty value is itself. Anything minus an empty
+   value is itself. An empty multirange has exactly the same points as an empty
+   range. Every range contains the empty range. Every multirange contains as many
+   empty ranges as you like.
+  </para>
+
+  <para>
+   The range union and difference operators will fail if the resulting range would
+   need to contain two disjoint sub-ranges, as such a range cannot be
+   represented. There are separate operators for union and difference that take
+   multirange parameters and return a multirange, and they do not fail even if
+   their arguments are disjoint. So if you need a union or difference operation
+   for ranges that may be disjoint, you can avoid errors by first casting your
+   ranges to multiranges.
   </para>
 
   <para>
    <xref linkend="range-functions-table"/> shows the functions
    available for use with range types.
+   <xref linkend="multirange-functions-table"/> shows the functions
+   available for use with multirange types.
   </para>
 
    <table id="range-functions-table">
@@ -18269,10 +18706,185 @@ SELECT NULLIF(value, '(none)') ...
     </tgroup>
    </table>
 
+   <table id="multirange-functions-table">
+    <title>Multirange Functions</title>
+    <tgroup cols="1">
+     <thead>
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        Function
+       </para>
+       <para>
+        Description
+       </para>
+       <para>
+        Example(s)
+       </para></entry>
+      </row>
+     </thead>
+     <tbody>
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>lower</primary>
+        </indexterm>
+        <function>lower</function> ( <type>anymultirange</type> )
+        <returnvalue>anyelement</returnvalue>
+       </para>
+       <para>
+        Extracts the lower bound of the multirange (<literal>NULL</literal> if the
+        multirange is empty or the lower bound is infinite).
+       </para>
+       <para>
+        <literal>lower('{[1.1,2.2)}'::nummultirange)</literal>
+        <returnvalue>1.1</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>upper</primary>
+        </indexterm>
+        <function>upper</function> ( <type>anymultirange</type> )
+        <returnvalue>anyelement</returnvalue>
+       </para>
+       <para>
+        Extracts the upper bound of the multirange (<literal>NULL</literal> if the
+        multirange is empty or the upper bound is infinite).
+       </para>
+       <para>
+        <literal>upper('{[1.1,2.2)}'::nummultirange)</literal>
+        <returnvalue>2.2</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>isempty</primary>
+        </indexterm>
+        <function>isempty</function> ( <type>anymultirange</type> )
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the multirange empty?
+       </para>
+       <para>
+        <literal>isempty('{[1.1,2.2)}'::nummultirange)</literal>
+        <returnvalue>f</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>lower_inc</primary>
+        </indexterm>
+        <function>lower_inc</function> ( <type>anymultirange</type> )
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the multirange's lower bound inclusive?
+       </para>
+       <para>
+        <literal>lower_inc('{[1.1,2.2)}'::nummultirange)</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>upper_inc</primary>
+        </indexterm>
+        <function>upper_inc</function> ( <type>anymultirange</type> )
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the multirange's upper bound inclusive?
+       </para>
+       <para>
+        <literal>upper_inc('{[1.1,2.2)}'::nummultirange)</literal>
+        <returnvalue>f</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>lower_inf</primary>
+        </indexterm>
+        <function>lower_inf</function> ( <type>anymultirange</type> )
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the multirange's lower bound infinite?
+       </para>
+       <para>
+        <literal>lower_inf('{(,)}'::datemultirange)</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>upper_inf</primary>
+        </indexterm>
+        <function>upper_inf</function> ( <type>anymultirange</type> )
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the multirange's upper bound infinite?
+       </para>
+       <para>
+        <literal>upper_inf('{(,)}'::datemultirange)</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>range_merge</primary>
+        </indexterm>
+        <function>range_merge</function> ( <type>anymultirange</type> )
+        <returnvalue>anyrange</returnvalue>
+       </para>
+       <para>
+        Computes the smallest range that includes the entire multirange.
+       </para>
+       <para>
+        <literal>range_merge('{[1,2), [3,4)}'::int4multirange)</literal>
+        <returnvalue>[1,4)</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>multirange</primary>
+        </indexterm>
+        <function>multirange</function> ( <type>anyrange</type> )
+        <returnvalue>anymultirange</returnvalue>
+       </para>
+       <para>
+        Returns a multirange containing just the given range.
+       </para>
+       <para>
+        <literal>multirange('[1,2)'::int4range)</literal>
+        <returnvalue>{[1,2)}</returnvalue>
+       </para></entry>
+      </row>
+     </tbody>
+    </tgroup>
+   </table>
+
   <para>
    The <function>lower_inc</function>, <function>upper_inc</function>,
    <function>lower_inf</function>, and <function>upper_inf</function>
-   functions all return false for an empty range.
+   functions all return false for an empty range or multirange.
   </para>
   </sect1>
 
@@ -18604,6 +19216,36 @@ SELECT NULLIF(value, '(none)') ...
        <entry>Yes</entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>range_agg</primary>
+        </indexterm>
+        <function>range_agg</function> ( <parameter>value</parameter>
+         <type>anyrange</type> )
+        <returnvalue>anymultirange</returnvalue>
+       </para>
+       <para>
+        Computes the union of the non-null input values.
+       </para></entry>
+       <entry>No</entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>range_intersect_agg</primary>
+        </indexterm>
+        <function>range_intersect_agg</function> ( <parameter>value</parameter>
+         <type>anyrange</type> )
+        <returnvalue>anymultirange</returnvalue>
+       </para>
+       <para>
+        Computes the intersection of the non-null input values.
+       </para></entry>
+       <entry>No</entry>
+      </row>
+
       <row>
        <entry role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/doc/src/sgml/rangetypes.sgml b/doc/src/sgml/rangetypes.sgml
index b75fb3a3929..c8784bdce38 100644
--- a/doc/src/sgml/rangetypes.sgml
+++ b/doc/src/sgml/rangetypes.sgml
@@ -27,6 +27,13 @@
   ranges from an instrument, and so forth can also be useful.
  </para>
 
+ <para>
+  Every range type has a corresponding multirange type. A multirange is
+  an ordered list of non-continguous, non-empty, non-null ranges. Most
+  range operators also work on multiranges, and they have a few functions
+  of their own.
+ </para>
+
  <sect2 id="rangetypes-builtin">
   <title>Built-in Range Types</title>
 
@@ -232,10 +239,30 @@ SELECT '[4,4]'::int4range;
 SELECT '[4,4)'::int4range;
 </programlisting>
   </para>
+
+  <para>
+   The input for a multirange is curly brackets (<literal>{</literal> and
+   <literal>}</literal>) containing zero or more valid ranges,
+   separated by commas. Whitespace is permitted around the brackets and
+   commas. This is intended to be reminiscent of array syntax, although
+   multiranges are much simpler: they have just one dimension and there is
+   no need to quote their contents. (The bounds of their ranges may be
+   quoted as above however.)
+  </para>
+
+  <para>
+  Examples:
+<programlisting>
+SELECT '{}'::int4multirange;
+SELECT '{[3,7)}'::int4multirange;
+SELECT '{[3,7), [8,9)}'::int4multirange;
+</programlisting>
+  </para>
+
  </sect2>
 
  <sect2 id="rangetypes-construct">
-  <title>Constructing Ranges</title>
+  <title>Constructing Ranges and Multiranges</title>
 
   <para>
    Each range type has a constructor function with the same name as the range
@@ -267,6 +294,19 @@ SELECT int8range(1, 14, '(]');
 
 -- Using NULL for either bound causes the range to be unbounded on that side.
 SELECT numrange(NULL, 2.2);
+</programlisting>
+  </para>
+
+  <para>
+   Each range type also has a multirange constructor with the same name as the
+   multirange type.  The constructor function takes zero or more arguments
+   which are all ranges of the appropriate type.
+   For example:
+
+<programlisting>
+SELECT nummultirange();
+SELECT nummultirange(numrange(1.0, 14.0));
+SELECT nummultirange(numrange(1.0, 14.0), numrange(20.0, 25.0));
 </programlisting>
   </para>
  </sect2>
@@ -341,6 +381,11 @@ SELECT '[1.234, 5.678]'::floatrange;
    function in this example.
   </para>
 
+  <para>
+   When you define your own range you automatically get a corresponding
+   multirange type.
+  </para>
+
   <para>
    Defining your own range type also allows you to specify a different
    subtype B-tree operator class or collation to use, so as to change the sort
diff --git a/src/backend/catalog/pg_range.c b/src/backend/catalog/pg_range.c
index a606d8c3adb..91b0fb0611a 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;
 	ObjectAddresses *addrs;
 
 	pg_range = table_open(RangeRelationId, RowExclusiveLock);
@@ -55,6 +56,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_rngmultitypid - 1] = ObjectIdGetDatum(multirangeTypeOid);
 
 	tup = heap_form_tuple(RelationGetDescr(pg_range), values, nulls);
 
@@ -93,6 +95,12 @@ RangeCreate(Oid rangeTypeOid, Oid rangeSubType, Oid rangeCollation,
 	record_object_address_dependencies(&myself, addrs, DEPENDENCY_NORMAL);
 	free_object_addresses(addrs);
 
+	/* 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 4252875ef50..23ef9cb1047 100644
--- a/src/backend/catalog/pg_type.c
+++ b/src/backend/catalog/pg_type.c
@@ -28,6 +28,7 @@
 #include "catalog/pg_proc.h"
 #include "catalog/pg_type.h"
 #include "commands/typecmds.h"
+#include "mb/pg_wchar.h"
 #include "miscadmin.h"
 #include "parser/scansup.h"
 #include "utils/acl.h"
@@ -37,6 +38,9 @@
 #include "utils/rel.h"
 #include "utils/syscache.h"
 
+static char *makeUniqueTypeName(const char *typeName, Oid typeNamespace,
+								bool tryOriginal);
+
 /* Potentially set by pg_upgrade_support functions */
 Oid			binary_upgrade_next_pg_type_oid = InvalidOid;
 
@@ -863,31 +867,10 @@ RenameTypeInternal(Oid typeOid, const char *newTypeName, Oid typeNamespace)
 char *
 makeArrayTypeName(const char *typeName, Oid typeNamespace)
 {
-	char	   *arr = (char *) palloc(NAMEDATALEN);
-	int			namelen = strlen(typeName);
-	int			i;
-
-	/*
-	 * 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;
-	}
+	char	   *arr;
 
-	if (i >= NAMEDATALEN - 1)
+	arr = makeUniqueTypeName(typeName, typeNamespace, false);
+	if (arr == NULL)
 		ereport(ERROR,
 				(errcode(ERRCODE_DUPLICATE_OBJECT),
 				 errmsg("could not form array type name for type \"%s\"",
@@ -958,3 +941,91 @@ moveArrayTypeName(Oid typeOid, const char *typeName, Oid typeNamespace)
 
 	return true;
 }
+
+
+/*
+ * makeMultirangeTypeName
+ *	  - given a range type name, make a multirange type name for it
+ *
+ * caller is responsible for pfreeing the result
+ */
+char *
+makeMultirangeTypeName(const char *rangeTypeName, Oid typeNamespace)
+{
+	char	   *buf;
+	char	   *mrname;
+	char	   *rangestr;
+
+	/*
+	 * 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...
+	 */
+	rangestr = strstr(rangeTypeName, "range");
+	if (rangestr)
+	{
+		char	   *prefix = pnstrdup(rangeTypeName, rangestr - rangeTypeName);
+
+		buf = psprintf("%s%s%s", prefix, "multi", rangestr);
+	}
+	else
+		buf = psprintf("%s_multirange", pnstrdup(rangeTypeName, NAMEDATALEN - 12));
+
+	/* clip it at NAMEDATALEN-1 bytes */
+	buf[pg_mbcliplen(buf, strlen(buf), NAMEDATALEN - 1)] = '\0';
+
+	mrname = makeUniqueTypeName(buf, typeNamespace, true);
+	if (mrname == NULL)
+		ereport(ERROR,
+				(errcode(ERRCODE_DUPLICATE_OBJECT),
+				 errmsg("could not form multirange type name for type \"%s\"",
+						rangeTypeName)));
+
+	return mrname;
+}
+
+/*
+ * makeUniqueTypeName
+ *		Generate a unique name for a prospective new type
+ *
+ * Given a typeName, return a new palloc'ed name by preprending underscores
+ * until a non-conflicting name results.
+ *
+ * If tryOriginal, first try with zero underscores.
+ */
+static char *
+makeUniqueTypeName(const char *typeName, Oid typeNamespace, bool tryOriginal)
+{
+	int			i;
+	int			namelen;
+	char		dest[NAMEDATALEN];
+
+	Assert(strlen(typeName) <= NAMEDATALEN - 1);
+
+	if (tryOriginal &&
+		!SearchSysCacheExists2(TYPENAMENSP,
+							   CStringGetDatum(typeName),
+							   ObjectIdGetDatum(typeNamespace)))
+		return pstrdup(typeName);
+
+	/*
+	 * The idea is to prepend underscores as needed until we make a name that
+	 * doesn't collide with anything ...
+	 */
+	namelen = strlen(typeName);
+	for (i = 1; i < NAMEDATALEN - 1; i++)
+	{
+		dest[i - 1] = '_';
+		strlcpy(dest + i, typeName, NAMEDATALEN - i);
+		if (namelen + i >= NAMEDATALEN)
+			truncate_identifier(dest, NAMEDATALEN, false);
+
+		if (!SearchSysCacheExists2(TYPENAMENSP,
+								   CStringGetDatum(dest),
+								   ObjectIdGetDatum(typeNamespace)))
+			return pstrdup(dest);
+	}
+
+	return NULL;
+}
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index 7c0b2c3bf02..0fcd8c8f16e 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -107,9 +107,14 @@ typedef struct
 
 /* Potentially set by pg_upgrade_support functions */
 Oid			binary_upgrade_next_array_pg_type_oid = InvalidOid;
+Oid			binary_upgrade_next_mrng_pg_type_oid = InvalidOid;
+Oid			binary_upgrade_next_mrng_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 rangeOid,
+									   Oid rangeArrayOid, Oid *castFuncOid);
 static Oid	findTypeInputFunction(List *procname, Oid typeOid);
 static Oid	findTypeOutputFunction(List *procname, Oid typeOid);
 static Oid	findTypeReceiveFunction(List *procname, Oid typeOid);
@@ -772,7 +777,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",
@@ -1323,6 +1329,11 @@ checkEnumOwner(HeapTuple tup)
 /*
  * DefineRange
  *		Registers a new range type.
+ *
+ * Perhaps it might be worthwhile to set pg_type.typelem to the base type,
+ * and likewise on multiranges to set it to the range type. But having a
+ * non-zero typelem is treated elsewhere as a synonym for being an array,
+ * and users might have queries with that same assumption.
  */
 ObjectAddress
 DefineRange(CreateRangeStmt *stmt)
@@ -1331,7 +1342,12 @@ DefineRange(CreateRangeStmt *stmt)
 	Oid			typeNamespace;
 	Oid			typoid;
 	char	   *rangeArrayName;
+	char	   *multirangeTypeName = NULL;
+	char	   *multirangeArrayName;
+	Oid			multirangeNamespace = InvalidOid;
 	Oid			rangeArrayOid;
+	Oid			multirangeOid;
+	Oid			multirangeArrayOid;
 	Oid			rangeSubtype = InvalidOid;
 	List	   *rangeSubOpclassName = NIL;
 	List	   *rangeCollationName = NIL;
@@ -1348,6 +1364,8 @@ DefineRange(CreateRangeStmt *stmt)
 	AclResult	aclresult;
 	ListCell   *lc;
 	ObjectAddress address;
+	ObjectAddress mltrngaddress;
+	Oid			castFuncOid;
 
 	/* Convert list of names to a name and namespace */
 	typeNamespace = QualifiedNameGetCreationNamespace(stmt->typeName,
@@ -1431,6 +1449,16 @@ DefineRange(CreateRangeStmt *stmt)
 						 errmsg("conflicting or redundant options")));
 			rangeSubtypeDiffName = defGetQualifiedName(defel);
 		}
+		else if (strcmp(defel->defname, "multirange_type_name") == 0)
+		{
+			if (multirangeTypeName != NULL)
+				ereport(ERROR,
+						(errcode(ERRCODE_SYNTAX_ERROR),
+						 errmsg("conflicting or redundant options")));
+			/* we can look up the subtype name immediately */
+			multirangeNamespace = QualifiedNameGetCreationNamespace(defGetQualifiedName(defel),
+																	&multirangeTypeName);
+		}
 		else
 			ereport(ERROR,
 					(errcode(ERRCODE_SYNTAX_ERROR),
@@ -1496,8 +1524,10 @@ DefineRange(CreateRangeStmt *stmt)
 	/* alignment must be TYPALIGN_INT or TYPALIGN_DOUBLE for ranges */
 	alignment = (subtypalign == TYPALIGN_DOUBLE) ? TYPALIGN_DOUBLE : TYPALIGN_INT;
 
-	/* Allocate OID for array type */
+	/* Allocate OID for array type, its multirange, and its multirange array */
 	rangeArrayOid = AssignTypeArrayOid();
+	multirangeOid = AssignTypeMultirangeOid();
+	multirangeArrayOid = AssignTypeMultirangeArrayOid();
 
 	/* Create the pg_type entry */
 	address =
@@ -1536,9 +1566,75 @@ DefineRange(CreateRangeStmt *stmt)
 	Assert(typoid == InvalidOid || typoid == address.objectId);
 	typoid = address.objectId;
 
+	/* Create the multirange that goes with it */
+	if (multirangeTypeName)
+	{
+		Oid		old_typoid;
+
+		/*
+		 * Look to see if multirange type already exists.
+		 */
+		old_typoid = GetSysCacheOid2(TYPENAMENSP, Anum_pg_type_oid,
+									 CStringGetDatum(multirangeTypeName),
+									 ObjectIdGetDatum(multirangeNamespace));
+
+		/*
+		 * If it's not a shell, see if it's an autogenerated array type, and if so
+		 * rename it out of the way.
+		 */
+		if (OidIsValid(old_typoid) && get_typisdefined(old_typoid))
+		{
+			if (!moveArrayTypeName(old_typoid, multirangeTypeName, multirangeNamespace))
+				ereport(ERROR,
+						(errcode(ERRCODE_DUPLICATE_OBJECT),
+						 errmsg("type \"%s\" already exists", multirangeTypeName)));
+		}
+	}
+	else
+	{
+		/* Generate multirange name automatically */
+		multirangeNamespace = typeNamespace;
+		multirangeTypeName = makeMultirangeTypeName(typeName, multirangeNamespace);
+	}
+
+	mltrngaddress =
+		TypeCreate(multirangeOid,	/* force assignment of this type OID */
+				   multirangeTypeName,	/* type name */
+				   multirangeNamespace,	/* 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_RANGE,	/* type-category (range 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,	/* subscript procedure - none */
+				   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) */
+	Assert(multirangeOid == mltrngaddress.objectId);
+
 	/* Create the entry in pg_range */
 	RangeCreate(typoid, rangeSubtype, rangeCollation, rangeSubOpclass,
-				rangeCanonical, rangeSubtypeDiff);
+				rangeCanonical, rangeSubtypeDiff, multirangeOid);
 
 	/*
 	 * Create the array type that goes with it.
@@ -1580,8 +1676,54 @@ 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 */
+			   multirangeNamespace,	/* 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 */
+			   F_ARRAY_SUBSCRIPT_HANDLER,	/* array subscript procedure */
+			   multirangeOid,	/* 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,
+							   multirangeOid, typoid, rangeArrayOid,
+							   &castFuncOid);
+
+	/* Create cast from the range type to its multirange type */
+	CastCreate(typoid, multirangeOid, castFuncOid, 'e', 'f', DEPENDENCY_INTERNAL);
+
+	pfree(multirangeTypeName);
+	pfree(multirangeArrayName);
 
 	return address;
 }
@@ -1659,6 +1801,149 @@ 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.
+ *
+ * Sets castFuncOid to the oid of the new constructor that can be used
+ * to cast from a range to a multirange.
+ */
+static void
+makeMultirangeConstructors(const char *name, Oid namespace,
+						   Oid multirangeOid, Oid rangeOid, Oid rangeArrayOid,
+						   Oid *castFuncOid)
+{
+	ObjectAddress myself,
+				referenced;
+	oidvector  *argtypes;
+	Datum		allParamTypes;
+	ArrayType  *allParameterTypes;
+	Datum		paramModes;
+	ArrayType  *parameterModes;
+
+	referenced.classId = TypeRelationId;
+	referenced.objectId = multirangeOid;
+	referenced.objectSubId = 0;
+
+	/* 0-arg constructor - for empty multiranges */
+	argtypes = buildoidvector(NULL, 0);
+	myself = ProcedureCreate(name,	/* name: same as multirange type */
+							 namespace,
+							 false, /* replace */
+							 false, /* returns set */
+							 multirangeOid, /* return type */
+							 BOOTSTRAP_SUPERUSERID, /* proowner */
+							 INTERNALlanguageId,	/* language */
+							 F_FMGR_INTERNAL_VALIDATOR,
+							 "multirange_constructor0", /* prosrc */
+							 NULL,	/* probin */
+							 PROKIND_FUNCTION,
+							 false, /* security_definer */
+							 false, /* leakproof */
+							 false, /* isStrict */
+							 PROVOLATILE_IMMUTABLE, /* volatility */
+							 PROPARALLEL_SAFE,	/* parallel safety */
+							 argtypes,	/* parameterTypes */
+							 PointerGetDatum(NULL), /* allParameterTypes */
+							 PointerGetDatum(NULL), /* parameterModes */
+							 PointerGetDatum(NULL), /* parameterNames */
+							 NIL,	/* parameterDefaults */
+							 PointerGetDatum(NULL), /* trftypes */
+							 PointerGetDatum(NULL), /* proconfig */
+							 InvalidOid,	/* prosupport */
+							 1.0,	/* procost */
+							 0.0);	/* prorows */
+
+	/*
+	 * Make the constructor 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);
+	pfree(argtypes);
+
+	/*
+	 * 1-arg constructor - for casts
+	 *
+	 * In theory we shouldn't need both this and the vararg (n-arg)
+	 * constructor, but having a separate 1-arg function lets us define casts
+	 * against it.
+	 */
+	argtypes = buildoidvector(&rangeOid, 1);
+	myself = ProcedureCreate(name,	/* name: same as multirange type */
+							 namespace,
+							 false, /* replace */
+							 false, /* returns set */
+							 multirangeOid, /* return type */
+							 BOOTSTRAP_SUPERUSERID, /* proowner */
+							 INTERNALlanguageId,	/* language */
+							 F_FMGR_INTERNAL_VALIDATOR,
+							 "multirange_constructor1", /* prosrc */
+							 NULL,	/* probin */
+							 PROKIND_FUNCTION,
+							 false, /* security_definer */
+							 false, /* leakproof */
+							 true,	/* isStrict */
+							 PROVOLATILE_IMMUTABLE, /* volatility */
+							 PROPARALLEL_SAFE,	/* parallel safety */
+							 argtypes,	/* parameterTypes */
+							 PointerGetDatum(NULL), /* allParameterTypes */
+							 PointerGetDatum(NULL), /* parameterModes */
+							 PointerGetDatum(NULL), /* parameterNames */
+							 NIL,	/* parameterDefaults */
+							 PointerGetDatum(NULL), /* trftypes */
+							 PointerGetDatum(NULL), /* proconfig */
+							 InvalidOid,	/* prosupport */
+							 1.0,	/* procost */
+							 0.0);	/* prorows */
+	/* ditto */
+	recordDependencyOn(&myself, &referenced, DEPENDENCY_INTERNAL);
+	pfree(argtypes);
+	*castFuncOid = myself.objectId;
+
+	/* n-arg constructor - vararg */
+	argtypes = buildoidvector(&rangeArrayOid, 1);
+	allParamTypes = ObjectIdGetDatum(rangeArrayOid);
+	allParameterTypes = construct_array(&allParamTypes,
+										1, OIDOID,
+										sizeof(Oid), true, 'i');
+	paramModes = CharGetDatum(FUNC_PARAM_VARIADIC);
+	parameterModes = construct_array(&paramModes, 1, CHAROID,
+									 1, true, 'c');
+	myself = ProcedureCreate(name,	/* name: same as multirange type */
+							 namespace,
+							 false, /* replace */
+							 false, /* returns set */
+							 multirangeOid, /* return type */
+							 BOOTSTRAP_SUPERUSERID, /* proowner */
+							 INTERNALlanguageId,	/* language */
+							 F_FMGR_INTERNAL_VALIDATOR,
+							 "multirange_constructor2", /* prosrc */
+							 NULL,	/* probin */
+							 PROKIND_FUNCTION,
+							 false, /* security_definer */
+							 false, /* leakproof */
+							 false, /* isStrict */
+							 PROVOLATILE_IMMUTABLE, /* volatility */
+							 PROPARALLEL_SAFE,	/* parallel safety */
+							 argtypes,	/* parameterTypes */
+							 PointerGetDatum(allParameterTypes),	/* allParameterTypes */
+							 PointerGetDatum(parameterModes),	/* parameterModes */
+							 PointerGetDatum(NULL), /* parameterNames */
+							 NIL,	/* parameterDefaults */
+							 PointerGetDatum(NULL), /* trftypes */
+							 PointerGetDatum(NULL), /* proconfig */
+							 InvalidOid,	/* prosupport */
+							 1.0,	/* procost */
+							 0.0);	/* prorows */
+	/* ditto */
+	recordDependencyOn(&myself, &referenced, DEPENDENCY_INTERNAL);
+	pfree(argtypes);
+	pfree(allParameterTypes);
+	pfree(parameterModes);
+}
 
 /*
  * Find suitable I/O and other support functions for a type.
@@ -2152,6 +2437,72 @@ AssignTypeArrayOid(void)
 	return type_array_oid;
 }
 
+/*
+ *	AssignTypeMultirangeOid
+ *
+ *	Pre-assign the range type's multirange OID for use in pg_type.oid
+ */
+Oid
+AssignTypeMultirangeOid(void)
+{
+	Oid			type_multirange_oid;
+
+	/* Use binary-upgrade override for pg_type.oid? */
+	if (IsBinaryUpgrade)
+	{
+		if (!OidIsValid(binary_upgrade_next_mrng_pg_type_oid))
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+					 errmsg("pg_type multirange OID value not set when in binary upgrade mode")));
+
+		type_multirange_oid = binary_upgrade_next_mrng_pg_type_oid;
+		binary_upgrade_next_mrng_pg_type_oid = InvalidOid;
+	}
+	else
+	{
+		Relation	pg_type = table_open(TypeRelationId, AccessShareLock);
+
+		type_multirange_oid = GetNewOidWithIndex(pg_type, TypeOidIndexId,
+												 Anum_pg_type_oid);
+		table_close(pg_type, AccessShareLock);
+	}
+
+	return type_multirange_oid;
+}
+
+/*
+ *	AssignTypeMultirangeArrayOid
+ *
+ *	Pre-assign the range type's multirange array OID for use in pg_type.typarray
+ */
+Oid
+AssignTypeMultirangeArrayOid(void)
+{
+	Oid			type_multirange_array_oid;
+
+	/* Use binary-upgrade override for pg_type.oid? */
+	if (IsBinaryUpgrade)
+	{
+		if (!OidIsValid(binary_upgrade_next_mrng_array_pg_type_oid))
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+					 errmsg("pg_type multirange array OID value not set when in binary upgrade mode")));
+
+		type_multirange_array_oid = binary_upgrade_next_mrng_array_pg_type_oid;
+		binary_upgrade_next_mrng_array_pg_type_oid = InvalidOid;
+	}
+	else
+	{
+		Relation	pg_type = table_open(TypeRelationId, AccessShareLock);
+
+		type_multirange_array_oid = GetNewOidWithIndex(pg_type, TypeOidIndexId,
+													   Anum_pg_type_oid);
+		table_close(pg_type, AccessShareLock);
+	}
+
+	return type_multirange_array_oid;
+}
+
 
 /*-------------------------------------------------------------------
  * DefineCompositeType
diff --git a/src/backend/executor/functions.c b/src/backend/executor/functions.c
index 459a33375b1..ca8d637e73a 100644
--- a/src/backend/executor/functions.c
+++ b/src/backend/executor/functions.c
@@ -1711,7 +1711,8 @@ check_sql_fn_retval(List *queryTreeLists,
 	if (fn_typtype == TYPTYPE_BASE ||
 		fn_typtype == TYPTYPE_DOMAIN ||
 		fn_typtype == TYPTYPE_ENUM ||
-		fn_typtype == TYPTYPE_RANGE)
+		fn_typtype == TYPTYPE_RANGE ||
+		fn_typtype == TYPTYPE_MULTIRANGE)
 	{
 		/*
 		 * For scalar-type returns, the target list must have exactly one
diff --git a/src/backend/parser/parse_coerce.c b/src/backend/parser/parse_coerce.c
index da6c3ae4b5f..e33618f9744 100644
--- a/src/backend/parser/parse_coerce.c
+++ b/src/backend/parser/parse_coerce.c
@@ -190,19 +190,21 @@ coerce_type(ParseState *pstate, Node *node,
 	if (targetTypeId == ANYARRAYOID ||
 		targetTypeId == ANYENUMOID ||
 		targetTypeId == ANYRANGEOID ||
+		targetTypeId == ANYMULTIRANGEOID ||
 		targetTypeId == ANYCOMPATIBLEARRAYOID ||
-		targetTypeId == ANYCOMPATIBLERANGEOID)
+		targetTypeId == ANYCOMPATIBLERANGEOID ||
+		targetTypeId == ANYCOMPATIBLEMULTIRANGEOID)
 	{
 		/*
 		 * 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 the pseudotype's input
-		 * function, 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 the
+		 * pseudotype's input function, 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
@@ -1570,8 +1572,8 @@ select_common_typmod(ParseState *pstate, List *exprs, Oid common_type)
  * 1) All arguments declared ANYELEMENT must have the same datatype.
  * 2) All arguments declared ANYARRAY must have the same datatype,
  *	  which must be a varlena array type.
- * 3) All arguments declared ANYRANGE must have the same datatype,
- *	  which must be a range type.
+ * 3) All arguments declared ANYRANGE or ANYMULTIRANGE must be a range or
+ *	  multirange type, all derived from the same base datatype.
  * 4) If there are arguments of more than one of these polymorphic types,
  *	  the array element type and/or range subtype must be the same as each
  *	  other and the same as the ANYELEMENT type.
@@ -1586,8 +1588,8 @@ select_common_typmod(ParseState *pstate, List *exprs, Oid common_type)
  *	  to a common supertype (chosen as per select_common_type's rules).
  *	  ANYCOMPATIBLENONARRAY works like ANYCOMPATIBLE but also requires the
  *	  common supertype to not be an array.  If there are ANYCOMPATIBLEARRAY
- *	  or ANYCOMPATIBLERANGE arguments, their element types or subtypes are
- *	  included while making the choice of common supertype.
+ *	  or ANYCOMPATIBLERANGE or ANYCOMPATIBLEMULTIRANGE arguments, their element
+ *	  types or subtypes are included while making the choice of common supertype.
  * 8) The resolved type of ANYCOMPATIBLEARRAY arguments will be the array
  *	  type over the common supertype (which might not be the same array type
  *	  as any of the original arrays).
@@ -1595,6 +1597,10 @@ select_common_typmod(ParseState *pstate, List *exprs, Oid common_type)
  *	  (after domain flattening), since we have no preference rule that would
  *	  let us choose one over another.  Furthermore, that range's subtype
  *	  must exactly match the common supertype chosen by rule 7.
+ * 10) All ANYCOMPATIBLEMULTIRANGE arguments must be the exact same multirange
+ *	  type (after domain flattening), since we have no preference rule that would
+ *	  let us choose one over another.  Furthermore, that multirange's range's
+ *	  subtype must exactly match the common supertype chosen by rule 7.
  *
  * Domains over arrays match ANYARRAY, and are immediately flattened to their
  * base type.  (Thus, for example, we will consider it a match if one ANYARRAY
@@ -1603,7 +1609,9 @@ select_common_typmod(ParseState *pstate, List *exprs, Oid common_type)
  * for ANYCOMPATIBLEARRAY and ANYCOMPATIBLENONARRAY.
  *
  * Similarly, domains over ranges match ANYRANGE or ANYCOMPATIBLERANGE,
- * and are immediately flattened to their base type.
+ * and are immediately flattened to their base type, and domains over
+ * multiranges match ANYMULTIRANGE or ANYCOMPATIBLEMULTIRANGE and are immediately
+ * flattened to their base type.
  *
  * Note that domains aren't currently considered to match ANYENUM,
  * even if their base type would match.
@@ -1621,8 +1629,12 @@ check_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			anycompatible_range_typeid = InvalidOid;
 	Oid			anycompatible_range_typelem = InvalidOid;
+	Oid			anycompatible_multirange_typeid = InvalidOid;
+	Oid			anycompatible_multirange_typelem = InvalidOid;
+	Oid			range_typelem = InvalidOid;
 	bool		have_anynonarray = false;
 	bool		have_anyenum = false;
 	bool		have_anycompatible_nonarray = false;
@@ -1671,6 +1683,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;
+		}
 		else if (decl_type == ANYCOMPATIBLEOID ||
 				 decl_type == ANYCOMPATIBLENONARRAYOID)
 		{
@@ -1715,6 +1736,45 @@ check_generic_type_consistency(const Oid *actual_arg_types,
 				anycompatible_actual_types[n_anycompatible_args++] = anycompatible_range_typelem;
 			}
 		}
+		else if (decl_type == ANYCOMPATIBLEMULTIRANGEOID)
+		{
+			if (actual_type == UNKNOWNOID)
+				continue;
+			actual_type = getBaseType(actual_type); /* flatten domains */
+			if (OidIsValid(anycompatible_multirange_typeid))
+			{
+				/* All ANYCOMPATIBLEMULTIRANGE arguments must be the same type */
+				if (anycompatible_multirange_typeid != actual_type)
+					return false;
+			}
+			else
+			{
+				anycompatible_multirange_typeid = actual_type;
+				anycompatible_multirange_typelem = get_multirange_range(actual_type);
+				if (!OidIsValid(anycompatible_multirange_typelem))
+					return false;	/* not a multirange type */
+
+				if (OidIsValid(anycompatible_range_typeid))
+				{
+					/*
+					 * ANYCOMPATIBLEMULTIRANGE and ANYCOMPATIBLERANGE
+					 * arguments must match
+					 */
+					if (anycompatible_range_typeid != anycompatible_multirange_typelem)
+						return false;
+				}
+				else
+				{
+					anycompatible_range_typeid = anycompatible_multirange_typelem;
+					anycompatible_range_typelem = get_range_subtype(anycompatible_range_typeid);
+					if (!OidIsValid(anycompatible_range_typelem))
+						return false;	/* not a range type */
+				}
+				/* collect the subtype for common-supertype choice */
+				anycompatible_actual_types[n_anycompatible_args++] =
+					anycompatible_range_typelem;
+			}
+		}
 	}
 
 	/* Get the element type based on the array type, if we have one */
@@ -1761,8 +1821,6 @@ check_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))
 	{
-		Oid			range_typelem;
-
 		range_typelem = get_range_subtype(range_typeid);
 		if (!OidIsValid(range_typelem))
 			return false;		/* should be a range, but isn't */
@@ -1781,6 +1839,45 @@ 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))
+	{
+		Oid			multirange_typelem;
+
+		multirange_typelem = get_multirange_range(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 */
+		}
+		else if (multirange_typelem != range_typeid)
+		{
+			/* otherwise, they better match */
+			return false;
+		}
+
+		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 */
@@ -1819,8 +1916,10 @@ check_generic_type_consistency(const Oid *actual_arg_types,
 		}
 
 		/*
-		 * the anycompatible type must exactly match the range element type,
-		 * if we were able to identify one
+		 * The anycompatible type must exactly match the range element type,
+		 * if we were able to identify one. This checks compatibility for
+		 * anycompatiblemultirange too since that also sets
+		 * anycompatible_range_typelem above.
 		 */
 		if (OidIsValid(anycompatible_range_typelem) &&
 			anycompatible_range_typelem != anycompatible_typeid)
@@ -1859,21 +1958,27 @@ check_generic_type_consistency(const Oid *actual_arg_types,
  *	  argument's actual type as the function's return type.
  * 2) If return type is ANYARRAY, and any argument is ANYARRAY, use the
  *	  argument's actual type as the function's return type.
- * 3) Similarly, if return type is ANYRANGE, and any argument is ANYRANGE,
- *	  use the argument's actual type as the function's return type.
- * 4) Otherwise, if return type is ANYELEMENT or ANYARRAY, and there is
+ * 3) Similarly, if return type is ANYRANGE or ANYMULTIRANGE, and any
+ *	  argument is ANYRANGE or ANYMULTIRANGE, use that argument's
+ *	  actual type, range type or multirange type as the function's return
+ *	  type.
+ * 4) Otherwise, if return type is ANYMULTIRANGE, and any argument is
+ *	  ANYMULTIRANGE, use the argument's actual type as the function's return
+ *	  type. Or if any argument is ANYRANGE, use its multirange type as the
+ *	  function's return type.
+ * 5) Otherwise, if return type is ANYELEMENT or ANYARRAY, and there is
  *	  at least one ANYELEMENT, ANYARRAY, or ANYRANGE input, deduce the
  *	  return type from those inputs, or throw error if we can't.
- * 5) Otherwise, if return type is ANYRANGE, throw error.  (We have no way to
- *	  select a specific range type if the arguments don't include ANYRANGE.)
- * 6) ANYENUM is treated the same as ANYELEMENT except that if it is used
+ * 6) Otherwise, if return type is ANYRANGE or ANYMULTIRANGE, throw error.
+ *	  (We have no way to select a specific range type if the arguments don't
+ *	  include ANYRANGE.)
  *	  (alone or in combination with plain ANYELEMENT), we add the extra
  *	  condition that the ANYELEMENT type must be an enum.
- * 7) ANYNONARRAY is treated the same as ANYELEMENT except that if it is used,
+ * 8) ANYNONARRAY is treated the same as ANYELEMENT except that if it is used,
  *	  we add the extra condition that the ANYELEMENT type must not be an array.
  *	  (This is a no-op if used in combination with ANYARRAY or ANYENUM, but
  *	  is an extra restriction if not.)
- * 8) ANYCOMPATIBLE, ANYCOMPATIBLEARRAY, ANYCOMPATIBLENONARRAY, and
+ * 9) ANYCOMPATIBLE, ANYCOMPATIBLEARRAY, ANYCOMPATIBLENONARRAY, and
  *	  ANYCOMPATIBLERANGE are handled by resolving the common supertype
  *	  of those arguments (or their element types/subtypes, for array and range
  *	  inputs), and then coercing all those arguments to the common supertype,
@@ -1927,10 +2032,15 @@ 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			anycompatible_typeid = InvalidOid;
 	Oid			anycompatible_array_typeid = InvalidOid;
 	Oid			anycompatible_range_typeid = InvalidOid;
 	Oid			anycompatible_range_typelem = InvalidOid;
+	Oid			anycompatible_multirange_typeid = InvalidOid;
+	Oid			anycompatible_multirange_typelem = InvalidOid;
+	Oid			range_typelem;
+	Oid			multirange_typelem;
 	bool		have_anynonarray = (rettype == ANYNONARRAYOID);
 	bool		have_anyenum = (rettype == ANYENUMOID);
 	bool		have_anycompatible_nonarray = (rettype == ANYCOMPATIBLENONARRAYOID);
@@ -2015,6 +2125,26 @@ enforce_generic_type_consistency(const Oid *actual_arg_types,
 								   format_type_be(actual_type))));
 			range_typeid = actual_type;
 		}
+		else if (decl_type == ANYMULTIRANGEOID)
+		{
+			n_poly_args++;
+			if (actual_type == UNKNOWNOID)
+			{
+				have_poly_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;
+		}
 		else if (decl_type == ANYCOMPATIBLEOID ||
 				 decl_type == ANYCOMPATIBLENONARRAYOID)
 		{
@@ -2083,6 +2213,40 @@ enforce_generic_type_consistency(const Oid *actual_arg_types,
 				anycompatible_actual_types[n_anycompatible_args++] = anycompatible_range_typelem;
 			}
 		}
+		else if (decl_type == ANYCOMPATIBLEMULTIRANGEOID)
+		{
+			have_poly_anycompatible = true;
+			if (actual_type == UNKNOWNOID)
+				continue;
+			if (allow_poly && decl_type == actual_type)
+				continue;		/* no new information here */
+			actual_type = getBaseType(actual_type); /* flatten domains */
+			if (OidIsValid(anycompatible_multirange_typeid))
+			{
+				/* All ANYCOMPATIBLEMULTIRANGE arguments must be the same type */
+				if (anycompatible_multirange_typeid != actual_type)
+					ereport(ERROR,
+							(errcode(ERRCODE_DATATYPE_MISMATCH),
+							 errmsg("arguments declared \"anycompatiblemultirange\" are not all alike"),
+							 errdetail("%s versus %s",
+									   format_type_be(anycompatible_multirange_typeid),
+									   format_type_be(actual_type))));
+			}
+			else
+			{
+				anycompatible_multirange_typeid = actual_type;
+				anycompatible_multirange_typelem = get_multirange_range(actual_type);
+				anycompatible_range_typelem = get_range_subtype(anycompatible_multirange_typelem);
+				if (!OidIsValid(anycompatible_multirange_typelem))
+					ereport(ERROR,
+							(errcode(ERRCODE_DATATYPE_MISMATCH),
+							 errmsg("argument declared %s is not a multirange type but type %s",
+									"anycompatiblemultirange",
+									format_type_be(actual_type))));
+				/* collect the subtype for common-supertype choice */
+				anycompatible_actual_types[n_anycompatible_args++] = anycompatible_range_typelem;
+			}
+		}
 	}
 
 	/*
@@ -2151,8 +2315,6 @@ 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))
 		{
-			Oid			range_typelem;
-
 			range_typelem = get_range_subtype(range_typeid);
 			if (!OidIsValid(range_typelem))
 				ereport(ERROR,
@@ -2181,6 +2343,61 @@ enforce_generic_type_consistency(const Oid *actual_arg_types,
 								   format_type_be(elem_typeid))));
 			}
 		}
+		else
+			range_typelem = InvalidOid;
+
+		/* Get the element type based on the multirange type, if we have one */
+		if (OidIsValid(multirange_typeid))
+		{
+			multirange_typelem = get_multirange_range(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))));
+			}
+
+			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 */
+				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))));
+			}
+		}
+		else
+			multirange_typelem = InvalidOid;
 
 		if (!OidIsValid(elem_typeid))
 		{
@@ -2189,6 +2406,7 @@ enforce_generic_type_consistency(const Oid *actual_arg_types,
 				elem_typeid = ANYELEMENTOID;
 				array_typeid = ANYARRAYOID;
 				range_typeid = ANYRANGEOID;
+				multirange_typeid = ANYMULTIRANGEOID;
 			}
 			else
 			{
@@ -2288,6 +2506,7 @@ enforce_generic_type_consistency(const Oid *actual_arg_types,
 				anycompatible_typeid = ANYCOMPATIBLEOID;
 				anycompatible_array_typeid = ANYCOMPATIBLEARRAYOID;
 				anycompatible_range_typeid = ANYCOMPATIBLERANGEOID;
+				anycompatible_multirange_typeid = ANYCOMPATIBLEMULTIRANGEOID;
 			}
 			else
 			{
@@ -2319,6 +2538,8 @@ enforce_generic_type_consistency(const Oid *actual_arg_types,
 				declared_arg_types[j] = anycompatible_array_typeid;
 			else if (decl_type == ANYCOMPATIBLERANGEOID)
 				declared_arg_types[j] = anycompatible_range_typeid;
+			else if (decl_type == ANYCOMPATIBLEMULTIRANGEOID)
+				declared_arg_types[j] = anycompatible_multirange_typeid;
 		}
 	}
 
@@ -2369,6 +2590,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;
+			}
 		}
 	}
 
@@ -2405,6 +2637,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 ANYCOMPATIBLE use the appropriate type */
 	if (rettype == ANYCOMPATIBLEOID ||
 		rettype == ANYCOMPATIBLENONARRAYOID)
@@ -2439,6 +2687,17 @@ enforce_generic_type_consistency(const Oid *actual_arg_types,
 		return anycompatible_range_typeid;
 	}
 
+	/* if we return ANYCOMPATIBLEMULTIRANGE use the appropriate argument type */
+	if (rettype == ANYCOMPATIBLEMULTIRANGEOID)
+	{
+		/* this error is unreachable if the function signature is valid: */
+		if (!OidIsValid(anycompatible_multirange_typeid))
+			ereport(ERROR,
+					(errcode(ERRCODE_DATATYPE_MISMATCH),
+					 errmsg_internal("could not identify anycompatiblemultirange type")));
+		return anycompatible_multirange_typeid;
+	}
+
 	/* we don't return a generic type; send back the original return type */
 	return rettype;
 }
@@ -2456,20 +2715,38 @@ check_valid_polymorphic_signature(Oid ret_type,
 								  const Oid *declared_arg_types,
 								  int nargs)
 {
-	if (ret_type == ANYRANGEOID || ret_type == ANYCOMPATIBLERANGEOID)
+	if (ret_type == ANYRANGEOID || ret_type == ANYMULTIRANGEOID)
 	{
 		/*
-		 * ANYRANGE requires an ANYRANGE input, else we can't tell which of
-		 * several range types with the same element type to use.  Likewise
-		 * for ANYCOMPATIBLERANGE.
+		 * ANYRANGE and ANYMULTIRANGE require an ANYRANGE or ANYMULTIRANGE
+		 * input, else we can't tell which of several range types with the
+		 * same element type to use.
 		 */
 		for (int i = 0; i < nargs; i++)
 		{
-			if (declared_arg_types[i] == ret_type)
+			if (declared_arg_types[i] == ANYRANGEOID ||
+				declared_arg_types[i] == ANYMULTIRANGEOID)
 				return NULL;	/* OK */
 		}
-		return psprintf(_("A result of type %s requires at least one input of type %s."),
-						format_type_be(ret_type), format_type_be(ret_type));
+		return psprintf(_("A result of type %s requires at least one input of type anyrange or anymultirange."),
+						format_type_be(ret_type));
+	}
+	else if (ret_type == ANYCOMPATIBLERANGEOID || ret_type == ANYCOMPATIBLEMULTIRANGEOID)
+	{
+		/*
+		 * ANYCOMPATIBLERANGE and ANYCOMPATIBLEMULTIRANGE require an
+		 * ANYCOMPATIBLERANGE or ANYCOMPATIBLEMULTIRANGE input, else we can't
+		 * tell which of several range types with the same element type to
+		 * use.
+		 */
+		for (int i = 0; i < nargs; i++)
+		{
+			if (declared_arg_types[i] == ANYCOMPATIBLERANGEOID ||
+				declared_arg_types[i] == ANYCOMPATIBLEMULTIRANGEOID)
+				return NULL;	/* OK */
+		}
+		return psprintf(_("A result of type %s requires at least one input of type anycompatiblerange or anycompatiblemultirange."),
+						format_type_be(ret_type));
 	}
 	else if (IsPolymorphicTypeFamily1(ret_type))
 	{
@@ -2480,7 +2757,7 @@ check_valid_polymorphic_signature(Oid ret_type,
 				return NULL;	/* OK */
 		}
 		/* Keep this list in sync with IsPolymorphicTypeFamily1! */
-		return psprintf(_("A result of type %s requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange."),
+		return psprintf(_("A result of type %s requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange."),
 						format_type_be(ret_type));
 	}
 	else if (IsPolymorphicTypeFamily2(ret_type))
@@ -2632,6 +2909,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 || targettype == ANYCOMPATIBLEMULTIRANGEOID)
+		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 ce09ad73754..82732146d3d 100644
--- a/src/backend/utils/adt/Makefile
+++ b/src/backend/utils/adt/Makefile
@@ -60,6 +60,8 @@ OBJS = \
 	mac8.o \
 	mcxtfuncs.o \
 	misc.o \
+	multirangetypes.o \
+	multirangetypes_selfuncs.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 00000000000..7c6c22f27e1
--- /dev/null
+++ b/src/backend/utils/adt/multirangetypes.c
@@ -0,0 +1,2605 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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 "common/hashfn.h"
+#include "lib/stringinfo.h"
+#include "libpq/pqformat.h"
+#include "miscadmin.h"
+#include "utils/builtins.h"
+#include "utils/lsyscache.h"
+#include "utils/rangetypes.h"
+#include "utils/multirangetypes.h"
+#include "utils/array.h"
+#include "utils/memutils.h"
+
+/* fn_extra cache entry for one of the range I/O functions */
+typedef struct MultirangeIOData
+{
+	TypeCacheEntry *typcache;	/* multirange type's typcache entry */
+	FmgrInfo	typioproc;		/* range type's I/O proc */
+	Oid			typioparam;		/* range type's I/O parameter */
+} 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 mltrngtypid,
+												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 with 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;
+	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 = NULL;
+	int32		range_str_len;
+	char	   *range_str_copy;
+
+	cache = get_multirange_io_data(fcinfo, mltrngtypoid, IOFunc_input);
+	rangetyp = cache->typcache->rngtype;
+
+	/* 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 + 1;
+					range_str_copy = pnstrdup(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->typioproc,
+																 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;
+	int32		range_count;
+	int32		i;
+	RangeType **ranges;
+
+	cache = get_multirange_io_data(fcinfo, mltrngtypoid, IOFunc_output);
+
+	initStringInfo(&buf);
+
+	appendStringInfoChar(&buf, '{');
+
+	multirange_deserialize(cache->typcache->rngtype, multirange, &range_count, &ranges);
+	for (i = 0; i < range_count; i++)
+	{
+		if (i > 0)
+			appendStringInfoChar(&buf, ',');
+		range = ranges[i];
+		rangeStr = OutputFunctionCall(&cache->typioproc, RangeTypePGetDatum(range));
+		appendStringInfoString(&buf, rangeStr);
+	}
+
+	appendStringInfoChar(&buf, '}');
+
+	PG_RETURN_CSTRING(buf.data);
+}
+
+/*
+ * Binary representation: First a int32-sized count of ranges, followed by
+ * ranges in their native binary representation.
+ */
+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;
+	uint32		range_count;
+	RangeType **ranges;
+	MultirangeType *ret;
+	StringInfoData tmpbuf;
+
+	cache = get_multirange_io_data(fcinfo, mltrngtypoid, IOFunc_receive);
+
+	range_count = pq_getmsgint(buf, 4);
+	ranges = palloc(range_count * sizeof(RangeType *));
+
+	initStringInfo(&tmpbuf);
+	for (int i = 0; i < range_count; i++)
+	{
+		uint32		range_len = pq_getmsgint(buf, 4);
+		const char *range_data = pq_getmsgbytes(buf, range_len);
+
+		resetStringInfo(&tmpbuf);
+		appendBinaryStringInfo(&tmpbuf, range_data, range_len);
+
+		ranges[i] = DatumGetRangeTypeP(ReceiveFunctionCall(&cache->typioproc,
+														   &tmpbuf,
+														   cache->typioparam,
+														   typmod));
+	}
+	pfree(tmpbuf.data);
+
+	pq_getmsgend(buf);
+
+	ret = make_multirange(mltrngtypoid, cache->typcache->rngtype,
+						  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;
+	MultirangeIOData *cache;
+
+	cache = get_multirange_io_data(fcinfo, mltrngtypoid, IOFunc_send);
+
+	/* construct output */
+	pq_begintypsend(buf);
+
+	pq_sendint32(buf, multirange->rangeCount);
+
+	multirange_deserialize(cache->typcache->rngtype, multirange, &range_count, &ranges);
+	for (int i = 0; i < range_count; i++)
+	{
+		Datum		range;
+
+		range = RangeTypePGetDatum(ranges[i]);
+		range = PointerGetDatum(SendFunctionCall(&cache->typioproc, range));
+
+		pq_sendint32(buf, VARSIZE(range) - VARHDRSZ);
+		pq_sendbytes(buf, VARDATA(range), VARSIZE(range) - VARHDRSZ);
+	}
+
+	PG_RETURN_BYTEA_P(pq_endtypsend(buf));
+}
+
+/*
+ * 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)
+	{
+		Oid			typiofunc;
+		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,
+						 &typiofunc);
+
+		if (!OidIsValid(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(typiofunc, &cache->typioproc,
+					  fcinfo->flinfo->fn_mcxt);
+
+		fcinfo->flinfo->fn_extra = (void *) cache;
+	}
+
+	return cache;
+}
+
+/*
+ * Converts a list of arbitrary ranges 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;
+}
+
+/*
+ *----------------------------------------------------------
+ * 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;
+}
+
+static Size
+multirange_size_estimate(TypeCacheEntry *rangetyp, int32 range_count,
+						 RangeType **ranges)
+{
+	char		elemalign = rangetyp->rngelemtype->typalign;
+	Size		size;
+	int			i;
+
+	/*
+	 * Count space for varlena header, multirange type's OID, other fields,
+	 * and padding so that ShortRangeTypes start aligned.
+	 */
+	size = att_align_nominal(sizeof(MultirangeType) +
+							 Max(range_count - 1, 0) * sizeof(uint32) +
+							 range_count * sizeof(uint8), elemalign);
+
+	/* Count space for all ranges */
+	for (i = 0; i < range_count; i++)
+		size += att_align_nominal(VARSIZE(ranges[i]) - sizeof(RangeType) - sizeof(char), elemalign);
+
+	return size;
+}
+
+static void
+write_multirange_data(MultirangeType *multirange, TypeCacheEntry *rangetyp,
+					  int32 range_count, RangeType **ranges)
+{
+	uint32	   *offsets;
+	uint32 		prev_offset = 0;
+	uint8	   *flags;
+	int			i;
+	Pointer		begin,
+				ptr;
+	char		elemalign = rangetyp->rngelemtype->typalign;
+
+	offsets = MultirangeGetOffsetsPtr(multirange);
+	flags = MultirangeGetFlagsPtr(multirange);
+	ptr = begin = MultirangeGetBoundariesPtr(multirange, elemalign);
+	for (i = 0; i < range_count; i++)
+	{
+		uint32		len;
+
+		if (i > 0)
+		{
+			offsets[i - 1] = ptr - begin;
+			if ((i & 0x3) != 0)
+				offsets[i - 1] -= prev_offset;
+			prev_offset = ptr - begin;
+		}
+		flags[i] = *((Pointer) ranges[i] + VARSIZE(ranges[i]) - sizeof(char));
+		len = VARSIZE(ranges[i]) - sizeof(RangeType) - sizeof(char);
+		memcpy(ptr, (Pointer) (ranges[i] + 1), len);
+		ptr += att_align_nominal(len, elemalign);
+	}
+}
+
+
+/*
+ * 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;
+	Size		size;
+
+	/* Sort and merge input ranges. */
+	range_count = multirange_canonicalize(rangetyp, range_count, ranges);
+
+
+	/* Note: zero-fill is required here, just as in heap tuples */
+	size = multirange_size_estimate(rangetyp, range_count, ranges);
+	multirange = palloc0(size);
+	SET_VARSIZE(multirange, size);
+
+	/* Now fill in the datum */
+	multirange->multirangetypid = mltrngtypoid;
+	multirange->rangeCount = range_count;
+
+	write_multirange_data(multirange, rangetyp, range_count, ranges);
+
+	return multirange;
+}
+
+static uint32
+multirange_get_bounds_offset(const MultirangeType *multirange, uint32 i)
+{
+	uint32 *offsets = MultirangeGetOffsetsPtr(multirange);
+	uint32	offset;
+	uint32	j;
+
+	j = i & 0xFFFFFFFC;
+
+	if (j == 0)
+		offset = 0;
+	else
+		offset = offsets[j - 1];
+
+	while (j < i)
+	{
+		offset += offsets[j];
+		j++;
+	}
+	return offset;
+}
+
+static RangeType *
+multirange_get_range(TypeCacheEntry *rangetyp,
+					 const MultirangeType *multirange, int i)
+{
+	uint32		offset;
+	uint8		flags;
+	Pointer		begin,
+				ptr;
+	int16		typlen = rangetyp->rngelemtype->typlen;
+	char		typalign = rangetyp->rngelemtype->typalign;
+	uint32		len;
+	RangeType  *range;
+
+	Assert(i < multirange->rangeCount);
+
+	offset = multirange_get_bounds_offset(multirange, i);
+	flags = MultirangeGetFlagsPtr(multirange)[i];
+	ptr = begin = MultirangeGetBoundariesPtr(multirange, typalign) + offset;
+
+	if (RANGE_HAS_LBOUND(flags))
+		ptr = (Pointer) att_addlength_pointer(ptr, typlen, ptr);
+	if (RANGE_HAS_UBOUND(flags))
+		ptr = (Pointer) att_addlength_pointer(ptr, typlen, ptr);
+
+	len = (ptr - begin) + sizeof(RangeType) + sizeof(char);
+
+	range = palloc0(len);
+	SET_VARSIZE(range, len);
+	range->rangetypid = rangetyp->type_id;
+
+	memcpy(range + 1, begin, ptr - begin);
+	*((Pointer) (range + 1) + (ptr - begin)) = flags;
+
+	return range;
+}
+
+/*
+ * 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(TypeCacheEntry *rangetyp,
+					   const MultirangeType *multirange, int32 *range_count,
+					   RangeType ***ranges)
+{
+	*range_count = multirange->rangeCount;
+
+	/* Convert each ShortRangeType into a RangeType */
+	if (*range_count > 0)
+	{
+		int			i;
+
+		*ranges = palloc(*range_count * sizeof(RangeType *));
+		for (i = 0; i < *range_count; i++)
+			(*ranges)[i] = multirange_get_range(rangetyp, multirange, i);
+	}
+	else
+	{
+		*ranges = NULL;
+	}
+}
+
+MultirangeType *
+make_empty_multirange(Oid mltrngtypoid, TypeCacheEntry *rangetyp)
+{
+	return make_multirange(mltrngtypoid, rangetyp, 0, NULL);
+}
+
+/*
+ *----------------------------------------------------------
+ * EXPANDED TOAST FUNCTIONS
+ *----------------------------------------------------------
+ */
+
+static void
+multirange_get_bounds(TypeCacheEntry *rangetyp,
+					  const MultirangeType *multirange,
+					  uint32 i, RangeBound *lower, RangeBound *upper)
+{
+	uint32		offset;
+	uint8		flags;
+	Pointer		ptr;
+	int16		typlen = rangetyp->rngelemtype->typlen;
+	char		typalign = rangetyp->rngelemtype->typalign;
+	bool		typbyval = rangetyp->rngelemtype->typbyval;
+	Datum		lbound;
+	Datum		ubound;
+
+	Assert(i < multirange->rangeCount);
+
+	offset = multirange_get_bounds_offset(multirange, i);
+	flags = MultirangeGetFlagsPtr(multirange)[i];
+	ptr = MultirangeGetBoundariesPtr(multirange, typalign) + offset;
+
+	/* multirange can't contain empty ranges */
+	Assert((flags & RANGE_EMPTY) == 0);
+
+	/* fetch lower bound, if any */
+	if (RANGE_HAS_LBOUND(flags))
+	{
+		/* att_align_pointer cannot be necessary here */
+		lbound = fetch_att(ptr, typbyval, typlen);
+		ptr = (Pointer) att_addlength_pointer(ptr, typlen, ptr);
+	}
+	else
+		lbound = (Datum) 0;
+
+	/* fetch upper bound, if any */
+	if (RANGE_HAS_UBOUND(flags))
+	{
+		ptr = (Pointer) att_align_pointer(ptr, typalign, typlen, ptr);
+		ubound = fetch_att(ptr, typbyval, typlen);
+		/* no need for att_addlength_pointer */
+	}
+	else
+		ubound = (Datum) 0;
+
+	/* emit results */
+
+	lower->val = lbound;
+	lower->infinite = (flags & RANGE_LB_INF) != 0;
+	lower->inclusive = (flags & RANGE_LB_INC) != 0;
+	lower->lower = true;
+
+	upper->val = ubound;
+	upper->infinite = (flags & RANGE_UB_INF) != 0;
+	upper->inclusive = (flags & RANGE_UB_INC) != 0;
+	upper->lower = false;
+}
+
+/*
+ *----------------------------------------------------------
+ * 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 ranges
+ * that match our return value, and there must be no NULLs.
+ */
+Datum
+multirange_constructor2(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,
+				(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+				 errmsg("multirange values cannot contain NULL members")));
+
+	rangeArray = PG_GETARG_ARRAYTYPE_P(0);
+
+	dims = ARR_NDIM(rangeArray);
+	if (dims > 1)
+		ereport(ERROR,
+				(errcode(ERRCODE_CARDINALITY_VIOLATION),
+				 errmsg("multiranges cannot be constructed from multi-dimensional arrays")));
+
+	rngtypid = ARR_ELEMTYPE(rangeArray);
+	if (rngtypid != rangetyp->type_id)
+		ereport(ERROR,
+				(errcode(ERRCODE_DATATYPE_MISMATCH),
+				 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,
+						(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+						 errmsg("multirange values cannot contain NULL members")));
+
+			/* make_multirange will do its own copy */
+			ranges[i] = DatumGetRangeTypeP(elements[i]);
+		}
+	}
+
+	PG_RETURN_MULTIRANGE_P(make_multirange(mltrngtypid, rangetyp, range_count, ranges));
+}
+
+/*
+ * Construct multirange value from a single range.  It'd be nice if we could
+ * just use multirange_constructor2 for this case, but we need a non-variadic
+ * single-arg function to let us define a CAST from a range to its multirange.
+ */
+Datum
+multirange_constructor1(PG_FUNCTION_ARGS)
+{
+	Oid			mltrngtypid = get_fn_expr_rettype(fcinfo->flinfo);
+	Oid			rngtypid;
+	TypeCacheEntry *typcache;
+	TypeCacheEntry *rangetyp;
+	RangeType  *range;
+
+	typcache = multirange_get_typcache(fcinfo, mltrngtypid);
+	rangetyp = typcache->rngtype;
+
+	/*
+	 * These checks should be guaranteed by our signature, but let's do them
+	 * just in case.
+	 */
+
+	if (PG_ARGISNULL(0))
+		ereport(ERROR,
+				(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+				 errmsg("multirange values cannot contain NULL members")));
+
+	range = PG_GETARG_RANGE_P(0);
+
+	/* Make sure the range type matches. */
+	rngtypid = RangeTypeGetOid(range);
+	if (rngtypid != rangetyp->type_id)
+		ereport(ERROR,
+				(errcode(ERRCODE_DATATYPE_MISMATCH),
+				 errmsg("type %u does not match constructor type", rngtypid)));
+
+	PG_RETURN_MULTIRANGE_P(make_multirange(mltrngtypid, rangetyp, 1, &range));
+}
+
+/*
+ * 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
+		elog(ERROR,				/* can't happen */
+			 "niladic multirange constructor must not receive arguments");
+}
+
+
+/* multirange, multirange -> multirange type functions */
+
+/* multirange union */
+Datum
+multirange_union(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
+	MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count1;
+	int32		range_count2;
+	int32		range_count3;
+	RangeType **ranges1;
+	RangeType **ranges2;
+	RangeType **ranges3;
+
+	if (MultirangeIsEmpty(mr1))
+		PG_RETURN_MULTIRANGE_P(mr2);
+	if (MultirangeIsEmpty(mr2))
+		PG_RETURN_MULTIRANGE_P(mr1);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr1));
+
+	multirange_deserialize(typcache->rngtype, mr1, &range_count1, &ranges1);
+	multirange_deserialize(typcache->rngtype, mr2, &range_count2, &ranges2);
+
+	range_count3 = range_count1 + range_count2;
+	ranges3 = palloc0(range_count3 * sizeof(RangeType *));
+	memcpy(ranges3, ranges1, range_count1 * sizeof(RangeType *));
+	memcpy(ranges3 + range_count1, ranges2, range_count2 * sizeof(RangeType *));
+	PG_RETURN_MULTIRANGE_P(make_multirange(typcache->type_id, typcache->rngtype,
+										   range_count3, ranges3));
+}
+
+/* multirange minus */
+Datum
+multirange_minus(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
+	MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
+	Oid			mltrngtypoid = MultirangeTypeGetOid(mr1);
+	TypeCacheEntry *typcache;
+	TypeCacheEntry *rangetyp;
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+
+	typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
+	rangetyp = typcache->rngtype;
+
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		PG_RETURN_MULTIRANGE_P(mr1);
+
+	multirange_deserialize(typcache->rngtype, mr1, &range_count1, &ranges1);
+	multirange_deserialize(typcache->rngtype, mr2, &range_count2, &ranges2);
+
+	PG_RETURN_MULTIRANGE_P(multirange_minus_internal(mltrngtypoid,
+													 rangetyp,
+													 range_count1,
+													 ranges1,
+													 range_count2,
+													 ranges2));
+}
+
+MultirangeType *
+multirange_minus_internal(Oid mltrngtypoid, TypeCacheEntry *rangetyp,
+						  int32 range_count1, RangeType **ranges1,
+						  int32 range_count2, RangeType **ranges2)
+{
+	RangeType  *r1;
+	RangeType  *r2;
+	RangeType **ranges3;
+	int32		range_count3;
+	int32		i1;
+	int32		i2;
+
+	/*
+	 * Worst case: every range in ranges1 makes a different cut to some range
+	 * in ranges2.
+	 */
+	ranges3 = palloc0((range_count1 + range_count2) * sizeof(RangeType *));
+	range_count3 = 0;
+
+	/*
+	 * For each range in mr1, keep subtracting until it's gone or the ranges
+	 * in mr2 have passed it. After a subtraction we assign what's left back
+	 * to r1. The parallel progress through mr1 and mr2 is similar to
+	 * multirange_overlaps_multirange_internal.
+	 */
+	r2 = ranges2[0];
+	for (i1 = 0, i2 = 0; i1 < range_count1; i1++)
+	{
+		r1 = ranges1[i1];
+
+		/* Discard r2s while r2 << r1 */
+		while (r2 != NULL && range_before_internal(rangetyp, r2, r1))
+		{
+			r2 = ++i2 >= range_count2 ? NULL : ranges2[i2];
+		}
+
+		while (r2 != NULL)
+		{
+			if (range_split_internal(rangetyp, r1, r2, &ranges3[range_count3], &r1))
+			{
+				/*
+				 * If r2 takes a bite out of the middle of r1, we need two
+				 * outputs
+				 */
+				range_count3++;
+				r2 = ++i2 >= range_count2 ? NULL : ranges2[i2];
+
+			}
+			else if (range_overlaps_internal(rangetyp, r1, r2))
+			{
+				/*
+				 * If r2 overlaps r1, replace r1 with r1 - r2.
+				 */
+				r1 = range_minus_internal(rangetyp, r1, r2);
+
+				/*
+				 * If r2 goes past r1, then we need to stay with it, in case
+				 * it hits future r1s. Otherwise we need to keep r1, in case
+				 * future r2s hit it. Since we already subtracted, there's no
+				 * point in using the overright/overleft calls.
+				 */
+				if (RangeIsEmpty(r1) || range_before_internal(rangetyp, r1, r2))
+					break;
+				else
+					r2 = ++i2 >= range_count2 ? NULL : ranges2[i2];
+
+			}
+			else
+			{
+				/*
+				 * This and all future r2s are past r1, so keep them. Also
+				 * assign whatever is left of r1 to the result.
+				 */
+				break;
+			}
+		}
+
+		/*
+		 * Nothing else can remove anything from r1, so keep it. Even if r1 is
+		 * empty here, make_multirange will remove it.
+		 */
+		ranges3[range_count3++] = r1;
+	}
+
+	return make_multirange(mltrngtypoid, rangetyp, range_count3, ranges3);
+}
+
+/* multirange intersection */
+Datum
+multirange_intersect(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
+	MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
+	Oid			mltrngtypoid = MultirangeTypeGetOid(mr1);
+	TypeCacheEntry *typcache;
+	TypeCacheEntry *rangetyp;
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+
+	typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
+	rangetyp = typcache->rngtype;
+
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		PG_RETURN_MULTIRANGE_P(make_empty_multirange(mltrngtypoid, rangetyp));
+
+	multirange_deserialize(rangetyp, mr1, &range_count1, &ranges1);
+	multirange_deserialize(rangetyp, mr2, &range_count2, &ranges2);
+
+	PG_RETURN_MULTIRANGE_P(multirange_intersect_internal(mltrngtypoid,
+														 rangetyp,
+														 range_count1,
+														 ranges1,
+														 range_count2,
+														 ranges2));
+}
+
+MultirangeType *
+multirange_intersect_internal(Oid mltrngtypoid, TypeCacheEntry *rangetyp,
+							  int32 range_count1, RangeType **ranges1,
+							  int32 range_count2, RangeType **ranges2)
+{
+	RangeType  *r1;
+	RangeType  *r2;
+	RangeType **ranges3;
+	int32		range_count3;
+	int32		i1;
+	int32		i2;
+
+	if (range_count1 == 0 || range_count2 == 0)
+		return make_multirange(mltrngtypoid, rangetyp, 0, NULL);
+
+	/*-----------------------------------------------
+	 * Worst case is a stitching pattern like this:
+	 *
+	 * mr1: --- --- --- ---
+	 * mr2:   --- --- ---
+	 * mr3:   - - - - - -
+	 *
+	 * That seems to be range_count1 + range_count2 - 1,
+	 * but one extra won't hurt.
+	 *-----------------------------------------------
+	 */
+	ranges3 = palloc0((range_count1 + range_count2) * sizeof(RangeType *));
+	range_count3 = 0;
+
+	/*
+	 * For each range in mr1, keep intersecting until the ranges in mr2 have
+	 * passed it. The parallel progress through mr1 and mr2 is similar to
+	 * multirange_minus_multirange_internal, but we don't have to assign back
+	 * to r1.
+	 */
+	r2 = ranges2[0];
+	for (i1 = 0, i2 = 0; i1 < range_count1; i1++)
+	{
+		r1 = ranges1[i1];
+
+		/* Discard r2s while r2 << r1 */
+		while (r2 != NULL && range_before_internal(rangetyp, r2, r1))
+		{
+			r2 = ++i2 >= range_count2 ? NULL : ranges2[i2];
+		}
+
+		while (r2 != NULL)
+		{
+			if (range_overlaps_internal(rangetyp, r1, r2))
+			{
+				/* Keep the overlapping part */
+				ranges3[range_count3++] = range_intersect_internal(rangetyp, r1, r2);
+
+				/* If we "used up" all of r2, go to the next one... */
+				if (range_overleft_internal(rangetyp, r2, r1))
+					r2 = ++i2 >= range_count2 ? NULL : ranges2[i2];
+
+				/* ...otherwise go to the next r1 */
+				else
+					break;
+			}
+			else
+				/* We're past r1, so move to the next one */
+				break;
+		}
+
+		/* If we're out of r2s, there can be no more intersections */
+		if (r2 == NULL)
+			break;
+	}
+
+	return make_multirange(mltrngtypoid, rangetyp, range_count3, ranges3);
+}
+
+/*
+ * range_agg_transfn: combine adjacent/overlapping ranges.
+ *
+ * All we do here is gather the input ranges into an array
+ * so that the finalfn can sort and combine them.
+ */
+Datum
+range_agg_transfn(PG_FUNCTION_ARGS)
+{
+	MemoryContext aggContext;
+	Oid			rngtypoid;
+	ArrayBuildState *state;
+
+	if (!AggCheckCallContext(fcinfo, &aggContext))
+		elog(ERROR, "range_agg_transfn called in non-aggregate context");
+
+	rngtypoid = get_fn_expr_argtype(fcinfo->flinfo, 1);
+	if (!type_is_range(rngtypoid))
+		ereport(ERROR,
+				(errcode(ERRCODE_DATATYPE_MISMATCH),
+				 errmsg("range_agg must be called with a range")));
+
+	if (PG_ARGISNULL(0))
+		state = initArrayResult(rngtypoid, aggContext, false);
+	else
+		state = (ArrayBuildState *) PG_GETARG_POINTER(0);
+
+	/* skip NULLs */
+	if (!PG_ARGISNULL(1))
+		accumArrayResult(state, PG_GETARG_DATUM(1), false, rngtypoid, aggContext);
+
+	PG_RETURN_POINTER(state);
+}
+
+/*
+ * range_agg_finalfn: use our internal array to merge touching ranges.
+ */
+Datum
+range_agg_finalfn(PG_FUNCTION_ARGS)
+{
+	MemoryContext aggContext;
+	Oid			mltrngtypoid;
+	TypeCacheEntry *typcache;
+	ArrayBuildState *state;
+	int32		range_count;
+	RangeType **ranges;
+	int			i;
+
+	if (!AggCheckCallContext(fcinfo, &aggContext))
+		elog(ERROR, "range_agg_finalfn called in non-aggregate context");
+
+	state = PG_ARGISNULL(0) ? NULL : (ArrayBuildState *) PG_GETARG_POINTER(0);
+	if (state == NULL)
+		/* This shouldn't be possible, but just in case.... */
+		PG_RETURN_NULL();
+
+	/* Also return NULL if we had zero inputs, like other aggregates */
+	range_count = state->nelems;
+	if (range_count == 0)
+		PG_RETURN_NULL();
+
+	mltrngtypoid = get_fn_expr_rettype(fcinfo->flinfo);
+	typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
+
+	ranges = palloc0(range_count * sizeof(RangeType *));
+	for (i = 0; i < range_count; i++)
+		ranges[i] = DatumGetRangeTypeP(state->dvalues[i]);
+
+	PG_RETURN_MULTIRANGE_P(make_multirange(mltrngtypoid, typcache->rngtype, range_count, ranges));
+}
+
+Datum
+multirange_intersect_agg_transfn(PG_FUNCTION_ARGS)
+{
+	MemoryContext aggContext;
+	Oid			mltrngtypoid;
+	TypeCacheEntry *typcache;
+	MultirangeType *result;
+	MultirangeType *current;
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+
+	if (!AggCheckCallContext(fcinfo, &aggContext))
+		elog(ERROR, "multirange_intersect_agg_transfn called in non-aggregate context");
+
+	mltrngtypoid = get_fn_expr_argtype(fcinfo->flinfo, 1);
+	if (!type_is_multirange(mltrngtypoid))
+		ereport(ERROR,
+				(errcode(ERRCODE_DATATYPE_MISMATCH),
+				 errmsg("range_intersect_agg must be called with a multirange")));
+
+	typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
+
+	/* strictness ensures these are non-null */
+	result = PG_GETARG_MULTIRANGE_P(0);
+	current = PG_GETARG_MULTIRANGE_P(1);
+
+	multirange_deserialize(typcache->rngtype, result, &range_count1, &ranges1);
+	multirange_deserialize(typcache->rngtype, current, &range_count2, &ranges2);
+
+	result = multirange_intersect_internal(mltrngtypoid,
+										   typcache->rngtype,
+										   range_count1,
+										   ranges1,
+										   range_count2,
+										   ranges2);
+	PG_RETURN_RANGE_P(result);
+}
+
+
+/* multirange -> element type functions */
+
+/* extract lower bound value */
+Datum
+multirange_lower(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	TypeCacheEntry *typcache;
+	RangeBound	lower;
+	RangeBound	upper;
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_NULL();
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_get_bounds(typcache->rngtype, mr, 0,
+						  &lower, &upper);
+
+	if (!lower.infinite)
+		PG_RETURN_DATUM(lower.val);
+	else
+		PG_RETURN_NULL();
+}
+
+/* extract upper bound value */
+Datum
+multirange_upper(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	TypeCacheEntry *typcache;
+	RangeBound	lower;
+	RangeBound	upper;
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_NULL();
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_get_bounds(typcache->rngtype, mr, mr->rangeCount - 1,
+						  &lower, &upper);
+
+	if (!upper.infinite)
+		PG_RETURN_DATUM(upper.val);
+	else
+		PG_RETURN_NULL();
+}
+
+
+/* multirange -> bool functions */
+
+/* is multirange empty? */
+Datum
+multirange_empty(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+
+	PG_RETURN_BOOL(MultirangeIsEmpty(mr));
+}
+
+/* is lower bound inclusive? */
+Datum
+multirange_lower_inc(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	TypeCacheEntry *typcache;
+	RangeBound	lower;
+	RangeBound	upper;
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+	multirange_get_bounds(typcache->rngtype, mr, 0,
+						  &lower, &upper);
+
+	PG_RETURN_BOOL(lower.inclusive);
+}
+
+/* is upper bound inclusive? */
+Datum
+multirange_upper_inc(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	TypeCacheEntry *typcache;
+	RangeBound	lower;
+	RangeBound	upper;
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+	multirange_get_bounds(typcache->rngtype, mr, mr->rangeCount - 1,
+						  &lower, &upper);
+
+	PG_RETURN_BOOL(upper.inclusive);
+}
+
+/* is lower bound infinite? */
+Datum
+multirange_lower_inf(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	TypeCacheEntry *typcache;
+	RangeBound	lower;
+	RangeBound	upper;
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+	multirange_get_bounds(typcache->rngtype, mr, 0,
+						  &lower, &upper);
+
+	PG_RETURN_BOOL(lower.infinite);
+}
+
+/* is upper bound infinite? */
+Datum
+multirange_upper_inf(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	TypeCacheEntry *typcache;
+	RangeBound	lower;
+	RangeBound	upper;
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+	multirange_get_bounds(typcache->rngtype, mr, mr->rangeCount - 1,
+						  &lower, &upper);
+
+	PG_RETURN_BOOL(upper.infinite);
+}
+
+
+
+/* multirange, element -> bool functions */
+
+static bool
+range_bounds_overlaps(TypeCacheEntry *typcache,
+					  RangeBound *lower1, RangeBound *upper1,
+					  RangeBound *lower2, RangeBound *upper2)
+{
+	if (range_cmp_bounds(typcache, lower1, lower2) >= 0 &&
+		range_cmp_bounds(typcache, lower1, upper2) <= 0)
+		return true;
+
+	if (range_cmp_bounds(typcache, lower2, lower1) >= 0 &&
+		range_cmp_bounds(typcache, lower2, upper1) <= 0)
+		return true;
+
+	return false;
+}
+
+static bool
+range_bounds_contains(TypeCacheEntry *typcache,
+					  RangeBound *lower1, RangeBound *upper1,
+					  RangeBound *lower2, RangeBound *upper2)
+{
+	if (range_cmp_bounds(typcache, lower1, lower2) <= 0 &&
+		range_cmp_bounds(typcache, upper1, upper2) >= 0)
+		return true;
+
+	return false;
+}
+
+/* contains? */
+Datum
+multirange_contains_elem(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	Datum		val = PG_GETARG_DATUM(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(multirange_contains_elem_internal(typcache, mr, val));
+}
+
+/* contained by? */
+Datum
+elem_contained_by_multirange(PG_FUNCTION_ARGS)
+{
+	Datum		val = PG_GETARG_DATUM(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(multirange_contains_elem_internal(typcache, mr, val));
+}
+
+/*
+ * Test whether multirange mr contains a specific element value.
+ */
+
+typedef int (*multirange_bsearch_callback) (TypeCacheEntry *typcache,
+											RangeBound *lower,
+											RangeBound *upper,
+											void *key,
+											bool *match);
+
+static bool
+multirange_bsearch_match(TypeCacheEntry *typcache, MultirangeType *mr,
+						 void *key, multirange_bsearch_callback callback)
+{
+	uint32		l,
+				u,
+				idx;
+	int			comparison;
+	bool		match = false;
+
+	l = 0;
+	u = mr->rangeCount;
+	while (l < u)
+	{
+		RangeBound	lower,
+					upper;
+
+		idx = (l + u) / 2;
+		multirange_get_bounds(typcache, mr, idx, &lower, &upper);
+		comparison = (*callback) (typcache, &lower, &upper, key, &match);
+
+		if (comparison < 0)
+			u = idx;
+		else if (comparison > 0)
+			l = idx + 1;
+		else
+			return match;
+	}
+
+	return false;
+}
+
+static int
+multirange_elem_bsearch_callback(TypeCacheEntry *typcache,
+								 RangeBound *lower, RangeBound *upper,
+								 void *key, bool *match)
+{
+	Datum	val = *((Datum *) key);
+	int		cmp;
+
+	if (!lower->infinite)
+	{
+		cmp = DatumGetInt32(FunctionCall2Coll(&typcache->rng_cmp_proc_finfo,
+											  typcache->rng_collation,
+											  lower->val, val));
+		if (cmp > 0 || (cmp == 0 && !lower->inclusive))
+			return -1;
+	}
+
+	if (!upper->infinite)
+	{
+		cmp = DatumGetInt32(FunctionCall2Coll(&typcache->rng_cmp_proc_finfo,
+											  typcache->rng_collation,
+											  upper->val, val));
+		if (cmp < 0 || (cmp == 0 && !upper->inclusive))
+			return 1;
+	}
+
+	*match = true;
+	return 0;
+}
+
+static int
+multirange_range_contains_bsearch_callback(TypeCacheEntry *typcache,
+										   RangeBound *lower, RangeBound *upper,
+										   void *key, bool *match)
+{
+	RangeBound *keyLower = (RangeBound *) key;
+	RangeBound *keyUpper = (RangeBound *) key + 1;
+
+	if (range_cmp_bounds(typcache, keyUpper, lower) < 0)
+		return -1;
+
+	if (range_cmp_bounds(typcache, keyLower, upper) > 0)
+		return -1;
+
+	*match = range_bounds_contains(typcache, lower, upper, keyLower, keyUpper);
+	return 0;
+}
+
+static int
+multirange_range_overlaps_bsearch_callback(TypeCacheEntry *typcache,
+										   RangeBound *lower, RangeBound *upper,
+										   void *key, bool *match)
+{
+	RangeBound *keyLower = (RangeBound *) key;
+	RangeBound *keyUpper = (RangeBound *) key + 1;
+
+	if (range_cmp_bounds(typcache, keyUpper, lower) < 0)
+		return -1;
+
+	if (range_cmp_bounds(typcache, keyLower, upper) > 0)
+		return -1;
+
+	*match = true;
+	return 0;
+}
+
+bool
+multirange_contains_elem_internal(TypeCacheEntry *typcache, MultirangeType *mr, Datum val)
+{
+	if (MultirangeIsEmpty(mr))
+		return false;
+
+	return multirange_bsearch_match(typcache->rngtype, mr, &val,
+								 	multirange_elem_bsearch_callback);
+}
+
+/* multirange, range -> bool functions */
+
+/* contains? */
+Datum
+multirange_contains_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(multirange_contains_range_internal(typcache, mr, r));
+}
+
+/* contained by? */
+Datum
+range_contained_by_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(multirange_contains_range_internal(typcache, mr, r));
+}
+
+/*
+ * Test whether multirange mr contains a specific range r.
+ */
+bool
+multirange_contains_range_internal(TypeCacheEntry *typcache, MultirangeType *mr, RangeType *r)
+{
+	TypeCacheEntry *rangetyp;
+	RangeBound	bounds[2];
+	bool		empty;
+
+	rangetyp = typcache->rngtype;
+
+	/*
+	 * Every multirange contains an infinite number of empty ranges, even an
+	 * empty one.
+	 */
+	if (RangeIsEmpty(r))
+		return true;
+
+	if (MultirangeIsEmpty(mr))
+		return false;
+
+	range_deserialize(rangetyp, r, &bounds[0], &bounds[1], &empty);
+	Assert(!empty);
+
+	return multirange_bsearch_match(rangetyp, mr, bounds,
+								 	multirange_range_contains_bsearch_callback);
+}
+
+
+/* multirange, multirange -> bool functions */
+
+/* equality (internal version) */
+bool
+multirange_eq_internal(TypeCacheEntry *typcache, MultirangeType *mr1, MultirangeType *mr2)
+{
+	TypeCacheEntry *rangetyp = typcache->rngtype;
+	int32		range_count_1;
+	int32		range_count_2;
+	int32		i;
+	RangeBound	lower1,
+				upper1,
+				lower2,
+				upper2;
+
+	/* Different types should be prevented by ANYMULTIRANGE matching rules */
+	if (MultirangeTypeGetOid(mr1) != MultirangeTypeGetOid(mr2))
+		elog(ERROR, "multirange types do not match");
+
+	range_count_1 = mr1->rangeCount;
+	range_count_2 = mr2->rangeCount;
+
+	if (range_count_1 != range_count_2)
+		return false;
+
+	for (i = 0; i < range_count_1; i++)
+	{
+		multirange_get_bounds(rangetyp, mr1, i, &lower1, &upper1);
+		multirange_get_bounds(rangetyp, mr2, i, &lower2, &upper2);
+
+		if (range_cmp_bounds(rangetyp, &lower1, &lower2) != 0 ||
+			range_cmp_bounds(rangetyp, &upper1, &upper2) != 0)
+			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));
+}
+
+/* overlaps? */
+Datum
+range_overlaps_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(range_overlaps_multirange_internal(typcache, r, mr));
+}
+
+Datum
+multirange_overlaps_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(range_overlaps_multirange_internal(typcache, r, mr));
+}
+
+Datum
+multirange_overlaps_multirange(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_overlaps_multirange_internal(typcache, mr1, mr2));
+}
+
+bool
+range_overlaps_multirange_internal(TypeCacheEntry *typcache, RangeType *r, MultirangeType *mr)
+{
+	TypeCacheEntry *rangetyp;
+	RangeBound	bounds[2];
+	bool		empty;
+
+	rangetyp = typcache->rngtype;
+
+	/*
+	 * Empties never overlap, even with empties. (This seems strange since
+	 * they *do* contain each other, but we want to follow how ranges work.)
+	 */
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		return false;
+
+	range_deserialize(rangetyp, r, &bounds[0], &bounds[1], &empty);
+	Assert(!empty);
+
+	return multirange_bsearch_match(rangetyp, mr, bounds,
+								 	multirange_range_overlaps_bsearch_callback);
+}
+
+bool
+multirange_overlaps_multirange_internal(TypeCacheEntry *typcache, MultirangeType *mr1,
+										MultirangeType *mr2)
+{
+	TypeCacheEntry *rangetyp;
+	int32		range_count1;
+	int32		range_count2;
+	int32		i1;
+	int32		i2;
+	RangeBound	lower1,
+				upper1,
+				lower2,
+				upper2;
+
+	/*
+	 * Empties never overlap, even with empties. (This seems strange since
+	 * they *do* contain each other, but we want to follow how ranges work.)
+	 */
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		return false;
+
+	rangetyp = typcache->rngtype;
+
+	range_count1 = mr1->rangeCount;
+	range_count2 = mr2->rangeCount;
+
+	/*
+	 * Every range in mr1 gets a chance to overlap with the ranges in mr2, but
+	 * we can use their ordering to avoid O(n^2). This is similar to
+	 * range_overlaps_multirange where r1 : r2 :: mrr : r, but there if we
+	 * don't find an overlap with r we're done, and here if we don't find an
+	 * overlap with r2 we try the next r2.
+	 */
+	i1 = 0;
+	multirange_get_bounds(rangetyp, mr1, i1, &lower1, &upper1);
+	for (i1 = 0, i2 = 0; i2 < range_count2; i2++)
+	{
+		multirange_get_bounds(rangetyp, mr2, i2, &lower2, &upper2);
+
+		/* Discard r1s while r1 << r2 */
+		while (range_cmp_bounds(rangetyp, &upper1, &lower2) < 0)
+		{
+			if (++i1 >= range_count1)
+				return false;
+			multirange_get_bounds(rangetyp, mr1, i1, &lower1, &upper1);
+		}
+
+		/*
+		 * If r1 && r2, we're done, otherwise we failed to find an overlap for
+		 * r2, so go to the next one.
+		 */
+		if (range_bounds_overlaps(rangetyp, &lower1, &upper1, &lower2, &upper2))
+			return true;
+	}
+
+	/* We looked through all of mr2 without finding an overlap */
+	return false;
+}
+
+/* does not extend to right of? */
+Datum
+range_overleft_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	RangeBound	lower1,
+				upper1,
+				lower2,
+				upper2;
+	bool		empty;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	range_deserialize(typcache->rngtype, r, &lower1, &upper1, &empty);
+	Assert(!empty);
+	multirange_get_bounds(typcache->rngtype, mr, mr->rangeCount - 1,
+						  &lower2, &upper2);
+
+	PG_RETURN_BOOL(range_cmp_bounds(typcache->rngtype, &upper1, &upper2) <= 0);
+}
+
+Datum
+multirange_overleft_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+	RangeBound	lower1,
+				upper1,
+				lower2,
+				upper2;
+	bool		empty;
+
+	if (MultirangeIsEmpty(mr) || RangeIsEmpty(r))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_get_bounds(typcache->rngtype, mr, mr->rangeCount - 1,
+						  &lower1, &upper1);
+	range_deserialize(typcache->rngtype, r, &lower2, &upper2, &empty);
+	Assert(!empty);
+
+	PG_RETURN_BOOL(range_cmp_bounds(typcache->rngtype, &upper1, &upper2) <= 0);
+}
+
+Datum
+multirange_overleft_multirange(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
+	MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	RangeBound	lower1,
+				upper1,
+				lower2,
+				upper2;
+
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr1));
+
+	multirange_get_bounds(typcache->rngtype, mr1, mr1->rangeCount - 1,
+						  &lower1, &upper1);
+	multirange_get_bounds(typcache->rngtype, mr2, mr2->rangeCount - 1,
+						  &lower2, &upper2);
+
+	PG_RETURN_BOOL(range_cmp_bounds(typcache->rngtype, &upper1, &upper2) <= 0);
+}
+
+/* does not extend to left of? */
+Datum
+range_overright_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	RangeBound	lower1,
+				upper1,
+				lower2,
+				upper2;
+	bool		empty;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	range_deserialize(typcache->rngtype, r, &lower1, &upper1, &empty);
+	Assert(!empty);
+	multirange_get_bounds(typcache->rngtype, mr, 0, &lower2, &upper2);
+
+	PG_RETURN_BOOL(range_cmp_bounds(typcache->rngtype, &lower1, &lower2) >= 0);
+}
+
+Datum
+multirange_overright_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+	RangeBound	lower1,
+				upper1,
+				lower2,
+				upper2;
+	bool		empty;
+
+	if (MultirangeIsEmpty(mr) || RangeIsEmpty(r))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_get_bounds(typcache->rngtype, mr, 0, &lower1, &upper1);
+	range_deserialize(typcache->rngtype, r, &lower2, &upper2, &empty);
+	Assert(!empty);
+
+	PG_RETURN_BOOL(range_cmp_bounds(typcache->rngtype, &lower1, &lower2) >= 0);
+}
+
+Datum
+multirange_overright_multirange(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
+	MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	RangeBound	lower1,
+				upper1,
+				lower2,
+				upper2;
+
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr1));
+
+	multirange_get_bounds(typcache->rngtype, mr1, 0, &lower1, &upper1);
+	multirange_get_bounds(typcache->rngtype, mr2, 0, &lower2, &upper2);
+
+	PG_RETURN_BOOL(range_cmp_bounds(typcache->rngtype, &lower1, &lower2) >= 0);
+}
+
+/* contains? */
+Datum
+multirange_contains_multirange(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_contains_multirange_internal(typcache, mr1, mr2));
+}
+
+/* contained by? */
+Datum
+multirange_contained_by_multirange(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_contains_multirange_internal(typcache, mr2, mr1));
+}
+
+/*
+ * Test whether multirange mr1 contains every range from another multirange mr2.
+ */
+bool
+multirange_contains_multirange_internal(TypeCacheEntry *typcache,
+										MultirangeType *mr1, MultirangeType *mr2)
+{
+	TypeCacheEntry *rangetyp;
+	int32		range_count1 = mr1->rangeCount;
+	int32		range_count2 = mr2->rangeCount;
+	int			i1,
+				i2;
+	RangeBound	lower1,
+				upper1,
+				lower2,
+				upper2;
+
+	rangetyp = typcache->rngtype;
+
+	/*
+	 * We follow the same logic for empties as ranges: - an empty multirange
+	 * contains an empty range/multirange. - an empty multirange can't contain
+	 * any other range/multirange. - an empty multirange is contained by any
+	 * other range/multirange.
+	 */
+
+	if (range_count2 == 0)
+		return true;
+	if (range_count1 == 0)
+		return false;
+
+	/*
+	 * Every range in mr2 must be contained by some range in mr1. To avoid
+	 * O(n^2) we walk through both ranges in tandem.
+	 */
+	i1 = 0;
+	multirange_get_bounds(typcache->rngtype, mr1, i1,
+						  &lower1, &upper1);
+	for (i2 = 0; i2 < range_count2; i2++)
+	{
+		multirange_get_bounds(typcache->rngtype, mr2, i2,
+							  &lower2, &upper2);
+
+		/* Discard r1s while r1 << r2 */
+		while (range_cmp_bounds(typcache->rngtype, &upper1, &lower2) < 0)
+		{
+			if (++i1 >= range_count1)
+				return false;
+			multirange_get_bounds(typcache->rngtype, mr1, i1,
+								  &lower1, &upper1);
+		}
+
+		/*
+		 * If r1 @> r2, go to the next r2, otherwise return false (since every
+		 * r1[n] and r1[n+1] must have a gap). Note this will give weird
+		 * answers if you don't canonicalize, e.g. with a custom
+		 * int2multirange {[1,1], [2,2]} there is a "gap". But that is
+		 * consistent with other range operators, e.g. '[1,1]'::int2range -|-
+		 * '[2,2]'::int2range is false.
+		 */
+		if (!range_bounds_contains(typcache->rngtype, &lower1, &upper1,
+								   &lower2, &upper2))
+			return false;
+	}
+
+	/* All ranges in mr2 are satisfied */
+	return true;
+}
+
+/* strictly left of? */
+Datum
+range_before_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(range_before_multirange_internal(typcache, r, mr));
+}
+
+Datum
+multirange_before_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(range_after_multirange_internal(typcache, r, mr));
+}
+
+Datum
+multirange_before_multirange(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_before_multirange_internal(typcache, mr1, mr2));
+}
+
+/* strictly right of? */
+Datum
+range_after_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(range_after_multirange_internal(typcache, r, mr));
+}
+
+Datum
+multirange_after_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(range_before_multirange_internal(typcache, r, mr));
+}
+
+Datum
+multirange_after_multirange(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_before_multirange_internal(typcache, mr2, mr1));
+}
+
+/* strictly left of? (internal version) */
+bool
+range_before_multirange_internal(TypeCacheEntry *typcache, RangeType *r,
+								 MultirangeType *mr)
+{
+	RangeBound	lower1,
+				upper1,
+				lower2,
+				upper2;
+	bool		empty;
+	int32		range_count;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		return false;
+
+	range_deserialize(typcache->rngtype, r, &lower1, &upper1, &empty);
+	Assert(!empty);
+
+	range_count = mr->rangeCount;
+	multirange_get_bounds(typcache->rngtype, mr, 0,
+						  &lower2, &upper2);
+
+	return (range_cmp_bounds(typcache->rngtype, &upper1, &lower2) < 0);
+}
+
+bool
+multirange_before_multirange_internal(TypeCacheEntry *typcache, MultirangeType *mr1,
+									  MultirangeType *mr2)
+{
+	RangeBound	lower1,
+				upper1,
+				lower2,
+				upper2;
+	int32		range_count1;
+	int32		range_count2;
+
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		return false;
+
+	range_count1 = mr1->rangeCount;
+	range_count2 = mr2->rangeCount;
+
+	multirange_get_bounds(typcache->rngtype, mr1, range_count1 - 1,
+						  &lower1, &upper1);
+	multirange_get_bounds(typcache->rngtype, mr2, 0,
+						  &lower2, &upper2);
+
+	return (range_cmp_bounds(typcache->rngtype, &upper1, &lower2) < 0);
+}
+
+/* strictly right of? (internal version) */
+bool
+range_after_multirange_internal(TypeCacheEntry *typcache, RangeType *r,
+								MultirangeType *mr)
+{
+	RangeBound	lower1,
+				upper1,
+				lower2,
+				upper2;
+	bool		empty;
+	int32		range_count;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		return false;
+
+	range_deserialize(typcache->rngtype, r, &lower1, &upper1, &empty);
+	Assert(!empty);
+
+	range_count = mr->rangeCount;
+	multirange_get_bounds(typcache->rngtype, mr, range_count - 1,
+						  &lower2, &upper2);
+
+	return (range_cmp_bounds(typcache->rngtype, &lower1, &upper2) > 0);
+}
+
+bool
+range_adjacent_multirange_internal(TypeCacheEntry *typcache, RangeType *r,
+								   MultirangeType *mr)
+{
+	RangeBound	lower1,
+				upper1,
+				lower2,
+				upper2;
+	bool		empty;
+	int32		range_count;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		return false;
+
+	range_deserialize(typcache->rngtype, r, &lower1, &upper1, &empty);
+	Assert(!empty);
+
+	range_count = mr->rangeCount;
+	multirange_get_bounds(typcache->rngtype, mr, 0,
+						  &lower2, &upper2);
+
+	if (bounds_adjacent(typcache->rngtype, upper1, lower2))
+		return true;
+
+	if (range_count > 1)
+		multirange_get_bounds(typcache->rngtype, mr, range_count - 1,
+							  &lower2, &upper2);
+
+	if (bounds_adjacent(typcache->rngtype, upper2, lower1))
+		return true;
+
+	return false;
+}
+
+/* adjacent to? */
+Datum
+range_adjacent_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(range_adjacent_multirange_internal(typcache, r, mr));
+}
+
+Datum
+multirange_adjacent_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		return false;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(range_adjacent_multirange_internal(typcache, r, mr));
+}
+
+Datum
+multirange_adjacent_multirange(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
+	MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count1;
+	int32		range_count2;
+	RangeBound	lower1,
+				upper1,
+				lower2,
+				upper2;
+
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		return false;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr1));
+
+	range_count1 = mr1->rangeCount;
+	range_count2 = mr2->rangeCount;
+	multirange_get_bounds(typcache->rngtype, mr1, range_count1 - 1,
+						  &lower1, &upper1);
+	multirange_get_bounds(typcache->rngtype, mr2, 0,
+						  &lower2, &upper2);
+	if (bounds_adjacent(typcache->rngtype, upper1, lower2))
+		PG_RETURN_BOOL(true);
+
+	if (range_count1 > 1)
+		multirange_get_bounds(typcache->rngtype, mr1, 0,
+							  &lower1, &upper1);
+	if (range_count2 > 1)
+		multirange_get_bounds(typcache->rngtype, mr2, range_count2 - 1,
+							  &lower2, &upper2);
+	if (bounds_adjacent(typcache->rngtype, upper2, lower1))
+		PG_RETURN_BOOL(true);
+	PG_RETURN_BOOL(false);
+}
+
+/* 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;
+	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));
+
+	range_count_1 = mr1->rangeCount;
+	range_count_2 = mr2->rangeCount;
+
+	/* Loop over source data */
+	range_count_max = Max(range_count_1, range_count_2);
+	for (i = 0; i < range_count_max; i++)
+	{
+		RangeBound	lower1,
+					upper1,
+					lower2,
+					upper2;
+
+		/*
+		 * 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;
+		}
+
+		multirange_get_bounds(typcache->rngtype, mr1, i, &lower1, &upper1);
+		multirange_get_bounds(typcache->rngtype, mr2, i, &lower2, &upper2);
+
+		cmp = range_cmp_bounds(typcache->rngtype, &lower1, &lower2);
+		if (cmp == 0)
+			cmp = range_cmp_bounds(typcache->rngtype, &upper1, &upper2);
+		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);
+}
+
+/* multirange -> range functions */
+
+/* Find the smallest range that includes everything in the multirange */
+Datum
+range_merge_from_multirange(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	Oid			mltrngtypoid = MultirangeTypeGetOid(mr);
+	TypeCacheEntry *typcache;
+	RangeType  *result;
+
+	typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
+
+	if (MultirangeIsEmpty(mr))
+	{
+		result = make_empty_range(typcache->rngtype);
+	}
+	else if (mr->rangeCount == 1)
+	{
+		result = multirange_get_range(typcache->rngtype, mr, 0);
+	}
+	else
+	{
+		RangeBound	firstLower,
+					firstUpper,
+					lastLower,
+					lastUpper;
+
+		multirange_get_bounds(typcache->rngtype, mr, 0,
+							  &firstLower, &firstUpper);
+		multirange_get_bounds(typcache->rngtype, mr, mr->rangeCount - 1,
+							  &lastLower, &lastUpper);
+
+		result = make_range(typcache->rngtype, &firstLower, &lastUpper, false);
+	}
+
+	PG_RETURN_RANGE_P(result);
+}
+
+/* Hash support */
+
+/* hash a multirange value */
+Datum
+hash_multirange(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	uint32		result = 1;
+	TypeCacheEntry *typcache,
+			   *scache;
+	int32		range_count,
+				i;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+	scache = typcache->rngtype->rngelemtype;
+	if (!OidIsValid(scache->hash_proc_finfo.fn_oid))
+	{
+		scache = lookup_type_cache(scache->type_id,
+								   TYPECACHE_HASH_PROC_FINFO);
+		if (!OidIsValid(scache->hash_proc_finfo.fn_oid))
+			ereport(ERROR,
+					(errcode(ERRCODE_UNDEFINED_FUNCTION),
+					 errmsg("could not identify a hash function for type %s",
+							format_type_be(scache->type_id))));
+	}
+
+	range_count = mr->rangeCount;
+	for (i = 0; i < range_count; i++)
+	{
+		RangeBound	lower,
+					upper;
+		uint8		flags = MultirangeGetFlagsPtr(mr)[i];
+		uint32		lower_hash;
+		uint32		upper_hash;
+		uint32		range_hash;
+
+		multirange_get_bounds(typcache->rngtype, mr, i, &lower, &upper);
+
+		if (RANGE_HAS_LBOUND(flags))
+			lower_hash = DatumGetUInt32(FunctionCall1Coll(&scache->hash_proc_finfo,
+														  typcache->rngtype->rng_collation,
+														  lower.val));
+		else
+			lower_hash = 0;
+
+		if (RANGE_HAS_UBOUND(flags))
+			upper_hash = DatumGetUInt32(FunctionCall1Coll(&scache->hash_proc_finfo,
+														  typcache->rngtype->rng_collation,
+														  upper.val));
+		else
+			upper_hash = 0;
+
+		/* Merge hashes of flags and bounds */
+		range_hash = hash_uint32((uint32) flags);
+		range_hash ^= lower_hash;
+		range_hash = (range_hash << 1) | (range_hash >> 31);
+		range_hash ^= upper_hash;
+
+		/*
+		 * Use the same approach as hash_array to combine the individual
+		 * elements' hash values:
+		 */
+		result = (result << 5) - result + range_hash;
+	}
+
+	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,
+			   *scache;
+	int32		range_count,
+				i;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+	scache = typcache->rngtype->rngelemtype;
+	if (!OidIsValid(scache->hash_extended_proc_finfo.fn_oid))
+	{
+		scache = lookup_type_cache(scache->type_id,
+								   TYPECACHE_HASH_EXTENDED_PROC_FINFO);
+		if (!OidIsValid(scache->hash_extended_proc_finfo.fn_oid))
+			ereport(ERROR,
+					(errcode(ERRCODE_UNDEFINED_FUNCTION),
+					 errmsg("could not identify a hash function for type %s",
+							format_type_be(scache->type_id))));
+	}
+
+	range_count = mr->rangeCount;
+	for (i = 0; i < range_count; i++)
+	{
+		RangeBound	lower,
+					upper;
+		uint8		flags = MultirangeGetFlagsPtr(mr)[i];
+		uint64		lower_hash;
+		uint64		upper_hash;
+		uint64		range_hash;
+
+		multirange_get_bounds(typcache->rngtype, mr, i, &lower, &upper);
+
+		if (RANGE_HAS_LBOUND(flags))
+			lower_hash = DatumGetUInt64(FunctionCall2Coll(&scache->hash_extended_proc_finfo,
+														  typcache->rngtype->rng_collation,
+														  lower.val,
+														  seed));
+		else
+			lower_hash = 0;
+
+		if (RANGE_HAS_UBOUND(flags))
+			upper_hash = DatumGetUInt64(FunctionCall2Coll(&scache->hash_extended_proc_finfo,
+														  typcache->rngtype->rng_collation,
+														  upper.val,
+														  seed));
+		else
+			upper_hash = 0;
+
+		/* Merge hashes of flags and bounds */
+		range_hash = DatumGetUInt64(hash_uint32_extended((uint32) flags,
+														 DatumGetInt64(seed)));
+		range_hash ^= lower_hash;
+		range_hash = ROTATE_HIGH_AND_LOW_32BITS(range_hash);
+		range_hash ^= upper_hash;
+
+		/*
+		 * Use the same approach as hash_array to combine the individual
+		 * elements' hash values:
+		 */
+		result = (result << 5) - result + range_hash;
+	}
+
+	PG_FREE_IF_COPY(mr, 0);
+
+	PG_RETURN_UINT64(result);
+}
diff --git a/src/backend/utils/adt/multirangetypes_selfuncs.c b/src/backend/utils/adt/multirangetypes_selfuncs.c
new file mode 100644
index 00000000000..b1e56841ccd
--- /dev/null
+++ b/src/backend/utils/adt/multirangetypes_selfuncs.c
@@ -0,0 +1,1307 @@
+/*-------------------------------------------------------------------------
+ *
+ * multirangetypes_selfuncs.c
+ *	  Functions for selectivity estimation of multirange operators
+ *
+ * Estimates are based on histograms of lower and upper bounds, and the
+ * fraction of empty multiranges.
+ *
+ * Portions Copyright (c) 1996-2020, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ *	  src/backend/utils/adt/multirangetypes_selfuncs.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include <math.h>
+
+#include "access/htup_details.h"
+#include "catalog/pg_operator.h"
+#include "catalog/pg_statistic.h"
+#include "catalog/pg_type.h"
+#include "utils/float.h"
+#include "utils/fmgrprotos.h"
+#include "utils/lsyscache.h"
+#include "utils/rangetypes.h"
+#include "utils/multirangetypes.h"
+#include "utils/selfuncs.h"
+#include "utils/typcache.h"
+
+static double calc_multirangesel(TypeCacheEntry *typcache, VariableStatData *vardata,
+								 const MultirangeType *constval, Oid operator);
+static double default_multirange_selectivity(Oid operator);
+static double default_multirange_selectivity(Oid operator);
+static double calc_hist_selectivity(TypeCacheEntry *typcache,
+									VariableStatData *vardata, const MultirangeType *constval,
+									Oid operator);
+static double calc_hist_selectivity_scalar(TypeCacheEntry *typcache,
+										   const RangeBound *constbound,
+										   const RangeBound *hist, int hist_nvalues,
+										   bool equal);
+static int	rbound_bsearch(TypeCacheEntry *typcache, const RangeBound *value,
+						   const RangeBound *hist, int hist_length, bool equal);
+static float8 get_position(TypeCacheEntry *typcache, const RangeBound *value,
+						   const RangeBound *hist1, const RangeBound *hist2);
+static float8 get_len_position(double value, double hist1, double hist2);
+static float8 get_distance(TypeCacheEntry *typcache, const RangeBound *bound1,
+						   const RangeBound *bound2);
+static int	length_hist_bsearch(Datum *length_hist_values,
+								int length_hist_nvalues, double value, bool equal);
+static double calc_length_hist_frac(Datum *length_hist_values,
+									int length_hist_nvalues, double length1, double length2, bool equal);
+static double calc_hist_selectivity_contained(TypeCacheEntry *typcache,
+											  const RangeBound *lower, RangeBound *upper,
+											  const RangeBound *hist_lower, int hist_nvalues,
+											  Datum *length_hist_values, int length_hist_nvalues);
+static double calc_hist_selectivity_contains(TypeCacheEntry *typcache,
+											 const RangeBound *lower, const RangeBound *upper,
+											 const RangeBound *hist_lower, int hist_nvalues,
+											 Datum *length_hist_values, int length_hist_nvalues);
+
+/*
+ * Returns a default selectivity estimate for given operator, when we don't
+ * have statistics or cannot use them for some reason.
+ */
+static double
+default_multirange_selectivity(Oid operator)
+{
+	switch (operator)
+	{
+		case OID_MULTIRANGE_OVERLAPS_MULTIRANGE_OP:
+		case OID_MULTIRANGE_OVERLAPS_RANGE_OP:
+		case OID_RANGE_OVERLAPS_MULTIRANGE_OP:
+			return 0.01;
+
+		case OID_MULTIRANGE_CONTAINS_RANGE_OP:
+		case OID_MULTIRANGE_CONTAINS_MULTIRANGE_OP:
+		case OID_MULTIRANGE_RANGE_CONTAINED_OP:
+		case OID_MULTIRANGE_MULTIRANGE_CONTAINED_OP:
+			return 0.005;
+
+		case OID_MULTIRANGE_CONTAINS_ELEM_OP:
+		case OID_MULTIRANGE_ELEM_CONTAINED_OP:
+
+			/*
+			 * "multirange @> elem" is more or less identical to a scalar
+			 * inequality "A >= b AND A <= c".
+			 */
+			return DEFAULT_MULTIRANGE_INEQ_SEL;
+
+		case OID_MULTIRANGE_LESS_OP:
+		case OID_MULTIRANGE_LESS_EQUAL_OP:
+		case OID_MULTIRANGE_GREATER_OP:
+		case OID_MULTIRANGE_GREATER_EQUAL_OP:
+		case OID_MULTIRANGE_LEFT_RANGE_OP:
+		case OID_MULTIRANGE_LEFT_MULTIRANGE_OP:
+		case OID_RANGE_LEFT_MULTIRANGE_OP:
+		case OID_MULTIRANGE_RIGHT_RANGE_OP:
+		case OID_MULTIRANGE_RIGHT_MULTIRANGE_OP:
+		case OID_RANGE_RIGHT_MULTIRANGE_OP:
+		case OID_MULTIRANGE_OVERLAPS_LEFT_RANGE_OP:
+		case OID_RANGE_OVERLAPS_LEFT_MULTIRANGE_OP:
+		case OID_MULTIRANGE_OVERLAPS_LEFT_MULTIRANGE_OP:
+		case OID_MULTIRANGE_OVERLAPS_RIGHT_RANGE_OP:
+		case OID_RANGE_OVERLAPS_RIGHT_MULTIRANGE_OP:
+		case OID_MULTIRANGE_OVERLAPS_RIGHT_MULTIRANGE_OP:
+			/* these are similar to regular scalar inequalities */
+			return DEFAULT_INEQ_SEL;
+
+		default:
+
+			/*
+			 * all multirange operators should be handled above, but just in
+			 * case
+			 */
+			return 0.01;
+	}
+}
+
+/*
+ * multirangesel -- restriction selectivity for multirange operators
+ */
+Datum
+multirangesel(PG_FUNCTION_ARGS)
+{
+	PlannerInfo *root = (PlannerInfo *) PG_GETARG_POINTER(0);
+	Oid			operator = PG_GETARG_OID(1);
+	List	   *args = (List *) PG_GETARG_POINTER(2);
+	int			varRelid = PG_GETARG_INT32(3);
+	VariableStatData vardata;
+	Node	   *other;
+	bool		varonleft;
+	Selectivity selec;
+	TypeCacheEntry *typcache = NULL;
+	MultirangeType *constmultirange = NULL;
+	RangeType  *constrange = NULL;
+
+	/*
+	 * If expression is not (variable op something) or (something op
+	 * variable), then punt and return a default estimate.
+	 */
+	if (!get_restriction_variable(root, args, varRelid,
+								  &vardata, &other, &varonleft))
+		PG_RETURN_FLOAT8(default_multirange_selectivity(operator));
+
+	/*
+	 * Can't do anything useful if the something is not a constant, either.
+	 */
+	if (!IsA(other, Const))
+	{
+		ReleaseVariableStats(vardata);
+		PG_RETURN_FLOAT8(default_multirange_selectivity(operator));
+	}
+
+	/*
+	 * All the multirange operators are strict, so we can cope with a NULL
+	 * constant right away.
+	 */
+	if (((Const *) other)->constisnull)
+	{
+		ReleaseVariableStats(vardata);
+		PG_RETURN_FLOAT8(0.0);
+	}
+
+	/*
+	 * If var is on the right, commute the operator, so that we can assume the
+	 * var is on the left in what follows.
+	 */
+	if (!varonleft)
+	{
+		/* we have other Op var, commute to make var Op other */
+		operator = get_commutator(operator);
+		if (!operator)
+		{
+			/* Use default selectivity (should we raise an error instead?) */
+			ReleaseVariableStats(vardata);
+			PG_RETURN_FLOAT8(default_multirange_selectivity(operator));
+		}
+	}
+
+	/*
+	 * OK, there's a Var and a Const we're dealing with here.  We need the
+	 * Const to be of same multirange type as the column, else we can't do
+	 * anything useful. (Such cases will likely fail at runtime, but here we'd
+	 * rather just return a default estimate.)
+	 *
+	 * If the operator is "multirange @> element", the constant should be of
+	 * the element type of the multirange column. Convert it to a multirange
+	 * that includes only that single point, so that we don't need special
+	 * handling for that in what follows.
+	 */
+	if (operator == OID_MULTIRANGE_CONTAINS_ELEM_OP)
+	{
+		typcache = multirange_get_typcache(fcinfo, vardata.vartype);
+
+		if (((Const *) other)->consttype == typcache->rngtype->rngelemtype->type_id)
+		{
+			RangeBound	lower,
+						upper;
+
+			lower.inclusive = true;
+			lower.val = ((Const *) other)->constvalue;
+			lower.infinite = false;
+			lower.lower = true;
+			upper.inclusive = true;
+			upper.val = ((Const *) other)->constvalue;
+			upper.infinite = false;
+			upper.lower = false;
+			constrange = range_serialize(typcache->rngtype, &lower, &upper, false);
+			constmultirange = make_multirange(typcache->type_id, typcache->rngtype,
+											  1, &constrange);
+		}
+	}
+	else if (operator == OID_MULTIRANGE_CONTAINS_RANGE_OP ||
+			 operator == OID_MULTIRANGE_OVERLAPS_RANGE_OP ||
+			 operator == OID_MULTIRANGE_OVERLAPS_LEFT_RANGE_OP ||
+			 operator == OID_MULTIRANGE_OVERLAPS_RIGHT_RANGE_OP ||
+			 operator == OID_MULTIRANGE_LEFT_RANGE_OP ||
+			 operator == OID_MULTIRANGE_RIGHT_RANGE_OP)
+	{
+		/*
+		 * Promote a range in "multirange OP range" just like we do an element
+		 * in "multirange OP element".
+		 */
+		typcache = multirange_get_typcache(fcinfo, vardata.vartype);
+		if (((Const *) other)->consttype == typcache->rngtype->type_id)
+		{
+			constrange = DatumGetRangeTypeP(((Const *) other)->constvalue);
+			constmultirange = make_multirange(typcache->type_id, typcache->rngtype,
+											  1, &constrange);
+		}
+	}
+	else if (operator == OID_RANGE_OVERLAPS_MULTIRANGE_OP ||
+			 operator == OID_RANGE_OVERLAPS_LEFT_MULTIRANGE_OP ||
+			 operator == OID_RANGE_OVERLAPS_RIGHT_MULTIRANGE_OP ||
+			 operator == OID_RANGE_LEFT_MULTIRANGE_OP ||
+			 operator == OID_RANGE_RIGHT_MULTIRANGE_OP ||
+			 operator == OID_MULTIRANGE_ELEM_CONTAINED_OP ||
+			 operator == OID_MULTIRANGE_RANGE_CONTAINED_OP)
+	{
+		/*
+		 * Here, the Var is the elem/range, not the multirange.  For now we
+		 * just punt and return the default estimate.  In future we could
+		 * disassemble the multirange constant to do something more
+		 * intelligent.
+		 */
+	}
+	else if (((Const *) other)->consttype == vardata.vartype)
+	{
+		/* Both sides are the same multirange type */
+		typcache = multirange_get_typcache(fcinfo, vardata.vartype);
+
+		constmultirange = DatumGetMultirangeTypeP(((Const *) other)->constvalue);
+	}
+
+	/*
+	 * If we got a valid constant on one side of the operator, proceed to
+	 * estimate using statistics. Otherwise punt and return a default constant
+	 * estimate.  Note that calc_multirangesel need not handle
+	 * OID_MULTIRANGE_*_CONTAINED_OP.
+	 */
+	if (constmultirange)
+		selec = calc_multirangesel(typcache, &vardata, constmultirange, operator);
+	else
+		selec = default_multirange_selectivity(operator);
+
+	ReleaseVariableStats(vardata);
+
+	CLAMP_PROBABILITY(selec);
+
+	PG_RETURN_FLOAT8((float8) selec);
+}
+
+static double
+calc_multirangesel(TypeCacheEntry *typcache, VariableStatData *vardata,
+				   const MultirangeType *constval, Oid operator)
+{
+	double		hist_selec;
+	double		selec;
+	float4		empty_frac,
+				null_frac;
+
+	/*
+	 * First look up the fraction of NULLs and empty multiranges from
+	 * pg_statistic.
+	 */
+	if (HeapTupleIsValid(vardata->statsTuple))
+	{
+		Form_pg_statistic stats;
+		AttStatsSlot sslot;
+
+		stats = (Form_pg_statistic) GETSTRUCT(vardata->statsTuple);
+		null_frac = stats->stanullfrac;
+
+		/* Try to get fraction of empty multiranges */
+		if (get_attstatsslot(&sslot, vardata->statsTuple,
+							 STATISTIC_KIND_RANGE_LENGTH_HISTOGRAM,
+							 InvalidOid,
+							 ATTSTATSSLOT_NUMBERS))
+		{
+			if (sslot.nnumbers != 1)
+				elog(ERROR, "invalid empty fraction statistic");	/* shouldn't happen */
+			empty_frac = sslot.numbers[0];
+			free_attstatsslot(&sslot);
+		}
+		else
+		{
+			/* No empty fraction statistic. Assume no empty ranges. */
+			empty_frac = 0.0;
+		}
+	}
+	else
+	{
+		/*
+		 * No stats are available. Follow through the calculations below
+		 * anyway, assuming no NULLs and no empty multiranges. This still
+		 * allows us to give a better-than-nothing estimate based on whether
+		 * the constant is an empty multirange or not.
+		 */
+		null_frac = 0.0;
+		empty_frac = 0.0;
+	}
+
+	if (MultirangeIsEmpty(constval))
+	{
+		/*
+		 * An empty multirange matches all multiranges, all empty multiranges,
+		 * or nothing, depending on the operator
+		 */
+		switch (operator)
+		{
+				/* these return false if either argument is empty */
+			case OID_RANGE_OVERLAPS_MULTIRANGE_OP:
+			case OID_MULTIRANGE_OVERLAPS_RANGE_OP:
+			case OID_MULTIRANGE_OVERLAPS_MULTIRANGE_OP:
+			case OID_RANGE_OVERLAPS_LEFT_MULTIRANGE_OP:
+			case OID_MULTIRANGE_OVERLAPS_LEFT_RANGE_OP:
+			case OID_MULTIRANGE_OVERLAPS_LEFT_MULTIRANGE_OP:
+			case OID_RANGE_OVERLAPS_RIGHT_MULTIRANGE_OP:
+			case OID_MULTIRANGE_OVERLAPS_RIGHT_RANGE_OP:
+			case OID_MULTIRANGE_OVERLAPS_RIGHT_MULTIRANGE_OP:
+			case OID_MULTIRANGE_LEFT_MULTIRANGE_OP:
+			case OID_MULTIRANGE_RIGHT_MULTIRANGE_OP:
+				/* nothing is less than an empty multirange */
+			case OID_MULTIRANGE_LESS_OP:
+				selec = 0.0;
+				break;
+
+				/*
+				 * only empty multiranges can be contained by an empty
+				 * multirange
+				 */
+			case OID_MULTIRANGE_RANGE_CONTAINED_OP:
+			case OID_MULTIRANGE_MULTIRANGE_CONTAINED_OP:
+				/* only empty ranges are <= an empty multirange */
+			case OID_MULTIRANGE_LESS_EQUAL_OP:
+				selec = empty_frac;
+				break;
+
+				/* everything contains an empty multirange */
+			case OID_MULTIRANGE_CONTAINS_RANGE_OP:
+			case OID_MULTIRANGE_CONTAINS_MULTIRANGE_OP:
+				/* everything is >= an empty multirange */
+			case OID_MULTIRANGE_GREATER_EQUAL_OP:
+				selec = 1.0;
+				break;
+
+				/* all non-empty multiranges are > an empty multirange */
+			case OID_MULTIRANGE_GREATER_OP:
+				selec = 1.0 - empty_frac;
+				break;
+
+				/* an element cannot be empty */
+			case OID_MULTIRANGE_ELEM_CONTAINED_OP:
+			case OID_MULTIRANGE_CONTAINS_ELEM_OP:
+			default:
+				elog(ERROR, "unexpected operator %u", operator);
+				selec = 0.0;	/* keep compiler quiet */
+				break;
+		}
+	}
+	else
+	{
+		/*
+		 * Calculate selectivity using bound histograms. If that fails for
+		 * some reason, e.g no histogram in pg_statistic, use the default
+		 * constant estimate for the fraction of non-empty values. This is
+		 * still somewhat better than just returning the default estimate,
+		 * because this still takes into account the fraction of empty and
+		 * NULL tuples, if we had statistics for them.
+		 */
+		hist_selec = calc_hist_selectivity(typcache, vardata, constval,
+										   operator);
+		if (hist_selec < 0.0)
+			hist_selec = default_multirange_selectivity(operator);
+
+		/*
+		 * Now merge the results for the empty multiranges and histogram
+		 * calculations, realizing that the histogram covers only the
+		 * non-null, non-empty values.
+		 */
+		if (operator == OID_MULTIRANGE_ELEM_CONTAINED_OP ||
+			operator == OID_MULTIRANGE_RANGE_CONTAINED_OP ||
+			operator == OID_MULTIRANGE_MULTIRANGE_CONTAINED_OP)
+		{
+			/* empty is contained by anything non-empty */
+			selec = (1.0 - empty_frac) * hist_selec + empty_frac;
+		}
+		else
+		{
+			/* with any other operator, empty Op non-empty matches nothing */
+			selec = (1.0 - empty_frac) * hist_selec;
+		}
+	}
+
+	/* all multirange operators are strict */
+	selec *= (1.0 - null_frac);
+
+	/* result should be in range, but make sure... */
+	CLAMP_PROBABILITY(selec);
+
+	return selec;
+}
+
+/*
+ * Calculate multirange operator selectivity using histograms of multirange bounds.
+ *
+ * This estimate is for the portion of values that are not empty and not
+ * NULL.
+ */
+static double
+calc_hist_selectivity(TypeCacheEntry *typcache, VariableStatData *vardata,
+					  const MultirangeType *constval, Oid operator)
+{
+	TypeCacheEntry *rng_typcache = typcache->rngtype;
+	AttStatsSlot hslot;
+	AttStatsSlot lslot;
+	int			nhist;
+	RangeBound *hist_lower;
+	RangeBound *hist_upper;
+	int			i;
+	RangeBound	const_lower;
+	RangeBound	const_upper;
+	bool		empty;
+	double		hist_selec;
+	int			range_count;
+	RangeType **ranges;
+	RangeType  *constrange;
+
+	/* Can't use the histogram with insecure multirange support functions */
+	if (!statistic_proc_security_check(vardata,
+									   rng_typcache->rng_cmp_proc_finfo.fn_oid))
+		return -1;
+	if (OidIsValid(rng_typcache->rng_subdiff_finfo.fn_oid) &&
+		!statistic_proc_security_check(vardata,
+									   rng_typcache->rng_subdiff_finfo.fn_oid))
+		return -1;
+
+	/* Try to get histogram of ranges */
+	if (!(HeapTupleIsValid(vardata->statsTuple) &&
+		  get_attstatsslot(&hslot, vardata->statsTuple,
+						   STATISTIC_KIND_BOUNDS_HISTOGRAM, InvalidOid,
+						   ATTSTATSSLOT_VALUES)))
+		return -1.0;
+
+	/* check that it's a histogram, not just a dummy entry */
+	if (hslot.nvalues < 2)
+	{
+		free_attstatsslot(&hslot);
+		return -1.0;
+	}
+
+	/*
+	 * Convert histogram of ranges into histograms of its lower and upper
+	 * bounds.
+	 */
+	nhist = hslot.nvalues;
+	hist_lower = (RangeBound *) palloc(sizeof(RangeBound) * nhist);
+	hist_upper = (RangeBound *) palloc(sizeof(RangeBound) * nhist);
+	for (i = 0; i < nhist; i++)
+	{
+		range_deserialize(rng_typcache, DatumGetRangeTypeP(hslot.values[i]),
+						  &hist_lower[i], &hist_upper[i], &empty);
+		/* The histogram should not contain any empty ranges */
+		if (empty)
+			elog(ERROR, "bounds histogram contains an empty range");
+	}
+
+	/* @> and @< also need a histogram of range lengths */
+	if (operator == OID_MULTIRANGE_CONTAINS_RANGE_OP ||
+		operator == OID_MULTIRANGE_CONTAINS_MULTIRANGE_OP ||
+		operator == OID_MULTIRANGE_RANGE_CONTAINED_OP ||
+		operator == OID_MULTIRANGE_MULTIRANGE_CONTAINED_OP)
+	{
+		if (!(HeapTupleIsValid(vardata->statsTuple) &&
+			  get_attstatsslot(&lslot, vardata->statsTuple,
+							   STATISTIC_KIND_RANGE_LENGTH_HISTOGRAM,
+							   InvalidOid,
+							   ATTSTATSSLOT_VALUES)))
+		{
+			free_attstatsslot(&hslot);
+			return -1.0;
+		}
+
+		/* check that it's a histogram, not just a dummy entry */
+		if (lslot.nvalues < 2)
+		{
+			free_attstatsslot(&lslot);
+			free_attstatsslot(&hslot);
+			return -1.0;
+		}
+	}
+	else
+		memset(&lslot, 0, sizeof(lslot));
+
+	/* Extract the bounds of the constant value. */
+	multirange_deserialize(rng_typcache, constval, &range_count, &ranges);
+	Assert(range_count > 0);
+	constrange = range_union_internal(rng_typcache, ranges[0],
+									  ranges[range_count - 1], false);
+	range_deserialize(rng_typcache, constrange, &const_lower, &const_upper, &empty);
+
+	/*
+	 * Calculate selectivity comparing the lower or upper bound of the
+	 * constant with the histogram of lower or upper bounds.
+	 */
+	switch (operator)
+	{
+		case OID_MULTIRANGE_LESS_OP:
+
+			/*
+			 * The regular b-tree comparison operators (<, <=, >, >=) compare
+			 * the lower bounds first, and the upper bounds for values with
+			 * equal lower bounds. Estimate that by comparing the lower bounds
+			 * only. This gives a fairly accurate estimate assuming there
+			 * aren't many rows with a lower bound equal to the constant's
+			 * lower bound.
+			 */
+			hist_selec =
+				calc_hist_selectivity_scalar(rng_typcache, &const_lower,
+											 hist_lower, nhist, false);
+			break;
+
+		case OID_MULTIRANGE_LESS_EQUAL_OP:
+			hist_selec =
+				calc_hist_selectivity_scalar(rng_typcache, &const_lower,
+											 hist_lower, nhist, true);
+			break;
+
+		case OID_MULTIRANGE_GREATER_OP:
+			hist_selec =
+				1 - calc_hist_selectivity_scalar(rng_typcache, &const_lower,
+												 hist_lower, nhist, false);
+			break;
+
+		case OID_MULTIRANGE_GREATER_EQUAL_OP:
+			hist_selec =
+				1 - calc_hist_selectivity_scalar(rng_typcache, &const_lower,
+												 hist_lower, nhist, true);
+			break;
+
+		case OID_RANGE_LEFT_MULTIRANGE_OP:
+		case OID_MULTIRANGE_LEFT_RANGE_OP:
+		case OID_MULTIRANGE_LEFT_MULTIRANGE_OP:
+			/* var << const when upper(var) < lower(const) */
+			hist_selec =
+				calc_hist_selectivity_scalar(rng_typcache, &const_lower,
+											 hist_upper, nhist, false);
+			break;
+
+		case OID_RANGE_RIGHT_MULTIRANGE_OP:
+		case OID_MULTIRANGE_RIGHT_RANGE_OP:
+		case OID_MULTIRANGE_RIGHT_MULTIRANGE_OP:
+			/* var >> const when lower(var) > upper(const) */
+			hist_selec =
+				1 - calc_hist_selectivity_scalar(rng_typcache, &const_upper,
+												 hist_lower, nhist, true);
+			break;
+
+		case OID_RANGE_OVERLAPS_RIGHT_MULTIRANGE_OP:
+		case OID_MULTIRANGE_OVERLAPS_RIGHT_RANGE_OP:
+		case OID_MULTIRANGE_OVERLAPS_RIGHT_MULTIRANGE_OP:
+			/* compare lower bounds */
+			hist_selec =
+				1 - calc_hist_selectivity_scalar(rng_typcache, &const_lower,
+												 hist_lower, nhist, false);
+			break;
+
+		case OID_RANGE_OVERLAPS_LEFT_MULTIRANGE_OP:
+		case OID_MULTIRANGE_OVERLAPS_LEFT_RANGE_OP:
+		case OID_MULTIRANGE_OVERLAPS_LEFT_MULTIRANGE_OP:
+			/* compare upper bounds */
+			hist_selec =
+				calc_hist_selectivity_scalar(rng_typcache, &const_upper,
+											 hist_upper, nhist, true);
+			break;
+
+		case OID_RANGE_OVERLAPS_MULTIRANGE_OP:
+		case OID_MULTIRANGE_OVERLAPS_RANGE_OP:
+		case OID_MULTIRANGE_OVERLAPS_MULTIRANGE_OP:
+		case OID_MULTIRANGE_CONTAINS_ELEM_OP:
+
+			/*
+			 * A && B <=> NOT (A << B OR A >> B).
+			 *
+			 * Since A << B and A >> B are mutually exclusive events we can
+			 * sum their probabilities to find probability of (A << B OR A >>
+			 * B).
+			 *
+			 * "multirange @> elem" is equivalent to "multirange &&
+			 * {[elem,elem]}". The caller already constructed the singular
+			 * range from the element constant, so just treat it the same as
+			 * &&.
+			 */
+			hist_selec =
+				calc_hist_selectivity_scalar(rng_typcache, &const_lower, hist_upper,
+											 nhist, false);
+			hist_selec +=
+				(1.0 - calc_hist_selectivity_scalar(rng_typcache, &const_upper, hist_lower,
+													nhist, true));
+			hist_selec = 1.0 - hist_selec;
+			break;
+
+		case OID_MULTIRANGE_CONTAINS_RANGE_OP:
+		case OID_MULTIRANGE_CONTAINS_MULTIRANGE_OP:
+			hist_selec =
+				calc_hist_selectivity_contains(rng_typcache, &const_lower,
+											   &const_upper, hist_lower, nhist,
+											   lslot.values, lslot.nvalues);
+			break;
+
+		case OID_MULTIRANGE_RANGE_CONTAINED_OP:
+		case OID_MULTIRANGE_MULTIRANGE_CONTAINED_OP:
+			if (const_lower.infinite)
+			{
+				/*
+				 * Lower bound no longer matters. Just estimate the fraction
+				 * with an upper bound <= const upper bound
+				 */
+				hist_selec =
+					calc_hist_selectivity_scalar(rng_typcache, &const_upper,
+												 hist_upper, nhist, true);
+			}
+			else if (const_upper.infinite)
+			{
+				hist_selec =
+					1.0 - calc_hist_selectivity_scalar(rng_typcache, &const_lower,
+													   hist_lower, nhist, false);
+			}
+			else
+			{
+				hist_selec =
+					calc_hist_selectivity_contained(rng_typcache, &const_lower,
+													&const_upper, hist_lower, nhist,
+													lslot.values, lslot.nvalues);
+			}
+			break;
+
+		default:
+			elog(ERROR, "unknown multirange operator %u", operator);
+			hist_selec = -1.0;	/* keep compiler quiet */
+			break;
+	}
+
+	free_attstatsslot(&lslot);
+	free_attstatsslot(&hslot);
+
+	return hist_selec;
+}
+
+
+/*
+ * Look up the fraction of values less than (or equal, if 'equal' argument
+ * is true) a given const in a histogram of range bounds.
+ */
+static double
+calc_hist_selectivity_scalar(TypeCacheEntry *typcache, const RangeBound *constbound,
+							 const RangeBound *hist, int hist_nvalues, bool equal)
+{
+	Selectivity selec;
+	int			index;
+
+	/*
+	 * Find the histogram bin the given constant falls into. Estimate
+	 * selectivity as the number of preceding whole bins.
+	 */
+	index = rbound_bsearch(typcache, constbound, hist, hist_nvalues, equal);
+	selec = (Selectivity) (Max(index, 0)) / (Selectivity) (hist_nvalues - 1);
+
+	/* Adjust using linear interpolation within the bin */
+	if (index >= 0 && index < hist_nvalues - 1)
+		selec += get_position(typcache, constbound, &hist[index],
+							  &hist[index + 1]) / (Selectivity) (hist_nvalues - 1);
+
+	return selec;
+}
+
+/*
+ * Binary search on an array of range bounds. Returns greatest index of range
+ * bound in array which is less(less or equal) than given range bound. If all
+ * range bounds in array are greater or equal(greater) than given range bound,
+ * return -1. When "equal" flag is set conditions in brackets are used.
+ *
+ * This function is used in scalar operator selectivity estimation. Another
+ * goal of this function is to find a histogram bin where to stop
+ * interpolation of portion of bounds which are less or equal to given bound.
+ */
+static int
+rbound_bsearch(TypeCacheEntry *typcache, const RangeBound *value, const RangeBound *hist,
+			   int hist_length, bool equal)
+{
+	int			lower = -1,
+				upper = hist_length - 1,
+				cmp,
+				middle;
+
+	while (lower < upper)
+	{
+		middle = (lower + upper + 1) / 2;
+		cmp = range_cmp_bounds(typcache, &hist[middle], value);
+
+		if (cmp < 0 || (equal && cmp == 0))
+			lower = middle;
+		else
+			upper = middle - 1;
+	}
+	return lower;
+}
+
+
+/*
+ * Binary search on length histogram. Returns greatest index of range length in
+ * histogram which is less than (less than or equal) the given length value. If
+ * all lengths in the histogram are greater than (greater than or equal) the
+ * given length, returns -1.
+ */
+static int
+length_hist_bsearch(Datum *length_hist_values, int length_hist_nvalues,
+					double value, bool equal)
+{
+	int			lower = -1,
+				upper = length_hist_nvalues - 1,
+				middle;
+
+	while (lower < upper)
+	{
+		double		middleval;
+
+		middle = (lower + upper + 1) / 2;
+
+		middleval = DatumGetFloat8(length_hist_values[middle]);
+		if (middleval < value || (equal && middleval <= value))
+			lower = middle;
+		else
+			upper = middle - 1;
+	}
+	return lower;
+}
+
+/*
+ * Get relative position of value in histogram bin in [0,1] range.
+ */
+static float8
+get_position(TypeCacheEntry *typcache, const RangeBound *value, const RangeBound *hist1,
+			 const RangeBound *hist2)
+{
+	bool		has_subdiff = OidIsValid(typcache->rng_subdiff_finfo.fn_oid);
+	float8		position;
+
+	if (!hist1->infinite && !hist2->infinite)
+	{
+		float8		bin_width;
+
+		/*
+		 * Both bounds are finite. Assuming the subtype's comparison function
+		 * works sanely, the value must be finite, too, because it lies
+		 * somewhere between the bounds.  If it doesn't, arbitrarily return
+		 * 0.5.
+		 */
+		if (value->infinite)
+			return 0.5;
+
+		/* Can't interpolate without subdiff function */
+		if (!has_subdiff)
+			return 0.5;
+
+		/* Calculate relative position using subdiff function. */
+		bin_width = DatumGetFloat8(FunctionCall2Coll(&typcache->rng_subdiff_finfo,
+													 typcache->rng_collation,
+													 hist2->val,
+													 hist1->val));
+		if (isnan(bin_width) || bin_width <= 0.0)
+			return 0.5;			/* punt for NaN or zero-width bin */
+
+		position = DatumGetFloat8(FunctionCall2Coll(&typcache->rng_subdiff_finfo,
+													typcache->rng_collation,
+													value->val,
+													hist1->val))
+			/ bin_width;
+
+		if (isnan(position))
+			return 0.5;			/* punt for NaN from subdiff, Inf/Inf, etc */
+
+		/* Relative position must be in [0,1] range */
+		position = Max(position, 0.0);
+		position = Min(position, 1.0);
+		return position;
+	}
+	else if (hist1->infinite && !hist2->infinite)
+	{
+		/*
+		 * Lower bin boundary is -infinite, upper is finite. If the value is
+		 * -infinite, return 0.0 to indicate it's equal to the lower bound.
+		 * Otherwise return 1.0 to indicate it's infinitely far from the lower
+		 * bound.
+		 */
+		return ((value->infinite && value->lower) ? 0.0 : 1.0);
+	}
+	else if (!hist1->infinite && hist2->infinite)
+	{
+		/* same as above, but in reverse */
+		return ((value->infinite && !value->lower) ? 1.0 : 0.0);
+	}
+	else
+	{
+		/*
+		 * If both bin boundaries are infinite, they should be equal to each
+		 * other, and the value should also be infinite and equal to both
+		 * bounds. (But don't Assert that, to avoid crashing if a user creates
+		 * a datatype with a broken comparison function).
+		 *
+		 * Assume the value to lie in the middle of the infinite bounds.
+		 */
+		return 0.5;
+	}
+}
+
+
+/*
+ * Get relative position of value in a length histogram bin in [0,1] range.
+ */
+static double
+get_len_position(double value, double hist1, double hist2)
+{
+	if (!isinf(hist1) && !isinf(hist2))
+	{
+		/*
+		 * Both bounds are finite. The value should be finite too, because it
+		 * lies somewhere between the bounds. If it doesn't, just return
+		 * something.
+		 */
+		if (isinf(value))
+			return 0.5;
+
+		return 1.0 - (hist2 - value) / (hist2 - hist1);
+	}
+	else if (isinf(hist1) && !isinf(hist2))
+	{
+		/*
+		 * Lower bin boundary is -infinite, upper is finite. Return 1.0 to
+		 * indicate the value is infinitely far from the lower bound.
+		 */
+		return 1.0;
+	}
+	else if (isinf(hist1) && isinf(hist2))
+	{
+		/* same as above, but in reverse */
+		return 0.0;
+	}
+	else
+	{
+		/*
+		 * If both bin boundaries are infinite, they should be equal to each
+		 * other, and the value should also be infinite and equal to both
+		 * bounds. (But don't Assert that, to avoid crashing unnecessarily if
+		 * the caller messes up)
+		 *
+		 * Assume the value to lie in the middle of the infinite bounds.
+		 */
+		return 0.5;
+	}
+}
+
+/*
+ * Measure distance between two range bounds.
+ */
+static float8
+get_distance(TypeCacheEntry *typcache, const RangeBound *bound1, const RangeBound *bound2)
+{
+	bool		has_subdiff = OidIsValid(typcache->rng_subdiff_finfo.fn_oid);
+
+	if (!bound1->infinite && !bound2->infinite)
+	{
+		/*
+		 * Neither bound is infinite, use subdiff function or return default
+		 * value of 1.0 if no subdiff is available.
+		 */
+		if (has_subdiff)
+		{
+			float8		res;
+
+			res = DatumGetFloat8(FunctionCall2Coll(&typcache->rng_subdiff_finfo,
+												   typcache->rng_collation,
+												   bound2->val,
+												   bound1->val));
+			/* Reject possible NaN result, also negative result */
+			if (isnan(res) || res < 0.0)
+				return 1.0;
+			else
+				return res;
+		}
+		else
+			return 1.0;
+	}
+	else if (bound1->infinite && bound2->infinite)
+	{
+		/* Both bounds are infinite */
+		if (bound1->lower == bound2->lower)
+			return 0.0;
+		else
+			return get_float8_infinity();
+	}
+	else
+	{
+		/* One bound is infinite, the other is not */
+		return get_float8_infinity();
+	}
+}
+
+/*
+ * Calculate the average of function P(x), in the interval [length1, length2],
+ * where P(x) is the fraction of tuples with length < x (or length <= x if
+ * 'equal' is true).
+ */
+static double
+calc_length_hist_frac(Datum *length_hist_values, int length_hist_nvalues,
+					  double length1, double length2, bool equal)
+{
+	double		frac;
+	double		A,
+				B,
+				PA,
+				PB;
+	double		pos;
+	int			i;
+	double		area;
+
+	Assert(length2 >= length1);
+
+	if (length2 < 0.0)
+		return 0.0;				/* shouldn't happen, but doesn't hurt to check */
+
+	/* All lengths in the table are <= infinite. */
+	if (isinf(length2) && equal)
+		return 1.0;
+
+	/*----------
+	 * The average of a function between A and B can be calculated by the
+	 * formula:
+	 *
+	 *			B
+	 *	  1		/
+	 * -------	| P(x)dx
+	 *	B - A	/
+	 *			A
+	 *
+	 * The geometrical interpretation of the integral is the area under the
+	 * graph of P(x). P(x) is defined by the length histogram. We calculate
+	 * the area in a piecewise fashion, iterating through the length histogram
+	 * bins. Each bin is a trapezoid:
+	 *
+	 *		 P(x2)
+	 *		  /|
+	 *		 / |
+	 * P(x1)/  |
+	 *	   |   |
+	 *	   |   |
+	 *	---+---+--
+	 *	   x1  x2
+	 *
+	 * where x1 and x2 are the boundaries of the current histogram, and P(x1)
+	 * and P(x1) are the cumulative fraction of tuples at the boundaries.
+	 *
+	 * The area of each trapezoid is 1/2 * (P(x2) + P(x1)) * (x2 - x1)
+	 *
+	 * The first bin contains the lower bound passed by the caller, so we
+	 * use linear interpolation between the previous and next histogram bin
+	 * boundary to calculate P(x1). Likewise for the last bin: we use linear
+	 * interpolation to calculate P(x2). For the bins in between, x1 and x2
+	 * lie on histogram bin boundaries, so P(x1) and P(x2) are simply:
+	 * P(x1) =	  (bin index) / (number of bins)
+	 * P(x2) = (bin index + 1 / (number of bins)
+	 */
+
+	/* First bin, the one that contains lower bound */
+	i = length_hist_bsearch(length_hist_values, length_hist_nvalues, length1, equal);
+	if (i >= length_hist_nvalues - 1)
+		return 1.0;
+
+	if (i < 0)
+	{
+		i = 0;
+		pos = 0.0;
+	}
+	else
+	{
+		/* interpolate length1's position in the bin */
+		pos = get_len_position(length1,
+							   DatumGetFloat8(length_hist_values[i]),
+							   DatumGetFloat8(length_hist_values[i + 1]));
+	}
+	PB = (((double) i) + pos) / (double) (length_hist_nvalues - 1);
+	B = length1;
+
+	/*
+	 * In the degenerate case that length1 == length2, simply return
+	 * P(length1). This is not merely an optimization: if length1 == length2,
+	 * we'd divide by zero later on.
+	 */
+	if (length2 == length1)
+		return PB;
+
+	/*
+	 * Loop through all the bins, until we hit the last bin, the one that
+	 * contains the upper bound. (if lower and upper bounds are in the same
+	 * bin, this falls out immediately)
+	 */
+	area = 0.0;
+	for (; i < length_hist_nvalues - 1; i++)
+	{
+		double		bin_upper = DatumGetFloat8(length_hist_values[i + 1]);
+
+		/* check if we've reached the last bin */
+		if (!(bin_upper < length2 || (equal && bin_upper <= length2)))
+			break;
+
+		/* the upper bound of previous bin is the lower bound of this bin */
+		A = B;
+		PA = PB;
+
+		B = bin_upper;
+		PB = (double) i / (double) (length_hist_nvalues - 1);
+
+		/*
+		 * Add the area of this trapezoid to the total. The point of the
+		 * if-check is to avoid NaN, in the corner case that PA == PB == 0,
+		 * and B - A == Inf. The area of a zero-height trapezoid (PA == PB ==
+		 * 0) is zero, regardless of the width (B - A).
+		 */
+		if (PA > 0 || PB > 0)
+			area += 0.5 * (PB + PA) * (B - A);
+	}
+
+	/* Last bin */
+	A = B;
+	PA = PB;
+
+	B = length2;				/* last bin ends at the query upper bound */
+	if (i >= length_hist_nvalues - 1)
+		pos = 0.0;
+	else
+	{
+		if (DatumGetFloat8(length_hist_values[i]) == DatumGetFloat8(length_hist_values[i + 1]))
+			pos = 0.0;
+		else
+			pos = get_len_position(length2, DatumGetFloat8(length_hist_values[i]), DatumGetFloat8(length_hist_values[i + 1]));
+	}
+	PB = (((double) i) + pos) / (double) (length_hist_nvalues - 1);
+
+	if (PA > 0 || PB > 0)
+		area += 0.5 * (PB + PA) * (B - A);
+
+	/*
+	 * Ok, we have calculated the area, ie. the integral. Divide by width to
+	 * get the requested average.
+	 *
+	 * Avoid NaN arising from infinite / infinite. This happens at least if
+	 * length2 is infinite. It's not clear what the correct value would be in
+	 * that case, so 0.5 seems as good as any value.
+	 */
+	if (isinf(area) && isinf(length2))
+		frac = 0.5;
+	else
+		frac = area / (length2 - length1);
+
+	return frac;
+}
+
+/*
+ * Calculate selectivity of "var <@ const" operator, ie. estimate the fraction
+ * of multiranges that fall within the constant lower and upper bounds. This uses
+ * the histograms of range lower bounds and range lengths, on the assumption
+ * that the range lengths are independent of the lower bounds.
+ *
+ * The caller has already checked that constant lower and upper bounds are
+ * finite.
+ */
+static double
+calc_hist_selectivity_contained(TypeCacheEntry *typcache,
+								const RangeBound *lower, RangeBound *upper,
+								const RangeBound *hist_lower, int hist_nvalues,
+								Datum *length_hist_values, int length_hist_nvalues)
+{
+	int			i,
+				upper_index;
+	float8		prev_dist;
+	double		bin_width;
+	double		upper_bin_width;
+	double		sum_frac;
+
+	/*
+	 * Begin by finding the bin containing the upper bound, in the lower bound
+	 * histogram. Any range with a lower bound > constant upper bound can't
+	 * match, ie. there are no matches in bins greater than upper_index.
+	 */
+	upper->inclusive = !upper->inclusive;
+	upper->lower = true;
+	upper_index = rbound_bsearch(typcache, upper, hist_lower, hist_nvalues,
+								 false);
+
+	/*
+	 * If the upper bound value is below the histogram's lower limit, there
+	 * are no matches.
+	 */
+	if (upper_index < 0)
+		return 0.0;
+
+	/*
+	 * If the upper bound value is at or beyond the histogram's upper limit,
+	 * start our loop at the last actual bin, as though the upper bound were
+	 * within that bin; get_position will clamp its result to 1.0 anyway.
+	 * (This corresponds to assuming that the data population above the
+	 * histogram's upper limit is empty, exactly like what we just assumed for
+	 * the lower limit.)
+	 */
+	upper_index = Min(upper_index, hist_nvalues - 2);
+
+	/*
+	 * Calculate upper_bin_width, ie. the fraction of the (upper_index,
+	 * upper_index + 1) bin which is greater than upper bound of query range
+	 * using linear interpolation of subdiff function.
+	 */
+	upper_bin_width = get_position(typcache, upper,
+								   &hist_lower[upper_index],
+								   &hist_lower[upper_index + 1]);
+
+	/*
+	 * In the loop, dist and prev_dist are the distance of the "current" bin's
+	 * lower and upper bounds from the constant upper bound.
+	 *
+	 * bin_width represents the width of the current bin. Normally it is 1.0,
+	 * meaning a full width bin, but can be less in the corner cases: start
+	 * and end of the loop. We start with bin_width = upper_bin_width, because
+	 * we begin at the bin containing the upper bound.
+	 */
+	prev_dist = 0.0;
+	bin_width = upper_bin_width;
+
+	sum_frac = 0.0;
+	for (i = upper_index; i >= 0; i--)
+	{
+		double		dist;
+		double		length_hist_frac;
+		bool		final_bin = false;
+
+		/*
+		 * dist -- distance from upper bound of query range to lower bound of
+		 * the current bin in the lower bound histogram. Or to the lower bound
+		 * of the constant range, if this is the final bin, containing the
+		 * constant lower bound.
+		 */
+		if (range_cmp_bounds(typcache, &hist_lower[i], lower) < 0)
+		{
+			dist = get_distance(typcache, lower, upper);
+
+			/*
+			 * Subtract from bin_width the portion of this bin that we want to
+			 * ignore.
+			 */
+			bin_width -= get_position(typcache, lower, &hist_lower[i],
+									  &hist_lower[i + 1]);
+			if (bin_width < 0.0)
+				bin_width = 0.0;
+			final_bin = true;
+		}
+		else
+			dist = get_distance(typcache, &hist_lower[i], upper);
+
+		/*
+		 * Estimate the fraction of tuples in this bin that are narrow enough
+		 * to not exceed the distance to the upper bound of the query range.
+		 */
+		length_hist_frac = calc_length_hist_frac(length_hist_values,
+												 length_hist_nvalues,
+												 prev_dist, dist, true);
+
+		/*
+		 * Add the fraction of tuples in this bin, with a suitable length, to
+		 * the total.
+		 */
+		sum_frac += length_hist_frac * bin_width / (double) (hist_nvalues - 1);
+
+		if (final_bin)
+			break;
+
+		bin_width = 1.0;
+		prev_dist = dist;
+	}
+
+	return sum_frac;
+}
+
+/*
+ * Calculate selectivity of "var @> const" operator, ie. estimate the fraction
+ * of multiranges that contain the constant lower and upper bounds. This uses
+ * the histograms of range lower bounds and range lengths, on the assumption
+ * that the range lengths are independent of the lower bounds.
+ */
+static double
+calc_hist_selectivity_contains(TypeCacheEntry *typcache,
+							   const RangeBound *lower, const RangeBound *upper,
+							   const RangeBound *hist_lower, int hist_nvalues,
+							   Datum *length_hist_values, int length_hist_nvalues)
+{
+	int			i,
+				lower_index;
+	double		bin_width,
+				lower_bin_width;
+	double		sum_frac;
+	float8		prev_dist;
+
+	/* Find the bin containing the lower bound of query range. */
+	lower_index = rbound_bsearch(typcache, lower, hist_lower, hist_nvalues,
+								 true);
+
+	/*
+	 * If the lower bound value is below the histogram's lower limit, there
+	 * are no matches.
+	 */
+	if (lower_index < 0)
+		return 0.0;
+
+	/*
+	 * If the lower bound value is at or beyond the histogram's upper limit,
+	 * start our loop at the last actual bin, as though the upper bound were
+	 * within that bin; get_position will clamp its result to 1.0 anyway.
+	 * (This corresponds to assuming that the data population above the
+	 * histogram's upper limit is empty, exactly like what we just assumed for
+	 * the lower limit.)
+	 */
+	lower_index = Min(lower_index, hist_nvalues - 2);
+
+	/*
+	 * Calculate lower_bin_width, ie. the fraction of the of (lower_index,
+	 * lower_index + 1) bin which is greater than lower bound of query range
+	 * using linear interpolation of subdiff function.
+	 */
+	lower_bin_width = get_position(typcache, lower, &hist_lower[lower_index],
+								   &hist_lower[lower_index + 1]);
+
+	/*
+	 * Loop through all the lower bound bins, smaller than the query lower
+	 * bound. In the loop, dist and prev_dist are the distance of the
+	 * "current" bin's lower and upper bounds from the constant upper bound.
+	 * We begin from query lower bound, and walk backwards, so the first bin's
+	 * upper bound is the query lower bound, and its distance to the query
+	 * upper bound is the length of the query range.
+	 *
+	 * bin_width represents the width of the current bin. Normally it is 1.0,
+	 * meaning a full width bin, except for the first bin, which is only
+	 * counted up to the constant lower bound.
+	 */
+	prev_dist = get_distance(typcache, lower, upper);
+	sum_frac = 0.0;
+	bin_width = lower_bin_width;
+	for (i = lower_index; i >= 0; i--)
+	{
+		float8		dist;
+		double		length_hist_frac;
+
+		/*
+		 * dist -- distance from upper bound of query range to current value
+		 * of lower bound histogram or lower bound of query range (if we've
+		 * reach it).
+		 */
+		dist = get_distance(typcache, &hist_lower[i], upper);
+
+		/*
+		 * Get average fraction of length histogram which covers intervals
+		 * longer than (or equal to) distance to upper bound of query range.
+		 */
+		length_hist_frac =
+			1.0 - calc_length_hist_frac(length_hist_values,
+										length_hist_nvalues,
+										prev_dist, dist, false);
+
+		sum_frac += length_hist_frac * bin_width / (double) (hist_nvalues - 1);
+
+		bin_width = 1.0;
+		prev_dist = dist;
+	}
+
+	return sum_frac;
+}
diff --git a/src/backend/utils/adt/pg_upgrade_support.c b/src/backend/utils/adt/pg_upgrade_support.c
index 8a5953881ee..521ebaaab6d 100644
--- a/src/backend/utils/adt/pg_upgrade_support.c
+++ b/src/backend/utils/adt/pg_upgrade_support.c
@@ -52,6 +52,28 @@ binary_upgrade_set_next_array_pg_type_oid(PG_FUNCTION_ARGS)
 	PG_RETURN_VOID();
 }
 
+Datum
+binary_upgrade_set_next_multirange_pg_type_oid(PG_FUNCTION_ARGS)
+{
+	Oid			typoid = PG_GETARG_OID(0);
+
+	CHECK_IS_BINARY_UPGRADE;
+	binary_upgrade_next_mrng_pg_type_oid = typoid;
+
+	PG_RETURN_VOID();
+}
+
+Datum
+binary_upgrade_set_next_multirange_array_pg_type_oid(PG_FUNCTION_ARGS)
+{
+	Oid			typoid = PG_GETARG_OID(0);
+
+	CHECK_IS_BINARY_UPGRADE;
+	binary_upgrade_next_mrng_array_pg_type_oid = typoid;
+
+	PG_RETURN_VOID();
+}
+
 Datum
 binary_upgrade_set_next_heap_pg_class_oid(PG_FUNCTION_ARGS)
 {
diff --git a/src/backend/utils/adt/pseudotypes.c b/src/backend/utils/adt/pseudotypes.c
index 3d6b2f90935..99a93271fe1 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"
 
 
 /*
@@ -227,6 +228,43 @@ anycompatiblerange_out(PG_FUNCTION_ARGS)
 	return range_out(fcinfo);
 }
 
+/*
+ * anycompatiblemultirange
+ *
+ * We may as well allow output, since multirange_out will in fact work.
+ */
+PSEUDOTYPE_DUMMY_INPUT_FUNC(anycompatiblemultirange);
+
+Datum
+anycompatiblemultirange_out(PG_FUNCTION_ARGS)
+{
+	return multirange_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
  *
diff --git a/src/backend/utils/adt/rangetypes.c b/src/backend/utils/adt/rangetypes.c
index 01ad8bc240b..ba4caa2d36a 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
 {
@@ -64,10 +62,6 @@ static const char *range_parse_bound(const char *string, const char *ptr,
 static char *range_deparse(char flags, const char *lbound_str,
 						   const char *ubound_str);
 static char *range_bound_escape(const char *value);
-static Size datum_compute_size(Size sz, Datum datum, bool typbyval,
-							   char typalign, int16 typlen, char typstorage);
-static Pointer datum_write(Pointer ptr, Datum datum, bool typbyval,
-						   char typalign, int16 typlen, char typstorage);
 
 
 /*
@@ -431,19 +425,37 @@ range_lower(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
 	TypeCacheEntry *typcache;
+	bool		isnull;
+	Datum		result;
+
+	typcache = range_get_typcache(fcinfo, RangeTypeGetOid(r1));
+
+	result = range_lower_internal(typcache, r1, &isnull);
+
+	if (isnull)
+		PG_RETURN_NULL();
+	else
+		PG_RETURN_DATUM(result);
+}
+
+Datum
+range_lower_internal(TypeCacheEntry *typcache, const RangeType *r1, bool *isnull)
+{
 	RangeBound	lower;
 	RangeBound	upper;
 	bool		empty;
 
-	typcache = range_get_typcache(fcinfo, RangeTypeGetOid(r1));
-
 	range_deserialize(typcache, r1, &lower, &upper, &empty);
 
 	/* Return NULL if there's no finite lower bound */
 	if (empty || lower.infinite)
-		PG_RETURN_NULL();
+	{
+		*isnull = true;
+		return 0;
+	}
 
-	PG_RETURN_DATUM(lower.val);
+	*isnull = false;
+	return lower.val;
 }
 
 /* extract upper bound value */
@@ -452,19 +464,37 @@ range_upper(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
 	TypeCacheEntry *typcache;
+	bool		isnull;
+	Datum		result;
+
+	typcache = range_get_typcache(fcinfo, RangeTypeGetOid(r1));
+
+	result = range_upper_internal(typcache, r1, &isnull);
+
+	if (isnull)
+		PG_RETURN_NULL();
+	else
+		PG_RETURN_DATUM(result);
+}
+
+Datum
+range_upper_internal(TypeCacheEntry *typcache, const RangeType *r1, bool *isnull)
+{
 	RangeBound	lower;
 	RangeBound	upper;
 	bool		empty;
 
-	typcache = range_get_typcache(fcinfo, RangeTypeGetOid(r1));
-
 	range_deserialize(typcache, r1, &lower, &upper, &empty);
 
 	/* Return NULL if there's no finite upper bound */
 	if (empty || upper.infinite)
-		PG_RETURN_NULL();
+	{
+		*isnull = true;
+		return 0;
+	}
 
-	PG_RETURN_DATUM(upper.val);
+	*isnull = false;
+	return upper.val;
 }
 
 
@@ -475,9 +505,8 @@ Datum
 range_empty(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
-	char		flags = range_get_flags(r1);
 
-	PG_RETURN_BOOL(flags & RANGE_EMPTY);
+	PG_RETURN_BOOL(range_has_flag(r1, RANGE_EMPTY));
 }
 
 /* is lower bound inclusive? */
@@ -485,9 +514,8 @@ Datum
 range_lower_inc(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
-	char		flags = range_get_flags(r1);
 
-	PG_RETURN_BOOL(flags & RANGE_LB_INC);
+	PG_RETURN_BOOL(range_has_flag(r1, RANGE_LB_INC));
 }
 
 /* is upper bound inclusive? */
@@ -495,9 +523,8 @@ Datum
 range_upper_inc(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
-	char		flags = range_get_flags(r1);
 
-	PG_RETURN_BOOL(flags & RANGE_UB_INC);
+	PG_RETURN_BOOL(range_has_flag(r1, RANGE_UB_INC));
 }
 
 /* is lower bound infinite? */
@@ -505,9 +532,8 @@ Datum
 range_lower_inf(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
-	char		flags = range_get_flags(r1);
 
-	PG_RETURN_BOOL(flags & RANGE_LB_INF);
+	PG_RETURN_BOOL(range_has_flag(r1, RANGE_LB_INF));
 }
 
 /* is upper bound infinite? */
@@ -515,9 +541,8 @@ Datum
 range_upper_inf(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
-	char		flags = range_get_flags(r1);
 
-	PG_RETURN_BOOL(flags & RANGE_UB_INF);
+	PG_RETURN_BOOL(range_has_flag(r1, RANGE_UB_INF));
 }
 
 
@@ -957,7 +982,25 @@ range_minus(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
 	RangeType  *r2 = PG_GETARG_RANGE_P(1);
+	RangeType  *ret;
 	TypeCacheEntry *typcache;
+
+	/* 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));
+
+	ret = range_minus_internal(typcache, r1, r2);
+	if (ret)
+		PG_RETURN_RANGE_P(ret);
+	else
+		PG_RETURN_NULL();
+}
+
+RangeType *
+range_minus_internal(TypeCacheEntry *typcache, RangeType *r1, RangeType *r2)
+{
 	RangeBound	lower1,
 				lower2;
 	RangeBound	upper1,
@@ -969,18 +1012,12 @@ range_minus(PG_FUNCTION_ARGS)
 				cmp_u1l2,
 				cmp_u1u2;
 
-	/* 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));
-
 	range_deserialize(typcache, r1, &lower1, &upper1, &empty1);
 	range_deserialize(typcache, r2, &lower2, &upper2, &empty2);
 
 	/* if either is empty, r1 is the correct answer */
 	if (empty1 || empty2)
-		PG_RETURN_RANGE_P(r1);
+		return r1;
 
 	cmp_l1l2 = range_cmp_bounds(typcache, &lower1, &lower2);
 	cmp_l1u2 = range_cmp_bounds(typcache, &lower1, &upper2);
@@ -993,34 +1030,34 @@ range_minus(PG_FUNCTION_ARGS)
 				 errmsg("result of range difference would not be contiguous")));
 
 	if (cmp_l1u2 > 0 || cmp_u1l2 < 0)
-		PG_RETURN_RANGE_P(r1);
+		return r1;
 
 	if (cmp_l1l2 >= 0 && cmp_u1u2 <= 0)
-		PG_RETURN_RANGE_P(make_empty_range(typcache));
+		return make_empty_range(typcache);
 
 	if (cmp_l1l2 <= 0 && cmp_u1l2 >= 0 && cmp_u1u2 <= 0)
 	{
 		lower2.inclusive = !lower2.inclusive;
 		lower2.lower = false;	/* it will become the upper bound */
-		PG_RETURN_RANGE_P(make_range(typcache, &lower1, &lower2, false));
+		return make_range(typcache, &lower1, &lower2, false);
 	}
 
 	if (cmp_l1l2 >= 0 && cmp_u1u2 >= 0 && cmp_l1u2 <= 0)
 	{
 		upper2.inclusive = !upper2.inclusive;
 		upper2.lower = true;	/* it will become the lower bound */
-		PG_RETURN_RANGE_P(make_range(typcache, &upper2, &upper1, false));
+		return make_range(typcache, &upper2, &upper1, false);
 	}
 
 	elog(ERROR, "unexpected case in range_minus");
-	PG_RETURN_NULL();
+	return NULL;
 }
 
 /*
  * Set union.  If strict is true, it is an error that the two input ranges
  * are not adjacent or overlapping.
  */
-static RangeType *
+RangeType *
 range_union_internal(TypeCacheEntry *typcache, RangeType *r1, RangeType *r2,
 					 bool strict)
 {
@@ -1101,6 +1138,19 @@ range_intersect(PG_FUNCTION_ARGS)
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
 	RangeType  *r2 = PG_GETARG_RANGE_P(1);
 	TypeCacheEntry *typcache;
+
+	/* 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));
+
+	PG_RETURN_RANGE_P(range_intersect_internal(typcache, r1, r2));
+}
+
+RangeType *
+range_intersect_internal(TypeCacheEntry *typcache, const RangeType *r1, const RangeType *r2)
+{
 	RangeBound	lower1,
 				lower2;
 	RangeBound	upper1,
@@ -1110,17 +1160,11 @@ range_intersect(PG_FUNCTION_ARGS)
 	RangeBound *result_lower;
 	RangeBound *result_upper;
 
-	/* 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));
-
 	range_deserialize(typcache, r1, &lower1, &upper1, &empty1);
 	range_deserialize(typcache, r2, &lower2, &upper2, &empty2);
 
-	if (empty1 || empty2 || !DatumGetBool(range_overlaps(fcinfo)))
-		PG_RETURN_RANGE_P(make_empty_range(typcache));
+	if (empty1 || empty2 || !range_overlaps_internal(typcache, r1, r2))
+		return make_empty_range(typcache);
 
 	if (range_cmp_bounds(typcache, &lower1, &lower2) >= 0)
 		result_lower = &lower1;
@@ -1132,9 +1176,81 @@ range_intersect(PG_FUNCTION_ARGS)
 	else
 		result_upper = &upper2;
 
-	PG_RETURN_RANGE_P(make_range(typcache, result_lower, result_upper, false));
+	return make_range(typcache, result_lower, result_upper, false);
+}
+
+/* range, range -> range, range functions */
+
+/*
+ * range_split_internal - if r2 intersects the middle of r1, leaving non-empty
+ * ranges on both sides, then return true and set output1 and output2 to the
+ * results of r1 - r2 (in order). Otherwise return false and don't set output1
+ * or output2. Neither input range should be empty.
+ */
+bool
+range_split_internal(TypeCacheEntry *typcache, const RangeType *r1, const RangeType *r2,
+					 RangeType **output1, RangeType **output2)
+{
+	RangeBound	lower1,
+				lower2;
+	RangeBound	upper1,
+				upper2;
+	bool		empty1,
+				empty2;
+
+	range_deserialize(typcache, r1, &lower1, &upper1, &empty1);
+	range_deserialize(typcache, r2, &lower2, &upper2, &empty2);
+
+	if (range_cmp_bounds(typcache, &lower1, &lower2) < 0 &&
+		range_cmp_bounds(typcache, &upper1, &upper2) > 0)
+	{
+		/*
+		 * Need to invert inclusive/exclusive for the lower2 and upper2
+		 * points. They can't be infinite though. We're allowed to overwrite
+		 * these RangeBounds since they only exist locally.
+		 */
+		lower2.inclusive = !lower2.inclusive;
+		lower2.lower = false;
+		upper2.inclusive = !upper2.inclusive;
+		upper2.lower = true;
+
+		*output1 = make_range(typcache, &lower1, &lower2, false);
+		*output2 = make_range(typcache, &upper2, &upper1, false);
+		return true;
+	}
+
+	return false;
 }
 
+/* range -> range aggregate functions */
+
+Datum
+range_intersect_agg_transfn(PG_FUNCTION_ARGS)
+{
+	MemoryContext aggContext;
+	Oid			rngtypoid;
+	TypeCacheEntry *typcache;
+	RangeType  *result;
+	RangeType  *current;
+
+	if (!AggCheckCallContext(fcinfo, &aggContext))
+		elog(ERROR, "range_intersect_agg_transfn called in non-aggregate context");
+
+	rngtypoid = get_fn_expr_argtype(fcinfo->flinfo, 1);
+	if (!type_is_range(rngtypoid))
+		ereport(ERROR, (errmsg("range_intersect_agg must be called with a range")));
+
+	typcache = range_get_typcache(fcinfo, rngtypoid);
+
+	/* strictness ensures these are non-null */
+	result = PG_GETARG_RANGE_P(0);
+	current = PG_GETARG_RANGE_P(1);
+
+	result = range_intersect_internal(typcache, result, current);
+	PG_RETURN_RANGE_P(result);
+}
+
+
 /* Btree support */
 
 /* btree comparator */
@@ -1144,12 +1260,6 @@ range_cmp(PG_FUNCTION_ARGS)
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
 	RangeType  *r2 = PG_GETARG_RANGE_P(1);
 	TypeCacheEntry *typcache;
-	RangeBound	lower1,
-				lower2;
-	RangeBound	upper1,
-				upper2;
-	bool		empty1,
-				empty2;
 	int			cmp;
 
 	check_stack_depth();		/* recurses when subtype is a range type */
@@ -1160,6 +1270,28 @@ range_cmp(PG_FUNCTION_ARGS)
 
 	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, const RangeType *r1, const 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);
 
@@ -1177,10 +1309,7 @@ range_cmp(PG_FUNCTION_ARGS)
 			cmp = range_cmp_bounds(typcache, &upper1, &upper2);
 	}
 
-	PG_FREE_IF_COPY(r1, 0);
-	PG_FREE_IF_COPY(r2, 1);
-
-	PG_RETURN_INT32(cmp);
+	return cmp;
 }
 
 /* inequality operators using the range_cmp function */
@@ -1218,13 +1347,10 @@ range_gt(PG_FUNCTION_ARGS)
 
 /* Hash support */
 
-/* hash a range value */
-Datum
-hash_range(PG_FUNCTION_ARGS)
+uint32
+hash_range_internal(TypeCacheEntry *typcache, const RangeType *r)
 {
-	RangeType  *r = PG_GETARG_RANGE_P(0);
 	uint32		result;
-	TypeCacheEntry *typcache;
 	TypeCacheEntry *scache;
 	RangeBound	lower;
 	RangeBound	upper;
@@ -1233,10 +1359,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 +1400,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, const RangeType *r, Datum seed)
+{
+	uint64		result;
 	TypeCacheEntry *scache;
 	RangeBound	lower;
 	RangeBound	upper;
@@ -1300,10 +1429,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 +1467,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));
 }
 
 /*
@@ -1769,6 +1912,21 @@ range_get_flags(const RangeType *range)
 	return *((char *) range + VARSIZE(range) - 1);
 }
 
+/*
+ * range_has_flag: set whether a range has a specific flag.
+ *
+ * This lets expose some of our functions that just check flags
+ * to the rest of the code base (like to multiranges)
+ * without writing full-fledged *_internal versions.
+ */
+bool
+range_has_flag(const RangeType *r1, char flag)
+{
+	char		flags = range_get_flags(r1);
+
+	return flags & flag;
+}
+
 /*
  * range_set_contain_empty: set the RANGE_CONTAIN_EMPTY bit in the value.
  *
@@ -1937,6 +2095,46 @@ range_cmp_bound_values(TypeCacheEntry *typcache, const RangeBound *b1,
 										   b1->val, b2->val));
 }
 
+/*
+ * qsort callback for sorting ranges.
+ *
+ * Two empty ranges compare equal; an empty range sorts to the left of any
+ * non-empty range.  Two non-empty ranges are sorted by lower bound first
+ * and by upper bound next.
+ */
+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.
  */
@@ -2395,7 +2593,7 @@ range_contains_elem_internal(TypeCacheEntry *typcache, const RangeType *r, Datum
  * Increment data_length by the space needed by the datum, including any
  * preceding alignment padding.
  */
-static Size
+Size
 datum_compute_size(Size data_length, Datum val, bool typbyval, char typalign,
 				   int16 typlen, char typstorage)
 {
@@ -2421,7 +2619,7 @@ datum_compute_size(Size data_length, Datum val, bool typbyval, char typalign,
  * Write the given datum beginning at ptr (after advancing to correct
  * alignment, if needed).  Return the pointer incremented by space used.
  */
-static Pointer
+Pointer
 datum_write(Pointer ptr, Datum datum, bool typbyval, char typalign,
 			int16 typlen, char typstorage)
 {
diff --git a/src/backend/utils/adt/rangetypes_typanalyze.c b/src/backend/utils/adt/rangetypes_typanalyze.c
index 57413b07201..dcef09b13b7 100644
--- a/src/backend/utils/adt/rangetypes_typanalyze.c
+++ b/src/backend/utils/adt/rangetypes_typanalyze.c
@@ -30,6 +30,7 @@
 #include "utils/fmgrprotos.h"
 #include "utils/lsyscache.h"
 #include "utils/rangetypes.h"
+#include "utils/multirangetypes.h"
 
 static int	float8_qsort_cmp(const void *a1, const void *a2);
 static int	range_bound_qsort_cmp(const void *a1, const void *a2, void *arg);
@@ -60,6 +61,33 @@ range_typanalyze(PG_FUNCTION_ARGS)
 	PG_RETURN_BOOL(true);
 }
 
+/*
+ * multirange_typanalyze -- typanalyze function for multirange columns
+ *
+ * We do the same analysis as for ranges, but on the smallest range that
+ * completely includes the multirange.
+ */
+Datum
+multirange_typanalyze(PG_FUNCTION_ARGS)
+{
+	VacAttrStats *stats = (VacAttrStats *) PG_GETARG_POINTER(0);
+	TypeCacheEntry *typcache;
+	Form_pg_attribute attr = stats->attr;
+
+	/* Get information about multirange type; note column might be a domain */
+	typcache = multirange_get_typcache(fcinfo, getBaseType(stats->attrtypid));
+
+	if (attr->attstattarget < 0)
+		attr->attstattarget = default_statistics_target;
+
+	stats->compute_stats = compute_range_stats;
+	stats->extra_data = typcache;
+	/* same as in std_typanalyze */
+	stats->minrows = 300 * attr->attstattarget;
+
+	PG_RETURN_BOOL(true);
+}
+
 /*
  * Comparison function for sorting float8s, used for range lengths.
  */
@@ -98,7 +126,8 @@ compute_range_stats(VacAttrStats *stats, AnalyzeAttrFetchFunc fetchfunc,
 					int samplerows, double totalrows)
 {
 	TypeCacheEntry *typcache = (TypeCacheEntry *) stats->extra_data;
-	bool		has_subdiff = OidIsValid(typcache->rng_subdiff_finfo.fn_oid);
+	TypeCacheEntry *mltrng_typcache = NULL;
+	bool		has_subdiff;
 	int			null_cnt = 0;
 	int			non_null_cnt = 0;
 	int			non_empty_cnt = 0;
@@ -112,6 +141,15 @@ compute_range_stats(VacAttrStats *stats, AnalyzeAttrFetchFunc fetchfunc,
 			   *uppers;
 	double		total_width = 0;
 
+	if (typcache->typtype == TYPTYPE_MULTIRANGE)
+	{
+		mltrng_typcache = typcache;
+		typcache = typcache->rngtype;
+	}
+	else
+		Assert(typcache->typtype == TYPTYPE_RANGE);
+	has_subdiff = OidIsValid(typcache->rng_subdiff_finfo.fn_oid);
+
 	/* Allocate memory to hold range bounds and lengths of the sample ranges. */
 	lowers = (RangeBound *) palloc(sizeof(RangeBound) * samplerows);
 	uppers = (RangeBound *) palloc(sizeof(RangeBound) * samplerows);
@@ -123,6 +161,7 @@ compute_range_stats(VacAttrStats *stats, AnalyzeAttrFetchFunc fetchfunc,
 		Datum		value;
 		bool		isnull,
 					empty;
+		MultirangeType *multirange;
 		RangeType  *range;
 		RangeBound	lower,
 					upper;
@@ -145,7 +184,24 @@ compute_range_stats(VacAttrStats *stats, AnalyzeAttrFetchFunc fetchfunc,
 		total_width += VARSIZE_ANY(DatumGetPointer(value));
 
 		/* Get range and deserialize it for further analysis. */
-		range = DatumGetRangeTypeP(value);
+		if (mltrng_typcache != NULL)
+		{
+			/* Treat multiranges like a big range without gaps. */
+			multirange = DatumGetMultirangeTypeP(value);
+			if (MultirangeIsEmpty(multirange))
+				range = make_empty_range(typcache);
+			else
+			{
+				int			range_count;
+				RangeType **ranges;
+
+				multirange_deserialize(typcache, multirange, &range_count, &ranges);
+				range = range_union_internal(typcache, ranges[0],
+											 ranges[range_count - 1], false);
+			}
+		}
+		else
+			range = DatumGetRangeTypeP(value);
 		range_deserialize(typcache, range, &lower, &upper, &empty);
 
 		if (!empty)
@@ -262,6 +318,13 @@ compute_range_stats(VacAttrStats *stats, AnalyzeAttrFetchFunc fetchfunc,
 			stats->stakind[slot_idx] = STATISTIC_KIND_BOUNDS_HISTOGRAM;
 			stats->stavalues[slot_idx] = bound_hist_values;
 			stats->numvalues[slot_idx] = num_hist;
+
+			/* Store ranges even if we're analyzing a multirange column */
+			stats->statypid[slot_idx] = typcache->type_id;
+			stats->statyplen[slot_idx] = typcache->typlen;
+			stats->statypbyval[slot_idx] = typcache->typbyval;
+			stats->statypalign[slot_idx] = 'd';
+
 			slot_idx++;
 		}
 
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index 204bcd03c0b..ad92636f7f1 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -2610,6 +2610,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
  *
@@ -3308,7 +3318,7 @@ get_namespace_name_or_temp(Oid nspid)
 		return get_namespace_name(nspid);
 }
 
-/*				---------- PG_RANGE CACHE ----------				 */
+/*				---------- PG_RANGE CACHES ----------				 */
 
 /*
  * get_range_subtype
@@ -3361,6 +3371,56 @@ get_range_collation(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->rngmultitypid;
+		ReleaseSysCache(tp);
+		return result;
+	}
+	else
+		return InvalidOid;
+}
+
+/*
+ * get_multirange_range
+ *		Returns the range type of a given multirange
+ *
+ * Returns InvalidOid if the type is not a multirange.
+ */
+Oid
+get_multirange_range(Oid multirangeOid)
+{
+	HeapTuple	tp;
+
+	tp = SearchSysCache1(RANGEMULTIRANGE, 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 809b27a038f..89f08a4f43c 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -650,6 +650,18 @@ static const struct cachedesc cacheinfo[] = {
 		},
 		64
 	},
+	{RangeRelationId,			/* RANGEMULTIRANGE */
+		RangeMultirangeTypidIndexId,
+		1,
+		{
+			Anum_pg_range_rngmultitypid,
+			0,
+			0,
+			0
+		},
+		4
+	},
+
 	{RangeRelationId,			/* RANGETYPE */
 		RangeTypidIndexId,
 		1,
diff --git a/src/backend/utils/cache/typcache.c b/src/backend/utils/cache/typcache.c
index 1e331098c0d..8c97ef39557 100644
--- a/src/backend/utils/cache/typcache.c
+++ b/src/backend/utils/cache/typcache.c
@@ -287,6 +287,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);
@@ -305,6 +306,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 TypeCacheTypCallback(Datum arg, int cacheid, uint32 hashvalue);
 static void TypeCacheOpcCallback(Datum arg, int cacheid, uint32 hashvalue);
@@ -557,8 +561,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))
@@ -595,7 +599,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))
@@ -620,7 +624,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))
@@ -645,7 +649,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))
@@ -695,6 +699,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;
@@ -737,6 +748,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;
@@ -816,6 +834,16 @@ lookup_type_cache(Oid type_id, int flags)
 			(void) lookup_type_cache(typentry->rngelemtype->type_id, 0);
 	}
 
+	/*
+	 * 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
 	 */
@@ -927,6 +955,22 @@ load_rangetype_info(TypeCacheEntry *typentry)
 	typentry->rngelemtype = lookup_type_cache(subtypeOid, 0);
 }
 
+/*
+ * load_multirangetype_info --- helper routine to set up multirange type
+ * information
+ */
+static void
+load_multirangetype_info(TypeCacheEntry *typentry)
+{
+	Oid			rangetypeOid;
+
+	rangetypeOid = get_multirange_range(typentry->type_id);
+	if (!OidIsValid(rangetypeOid))
+		elog(ERROR, "cache lookup failed for multirange type %u",
+			 typentry->type_id);
+
+	typentry->rngtype = lookup_type_cache(rangetypeOid, TYPECACHE_RANGE_INFO);
+}
 
 /*
  * load_domaintype_info --- helper routine to set up domain constraint info
@@ -1559,11 +1603,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
@@ -1606,6 +1650,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 9696c88f241..f6fa4ab2fb2 100644
--- a/src/backend/utils/fmgr/funcapi.c
+++ b/src/backend/utils/fmgr/funcapi.c
@@ -35,6 +35,7 @@ typedef struct polymorphic_actuals
 	Oid			anyelement_type;	/* anyelement mapping, if known */
 	Oid			anyarray_type;	/* anyarray mapping, if known */
 	Oid			anyrange_type;	/* anyrange mapping, if known */
+	Oid			anymultirange_type; /* anymultirange mapping, if known */
 } polymorphic_actuals;
 
 static void shutdown_MultiFuncCall(Datum arg);
@@ -46,6 +47,7 @@ static TypeFuncClass internal_get_result_type(Oid funcid,
 static void resolve_anyelement_from_others(polymorphic_actuals *actuals);
 static void resolve_anyarray_from_others(polymorphic_actuals *actuals);
 static void resolve_anyrange_from_others(polymorphic_actuals *actuals);
+static void resolve_anymultirange_from_others(polymorphic_actuals *actuals);
 static bool resolve_polymorphic_tupdesc(TupleDesc tupdesc,
 										oidvector *declared_args,
 										Node *call_expr);
@@ -503,6 +505,34 @@ resolve_anyelement_from_others(polymorphic_actuals *actuals)
 							format_type_be(range_base_type))));
 		actuals->anyelement_type = range_typelem;
 	}
+	else if (OidIsValid(actuals->anymultirange_type))
+	{
+		/* Use the element type based on the multirange type */
+		Oid			multirange_base_type;
+		Oid			multirange_typelem;
+		Oid			range_base_type;
+		Oid			range_typelem;
+
+		multirange_base_type = getBaseType(actuals->anymultirange_type);
+		multirange_typelem = get_multirange_range(multirange_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(multirange_base_type))));
+
+		range_base_type = getBaseType(multirange_typelem);
+		range_typelem = get_range_subtype(range_base_type);
+
+		if (!OidIsValid(range_typelem))
+			ereport(ERROR,
+					(errcode(ERRCODE_DATATYPE_MISMATCH),
+					 errmsg("argument declared %s does not contain a range type but type %s",
+							"anymultirange",
+							format_type_be(range_base_type))));
+		actuals->anyelement_type = range_typelem;
+	}
 	else
 		elog(ERROR, "could not determine polymorphic type");
 }
@@ -540,10 +570,53 @@ static void
 resolve_anyrange_from_others(polymorphic_actuals *actuals)
 {
 	/*
-	 * We can't deduce a range type from other polymorphic inputs, because
-	 * there may be multiple range types with the same subtype.
+	 * We can't deduce a range type from other polymorphic array or base
+	 * types, because there may be multiple range types with the same subtype,
+	 * but we can deduce it from a polymorphic multirange type.
+	 */
+	if (OidIsValid(actuals->anymultirange_type))
+	{
+		/* Use the element type based on the multirange type */
+		Oid			multirange_base_type = getBaseType(actuals->anymultirange_type);
+		Oid			multirange_typelem = get_multirange_range(multirange_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(multirange_base_type))));
+		actuals->anyrange_type = multirange_typelem;
+	}
+	else
+		elog(ERROR, "could not determine polymorphic type");
+}
+
+/*
+ * Resolve actual type of ANYMULTIRANGE from other polymorphic inputs
+ */
+static void
+resolve_anymultirange_from_others(polymorphic_actuals *actuals)
+{
+	/*
+	 * We can't deduce a multirange type from polymorphic array or base types,
+	 * because there may be multiple range types with the same subtype, but we
+	 * can deduce it from a polymorphic range type.
 	 */
-	elog(ERROR, "could not determine polymorphic type");
+	if (OidIsValid(actuals->anyrange_type))
+	{
+		Oid			range_base_type = getBaseType(actuals->anyrange_type);
+		Oid			multirange_typeid = get_range_multirange(range_base_type);
+
+		if (!OidIsValid(multirange_typeid))
+			ereport(ERROR,
+					(errcode(ERRCODE_UNDEFINED_OBJECT),
+					 errmsg("could not find multirange type for data type %s",
+							format_type_be(actuals->anyrange_type))));
+		actuals->anymultirange_type = multirange_typeid;
+	}
+	else
+		elog(ERROR, "could not determine polymorphic type");
 }
 
 /*
@@ -566,9 +639,11 @@ 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_anycompatible_result = false;
 	bool		have_anycompatible_array_result = false;
 	bool		have_anycompatible_range_result = false;
+	bool		have_anycompatible_multirange_result = false;
 	polymorphic_actuals poly_actuals;
 	polymorphic_actuals anyc_actuals;
 	Oid			anycollation = InvalidOid;
@@ -594,6 +669,10 @@ resolve_polymorphic_tupdesc(TupleDesc tupdesc, oidvector *declared_args,
 				have_polymorphic_result = true;
 				have_anyrange_result = true;
 				break;
+			case ANYMULTIRANGEOID:
+				have_polymorphic_result = true;
+				have_anymultirange_result = true;
+				break;
 			case ANYCOMPATIBLEOID:
 			case ANYCOMPATIBLENONARRAYOID:
 				have_polymorphic_result = true;
@@ -607,6 +686,10 @@ resolve_polymorphic_tupdesc(TupleDesc tupdesc, oidvector *declared_args,
 				have_polymorphic_result = true;
 				have_anycompatible_range_result = true;
 				break;
+			case ANYCOMPATIBLEMULTIRANGEOID:
+				have_polymorphic_result = true;
+				have_anycompatible_multirange_result = true;
+				break;
 			default:
 				break;
 		}
@@ -660,6 +743,15 @@ resolve_polymorphic_tupdesc(TupleDesc tupdesc, oidvector *declared_args,
 						return false;
 				}
 				break;
+			case ANYMULTIRANGEOID:
+				if (!OidIsValid(poly_actuals.anymultirange_type))
+				{
+					poly_actuals.anymultirange_type =
+						get_call_expr_argtype(call_expr, i);
+					if (!OidIsValid(poly_actuals.anymultirange_type))
+						return false;
+				}
+				break;
 			case ANYCOMPATIBLEOID:
 			case ANYCOMPATIBLENONARRAYOID:
 				if (!OidIsValid(anyc_actuals.anyelement_type))
@@ -688,6 +780,15 @@ resolve_polymorphic_tupdesc(TupleDesc tupdesc, oidvector *declared_args,
 						return false;
 				}
 				break;
+			case ANYCOMPATIBLEMULTIRANGEOID:
+				if (!OidIsValid(anyc_actuals.anymultirange_type))
+				{
+					anyc_actuals.anymultirange_type =
+						get_call_expr_argtype(call_expr, i);
+					if (!OidIsValid(anyc_actuals.anymultirange_type))
+						return false;
+				}
+				break;
 			default:
 				break;
 		}
@@ -703,6 +804,9 @@ resolve_polymorphic_tupdesc(TupleDesc tupdesc, oidvector *declared_args,
 	if (have_anyrange_result && !OidIsValid(poly_actuals.anyrange_type))
 		resolve_anyrange_from_others(&poly_actuals);
 
+	if (have_anymultirange_result && !OidIsValid(poly_actuals.anymultirange_type))
+		resolve_anymultirange_from_others(&poly_actuals);
+
 	if (have_anycompatible_result && !OidIsValid(anyc_actuals.anyelement_type))
 		resolve_anyelement_from_others(&anyc_actuals);
 
@@ -712,6 +816,9 @@ resolve_polymorphic_tupdesc(TupleDesc tupdesc, oidvector *declared_args,
 	if (have_anycompatible_range_result && !OidIsValid(anyc_actuals.anyrange_type))
 		resolve_anyrange_from_others(&anyc_actuals);
 
+	if (have_anycompatible_multirange_result && !OidIsValid(anyc_actuals.anymultirange_type))
+		resolve_anymultirange_from_others(&anyc_actuals);
+
 	/*
 	 * Identify the collation to use for polymorphic OUT parameters. (It'll
 	 * necessarily be the same for both anyelement and anyarray, likewise for
@@ -780,6 +887,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),
+								   poly_actuals.anymultirange_type,
+								   -1,
+								   0);
+				/* no collation should be attached to a multirange type */
+				break;
 			case ANYCOMPATIBLEOID:
 			case ANYCOMPATIBLENONARRAYOID:
 				TupleDescInitEntry(tupdesc, i + 1,
@@ -805,6 +920,14 @@ resolve_polymorphic_tupdesc(TupleDesc tupdesc, oidvector *declared_args,
 								   0);
 				/* no collation should be attached to a range type */
 				break;
+			case ANYCOMPATIBLEMULTIRANGEOID:
+				TupleDescInitEntry(tupdesc, i + 1,
+								   NameStr(att->attname),
+								   anyc_actuals.anymultirange_type,
+								   -1,
+								   0);
+				/* no collation should be attached to a multirange type */
+				break;
 			default:
 				break;
 		}
@@ -834,9 +957,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;
 	bool		have_anycompatible_result = false;
 	bool		have_anycompatible_array_result = false;
 	bool		have_anycompatible_range_result = false;
+	bool		have_anycompatible_multirange_result = false;
 	polymorphic_actuals poly_actuals;
 	polymorphic_actuals anyc_actuals;
 	int			inargno;
@@ -912,6 +1037,24 @@ resolve_polymorphic_argtypes(int numargs, Oid *argtypes, char *argmodes,
 					argtypes[i] = poly_actuals.anyrange_type;
 				}
 				break;
+			case ANYMULTIRANGEOID:
+				if (argmode == PROARGMODE_OUT || argmode == PROARGMODE_TABLE)
+				{
+					have_polymorphic_result = true;
+					have_anymultirange_result = true;
+				}
+				else
+				{
+					if (!OidIsValid(poly_actuals.anymultirange_type))
+					{
+						poly_actuals.anymultirange_type =
+							get_call_expr_argtype(call_expr, inargno);
+						if (!OidIsValid(poly_actuals.anymultirange_type))
+							return false;
+					}
+					argtypes[i] = poly_actuals.anymultirange_type;
+				}
+				break;
 			case ANYCOMPATIBLEOID:
 			case ANYCOMPATIBLENONARRAYOID:
 				if (argmode == PROARGMODE_OUT || argmode == PROARGMODE_TABLE)
@@ -967,6 +1110,24 @@ resolve_polymorphic_argtypes(int numargs, Oid *argtypes, char *argmodes,
 					argtypes[i] = anyc_actuals.anyrange_type;
 				}
 				break;
+			case ANYCOMPATIBLEMULTIRANGEOID:
+				if (argmode == PROARGMODE_OUT || argmode == PROARGMODE_TABLE)
+				{
+					have_polymorphic_result = true;
+					have_anycompatible_multirange_result = true;
+				}
+				else
+				{
+					if (!OidIsValid(anyc_actuals.anymultirange_type))
+					{
+						anyc_actuals.anymultirange_type =
+							get_call_expr_argtype(call_expr, inargno);
+						if (!OidIsValid(anyc_actuals.anymultirange_type))
+							return false;
+					}
+					argtypes[i] = anyc_actuals.anymultirange_type;
+				}
+				break;
 			default:
 				break;
 		}
@@ -988,6 +1149,9 @@ resolve_polymorphic_argtypes(int numargs, Oid *argtypes, char *argmodes,
 	if (have_anyrange_result && !OidIsValid(poly_actuals.anyrange_type))
 		resolve_anyrange_from_others(&poly_actuals);
 
+	if (have_anymultirange_result && !OidIsValid(poly_actuals.anymultirange_type))
+		resolve_anymultirange_from_others(&poly_actuals);
+
 	if (have_anycompatible_result && !OidIsValid(anyc_actuals.anyelement_type))
 		resolve_anyelement_from_others(&anyc_actuals);
 
@@ -997,6 +1161,9 @@ resolve_polymorphic_argtypes(int numargs, Oid *argtypes, char *argmodes,
 	if (have_anycompatible_range_result && !OidIsValid(anyc_actuals.anyrange_type))
 		resolve_anyrange_from_others(&anyc_actuals);
 
+	if (have_anycompatible_multirange_result && !OidIsValid(anyc_actuals.anymultirange_type))
+		resolve_anymultirange_from_others(&anyc_actuals);
+
 	/* And finally replace the output column types as needed */
 	for (i = 0; i < numargs; i++)
 	{
@@ -1013,6 +1180,9 @@ resolve_polymorphic_argtypes(int numargs, Oid *argtypes, char *argmodes,
 			case ANYRANGEOID:
 				argtypes[i] = poly_actuals.anyrange_type;
 				break;
+			case ANYMULTIRANGEOID:
+				argtypes[i] = poly_actuals.anymultirange_type;
+				break;
 			case ANYCOMPATIBLEOID:
 			case ANYCOMPATIBLENONARRAYOID:
 				argtypes[i] = anyc_actuals.anyelement_type;
@@ -1023,6 +1193,9 @@ resolve_polymorphic_argtypes(int numargs, Oid *argtypes, char *argmodes,
 			case ANYCOMPATIBLERANGEOID:
 				argtypes[i] = anyc_actuals.anyrange_type;
 				break;
+			case ANYCOMPATIBLEMULTIRANGEOID:
+				argtypes[i] = anyc_actuals.anymultirange_type;
+				break;
 			default:
 				break;
 		}
@@ -1052,6 +1225,7 @@ get_type_func_class(Oid typid, Oid *base_typeid)
 		case TYPTYPE_BASE:
 		case TYPTYPE_ENUM:
 		case TYPTYPE_RANGE:
+		case TYPTYPE_MULTIRANGE:
 			return TYPEFUNC_SCALAR;
 		case TYPTYPE_DOMAIN:
 			*base_typeid = typid = getBaseType(typid);
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 673a6703475..03023a382c0 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -272,7 +272,8 @@ static void dumpSearchPath(Archive *AH);
 static void binary_upgrade_set_type_oids_by_type_oid(Archive *fout,
 													 PQExpBuffer upgrade_buffer,
 													 Oid pg_type_oid,
-													 bool force_array_type);
+													 bool force_array_type,
+													 bool include_multirange_type);
 static void binary_upgrade_set_type_oids_by_rel_oid(Archive *fout,
 													PQExpBuffer upgrade_buffer, Oid pg_rel_oid);
 static void binary_upgrade_set_pg_class_oids(Archive *fout,
@@ -1653,7 +1654,7 @@ selectDumpableType(TypeInfo *tyinfo, Archive *fout)
 	}
 
 	/* skip auto-generated array types */
-	if (tyinfo->isArray)
+	if (tyinfo->isArray || tyinfo->isMultirange)
 	{
 		tyinfo->dobj.objType = DO_DUMMY_TYPE;
 
@@ -4461,16 +4462,49 @@ append_depends_on_extension(Archive *fout,
 	}
 }
 
+static Oid
+get_next_possible_free_pg_type_oid(Archive *fout, PQExpBuffer upgrade_query)
+{
+	/*
+	 * If the old version didn't assign an array type, but the new version
+	 * does, we must select an unused type OID to assign.  This currently only
+	 * happens for domains, when upgrading pre-v11 to v11 and up.
+	 *
+	 * Note: local state here is kind of ugly, but we must have some, since we
+	 * mustn't choose the same unused OID more than once.
+	 */
+	static Oid	next_possible_free_oid = FirstNormalObjectId;
+	PGresult   *res;
+	bool		is_dup;
+
+	do
+	{
+		++next_possible_free_oid;
+		printfPQExpBuffer(upgrade_query,
+						  "SELECT EXISTS(SELECT 1 "
+						  "FROM pg_catalog.pg_type "
+						  "WHERE oid = '%u'::pg_catalog.oid);",
+						  next_possible_free_oid);
+		res = ExecuteSqlQueryForSingleRow(fout, upgrade_query->data);
+		is_dup = (PQgetvalue(res, 0, 0)[0] == 't');
+		PQclear(res);
+	} while (is_dup);
+
+	return next_possible_free_oid;
+}
 
 static void
 binary_upgrade_set_type_oids_by_type_oid(Archive *fout,
 										 PQExpBuffer upgrade_buffer,
 										 Oid pg_type_oid,
-										 bool force_array_type)
+										 bool force_array_type,
+										 bool include_multirange_type)
 {
 	PQExpBuffer upgrade_query = createPQExpBuffer();
 	PGresult   *res;
 	Oid			pg_type_array_oid;
+	Oid			pg_type_multirange_oid;
+	Oid			pg_type_multirange_array_oid;
 
 	appendPQExpBufferStr(upgrade_buffer, "\n-- For binary upgrade, must preserve pg_type oid\n");
 	appendPQExpBuffer(upgrade_buffer,
@@ -4491,33 +4525,7 @@ binary_upgrade_set_type_oids_by_type_oid(Archive *fout,
 	PQclear(res);
 
 	if (!OidIsValid(pg_type_array_oid) && force_array_type)
-	{
-		/*
-		 * If the old version didn't assign an array type, but the new version
-		 * does, we must select an unused type OID to assign.  This currently
-		 * only happens for domains, when upgrading pre-v11 to v11 and up.
-		 *
-		 * Note: local state here is kind of ugly, but we must have some,
-		 * since we mustn't choose the same unused OID more than once.
-		 */
-		static Oid	next_possible_free_oid = FirstNormalObjectId;
-		bool		is_dup;
-
-		do
-		{
-			++next_possible_free_oid;
-			printfPQExpBuffer(upgrade_query,
-							  "SELECT EXISTS(SELECT 1 "
-							  "FROM pg_catalog.pg_type "
-							  "WHERE oid = '%u'::pg_catalog.oid);",
-							  next_possible_free_oid);
-			res = ExecuteSqlQueryForSingleRow(fout, upgrade_query->data);
-			is_dup = (PQgetvalue(res, 0, 0)[0] == 't');
-			PQclear(res);
-		} while (is_dup);
-
-		pg_type_array_oid = next_possible_free_oid;
-	}
+		pg_type_array_oid = get_next_possible_free_pg_type_oid(fout, upgrade_query);
 
 	if (OidIsValid(pg_type_array_oid))
 	{
@@ -4528,6 +4536,46 @@ binary_upgrade_set_type_oids_by_type_oid(Archive *fout,
 						  pg_type_array_oid);
 	}
 
+	/*
+	 * Pre-set the multirange type oid and its own array type oid.
+	 */
+	if (include_multirange_type)
+	{
+		if (fout->remoteVersion >= 130000)
+		{
+			appendPQExpBuffer(upgrade_query,
+							  "SELECT t.oid, t.typarray "
+							  "FROM pg_catalog.pg_type t "
+							  "JOIN pg_catalog.pg_range r "
+							  "ON t.oid = r.rngmultitypid "
+							  "WHERE r.rngtypid = '%u'::pg_catalog.oid;",
+							  pg_type_oid);
+
+			res = ExecuteSqlQueryForSingleRow(fout, upgrade_query->data);
+
+			pg_type_multirange_oid = atooid(PQgetvalue(res, 0, PQfnumber(res, "oid")));
+			pg_type_multirange_array_oid = atooid(PQgetvalue(res, 0, PQfnumber(res, "typarray")));
+
+			PQclear(res);
+		}
+		else
+		{
+			pg_type_multirange_oid = get_next_possible_free_pg_type_oid(fout, upgrade_query);
+			pg_type_multirange_array_oid = get_next_possible_free_pg_type_oid(fout, upgrade_query);
+		}
+
+		appendPQExpBufferStr(upgrade_buffer,
+							 "\n-- For binary upgrade, must preserve multirange pg_type oid\n");
+		appendPQExpBuffer(upgrade_buffer,
+						  "SELECT pg_catalog.binary_upgrade_set_next_multirange_pg_type_oid('%u'::pg_catalog.oid);\n\n",
+						  pg_type_multirange_oid);
+		appendPQExpBufferStr(upgrade_buffer,
+							 "\n-- For binary upgrade, must preserve multirange pg_type array oid\n");
+		appendPQExpBuffer(upgrade_buffer,
+						  "SELECT pg_catalog.binary_upgrade_set_next_multirange_array_pg_type_oid('%u'::pg_catalog.oid);\n\n",
+						  pg_type_multirange_array_oid);
+	}
+
 	destroyPQExpBuffer(upgrade_query);
 }
 
@@ -4552,7 +4600,7 @@ binary_upgrade_set_type_oids_by_rel_oid(Archive *fout,
 
 	if (OidIsValid(pg_type_oid))
 		binary_upgrade_set_type_oids_by_type_oid(fout, upgrade_buffer,
-												 pg_type_oid, false);
+												 pg_type_oid, false, false);
 
 	PQclear(upgrade_res);
 	destroyPQExpBuffer(upgrade_query);
@@ -5097,6 +5145,11 @@ getTypes(Archive *fout, int *numTypes)
 		else
 			tyinfo[i].isArray = false;
 
+		if (tyinfo[i].typtype == 'm')
+			tyinfo[i].isMultirange = true;
+		else
+			tyinfo[i].isMultirange = false;
+
 		/* Decide whether we want to dump it */
 		selectDumpableType(&tyinfo[i], fout);
 
@@ -8326,9 +8379,12 @@ getProcLangs(Archive *fout, int *numProcLangs)
 
 /*
  * getCasts
- *	  get basic information about every cast in the system
+ *	  get basic information about most casts in the system
  *
  * numCasts is set to the number of casts read in
+ *
+ * Skip casts from a range to its multirange, since we'll create those
+ * automatically.
  */
 CastInfo *
 getCasts(Archive *fout, int *numCasts)
@@ -8346,7 +8402,20 @@ getCasts(Archive *fout, int *numCasts)
 	int			i_castcontext;
 	int			i_castmethod;
 
-	if (fout->remoteVersion >= 80400)
+	if (fout->remoteVersion >= 130000)
+	{
+		appendPQExpBufferStr(query, "SELECT tableoid, oid, "
+							 "castsource, casttarget, castfunc, castcontext, "
+							 "castmethod "
+							 "FROM pg_cast c "
+							 "WHERE NOT EXISTS ( "
+							 "SELECT 1 FROM pg_range r "
+							 "WHERE c.castsource = r.rngtypid "
+							 "AND c.casttarget = r.rngmultitypid "
+							 ") "
+							 "ORDER BY 3,4");
+	}
+	else if (fout->remoteVersion >= 80400)
 	{
 		appendPQExpBufferStr(query, "SELECT tableoid, oid, "
 							 "castsource, casttarget, castfunc, castcontext, "
@@ -10496,7 +10565,7 @@ dumpEnumType(Archive *fout, TypeInfo *tyinfo)
 	if (dopt->binary_upgrade)
 		binary_upgrade_set_type_oids_by_type_oid(fout, q,
 												 tyinfo->dobj.catId.oid,
-												 false);
+												 false, false);
 
 	appendPQExpBuffer(q, "CREATE TYPE %s AS ENUM (",
 					  qualtypname);
@@ -10594,7 +10663,17 @@ dumpRangeType(Archive *fout, TypeInfo *tyinfo)
 	char	   *procname;
 
 	appendPQExpBuffer(query,
-					  "SELECT pg_catalog.format_type(rngsubtype, NULL) AS rngsubtype, "
+					  "SELECT ");
+
+	if (fout->remoteVersion >= 140000)
+		appendPQExpBuffer(query,
+						  "pg_catalog.format_type(rngmultitypid, NULL) AS rngmultitype, ");
+	else
+		appendPQExpBuffer(query,
+						  "NULL AS rngmultitype, ");
+
+	appendPQExpBuffer(query,
+					  "pg_catalog.format_type(rngsubtype, NULL) AS rngsubtype, "
 					  "opc.opcname AS opcname, "
 					  "(SELECT nspname FROM pg_catalog.pg_namespace nsp "
 					  "  WHERE nsp.oid = opc.opcnamespace) AS opcnsp, "
@@ -10622,7 +10701,7 @@ dumpRangeType(Archive *fout, TypeInfo *tyinfo)
 	if (dopt->binary_upgrade)
 		binary_upgrade_set_type_oids_by_type_oid(fout, q,
 												 tyinfo->dobj.catId.oid,
-												 false);
+												 false, true);
 
 	appendPQExpBuffer(q, "CREATE TYPE %s AS RANGE (",
 					  qualtypname);
@@ -10630,6 +10709,10 @@ dumpRangeType(Archive *fout, TypeInfo *tyinfo)
 	appendPQExpBuffer(q, "\n    subtype = %s",
 					  PQgetvalue(res, 0, PQfnumber(res, "rngsubtype")));
 
+	if (PQgetvalue(res, 0, PQfnumber(res, "rngmultitype")))
+		appendPQExpBuffer(q, ",\n    multirange_type_name = %s",
+						  PQgetvalue(res, 0, PQfnumber(res, "rngmultitype")));
+
 	/* print subtype_opclass only if not default for subtype */
 	if (PQgetvalue(res, 0, PQfnumber(res, "opcdefault"))[0] != 't')
 	{
@@ -10728,7 +10811,7 @@ dumpUndefinedType(Archive *fout, TypeInfo *tyinfo)
 	if (dopt->binary_upgrade)
 		binary_upgrade_set_type_oids_by_type_oid(fout, q,
 												 tyinfo->dobj.catId.oid,
-												 false);
+												 false, false);
 
 	appendPQExpBuffer(q, "CREATE TYPE %s;\n",
 					  qualtypname);
@@ -10913,7 +10996,7 @@ dumpBaseType(Archive *fout, TypeInfo *tyinfo)
 	if (dopt->binary_upgrade)
 		binary_upgrade_set_type_oids_by_type_oid(fout, q,
 												 tyinfo->dobj.catId.oid,
-												 false);
+												 false, false);
 
 	appendPQExpBuffer(q,
 					  "CREATE TYPE %s (\n"
@@ -11103,7 +11186,8 @@ dumpDomain(Archive *fout, TypeInfo *tyinfo)
 	if (dopt->binary_upgrade)
 		binary_upgrade_set_type_oids_by_type_oid(fout, q,
 												 tyinfo->dobj.catId.oid,
-												 true); /* force array type */
+												 true,		/* force array type */
+												 false);	/* force multirange type */
 
 	qtypname = pg_strdup(fmtId(tyinfo->dobj.name));
 	qualtypname = pg_strdup(fmtQualifiedDumpable(tyinfo));
@@ -11291,7 +11375,7 @@ dumpCompositeType(Archive *fout, TypeInfo *tyinfo)
 	{
 		binary_upgrade_set_type_oids_by_type_oid(fout, q,
 												 tyinfo->dobj.catId.oid,
-												 false);
+												 false, false);
 		binary_upgrade_set_pg_class_oids(fout, q, tyinfo->typrelid, false);
 	}
 
@@ -11565,7 +11649,7 @@ dumpShellType(Archive *fout, ShellTypeInfo *stinfo)
 	if (dopt->binary_upgrade)
 		binary_upgrade_set_type_oids_by_type_oid(fout, q,
 												 stinfo->baseType->dobj.catId.oid,
-												 false);
+												 false, false);
 
 	appendPQExpBuffer(q, "CREATE TYPE %s;\n",
 					  fmtQualifiedDumpable(stinfo));
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 317bb839702..d7f77f1d3e0 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -176,6 +176,7 @@ typedef struct _typeInfo
 	char		typrelkind;		/* 'r', 'v', 'c', etc */
 	char		typtype;		/* 'b', 'c', etc */
 	bool		isArray;		/* true if auto-generated array type */
+	bool		isMultirange;	/* true if auto-generated multirange type */
 	bool		isDefined;		/* true if typisdefined */
 	/* If needed, we'll create a "shell type" entry for it; link that here: */
 	struct _shellTypeInfo *shellType;	/* shell-type entry, or NULL */
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index ec636620601..11dc98ee0a5 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -1601,6 +1601,7 @@ my %tests = (
 			\QOPERATOR 3 dump_test.~~(integer,integer);\E\n.+
 			\QCREATE TYPE dump_test.range_type_custom AS RANGE (\E\n\s+
 			\Qsubtype = integer,\E\n\s+
+			\Qmultirange_type_name = dump_test.multirange_type_custom,\E\n\s+
 			\Qsubtype_opclass = dump_test.op_class_custom\E\n
 			\Q);\E
 			/xms,
@@ -1698,6 +1699,7 @@ my %tests = (
 		regexp => qr/^
 			\QCREATE TYPE dump_test.textrange AS RANGE (\E
 			\n\s+\Qsubtype = text,\E
+			\n\s+\Qmultirange_type_name = dump_test.textmultirange,\E
 			\n\s+\Qcollation = pg_catalog."C"\E
 			\n\);/xm,
 		like =>
diff --git a/src/include/access/tupmacs.h b/src/include/access/tupmacs.h
index 70157cf90ab..c262265b951 100644
--- a/src/include/access/tupmacs.h
+++ b/src/include/access/tupmacs.h
@@ -139,8 +139,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/binary_upgrade.h b/src/include/catalog/binary_upgrade.h
index 02fecb90f79..1ff62e9f8e6 100644
--- a/src/include/catalog/binary_upgrade.h
+++ b/src/include/catalog/binary_upgrade.h
@@ -16,6 +16,8 @@
 
 extern PGDLLIMPORT Oid binary_upgrade_next_pg_type_oid;
 extern PGDLLIMPORT Oid binary_upgrade_next_array_pg_type_oid;
+extern PGDLLIMPORT Oid binary_upgrade_next_mrng_pg_type_oid;
+extern PGDLLIMPORT Oid binary_upgrade_next_mrng_array_pg_type_oid;
 
 extern PGDLLIMPORT Oid binary_upgrade_next_heap_pg_class_oid;
 extern PGDLLIMPORT Oid binary_upgrade_next_index_pg_class_oid;
diff --git a/src/include/catalog/pg_aggregate.dat b/src/include/catalog/pg_aggregate.dat
index ffabe275c00..03bab74253d 100644
--- a/src/include/catalog/pg_aggregate.dat
+++ b/src/include/catalog/pg_aggregate.dat
@@ -538,6 +538,17 @@
   aggtransfn => 'bytea_string_agg_transfn',
   aggfinalfn => 'bytea_string_agg_finalfn', aggtranstype => 'internal' },
 
+# range
+{ aggfnoid => 'range_intersect_agg(anyrange)',
+  aggtransfn => 'range_intersect_agg_transfn',
+  aggcombinefn => 'range_intersect_agg_transfn', aggtranstype => 'anyrange' },
+{ aggfnoid => 'range_intersect_agg(anymultirange)',
+  aggtransfn => 'multirange_intersect_agg_transfn',
+  aggcombinefn => 'multirange_intersect_agg_transfn', aggtranstype => 'anymultirange' },
+{ aggfnoid => 'range_agg(anyrange)', aggtransfn => 'range_agg_transfn',
+  aggfinalfn => 'range_agg_finalfn', aggfinalextra => 't',
+  aggtranstype => 'internal' },
+
 # json
 { aggfnoid => 'json_agg', aggtransfn => 'json_agg_transfn',
   aggfinalfn => 'json_agg_finalfn', aggtranstype => 'internal' },
diff --git a/src/include/catalog/pg_amop.dat b/src/include/catalog/pg_amop.dat
index 2c899f19d92..78d7d2c5232 100644
--- a/src/include/catalog/pg_amop.dat
+++ b/src/include/catalog/pg_amop.dat
@@ -1374,6 +1374,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 db3e8c2d01a..9d423d535cd 100644
--- a/src/include/catalog/pg_amproc.dat
+++ b/src/include/catalog/pg_amproc.dat
@@ -285,6 +285,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' },
 { amprocfamily => 'btree/xid8_ops', amproclefttype => 'xid8',
@@ -457,6 +459,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',
@@ -1280,12 +1287,13 @@
   amprocrighttype => 'anyrange', amprocnum => '4',
   amproc => 'brin_inclusion_union' },
 { amprocfamily => 'brin/range_inclusion_ops', amproclefttype => 'anyrange',
-  amprocrighttype => 'anyrange', amprocnum => '11', amproc => 'range_merge' },
+  amprocrighttype => 'anyrange', amprocnum => '11',
+  amproc => 'range_merge(anyrange,anyrange)' },
 { amprocfamily => 'brin/range_inclusion_ops', amproclefttype => 'anyrange',
   amprocrighttype => 'anyrange', amprocnum => '13',
   amproc => 'range_contains' },
 { amprocfamily => 'brin/range_inclusion_ops', amproclefttype => 'anyrange',
-  amprocrighttype => 'anyrange', amprocnum => '14', amproc => 'isempty' },
+  amprocrighttype => 'anyrange', amprocnum => '14', amproc => 'isempty(anyrange)' },
 
 # minmax pg_lsn
 { amprocfamily => 'brin/pg_lsn_minmax_ops', amproclefttype => 'pg_lsn',
diff --git a/src/include/catalog/pg_cast.dat b/src/include/catalog/pg_cast.dat
index 5a58f50fbb2..d6ca624addc 100644
--- a/src/include/catalog/pg_cast.dat
+++ b/src/include/catalog/pg_cast.dat
@@ -530,4 +530,17 @@
 { castsource => 'jsonb', casttarget => 'float8', castfunc => 'float8(jsonb)',
   castcontext => 'e', castmethod => 'f' },
 
+# range to multirange
+{ castsource => 'int4range', casttarget => 'int4multirange', castfunc => 'int4multirange(int4range)',
+  castcontext => 'e', castmethod => 'f' },
+{ castsource => 'int8range', casttarget => 'int8multirange', castfunc => 'int8multirange(int8range)',
+  castcontext => 'e', castmethod => 'f' },
+{ castsource => 'numrange', casttarget => 'nummultirange', castfunc => 'nummultirange(numrange)',
+  castcontext => 'e', castmethod => 'f' },
+{ castsource => 'daterange', casttarget => 'datemultirange', castfunc => 'datemultirange(daterange)',
+  castcontext => 'e', castmethod => 'f' },
+{ castsource => 'tsrange', casttarget => 'tsmultirange', castfunc => 'tsmultirange(tsrange)',
+  castcontext => 'e', castmethod => 'f' },
+{ castsource => 'tstzrange', casttarget => 'tstzmultirange', castfunc => 'tstzmultirange(tstzrange)',
+  castcontext => 'e', castmethod => 'f' },
 ]
diff --git a/src/include/catalog/pg_opclass.dat b/src/include/catalog/pg_opclass.dat
index be5712692fe..4c5e475ff7b 100644
--- a/src/include/catalog/pg_opclass.dat
+++ b/src/include/catalog/pg_opclass.dat
@@ -232,6 +232,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 7ae19235ee2..f44a826e2e9 100644
--- a/src/include/catalog/pg_operator.dat
+++ b/src/include/catalog/pg_operator.dat
@@ -3277,5 +3277,174 @@
   oprname => '@@', oprleft => 'jsonb', oprright => 'jsonpath',
   oprresult => 'bool', oprcode => 'jsonb_path_match_opr(jsonb,jsonpath)',
   oprrest => 'matchingsel', oprjoin => 'matchingjoinsel' },
+{ 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 => 'multirangesel', 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 => 'multirangesel', 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 => 'multirangesel', 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 => 'multirangesel', oprjoin => 'scalargtjoinsel' },
+{ oid => '8073', oid_symbol => 'OID_RANGE_OVERLAPS_MULTIRANGE_OP',
+  descr => 'overlaps',
+  oprname => '&&', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '&&(anymultirange,anyrange)',
+  oprcode => 'range_overlaps_multirange', oprrest => 'multirangesel',
+  oprjoin => 'areajoinsel' },
+{ oid => '8074', oid_symbol => 'OID_MULTIRANGE_OVERLAPS_RANGE_OP', descr => 'contains',
+  oprname => '&&', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcom => '&&(anyrange,anymultirange)',
+  oprcode => 'multirange_overlaps_range', oprrest => 'multirangesel',
+  oprjoin => 'areajoinsel' },
+{ oid => '8075', oid_symbol => 'OID_MULTIRANGE_OVERLAPS_MULTIRANGE_OP', descr => 'contains',
+  oprname => '&&', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '&&(anymultirange,anymultirange)',
+  oprcode => 'multirange_overlaps_multirange', oprrest => 'multirangesel',
+  oprjoin => 'areajoinsel' },
+{ oid => '8064', oid_symbol => 'OID_MULTIRANGE_CONTAINS_ELEM_OP',
+  descr => 'contains',
+  oprname => '@>', oprleft => 'anymultirange', oprright => 'anyelement',
+  oprresult => 'bool', oprcom => '<@(anyelement,anymultirange)',
+  oprcode => 'multirange_contains_elem', oprrest => 'multirangesel',
+  oprjoin => 'contjoinsel' },
+{ oid => '8065', oid_symbol => 'OID_MULTIRANGE_CONTAINS_RANGE_OP', descr => 'contains',
+  oprname => '@>', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcom => '<@(anyrange,anymultirange)',
+  oprcode => 'multirange_contains_range', oprrest => 'multirangesel',
+  oprjoin => 'contjoinsel' },
+{ oid => '8066', oid_symbol => 'OID_MULTIRANGE_CONTAINS_MULTIRANGE_OP', descr => 'contains',
+  oprname => '@>', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '<@(anymultirange,anymultirange)',
+  oprcode => 'multirange_contains_multirange', oprrest => 'multirangesel',
+  oprjoin => 'contjoinsel' },
+{ oid => '8067', oid_symbol => 'OID_MULTIRANGE_ELEM_CONTAINED_OP',
+  descr => 'is contained by',
+  oprname => '<@', oprleft => 'anyelement', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '@>(anymultirange,anyelement)',
+  oprcode => 'elem_contained_by_multirange', oprrest => 'multirangesel',
+  oprjoin => 'contjoinsel' },
+{ oid => '8068', oid_symbol => 'OID_MULTIRANGE_RANGE_CONTAINED_OP',
+  descr => 'is contained by',
+  oprname => '<@', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '@>(anymultirange,anyrange)',
+  oprcode => 'range_contained_by_multirange', oprrest => 'multirangesel',
+  oprjoin => 'contjoinsel' },
+{ oid => '8069', oid_symbol => 'OID_MULTIRANGE_MULTIRANGE_CONTAINED_OP',
+  descr => 'is contained by',
+  oprname => '<@', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '@>(anymultirange,anymultirange)',
+  oprcode => 'multirange_contained_by_multirange', oprrest => 'multirangesel',
+  oprjoin => 'contjoinsel' },
+{ oid => '8106', oid_symbol => 'OID_RANGE_OVERLAPS_LEFT_MULTIRANGE_OP',
+  descr => 'overlaps or is left of',
+  oprname => '&<', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcode => 'range_overleft_multirange', oprrest => 'multirangesel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8107', oid_symbol => 'OID_MULTIRANGE_OVERLAPS_LEFT_RANGE_OP',
+  descr => 'overlaps or is left of',
+  oprname => '&<', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcode => 'multirange_overleft_range', oprrest => 'multirangesel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8108', oid_symbol => 'OID_MULTIRANGE_OVERLAPS_LEFT_MULTIRANGE_OP',
+  descr => 'overlaps or is left of',
+  oprname => '&<', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcode => 'multirange_overleft_multirange', oprrest => 'multirangesel',
+  oprjoin => 'scalargtjoinsel' },
+{ oid => '8109', oid_symbol => 'OID_RANGE_OVERLAPS_RIGHT_MULTIRANGE_OP',
+  descr => 'overlaps or is right of',
+  oprname => '&>', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcode => 'range_overright_multirange', oprrest => 'multirangesel',
+  oprjoin => 'scalargtjoinsel' },
+{ oid => '8110', oid_symbol => 'OID_MULTIRANGE_OVERLAPS_RIGHT_RANGE_OP',
+  descr => 'overlaps or is right of',
+  oprname => '&>', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcode => 'multirange_overright_range', oprrest => 'multirangesel',
+  oprjoin => 'scalargtjoinsel' },
+{ oid => '8111', oid_symbol => 'OID_MULTIRANGE_OVERLAPS_RIGHT_MULTIRANGE_OP',
+  descr => 'overlaps or is right of',
+  oprname => '&>', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcode => 'multirange_overright_multirange', oprrest => 'multirangesel',
+  oprjoin => 'scalargtjoinsel' },
+{ oid => '8091', oid_symbol => 'OID_RANGE_ADJACENT_MULTIRANGE_OP', descr => 'is adjacent to',
+  oprname => '-|-', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '-|-(anymultirange,anyrange)',
+  oprcode => 'range_adjacent_multirange', oprrest => 'matchingsel',
+  oprjoin => 'matchingjoinsel' },
+{ oid => '8092', oid_symbol => 'OID_MULTIRANGE_ADJACENT_RANGE_OP', descr => 'is adjacent to',
+  oprname => '-|-', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcom => '-|-(anyrange,anymultirange)',
+  oprcode => 'multirange_adjacent_range', oprrest => 'matchingsel',
+  oprjoin => 'matchingjoinsel' },
+{ oid => '8093', oid_symbol => 'OID_MULTIRANGE_ADJACENT_MULTIRANGE_OP', descr => 'is adjacent to',
+  oprname => '-|-', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '-|-(anymultirange,anymultirange)',
+  oprcode => 'multirange_adjacent_multirange', oprrest => 'matchingsel',
+  oprjoin => 'matchingjoinsel' },
+{ oid => '8117', descr => 'multirange union',
+  oprname => '+', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'anymultirange', oprcom => '+(anymultirange,anymultirange)',
+  oprcode => 'multirange_union' },
+{ oid => '8123', descr => 'multirange minus',
+  oprname => '-', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'anymultirange', oprcode => 'multirange_minus' },
+{ oid => '8129', descr => 'multirange intersect',
+  oprname => '*', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'anymultirange', oprcom => '*(anymultirange,anymultirange)',
+  oprcode => 'multirange_intersect' },
+{ oid => '8082', oid_symbol => 'OID_RANGE_LEFT_MULTIRANGE_OP', descr => 'is left of',
+  oprname => '<<', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '>>(anymultirange,anyrange)',
+  oprcode => 'range_before_multirange', oprrest => 'multirangesel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8083', oid_symbol => 'OID_MULTIRANGE_LEFT_RANGE_OP', descr => 'is left of',
+  oprname => '<<', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcom => '>>(anyrange,anymultirange)',
+  oprcode => 'multirange_before_range', oprrest => 'multirangesel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8084', oid_symbol => 'OID_MULTIRANGE_LEFT_MULTIRANGE_OP', descr => 'is left of',
+  oprname => '<<', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '>>(anymultirange,anymultirange)',
+  oprcode => 'multirange_before_multirange', oprrest => 'multirangesel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8085', oid_symbol => 'OID_RANGE_RIGHT_MULTIRANGE_OP', descr => 'is right of',
+  oprname => '>>', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '<<(anymultirange,anyrange)',
+  oprcode => 'range_after_multirange', oprrest => 'multirangesel',
+  oprjoin => 'scalargtjoinsel' },
+{ oid => '8086', oid_symbol => 'OID_MULTIRANGE_RIGHT_RANGE_OP', descr => 'is right of',
+  oprname => '>>', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcom => '<<(anyrange,anymultirange)',
+  oprcode => 'multirange_after_range', oprrest => 'multirangesel',
+  oprjoin => 'scalargtjoinsel' },
+{ oid => '8087', oid_symbol => 'OID_MULTIRANGE_RIGHT_MULTIRANGE_OP', descr => 'is right of',
+  oprname => '>>', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '<<(anymultirange,anymultirange)',
+  oprcode => 'multirange_after_multirange', oprrest => 'multirangesel',
+  oprjoin => 'scalargtjoinsel' },
 
 ]
diff --git a/src/include/catalog/pg_opfamily.dat b/src/include/catalog/pg_opfamily.dat
index 11c7ad2c145..3ff81026fc7 100644
--- a/src/include/catalog/pg_opfamily.dat
+++ b/src/include/catalog/pg_opfamily.dat
@@ -232,5 +232,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 e6c7b070f64..e86ef06b9f7 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -7253,6 +7253,14 @@
   proname => 'anycompatiblerange_out', provolatile => 's',
   prorettype => 'cstring', proargtypes => 'anycompatiblerange',
   prosrc => 'anycompatiblerange_out' },
+{ oid => '8154', descr => 'I/O',
+  proname => 'anycompatiblemultirange_in', provolatile => 's',
+  prorettype => 'anycompatiblemultirange', proargtypes => 'cstring oid int4',
+  prosrc => 'anycompatiblemultirange_in' },
+{ oid => '8155', descr => 'I/O',
+  proname => 'anycompatiblemultirange_out', provolatile => 's',
+  prorettype => 'cstring', proargtypes => 'anycompatiblemultirange',
+  prosrc => 'anycompatiblemultirange_out' },
 
 # tablesample method handlers
 { oid => '3313', descr => 'BERNOULLI tablesample method handler',
@@ -9873,6 +9881,10 @@
   descr => 'the smallest range which includes both of the given ranges',
   proname => 'range_merge', prorettype => 'anyrange',
   proargtypes => 'anyrange anyrange', prosrc => 'range_merge' },
+{ oid => '8143',
+  descr => 'the smallest range which includes the whole multirange',
+  proname => 'range_merge', prorettype => 'anyrange',
+  proargtypes => 'anymultirange', prosrc => 'range_merge_from_multirange' },
 { oid => '3868',
   proname => 'range_intersect', prorettype => 'anyrange',
   proargtypes => 'anyrange anyrange', prosrc => 'range_intersect' },
@@ -9922,6 +9934,13 @@
 { oid => '3169', descr => 'restriction selectivity for range operators',
   proname => 'rangesel', provolatile => 's', prorettype => 'float8',
   proargtypes => 'internal oid internal int4', prosrc => 'rangesel' },
+{ oid => '8133', descr => 'range aggregate by intersecting',
+  proname => 'range_intersect_agg_transfn', prorettype => 'anyrange',
+  proargtypes => 'anyrange anyrange', prosrc => 'range_intersect_agg_transfn'},
+{ oid => '8134', descr => 'range aggregate by intersecting',
+  proname => 'range_intersect_agg', prokind => 'a', proisstrict => 'f',
+  prorettype => 'anyrange', proargtypes => 'anyrange',
+  prosrc => 'aggregate_dummy'},
 
 { oid => '3914', descr => 'convert an int4 range to canonical form',
   proname => 'int4range_canonical', prorettype => 'int4range',
@@ -9990,6 +10009,261 @@
   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 => '8094', descr => 'lower bound of multirange',
+  proname => 'lower', prorettype => 'anyelement', proargtypes => 'anymultirange',
+  prosrc => 'multirange_lower' },
+{ oid => '8095', descr => 'upper bound of multirange',
+  proname => 'upper', prorettype => 'anyelement', proargtypes => 'anymultirange',
+  prosrc => 'multirange_upper' },
+{ oid => '8057', descr => 'is the multirange empty?',
+  proname => 'isempty', prorettype => 'bool',
+  proargtypes => 'anymultirange', prosrc => 'multirange_empty' },
+{ oid => '8096', descr => 'is the multirange\'s lower bound inclusive?',
+  proname => 'lower_inc', prorettype => 'bool', proargtypes => 'anymultirange',
+  prosrc => 'multirange_lower_inc' },
+{ oid => '8097', descr => 'is the multirange\'s upper bound inclusive?',
+  proname => 'upper_inc', prorettype => 'bool', proargtypes => 'anymultirange',
+  prosrc => 'multirange_upper_inc' },
+{ oid => '8098', descr => 'is the multirange\'s lower bound infinite?',
+  proname => 'lower_inf', prorettype => 'bool', proargtypes => 'anymultirange',
+  prosrc => 'multirange_lower_inf' },
+{ oid => '8099', descr => 'is the multirange\'s upper bound infinite?',
+  proname => 'upper_inf', prorettype => 'bool', proargtypes => 'anymultirange',
+  prosrc => 'multirange_upper_inf' },
+{ oid => '8008', descr => 'multirange typanalyze',
+  proname => 'multirange_typanalyze', provolatile => 's', prorettype => 'bool',
+  proargtypes => 'internal', prosrc => 'multirange_typanalyze' },
+{ oid => '8156', descr => 'restriction selectivity for multirange operators',
+  proname => 'multirangesel', provolatile => 's', prorettype => 'float8',
+  proargtypes => 'internal oid internal int4', prosrc => 'multirangesel' },
+{ oid => '8033',
+  proname => 'multirange_eq', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_eq' },
+{ oid => '8034',
+  proname => 'multirange_ne', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_ne' },
+{ oid => '8070',
+  proname => 'range_overlaps_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_overlaps_multirange' },
+{ oid => '8071',
+  proname => 'multirange_overlaps_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_overlaps_range' },
+{ oid => '8072',
+  proname => 'multirange_overlaps_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_overlaps_multirange' },
+{ oid => '8058',
+  proname => 'multirange_contains_elem', prorettype => 'bool',
+  proargtypes => 'anymultirange anyelement', prosrc => 'multirange_contains_elem' },
+{ oid => '8059',
+  proname => 'multirange_contains_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_contains_range' },
+{ oid => '8060',
+  proname => 'multirange_contains_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_contains_multirange' },
+{ oid => '8061',
+  proname => 'elem_contained_by_multirange', prorettype => 'bool',
+  proargtypes => 'anyelement anymultirange', prosrc => 'elem_contained_by_multirange' },
+{ oid => '8062',
+  proname => 'range_contained_by_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_contained_by_multirange' },
+{ oid => '8063',
+  proname => 'multirange_contained_by_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_contained_by_multirange' },
+{ oid => '8088',
+  proname => 'range_adjacent_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_adjacent_multirange' },
+{ oid => '8089',
+  proname => 'multirange_adjacent_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_adjacent_multirange' },
+{ oid => '8090',
+  proname => 'multirange_adjacent_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_adjacent_range' },
+{ oid => '8076',
+  proname => 'range_before_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_before_multirange' },
+{ oid => '8077',
+  proname => 'multirange_before_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_before_range' },
+{ oid => '8078',
+  proname => 'multirange_before_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_before_multirange' },
+{ oid => '8079',
+  proname => 'range_after_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_after_multirange' },
+{ oid => '8080',
+  proname => 'multirange_after_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_after_range' },
+{ oid => '8081',
+  proname => 'multirange_after_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_after_multirange' },
+{ oid => '8100',
+  proname => 'range_overleft_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_overleft_multirange' },
+{ oid => '8101',
+  proname => 'multirange_overleft_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_overleft_range' },
+{ oid => '8102',
+  proname => 'multirange_overleft_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_overleft_multirange' },
+{ oid => '8103',
+  proname => 'range_overright_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_overright_multirange' },
+{ oid => '8104',
+  proname => 'multirange_overright_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_overright_range' },
+{ oid => '8105',
+  proname => 'multirange_overright_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_overright_multirange' },
+{ oid => '8114',
+  proname => 'multirange_union', prorettype => 'anymultirange',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_union' },
+{ oid => '8120',
+  proname => 'multirange_minus', prorettype => 'anymultirange',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_minus' },
+{ oid => '8126',
+  proname => 'multirange_intersect', prorettype => 'anymultirange',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_intersect' },
+{ oid => '8022', descr => 'less-equal-greater',
+  proname => 'multirange_cmp', prorettype => 'int4',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_cmp' },
+{ oid => '8023',
+  proname => 'multirange_lt', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_lt' },
+{ oid => '8024',
+  proname => 'multirange_le', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_le' },
+{ oid => '8025',
+  proname => 'multirange_ge', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_ge' },
+{ oid => '8026',
+  proname => 'multirange_gt', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_gt' },
+{ oid => '8038', descr => 'hash a multirange',
+  proname => 'hash_multirange', prorettype => 'int4', proargtypes => 'anymultirange',
+  prosrc => 'hash_multirange' },
+{ oid => '8039', descr => 'hash a multirange',
+  proname => 'hash_multirange_extended', prorettype => 'int8',
+  proargtypes => 'anymultirange int8', prosrc => 'hash_multirange_extended' },
+
+{ oid => '8045', descr => 'int4multirange constructor',
+  proname => 'int4multirange', proisstrict => 'f',
+  prorettype => 'int4multirange', proargtypes => '',
+  prosrc => 'multirange_constructor0' },
+{ oid => '8146', descr => 'int4multirange constructor',
+  proname => 'int4multirange', proisstrict => 't',
+  prorettype => 'int4multirange', proargtypes => 'int4range',
+  prosrc => 'multirange_constructor1' },
+{ oid => '8046', descr => 'int4multirange constructor',
+  proname => 'int4multirange', provariadic => 'int4range', proisstrict => 'f',
+  prorettype => 'int4multirange', proargtypes => '_int4range',
+  proallargtypes => '{_int4range}', proargmodes => '{v}',
+  prosrc => 'multirange_constructor2' },
+{ oid => '8047', descr => 'nummultirange constructor',
+  proname => 'nummultirange', proisstrict => 'f',
+  prorettype => 'nummultirange', proargtypes => '',
+  prosrc => 'multirange_constructor0' },
+{ oid => '8147', descr => 'nummultirange constructor',
+  proname => 'nummultirange', proisstrict => 't',
+  prorettype => 'nummultirange', proargtypes => 'numrange',
+  prosrc => 'multirange_constructor1' },
+{ oid => '8048', descr => 'nummultirange constructor',
+  proname => 'nummultirange', provariadic => 'numrange', proisstrict => 'f',
+  prorettype => 'nummultirange', proargtypes => '_numrange',
+  proallargtypes => '{_numrange}', proargmodes => '{v}',
+  prosrc => 'multirange_constructor2' },
+{ oid => '8049', descr => 'tsmultirange constructor',
+  proname => 'tsmultirange', proisstrict => 'f',
+  prorettype => 'tsmultirange', proargtypes => '',
+  prosrc => 'multirange_constructor0' },
+{ oid => '8148', descr => 'tsmultirange constructor',
+  proname => 'tsmultirange', proisstrict => 't',
+  prorettype => 'tsmultirange', proargtypes => 'tsrange',
+  prosrc => 'multirange_constructor1' },
+{ oid => '8050', descr => 'tsmultirange constructor',
+  proname => 'tsmultirange', provariadic => 'tsrange', proisstrict => 'f',
+  prorettype => 'tsmultirange', proargtypes => '_tsrange',
+  proallargtypes => '{_tsrange}', proargmodes => '{v}',
+  prosrc => 'multirange_constructor2' },
+{ oid => '8051', descr => 'tstzmultirange constructor',
+  proname => 'tstzmultirange', proisstrict => 'f',
+  prorettype => 'tstzmultirange', proargtypes => '',
+  prosrc => 'multirange_constructor0' },
+{ oid => '8149', descr => 'tstzmultirange constructor',
+  proname => 'tstzmultirange', proisstrict => 't',
+  prorettype => 'tstzmultirange', proargtypes => 'tstzrange',
+  prosrc => 'multirange_constructor1' },
+{ oid => '8052', descr => 'tstzmultirange constructor',
+  proname => 'tstzmultirange', provariadic => 'tstzrange', proisstrict => 'f',
+  prorettype => 'tstzmultirange', proargtypes => '_tstzrange',
+  proallargtypes => '{_tstzrange}', proargmodes => '{v}',
+  prosrc => 'multirange_constructor2' },
+{ oid => '8053', descr => 'datemultirange constructor',
+  proname => 'datemultirange', proisstrict => 'f',
+  prorettype => 'datemultirange', proargtypes => '',
+  prosrc => 'multirange_constructor0' },
+{ oid => '8150', descr => 'datemultirange constructor',
+  proname => 'datemultirange', proisstrict => 't',
+  prorettype => 'datemultirange', proargtypes => 'daterange',
+  prosrc => 'multirange_constructor1' },
+{ oid => '8054', descr => 'datemultirange constructor',
+  proname => 'datemultirange', provariadic => 'daterange', proisstrict => 'f',
+  prorettype => 'datemultirange', proargtypes => '_daterange',
+  proallargtypes => '{_daterange}', proargmodes => '{v}',
+  prosrc => 'multirange_constructor2' },
+{ oid => '8055', descr => 'int8multirange constructor',
+  proname => 'int8multirange', proisstrict => 'f',
+  prorettype => 'int8multirange', proargtypes => '',
+  prosrc => 'multirange_constructor0' },
+{ oid => '8151', descr => 'int8multirange constructor',
+  proname => 'int8multirange', proisstrict => 't',
+  prorettype => 'int8multirange', proargtypes => 'int8range',
+  prosrc => 'multirange_constructor1' },
+{ oid => '8056', descr => 'int8multirange constructor',
+  proname => 'int8multirange', provariadic => 'int8range', proisstrict => 'f',
+  prorettype => 'int8multirange', proargtypes => '_int8range',
+  proallargtypes => '{_int8range}', proargmodes => '{v}',
+  prosrc => 'multirange_constructor2' },
+{ oid => '8152', descr => 'anymultirange cast',
+  proname => 'multirange', proisstrict => 't',
+  prorettype => 'anymultirange', proargtypes => 'anyrange',
+  prosrc => 'multirange_constructor1' },
+{ oid => '8130', descr => 'aggregate transition function',
+  proname => 'range_agg_transfn', proisstrict => 'f', prorettype => 'internal',
+  proargtypes => 'internal anyrange', prosrc => 'range_agg_transfn' },
+{ oid => '8131', descr => 'aggregate final function',
+  proname => 'range_agg_finalfn', proisstrict => 'f', prorettype => 'anymultirange',
+  proargtypes => 'internal anyrange', prosrc => 'range_agg_finalfn' },
+{ oid => '8132', descr => 'combine aggregate input into a multirange',
+  proname => 'range_agg', prokind => 'a', proisstrict => 'f',
+  prorettype => 'anymultirange', proargtypes => 'anyrange',
+  prosrc => 'aggregate_dummy' },
+{ oid => '8135', descr => 'range aggregate by intersecting',
+  proname => 'multirange_intersect_agg_transfn', prorettype => 'anymultirange',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_intersect_agg_transfn'},
+{ oid => '8136', descr => 'range aggregate by intersecting',
+  proname => 'range_intersect_agg', prokind => 'a', proisstrict => 'f',
+  prorettype => 'anymultirange', proargtypes => 'anymultirange',
+  prosrc => 'aggregate_dummy'},
+
 # date, time, timestamp constructors
 { oid => '3846', descr => 'construct date',
   proname => 'make_date', prorettype => 'date', proargtypes => 'int4 int4 int4',
@@ -10372,6 +10646,14 @@
   proname => 'binary_upgrade_set_next_array_pg_type_oid', provolatile => 'v',
   proparallel => 'r', prorettype => 'void', proargtypes => 'oid',
   prosrc => 'binary_upgrade_set_next_array_pg_type_oid' },
+{ oid => '8144', descr => 'for use by pg_upgrade',
+  proname => 'binary_upgrade_set_next_multirange_pg_type_oid', provolatile => 'v',
+  proparallel => 'r', prorettype => 'void', proargtypes => 'oid',
+  prosrc => 'binary_upgrade_set_next_multirange_pg_type_oid' },
+{ oid => '8145', descr => 'for use by pg_upgrade',
+  proname => 'binary_upgrade_set_next_multirange_array_pg_type_oid', provolatile => 'v',
+  proparallel => 'r', prorettype => 'void', proargtypes => 'oid',
+  prosrc => 'binary_upgrade_set_next_multirange_array_pg_type_oid' },
 { oid => '3586', descr => 'for use by pg_upgrade',
   proname => 'binary_upgrade_set_next_heap_pg_class_oid', provolatile => 'v',
   proparallel => 'r', prorettype => 'void', proargtypes => 'oid',
diff --git a/src/include/catalog/pg_range.dat b/src/include/catalog/pg_range.dat
index 479754c2455..10060255c95 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',
+  rngmultitypid => 'int4multirange' },
 { rngtypid => 'numrange', rngsubtype => 'numeric',
   rngsubopc => 'btree/numeric_ops', rngcanonical => '-',
-  rngsubdiff => 'numrange_subdiff' },
+  rngsubdiff => 'numrange_subdiff', rngmultitypid => 'nummultirange' },
 { rngtypid => 'tsrange', rngsubtype => 'timestamp',
   rngsubopc => 'btree/timestamp_ops', rngcanonical => '-',
-  rngsubdiff => 'tsrange_subdiff' },
+  rngsubdiff => 'tsrange_subdiff', rngmultitypid => 'tsmultirange' },
 { rngtypid => 'tstzrange', rngsubtype => 'timestamptz',
   rngsubopc => 'btree/timestamptz_ops', rngcanonical => '-',
-  rngsubdiff => 'tstzrange_subdiff' },
+  rngsubdiff => 'tstzrange_subdiff', rngmultitypid => 'tstzmultirange' },
 { rngtypid => 'daterange', rngsubtype => 'date', rngsubopc => 'btree/date_ops',
-  rngcanonical => 'daterange_canonical', rngsubdiff => 'daterange_subdiff' },
+  rngcanonical => 'daterange_canonical', rngsubdiff => 'daterange_subdiff',
+  rngmultitypid => 'datemultirange' },
 { rngtypid => 'int8range', rngsubtype => 'int8', rngsubopc => 'btree/int8_ops',
-  rngcanonical => 'int8range_canonical', rngsubdiff => 'int8range_subdiff' },
+  rngcanonical => 'int8range_canonical', rngsubdiff => 'int8range_subdiff',
+  rngmultitypid => 'int8multirange' },
 
 ]
diff --git a/src/include/catalog/pg_range.h b/src/include/catalog/pg_range.h
index c28bb57b6c9..07e6dbba667 100644
--- a/src/include/catalog/pg_range.h
+++ b/src/include/catalog/pg_range.h
@@ -34,6 +34,9 @@ CATALOG(pg_range,3541,RangeRelationId)
 	/* OID of range's element type (subtype) */
 	Oid			rngsubtype BKI_LOOKUP(pg_type);
 
+	/* OID of the range's multirange type */
+	Oid			rngmultitypid BKI_LOOKUP(pg_type);
+
 	/* collation for this range type, or 0 */
 	Oid			rngcollation BKI_DEFAULT(0);
 
@@ -57,13 +60,17 @@ typedef FormData_pg_range *Form_pg_range;
 DECLARE_UNIQUE_INDEX(pg_range_rngtypid_index, 3542, on pg_range using btree(rngtypid oid_ops));
 #define RangeTypidIndexId					3542
 
+DECLARE_UNIQUE_INDEX(pg_range_rngmultitypid_index, 8001, on pg_range using btree(rngmultitypid oid_ops));
+#define RangeMultirangeTypidIndexId			8001
+
+
 /*
  * prototypes for functions in pg_range.c
  */
 
 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 28240bdce39..da8c5a3e9b3 100644
--- a/src/include/catalog/pg_type.dat
+++ b/src/include/catalog/pg_type.dat
@@ -496,6 +496,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 => 'R', 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 => 'R', 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 => 'R', 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 => 'R', 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 => 'R', 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 => 'R', 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
@@ -626,5 +660,16 @@
   typtype => 'p', typcategory => 'P', typinput => 'anycompatiblerange_in',
   typoutput => 'anycompatiblerange_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' },
+{ oid => '8153',
+  descr => 'pseudo-type representing a multirange over a polymorphic common type',
+  typname => 'anycompatiblemultirange', typlen => '-1', typbyval => 'f',
+  typtype => 'p', typcategory => 'P', typinput => 'anycompatiblemultirange_in',
+  typoutput => 'anycompatiblemultirange_out', typreceive => '-', typsend => '-',
+  typalign => 'd', typstorage => 'x' },
 
 ]
diff --git a/src/include/catalog/pg_type.h b/src/include/catalog/pg_type.h
index 70563a6408b..545b789608a 100644
--- a/src/include/catalog/pg_type.h
+++ b/src/include/catalog/pg_type.h
@@ -276,6 +276,7 @@ DECLARE_UNIQUE_INDEX(pg_type_typname_nsp_index, 2704, on pg_type using btree(typ
 #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 */
 
@@ -317,13 +318,15 @@ DECLARE_UNIQUE_INDEX(pg_type_typname_nsp_index, 2704, on pg_type using btree(typ
 	 (typid) == ANYARRAYOID || \
 	 (typid) == ANYNONARRAYOID || \
 	 (typid) == ANYENUMOID || \
-	 (typid) == ANYRANGEOID)
+	 (typid) == ANYRANGEOID || \
+	 (typid) == ANYMULTIRANGEOID)
 
 #define IsPolymorphicTypeFamily2(typid)  \
 	((typid) == ANYCOMPATIBLEOID || \
 	 (typid) == ANYCOMPATIBLEARRAYOID || \
 	 (typid) == ANYCOMPATIBLENONARRAYOID || \
-	 (typid) == ANYCOMPATIBLERANGEOID)
+	 (typid) == ANYCOMPATIBLERANGEOID || \
+	 (typid) == ANYCOMPATIBLEMULTIRANGEOID)
 
 /* Is this a "true" array type?  (Requires fmgroids.h) */
 #define IsTrueArrayType(typeForm)  \
@@ -397,4 +400,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/commands/typecmds.h b/src/include/commands/typecmds.h
index 23130895af4..989914dfe11 100644
--- a/src/include/commands/typecmds.h
+++ b/src/include/commands/typecmds.h
@@ -29,6 +29,8 @@ extern ObjectAddress DefineRange(CreateRangeStmt *stmt);
 extern ObjectAddress AlterEnum(AlterEnumStmt *stmt);
 extern ObjectAddress DefineCompositeType(RangeVar *typevar, List *coldeflist);
 extern Oid	AssignTypeArrayOid(void);
+extern Oid	AssignTypeMultirangeOid(void);
+extern Oid	AssignTypeMultirangeArrayOid(void);
 
 extern ObjectAddress AlterDomainDefault(List *names, Node *defaultRaw);
 extern ObjectAddress AlterDomainNotNull(List *names, bool notNull);
diff --git a/src/include/utils/lsyscache.h b/src/include/utils/lsyscache.h
index a990d11ea86..e5ad7b95d17 100644
--- a/src/include/utils/lsyscache.h
+++ b/src/include/utils/lsyscache.h
@@ -161,6 +161,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);
@@ -190,6 +191,8 @@ 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_collation(Oid rangeOid);
+extern Oid	get_range_multirange(Oid rangeOid);
+extern Oid	get_multirange_range(Oid multirangeOid);
 extern Oid	get_index_column_opclass(Oid index_oid, int attno);
 extern bool get_index_isreplident(Oid index_oid);
 extern bool get_index_isvalid(Oid index_oid);
diff --git a/src/include/utils/multirangetypes.h b/src/include/utils/multirangetypes.h
new file mode 100644
index 00000000000..16a3e4410dc
--- /dev/null
+++ b/src/include/utils/multirangetypes.h
@@ -0,0 +1,111 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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"
+#include "utils/expandeddatum.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
+{
+	int32		vl_len_;		/* varlena header (do not touch directly!) */
+	Oid			multirangetypid;	/* multirange type's own OID */
+	uint32		rangeCount;		/* the number of ranges */
+
+	/*
+	 * Following the count are the range objects themselves, as ShortRangeType
+	 * structs. 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;
+
+#define MultirangeGetOffsetsPtr(mr) ((uint32 *)((Pointer) (mr) + sizeof(MultirangeType)))
+#define MultirangeGetFlagsPtr(mr) ((uint8 *)((Pointer) (mr) + sizeof(MultirangeType) + ((mr)->rangeCount - 1) * sizeof(uint32)))
+#define MultirangeGetBoundariesPtr(mr, align) ((Pointer) (mr) + att_align_nominal(sizeof(MultirangeType) + ((mr)->rangeCount - 1) * sizeof(uint32) + (mr)->rangeCount * sizeof(uint8), (align)))
+
+/* Use these macros in preference to accessing these fields 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);
+extern bool multirange_contains_elem_internal(TypeCacheEntry *typcache, MultirangeType *mr,
+											  Datum elem);
+extern bool multirange_contains_range_internal(TypeCacheEntry *typcache, MultirangeType *mr,
+											   RangeType *r);
+extern bool multirange_contains_multirange_internal(TypeCacheEntry *typcache,
+													MultirangeType *mr1,
+													MultirangeType *mr2);
+extern bool range_overlaps_multirange_internal(TypeCacheEntry *typcache, RangeType *r,
+											   MultirangeType *mr);
+extern bool multirange_overlaps_multirange_internal(TypeCacheEntry *typcache,
+													MultirangeType *mr1,
+													MultirangeType *mr2);
+extern bool range_before_multirange_internal(TypeCacheEntry *typcache, RangeType *r,
+											 MultirangeType *mr);
+extern bool range_after_multirange_internal(TypeCacheEntry *typcache, RangeType *r,
+											MultirangeType *mr);
+extern bool range_adjacent_multirange_internal(TypeCacheEntry *typcache, RangeType *r,
+											MultirangeType *mr);
+extern bool multirange_before_multirange_internal(TypeCacheEntry *typcache,
+												  MultirangeType *mr1,
+												  MultirangeType *mr2);
+extern MultirangeType *multirange_minus_internal(Oid mltrngtypoid,
+												 TypeCacheEntry *rangetyp,
+												 int32 range_count1,
+												 RangeType **ranges1,
+												 int32 range_count2,
+												 RangeType **ranges2);
+extern MultirangeType *multirange_intersect_internal(Oid mltrngtypoid,
+													 TypeCacheEntry *rangetyp,
+													 int32 range_count1,
+													 RangeType **ranges1,
+													 int32 range_count2,
+													 RangeType **ranges2);
+
+/* assorted support functions */
+extern TypeCacheEntry *multirange_get_typcache(FunctionCallInfo fcinfo,
+											   Oid mltrngtypid);
+extern void multirange_deserialize(TypeCacheEntry *rangetyp,
+								   const 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 b77c41cf1b8..139e19c7d66 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)
 
@@ -92,13 +94,21 @@ typedef struct
  * prototypes for functions defined in rangetypes.c
  */
 
-extern bool range_contains_elem_internal(TypeCacheEntry *typcache, const RangeType *r, Datum val);
-
 /* internal versions of the above */
+extern int	range_cmp_internal(TypeCacheEntry *typcache, const RangeType *r1,
+							   const RangeType *r2);
+extern uint32 hash_range_internal(TypeCacheEntry *typcache, const RangeType *r);
+extern uint64 hash_range_extended_internal(TypeCacheEntry *typcache, const RangeType *r,
+										   Datum seed);
+extern Datum range_lower_internal(TypeCacheEntry *typcache, const RangeType *r1,
+								  bool *isnull);
+extern Datum range_upper_internal(TypeCacheEntry *typcache, const RangeType *r1,
+								  bool *isnull);
 extern bool range_eq_internal(TypeCacheEntry *typcache, const RangeType *r1,
 							  const RangeType *r2);
 extern bool range_ne_internal(TypeCacheEntry *typcache, const RangeType *r1,
 							  const RangeType *r2);
+extern bool range_contains_elem_internal(TypeCacheEntry *typcache, const RangeType *r, Datum val);
 extern bool range_contains_internal(TypeCacheEntry *typcache, const RangeType *r1,
 									const RangeType *r2);
 extern bool range_contained_by_internal(TypeCacheEntry *typcache, const RangeType *r1,
@@ -115,8 +125,18 @@ extern bool range_overleft_internal(TypeCacheEntry *typcache, const RangeType *r
 									const RangeType *r2);
 extern bool range_overright_internal(TypeCacheEntry *typcache, const RangeType *r1,
 									 const RangeType *r2);
+extern RangeType *range_union_internal(TypeCacheEntry *typcache, RangeType *r1,
+									   RangeType *r2, bool strict);
+extern RangeType *range_minus_internal(TypeCacheEntry *typcache, RangeType *r1,
+									   RangeType *r2);
+extern RangeType *range_intersect_internal(TypeCacheEntry *typcache, const RangeType *r1,
+										   const RangeType *r2);
 
 /* assorted support functions */
+extern Size datum_compute_size(Size sz, Datum datum, bool typbyval,
+							   char typalign, int16 typlen, char typstorage);
+extern Pointer datum_write(Pointer ptr, Datum datum, bool typbyval,
+						   char typalign, int16 typlen, char typstorage);
 extern TypeCacheEntry *range_get_typcache(FunctionCallInfo fcinfo,
 										  Oid rngtypid);
 extern RangeType *range_serialize(TypeCacheEntry *typcache, RangeBound *lower,
@@ -125,6 +145,7 @@ extern void range_deserialize(TypeCacheEntry *typcache, const RangeType *range,
 							  RangeBound *lower, RangeBound *upper,
 							  bool *empty);
 extern char range_get_flags(const RangeType *range);
+extern bool range_has_flag(const RangeType *range, char flag);
 extern void range_set_contain_empty(RangeType *range);
 extern RangeType *make_range(TypeCacheEntry *typcache, RangeBound *lower,
 							 RangeBound *upper, bool empty);
@@ -132,8 +153,12 @@ extern int	range_cmp_bounds(TypeCacheEntry *typcache, const RangeBound *b1,
 							 const RangeBound *b2);
 extern int	range_cmp_bound_values(TypeCacheEntry *typcache, const RangeBound *b1,
 								   const RangeBound *b2);
+extern int	range_compare(const void *key1, const void *key2, void *arg);
 extern bool bounds_adjacent(TypeCacheEntry *typcache, RangeBound bound1,
 							RangeBound bound2);
 extern RangeType *make_empty_range(TypeCacheEntry *typcache);
+extern bool range_split_internal(TypeCacheEntry *typcache, const RangeType *r1,
+								 const RangeType *r2, RangeType **output1,
+								 RangeType **output2);
 
 #endif							/* RANGETYPES_H */
diff --git a/src/include/utils/selfuncs.h b/src/include/utils/selfuncs.h
index 3a2cfb7efa6..68ffd7761f1 100644
--- a/src/include/utils/selfuncs.h
+++ b/src/include/utils/selfuncs.h
@@ -39,6 +39,9 @@
 /* default selectivity estimate for range inequalities "A > b AND A < c" */
 #define DEFAULT_RANGE_INEQ_SEL	0.005
 
+/* default selectivity estimate for multirange inequalities "A > b AND A < c" */
+#define DEFAULT_MULTIRANGE_INEQ_SEL	0.005
+
 /* default selectivity estimate for pattern-match operators such as LIKE */
 #define DEFAULT_MATCH_SEL	0.005
 
diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h
index f27b73d76d0..e8f393520a3 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -79,6 +79,7 @@ enum SysCacheIdentifier
 	PUBLICATIONOID,
 	PUBLICATIONREL,
 	PUBLICATIONRELMAP,
+	RANGEMULTIRANGE,
 	RANGETYPE,
 	RELNAMENSP,
 	RELOID,
diff --git a/src/include/utils/typcache.h b/src/include/utils/typcache.h
index 38c8fe01929..dd69a06342f 100644
--- a/src/include/utils/typcache.h
+++ b/src/include/utils/typcache.h
@@ -101,6 +101,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; /* multirange's range underlying type */
+
 	/*
 	 * Domain's base type and typmod if it's a domain type.  Zeroes if not
 	 * domain, or if information hasn't been requested.
@@ -128,22 +133,23 @@ typedef struct TypeCacheEntry
 } TypeCacheEntry;
 
 /* Bit flags to indicate which fields a given caller needs to have set */
-#define TYPECACHE_EQ_OPR			0x0001
-#define TYPECACHE_LT_OPR			0x0002
-#define TYPECACHE_GT_OPR			0x0004
-#define TYPECACHE_CMP_PROC			0x0008
-#define TYPECACHE_HASH_PROC			0x0010
-#define TYPECACHE_EQ_OPR_FINFO		0x0020
-#define TYPECACHE_CMP_PROC_FINFO	0x0040
-#define TYPECACHE_HASH_PROC_FINFO	0x0080
-#define TYPECACHE_TUPDESC			0x0100
-#define TYPECACHE_BTREE_OPFAMILY	0x0200
-#define TYPECACHE_HASH_OPFAMILY		0x0400
-#define TYPECACHE_RANGE_INFO		0x0800
-#define TYPECACHE_DOMAIN_BASE_INFO			0x1000
-#define TYPECACHE_DOMAIN_CONSTR_INFO		0x2000
-#define TYPECACHE_HASH_EXTENDED_PROC		0x4000
-#define TYPECACHE_HASH_EXTENDED_PROC_FINFO	0x8000
+#define TYPECACHE_EQ_OPR			0x00001
+#define TYPECACHE_LT_OPR			0x00002
+#define TYPECACHE_GT_OPR			0x00004
+#define TYPECACHE_CMP_PROC			0x00008
+#define TYPECACHE_HASH_PROC			0x00010
+#define TYPECACHE_EQ_OPR_FINFO		0x00020
+#define TYPECACHE_CMP_PROC_FINFO	0x00040
+#define TYPECACHE_HASH_PROC_FINFO	0x00080
+#define TYPECACHE_TUPDESC			0x00100
+#define TYPECACHE_BTREE_OPFAMILY	0x00200
+#define TYPECACHE_HASH_OPFAMILY		0x00400
+#define TYPECACHE_RANGE_INFO		0x00800
+#define TYPECACHE_DOMAIN_BASE_INFO			0x01000
+#define TYPECACHE_DOMAIN_CONSTR_INFO		0x02000
+#define TYPECACHE_HASH_EXTENDED_PROC		0x04000
+#define TYPECACHE_HASH_EXTENDED_PROC_FINFO	0x08000
+#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 555da952e1b..042deb2a96c 100644
--- a/src/pl/plpgsql/src/pl_comp.c
+++ b/src/pl/plpgsql/src/pl_comp.c
@@ -515,6 +515,8 @@ do_compile(FunctionCallInfo fcinfo,
 					else if (rettypeid == ANYRANGEOID ||
 							 rettypeid == ANYCOMPATIBLERANGEOID)
 						rettypeid = INT4RANGEOID;
+					else if (rettypeid == ANYMULTIRANGEOID)
+						rettypeid = INT4MULTIRANGEOID;
 					else		/* ANYELEMENT or ANYNONARRAY or ANYCOMPATIBLE */
 						rettypeid = INT4OID;
 					/* XXX what could we use for ANYENUM? */
@@ -2109,6 +2111,7 @@ build_datatype(HeapTuple typeTup, int32 typmod,
 		case TYPTYPE_BASE:
 		case TYPTYPE_ENUM:
 		case TYPTYPE_RANGE:
+		case TYPTYPE_MULTIRANGE:
 			typ->ttype = PLPGSQL_TTYPE_SCALAR;
 			break;
 		case TYPTYPE_COMPOSITE:
@@ -2526,6 +2529,9 @@ plpgsql_resolve_polymorphic_argtypes(int numargs,
 				case ANYCOMPATIBLERANGEOID:
 					argtypes[i] = INT4RANGEOID;
 					break;
+				case ANYMULTIRANGEOID:
+					argtypes[i] = INT4MULTIRANGEOID;
+					break;
 				default:
 					break;
 			}
diff --git a/src/test/regress/expected/dependency.out b/src/test/regress/expected/dependency.out
index b4a11b8aa9b..82327951487 100644
--- a/src/test/regress/expected/dependency.out
+++ b/src/test/regress/expected/dependency.out
@@ -140,6 +140,7 @@ owner of sequence deptest_a_seq
 owner of table deptest
 owner of function deptest_func()
 owner of type deptest_enum
+owner of type deptest_multirange
 owner of type deptest_range
 owner of table deptest2
 owner of sequence ss1
diff --git a/src/test/regress/expected/hash_func.out b/src/test/regress/expected/hash_func.out
index 827e69fc7ab..dca31365bcf 100644
--- a/src/test/regress/expected/hash_func.out
+++ b/src/test/regress/expected/hash_func.out
@@ -305,6 +305,19 @@ 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)
+
 CREATE TYPE hash_test_t1 AS (a int, b text);
 SELECT v as value, hash_record(v)::bit(32) as standard,
        hash_record_extended(v, 0)::bit(32) as extended0,
diff --git a/src/test/regress/expected/multirangetypes.out b/src/test/regress/expected/multirangetypes.out
new file mode 100644
index 00000000000..fa34fcd5bcb
--- /dev/null
+++ b/src/test/regress/expected/multirangetypes.out
@@ -0,0 +1,2443 @@
+-- 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)
+
+--
+-- test casts, both a built-in range type and a user-defined one:
+--
+select 'empty'::int4range::int4multirange;
+ int4multirange 
+----------------
+ {}
+(1 row)
+
+select int4range(1, 3)::int4multirange;
+ int4range 
+-----------
+ {[1,3)}
+(1 row)
+
+select int4range(1, null)::int4multirange;
+ int4range 
+-----------
+ {[1,)}
+(1 row)
+
+select int4range(null, null)::int4multirange;
+ int4range 
+-----------
+ {(,)}
+(1 row)
+
+select 'empty'::textrange::textmultirange;
+ textmultirange 
+----------------
+ {}
+(1 row)
+
+select textrange('a', 'c')::textmultirange;
+ textrange 
+-----------
+ {[a,c)}
+(1 row)
+
+select textrange('a', null)::textmultirange;
+ textrange 
+-----------
+ {[a,)}
+(1 row)
+
+select textrange(null, null)::textmultirange;
+ textrange 
+-----------
+ {(,)}
+(1 row)
+
+--
+-- create some test data and test the operators
+--
+CREATE TABLE nummultirange_test (nmr NUMMULTIRANGE);
+CREATE INDEX nummultirange_test_btree ON nummultirange_test(nmr);
+INSERT INTO nummultirange_test VALUES('{}');
+INSERT INTO nummultirange_test VALUES('{[,)}');
+INSERT INTO nummultirange_test VALUES('{[3,]}');
+INSERT INTO nummultirange_test VALUES('{[,), [3,]}');
+INSERT INTO nummultirange_test VALUES('{[, 5)}');
+INSERT INTO nummultirange_test VALUES(nummultirange());
+INSERT INTO nummultirange_test VALUES(nummultirange(variadic '{}'::numrange[]));
+INSERT INTO nummultirange_test VALUES(nummultirange(numrange(1.1, 2.2)));
+INSERT INTO nummultirange_test VALUES('{empty}');
+INSERT INTO nummultirange_test VALUES(nummultirange(numrange(1.7, 1.7, '[]'), numrange(1.7, 1.9)));
+INSERT INTO nummultirange_test VALUES(nummultirange(numrange(1.7, 1.7, '[]'), numrange(1.9, 2.1)));
+SELECT nmr, isempty(nmr), lower(nmr), upper(nmr) FROM nummultirange_test ORDER BY nmr;
+          nmr          | isempty | lower | upper 
+-----------------------+---------+-------+-------
+ {}                    | t       |       |      
+ {}                    | t       |       |      
+ {}                    | t       |       |      
+ {}                    | t       |       |      
+ {(,5)}                | f       |       |     5
+ {(,)}                 | f       |       |      
+ {(,)}                 | f       |       |      
+ {[1.1,2.2)}           | f       |   1.1 |   2.2
+ {[1.7,1.7],[1.9,2.1)} | f       |   1.7 |   2.1
+ {[1.7,1.9)}           | f       |   1.7 |   1.9
+ {[3,)}                | f       |     3 |      
+(11 rows)
+
+SELECT nmr, lower_inc(nmr), lower_inf(nmr), upper_inc(nmr), upper_inf(nmr) FROM nummultirange_test ORDER BY nmr;
+          nmr          | lower_inc | lower_inf | upper_inc | upper_inf 
+-----------------------+-----------+-----------+-----------+-----------
+ {}                    | f         | f         | f         | f
+ {}                    | f         | f         | f         | f
+ {}                    | f         | f         | f         | f
+ {}                    | f         | f         | f         | f
+ {(,5)}                | f         | t         | f         | f
+ {(,)}                 | f         | t         | f         | t
+ {(,)}                 | f         | t         | f         | t
+ {[1.1,2.2)}           | t         | f         | f         | f
+ {[1.7,1.7],[1.9,2.1)} | t         | f         | f         | f
+ {[1.7,1.9)}           | t         | f         | f         | f
+ {[3,)}                | t         | f         | f         | t
+(11 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr = '{}';
+ nmr 
+-----
+ {}
+ {}
+ {}
+ {}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr = '{(,5)}';
+  nmr   
+--------
+ {(,5)}
+(1 row)
+
+SELECT * FROM nummultirange_test WHERE nmr = '{[3,)}';
+  nmr   
+--------
+ {[3,)}
+(1 row)
+
+SELECT * FROM nummultirange_test WHERE nmr = '{[1.7,1.7]}';
+ nmr 
+-----
+(0 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr = '{[1.7,1.7],[1.9,2.1)}';
+          nmr          
+-----------------------
+ {[1.7,1.7],[1.9,2.1)}
+(1 row)
+
+SELECT * FROM nummultirange_test WHERE nmr < '{}';
+ nmr 
+-----
+(0 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr < '{[-1000.0, -1000.0]}';
+  nmr   
+--------
+ {}
+ {(,)}
+ {(,)}
+ {(,5)}
+ {}
+ {}
+ {}
+(7 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr < '{[0.0, 1.0]}';
+  nmr   
+--------
+ {}
+ {(,)}
+ {(,)}
+ {(,5)}
+ {}
+ {}
+ {}
+(7 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr < '{[1000.0, 1001.0]}';
+          nmr          
+-----------------------
+ {}
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+ {}
+ {}
+ {[1.1,2.2)}
+ {}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(11 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr <= '{}';
+ nmr 
+-----
+ {}
+ {}
+ {}
+ {}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr <= '{[3,)}';
+          nmr          
+-----------------------
+ {}
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+ {}
+ {}
+ {[1.1,2.2)}
+ {}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(11 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr >= '{}';
+          nmr          
+-----------------------
+ {}
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+ {}
+ {}
+ {[1.1,2.2)}
+ {}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(11 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr >= '{[3,)}';
+  nmr   
+--------
+ {[3,)}
+(1 row)
+
+SELECT * FROM nummultirange_test WHERE nmr > '{}';
+          nmr          
+-----------------------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+ {[1.1,2.2)}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(7 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr > '{[-1000.0, -1000.0]}';
+          nmr          
+-----------------------
+ {[3,)}
+ {[1.1,2.2)}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr > '{[0.0, 1.0]}';
+          nmr          
+-----------------------
+ {[3,)}
+ {[1.1,2.2)}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr > '{[1000.0, 1001.0]}';
+ nmr 
+-----
+(0 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr <> '{}';
+          nmr          
+-----------------------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+ {[1.1,2.2)}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(7 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr <> '{(,5)}';
+          nmr          
+-----------------------
+ {}
+ {(,)}
+ {[3,)}
+ {(,)}
+ {}
+ {}
+ {[1.1,2.2)}
+ {}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(10 rows)
+
+select nummultirange(numrange(2.0, 1.0));
+ERROR:  range lower bound must be less than or equal to range upper bound
+select nummultirange(numrange(5.0, 6.0), numrange(1.0, 2.0));
+     nummultirange     
+-----------------------
+ {[1.0,2.0),[5.0,6.0)}
+(1 row)
+
+analyze nummultirange_test;
+-- overlaps
+SELECT * FROM nummultirange_test WHERE range_overlaps_multirange(numrange(4.0, 4.2), nmr);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE numrange(4.0, 4.2) && nmr;
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE multirange_overlaps_range(nmr, numrange(4.0, 4.2));
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr && numrange(4.0, 4.2);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE multirange_overlaps_multirange(nmr, nummultirange(numrange(4.0, 4.2), numrange(6.0, 7.0)));
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr && nummultirange(numrange(4.0, 4.2), numrange(6.0, 7.0));
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr && nummultirange(numrange(6.0, 7.0));
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+(3 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr && nummultirange(numrange(6.0, 7.0), numrange(8.0, 9.0));
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+(3 rows)
+
+-- mr contains x
+SELECT * FROM nummultirange_test WHERE multirange_contains_elem(nmr, 4.0);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr @> 4.0;
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE multirange_contains_range(nmr, numrange(4.0, 4.2));
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr @> numrange(4.0, 4.2);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE multirange_contains_multirange(nmr, '{[4.0,4.2), [6.0, 8.0)}');
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+(3 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr @> '{[4.0,4.2), [6.0, 8.0)}'::nummultirange;
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+(3 rows)
+
+-- x is contained by mr
+SELECT * FROM nummultirange_test WHERE elem_contained_by_multirange(4.0, nmr);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE 4.0 <@ nmr;
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE range_contained_by_multirange(numrange(4.0, 4.2), nmr);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE numrange(4.0, 4.2) <@ nmr;
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE multirange_contained_by_multirange('{[4.0,4.2), [6.0, 8.0)}', nmr);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+(3 rows)
+
+SELECT * FROM nummultirange_test WHERE '{[4.0,4.2), [6.0, 8.0)}'::nummultirange <@ nmr;
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+(3 rows)
+
+-- overlaps
+SELECT 'empty'::numrange && nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT 'empty'::numrange && nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() && 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) && 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() && nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() && nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) && nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) && nummultirange(numrange(1,2), numrange(7,8));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(7,8)) && nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) && nummultirange(numrange(1,2), numrange(3.5,8));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(3.5,8)) && numrange(3,4);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(3.5,8)) && nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+-- contains
+SELECT nummultirange() @> nummultirange();
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange() @> 'empty'::numrange;
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(null,null)) @> numrange(1,2);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(null,null)) @> numrange(null,2);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(null,null)) @> numrange(2,null);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(null,5)) @> numrange(null,3);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(null,5)) @> numrange(null,8);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(5,null)) @> numrange(8,null);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(5,null)) @> numrange(3,null);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,5)) @> numrange(8,9);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,5)) @> numrange(3,9);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,5)) @> numrange(1,4);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,5)) @> numrange(1,5);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(-4,-2), numrange(1,5)) @> numrange(1,5);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,5), numrange(8,9)) @> numrange(1,5);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,5), numrange(8,9)) @> numrange(6,7);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,5), numrange(6,9)) @> numrange(6,7);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[1,5)}'::nummultirange @> '{[1,5)}';
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[-4,-2), [1,5)}'::nummultirange @> '{[1,5)}';
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[1,5), [8,9)}'::nummultirange @> '{[1,5)}';
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[1,5), [8,9)}'::nummultirange @> '{[6,7)}';
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT '{[1,5), [6,9)}'::nummultirange @> '{[6,7)}';
+ ?column? 
+----------
+ t
+(1 row)
+
+-- is contained by
+SELECT nummultirange() <@ nummultirange();
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT 'empty'::numrange <@ nummultirange();
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,2) <@ nummultirange(numrange(null,null));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(null,2) <@ nummultirange(numrange(null,null));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(2,null) <@ nummultirange(numrange(null,null));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(null,3) <@ nummultirange(numrange(null,5));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(null,8) <@ nummultirange(numrange(null,5));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(8,null) <@ nummultirange(numrange(5,null));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(3,null) <@ nummultirange(numrange(5,null));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(8,9) <@ nummultirange(numrange(1,5));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(3,9) <@ nummultirange(numrange(1,5));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(1,4) <@ nummultirange(numrange(1,5));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,5) <@ nummultirange(numrange(1,5));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,5) <@ nummultirange(numrange(-4,-2), numrange(1,5));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,5) <@ nummultirange(numrange(1,5), numrange(8,9));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(6,7) <@ nummultirange(numrange(1,5), numrange(8,9));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(6,7) <@ nummultirange(numrange(1,5), numrange(6,9));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[1,5)}' <@ '{[1,5)}'::nummultirange;
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[1,5)}' <@ '{[-4,-2), [1,5)}'::nummultirange;
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[1,5)}' <@ '{[1,5), [8,9)}'::nummultirange;
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[6,7)}' <@ '{[1,5), [8,9)}'::nummultirange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT '{[6,7)}' <@ '{[1,5), [6,9)}'::nummultirange;
+ ?column? 
+----------
+ t
+(1 row)
+
+-- overleft
+SELECT 'empty'::numrange &< nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT 'empty'::numrange &< nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() &< 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) &< 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() &< nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) &< nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() &< nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(6,7) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(1,2) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,4) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,6) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(3.5,6) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(6,7)) &< numrange(3,4);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) &< numrange(3,4);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,4)) &< numrange(3,4);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,6)) &< numrange(3,4);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3.5,6)) &< numrange(3,4);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(6,7)) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,4)) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,6)) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3.5,6)) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+-- overright
+SELECT nummultirange() &> 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) &> 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT 'empty'::numrange &> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT 'empty'::numrange &> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() &> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() &> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) &> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> numrange(6,7);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> numrange(1,2);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> numrange(1,4);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> numrange(1,6);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> numrange(3.5,6);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(3,4) &> nummultirange(numrange(6,7));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(3,4) &> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(3,4) &> nummultirange(numrange(1,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(3,4) &> nummultirange(numrange(1,6));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(3,4) &> nummultirange(numrange(3.5,6));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(6,7));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(1,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(1,6));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(3.5,6));
+ ?column? 
+----------
+ f
+(1 row)
+
+-- meets
+SELECT 'empty'::numrange -|- nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT 'empty'::numrange -|- nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() -|- 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() -|- nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() -|- nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(1,2) -|- nummultirange(numrange(2,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,2) -|- nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- numrange(2,4);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- numrange(3,4);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- nummultirange(numrange(2,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(5,6)) -|- nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(5,6)) -|- nummultirange(numrange(6,7));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(5,6)) -|- nummultirange(numrange(8,9));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- nummultirange(numrange(2,4), numrange(6,7));
+ ?column? 
+----------
+ t
+(1 row)
+
+-- strictly left
+select 'empty'::numrange << nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(1,2) << nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(1,2) << nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+select numrange(1,2) << nummultirange(numrange(0,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(1,2) << nummultirange(numrange(0,4), numrange(7,8));
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange() << 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange() << numrange(1,2);
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(3,4)) << numrange(3,6);
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(0,2)) << numrange(3,6);
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange(numrange(0,2), numrange(7,8)) << numrange(3,6);
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(-4,-2), numrange(0,2)) << numrange(3,6);
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange() << nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange() << nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(1,2)) << nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(1,2)) << nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(1,2)) << nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange(numrange(1,2)) << nummultirange(numrange(3,4), numrange(7,8));
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange(numrange(1,2), numrange(4,5)) << nummultirange(numrange(3,4), numrange(7,8));
+ ?column? 
+----------
+ f
+(1 row)
+
+-- strictly right
+select nummultirange() >> 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange() >> numrange(1,2);
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(3,4)) >> numrange(1,2);
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange(numrange(0,4)) >> numrange(1,2);
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(0,4), numrange(7,8)) >> numrange(1,2);
+ ?column? 
+----------
+ f
+(1 row)
+
+select 'empty'::numrange >> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(1,2) >> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(3,6) >> nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(3,6) >> nummultirange(numrange(0,2));
+ ?column? 
+----------
+ t
+(1 row)
+
+select numrange(3,6) >> nummultirange(numrange(0,2), numrange(7,8));
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(3,6) >> nummultirange(numrange(-4,-2), numrange(0,2));
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange() >> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(1,2)) >> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange() >> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(1,2)) >> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(3,4)) >> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange(numrange(3,4), numrange(7,8)) >> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange(numrange(3,4), numrange(7,8)) >> nummultirange(numrange(1,2), numrange(4,5));
+ ?column? 
+----------
+ f
+(1 row)
+
+-- union
+SELECT nummultirange() + nummultirange();
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange() + nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) + nummultirange();
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) + nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) + nummultirange(numrange(2,4));
+ ?column? 
+----------
+ {[1,4)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) + nummultirange(numrange(3,4));
+   ?column?    
+---------------
+ {[1,2),[3,4)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) + nummultirange(numrange(2,4));
+ ?column? 
+----------
+ {[1,5)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) + nummultirange(numrange(3,4));
+   ?column?    
+---------------
+ {[1,2),[3,5)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) + nummultirange(numrange(0,9));
+ ?column? 
+----------
+ {[0,9)}
+(1 row)
+
+-- merge
+SELECT range_merge(nummultirange());
+ range_merge 
+-------------
+ empty
+(1 row)
+
+SELECT range_merge(nummultirange(numrange(1,2)));
+ range_merge 
+-------------
+ [1,2)
+(1 row)
+
+SELECT range_merge(nummultirange(numrange(1,2), numrange(7,8)));
+ range_merge 
+-------------
+ [1,8)
+(1 row)
+
+-- minus
+SELECT nummultirange() - nummultirange();
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange() - nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) - nummultirange();
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(3,4)) - nummultirange();
+   ?column?    
+---------------
+ {[1,2),[3,4)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) - nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) - nummultirange(numrange(2,4));
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) - nummultirange(numrange(3,4));
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {[2,4)}
+(1 row)
+
+SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(2,3));
+   ?column?    
+---------------
+ {[1,2),[3,4)}
+(1 row)
+
+SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(0,8));
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(0,2));
+ ?column? 
+----------
+ {[2,4)}
+(1 row)
+
+SELECT nummultirange(numrange(1,8)) - nummultirange(numrange(0,2), numrange(3,4));
+   ?column?    
+---------------
+ {[2,3),[4,8)}
+(1 row)
+
+SELECT nummultirange(numrange(1,8)) - nummultirange(numrange(2,3), numrange(5,null));
+   ?column?    
+---------------
+ {[1,2),[3,5)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(-2,0));
+   ?column?    
+---------------
+ {[1,2),[4,5)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(2,4));
+   ?column?    
+---------------
+ {[1,2),[4,5)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(3,5));
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(0,9));
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange(numrange(1,3), numrange(4,5)) - nummultirange(numrange(2,9));
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(8,9));
+   ?column?    
+---------------
+ {[1,2),[4,5)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(-2,0), numrange(8,9));
+   ?column?    
+---------------
+ {[1,2),[4,5)}
+(1 row)
+
+-- intersection
+SELECT nummultirange() * nummultirange();
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange() * nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) * nummultirange();
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT '{[1,3)}'::nummultirange * '{[1,5)}'::nummultirange;
+ ?column? 
+----------
+ {[1,3)}
+(1 row)
+
+SELECT '{[1,3)}'::nummultirange * '{[0,5)}'::nummultirange;
+ ?column? 
+----------
+ {[1,3)}
+(1 row)
+
+SELECT '{[1,3)}'::nummultirange * '{[0,2)}'::nummultirange;
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT '{[1,3)}'::nummultirange * '{[2,5)}'::nummultirange;
+ ?column? 
+----------
+ {[2,3)}
+(1 row)
+
+SELECT '{[1,4)}'::nummultirange * '{[2,3)}'::nummultirange;
+ ?column? 
+----------
+ {[2,3)}
+(1 row)
+
+SELECT '{[1,4)}'::nummultirange * '{[0,2), [3,5)}'::nummultirange;
+   ?column?    
+---------------
+ {[1,2),[3,4)}
+(1 row)
+
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[0,8), [9,12)}'::nummultirange;
+       ?column?       
+----------------------
+ {[1,4),[7,8),[9,10)}
+(1 row)
+
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[9,12)}'::nummultirange;
+ ?column? 
+----------
+ {[9,10)}
+(1 row)
+
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[-5,-4), [5,6), [9,12)}'::nummultirange;
+ ?column? 
+----------
+ {[9,10)}
+(1 row)
+
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[0,2), [3,8), [9,12)}'::nummultirange;
+          ?column?          
+----------------------------
+ {[1,2),[3,4),[7,8),[9,10)}
+(1 row)
+
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[0,2), [3,8), [9,12)}'::nummultirange;
+          ?column?          
+----------------------------
+ {[1,2),[3,4),[7,8),[9,10)}
+(1 row)
+
+--
+-- range_agg function
+--
+create table reservations ( room_id integer not null, booked_during daterange );
+insert into reservations values
+-- 1: has a meets and a gap
+(1, daterange('2018-07-01', '2018-07-07')),
+(1, daterange('2018-07-07', '2018-07-14')),
+(1, daterange('2018-07-20', '2018-07-22')),
+-- 2: just a single row
+(2, daterange('2018-07-01', '2018-07-03')),
+-- 3: one null range
+(3, NULL),
+-- 4: two null ranges
+(4, NULL),
+(4, NULL),
+-- 5: a null range and a non-null range
+(5, NULL),
+(5, daterange('2018-07-01', '2018-07-03')),
+-- 6: has overlap
+(6, daterange('2018-07-01', '2018-07-07')),
+(6, daterange('2018-07-05', '2018-07-10')),
+-- 7: two ranges that meet: no gap or overlap
+(7, daterange('2018-07-01', '2018-07-07')),
+(7, daterange('2018-07-07', '2018-07-14')),
+-- 8: an empty range
+(8, 'empty'::daterange)
+;
+SELECT   room_id, range_agg(booked_during)
+FROM     reservations
+GROUP BY room_id
+ORDER BY room_id;
+ room_id |                     range_agg                     
+---------+---------------------------------------------------
+       1 | {[07-01-2018,07-14-2018),[07-20-2018,07-22-2018)}
+       2 | {[07-01-2018,07-03-2018)}
+       3 | 
+       4 | 
+       5 | {[07-01-2018,07-03-2018)}
+       6 | {[07-01-2018,07-10-2018)}
+       7 | {[07-01-2018,07-14-2018)}
+       8 | {}
+(8 rows)
+
+-- range_agg on a custom range type too
+SELECT  range_agg(r)
+FROM    (VALUES
+          ('[a,c]'::textrange),
+          ('[b,b]'::textrange),
+          ('[c,f]'::textrange),
+          ('[g,h)'::textrange),
+          ('[h,j)'::textrange)
+        ) t(r);
+   range_agg   
+---------------
+ {[a,f],[g,j)}
+(1 row)
+
+select range_intersect_agg(nmr) from nummultirange_test;
+ range_intersect_agg 
+---------------------
+ {}
+(1 row)
+
+select range_intersect_agg(nmr) from nummultirange_test where false;
+ range_intersect_agg 
+---------------------
+ 
+(1 row)
+
+-- test with just one input:
+select range_intersect_agg(nmr) from (values ('{[1,2]}'::nummultirange)) t(nmr);
+ range_intersect_agg 
+---------------------
+ {[1,2]}
+(1 row)
+
+select range_intersect_agg(nmr) from nummultirange_test where nmr @> 4.0;
+ range_intersect_agg 
+---------------------
+ {[3,5)}
+(1 row)
+
+create table nummultirange_test2(nmr nummultirange);
+create index nummultirange_test2_hash_idx on nummultirange_test2 using hash (nmr);
+INSERT INTO nummultirange_test2 VALUES('{[, 5)}');
+INSERT INTO nummultirange_test2 VALUES(nummultirange(numrange(1.1, 2.2)));
+INSERT INTO nummultirange_test2 VALUES(nummultirange(numrange(1.1, 2.2)));
+INSERT INTO nummultirange_test2 VALUES(nummultirange(numrange(1.1, 2.2,'()')));
+INSERT INTO nummultirange_test2 VALUES('{}');
+select * from nummultirange_test2 where nmr = '{}';
+ nmr 
+-----
+ {}
+(1 row)
+
+select * from nummultirange_test2 where nmr = nummultirange(numrange(1.1, 2.2));
+     nmr     
+-------------
+ {[1.1,2.2)}
+ {[1.1,2.2)}
+(2 rows)
+
+select * from nummultirange_test2 where nmr = nummultirange(numrange(1.1, 2.3));
+ nmr 
+-----
+(0 rows)
+
+set enable_nestloop=t;
+set enable_hashjoin=f;
+set enable_mergejoin=f;
+select * from nummultirange_test natural join nummultirange_test2 order by nmr;
+     nmr     
+-------------
+ {}
+ {}
+ {}
+ {}
+ {(,5)}
+ {[1.1,2.2)}
+ {[1.1,2.2)}
+(7 rows)
+
+set enable_nestloop=f;
+set enable_hashjoin=t;
+set enable_mergejoin=f;
+select * from nummultirange_test natural join nummultirange_test2 order by nmr;
+     nmr     
+-------------
+ {}
+ {}
+ {}
+ {}
+ {(,5)}
+ {[1.1,2.2)}
+ {[1.1,2.2)}
+(7 rows)
+
+set enable_nestloop=f;
+set enable_hashjoin=f;
+set enable_mergejoin=t;
+select * from nummultirange_test natural join nummultirange_test2 order by nmr;
+     nmr     
+-------------
+ {}
+ {}
+ {}
+ {}
+ {(,5)}
+ {[1.1,2.2)}
+ {[1.1,2.2)}
+(7 rows)
+
+set enable_nestloop to default;
+set enable_hashjoin to default;
+set enable_mergejoin to default;
+DROP TABLE nummultirange_test2;
+--
+-- Test user-defined multirange of floats
+--
+select '{[123.001, 5.e9)}'::float8multirange @> 888.882::float8;
+ ?column? 
+----------
+ t
+(1 row)
+
+create table float8multirange_test(f8mr float8multirange, i int);
+insert into float8multirange_test values(float8multirange(float8range(-100.00007, '1.111113e9')), 42);
+select * from float8multirange_test;
+           f8mr            | i  
+---------------------------+----
+ {[-100.00007,1111113000)} | 42
+(1 row)
+
+drop table float8multirange_test;
+--
+-- Test multirange types over domains
+--
+create domain mydomain as int4;
+create type mydomainrange as range(subtype=mydomain);
+select '{[4,50)}'::mydomainmultirange @> 7::mydomain;
+ ?column? 
+----------
+ t
+(1 row)
+
+drop domain mydomain cascade;
+NOTICE:  drop cascades to type mydomainrange
+--
+-- Test domains over multirange types
+--
+create domain restrictedmultirange as int4multirange check (upper(value) < 10);
+select '{[4,5)}'::restrictedmultirange @> 7;
+ ?column? 
+----------
+ f
+(1 row)
+
+select '{[4,50)}'::restrictedmultirange @> 7; -- should fail
+ERROR:  value for domain restrictedmultirange violates check constraint "restrictedmultirange_check"
+drop domain restrictedmultirange;
+--
+-- Test multiple multirange types over the same subtype and manual naming of
+-- the multirange type.
+--
+-- should fail
+create type textrange1 as range(subtype=text, multirange_type_name=int, collation="C");
+ERROR:  type "int4" already exists
+-- should pass
+create type textrange1 as range(subtype=text, multirange_type_name=multirange_of_text, collation="C");
+-- should pass, because existing _textrange1 is automatically renamed
+create type textrange2 as range(subtype=text, multirange_type_name=_textrange1, collation="C");
+select multirange_of_text(textrange2('a','Z'));  -- should fail
+ERROR:  function multirange_of_text(textrange2) does not exist
+LINE 1: select multirange_of_text(textrange2('a','Z'));
+               ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+select multirange_of_text(textrange1('a','Z')) @> 'b'::text;
+ERROR:  range lower bound must be less than or equal to range upper bound
+select _textrange1(textrange2('a','z')) @> 'b'::text;
+ ?column? 
+----------
+ t
+(1 row)
+
+drop type textrange1;
+drop type textrange2;
+--
+-- Test polymorphic type system
+--
+create function anyarray_anymultirange_func(a anyarray, r anymultirange)
+  returns anyelement as 'select $1[1] + lower($2);' language sql;
+select anyarray_anymultirange_func(ARRAY[1,2], int4multirange(int4range(10,20)));
+ anyarray_anymultirange_func 
+-----------------------------
+                          11
+(1 row)
+
+-- should fail
+select anyarray_anymultirange_func(ARRAY[1,2], nummultirange(numrange(10,20)));
+ERROR:  function anyarray_anymultirange_func(integer[], nummultirange) does not exist
+LINE 1: select anyarray_anymultirange_func(ARRAY[1,2], nummultirange...
+               ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+drop function anyarray_anymultirange_func(anyarray, anymultirange);
+-- should fail
+create function bogus_func(anyelement)
+  returns anymultirange as 'select int4multirange(int4range(1,10))' language sql;
+ERROR:  cannot determine result data type
+DETAIL:  A result of type anymultirange requires at least one input of type anyrange or anymultirange.
+-- should fail
+create function bogus_func(int)
+  returns anymultirange as 'select int4multirange(int4range(1,10))' language sql;
+ERROR:  cannot determine result data type
+DETAIL:  A result of type anymultirange requires at least one input of type anyrange or anymultirange.
+create function range_add_bounds(anymultirange)
+  returns anyelement as 'select lower($1) + upper($1)' language sql;
+select range_add_bounds(int4multirange(int4range(1, 17)));
+ range_add_bounds 
+------------------
+               18
+(1 row)
+
+select range_add_bounds(nummultirange(numrange(1.0001, 123.123)));
+ range_add_bounds 
+------------------
+         124.1231
+(1 row)
+
+create function multirangetypes_sql(q anymultirange, b anyarray, out c anyelement)
+  as $$ select upper($1) + $2[1] $$
+  language sql;
+select multirangetypes_sql(int4multirange(int4range(1,10)), ARRAY[2,20]);
+ multirangetypes_sql 
+---------------------
+                  12
+(1 row)
+
+select multirangetypes_sql(nummultirange(numrange(1,10)), ARRAY[2,20]);  -- match failure
+ERROR:  function multirangetypes_sql(nummultirange, integer[]) does not exist
+LINE 1: select multirangetypes_sql(nummultirange(numrange(1,10)), AR...
+               ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+create function anycompatiblearray_anycompatiblemultirange_func(a anycompatiblearray, mr anycompatiblemultirange)
+  returns anycompatible as 'select $1[1] + lower($2);' language sql;
+select anycompatiblearray_anycompatiblemultirange_func(ARRAY[1,2], multirange(int4range(10,20)));
+ anycompatiblearray_anycompatiblemultirange_func 
+-------------------------------------------------
+                                              11
+(1 row)
+
+select anycompatiblearray_anycompatiblemultirange_func(ARRAY[1,2], multirange(numrange(10,20)));
+ anycompatiblearray_anycompatiblemultirange_func 
+-------------------------------------------------
+                                              11
+(1 row)
+
+-- should fail
+select anycompatiblearray_anycompatiblemultirange_func(ARRAY[1.1,2], multirange(int4range(10,20)));
+ERROR:  function anycompatiblearray_anycompatiblemultirange_func(numeric[], int4multirange) does not exist
+LINE 1: select anycompatiblearray_anycompatiblemultirange_func(ARRAY...
+               ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+drop function anycompatiblearray_anycompatiblemultirange_func(anycompatiblearray, anycompatiblemultirange);
+create function anycompatiblerange_anycompatiblemultirange_func(r anycompatiblerange, mr anycompatiblemultirange)
+  returns anycompatible as 'select lower($1) + lower($2);' language sql;
+select anycompatiblerange_anycompatiblemultirange_func(int4range(1,2), multirange(int4range(10,20)));
+ anycompatiblerange_anycompatiblemultirange_func 
+-------------------------------------------------
+                                              11
+(1 row)
+
+-- should fail
+select anycompatiblerange_anycompatiblemultirange_func(numrange(1,2), multirange(int4range(10,20)));
+ERROR:  function anycompatiblerange_anycompatiblemultirange_func(numrange, int4multirange) does not exist
+LINE 1: select anycompatiblerange_anycompatiblemultirange_func(numra...
+               ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+drop function anycompatiblerange_anycompatiblemultirange_func(anycompatiblerange, anycompatiblemultirange);
+-- should fail
+create function bogus_func(anycompatible)
+  returns anycompatiblerange as 'select int4range(1,10)' language sql;
+ERROR:  cannot determine result data type
+DETAIL:  A result of type anycompatiblerange requires at least one input of type anycompatiblerange or anycompatiblemultirange.
+--
+-- Arrays of multiranges
+--
+select ARRAY[nummultirange(numrange(1.1, 1.2)), nummultirange(numrange(12.3, 155.5))];
+              array               
+----------------------------------
+ {"{[1.1,1.2)}","{[12.3,155.5)}"}
+(1 row)
+
+create table i8mr_array (f1 int, f2 int8multirange[]);
+insert into i8mr_array values (42, array[int8multirange(int8range(1,10)), int8multirange(int8range(2,20))]);
+select * from i8mr_array;
+ f1 |           f2            
+----+-------------------------
+ 42 | {"{[1,10)}","{[2,20)}"}
+(1 row)
+
+drop table i8mr_array;
+--
+-- Multiranges of arrays
+--
+select arraymultirange(arrayrange(ARRAY[1,2], ARRAY[2,1]));
+   arraymultirange   
+---------------------
+ {["{1,2}","{2,1}")}
+(1 row)
+
+select arraymultirange(arrayrange(ARRAY[2,1], ARRAY[1,2]));  -- fail
+ERROR:  range lower bound must be less than or equal to range upper bound
+select array[1,1] <@ arraymultirange(arrayrange(array[1,2], array[2,1]));
+ ?column? 
+----------
+ f
+(1 row)
+
+select array[1,3] <@ arraymultirange(arrayrange(array[1,2], array[2,1]));
+ ?column? 
+----------
+ t
+(1 row)
+
+--
+-- Ranges of composites
+--
+create type two_ints as (a int, b int);
+create type two_ints_range as range (subtype = two_ints);
+-- with force_parallel_mode on, this exercises tqueue.c's range remapping
+select *, row_to_json(upper(t)) as u from
+  (values (two_ints_multirange(two_ints_range(row(1,2), row(3,4)))),
+          (two_ints_multirange(two_ints_range(row(5,6), row(7,8))))) v(t);
+          t          |       u       
+---------------------+---------------
+ {["(1,2)","(3,4)")} | {"a":3,"b":4}
+ {["(5,6)","(7,8)")} | {"a":7,"b":8}
+(2 rows)
+
+drop type two_ints cascade;
+NOTICE:  drop cascades to type two_ints_range
+--
+-- Check behavior when subtype lacks a hash function
+--
+set enable_sort = off;  -- try to make it pick a hash setop implementation
+select '{(2,5)}'::cashmultirange except select '{(5,6)}'::cashmultirange;
+ cashmultirange  
+-----------------
+ {($2.00,$5.00)}
+(1 row)
+
+reset enable_sort;
+--
+-- OUT/INOUT/TABLE functions
+--
+-- infer anymultirange from anymultirange
+create function mr_outparam_succeed(i anymultirange, out r anymultirange, out t text)
+  as $$ select $1, 'foo'::text $$ language sql;
+select * from mr_outparam_succeed(int4multirange(int4range(1,2)));
+    r    |  t  
+---------+-----
+ {[1,2)} | foo
+(1 row)
+
+-- infer anyarray from anymultirange
+create function mr_outparam_succeed2(i anymultirange, out r anyarray, out t text)
+  as $$ select ARRAY[upper($1)], 'foo'::text $$ language sql;
+select * from mr_outparam_succeed2(int4multirange(int4range(1,2)));
+  r  |  t  
+-----+-----
+ {2} | foo
+(1 row)
+
+-- infer anyrange from anymultirange
+create function mr_outparam_succeed3(i anymultirange, out r anyrange, out t text)
+  as $$ select range_merge($1), 'foo'::text $$ language sql;
+select * from mr_outparam_succeed3(int4multirange(int4range(1,2)));
+   r   |  t  
+-------+-----
+ [1,2) | foo
+(1 row)
+
+-- infer anymultirange from anyrange
+create function mr_outparam_succeed4(i anyrange, out r anymultirange, out t text)
+  as $$ select multirange($1), 'foo'::text $$ language sql;
+select * from mr_outparam_succeed4(int4range(1,2));
+    r    |  t  
+---------+-----
+ {[1,2)} | foo
+(1 row)
+
+-- infer anyelement from anymultirange
+create function mr_inoutparam_succeed(out i anyelement, inout r anymultirange)
+  as $$ select upper($1), $1 $$ language sql;
+select * from mr_inoutparam_succeed(int4multirange(int4range(1,2)));
+ i |    r    
+---+---------
+ 2 | {[1,2)}
+(1 row)
+
+-- infer anyelement+anymultirange from anyelement+anymultirange
+create function mr_table_succeed(i anyelement, r anymultirange) returns table(i anyelement, r anymultirange)
+  as $$ select $1, $2 $$ language sql;
+select * from mr_table_succeed(123, int4multirange(int4range(1,11)));
+  i  |    r     
+-----+----------
+ 123 | {[1,11)}
+(1 row)
+
+-- use anymultirange in plpgsql
+create function mr_polymorphic(i anyrange) returns anymultirange
+  as $$ begin return multirange($1); end; $$ language plpgsql;
+select mr_polymorphic(int4range(1, 4));
+ mr_polymorphic 
+----------------
+ {[1,4)}
+(1 row)
+
+-- should fail
+create function mr_outparam_fail(i anyelement, out r anymultirange, out t text)
+  as $$ select '[1,10]', 'foo' $$ language sql;
+ERROR:  cannot determine result data type
+DETAIL:  A result of type anymultirange requires at least one input of type anyrange or anymultirange.
+--should fail
+create function mr_inoutparam_fail(inout i anyelement, out r anymultirange)
+  as $$ select $1, '[1,10]' $$ language sql;
+ERROR:  cannot determine result data type
+DETAIL:  A result of type anymultirange requires at least one input of type anyrange or anymultirange.
+--should fail
+create function mr_table_fail(i anyelement) returns table(i anyelement, r anymultirange)
+  as $$ select $1, '[1,10]' $$ language sql;
+ERROR:  cannot determine result data type
+DETAIL:  A result of type anymultirange requires at least one input of type anyrange or anymultirange.
diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out
index 507b474b1bb..5cac48e02a7 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -39,6 +39,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;
@@ -183,7 +187,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::regtype, p2.prorettype::regtype
 FROM pg_proc AS p1, pg_proc AS p2
 WHERE p1.oid != p2.oid AND
@@ -192,6 +197,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        
@@ -210,6 +217,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        
@@ -230,6 +239,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        
@@ -342,7 +353,8 @@ WHERE p1.prorettype IN
      '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     
 ------+----------------
@@ -357,20 +369,25 @@ ORDER BY 2;
  3532 | enum_recv
 (9 rows)
 
--- anyrange is tighter than the rest, can only resolve from anyrange input
+-- anyrange and anymultirange are tighter than the rest, can only resolve
+-- from each other
 SELECT p1.oid, p1.proname
 FROM pg_proc as p1
-WHERE p1.prorettype = 'anyrange'::regtype
+WHERE p1.prorettype IN ('anyrange'::regtype, 'anymultirange'::regtype)
   AND NOT
-     'anyrange'::regtype = ANY (p1.proargtypes)
+    ('anyrange'::regtype = ANY (p1.proargtypes) OR
+      'anymultirange'::regtype = ANY (p1.proargtypes))
 ORDER BY 2;
  oid  |     proname      
 ------+------------------
+ 8002 | anymultirange_in
  3832 | anyrange_in
+ 8004 | multirange_in
+ 8006 | multirange_recv
  3876 | range_gist_union
  3834 | range_in
  3836 | range_recv
-(4 rows)
+(7 rows)
 
 -- similarly for the anycompatible family
 SELECT p1.oid, p1.proname
@@ -2204,13 +2221,14 @@ ORDER BY 1, 2, 3;
                     | float_ops        | float4_ops       | real
                     | float_ops        | float8_ops       | double precision
                     | jsonb_ops        | jsonb_ops        | jsonb
+                    | multirange_ops   | multirange_ops   | anymultirange
                     | numeric_ops      | numeric_ops      | numeric
                     | range_ops        | range_ops        | anyrange
                     | record_image_ops | record_image_ops | record
                     | record_ops       | record_ops       | record
                     | tsquery_ops      | tsquery_ops      | tsquery
                     | tsvector_ops     | tsvector_ops     | tsvector
-(14 rows)
+(15 rows)
 
 -- **************** pg_index ****************
 -- Look for illegal values in pg_index fields.
diff --git a/src/test/regress/expected/plpgsql.out b/src/test/regress/expected/plpgsql.out
index d0a6b630b8f..d55006d8c95 100644
--- a/src/test/regress/expected/plpgsql.out
+++ b/src/test/regress/expected/plpgsql.out
@@ -1811,7 +1811,7 @@ begin
   return array[x + 1, x + 2];
 end$$ language plpgsql;
 ERROR:  cannot determine result data type
-DETAIL:  A result of type anyrange requires at least one input of type anyrange.
+DETAIL:  A result of type anyrange requires at least one input of type anyrange or anymultirange.
 create function f1(x anyrange) returns anyarray as $$
 begin
   return array[lower(x), upper(x)];
@@ -1856,7 +1856,7 @@ begin
   return array[x + 1, x + 2];
 end$$ language plpgsql;
 ERROR:  cannot determine result data type
-DETAIL:  A result of type anycompatiblerange requires at least one input of type anycompatiblerange.
+DETAIL:  A result of type anycompatiblerange requires at least one input of type anycompatiblerange or anycompatiblemultirange.
 create function f1(x anycompatiblerange, y anycompatiblearray) returns anycompatiblerange as $$
 begin
   return x;
diff --git a/src/test/regress/expected/polymorphism.out b/src/test/regress/expected/polymorphism.out
index f5dfdf617fd..772345584f0 100644
--- a/src/test/regress/expected/polymorphism.out
+++ b/src/test/regress/expected/polymorphism.out
@@ -61,7 +61,7 @@ create function polyf(x anyelement) returns anyrange as $$
   select array[x + 1, x + 2]
 $$ language sql;
 ERROR:  cannot determine result data type
-DETAIL:  A result of type anyrange requires at least one input of type anyrange.
+DETAIL:  A result of type anyrange requires at least one input of type anyrange or anymultirange.
 create function polyf(x anyrange) returns anyarray as $$
   select array[lower(x), upper(x)]
 $$ language sql;
@@ -97,12 +97,27 @@ LINE 1: select polyf(int4range(42, 49), 11, 4.5) as fail;
                ^
 HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
 drop function polyf(x anycompatiblerange, y anycompatible, z anycompatible);
+create function polyf(x anycompatiblemultirange, y anycompatible, z anycompatible) returns anycompatiblearray as $$
+  select array[lower(x), upper(x), y, z]
+$$ language sql;
+select polyf(multirange(int4range(42, 49)), 11, 2::smallint) as int, polyf(multirange(float8range(4.5, 7.8)), 7.8, 11::real) as num;
+     int      |       num        
+--------------+------------------
+ {42,49,11,2} | {4.5,7.8,7.8,11}
+(1 row)
+
+select polyf(multirange(int4range(42, 49)), 11, 4.5) as fail;  -- range type doesn't fit
+ERROR:  function polyf(int4multirange, integer, numeric) does not exist
+LINE 1: select polyf(multirange(int4range(42, 49)), 11, 4.5) as fail...
+               ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+drop function polyf(x anycompatiblemultirange, y anycompatible, z anycompatible);
 -- fail, can't infer type:
 create function polyf(x anycompatible) returns anycompatiblerange as $$
   select array[x + 1, x + 2]
 $$ language sql;
 ERROR:  cannot determine result data type
-DETAIL:  A result of type anycompatiblerange requires at least one input of type anycompatiblerange.
+DETAIL:  A result of type anycompatiblerange requires at least one input of type anycompatiblerange or anycompatiblemultirange.
 create function polyf(x anycompatiblerange, y anycompatiblearray) returns anycompatiblerange as $$
   select x
 $$ language sql;
@@ -113,6 +128,22 @@ select polyf(int4range(42, 49), array[11]) as int, polyf(float8range(4.5, 7.8),
 (1 row)
 
 drop function polyf(x anycompatiblerange, y anycompatiblearray);
+-- fail, can't infer type:
+create function polyf(x anycompatible) returns anycompatiblemultirange as $$
+  select array[x + 1, x + 2]
+$$ language sql;
+ERROR:  cannot determine result data type
+DETAIL:  A result of type anycompatiblemultirange requires at least one input of type anycompatiblerange or anycompatiblemultirange.
+create function polyf(x anycompatiblemultirange, y anycompatiblearray) returns anycompatiblemultirange as $$
+  select x
+$$ language sql;
+select polyf(multirange(int4range(42, 49)), array[11]) as int, polyf(multirange(float8range(4.5, 7.8)), array[7]) as num;
+    int    |     num     
+-----------+-------------
+ {[42,49)} | {[4.5,7.8)}
+(1 row)
+
+drop function polyf(x anycompatiblemultirange, y anycompatiblearray);
 create function polyf(a anyelement, b anyarray,
                       c anycompatible, d anycompatible,
                       OUT x anyarray, OUT y anycompatiblearray)
@@ -231,7 +262,7 @@ CREATE AGGREGATE myaggp01a(*) (SFUNC = stfnp, STYPE = int4[],
 CREATE AGGREGATE myaggp02a(*) (SFUNC = stfnp, STYPE = anyarray,
   FINALFUNC = ffp, INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 --     N    P
 -- should CREATE
 CREATE AGGREGATE myaggp03a(*) (SFUNC = stfp, STYPE = int4[],
@@ -243,11 +274,11 @@ CREATE AGGREGATE myaggp03b(*) (SFUNC = stfp, STYPE = int4[],
 CREATE AGGREGATE myaggp04a(*) (SFUNC = stfp, STYPE = anyarray,
   FINALFUNC = ffp, INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 CREATE AGGREGATE myaggp04b(*) (SFUNC = stfp, STYPE = anyarray,
   INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 --    Case2 (R = P) && ((B = P) || (B = N))
 --    -------------------------------------
 --    S    tf1      B    tf2
@@ -302,13 +333,13 @@ ERROR:  function tfp(integer[], anyelement) does not exist
 CREATE AGGREGATE myaggp13a(BASETYPE = int, SFUNC = tfnp, STYPE = anyarray,
   FINALFUNC = ffp, INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 --    P    N        N    P
 -- should ERROR: tf2p(anyarray, int) not matched by tf2p(int[],anyelement)
 CREATE AGGREGATE myaggp14a(BASETYPE = int, SFUNC = tf2p, STYPE = anyarray,
   FINALFUNC = ffp, INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 --    P    N        P    N
 -- should ERROR: tfnp(anyarray, anyelement) not matched by tfnp(int[],int)
 CREATE AGGREGATE myaggp15a(BASETYPE = anyelement, SFUNC = tfnp,
@@ -324,21 +355,21 @@ ERROR:  function tf2p(anyarray, anyelement) does not exist
 CREATE AGGREGATE myaggp17a(BASETYPE = int, SFUNC = tf1p, STYPE = anyarray,
   FINALFUNC = ffp, INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 CREATE AGGREGATE myaggp17b(BASETYPE = int, SFUNC = tf1p, STYPE = anyarray,
   INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 --    P    P        N    P
 -- should ERROR: tfp(anyarray, int) not matched by tfp(anyarray, anyelement)
 CREATE AGGREGATE myaggp18a(BASETYPE = int, SFUNC = tfp, STYPE = anyarray,
   FINALFUNC = ffp, INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 CREATE AGGREGATE myaggp18b(BASETYPE = int, SFUNC = tfp, STYPE = anyarray,
   INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 --    P    P        P    N
 -- should ERROR: tf1p(anyarray, anyelement) not matched by tf1p(anyarray, int)
 CREATE AGGREGATE myaggp19a(BASETYPE = anyelement, SFUNC = tf1p,
@@ -368,11 +399,11 @@ CREATE AGGREGATE myaggn01b(*) (SFUNC = stfnp, STYPE = int4[],
 CREATE AGGREGATE myaggn02a(*) (SFUNC = stfnp, STYPE = anyarray,
   FINALFUNC = ffnp, INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 CREATE AGGREGATE myaggn02b(*) (SFUNC = stfnp, STYPE = anyarray,
   INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 --     N    P
 -- should CREATE
 CREATE AGGREGATE myaggn03a(*) (SFUNC = stfp, STYPE = int4[],
@@ -382,7 +413,7 @@ CREATE AGGREGATE myaggn03a(*) (SFUNC = stfp, STYPE = int4[],
 CREATE AGGREGATE myaggn04a(*) (SFUNC = stfp, STYPE = anyarray,
   FINALFUNC = ffnp, INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 --    Case4 (R = N) && ((B = P) || (B = N))
 --    -------------------------------------
 --    S    tf1      B    tf2
@@ -436,21 +467,21 @@ ERROR:  function tfp(integer[], anyelement) does not exist
 CREATE AGGREGATE myaggn13a(BASETYPE = int, SFUNC = tfnp, STYPE = anyarray,
   FINALFUNC = ffnp, INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 CREATE AGGREGATE myaggn13b(BASETYPE = int, SFUNC = tfnp, STYPE = anyarray,
   INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 --    P    N        N    P
 -- should ERROR: tf2p(anyarray, int) not matched by tf2p(int[],anyelement)
 CREATE AGGREGATE myaggn14a(BASETYPE = int, SFUNC = tf2p, STYPE = anyarray,
   FINALFUNC = ffnp, INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 CREATE AGGREGATE myaggn14b(BASETYPE = int, SFUNC = tf2p, STYPE = anyarray,
   INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 --    P    N        P    N
 -- should ERROR: tfnp(anyarray, anyelement) not matched by tfnp(int[],int)
 CREATE AGGREGATE myaggn15a(BASETYPE = anyelement, SFUNC = tfnp,
@@ -472,13 +503,13 @@ ERROR:  function tf2p(anyarray, anyelement) does not exist
 CREATE AGGREGATE myaggn17a(BASETYPE = int, SFUNC = tf1p, STYPE = anyarray,
   FINALFUNC = ffnp, INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 --    P    P        N    P
 -- should ERROR: tfp(anyarray, int) not matched by tfp(anyarray, anyelement)
 CREATE AGGREGATE myaggn18a(BASETYPE = int, SFUNC = tfp, STYPE = anyarray,
   FINALFUNC = ffnp, INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 --    P    P        P    N
 -- should ERROR: tf1p(anyarray, anyelement) not matched by tf1p(anyarray, int)
 CREATE AGGREGATE myaggn19a(BASETYPE = anyelement, SFUNC = tf1p,
@@ -1855,7 +1886,59 @@ returns anycompatiblerange as $$
   select $1
 $$ language sql;
 ERROR:  cannot determine result data type
-DETAIL:  A result of type anycompatiblerange requires at least one input of type anycompatiblerange.
+DETAIL:  A result of type anycompatiblerange requires at least one input of type anycompatiblerange or anycompatiblemultirange.
+create function anyctest(anycompatible, anycompatiblemultirange)
+returns anycompatiblemultirange as $$
+  select $2
+$$ language sql;
+select x, pg_typeof(x) from anyctest(11, multirange(int4range(4,7))) x;
+    x    |   pg_typeof    
+---------+----------------
+ {[4,7)} | int4multirange
+(1 row)
+
+select x, pg_typeof(x) from anyctest(11, multirange(numrange(4,7))) x;
+    x    |   pg_typeof   
+---------+---------------
+ {[4,7)} | nummultirange
+(1 row)
+
+select x, pg_typeof(x) from anyctest(11, 12) x;  -- fail
+ERROR:  function anyctest(integer, integer) does not exist
+LINE 1: select x, pg_typeof(x) from anyctest(11, 12) x;
+                                    ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+select x, pg_typeof(x) from anyctest(11.2, multirange(int4range(4,7))) x;  -- fail
+ERROR:  function anyctest(numeric, int4multirange) does not exist
+LINE 1: select x, pg_typeof(x) from anyctest(11.2, multirange(int4ra...
+                                    ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+select x, pg_typeof(x) from anyctest(11.2, '{[4,7)}') x;  -- fail
+ERROR:  could not identify anycompatiblemultirange type
+drop function anyctest(anycompatible, anycompatiblemultirange);
+create function anyctest(anycompatiblemultirange, anycompatiblemultirange)
+returns anycompatible as $$
+  select lower($1) + upper($2)
+$$ language sql;
+select x, pg_typeof(x) from anyctest(multirange(int4range(11,12)), multirange(int4range(4,7))) x;
+ x  | pg_typeof 
+----+-----------
+ 18 | integer
+(1 row)
+
+select x, pg_typeof(x) from anyctest(multirange(int4range(11,12)), multirange(numrange(4,7))) x; -- fail
+ERROR:  function anyctest(int4multirange, nummultirange) does not exist
+LINE 1: select x, pg_typeof(x) from anyctest(multirange(int4range(11...
+                                    ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+drop function anyctest(anycompatiblemultirange, anycompatiblemultirange);
+-- fail, can't infer result type:
+create function anyctest(anycompatible)
+returns anycompatiblemultirange as $$
+  select $1
+$$ language sql;
+ERROR:  cannot determine result data type
+DETAIL:  A result of type anycompatiblemultirange requires at least one input of type anycompatiblerange or anycompatiblemultirange.
 create function anyctest(anycompatiblenonarray, anycompatiblenonarray)
 returns anycompatiblearray as $$
   select array[$1, $2]
diff --git a/src/test/regress/expected/rangefuncs.out b/src/test/regress/expected/rangefuncs.out
index 06bd129fd22..5bbd5f6c0bd 100644
--- a/src/test/regress/expected/rangefuncs.out
+++ b/src/test/regress/expected/rangefuncs.out
@@ -1556,7 +1556,7 @@ DROP FUNCTION dup(anyelement);
 CREATE FUNCTION bad (f1 int, out f2 anyelement, out f3 anyarray)
 AS 'select $1, array[$1,$1]' LANGUAGE sql;
 ERROR:  cannot determine result data type
-DETAIL:  A result of type anyelement requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyelement requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 CREATE FUNCTION dup (f1 anycompatible, f2 anycompatiblearray, f3 out anycompatible, f4 out anycompatiblearray)
 AS 'select $1, $2' LANGUAGE sql;
 SELECT dup(22, array[44]);
diff --git a/src/test/regress/expected/rangetypes.out b/src/test/regress/expected/rangetypes.out
index c421f5394fe..6a1bbadc91a 100644
--- a/src/test/regress/expected/rangetypes.out
+++ b/src/test/regress/expected/rangetypes.out
@@ -518,6 +518,25 @@ select numrange(1.0, 2.0) * numrange(2.5, 3.0);
  empty
 (1 row)
 
+select range_intersect_agg(nr) from numrange_test;
+ range_intersect_agg 
+---------------------
+ empty
+(1 row)
+
+select range_intersect_agg(nr) from numrange_test where false;
+ range_intersect_agg 
+---------------------
+ 
+(1 row)
+
+select range_intersect_agg(nr) from numrange_test where nr @> 4.0;
+ range_intersect_agg 
+---------------------
+ [3,5)
+(1 row)
+
+analyze numrange_test;
 create table numrange_test2(nr numrange);
 create index numrange_test2_hash_idx on numrange_test2 using hash (nr);
 INSERT INTO numrange_test2 VALUES('[, 5)');
@@ -1371,12 +1390,12 @@ drop function anyarray_anyrange_func(anyarray, anyrange);
 create function bogus_func(anyelement)
   returns anyrange as 'select int4range(1,10)' language sql;
 ERROR:  cannot determine result data type
-DETAIL:  A result of type anyrange requires at least one input of type anyrange.
+DETAIL:  A result of type anyrange requires at least one input of type anyrange or anymultirange.
 -- should fail
 create function bogus_func(int)
   returns anyrange as 'select int4range(1,10)' language sql;
 ERROR:  cannot determine result data type
-DETAIL:  A result of type anyrange requires at least one input of type anyrange.
+DETAIL:  A result of type anyrange requires at least one input of type anyrange or anymultirange.
 create function range_add_bounds(anyrange)
   returns anyelement as 'select lower($1) + upper($1)' language sql;
 select range_add_bounds(int4range(1, 17));
@@ -1430,7 +1449,7 @@ drop function anycompatiblearray_anycompatiblerange_func(anycompatiblearray, any
 create function bogus_func(anycompatible)
   returns anycompatiblerange as 'select int4range(1,10)' language sql;
 ERROR:  cannot determine result data type
-DETAIL:  A result of type anycompatiblerange requires at least one input of type anycompatiblerange.
+DETAIL:  A result of type anycompatiblerange requires at least one input of type anycompatiblerange or anycompatiblemultirange.
 --
 -- Arrays of ranges
 --
@@ -1508,6 +1527,7 @@ reset enable_sort;
 --
 -- OUT/INOUT/TABLE functions
 --
+-- infer anyrange from anyrange
 create function outparam_succeed(i anyrange, out r anyrange, out t text)
   as $$ select $1, 'foo'::text $$ language sql;
 select * from outparam_succeed(int4range(1,2));
@@ -1525,6 +1545,16 @@ select * from outparam2_succeed(int4range(1,11));
  {1,11} | {11,1}
 (1 row)
 
+-- infer anyarray from anyrange
+create function outparam_succeed2(i anyrange, out r anyarray, out t text)
+  as $$ select ARRAY[upper($1)], 'foo'::text $$ language sql;
+select * from outparam_succeed2(int4range(int4range(1,2)));
+  r  |  t  
+-----+-----
+ {2} | foo
+(1 row)
+
+-- infer anyelement from anyrange
 create function inoutparam_succeed(out i anyelement, inout r anyrange)
   as $$ select upper($1), $1 $$ language sql;
 select * from inoutparam_succeed(int4range(1,2));
@@ -1547,14 +1577,14 @@ select * from table_succeed(int4range(1,11));
 create function outparam_fail(i anyelement, out r anyrange, out t text)
   as $$ select '[1,10]', 'foo' $$ language sql;
 ERROR:  cannot determine result data type
-DETAIL:  A result of type anyrange requires at least one input of type anyrange.
+DETAIL:  A result of type anyrange requires at least one input of type anyrange or anymultirange.
 --should fail
 create function inoutparam_fail(inout i anyelement, out r anyrange)
   as $$ select $1, '[1,10]' $$ language sql;
 ERROR:  cannot determine result data type
-DETAIL:  A result of type anyrange requires at least one input of type anyrange.
+DETAIL:  A result of type anyrange requires at least one input of type anyrange or anymultirange.
 --should fail
 create function table_fail(i anyelement) returns table(i anyelement, r anyrange)
   as $$ select $1, '[1,10]' $$ language sql;
 ERROR:  cannot determine result data type
-DETAIL:  A result of type anyrange requires at least one input of type anyrange.
+DETAIL:  A result of type anyrange requires at least one input of type anyrange or anymultirange.
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index 192445878d2..d9ce961be2b 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -92,6 +92,7 @@ num_exp_sqrt|t
 num_exp_sub|t
 num_input_test|f
 num_result|f
+nummultirange_test|t
 numrange_test|t
 onek|t
 onek2|t
@@ -172,6 +173,7 @@ quad_poly_tbl|t
 radix_text_tbl|t
 ramp|f
 real_city|f
+reservations|f
 road|t
 shighway|t
 slow_emp4000|f
diff --git a/src/test/regress/expected/type_sanity.out b/src/test/regress/expected/type_sanity.out
index 13567ddf84b..0c74dc96a87 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'));
@@ -196,18 +196,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
@@ -241,17 +242,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
@@ -319,18 +321,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
@@ -364,17 +367,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
@@ -660,3 +664,11 @@ WHERE pronargs != 2
 ----------+------------+---------
 (0 rows)
 
+-- every range should have a valid multirange
+SELECT p1.rngtypid, p1.rngsubtype, p1.rngmultitypid
+FROM pg_range p1
+WHERE p1.rngmultitypid IS NULL OR p1.rngmultitypid = 0;
+ rngtypid | rngsubtype | rngmultitypid 
+----------+------------+---------------
+(0 rows)
+
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index ae89ed7f0b4..432f454aa17 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -19,15 +19,16 @@ 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 xid
+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
 # geometry depends on point, lseg, box, path, polygon and circle
 # horology depends on interval, timetz, timestamp, timestamptz
 # ----------
-test: geometry horology regex oidjoins type_sanity opr_sanity misc_sanity comments expressions unicode
+test: geometry horology regex oidjoins type_sanity opr_sanity misc_sanity comments expressions unicode xid
 
 # ----------
 # These four each depend on the previous one
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index 525bdc804f6..081fce32e75 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -20,6 +20,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 681cc92ac20..0292dedd15a 100644
--- a/src/test/regress/sql/hash_func.sql
+++ b/src/test/regress/sql/hash_func.sql
@@ -227,6 +227,16 @@ FROM   (VALUES (int4range(10, 20)), (int4range(23, 43)),
 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);
+
 CREATE TYPE hash_test_t1 AS (a int, b text);
 SELECT v as value, hash_record(v)::bit(32) as standard,
        hash_record_extended(v, 0)::bit(32) as extended0,
diff --git a/src/test/regress/sql/multirangetypes.sql b/src/test/regress/sql/multirangetypes.sql
new file mode 100644
index 00000000000..31375e63238
--- /dev/null
+++ b/src/test/regress/sql/multirangetypes.sql
@@ -0,0 +1,663 @@
+-- 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'));
+
+--
+-- test casts, both a built-in range type and a user-defined one:
+--
+select 'empty'::int4range::int4multirange;
+select int4range(1, 3)::int4multirange;
+select int4range(1, null)::int4multirange;
+select int4range(null, null)::int4multirange;
+select 'empty'::textrange::textmultirange;
+select textrange('a', 'c')::textmultirange;
+select textrange('a', null)::textmultirange;
+select textrange(null, null)::textmultirange;
+
+--
+-- create some test data and test the operators
+--
+
+CREATE TABLE nummultirange_test (nmr NUMMULTIRANGE);
+CREATE INDEX nummultirange_test_btree ON nummultirange_test(nmr);
+
+INSERT INTO nummultirange_test VALUES('{}');
+INSERT INTO nummultirange_test VALUES('{[,)}');
+INSERT INTO nummultirange_test VALUES('{[3,]}');
+INSERT INTO nummultirange_test VALUES('{[,), [3,]}');
+INSERT INTO nummultirange_test VALUES('{[, 5)}');
+INSERT INTO nummultirange_test VALUES(nummultirange());
+INSERT INTO nummultirange_test VALUES(nummultirange(variadic '{}'::numrange[]));
+INSERT INTO nummultirange_test VALUES(nummultirange(numrange(1.1, 2.2)));
+INSERT INTO nummultirange_test VALUES('{empty}');
+INSERT INTO nummultirange_test VALUES(nummultirange(numrange(1.7, 1.7, '[]'), numrange(1.7, 1.9)));
+INSERT INTO nummultirange_test VALUES(nummultirange(numrange(1.7, 1.7, '[]'), numrange(1.9, 2.1)));
+
+SELECT nmr, isempty(nmr), lower(nmr), upper(nmr) FROM nummultirange_test ORDER BY nmr;
+SELECT nmr, lower_inc(nmr), lower_inf(nmr), upper_inc(nmr), upper_inf(nmr) FROM nummultirange_test ORDER BY nmr;
+
+SELECT * FROM nummultirange_test WHERE nmr = '{}';
+SELECT * FROM nummultirange_test WHERE nmr = '{(,5)}';
+SELECT * FROM nummultirange_test WHERE nmr = '{[3,)}';
+SELECT * FROM nummultirange_test WHERE nmr = '{[1.7,1.7]}';
+SELECT * FROM nummultirange_test WHERE nmr = '{[1.7,1.7],[1.9,2.1)}';
+SELECT * FROM nummultirange_test WHERE nmr < '{}';
+SELECT * FROM nummultirange_test WHERE nmr < '{[-1000.0, -1000.0]}';
+SELECT * FROM nummultirange_test WHERE nmr < '{[0.0, 1.0]}';
+SELECT * FROM nummultirange_test WHERE nmr < '{[1000.0, 1001.0]}';
+SELECT * FROM nummultirange_test WHERE nmr <= '{}';
+SELECT * FROM nummultirange_test WHERE nmr <= '{[3,)}';
+SELECT * FROM nummultirange_test WHERE nmr >= '{}';
+SELECT * FROM nummultirange_test WHERE nmr >= '{[3,)}';
+SELECT * FROM nummultirange_test WHERE nmr > '{}';
+SELECT * FROM nummultirange_test WHERE nmr > '{[-1000.0, -1000.0]}';
+SELECT * FROM nummultirange_test WHERE nmr > '{[0.0, 1.0]}';
+SELECT * FROM nummultirange_test WHERE nmr > '{[1000.0, 1001.0]}';
+SELECT * FROM nummultirange_test WHERE nmr <> '{}';
+SELECT * FROM nummultirange_test WHERE nmr <> '{(,5)}';
+
+select nummultirange(numrange(2.0, 1.0));
+select nummultirange(numrange(5.0, 6.0), numrange(1.0, 2.0));
+
+analyze nummultirange_test;
+
+-- overlaps
+SELECT * FROM nummultirange_test WHERE range_overlaps_multirange(numrange(4.0, 4.2), nmr);
+SELECT * FROM nummultirange_test WHERE numrange(4.0, 4.2) && nmr;
+SELECT * FROM nummultirange_test WHERE multirange_overlaps_range(nmr, numrange(4.0, 4.2));
+SELECT * FROM nummultirange_test WHERE nmr && numrange(4.0, 4.2);
+SELECT * FROM nummultirange_test WHERE multirange_overlaps_multirange(nmr, nummultirange(numrange(4.0, 4.2), numrange(6.0, 7.0)));
+SELECT * FROM nummultirange_test WHERE nmr && nummultirange(numrange(4.0, 4.2), numrange(6.0, 7.0));
+SELECT * FROM nummultirange_test WHERE nmr && nummultirange(numrange(6.0, 7.0));
+SELECT * FROM nummultirange_test WHERE nmr && nummultirange(numrange(6.0, 7.0), numrange(8.0, 9.0));
+
+-- mr contains x
+SELECT * FROM nummultirange_test WHERE multirange_contains_elem(nmr, 4.0);
+SELECT * FROM nummultirange_test WHERE nmr @> 4.0;
+SELECT * FROM nummultirange_test WHERE multirange_contains_range(nmr, numrange(4.0, 4.2));
+SELECT * FROM nummultirange_test WHERE nmr @> numrange(4.0, 4.2);
+SELECT * FROM nummultirange_test WHERE multirange_contains_multirange(nmr, '{[4.0,4.2), [6.0, 8.0)}');
+SELECT * FROM nummultirange_test WHERE nmr @> '{[4.0,4.2), [6.0, 8.0)}'::nummultirange;
+
+-- x is contained by mr
+SELECT * FROM nummultirange_test WHERE elem_contained_by_multirange(4.0, nmr);
+SELECT * FROM nummultirange_test WHERE 4.0 <@ nmr;
+SELECT * FROM nummultirange_test WHERE range_contained_by_multirange(numrange(4.0, 4.2), nmr);
+SELECT * FROM nummultirange_test WHERE numrange(4.0, 4.2) <@ nmr;
+SELECT * FROM nummultirange_test WHERE multirange_contained_by_multirange('{[4.0,4.2), [6.0, 8.0)}', nmr);
+SELECT * FROM nummultirange_test WHERE '{[4.0,4.2), [6.0, 8.0)}'::nummultirange <@ nmr;
+
+-- overlaps
+SELECT 'empty'::numrange && nummultirange();
+SELECT 'empty'::numrange && nummultirange(numrange(1,2));
+SELECT nummultirange() && 'empty'::numrange;
+SELECT nummultirange(numrange(1,2)) && 'empty'::numrange;
+SELECT nummultirange() && nummultirange();
+SELECT nummultirange() && nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) && nummultirange();
+SELECT nummultirange(numrange(3,4)) && nummultirange(numrange(1,2), numrange(7,8));
+SELECT nummultirange(numrange(1,2), numrange(7,8)) && nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(3,4)) && nummultirange(numrange(1,2), numrange(3.5,8));
+SELECT nummultirange(numrange(1,2), numrange(3.5,8)) && numrange(3,4);
+SELECT nummultirange(numrange(1,2), numrange(3.5,8)) && nummultirange(numrange(3,4));
+
+-- contains
+SELECT nummultirange() @> nummultirange();
+SELECT nummultirange() @> 'empty'::numrange;
+SELECT nummultirange(numrange(null,null)) @> numrange(1,2);
+SELECT nummultirange(numrange(null,null)) @> numrange(null,2);
+SELECT nummultirange(numrange(null,null)) @> numrange(2,null);
+SELECT nummultirange(numrange(null,5)) @> numrange(null,3);
+SELECT nummultirange(numrange(null,5)) @> numrange(null,8);
+SELECT nummultirange(numrange(5,null)) @> numrange(8,null);
+SELECT nummultirange(numrange(5,null)) @> numrange(3,null);
+SELECT nummultirange(numrange(1,5)) @> numrange(8,9);
+SELECT nummultirange(numrange(1,5)) @> numrange(3,9);
+SELECT nummultirange(numrange(1,5)) @> numrange(1,4);
+SELECT nummultirange(numrange(1,5)) @> numrange(1,5);
+SELECT nummultirange(numrange(-4,-2), numrange(1,5)) @> numrange(1,5);
+SELECT nummultirange(numrange(1,5), numrange(8,9)) @> numrange(1,5);
+SELECT nummultirange(numrange(1,5), numrange(8,9)) @> numrange(6,7);
+SELECT nummultirange(numrange(1,5), numrange(6,9)) @> numrange(6,7);
+SELECT '{[1,5)}'::nummultirange @> '{[1,5)}';
+SELECT '{[-4,-2), [1,5)}'::nummultirange @> '{[1,5)}';
+SELECT '{[1,5), [8,9)}'::nummultirange @> '{[1,5)}';
+SELECT '{[1,5), [8,9)}'::nummultirange @> '{[6,7)}';
+SELECT '{[1,5), [6,9)}'::nummultirange @> '{[6,7)}';
+
+-- is contained by
+SELECT nummultirange() <@ nummultirange();
+SELECT 'empty'::numrange <@ nummultirange();
+SELECT numrange(1,2) <@ nummultirange(numrange(null,null));
+SELECT numrange(null,2) <@ nummultirange(numrange(null,null));
+SELECT numrange(2,null) <@ nummultirange(numrange(null,null));
+SELECT numrange(null,3) <@ nummultirange(numrange(null,5));
+SELECT numrange(null,8) <@ nummultirange(numrange(null,5));
+SELECT numrange(8,null) <@ nummultirange(numrange(5,null));
+SELECT numrange(3,null) <@ nummultirange(numrange(5,null));
+SELECT numrange(8,9) <@ nummultirange(numrange(1,5));
+SELECT numrange(3,9) <@ nummultirange(numrange(1,5));
+SELECT numrange(1,4) <@ nummultirange(numrange(1,5));
+SELECT numrange(1,5) <@ nummultirange(numrange(1,5));
+SELECT numrange(1,5) <@ nummultirange(numrange(-4,-2), numrange(1,5));
+SELECT numrange(1,5) <@ nummultirange(numrange(1,5), numrange(8,9));
+SELECT numrange(6,7) <@ nummultirange(numrange(1,5), numrange(8,9));
+SELECT numrange(6,7) <@ nummultirange(numrange(1,5), numrange(6,9));
+SELECT '{[1,5)}' <@ '{[1,5)}'::nummultirange;
+SELECT '{[1,5)}' <@ '{[-4,-2), [1,5)}'::nummultirange;
+SELECT '{[1,5)}' <@ '{[1,5), [8,9)}'::nummultirange;
+SELECT '{[6,7)}' <@ '{[1,5), [8,9)}'::nummultirange;
+SELECT '{[6,7)}' <@ '{[1,5), [6,9)}'::nummultirange;
+
+-- overleft
+SELECT 'empty'::numrange &< nummultirange();
+SELECT 'empty'::numrange &< nummultirange(numrange(1,2));
+SELECT nummultirange() &< 'empty'::numrange;
+SELECT nummultirange(numrange(1,2)) &< 'empty'::numrange;
+SELECT nummultirange() &< nummultirange();
+SELECT nummultirange(numrange(1,2)) &< nummultirange();
+SELECT nummultirange() &< nummultirange(numrange(1,2));
+SELECT numrange(6,7) &< nummultirange(numrange(3,4));
+SELECT numrange(1,2) &< nummultirange(numrange(3,4));
+SELECT numrange(1,4) &< nummultirange(numrange(3,4));
+SELECT numrange(1,6) &< nummultirange(numrange(3,4));
+SELECT numrange(3.5,6) &< nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(6,7)) &< numrange(3,4);
+SELECT nummultirange(numrange(1,2)) &< numrange(3,4);
+SELECT nummultirange(numrange(1,4)) &< numrange(3,4);
+SELECT nummultirange(numrange(1,6)) &< numrange(3,4);
+SELECT nummultirange(numrange(3.5,6)) &< numrange(3,4);
+SELECT nummultirange(numrange(6,7)) &< nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,2)) &< nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,4)) &< nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,6)) &< nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(3.5,6)) &< nummultirange(numrange(3,4));
+
+-- overright
+SELECT nummultirange() &> 'empty'::numrange;
+SELECT nummultirange(numrange(1,2)) &> 'empty'::numrange;
+SELECT 'empty'::numrange &> nummultirange();
+SELECT 'empty'::numrange &> nummultirange(numrange(1,2));
+SELECT nummultirange() &> nummultirange();
+SELECT nummultirange() &> nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) &> nummultirange();
+SELECT nummultirange(numrange(3,4)) &> numrange(6,7);
+SELECT nummultirange(numrange(3,4)) &> numrange(1,2);
+SELECT nummultirange(numrange(3,4)) &> numrange(1,4);
+SELECT nummultirange(numrange(3,4)) &> numrange(1,6);
+SELECT nummultirange(numrange(3,4)) &> numrange(3.5,6);
+SELECT numrange(3,4) &> nummultirange(numrange(6,7));
+SELECT numrange(3,4) &> nummultirange(numrange(1,2));
+SELECT numrange(3,4) &> nummultirange(numrange(1,4));
+SELECT numrange(3,4) &> nummultirange(numrange(1,6));
+SELECT numrange(3,4) &> nummultirange(numrange(3.5,6));
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(6,7));
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(1,4));
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(1,6));
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(3.5,6));
+
+-- meets
+SELECT 'empty'::numrange -|- nummultirange();
+SELECT 'empty'::numrange -|- nummultirange(numrange(1,2));
+SELECT nummultirange() -|- 'empty'::numrange;
+SELECT nummultirange(numrange(1,2)) -|- 'empty'::numrange;
+SELECT nummultirange() -|- nummultirange();
+SELECT nummultirange(numrange(1,2)) -|- nummultirange();
+SELECT nummultirange() -|- nummultirange(numrange(1,2));
+SELECT numrange(1,2) -|- nummultirange(numrange(2,4));
+SELECT numrange(1,2) -|- nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,2)) -|- numrange(2,4);
+SELECT nummultirange(numrange(1,2)) -|- numrange(3,4);
+SELECT nummultirange(numrange(1,2)) -|- nummultirange(numrange(2,4));
+SELECT nummultirange(numrange(1,2)) -|- nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,2), numrange(5,6)) -|- nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,2), numrange(5,6)) -|- nummultirange(numrange(6,7));
+SELECT nummultirange(numrange(1,2), numrange(5,6)) -|- nummultirange(numrange(8,9));
+SELECT nummultirange(numrange(1,2)) -|- nummultirange(numrange(2,4), numrange(6,7));
+
+-- strictly left
+select 'empty'::numrange << nummultirange();
+select numrange(1,2) << nummultirange();
+select numrange(1,2) << nummultirange(numrange(3,4));
+select numrange(1,2) << nummultirange(numrange(0,4));
+select numrange(1,2) << nummultirange(numrange(0,4), numrange(7,8));
+select nummultirange() << 'empty'::numrange;
+select nummultirange() << numrange(1,2);
+select nummultirange(numrange(3,4)) << numrange(3,6);
+select nummultirange(numrange(0,2)) << numrange(3,6);
+select nummultirange(numrange(0,2), numrange(7,8)) << numrange(3,6);
+select nummultirange(numrange(-4,-2), numrange(0,2)) << numrange(3,6);
+select nummultirange() << nummultirange();
+select nummultirange() << nummultirange(numrange(1,2));
+select nummultirange(numrange(1,2)) << nummultirange();
+select nummultirange(numrange(1,2)) << nummultirange(numrange(1,2));
+select nummultirange(numrange(1,2)) << nummultirange(numrange(3,4));
+select nummultirange(numrange(1,2)) << nummultirange(numrange(3,4), numrange(7,8));
+select nummultirange(numrange(1,2), numrange(4,5)) << nummultirange(numrange(3,4), numrange(7,8));
+
+-- strictly right
+select nummultirange() >> 'empty'::numrange;
+select nummultirange() >> numrange(1,2);
+select nummultirange(numrange(3,4)) >> numrange(1,2);
+select nummultirange(numrange(0,4)) >> numrange(1,2);
+select nummultirange(numrange(0,4), numrange(7,8)) >> numrange(1,2);
+select 'empty'::numrange >> nummultirange();
+select numrange(1,2) >> nummultirange();
+select numrange(3,6) >> nummultirange(numrange(3,4));
+select numrange(3,6) >> nummultirange(numrange(0,2));
+select numrange(3,6) >> nummultirange(numrange(0,2), numrange(7,8));
+select numrange(3,6) >> nummultirange(numrange(-4,-2), numrange(0,2));
+select nummultirange() >> nummultirange();
+select nummultirange(numrange(1,2)) >> nummultirange();
+select nummultirange() >> nummultirange(numrange(1,2));
+select nummultirange(numrange(1,2)) >> nummultirange(numrange(1,2));
+select nummultirange(numrange(3,4)) >> nummultirange(numrange(1,2));
+select nummultirange(numrange(3,4), numrange(7,8)) >> nummultirange(numrange(1,2));
+select nummultirange(numrange(3,4), numrange(7,8)) >> nummultirange(numrange(1,2), numrange(4,5));
+
+-- union
+SELECT nummultirange() + nummultirange();
+SELECT nummultirange() + nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) + nummultirange();
+SELECT nummultirange(numrange(1,2)) + nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) + nummultirange(numrange(2,4));
+SELECT nummultirange(numrange(1,2)) + nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) + nummultirange(numrange(2,4));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) + nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) + nummultirange(numrange(0,9));
+
+-- merge
+SELECT range_merge(nummultirange());
+SELECT range_merge(nummultirange(numrange(1,2)));
+SELECT range_merge(nummultirange(numrange(1,2), numrange(7,8)));
+
+-- minus
+SELECT nummultirange() - nummultirange();
+SELECT nummultirange() - nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) - nummultirange();
+SELECT nummultirange(numrange(1,2), numrange(3,4)) - nummultirange();
+SELECT nummultirange(numrange(1,2)) - nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) - nummultirange(numrange(2,4));
+SELECT nummultirange(numrange(1,2)) - nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(2,3));
+SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(0,8));
+SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(0,2));
+SELECT nummultirange(numrange(1,8)) - nummultirange(numrange(0,2), numrange(3,4));
+SELECT nummultirange(numrange(1,8)) - nummultirange(numrange(2,3), numrange(5,null));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(-2,0));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(2,4));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(3,5));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(0,9));
+SELECT nummultirange(numrange(1,3), numrange(4,5)) - nummultirange(numrange(2,9));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(8,9));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(-2,0), numrange(8,9));
+
+-- intersection
+SELECT nummultirange() * nummultirange();
+SELECT nummultirange() * nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) * nummultirange();
+SELECT '{[1,3)}'::nummultirange * '{[1,5)}'::nummultirange;
+SELECT '{[1,3)}'::nummultirange * '{[0,5)}'::nummultirange;
+SELECT '{[1,3)}'::nummultirange * '{[0,2)}'::nummultirange;
+SELECT '{[1,3)}'::nummultirange * '{[2,5)}'::nummultirange;
+SELECT '{[1,4)}'::nummultirange * '{[2,3)}'::nummultirange;
+SELECT '{[1,4)}'::nummultirange * '{[0,2), [3,5)}'::nummultirange;
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[0,8), [9,12)}'::nummultirange;
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[9,12)}'::nummultirange;
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[-5,-4), [5,6), [9,12)}'::nummultirange;
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[0,2), [3,8), [9,12)}'::nummultirange;
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[0,2), [3,8), [9,12)}'::nummultirange;
+
+--
+-- range_agg function
+--
+create table reservations ( room_id integer not null, booked_during daterange );
+insert into reservations values
+-- 1: has a meets and a gap
+(1, daterange('2018-07-01', '2018-07-07')),
+(1, daterange('2018-07-07', '2018-07-14')),
+(1, daterange('2018-07-20', '2018-07-22')),
+-- 2: just a single row
+(2, daterange('2018-07-01', '2018-07-03')),
+-- 3: one null range
+(3, NULL),
+-- 4: two null ranges
+(4, NULL),
+(4, NULL),
+-- 5: a null range and a non-null range
+(5, NULL),
+(5, daterange('2018-07-01', '2018-07-03')),
+-- 6: has overlap
+(6, daterange('2018-07-01', '2018-07-07')),
+(6, daterange('2018-07-05', '2018-07-10')),
+-- 7: two ranges that meet: no gap or overlap
+(7, daterange('2018-07-01', '2018-07-07')),
+(7, daterange('2018-07-07', '2018-07-14')),
+-- 8: an empty range
+(8, 'empty'::daterange)
+;
+SELECT   room_id, range_agg(booked_during)
+FROM     reservations
+GROUP BY room_id
+ORDER BY room_id;
+
+-- range_agg on a custom range type too
+SELECT  range_agg(r)
+FROM    (VALUES
+          ('[a,c]'::textrange),
+          ('[b,b]'::textrange),
+          ('[c,f]'::textrange),
+          ('[g,h)'::textrange),
+          ('[h,j)'::textrange)
+        ) t(r);
+
+select range_intersect_agg(nmr) from nummultirange_test;
+select range_intersect_agg(nmr) from nummultirange_test where false;
+-- test with just one input:
+select range_intersect_agg(nmr) from (values ('{[1,2]}'::nummultirange)) t(nmr);
+select range_intersect_agg(nmr) from nummultirange_test where nmr @> 4.0;
+
+create table nummultirange_test2(nmr nummultirange);
+create index nummultirange_test2_hash_idx on nummultirange_test2 using hash (nmr);
+
+INSERT INTO nummultirange_test2 VALUES('{[, 5)}');
+INSERT INTO nummultirange_test2 VALUES(nummultirange(numrange(1.1, 2.2)));
+INSERT INTO nummultirange_test2 VALUES(nummultirange(numrange(1.1, 2.2)));
+INSERT INTO nummultirange_test2 VALUES(nummultirange(numrange(1.1, 2.2,'()')));
+INSERT INTO nummultirange_test2 VALUES('{}');
+
+select * from nummultirange_test2 where nmr = '{}';
+select * from nummultirange_test2 where nmr = nummultirange(numrange(1.1, 2.2));
+select * from nummultirange_test2 where nmr = nummultirange(numrange(1.1, 2.3));
+
+set enable_nestloop=t;
+set enable_hashjoin=f;
+set enable_mergejoin=f;
+select * from nummultirange_test natural join nummultirange_test2 order by nmr;
+set enable_nestloop=f;
+set enable_hashjoin=t;
+set enable_mergejoin=f;
+select * from nummultirange_test natural join nummultirange_test2 order by nmr;
+set enable_nestloop=f;
+set enable_hashjoin=f;
+set enable_mergejoin=t;
+select * from nummultirange_test natural join nummultirange_test2 order by nmr;
+
+set enable_nestloop to default;
+set enable_hashjoin to default;
+set enable_mergejoin to default;
+
+DROP TABLE nummultirange_test2;
+
+--
+-- Test user-defined multirange of floats
+--
+
+select '{[123.001, 5.e9)}'::float8multirange @> 888.882::float8;
+create table float8multirange_test(f8mr float8multirange, i int);
+insert into float8multirange_test values(float8multirange(float8range(-100.00007, '1.111113e9')), 42);
+select * from float8multirange_test;
+drop table float8multirange_test;
+
+--
+-- Test multirange types over domains
+--
+
+create domain mydomain as int4;
+create type mydomainrange as range(subtype=mydomain);
+select '{[4,50)}'::mydomainmultirange @> 7::mydomain;
+drop domain mydomain cascade;
+
+--
+-- Test domains over multirange types
+--
+
+create domain restrictedmultirange as int4multirange check (upper(value) < 10);
+select '{[4,5)}'::restrictedmultirange @> 7;
+select '{[4,50)}'::restrictedmultirange @> 7; -- should fail
+drop domain restrictedmultirange;
+
+--
+-- Test multiple multirange types over the same subtype and manual naming of
+-- the multirange type.
+--
+
+-- should fail
+create type textrange1 as range(subtype=text, multirange_type_name=int, collation="C");
+-- should pass
+create type textrange1 as range(subtype=text, multirange_type_name=multirange_of_text, collation="C");
+-- should pass, because existing _textrange1 is automatically renamed
+create type textrange2 as range(subtype=text, multirange_type_name=_textrange1, collation="C");
+
+select multirange_of_text(textrange2('a','Z'));  -- should fail
+select multirange_of_text(textrange1('a','Z')) @> 'b'::text;
+select _textrange1(textrange2('a','z')) @> 'b'::text;
+
+drop type textrange1;
+drop type textrange2;
+
+--
+-- Test polymorphic type system
+--
+
+create function anyarray_anymultirange_func(a anyarray, r anymultirange)
+  returns anyelement as 'select $1[1] + lower($2);' language sql;
+
+select anyarray_anymultirange_func(ARRAY[1,2], int4multirange(int4range(10,20)));
+
+-- should fail
+select anyarray_anymultirange_func(ARRAY[1,2], nummultirange(numrange(10,20)));
+
+drop function anyarray_anymultirange_func(anyarray, anymultirange);
+
+-- should fail
+create function bogus_func(anyelement)
+  returns anymultirange as 'select int4multirange(int4range(1,10))' language sql;
+
+-- should fail
+create function bogus_func(int)
+  returns anymultirange as 'select int4multirange(int4range(1,10))' language sql;
+
+create function range_add_bounds(anymultirange)
+  returns anyelement as 'select lower($1) + upper($1)' language sql;
+
+select range_add_bounds(int4multirange(int4range(1, 17)));
+select range_add_bounds(nummultirange(numrange(1.0001, 123.123)));
+
+create function multirangetypes_sql(q anymultirange, b anyarray, out c anyelement)
+  as $$ select upper($1) + $2[1] $$
+  language sql;
+
+select multirangetypes_sql(int4multirange(int4range(1,10)), ARRAY[2,20]);
+select multirangetypes_sql(nummultirange(numrange(1,10)), ARRAY[2,20]);  -- match failure
+
+create function anycompatiblearray_anycompatiblemultirange_func(a anycompatiblearray, mr anycompatiblemultirange)
+  returns anycompatible as 'select $1[1] + lower($2);' language sql;
+
+select anycompatiblearray_anycompatiblemultirange_func(ARRAY[1,2], multirange(int4range(10,20)));
+
+select anycompatiblearray_anycompatiblemultirange_func(ARRAY[1,2], multirange(numrange(10,20)));
+
+-- should fail
+select anycompatiblearray_anycompatiblemultirange_func(ARRAY[1.1,2], multirange(int4range(10,20)));
+
+drop function anycompatiblearray_anycompatiblemultirange_func(anycompatiblearray, anycompatiblemultirange);
+
+create function anycompatiblerange_anycompatiblemultirange_func(r anycompatiblerange, mr anycompatiblemultirange)
+  returns anycompatible as 'select lower($1) + lower($2);' language sql;
+
+select anycompatiblerange_anycompatiblemultirange_func(int4range(1,2), multirange(int4range(10,20)));
+
+-- should fail
+select anycompatiblerange_anycompatiblemultirange_func(numrange(1,2), multirange(int4range(10,20)));
+
+drop function anycompatiblerange_anycompatiblemultirange_func(anycompatiblerange, anycompatiblemultirange);
+
+-- should fail
+create function bogus_func(anycompatible)
+  returns anycompatiblerange as 'select int4range(1,10)' language sql;
+
+--
+-- Arrays of multiranges
+--
+
+select ARRAY[nummultirange(numrange(1.1, 1.2)), nummultirange(numrange(12.3, 155.5))];
+
+create table i8mr_array (f1 int, f2 int8multirange[]);
+insert into i8mr_array values (42, array[int8multirange(int8range(1,10)), int8multirange(int8range(2,20))]);
+select * from i8mr_array;
+drop table i8mr_array;
+
+--
+-- Multiranges of arrays
+--
+
+select arraymultirange(arrayrange(ARRAY[1,2], ARRAY[2,1]));
+select arraymultirange(arrayrange(ARRAY[2,1], ARRAY[1,2]));  -- fail
+
+select array[1,1] <@ arraymultirange(arrayrange(array[1,2], array[2,1]));
+select array[1,3] <@ arraymultirange(arrayrange(array[1,2], array[2,1]));
+
+--
+-- Ranges of composites
+--
+
+create type two_ints as (a int, b int);
+create type two_ints_range as range (subtype = two_ints);
+
+-- with force_parallel_mode on, this exercises tqueue.c's range remapping
+select *, row_to_json(upper(t)) as u from
+  (values (two_ints_multirange(two_ints_range(row(1,2), row(3,4)))),
+          (two_ints_multirange(two_ints_range(row(5,6), row(7,8))))) v(t);
+
+drop type two_ints cascade;
+
+--
+-- Check behavior when subtype lacks a hash function
+--
+
+set enable_sort = off;  -- try to make it pick a hash setop implementation
+
+select '{(2,5)}'::cashmultirange except select '{(5,6)}'::cashmultirange;
+
+reset enable_sort;
+
+--
+-- OUT/INOUT/TABLE functions
+--
+
+-- infer anymultirange from anymultirange
+create function mr_outparam_succeed(i anymultirange, out r anymultirange, out t text)
+  as $$ select $1, 'foo'::text $$ language sql;
+
+select * from mr_outparam_succeed(int4multirange(int4range(1,2)));
+
+-- infer anyarray from anymultirange
+create function mr_outparam_succeed2(i anymultirange, out r anyarray, out t text)
+  as $$ select ARRAY[upper($1)], 'foo'::text $$ language sql;
+
+select * from mr_outparam_succeed2(int4multirange(int4range(1,2)));
+
+-- infer anyrange from anymultirange
+create function mr_outparam_succeed3(i anymultirange, out r anyrange, out t text)
+  as $$ select range_merge($1), 'foo'::text $$ language sql;
+select * from mr_outparam_succeed3(int4multirange(int4range(1,2)));
+
+-- infer anymultirange from anyrange
+create function mr_outparam_succeed4(i anyrange, out r anymultirange, out t text)
+  as $$ select multirange($1), 'foo'::text $$ language sql;
+
+select * from mr_outparam_succeed4(int4range(1,2));
+
+-- infer anyelement from anymultirange
+create function mr_inoutparam_succeed(out i anyelement, inout r anymultirange)
+  as $$ select upper($1), $1 $$ language sql;
+
+select * from mr_inoutparam_succeed(int4multirange(int4range(1,2)));
+
+-- infer anyelement+anymultirange from anyelement+anymultirange
+create function mr_table_succeed(i anyelement, r anymultirange) returns table(i anyelement, r anymultirange)
+  as $$ select $1, $2 $$ language sql;
+
+select * from mr_table_succeed(123, int4multirange(int4range(1,11)));
+
+-- use anymultirange in plpgsql
+create function mr_polymorphic(i anyrange) returns anymultirange
+  as $$ begin return multirange($1); end; $$ language plpgsql;
+select mr_polymorphic(int4range(1, 4));
+
+-- should fail
+create function mr_outparam_fail(i anyelement, out r anymultirange, out t text)
+  as $$ select '[1,10]', 'foo' $$ language sql;
+
+--should fail
+create function mr_inoutparam_fail(inout i anyelement, out r anymultirange)
+  as $$ select $1, '[1,10]' $$ language sql;
+
+--should fail
+create function mr_table_fail(i anyelement) returns table(i anyelement, r anymultirange)
+  as $$ select $1, '[1,10]' $$ language sql;
diff --git a/src/test/regress/sql/opr_sanity.sql b/src/test/regress/sql/opr_sanity.sql
index 4189a5a4e09..bbd3834b634 100644
--- a/src/test/regress/sql/opr_sanity.sql
+++ b/src/test/regress/sql/opr_sanity.sql
@@ -42,6 +42,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;
@@ -166,7 +170,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::regtype, p2.prorettype::regtype
 FROM pg_proc AS p1, pg_proc AS p2
@@ -176,6 +181,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;
 
@@ -187,6 +194,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;
 
@@ -198,6 +207,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;
 
@@ -281,16 +292,19 @@ WHERE p1.prorettype IN
      '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;
 
--- anyrange is tighter than the rest, can only resolve from anyrange input
+-- anyrange and anymultirange are tighter than the rest, can only resolve
+-- from each other
 
 SELECT p1.oid, p1.proname
 FROM pg_proc as p1
-WHERE p1.prorettype = 'anyrange'::regtype
+WHERE p1.prorettype IN ('anyrange'::regtype, 'anymultirange'::regtype)
   AND NOT
-     'anyrange'::regtype = ANY (p1.proargtypes)
+    ('anyrange'::regtype = ANY (p1.proargtypes) OR
+      'anymultirange'::regtype = ANY (p1.proargtypes))
 ORDER BY 2;
 
 -- similarly for the anycompatible family
diff --git a/src/test/regress/sql/polymorphism.sql b/src/test/regress/sql/polymorphism.sql
index ff517fea41a..891b023ada0 100644
--- a/src/test/regress/sql/polymorphism.sql
+++ b/src/test/regress/sql/polymorphism.sql
@@ -71,6 +71,16 @@ select polyf(int4range(42, 49), 11, 4.5) as fail;  -- range type doesn't fit
 
 drop function polyf(x anycompatiblerange, y anycompatible, z anycompatible);
 
+create function polyf(x anycompatiblemultirange, y anycompatible, z anycompatible) returns anycompatiblearray as $$
+  select array[lower(x), upper(x), y, z]
+$$ language sql;
+
+select polyf(multirange(int4range(42, 49)), 11, 2::smallint) as int, polyf(multirange(float8range(4.5, 7.8)), 7.8, 11::real) as num;
+
+select polyf(multirange(int4range(42, 49)), 11, 4.5) as fail;  -- range type doesn't fit
+
+drop function polyf(x anycompatiblemultirange, y anycompatible, z anycompatible);
+
 -- fail, can't infer type:
 create function polyf(x anycompatible) returns anycompatiblerange as $$
   select array[x + 1, x + 2]
@@ -84,6 +94,19 @@ select polyf(int4range(42, 49), array[11]) as int, polyf(float8range(4.5, 7.8),
 
 drop function polyf(x anycompatiblerange, y anycompatiblearray);
 
+-- fail, can't infer type:
+create function polyf(x anycompatible) returns anycompatiblemultirange as $$
+  select array[x + 1, x + 2]
+$$ language sql;
+
+create function polyf(x anycompatiblemultirange, y anycompatiblearray) returns anycompatiblemultirange as $$
+  select x
+$$ language sql;
+
+select polyf(multirange(int4range(42, 49)), array[11]) as int, polyf(multirange(float8range(4.5, 7.8)), array[7]) as num;
+
+drop function polyf(x anycompatiblemultirange, y anycompatiblearray);
+
 create function polyf(a anyelement, b anyarray,
                       c anycompatible, d anycompatible,
                       OUT x anyarray, OUT y anycompatiblearray)
@@ -997,6 +1020,35 @@ returns anycompatiblerange as $$
   select $1
 $$ language sql;
 
+create function anyctest(anycompatible, anycompatiblemultirange)
+returns anycompatiblemultirange as $$
+  select $2
+$$ language sql;
+
+select x, pg_typeof(x) from anyctest(11, multirange(int4range(4,7))) x;
+select x, pg_typeof(x) from anyctest(11, multirange(numrange(4,7))) x;
+select x, pg_typeof(x) from anyctest(11, 12) x;  -- fail
+select x, pg_typeof(x) from anyctest(11.2, multirange(int4range(4,7))) x;  -- fail
+select x, pg_typeof(x) from anyctest(11.2, '{[4,7)}') x;  -- fail
+
+drop function anyctest(anycompatible, anycompatiblemultirange);
+
+create function anyctest(anycompatiblemultirange, anycompatiblemultirange)
+returns anycompatible as $$
+  select lower($1) + upper($2)
+$$ language sql;
+
+select x, pg_typeof(x) from anyctest(multirange(int4range(11,12)), multirange(int4range(4,7))) x;
+select x, pg_typeof(x) from anyctest(multirange(int4range(11,12)), multirange(numrange(4,7))) x; -- fail
+
+drop function anyctest(anycompatiblemultirange, anycompatiblemultirange);
+
+-- fail, can't infer result type:
+create function anyctest(anycompatible)
+returns anycompatiblemultirange as $$
+  select $1
+$$ language sql;
+
 create function anyctest(anycompatiblenonarray, anycompatiblenonarray)
 returns anycompatiblearray as $$
   select array[$1, $2]
diff --git a/src/test/regress/sql/rangetypes.sql b/src/test/regress/sql/rangetypes.sql
index 4048b1d1854..b69efede3ae 100644
--- a/src/test/regress/sql/rangetypes.sql
+++ b/src/test/regress/sql/rangetypes.sql
@@ -118,6 +118,12 @@ select numrange(1.0, 2.0) * numrange(2.0, 3.0);
 select numrange(1.0, 2.0) * numrange(1.5, 3.0);
 select numrange(1.0, 2.0) * numrange(2.5, 3.0);
 
+select range_intersect_agg(nr) from numrange_test;
+select range_intersect_agg(nr) from numrange_test where false;
+select range_intersect_agg(nr) from numrange_test where nr @> 4.0;
+
+analyze numrange_test;
+
 create table numrange_test2(nr numrange);
 create index numrange_test2_hash_idx on numrange_test2 using hash (nr);
 
@@ -528,6 +534,7 @@ reset enable_sort;
 -- OUT/INOUT/TABLE functions
 --
 
+-- infer anyrange from anyrange
 create function outparam_succeed(i anyrange, out r anyrange, out t text)
   as $$ select $1, 'foo'::text $$ language sql;
 
@@ -539,6 +546,13 @@ create function outparam2_succeed(r anyrange, out lu anyarray, out ul anyarray)
 
 select * from outparam2_succeed(int4range(1,11));
 
+-- infer anyarray from anyrange
+create function outparam_succeed2(i anyrange, out r anyarray, out t text)
+  as $$ select ARRAY[upper($1)], 'foo'::text $$ language sql;
+
+select * from outparam_succeed2(int4range(int4range(1,2)));
+
+-- infer anyelement from anyrange
 create function inoutparam_succeed(out i anyelement, inout r anyrange)
   as $$ select upper($1), $1 $$ language sql;
 
diff --git a/src/test/regress/sql/type_sanity.sql b/src/test/regress/sql/type_sanity.sql
index 8c6e614f20a..4739aca84a3 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'));
@@ -154,7 +154,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')
@@ -183,7 +183,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')
@@ -235,7 +235,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')
@@ -264,7 +264,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')
@@ -489,3 +489,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.rngmultitypid
+FROM pg_range p1
+WHERE p1.rngmultitypid IS NULL OR p1.rngmultitypid = 0;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index a9dca717a6d..abbce78210e 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -626,6 +626,7 @@ ExecutorStart_hook_type
 ExpandedArrayHeader
 ExpandedObjectHeader
 ExpandedObjectMethods
+ExpandedMultirangeHeader
 ExpandedRecordFieldInfo
 ExpandedRecordHeader
 ExplainDirectModify_function
@@ -1395,6 +1396,9 @@ MultiXactMember
 MultiXactOffset
 MultiXactStateData
 MultiXactStatus
+MultirangeIOData
+MultirangeParseState
+MultirangeType
 NDBOX
 NODE
 NUMCacheEntry
@@ -2276,6 +2280,7 @@ ShellTypeInfo
 ShippableCacheEntry
 ShippableCacheKey
 ShmemIndexEnt
+ShortRangeType
 ShutdownForeignScan_function
 ShutdownInformation
 ShutdownMode
#141Alexander Korotkov
aekorotkov@gmail.com
In reply to: Alexander Korotkov (#140)
1 attachment(s)
Re: range_agg

On Wed, Dec 16, 2020 at 2:21 AM Alexander Korotkov <aekorotkov@gmail.com> wrote:

I decided to work on this patch myself. The next revision is attached.

The changes are as follows.

1. CREATE TYPE ... AS RANGE command now accepts new argument
multirange_type_name. If multirange_type_name isn't specified, then
multirange type name is selected automatically. pg_dump always
specifies multirange_type_name (if dumping at least pg14). Thanks to
that dumps are always restorable.
2. Multiranges now have a new binary format. After the MultirangeType
struct, an array of offsets comes, then an array of flags and finally
bounds themselves. Offsets points to the bounds of particular range
within multirange. Thanks to that particular range could be accessed
by number without deserialization of the whole multirange. Offsets
are stored in compression-friendly format similar to jsonb (actually
only every 4th of those "offsets" is really offsets, others are
lengths).
3. Most of simple functions working with multirages now don't
deserialize the whole multirange. Instead they fetch bounds of
particular ranges, and that doesn't even require any additional memory
allocation.
4. I've removed ExpandedObject support from the patch. I don't see
much point in it assuming all the functions are returning serialized
multirage anyway. We can add ExpandedObject support in future if
needed.
5. multirange_contains_element(), multirange_contains_range(),
multirange_overlaps_range() now use binary search. Thanks to binary
format, which doesn't require full deserialization, these functions
now work with O(log N) complexity.

Comments and documentation still need revision according to these
changes. I'm going to continue with this.

The next 27th revision is attached. It contains minor documentation
and code changes, in particular it should address
commitfest.cputube.org complaints.

------
Regards,
Alexander Korotkov

Attachments:

v27-multirange.patchapplication/octet-stream; name=v27-multirange.patchDownload
commit 471299f14b265a3794f26a07f780c547e5d1b0e5
Author: Alexander Korotkov <akorotkov@postgresql.org>
Date:   Sun Nov 29 22:32:30 2020 +0300

    Multiranges v27

diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 62711ee83ff..34918485972 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -6237,6 +6237,16 @@ SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</replaceable>:<replaceable>&l
       </para></entry>
      </row>
 
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>rngmultitypid</structfield> <type>oid</type>
+       (references <link linkend="catalog-pg-type"><structname>pg_type</structname></link>.<structfield>oid</structfield>)
+      </para>
+      <para>
+       OID of the multirange type for this range type
+      </para></entry>
+     </row>
+
      <row>
       <entry role="catalog_table_entry"><para role="column_definition">
        <structfield>rngcollation</structfield> <type>oid</type>
@@ -8671,8 +8681,9 @@ SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</replaceable>:<replaceable>&l
        <literal>c</literal> for a composite type (e.g., a table's row type),
        <literal>d</literal> for a domain,
        <literal>e</literal> for an enum type,
-       <literal>p</literal> for a pseudo-type, or
-       <literal>r</literal> for a range type.
+       <literal>p</literal> for a pseudo-type,
+       <literal>r</literal> for a range type, or
+       <literal>m</literal> for a multirange type.
        See also <structfield>typrelid</structfield> and
        <structfield>typbasetype</structfield>.
       </para></entry>
diff --git a/doc/src/sgml/datatype.sgml b/doc/src/sgml/datatype.sgml
index 9eb19a1c616..58d168c763e 100644
--- a/doc/src/sgml/datatype.sgml
+++ b/doc/src/sgml/datatype.sgml
@@ -4907,6 +4907,10 @@ SELECT * FROM pg_attribute
     <primary>anyrange</primary>
    </indexterm>
 
+   <indexterm zone="datatype-pseudo">
+    <primary>anymultirange</primary>
+   </indexterm>
+
    <indexterm zone="datatype-pseudo">
     <primary>anycompatible</primary>
    </indexterm>
@@ -4923,6 +4927,10 @@ SELECT * FROM pg_attribute
     <primary>anycompatiblerange</primary>
    </indexterm>
 
+   <indexterm zone="datatype-pseudo">
+    <primary>anycompatiblemultirange</primary>
+   </indexterm>
+
    <indexterm zone="datatype-pseudo">
     <primary>void</primary>
    </indexterm>
@@ -5034,6 +5042,13 @@ SELECT * FROM pg_attribute
         <xref linkend="rangetypes"/>).</entry>
        </row>
 
+       <row>
+        <entry><type>anymultirange</type></entry>
+        <entry>Indicates that a function accepts any multirange data type
+        (see <xref linkend="extend-types-polymorphic"/> and
+        <xref linkend="rangetypes"/>).</entry>
+       </row>
+
        <row>
         <entry><type>anycompatible</type></entry>
         <entry>Indicates that a function accepts any data type,
@@ -5063,6 +5078,14 @@ SELECT * FROM pg_attribute
         <xref linkend="rangetypes"/>).</entry>
        </row>
 
+       <row>
+        <entry><type>anycompatiblemultirange</type></entry>
+        <entry>Indicates that a function accepts any multirange data type,
+        with automatic promotion of multiple arguments to a common data type
+        (see <xref linkend="extend-types-polymorphic"/> and
+        <xref linkend="rangetypes"/>).</entry>
+       </row>
+
        <row>
         <entry><type>cstring</type></entry>
         <entry>Indicates that a function accepts or returns a null-terminated C string.</entry>
diff --git a/doc/src/sgml/extend.sgml b/doc/src/sgml/extend.sgml
index 1c37026bb05..6e3d82b85b8 100644
--- a/doc/src/sgml/extend.sgml
+++ b/doc/src/sgml/extend.sgml
@@ -288,6 +288,14 @@
         </entry>
        </row>
 
+       <row>
+        <entry><type>anymultirange</type></entry>
+        <entry>Simple</entry>
+        <entry>Indicates that a function accepts any multirange data type
+        (see <xref linkend="rangetypes"/>)
+        </entry>
+       </row>
+
        <row>
         <entry><type>anycompatible</type></entry>
         <entry>Common</entry>
@@ -319,6 +327,14 @@
         with automatic promotion of multiple arguments to a common data type
         </entry>
        </row>
+
+       <row>
+        <entry><type>anycompatiblemultirange</type></entry>
+        <entry>Common</entry>
+        <entry>Indicates that a function accepts any multirange data type,
+        with automatic promotion of multiple arguments to a common data type
+        </entry>
+       </row>
       </tbody>
      </tgroup>
     </table>
@@ -346,17 +362,15 @@
      position declared as <type>anyarray</type> can have any array data type,
      but similarly they must all be the same type.  And similarly,
      positions declared as <type>anyrange</type> must all be the same range
-     type.  Furthermore, if there are
+     type.  Likewise for <type>anymultirange</type>.
+    </para>
+
+    <para>
+     Furthermore, if there are
      positions declared <type>anyarray</type> and others declared
      <type>anyelement</type>, the actual array type in the
      <type>anyarray</type> positions must be an array whose elements are
      the same type appearing in the <type>anyelement</type> positions.
-     Similarly, if there are positions declared <type>anyrange</type>
-     and others declared <type>anyelement</type> or <type>anyarray</type>,
-     the actual range type in the <type>anyrange</type> positions must be a
-     range whose subtype is the same type appearing in
-     the <type>anyelement</type> positions and the same as the element type
-     of the <type>anyarray</type> positions.
      <type>anynonarray</type> is treated exactly the same as <type>anyelement</type>,
      but adds the additional constraint that the actual type must not be
      an array type.
@@ -365,6 +379,19 @@
      be an enum type.
     </para>
 
+    <para>
+     Similarly, if there are positions declared <type>anyrange</type>
+     and others declared <type>anyelement</type> or <type>anyarray</type>,
+     the actual range type in the <type>anyrange</type> positions must be a
+     range whose subtype is the same type appearing in
+     the <type>anyelement</type> positions and the same as the element type
+     of the <type>anyarray</type> positions.
+     If there are positions declared <type>anymultirange</type>,
+     their actual multirange type must contain ranges matching parameters declared
+     <type>anyrange</type> and base elements matching parameters declared
+     <type>anyelement</type> and <type>anyarray</type>.
+    </para>
+
     <para>
      Thus, when more than one argument position is declared with a polymorphic
      type, the net effect is that only certain combinations of actual argument
@@ -420,7 +447,8 @@
      Selection of the common type considers the actual types
      of <type>anycompatible</type> and <type>anycompatiblenonarray</type>
      inputs, the array element types of <type>anycompatiblearray</type>
-     inputs, and the range subtypes of <type>anycompatiblerange</type>
+     inputs, the range subtypes of <type>anycompatiblerange</type> inputs,
+     and the multirange subtypes of <type>anycompatiablemultirange</type>
      inputs.  If <type>anycompatiblenonarray</type> is present then the
      common type is required to be a non-array type.  Once a common type is
      identified, arguments in <type>anycompatible</type>
@@ -431,12 +459,15 @@
 
     <para>
      Since there is no way to select a range type knowing only its subtype,
-     use of <type>anycompatiblerange</type> requires that all arguments
-     declared with that type have the same actual range type, and that that
-     type's subtype agree with the selected common type, so that no casting
-     of the range values is required.  As with <type>anyrange</type>, use
-     of <type>anycompatiblerange</type> as a function result type requires
-     that there be an <type>anycompatiblerange</type> argument.
+     use of <type>anycompatiblerange</type> and/or
+     <type>anycompatiblemultirange</type> requires that all arguments declared
+     with that type have the same actual range and/or multirange type, and that
+     that type's subtype agree with the selected common type, so that no casting
+     of the range values is required.  As with <type>anyrange</type> and
+     <type>anymultirange</type>, use of <type>anycompatiblerange</type> and
+     <type>anymultirange</type> as a function result type requires that there be
+     an <type>anycompatiblerange</type> or <type>anycompatiblemultirange</type>
+     argument.
     </para>
 
     <para>
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index df29af6371a..d5cd705eebb 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -17884,12 +17884,15 @@ SELECT NULLIF(value, '(none)') ...
   <para>
    <xref linkend="range-operators-table"/> shows the specialized operators
    available for range types.
+   <xref linkend="multirange-operators-table"/> shows the specialized operators
+   available for multirange types.
    In addition to those, the usual comparison operators shown in
    <xref linkend="functions-comparison-op-table"/> are available for range
-   types.  The comparison operators order first by the range lower bounds, and
-   only if those are equal do they compare the upper bounds.  This does not
-   usually result in a useful overall ordering, but the operators are provided
-   to allow unique indexes to be constructed on ranges.
+   and multirange types.  The comparison operators order first by the range lower
+   bounds, and only if those are equal do they compare the upper bounds.  The
+   multirange operators compare each range until one is unequal. This
+   does not usually result in a useful overall ordering, but the operators are
+   provided to allow unique indexes to be constructed on ranges.
   </para>
 
    <table id="range-operators-table">
@@ -18099,15 +18102,449 @@ SELECT NULLIF(value, '(none)') ...
     </tgroup>
    </table>
 
+   <table id="multirange-operators-table">
+    <title>Multirange Operators</title>
+    <tgroup cols="1">
+     <thead>
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        Operator
+       </para>
+       <para>
+        Description
+       </para>
+       <para>
+        Example(s)
+       </para></entry>
+      </row>
+     </thead>
+
+     <tbody>
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>@&gt;</literal> <type>anymultirange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Does the first multirange contain the second?
+       </para>
+       <para>
+        <literal>'{[2,4)}'::int4multirange @&gt; '{[2,3)}'::int4multirange</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>@&gt;</literal> <type>anyrange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Does the multirange contain the range?
+       </para>
+       <para>
+        <literal>'{[2,4)}'::int4multirange @&gt; int4range(2,3)</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>@&gt;</literal> <type>anyelement</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Does the multirange contain the element?
+       </para>
+       <para>
+        <literal>'{[2011-01-01,2011-03-01)}'::tsmultirange @&gt; '2011-01-10'::timestamp</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>&lt;@</literal> <type>anymultirange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the first multirange contained by the second?
+       </para>
+       <para>
+        <literal>'{[2,4)}'::int4multirange &lt;@ '{[1,7)}'::int4multirange</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>&lt;@</literal> <type>anyrange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the multirange contained by the range?
+       </para>
+       <para>
+        <literal>'{[2,4)}'::int4multirange &lt;@ int4range(1,7)</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anyrange</type> <literal>&lt;@</literal> <type>anymultirange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the range contained by the multirange?
+       </para>
+       <para>
+        <literal>int4range(2,4) &lt;@ '{[1,7)}'::int4multirange</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anyelement</type> <literal>&lt;@</literal> <type>anymultirange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the element contained by the multirange?
+       </para>
+       <para>
+        <literal>42 &lt;@ '{[1,7)}'::int4multirange</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>&amp;&amp;</literal> <type>anymultirange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Do the multiranges overlap, that is, have any elements in common?
+       </para>
+       <para>
+        <literal>'{[3,7)}'::int8multirange &amp;&amp; '{[4,12)}'::int8multirange</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>&amp;&amp;</literal> <type>anyrange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Does the multirange overlap the range?
+       </para>
+       <para>
+        <literal>'{[3,7)}'::int8multirange &amp;&amp; int8range(4,12)</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anyrange</type> <literal>&amp;&amp;</literal> <type>anymultirange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Does the range overlap the multirange?
+       </para>
+       <para>
+        <literal>int8range(3,7) &amp;&amp; '{[4,12)}'::int8multirange</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>&lt;&lt;</literal> <type>anymultirange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the first multirange strictly left of the second?
+       </para>
+       <para>
+        <literal>'{[1,10)}'::int8multirange &lt;&lt; '{[100,110)}'::int8multirange</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>&lt;&lt;</literal> <type>anyrange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the multirange strictly left of the range?
+       </para>
+       <para>
+        <literal>'{[1,10)}'::int8multirange &lt;&lt; int8range(100,110)</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anyrange</type> <literal>&lt;&lt;</literal> <type>anymultirange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the range strictly left of the multirange?
+       </para>
+       <para>
+        <literal>int8range(1,10) &lt;&lt; '{[100,110)}'::int8multirange</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>&gt;&gt;</literal> <type>anymultirange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the first multirange strictly right of the second?
+       </para>
+       <para>
+        <literal>'{[50,60)}'::int8multirange &gt;&gt; '{[20,30)}'::int8multirange</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>&gt;&gt;</literal> <type>anyrange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the multirange strictly right of the range?
+       </para>
+       <para>
+        <literal>'{[50,60)}'::int8multirange &gt;&gt; int8range(20,30)</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anyrange</type> <literal>&gt;&gt;</literal> <type>anymultirange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the range strictly right of the multirange?
+       </para>
+       <para>
+        <literal>int8range(50,60) &gt;&gt; '{[20,30)}'::int8multirange</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>&amp;&lt;</literal> <type>anymultirange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Does the first multirange not extend to the right of the second?
+       </para>
+       <para>
+        <literal>'{[1,20)}'::int8multirange &amp;&lt; '{[18,20)}'::int8multirange</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>&amp;&lt;</literal> <type>anyrange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Does the multirange not extend to the right of the range?
+       </para>
+       <para>
+        <literal>'{[1,20)}'::int8multirange &amp;&lt; int8range(18,20)</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anyrange</type> <literal>&amp;&lt;</literal> <type>anymultirange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Does the range not extend to the right of the multirange?
+       </para>
+       <para>
+        <literal>int8range(1,20) &amp;&lt; '{[18,20)}'::int8multirange</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>&amp;&gt;</literal> <type>anymultirange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Does the first multirange not extend to the left of the second?
+       </para>
+       <para>
+        <literal>'{[7,20)}'::int8multirange &amp;&gt; '{[5,10)}'::int8multirange</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>&amp;&gt;</literal> <type>anyrange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Does the multirange not extend to the left of the range?
+       </para>
+       <para>
+        <literal>'{[7,20)}'::int8multirange &amp;&gt; int8range(5,10)</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anyrange</type> <literal>&amp;&gt;</literal> <type>anymultirange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Does the range not extend to the left of the multirange?
+       </para>
+       <para>
+        <literal>int8range(7,20) &amp;&gt; '{[5,10)}'::int8multirange</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>-|-</literal> <type>anymultirange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Are the multiranges adjacent?
+       </para>
+       <para>
+        <literal>'{[1.1,2.2)}'::nummultirange -|- '{[2.2,3.3)}'::nummultirange</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>-|-</literal> <type>anyrange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the multirange adjacent to the range?
+       </para>
+       <para>
+        <literal>'{[1.1,2.2)}'::nummultirange -|- numrange(2.2,3.3)</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anyrange</type> <literal>-|-</literal> <type>anymultirange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the range adjacent to the multirange?
+       </para>
+       <para>
+        <literal>numrange(1.1,2.2) -|- '{[2.2,3.3)}'::nummultirange</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>+</literal> <type>anymultirange</type>
+        <returnvalue>anymultirange</returnvalue>
+       </para>
+       <para>
+        Computes the union of the multiranges.  The multiranges need not overlap
+        or be adjacent.
+       </para>
+       <para>
+        <literal>'{[5,10)}'::nummultirange + '{[15,20)}'::nummultirange</literal>
+        <returnvalue>{[5,10), [15,20)}</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>*</literal> <type>anymultirange</type>
+        <returnvalue>anymultirange</returnvalue>
+       </para>
+       <para>
+        Computes the intersection of the multiranges.
+       </para>
+       <para>
+        <literal>'{[5,15)}'::int8multirange * '{[10,20)}'::int8multirange</literal>
+        <returnvalue>{[10,15)}</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>-</literal> <type>anymultirange</type>
+        <returnvalue>anymultirange</returnvalue>
+       </para>
+       <para>
+        Computes the difference of the multiranges.
+       </para>
+       <para>
+        <literal>'{[5,20)}'::int8multirange - '{[10,15)}'::int8multirange</literal>
+        <returnvalue>{[5,10), [15,20)}</returnvalue>
+       </para></entry>
+      </row>
+     </tbody>
+    </tgroup>
+   </table>
+
   <para>
    The left-of/right-of/adjacent operators always return false when an empty
-   range is involved; that is, an empty range is not considered to be either
-   before or after any other range.
+   range or multirange is involved; that is, an empty range is not considered to
+   be either before or after any other range.
+  </para>
+
+  <para>
+   Elsewhere empty ranges and multiranges are treated as the additive identity:
+   anything unioned with an empty value is itself. Anything minus an empty
+   value is itself. An empty multirange has exactly the same points as an empty
+   range. Every range contains the empty range. Every multirange contains as many
+   empty ranges as you like.
+  </para>
+
+  <para>
+   The range union and difference operators will fail if the resulting range would
+   need to contain two disjoint sub-ranges, as such a range cannot be
+   represented. There are separate operators for union and difference that take
+   multirange parameters and return a multirange, and they do not fail even if
+   their arguments are disjoint. So if you need a union or difference operation
+   for ranges that may be disjoint, you can avoid errors by first casting your
+   ranges to multiranges.
   </para>
 
   <para>
    <xref linkend="range-functions-table"/> shows the functions
    available for use with range types.
+   <xref linkend="multirange-functions-table"/> shows the functions
+   available for use with multirange types.
   </para>
 
    <table id="range-functions-table">
@@ -18269,10 +18706,185 @@ SELECT NULLIF(value, '(none)') ...
     </tgroup>
    </table>
 
+   <table id="multirange-functions-table">
+    <title>Multirange Functions</title>
+    <tgroup cols="1">
+     <thead>
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        Function
+       </para>
+       <para>
+        Description
+       </para>
+       <para>
+        Example(s)
+       </para></entry>
+      </row>
+     </thead>
+     <tbody>
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>lower</primary>
+        </indexterm>
+        <function>lower</function> ( <type>anymultirange</type> )
+        <returnvalue>anyelement</returnvalue>
+       </para>
+       <para>
+        Extracts the lower bound of the multirange (<literal>NULL</literal> if the
+        multirange is empty or the lower bound is infinite).
+       </para>
+       <para>
+        <literal>lower('{[1.1,2.2)}'::nummultirange)</literal>
+        <returnvalue>1.1</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>upper</primary>
+        </indexterm>
+        <function>upper</function> ( <type>anymultirange</type> )
+        <returnvalue>anyelement</returnvalue>
+       </para>
+       <para>
+        Extracts the upper bound of the multirange (<literal>NULL</literal> if the
+        multirange is empty or the upper bound is infinite).
+       </para>
+       <para>
+        <literal>upper('{[1.1,2.2)}'::nummultirange)</literal>
+        <returnvalue>2.2</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>isempty</primary>
+        </indexterm>
+        <function>isempty</function> ( <type>anymultirange</type> )
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the multirange empty?
+       </para>
+       <para>
+        <literal>isempty('{[1.1,2.2)}'::nummultirange)</literal>
+        <returnvalue>f</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>lower_inc</primary>
+        </indexterm>
+        <function>lower_inc</function> ( <type>anymultirange</type> )
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the multirange's lower bound inclusive?
+       </para>
+       <para>
+        <literal>lower_inc('{[1.1,2.2)}'::nummultirange)</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>upper_inc</primary>
+        </indexterm>
+        <function>upper_inc</function> ( <type>anymultirange</type> )
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the multirange's upper bound inclusive?
+       </para>
+       <para>
+        <literal>upper_inc('{[1.1,2.2)}'::nummultirange)</literal>
+        <returnvalue>f</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>lower_inf</primary>
+        </indexterm>
+        <function>lower_inf</function> ( <type>anymultirange</type> )
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the multirange's lower bound infinite?
+       </para>
+       <para>
+        <literal>lower_inf('{(,)}'::datemultirange)</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>upper_inf</primary>
+        </indexterm>
+        <function>upper_inf</function> ( <type>anymultirange</type> )
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the multirange's upper bound infinite?
+       </para>
+       <para>
+        <literal>upper_inf('{(,)}'::datemultirange)</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>range_merge</primary>
+        </indexterm>
+        <function>range_merge</function> ( <type>anymultirange</type> )
+        <returnvalue>anyrange</returnvalue>
+       </para>
+       <para>
+        Computes the smallest range that includes the entire multirange.
+       </para>
+       <para>
+        <literal>range_merge('{[1,2), [3,4)}'::int4multirange)</literal>
+        <returnvalue>[1,4)</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>multirange</primary>
+        </indexterm>
+        <function>multirange</function> ( <type>anyrange</type> )
+        <returnvalue>anymultirange</returnvalue>
+       </para>
+       <para>
+        Returns a multirange containing just the given range.
+       </para>
+       <para>
+        <literal>multirange('[1,2)'::int4range)</literal>
+        <returnvalue>{[1,2)}</returnvalue>
+       </para></entry>
+      </row>
+     </tbody>
+    </tgroup>
+   </table>
+
   <para>
    The <function>lower_inc</function>, <function>upper_inc</function>,
    <function>lower_inf</function>, and <function>upper_inf</function>
-   functions all return false for an empty range.
+   functions all return false for an empty range or multirange.
   </para>
   </sect1>
 
@@ -18604,6 +19216,36 @@ SELECT NULLIF(value, '(none)') ...
        <entry>Yes</entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>range_agg</primary>
+        </indexterm>
+        <function>range_agg</function> ( <parameter>value</parameter>
+         <type>anyrange</type> )
+        <returnvalue>anymultirange</returnvalue>
+       </para>
+       <para>
+        Computes the union of the non-null input values.
+       </para></entry>
+       <entry>No</entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>range_intersect_agg</primary>
+        </indexterm>
+        <function>range_intersect_agg</function> ( <parameter>value</parameter>
+         <type>anyrange</type> )
+        <returnvalue>anymultirange</returnvalue>
+       </para>
+       <para>
+        Computes the intersection of the non-null input values.
+       </para></entry>
+       <entry>No</entry>
+      </row>
+
       <row>
        <entry role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/doc/src/sgml/rangetypes.sgml b/doc/src/sgml/rangetypes.sgml
index b75fb3a3929..83aa9bc4e9e 100644
--- a/doc/src/sgml/rangetypes.sgml
+++ b/doc/src/sgml/rangetypes.sgml
@@ -27,40 +27,53 @@
   ranges from an instrument, and so forth can also be useful.
  </para>
 
+ <para>
+  Every range type has a corresponding multirange type. A multirange is
+  an ordered list of non-continguous, non-empty, non-null ranges. Most
+  range operators also work on multiranges, and they have a few functions
+  of their own.
+ </para>
+
  <sect2 id="rangetypes-builtin">
-  <title>Built-in Range Types</title>
+  <title>Built-in Range and Multirange Types</title>
 
  <para>
   PostgreSQL comes with the following built-in range types:
   <itemizedlist>
     <listitem>
       <para>
-       <type>int4range</type> &mdash; Range of <type>integer</type>
+       <type>int4range</type> &mdash; Range of <type>integer</type>,
+       <type>int4multirange</type> &mdash; corresponding Multirange
       </para>
     </listitem>
     <listitem>
       <para>
-       <type>int8range</type> &mdash; Range of <type>bigint</type>
+       <type>int8range</type> &mdash; Range of <type>bigint</type>,
+       <type>int8multirange</type> &mdash; corresponding Multirange
       </para>
     </listitem>
     <listitem>
       <para>
-       <type>numrange</type> &mdash; Range of <type>numeric</type>
+       <type>numrange</type> &mdash; Range of <type>numeric</type>,
+       <type>nummultirange</type> &mdash; corresponding Multirange
       </para>
     </listitem>
     <listitem>
       <para>
-       <type>tsrange</type> &mdash; Range of <type>timestamp without time zone</type>
+       <type>tsrange</type> &mdash; Range of <type>timestamp without time zone</type>,
+       <type>tsmultirange</type> &mdash; corresponding Multirange
       </para>
     </listitem>
     <listitem>
       <para>
-       <type>tstzrange</type> &mdash; Range of <type>timestamp with time zone</type>
+       <type>tstzrange</type> &mdash; Range of <type>timestamp with time zone</type>,
+       <type>tstzmultirange</type> &mdash; corresponding Multirange
       </para>
     </listitem>
     <listitem>
       <para>
-       <type>daterange</type> &mdash; Range of <type>date</type>
+       <type>daterange</type> &mdash; Range of <type>date</type>,
+       <type>datemultirange</type> &mdash; corresponding Multirange
       </para>
     </listitem>
   </itemizedlist>
@@ -232,10 +245,30 @@ SELECT '[4,4]'::int4range;
 SELECT '[4,4)'::int4range;
 </programlisting>
   </para>
+
+  <para>
+   The input for a multirange is curly brackets (<literal>{</literal> and
+   <literal>}</literal>) containing zero or more valid ranges,
+   separated by commas. Whitespace is permitted around the brackets and
+   commas. This is intended to be reminiscent of array syntax, although
+   multiranges are much simpler: they have just one dimension and there is
+   no need to quote their contents. (The bounds of their ranges may be
+   quoted as above however.)
+  </para>
+
+  <para>
+  Examples:
+<programlisting>
+SELECT '{}'::int4multirange;
+SELECT '{[3,7)}'::int4multirange;
+SELECT '{[3,7), [8,9)}'::int4multirange;
+</programlisting>
+  </para>
+
  </sect2>
 
  <sect2 id="rangetypes-construct">
-  <title>Constructing Ranges</title>
+  <title>Constructing Ranges and Multiranges</title>
 
   <para>
    Each range type has a constructor function with the same name as the range
@@ -267,6 +300,19 @@ SELECT int8range(1, 14, '(]');
 
 -- Using NULL for either bound causes the range to be unbounded on that side.
 SELECT numrange(NULL, 2.2);
+</programlisting>
+  </para>
+
+  <para>
+   Each range type also has a multirange constructor with the same name as the
+   multirange type.  The constructor function takes zero or more arguments
+   which are all ranges of the appropriate type.
+   For example:
+
+<programlisting>
+SELECT nummultirange();
+SELECT nummultirange(numrange(1.0, 14.0));
+SELECT nummultirange(numrange(1.0, 14.0), numrange(20.0, 25.0));
 </programlisting>
   </para>
  </sect2>
@@ -341,6 +387,11 @@ SELECT '[1.234, 5.678]'::floatrange;
    function in this example.
   </para>
 
+  <para>
+   When you define your own range you automatically get a corresponding
+   multirange type.
+  </para>
+
   <para>
    Defining your own range type also allows you to specify a different
    subtype B-tree operator class or collation to use, so as to change the sort
diff --git a/doc/src/sgml/ref/create_type.sgml b/doc/src/sgml/ref/create_type.sgml
index d575f166142..e541371b39b 100644
--- a/doc/src/sgml/ref/create_type.sgml
+++ b/doc/src/sgml/ref/create_type.sgml
@@ -33,6 +33,7 @@ CREATE TYPE <replaceable class="parameter">name</replaceable> AS RANGE (
     [ , COLLATION = <replaceable class="parameter">collation</replaceable> ]
     [ , CANONICAL = <replaceable class="parameter">canonical_function</replaceable> ]
     [ , SUBTYPE_DIFF = <replaceable class="parameter">subtype_diff_function</replaceable> ]
+    [ , MULTIRANGE_TYPE_NAME = <replaceable class="parameter">multirange_type_name</replaceable> ]
 )
 
 CREATE TYPE <replaceable class="parameter">name</replaceable> (
@@ -174,6 +175,19 @@ CREATE TYPE <replaceable class="parameter">name</replaceable>
     the range type.  See <xref linkend="rangetypes-defining"/> for more
     information.
    </para>
+
+   <para>
+    The optional <replaceable class="parameter">multirange_type_name</replaceable>
+    parameter specifies the name of the corresponding multirange type.  If not
+    specified, this name is chosen automatically as follows.
+    If range type name contains <literal>range</literal> substring, then
+    multirange type name is formed by replacement of the <literal>range</literal>
+    substring with <literal>multirange</literal> substring in the range
+    type name.  Otherwise, multirange type name is formed by appending
+    <literal>_multirange</literal> suffix to the range type name.
+    If automatically generated multirange type name already exists, it's
+    prepended with underscore sign until the name is available.
+   </para>
   </refsect2>
 
   <refsect2>
@@ -630,6 +644,15 @@ CREATE TYPE <replaceable class="parameter">name</replaceable>
     </listitem>
    </varlistentry>
 
+   <varlistentry>
+    <term><replaceable class="parameter">multirange_type_name</replaceable></term>
+    <listitem>
+     <para>
+      The name of the corresponding multirange type.
+     </para>
+    </listitem>
+   </varlistentry>
+
    <varlistentry>
     <term><replaceable class="parameter">input_function</replaceable></term>
     <listitem>
diff --git a/src/backend/catalog/pg_range.c b/src/backend/catalog/pg_range.c
index a606d8c3adb..91b0fb0611a 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;
 	ObjectAddresses *addrs;
 
 	pg_range = table_open(RangeRelationId, RowExclusiveLock);
@@ -55,6 +56,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_rngmultitypid - 1] = ObjectIdGetDatum(multirangeTypeOid);
 
 	tup = heap_form_tuple(RelationGetDescr(pg_range), values, nulls);
 
@@ -93,6 +95,12 @@ RangeCreate(Oid rangeTypeOid, Oid rangeSubType, Oid rangeCollation,
 	record_object_address_dependencies(&myself, addrs, DEPENDENCY_NORMAL);
 	free_object_addresses(addrs);
 
+	/* 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 4252875ef50..23ef9cb1047 100644
--- a/src/backend/catalog/pg_type.c
+++ b/src/backend/catalog/pg_type.c
@@ -28,6 +28,7 @@
 #include "catalog/pg_proc.h"
 #include "catalog/pg_type.h"
 #include "commands/typecmds.h"
+#include "mb/pg_wchar.h"
 #include "miscadmin.h"
 #include "parser/scansup.h"
 #include "utils/acl.h"
@@ -37,6 +38,9 @@
 #include "utils/rel.h"
 #include "utils/syscache.h"
 
+static char *makeUniqueTypeName(const char *typeName, Oid typeNamespace,
+								bool tryOriginal);
+
 /* Potentially set by pg_upgrade_support functions */
 Oid			binary_upgrade_next_pg_type_oid = InvalidOid;
 
@@ -863,31 +867,10 @@ RenameTypeInternal(Oid typeOid, const char *newTypeName, Oid typeNamespace)
 char *
 makeArrayTypeName(const char *typeName, Oid typeNamespace)
 {
-	char	   *arr = (char *) palloc(NAMEDATALEN);
-	int			namelen = strlen(typeName);
-	int			i;
-
-	/*
-	 * 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;
-	}
+	char	   *arr;
 
-	if (i >= NAMEDATALEN - 1)
+	arr = makeUniqueTypeName(typeName, typeNamespace, false);
+	if (arr == NULL)
 		ereport(ERROR,
 				(errcode(ERRCODE_DUPLICATE_OBJECT),
 				 errmsg("could not form array type name for type \"%s\"",
@@ -958,3 +941,91 @@ moveArrayTypeName(Oid typeOid, const char *typeName, Oid typeNamespace)
 
 	return true;
 }
+
+
+/*
+ * makeMultirangeTypeName
+ *	  - given a range type name, make a multirange type name for it
+ *
+ * caller is responsible for pfreeing the result
+ */
+char *
+makeMultirangeTypeName(const char *rangeTypeName, Oid typeNamespace)
+{
+	char	   *buf;
+	char	   *mrname;
+	char	   *rangestr;
+
+	/*
+	 * 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...
+	 */
+	rangestr = strstr(rangeTypeName, "range");
+	if (rangestr)
+	{
+		char	   *prefix = pnstrdup(rangeTypeName, rangestr - rangeTypeName);
+
+		buf = psprintf("%s%s%s", prefix, "multi", rangestr);
+	}
+	else
+		buf = psprintf("%s_multirange", pnstrdup(rangeTypeName, NAMEDATALEN - 12));
+
+	/* clip it at NAMEDATALEN-1 bytes */
+	buf[pg_mbcliplen(buf, strlen(buf), NAMEDATALEN - 1)] = '\0';
+
+	mrname = makeUniqueTypeName(buf, typeNamespace, true);
+	if (mrname == NULL)
+		ereport(ERROR,
+				(errcode(ERRCODE_DUPLICATE_OBJECT),
+				 errmsg("could not form multirange type name for type \"%s\"",
+						rangeTypeName)));
+
+	return mrname;
+}
+
+/*
+ * makeUniqueTypeName
+ *		Generate a unique name for a prospective new type
+ *
+ * Given a typeName, return a new palloc'ed name by preprending underscores
+ * until a non-conflicting name results.
+ *
+ * If tryOriginal, first try with zero underscores.
+ */
+static char *
+makeUniqueTypeName(const char *typeName, Oid typeNamespace, bool tryOriginal)
+{
+	int			i;
+	int			namelen;
+	char		dest[NAMEDATALEN];
+
+	Assert(strlen(typeName) <= NAMEDATALEN - 1);
+
+	if (tryOriginal &&
+		!SearchSysCacheExists2(TYPENAMENSP,
+							   CStringGetDatum(typeName),
+							   ObjectIdGetDatum(typeNamespace)))
+		return pstrdup(typeName);
+
+	/*
+	 * The idea is to prepend underscores as needed until we make a name that
+	 * doesn't collide with anything ...
+	 */
+	namelen = strlen(typeName);
+	for (i = 1; i < NAMEDATALEN - 1; i++)
+	{
+		dest[i - 1] = '_';
+		strlcpy(dest + i, typeName, NAMEDATALEN - i);
+		if (namelen + i >= NAMEDATALEN)
+			truncate_identifier(dest, NAMEDATALEN, false);
+
+		if (!SearchSysCacheExists2(TYPENAMENSP,
+								   CStringGetDatum(dest),
+								   ObjectIdGetDatum(typeNamespace)))
+			return pstrdup(dest);
+	}
+
+	return NULL;
+}
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index 7c0b2c3bf02..0fcd8c8f16e 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -107,9 +107,14 @@ typedef struct
 
 /* Potentially set by pg_upgrade_support functions */
 Oid			binary_upgrade_next_array_pg_type_oid = InvalidOid;
+Oid			binary_upgrade_next_mrng_pg_type_oid = InvalidOid;
+Oid			binary_upgrade_next_mrng_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 rangeOid,
+									   Oid rangeArrayOid, Oid *castFuncOid);
 static Oid	findTypeInputFunction(List *procname, Oid typeOid);
 static Oid	findTypeOutputFunction(List *procname, Oid typeOid);
 static Oid	findTypeReceiveFunction(List *procname, Oid typeOid);
@@ -772,7 +777,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",
@@ -1323,6 +1329,11 @@ checkEnumOwner(HeapTuple tup)
 /*
  * DefineRange
  *		Registers a new range type.
+ *
+ * Perhaps it might be worthwhile to set pg_type.typelem to the base type,
+ * and likewise on multiranges to set it to the range type. But having a
+ * non-zero typelem is treated elsewhere as a synonym for being an array,
+ * and users might have queries with that same assumption.
  */
 ObjectAddress
 DefineRange(CreateRangeStmt *stmt)
@@ -1331,7 +1342,12 @@ DefineRange(CreateRangeStmt *stmt)
 	Oid			typeNamespace;
 	Oid			typoid;
 	char	   *rangeArrayName;
+	char	   *multirangeTypeName = NULL;
+	char	   *multirangeArrayName;
+	Oid			multirangeNamespace = InvalidOid;
 	Oid			rangeArrayOid;
+	Oid			multirangeOid;
+	Oid			multirangeArrayOid;
 	Oid			rangeSubtype = InvalidOid;
 	List	   *rangeSubOpclassName = NIL;
 	List	   *rangeCollationName = NIL;
@@ -1348,6 +1364,8 @@ DefineRange(CreateRangeStmt *stmt)
 	AclResult	aclresult;
 	ListCell   *lc;
 	ObjectAddress address;
+	ObjectAddress mltrngaddress;
+	Oid			castFuncOid;
 
 	/* Convert list of names to a name and namespace */
 	typeNamespace = QualifiedNameGetCreationNamespace(stmt->typeName,
@@ -1431,6 +1449,16 @@ DefineRange(CreateRangeStmt *stmt)
 						 errmsg("conflicting or redundant options")));
 			rangeSubtypeDiffName = defGetQualifiedName(defel);
 		}
+		else if (strcmp(defel->defname, "multirange_type_name") == 0)
+		{
+			if (multirangeTypeName != NULL)
+				ereport(ERROR,
+						(errcode(ERRCODE_SYNTAX_ERROR),
+						 errmsg("conflicting or redundant options")));
+			/* we can look up the subtype name immediately */
+			multirangeNamespace = QualifiedNameGetCreationNamespace(defGetQualifiedName(defel),
+																	&multirangeTypeName);
+		}
 		else
 			ereport(ERROR,
 					(errcode(ERRCODE_SYNTAX_ERROR),
@@ -1496,8 +1524,10 @@ DefineRange(CreateRangeStmt *stmt)
 	/* alignment must be TYPALIGN_INT or TYPALIGN_DOUBLE for ranges */
 	alignment = (subtypalign == TYPALIGN_DOUBLE) ? TYPALIGN_DOUBLE : TYPALIGN_INT;
 
-	/* Allocate OID for array type */
+	/* Allocate OID for array type, its multirange, and its multirange array */
 	rangeArrayOid = AssignTypeArrayOid();
+	multirangeOid = AssignTypeMultirangeOid();
+	multirangeArrayOid = AssignTypeMultirangeArrayOid();
 
 	/* Create the pg_type entry */
 	address =
@@ -1536,9 +1566,75 @@ DefineRange(CreateRangeStmt *stmt)
 	Assert(typoid == InvalidOid || typoid == address.objectId);
 	typoid = address.objectId;
 
+	/* Create the multirange that goes with it */
+	if (multirangeTypeName)
+	{
+		Oid		old_typoid;
+
+		/*
+		 * Look to see if multirange type already exists.
+		 */
+		old_typoid = GetSysCacheOid2(TYPENAMENSP, Anum_pg_type_oid,
+									 CStringGetDatum(multirangeTypeName),
+									 ObjectIdGetDatum(multirangeNamespace));
+
+		/*
+		 * If it's not a shell, see if it's an autogenerated array type, and if so
+		 * rename it out of the way.
+		 */
+		if (OidIsValid(old_typoid) && get_typisdefined(old_typoid))
+		{
+			if (!moveArrayTypeName(old_typoid, multirangeTypeName, multirangeNamespace))
+				ereport(ERROR,
+						(errcode(ERRCODE_DUPLICATE_OBJECT),
+						 errmsg("type \"%s\" already exists", multirangeTypeName)));
+		}
+	}
+	else
+	{
+		/* Generate multirange name automatically */
+		multirangeNamespace = typeNamespace;
+		multirangeTypeName = makeMultirangeTypeName(typeName, multirangeNamespace);
+	}
+
+	mltrngaddress =
+		TypeCreate(multirangeOid,	/* force assignment of this type OID */
+				   multirangeTypeName,	/* type name */
+				   multirangeNamespace,	/* 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_RANGE,	/* type-category (range 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,	/* subscript procedure - none */
+				   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) */
+	Assert(multirangeOid == mltrngaddress.objectId);
+
 	/* Create the entry in pg_range */
 	RangeCreate(typoid, rangeSubtype, rangeCollation, rangeSubOpclass,
-				rangeCanonical, rangeSubtypeDiff);
+				rangeCanonical, rangeSubtypeDiff, multirangeOid);
 
 	/*
 	 * Create the array type that goes with it.
@@ -1580,8 +1676,54 @@ 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 */
+			   multirangeNamespace,	/* 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 */
+			   F_ARRAY_SUBSCRIPT_HANDLER,	/* array subscript procedure */
+			   multirangeOid,	/* 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,
+							   multirangeOid, typoid, rangeArrayOid,
+							   &castFuncOid);
+
+	/* Create cast from the range type to its multirange type */
+	CastCreate(typoid, multirangeOid, castFuncOid, 'e', 'f', DEPENDENCY_INTERNAL);
+
+	pfree(multirangeTypeName);
+	pfree(multirangeArrayName);
 
 	return address;
 }
@@ -1659,6 +1801,149 @@ 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.
+ *
+ * Sets castFuncOid to the oid of the new constructor that can be used
+ * to cast from a range to a multirange.
+ */
+static void
+makeMultirangeConstructors(const char *name, Oid namespace,
+						   Oid multirangeOid, Oid rangeOid, Oid rangeArrayOid,
+						   Oid *castFuncOid)
+{
+	ObjectAddress myself,
+				referenced;
+	oidvector  *argtypes;
+	Datum		allParamTypes;
+	ArrayType  *allParameterTypes;
+	Datum		paramModes;
+	ArrayType  *parameterModes;
+
+	referenced.classId = TypeRelationId;
+	referenced.objectId = multirangeOid;
+	referenced.objectSubId = 0;
+
+	/* 0-arg constructor - for empty multiranges */
+	argtypes = buildoidvector(NULL, 0);
+	myself = ProcedureCreate(name,	/* name: same as multirange type */
+							 namespace,
+							 false, /* replace */
+							 false, /* returns set */
+							 multirangeOid, /* return type */
+							 BOOTSTRAP_SUPERUSERID, /* proowner */
+							 INTERNALlanguageId,	/* language */
+							 F_FMGR_INTERNAL_VALIDATOR,
+							 "multirange_constructor0", /* prosrc */
+							 NULL,	/* probin */
+							 PROKIND_FUNCTION,
+							 false, /* security_definer */
+							 false, /* leakproof */
+							 false, /* isStrict */
+							 PROVOLATILE_IMMUTABLE, /* volatility */
+							 PROPARALLEL_SAFE,	/* parallel safety */
+							 argtypes,	/* parameterTypes */
+							 PointerGetDatum(NULL), /* allParameterTypes */
+							 PointerGetDatum(NULL), /* parameterModes */
+							 PointerGetDatum(NULL), /* parameterNames */
+							 NIL,	/* parameterDefaults */
+							 PointerGetDatum(NULL), /* trftypes */
+							 PointerGetDatum(NULL), /* proconfig */
+							 InvalidOid,	/* prosupport */
+							 1.0,	/* procost */
+							 0.0);	/* prorows */
+
+	/*
+	 * Make the constructor 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);
+	pfree(argtypes);
+
+	/*
+	 * 1-arg constructor - for casts
+	 *
+	 * In theory we shouldn't need both this and the vararg (n-arg)
+	 * constructor, but having a separate 1-arg function lets us define casts
+	 * against it.
+	 */
+	argtypes = buildoidvector(&rangeOid, 1);
+	myself = ProcedureCreate(name,	/* name: same as multirange type */
+							 namespace,
+							 false, /* replace */
+							 false, /* returns set */
+							 multirangeOid, /* return type */
+							 BOOTSTRAP_SUPERUSERID, /* proowner */
+							 INTERNALlanguageId,	/* language */
+							 F_FMGR_INTERNAL_VALIDATOR,
+							 "multirange_constructor1", /* prosrc */
+							 NULL,	/* probin */
+							 PROKIND_FUNCTION,
+							 false, /* security_definer */
+							 false, /* leakproof */
+							 true,	/* isStrict */
+							 PROVOLATILE_IMMUTABLE, /* volatility */
+							 PROPARALLEL_SAFE,	/* parallel safety */
+							 argtypes,	/* parameterTypes */
+							 PointerGetDatum(NULL), /* allParameterTypes */
+							 PointerGetDatum(NULL), /* parameterModes */
+							 PointerGetDatum(NULL), /* parameterNames */
+							 NIL,	/* parameterDefaults */
+							 PointerGetDatum(NULL), /* trftypes */
+							 PointerGetDatum(NULL), /* proconfig */
+							 InvalidOid,	/* prosupport */
+							 1.0,	/* procost */
+							 0.0);	/* prorows */
+	/* ditto */
+	recordDependencyOn(&myself, &referenced, DEPENDENCY_INTERNAL);
+	pfree(argtypes);
+	*castFuncOid = myself.objectId;
+
+	/* n-arg constructor - vararg */
+	argtypes = buildoidvector(&rangeArrayOid, 1);
+	allParamTypes = ObjectIdGetDatum(rangeArrayOid);
+	allParameterTypes = construct_array(&allParamTypes,
+										1, OIDOID,
+										sizeof(Oid), true, 'i');
+	paramModes = CharGetDatum(FUNC_PARAM_VARIADIC);
+	parameterModes = construct_array(&paramModes, 1, CHAROID,
+									 1, true, 'c');
+	myself = ProcedureCreate(name,	/* name: same as multirange type */
+							 namespace,
+							 false, /* replace */
+							 false, /* returns set */
+							 multirangeOid, /* return type */
+							 BOOTSTRAP_SUPERUSERID, /* proowner */
+							 INTERNALlanguageId,	/* language */
+							 F_FMGR_INTERNAL_VALIDATOR,
+							 "multirange_constructor2", /* prosrc */
+							 NULL,	/* probin */
+							 PROKIND_FUNCTION,
+							 false, /* security_definer */
+							 false, /* leakproof */
+							 false, /* isStrict */
+							 PROVOLATILE_IMMUTABLE, /* volatility */
+							 PROPARALLEL_SAFE,	/* parallel safety */
+							 argtypes,	/* parameterTypes */
+							 PointerGetDatum(allParameterTypes),	/* allParameterTypes */
+							 PointerGetDatum(parameterModes),	/* parameterModes */
+							 PointerGetDatum(NULL), /* parameterNames */
+							 NIL,	/* parameterDefaults */
+							 PointerGetDatum(NULL), /* trftypes */
+							 PointerGetDatum(NULL), /* proconfig */
+							 InvalidOid,	/* prosupport */
+							 1.0,	/* procost */
+							 0.0);	/* prorows */
+	/* ditto */
+	recordDependencyOn(&myself, &referenced, DEPENDENCY_INTERNAL);
+	pfree(argtypes);
+	pfree(allParameterTypes);
+	pfree(parameterModes);
+}
 
 /*
  * Find suitable I/O and other support functions for a type.
@@ -2152,6 +2437,72 @@ AssignTypeArrayOid(void)
 	return type_array_oid;
 }
 
+/*
+ *	AssignTypeMultirangeOid
+ *
+ *	Pre-assign the range type's multirange OID for use in pg_type.oid
+ */
+Oid
+AssignTypeMultirangeOid(void)
+{
+	Oid			type_multirange_oid;
+
+	/* Use binary-upgrade override for pg_type.oid? */
+	if (IsBinaryUpgrade)
+	{
+		if (!OidIsValid(binary_upgrade_next_mrng_pg_type_oid))
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+					 errmsg("pg_type multirange OID value not set when in binary upgrade mode")));
+
+		type_multirange_oid = binary_upgrade_next_mrng_pg_type_oid;
+		binary_upgrade_next_mrng_pg_type_oid = InvalidOid;
+	}
+	else
+	{
+		Relation	pg_type = table_open(TypeRelationId, AccessShareLock);
+
+		type_multirange_oid = GetNewOidWithIndex(pg_type, TypeOidIndexId,
+												 Anum_pg_type_oid);
+		table_close(pg_type, AccessShareLock);
+	}
+
+	return type_multirange_oid;
+}
+
+/*
+ *	AssignTypeMultirangeArrayOid
+ *
+ *	Pre-assign the range type's multirange array OID for use in pg_type.typarray
+ */
+Oid
+AssignTypeMultirangeArrayOid(void)
+{
+	Oid			type_multirange_array_oid;
+
+	/* Use binary-upgrade override for pg_type.oid? */
+	if (IsBinaryUpgrade)
+	{
+		if (!OidIsValid(binary_upgrade_next_mrng_array_pg_type_oid))
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+					 errmsg("pg_type multirange array OID value not set when in binary upgrade mode")));
+
+		type_multirange_array_oid = binary_upgrade_next_mrng_array_pg_type_oid;
+		binary_upgrade_next_mrng_array_pg_type_oid = InvalidOid;
+	}
+	else
+	{
+		Relation	pg_type = table_open(TypeRelationId, AccessShareLock);
+
+		type_multirange_array_oid = GetNewOidWithIndex(pg_type, TypeOidIndexId,
+													   Anum_pg_type_oid);
+		table_close(pg_type, AccessShareLock);
+	}
+
+	return type_multirange_array_oid;
+}
+
 
 /*-------------------------------------------------------------------
  * DefineCompositeType
diff --git a/src/backend/executor/functions.c b/src/backend/executor/functions.c
index 459a33375b1..ca8d637e73a 100644
--- a/src/backend/executor/functions.c
+++ b/src/backend/executor/functions.c
@@ -1711,7 +1711,8 @@ check_sql_fn_retval(List *queryTreeLists,
 	if (fn_typtype == TYPTYPE_BASE ||
 		fn_typtype == TYPTYPE_DOMAIN ||
 		fn_typtype == TYPTYPE_ENUM ||
-		fn_typtype == TYPTYPE_RANGE)
+		fn_typtype == TYPTYPE_RANGE ||
+		fn_typtype == TYPTYPE_MULTIRANGE)
 	{
 		/*
 		 * For scalar-type returns, the target list must have exactly one
diff --git a/src/backend/parser/parse_coerce.c b/src/backend/parser/parse_coerce.c
index da6c3ae4b5f..e33618f9744 100644
--- a/src/backend/parser/parse_coerce.c
+++ b/src/backend/parser/parse_coerce.c
@@ -190,19 +190,21 @@ coerce_type(ParseState *pstate, Node *node,
 	if (targetTypeId == ANYARRAYOID ||
 		targetTypeId == ANYENUMOID ||
 		targetTypeId == ANYRANGEOID ||
+		targetTypeId == ANYMULTIRANGEOID ||
 		targetTypeId == ANYCOMPATIBLEARRAYOID ||
-		targetTypeId == ANYCOMPATIBLERANGEOID)
+		targetTypeId == ANYCOMPATIBLERANGEOID ||
+		targetTypeId == ANYCOMPATIBLEMULTIRANGEOID)
 	{
 		/*
 		 * 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 the pseudotype's input
-		 * function, 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 the
+		 * pseudotype's input function, 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
@@ -1570,8 +1572,8 @@ select_common_typmod(ParseState *pstate, List *exprs, Oid common_type)
  * 1) All arguments declared ANYELEMENT must have the same datatype.
  * 2) All arguments declared ANYARRAY must have the same datatype,
  *	  which must be a varlena array type.
- * 3) All arguments declared ANYRANGE must have the same datatype,
- *	  which must be a range type.
+ * 3) All arguments declared ANYRANGE or ANYMULTIRANGE must be a range or
+ *	  multirange type, all derived from the same base datatype.
  * 4) If there are arguments of more than one of these polymorphic types,
  *	  the array element type and/or range subtype must be the same as each
  *	  other and the same as the ANYELEMENT type.
@@ -1586,8 +1588,8 @@ select_common_typmod(ParseState *pstate, List *exprs, Oid common_type)
  *	  to a common supertype (chosen as per select_common_type's rules).
  *	  ANYCOMPATIBLENONARRAY works like ANYCOMPATIBLE but also requires the
  *	  common supertype to not be an array.  If there are ANYCOMPATIBLEARRAY
- *	  or ANYCOMPATIBLERANGE arguments, their element types or subtypes are
- *	  included while making the choice of common supertype.
+ *	  or ANYCOMPATIBLERANGE or ANYCOMPATIBLEMULTIRANGE arguments, their element
+ *	  types or subtypes are included while making the choice of common supertype.
  * 8) The resolved type of ANYCOMPATIBLEARRAY arguments will be the array
  *	  type over the common supertype (which might not be the same array type
  *	  as any of the original arrays).
@@ -1595,6 +1597,10 @@ select_common_typmod(ParseState *pstate, List *exprs, Oid common_type)
  *	  (after domain flattening), since we have no preference rule that would
  *	  let us choose one over another.  Furthermore, that range's subtype
  *	  must exactly match the common supertype chosen by rule 7.
+ * 10) All ANYCOMPATIBLEMULTIRANGE arguments must be the exact same multirange
+ *	  type (after domain flattening), since we have no preference rule that would
+ *	  let us choose one over another.  Furthermore, that multirange's range's
+ *	  subtype must exactly match the common supertype chosen by rule 7.
  *
  * Domains over arrays match ANYARRAY, and are immediately flattened to their
  * base type.  (Thus, for example, we will consider it a match if one ANYARRAY
@@ -1603,7 +1609,9 @@ select_common_typmod(ParseState *pstate, List *exprs, Oid common_type)
  * for ANYCOMPATIBLEARRAY and ANYCOMPATIBLENONARRAY.
  *
  * Similarly, domains over ranges match ANYRANGE or ANYCOMPATIBLERANGE,
- * and are immediately flattened to their base type.
+ * and are immediately flattened to their base type, and domains over
+ * multiranges match ANYMULTIRANGE or ANYCOMPATIBLEMULTIRANGE and are immediately
+ * flattened to their base type.
  *
  * Note that domains aren't currently considered to match ANYENUM,
  * even if their base type would match.
@@ -1621,8 +1629,12 @@ check_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			anycompatible_range_typeid = InvalidOid;
 	Oid			anycompatible_range_typelem = InvalidOid;
+	Oid			anycompatible_multirange_typeid = InvalidOid;
+	Oid			anycompatible_multirange_typelem = InvalidOid;
+	Oid			range_typelem = InvalidOid;
 	bool		have_anynonarray = false;
 	bool		have_anyenum = false;
 	bool		have_anycompatible_nonarray = false;
@@ -1671,6 +1683,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;
+		}
 		else if (decl_type == ANYCOMPATIBLEOID ||
 				 decl_type == ANYCOMPATIBLENONARRAYOID)
 		{
@@ -1715,6 +1736,45 @@ check_generic_type_consistency(const Oid *actual_arg_types,
 				anycompatible_actual_types[n_anycompatible_args++] = anycompatible_range_typelem;
 			}
 		}
+		else if (decl_type == ANYCOMPATIBLEMULTIRANGEOID)
+		{
+			if (actual_type == UNKNOWNOID)
+				continue;
+			actual_type = getBaseType(actual_type); /* flatten domains */
+			if (OidIsValid(anycompatible_multirange_typeid))
+			{
+				/* All ANYCOMPATIBLEMULTIRANGE arguments must be the same type */
+				if (anycompatible_multirange_typeid != actual_type)
+					return false;
+			}
+			else
+			{
+				anycompatible_multirange_typeid = actual_type;
+				anycompatible_multirange_typelem = get_multirange_range(actual_type);
+				if (!OidIsValid(anycompatible_multirange_typelem))
+					return false;	/* not a multirange type */
+
+				if (OidIsValid(anycompatible_range_typeid))
+				{
+					/*
+					 * ANYCOMPATIBLEMULTIRANGE and ANYCOMPATIBLERANGE
+					 * arguments must match
+					 */
+					if (anycompatible_range_typeid != anycompatible_multirange_typelem)
+						return false;
+				}
+				else
+				{
+					anycompatible_range_typeid = anycompatible_multirange_typelem;
+					anycompatible_range_typelem = get_range_subtype(anycompatible_range_typeid);
+					if (!OidIsValid(anycompatible_range_typelem))
+						return false;	/* not a range type */
+				}
+				/* collect the subtype for common-supertype choice */
+				anycompatible_actual_types[n_anycompatible_args++] =
+					anycompatible_range_typelem;
+			}
+		}
 	}
 
 	/* Get the element type based on the array type, if we have one */
@@ -1761,8 +1821,6 @@ check_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))
 	{
-		Oid			range_typelem;
-
 		range_typelem = get_range_subtype(range_typeid);
 		if (!OidIsValid(range_typelem))
 			return false;		/* should be a range, but isn't */
@@ -1781,6 +1839,45 @@ 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))
+	{
+		Oid			multirange_typelem;
+
+		multirange_typelem = get_multirange_range(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 */
+		}
+		else if (multirange_typelem != range_typeid)
+		{
+			/* otherwise, they better match */
+			return false;
+		}
+
+		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 */
@@ -1819,8 +1916,10 @@ check_generic_type_consistency(const Oid *actual_arg_types,
 		}
 
 		/*
-		 * the anycompatible type must exactly match the range element type,
-		 * if we were able to identify one
+		 * The anycompatible type must exactly match the range element type,
+		 * if we were able to identify one. This checks compatibility for
+		 * anycompatiblemultirange too since that also sets
+		 * anycompatible_range_typelem above.
 		 */
 		if (OidIsValid(anycompatible_range_typelem) &&
 			anycompatible_range_typelem != anycompatible_typeid)
@@ -1859,21 +1958,27 @@ check_generic_type_consistency(const Oid *actual_arg_types,
  *	  argument's actual type as the function's return type.
  * 2) If return type is ANYARRAY, and any argument is ANYARRAY, use the
  *	  argument's actual type as the function's return type.
- * 3) Similarly, if return type is ANYRANGE, and any argument is ANYRANGE,
- *	  use the argument's actual type as the function's return type.
- * 4) Otherwise, if return type is ANYELEMENT or ANYARRAY, and there is
+ * 3) Similarly, if return type is ANYRANGE or ANYMULTIRANGE, and any
+ *	  argument is ANYRANGE or ANYMULTIRANGE, use that argument's
+ *	  actual type, range type or multirange type as the function's return
+ *	  type.
+ * 4) Otherwise, if return type is ANYMULTIRANGE, and any argument is
+ *	  ANYMULTIRANGE, use the argument's actual type as the function's return
+ *	  type. Or if any argument is ANYRANGE, use its multirange type as the
+ *	  function's return type.
+ * 5) Otherwise, if return type is ANYELEMENT or ANYARRAY, and there is
  *	  at least one ANYELEMENT, ANYARRAY, or ANYRANGE input, deduce the
  *	  return type from those inputs, or throw error if we can't.
- * 5) Otherwise, if return type is ANYRANGE, throw error.  (We have no way to
- *	  select a specific range type if the arguments don't include ANYRANGE.)
- * 6) ANYENUM is treated the same as ANYELEMENT except that if it is used
+ * 6) Otherwise, if return type is ANYRANGE or ANYMULTIRANGE, throw error.
+ *	  (We have no way to select a specific range type if the arguments don't
+ *	  include ANYRANGE.)
  *	  (alone or in combination with plain ANYELEMENT), we add the extra
  *	  condition that the ANYELEMENT type must be an enum.
- * 7) ANYNONARRAY is treated the same as ANYELEMENT except that if it is used,
+ * 8) ANYNONARRAY is treated the same as ANYELEMENT except that if it is used,
  *	  we add the extra condition that the ANYELEMENT type must not be an array.
  *	  (This is a no-op if used in combination with ANYARRAY or ANYENUM, but
  *	  is an extra restriction if not.)
- * 8) ANYCOMPATIBLE, ANYCOMPATIBLEARRAY, ANYCOMPATIBLENONARRAY, and
+ * 9) ANYCOMPATIBLE, ANYCOMPATIBLEARRAY, ANYCOMPATIBLENONARRAY, and
  *	  ANYCOMPATIBLERANGE are handled by resolving the common supertype
  *	  of those arguments (or their element types/subtypes, for array and range
  *	  inputs), and then coercing all those arguments to the common supertype,
@@ -1927,10 +2032,15 @@ 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			anycompatible_typeid = InvalidOid;
 	Oid			anycompatible_array_typeid = InvalidOid;
 	Oid			anycompatible_range_typeid = InvalidOid;
 	Oid			anycompatible_range_typelem = InvalidOid;
+	Oid			anycompatible_multirange_typeid = InvalidOid;
+	Oid			anycompatible_multirange_typelem = InvalidOid;
+	Oid			range_typelem;
+	Oid			multirange_typelem;
 	bool		have_anynonarray = (rettype == ANYNONARRAYOID);
 	bool		have_anyenum = (rettype == ANYENUMOID);
 	bool		have_anycompatible_nonarray = (rettype == ANYCOMPATIBLENONARRAYOID);
@@ -2015,6 +2125,26 @@ enforce_generic_type_consistency(const Oid *actual_arg_types,
 								   format_type_be(actual_type))));
 			range_typeid = actual_type;
 		}
+		else if (decl_type == ANYMULTIRANGEOID)
+		{
+			n_poly_args++;
+			if (actual_type == UNKNOWNOID)
+			{
+				have_poly_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;
+		}
 		else if (decl_type == ANYCOMPATIBLEOID ||
 				 decl_type == ANYCOMPATIBLENONARRAYOID)
 		{
@@ -2083,6 +2213,40 @@ enforce_generic_type_consistency(const Oid *actual_arg_types,
 				anycompatible_actual_types[n_anycompatible_args++] = anycompatible_range_typelem;
 			}
 		}
+		else if (decl_type == ANYCOMPATIBLEMULTIRANGEOID)
+		{
+			have_poly_anycompatible = true;
+			if (actual_type == UNKNOWNOID)
+				continue;
+			if (allow_poly && decl_type == actual_type)
+				continue;		/* no new information here */
+			actual_type = getBaseType(actual_type); /* flatten domains */
+			if (OidIsValid(anycompatible_multirange_typeid))
+			{
+				/* All ANYCOMPATIBLEMULTIRANGE arguments must be the same type */
+				if (anycompatible_multirange_typeid != actual_type)
+					ereport(ERROR,
+							(errcode(ERRCODE_DATATYPE_MISMATCH),
+							 errmsg("arguments declared \"anycompatiblemultirange\" are not all alike"),
+							 errdetail("%s versus %s",
+									   format_type_be(anycompatible_multirange_typeid),
+									   format_type_be(actual_type))));
+			}
+			else
+			{
+				anycompatible_multirange_typeid = actual_type;
+				anycompatible_multirange_typelem = get_multirange_range(actual_type);
+				anycompatible_range_typelem = get_range_subtype(anycompatible_multirange_typelem);
+				if (!OidIsValid(anycompatible_multirange_typelem))
+					ereport(ERROR,
+							(errcode(ERRCODE_DATATYPE_MISMATCH),
+							 errmsg("argument declared %s is not a multirange type but type %s",
+									"anycompatiblemultirange",
+									format_type_be(actual_type))));
+				/* collect the subtype for common-supertype choice */
+				anycompatible_actual_types[n_anycompatible_args++] = anycompatible_range_typelem;
+			}
+		}
 	}
 
 	/*
@@ -2151,8 +2315,6 @@ 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))
 		{
-			Oid			range_typelem;
-
 			range_typelem = get_range_subtype(range_typeid);
 			if (!OidIsValid(range_typelem))
 				ereport(ERROR,
@@ -2181,6 +2343,61 @@ enforce_generic_type_consistency(const Oid *actual_arg_types,
 								   format_type_be(elem_typeid))));
 			}
 		}
+		else
+			range_typelem = InvalidOid;
+
+		/* Get the element type based on the multirange type, if we have one */
+		if (OidIsValid(multirange_typeid))
+		{
+			multirange_typelem = get_multirange_range(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))));
+			}
+
+			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 */
+				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))));
+			}
+		}
+		else
+			multirange_typelem = InvalidOid;
 
 		if (!OidIsValid(elem_typeid))
 		{
@@ -2189,6 +2406,7 @@ enforce_generic_type_consistency(const Oid *actual_arg_types,
 				elem_typeid = ANYELEMENTOID;
 				array_typeid = ANYARRAYOID;
 				range_typeid = ANYRANGEOID;
+				multirange_typeid = ANYMULTIRANGEOID;
 			}
 			else
 			{
@@ -2288,6 +2506,7 @@ enforce_generic_type_consistency(const Oid *actual_arg_types,
 				anycompatible_typeid = ANYCOMPATIBLEOID;
 				anycompatible_array_typeid = ANYCOMPATIBLEARRAYOID;
 				anycompatible_range_typeid = ANYCOMPATIBLERANGEOID;
+				anycompatible_multirange_typeid = ANYCOMPATIBLEMULTIRANGEOID;
 			}
 			else
 			{
@@ -2319,6 +2538,8 @@ enforce_generic_type_consistency(const Oid *actual_arg_types,
 				declared_arg_types[j] = anycompatible_array_typeid;
 			else if (decl_type == ANYCOMPATIBLERANGEOID)
 				declared_arg_types[j] = anycompatible_range_typeid;
+			else if (decl_type == ANYCOMPATIBLEMULTIRANGEOID)
+				declared_arg_types[j] = anycompatible_multirange_typeid;
 		}
 	}
 
@@ -2369,6 +2590,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;
+			}
 		}
 	}
 
@@ -2405,6 +2637,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 ANYCOMPATIBLE use the appropriate type */
 	if (rettype == ANYCOMPATIBLEOID ||
 		rettype == ANYCOMPATIBLENONARRAYOID)
@@ -2439,6 +2687,17 @@ enforce_generic_type_consistency(const Oid *actual_arg_types,
 		return anycompatible_range_typeid;
 	}
 
+	/* if we return ANYCOMPATIBLEMULTIRANGE use the appropriate argument type */
+	if (rettype == ANYCOMPATIBLEMULTIRANGEOID)
+	{
+		/* this error is unreachable if the function signature is valid: */
+		if (!OidIsValid(anycompatible_multirange_typeid))
+			ereport(ERROR,
+					(errcode(ERRCODE_DATATYPE_MISMATCH),
+					 errmsg_internal("could not identify anycompatiblemultirange type")));
+		return anycompatible_multirange_typeid;
+	}
+
 	/* we don't return a generic type; send back the original return type */
 	return rettype;
 }
@@ -2456,20 +2715,38 @@ check_valid_polymorphic_signature(Oid ret_type,
 								  const Oid *declared_arg_types,
 								  int nargs)
 {
-	if (ret_type == ANYRANGEOID || ret_type == ANYCOMPATIBLERANGEOID)
+	if (ret_type == ANYRANGEOID || ret_type == ANYMULTIRANGEOID)
 	{
 		/*
-		 * ANYRANGE requires an ANYRANGE input, else we can't tell which of
-		 * several range types with the same element type to use.  Likewise
-		 * for ANYCOMPATIBLERANGE.
+		 * ANYRANGE and ANYMULTIRANGE require an ANYRANGE or ANYMULTIRANGE
+		 * input, else we can't tell which of several range types with the
+		 * same element type to use.
 		 */
 		for (int i = 0; i < nargs; i++)
 		{
-			if (declared_arg_types[i] == ret_type)
+			if (declared_arg_types[i] == ANYRANGEOID ||
+				declared_arg_types[i] == ANYMULTIRANGEOID)
 				return NULL;	/* OK */
 		}
-		return psprintf(_("A result of type %s requires at least one input of type %s."),
-						format_type_be(ret_type), format_type_be(ret_type));
+		return psprintf(_("A result of type %s requires at least one input of type anyrange or anymultirange."),
+						format_type_be(ret_type));
+	}
+	else if (ret_type == ANYCOMPATIBLERANGEOID || ret_type == ANYCOMPATIBLEMULTIRANGEOID)
+	{
+		/*
+		 * ANYCOMPATIBLERANGE and ANYCOMPATIBLEMULTIRANGE require an
+		 * ANYCOMPATIBLERANGE or ANYCOMPATIBLEMULTIRANGE input, else we can't
+		 * tell which of several range types with the same element type to
+		 * use.
+		 */
+		for (int i = 0; i < nargs; i++)
+		{
+			if (declared_arg_types[i] == ANYCOMPATIBLERANGEOID ||
+				declared_arg_types[i] == ANYCOMPATIBLEMULTIRANGEOID)
+				return NULL;	/* OK */
+		}
+		return psprintf(_("A result of type %s requires at least one input of type anycompatiblerange or anycompatiblemultirange."),
+						format_type_be(ret_type));
 	}
 	else if (IsPolymorphicTypeFamily1(ret_type))
 	{
@@ -2480,7 +2757,7 @@ check_valid_polymorphic_signature(Oid ret_type,
 				return NULL;	/* OK */
 		}
 		/* Keep this list in sync with IsPolymorphicTypeFamily1! */
-		return psprintf(_("A result of type %s requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange."),
+		return psprintf(_("A result of type %s requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange."),
 						format_type_be(ret_type));
 	}
 	else if (IsPolymorphicTypeFamily2(ret_type))
@@ -2632,6 +2909,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 || targettype == ANYCOMPATIBLEMULTIRANGEOID)
+		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 ce09ad73754..82732146d3d 100644
--- a/src/backend/utils/adt/Makefile
+++ b/src/backend/utils/adt/Makefile
@@ -60,6 +60,8 @@ OBJS = \
 	mac8.o \
 	mcxtfuncs.o \
 	misc.o \
+	multirangetypes.o \
+	multirangetypes_selfuncs.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 00000000000..cf060462de6
--- /dev/null
+++ b/src/backend/utils/adt/multirangetypes.c
@@ -0,0 +1,2610 @@
+/*-------------------------------------------------------------------------
+ *
+ * multirangetypes.c
+ *	  I/O functions, operators, and support functions for multirange types.
+ *
+ * The stored (serialized) format of a multirange value is:
+ *
+ *	12 bytes: MultirangeType struct including varlena header, multirange
+ *			  type's OID and the number of ranges in the multirange.
+ *	4 * (rangesCount - 1) bytes: 32-bit items pointing to the each range
+ *								 in the multirange starting from
+ *								 the second one.
+ *	1 * rangesCount bytes : 8-bit flags for each range in the multirange
+ *	The rest of multirange are range bound values pointed by multirange items.
+ *
+ *	Majority of items contain lengths of corresponding range bound values.
+ *	Thanks to that items are typically low numbers.  This makes multiranges
+ *	compression-friendly.  Every MULTIRANGE_ITEM_OFFSET_STRIDE item contains
+ *	an offset of the corresponding range bound values.  That allows fast lookups
+ *	for particular range index.  Offsets are counted starting from end of flags
+ *	aligned to the bound type.
+ *
+ * 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 "common/hashfn.h"
+#include "lib/stringinfo.h"
+#include "libpq/pqformat.h"
+#include "miscadmin.h"
+#include "utils/builtins.h"
+#include "utils/lsyscache.h"
+#include "utils/rangetypes.h"
+#include "utils/multirangetypes.h"
+#include "utils/array.h"
+#include "utils/memutils.h"
+
+/* fn_extra cache entry for one of the range I/O functions */
+typedef struct MultirangeIOData
+{
+	TypeCacheEntry *typcache;	/* multirange type's typcache entry */
+	FmgrInfo	typioproc;		/* range type's I/O proc */
+	Oid			typioparam;		/* range type's I/O parameter */
+} 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;
+
+#define MultirangeGetItemsPtr(mr) ((uint32 *) ((Pointer) (mr) + sizeof(MultirangeType)))
+#define MultirangeGetFlagsPtr(mr) ((uint8 *) ((Pointer) (mr) + sizeof(MultirangeType) + ((mr)->rangeCount - 1) * sizeof(uint32)))
+#define MultirangeGetBoundariesPtr(mr, align) ((Pointer) (mr) + att_align_nominal(sizeof(MultirangeType) + ((mr)->rangeCount - 1) * sizeof(uint32) + (mr)->rangeCount * sizeof(uint8), (align)))
+
+#define MULTIRANGE_ITEM_OFF_BIT 0x80000000
+#define MULTIRANGE_ITEM_GET_OFFLEN(item) ((item) & 0x7FFFFFFF)
+#define MULTIRANGE_ITEM_HAS_OFF(item) ((item) & MULTIRANGE_ITEM_OFF_BIT)
+#define MULTIRANGE_ITEM_OFFSET_STRIDE 4
+
+typedef int (*multirange_bsearch_callback) (TypeCacheEntry *typcache,
+											RangeBound *lower,
+											RangeBound *upper,
+											void *key,
+											bool *match);
+
+static MultirangeIOData *get_multirange_io_data(FunctionCallInfo fcinfo, Oid mltrngtypid,
+												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 with 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;
+	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 = NULL;
+	int32		range_str_len;
+	char	   *range_str_copy;
+
+	cache = get_multirange_io_data(fcinfo, mltrngtypoid, IOFunc_input);
+	rangetyp = cache->typcache->rngtype;
+
+	/* 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 + 1;
+					range_str_copy = pnstrdup(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->typioproc,
+																 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;
+	int32		range_count;
+	int32		i;
+	RangeType **ranges;
+
+	cache = get_multirange_io_data(fcinfo, mltrngtypoid, IOFunc_output);
+
+	initStringInfo(&buf);
+
+	appendStringInfoChar(&buf, '{');
+
+	multirange_deserialize(cache->typcache->rngtype, multirange, &range_count, &ranges);
+	for (i = 0; i < range_count; i++)
+	{
+		if (i > 0)
+			appendStringInfoChar(&buf, ',');
+		range = ranges[i];
+		rangeStr = OutputFunctionCall(&cache->typioproc, RangeTypePGetDatum(range));
+		appendStringInfoString(&buf, rangeStr);
+	}
+
+	appendStringInfoChar(&buf, '}');
+
+	PG_RETURN_CSTRING(buf.data);
+}
+
+/*
+ * Binary representation: First a int32-sized count of ranges, followed by
+ * ranges in their native binary representation.
+ */
+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;
+	uint32		range_count;
+	RangeType **ranges;
+	MultirangeType *ret;
+	StringInfoData tmpbuf;
+
+	cache = get_multirange_io_data(fcinfo, mltrngtypoid, IOFunc_receive);
+
+	range_count = pq_getmsgint(buf, 4);
+	ranges = palloc(range_count * sizeof(RangeType *));
+
+	initStringInfo(&tmpbuf);
+	for (int i = 0; i < range_count; i++)
+	{
+		uint32		range_len = pq_getmsgint(buf, 4);
+		const char *range_data = pq_getmsgbytes(buf, range_len);
+
+		resetStringInfo(&tmpbuf);
+		appendBinaryStringInfo(&tmpbuf, range_data, range_len);
+
+		ranges[i] = DatumGetRangeTypeP(ReceiveFunctionCall(&cache->typioproc,
+														   &tmpbuf,
+														   cache->typioparam,
+														   typmod));
+	}
+	pfree(tmpbuf.data);
+
+	pq_getmsgend(buf);
+
+	ret = make_multirange(mltrngtypoid, cache->typcache->rngtype,
+						  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;
+	MultirangeIOData *cache;
+
+	cache = get_multirange_io_data(fcinfo, mltrngtypoid, IOFunc_send);
+
+	/* construct output */
+	pq_begintypsend(buf);
+
+	pq_sendint32(buf, multirange->rangeCount);
+
+	multirange_deserialize(cache->typcache->rngtype, multirange, &range_count, &ranges);
+	for (int i = 0; i < range_count; i++)
+	{
+		Datum		range;
+
+		range = RangeTypePGetDatum(ranges[i]);
+		range = PointerGetDatum(SendFunctionCall(&cache->typioproc, range));
+
+		pq_sendint32(buf, VARSIZE(range) - VARHDRSZ);
+		pq_sendbytes(buf, VARDATA(range), VARSIZE(range) - VARHDRSZ);
+	}
+
+	PG_RETURN_BYTEA_P(pq_endtypsend(buf));
+}
+
+/*
+ * 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)
+	{
+		Oid			typiofunc;
+		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,
+						 &typiofunc);
+
+		if (!OidIsValid(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(typiofunc, &cache->typioproc,
+					  fcinfo->flinfo->fn_mcxt);
+
+		fcinfo->flinfo->fn_extra = (void *) cache;
+	}
+
+	return cache;
+}
+
+/*
+ * Converts a list of arbitrary ranges 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;
+}
+
+/*
+ *----------------------------------------------------------
+ * 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;
+}
+
+static Size
+multirange_size_estimate(TypeCacheEntry *rangetyp, int32 range_count,
+						 RangeType **ranges)
+{
+	char		elemalign = rangetyp->rngelemtype->typalign;
+	Size		size;
+	int			i;
+
+	/*
+	 * Count space for varlena header, multirange type's OID, other fields,
+	 * and padding so that ShortRangeTypes start aligned.
+	 */
+	size = att_align_nominal(sizeof(MultirangeType) +
+							 Max(range_count - 1, 0) * sizeof(uint32) +
+							 range_count * sizeof(uint8), elemalign);
+
+	/* Count space for all ranges */
+	for (i = 0; i < range_count; i++)
+		size += att_align_nominal(VARSIZE(ranges[i]) - sizeof(RangeType) - sizeof(char), elemalign);
+
+	return size;
+}
+
+static void
+write_multirange_data(MultirangeType *multirange, TypeCacheEntry *rangetyp,
+					  int32 range_count, RangeType **ranges)
+{
+	uint32	   *items;
+	uint32 		prev_offset = 0;
+	uint8	   *flags;
+	int			i;
+	Pointer		begin,
+				ptr;
+	char		elemalign = rangetyp->rngelemtype->typalign;
+
+	items = MultirangeGetItemsPtr(multirange);
+	flags = MultirangeGetFlagsPtr(multirange);
+	ptr = begin = MultirangeGetBoundariesPtr(multirange, elemalign);
+	for (i = 0; i < range_count; i++)
+	{
+		uint32		len;
+
+		if (i > 0)
+		{
+			items[i - 1] = ptr - begin;
+			if ((i % MULTIRANGE_ITEM_OFFSET_STRIDE) != 0)
+				items[i - 1] -= prev_offset;
+			else
+				items[i - 1] |= MULTIRANGE_ITEM_OFF_BIT;
+			prev_offset = ptr - begin;
+		}
+		flags[i] = *((Pointer) ranges[i] + VARSIZE(ranges[i]) - sizeof(char));
+		len = VARSIZE(ranges[i]) - sizeof(RangeType) - sizeof(char);
+		memcpy(ptr, (Pointer) (ranges[i] + 1), len);
+		ptr += att_align_nominal(len, elemalign);
+	}
+}
+
+
+/*
+ * 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;
+	Size		size;
+
+	/* Sort and merge input ranges. */
+	range_count = multirange_canonicalize(rangetyp, range_count, ranges);
+
+
+	/* Note: zero-fill is required here, just as in heap tuples */
+	size = multirange_size_estimate(rangetyp, range_count, ranges);
+	multirange = palloc0(size);
+	SET_VARSIZE(multirange, size);
+
+	/* Now fill in the datum */
+	multirange->multirangetypid = mltrngtypoid;
+	multirange->rangeCount = range_count;
+
+	write_multirange_data(multirange, rangetyp, range_count, ranges);
+
+	return multirange;
+}
+
+static uint32
+multirange_get_bounds_offset(const MultirangeType *multirange, uint32 i)
+{
+	uint32 *items = MultirangeGetItemsPtr(multirange);
+	uint32	offset = 0;
+
+	while (i > 0)
+	{
+		offset += MULTIRANGE_ITEM_GET_OFFLEN(items[i - 1]);
+		if (MULTIRANGE_ITEM_HAS_OFF(items[i - 1]))
+			break;
+		i--;
+	}
+	return offset;
+}
+
+static RangeType *
+multirange_get_range(TypeCacheEntry *rangetyp,
+					 const MultirangeType *multirange, int i)
+{
+	uint32		offset;
+	uint8		flags;
+	Pointer		begin,
+				ptr;
+	int16		typlen = rangetyp->rngelemtype->typlen;
+	char		typalign = rangetyp->rngelemtype->typalign;
+	uint32		len;
+	RangeType  *range;
+
+	Assert(i < multirange->rangeCount);
+
+	offset = multirange_get_bounds_offset(multirange, i);
+	flags = MultirangeGetFlagsPtr(multirange)[i];
+	ptr = begin = MultirangeGetBoundariesPtr(multirange, typalign) + offset;
+
+	if (RANGE_HAS_LBOUND(flags))
+		ptr = (Pointer) att_addlength_pointer(ptr, typlen, ptr);
+	if (RANGE_HAS_UBOUND(flags))
+		ptr = (Pointer) att_addlength_pointer(ptr, typlen, ptr);
+
+	len = (ptr - begin) + sizeof(RangeType) + sizeof(char);
+
+	range = palloc0(len);
+	SET_VARSIZE(range, len);
+	range->rangetypid = rangetyp->type_id;
+
+	memcpy(range + 1, begin, ptr - begin);
+	*((Pointer) (range + 1) + (ptr - begin)) = flags;
+
+	return range;
+}
+
+/*
+ * 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(TypeCacheEntry *rangetyp,
+					   const MultirangeType *multirange, int32 *range_count,
+					   RangeType ***ranges)
+{
+	*range_count = multirange->rangeCount;
+
+	/* Convert each ShortRangeType into a RangeType */
+	if (*range_count > 0)
+	{
+		int			i;
+
+		*ranges = palloc(*range_count * sizeof(RangeType *));
+		for (i = 0; i < *range_count; i++)
+			(*ranges)[i] = multirange_get_range(rangetyp, multirange, i);
+	}
+	else
+	{
+		*ranges = NULL;
+	}
+}
+
+MultirangeType *
+make_empty_multirange(Oid mltrngtypoid, TypeCacheEntry *rangetyp)
+{
+	return make_multirange(mltrngtypoid, rangetyp, 0, NULL);
+}
+
+/*
+ *----------------------------------------------------------
+ * EXPANDED TOAST FUNCTIONS
+ *----------------------------------------------------------
+ */
+
+static void
+multirange_get_bounds(TypeCacheEntry *rangetyp,
+					  const MultirangeType *multirange,
+					  uint32 i, RangeBound *lower, RangeBound *upper)
+{
+	uint32		offset;
+	uint8		flags;
+	Pointer		ptr;
+	int16		typlen = rangetyp->rngelemtype->typlen;
+	char		typalign = rangetyp->rngelemtype->typalign;
+	bool		typbyval = rangetyp->rngelemtype->typbyval;
+	Datum		lbound;
+	Datum		ubound;
+
+	Assert(i < multirange->rangeCount);
+
+	offset = multirange_get_bounds_offset(multirange, i);
+	flags = MultirangeGetFlagsPtr(multirange)[i];
+	ptr = MultirangeGetBoundariesPtr(multirange, typalign) + offset;
+
+	/* multirange can't contain empty ranges */
+	Assert((flags & RANGE_EMPTY) == 0);
+
+	/* fetch lower bound, if any */
+	if (RANGE_HAS_LBOUND(flags))
+	{
+		/* att_align_pointer cannot be necessary here */
+		lbound = fetch_att(ptr, typbyval, typlen);
+		ptr = (Pointer) att_addlength_pointer(ptr, typlen, ptr);
+	}
+	else
+		lbound = (Datum) 0;
+
+	/* fetch upper bound, if any */
+	if (RANGE_HAS_UBOUND(flags))
+	{
+		ptr = (Pointer) att_align_pointer(ptr, typalign, typlen, ptr);
+		ubound = fetch_att(ptr, typbyval, typlen);
+		/* no need for att_addlength_pointer */
+	}
+	else
+		ubound = (Datum) 0;
+
+	/* emit results */
+
+	lower->val = lbound;
+	lower->infinite = (flags & RANGE_LB_INF) != 0;
+	lower->inclusive = (flags & RANGE_LB_INC) != 0;
+	lower->lower = true;
+
+	upper->val = ubound;
+	upper->infinite = (flags & RANGE_UB_INF) != 0;
+	upper->inclusive = (flags & RANGE_UB_INC) != 0;
+	upper->lower = false;
+}
+
+/*
+ *----------------------------------------------------------
+ * 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 ranges
+ * that match our return value, and there must be no NULLs.
+ */
+Datum
+multirange_constructor2(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,
+				(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+				 errmsg("multirange values cannot contain NULL members")));
+
+	rangeArray = PG_GETARG_ARRAYTYPE_P(0);
+
+	dims = ARR_NDIM(rangeArray);
+	if (dims > 1)
+		ereport(ERROR,
+				(errcode(ERRCODE_CARDINALITY_VIOLATION),
+				 errmsg("multiranges cannot be constructed from multi-dimensional arrays")));
+
+	rngtypid = ARR_ELEMTYPE(rangeArray);
+	if (rngtypid != rangetyp->type_id)
+		ereport(ERROR,
+				(errcode(ERRCODE_DATATYPE_MISMATCH),
+				 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,
+						(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+						 errmsg("multirange values cannot contain NULL members")));
+
+			/* make_multirange will do its own copy */
+			ranges[i] = DatumGetRangeTypeP(elements[i]);
+		}
+	}
+
+	PG_RETURN_MULTIRANGE_P(make_multirange(mltrngtypid, rangetyp, range_count, ranges));
+}
+
+/*
+ * Construct multirange value from a single range.  It'd be nice if we could
+ * just use multirange_constructor2 for this case, but we need a non-variadic
+ * single-arg function to let us define a CAST from a range to its multirange.
+ */
+Datum
+multirange_constructor1(PG_FUNCTION_ARGS)
+{
+	Oid			mltrngtypid = get_fn_expr_rettype(fcinfo->flinfo);
+	Oid			rngtypid;
+	TypeCacheEntry *typcache;
+	TypeCacheEntry *rangetyp;
+	RangeType  *range;
+
+	typcache = multirange_get_typcache(fcinfo, mltrngtypid);
+	rangetyp = typcache->rngtype;
+
+	/*
+	 * These checks should be guaranteed by our signature, but let's do them
+	 * just in case.
+	 */
+
+	if (PG_ARGISNULL(0))
+		ereport(ERROR,
+				(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+				 errmsg("multirange values cannot contain NULL members")));
+
+	range = PG_GETARG_RANGE_P(0);
+
+	/* Make sure the range type matches. */
+	rngtypid = RangeTypeGetOid(range);
+	if (rngtypid != rangetyp->type_id)
+		ereport(ERROR,
+				(errcode(ERRCODE_DATATYPE_MISMATCH),
+				 errmsg("type %u does not match constructor type", rngtypid)));
+
+	PG_RETURN_MULTIRANGE_P(make_multirange(mltrngtypid, rangetyp, 1, &range));
+}
+
+/*
+ * 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
+		elog(ERROR,				/* can't happen */
+			 "niladic multirange constructor must not receive arguments");
+}
+
+
+/* multirange, multirange -> multirange type functions */
+
+/* multirange union */
+Datum
+multirange_union(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
+	MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count1;
+	int32		range_count2;
+	int32		range_count3;
+	RangeType **ranges1;
+	RangeType **ranges2;
+	RangeType **ranges3;
+
+	if (MultirangeIsEmpty(mr1))
+		PG_RETURN_MULTIRANGE_P(mr2);
+	if (MultirangeIsEmpty(mr2))
+		PG_RETURN_MULTIRANGE_P(mr1);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr1));
+
+	multirange_deserialize(typcache->rngtype, mr1, &range_count1, &ranges1);
+	multirange_deserialize(typcache->rngtype, mr2, &range_count2, &ranges2);
+
+	range_count3 = range_count1 + range_count2;
+	ranges3 = palloc0(range_count3 * sizeof(RangeType *));
+	memcpy(ranges3, ranges1, range_count1 * sizeof(RangeType *));
+	memcpy(ranges3 + range_count1, ranges2, range_count2 * sizeof(RangeType *));
+	PG_RETURN_MULTIRANGE_P(make_multirange(typcache->type_id, typcache->rngtype,
+										   range_count3, ranges3));
+}
+
+/* multirange minus */
+Datum
+multirange_minus(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
+	MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
+	Oid			mltrngtypoid = MultirangeTypeGetOid(mr1);
+	TypeCacheEntry *typcache;
+	TypeCacheEntry *rangetyp;
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+
+	typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
+	rangetyp = typcache->rngtype;
+
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		PG_RETURN_MULTIRANGE_P(mr1);
+
+	multirange_deserialize(typcache->rngtype, mr1, &range_count1, &ranges1);
+	multirange_deserialize(typcache->rngtype, mr2, &range_count2, &ranges2);
+
+	PG_RETURN_MULTIRANGE_P(multirange_minus_internal(mltrngtypoid,
+													 rangetyp,
+													 range_count1,
+													 ranges1,
+													 range_count2,
+													 ranges2));
+}
+
+MultirangeType *
+multirange_minus_internal(Oid mltrngtypoid, TypeCacheEntry *rangetyp,
+						  int32 range_count1, RangeType **ranges1,
+						  int32 range_count2, RangeType **ranges2)
+{
+	RangeType  *r1;
+	RangeType  *r2;
+	RangeType **ranges3;
+	int32		range_count3;
+	int32		i1;
+	int32		i2;
+
+	/*
+	 * Worst case: every range in ranges1 makes a different cut to some range
+	 * in ranges2.
+	 */
+	ranges3 = palloc0((range_count1 + range_count2) * sizeof(RangeType *));
+	range_count3 = 0;
+
+	/*
+	 * For each range in mr1, keep subtracting until it's gone or the ranges
+	 * in mr2 have passed it. After a subtraction we assign what's left back
+	 * to r1. The parallel progress through mr1 and mr2 is similar to
+	 * multirange_overlaps_multirange_internal.
+	 */
+	r2 = ranges2[0];
+	for (i1 = 0, i2 = 0; i1 < range_count1; i1++)
+	{
+		r1 = ranges1[i1];
+
+		/* Discard r2s while r2 << r1 */
+		while (r2 != NULL && range_before_internal(rangetyp, r2, r1))
+		{
+			r2 = ++i2 >= range_count2 ? NULL : ranges2[i2];
+		}
+
+		while (r2 != NULL)
+		{
+			if (range_split_internal(rangetyp, r1, r2, &ranges3[range_count3], &r1))
+			{
+				/*
+				 * If r2 takes a bite out of the middle of r1, we need two
+				 * outputs
+				 */
+				range_count3++;
+				r2 = ++i2 >= range_count2 ? NULL : ranges2[i2];
+
+			}
+			else if (range_overlaps_internal(rangetyp, r1, r2))
+			{
+				/*
+				 * If r2 overlaps r1, replace r1 with r1 - r2.
+				 */
+				r1 = range_minus_internal(rangetyp, r1, r2);
+
+				/*
+				 * If r2 goes past r1, then we need to stay with it, in case
+				 * it hits future r1s. Otherwise we need to keep r1, in case
+				 * future r2s hit it. Since we already subtracted, there's no
+				 * point in using the overright/overleft calls.
+				 */
+				if (RangeIsEmpty(r1) || range_before_internal(rangetyp, r1, r2))
+					break;
+				else
+					r2 = ++i2 >= range_count2 ? NULL : ranges2[i2];
+
+			}
+			else
+			{
+				/*
+				 * This and all future r2s are past r1, so keep them. Also
+				 * assign whatever is left of r1 to the result.
+				 */
+				break;
+			}
+		}
+
+		/*
+		 * Nothing else can remove anything from r1, so keep it. Even if r1 is
+		 * empty here, make_multirange will remove it.
+		 */
+		ranges3[range_count3++] = r1;
+	}
+
+	return make_multirange(mltrngtypoid, rangetyp, range_count3, ranges3);
+}
+
+/* multirange intersection */
+Datum
+multirange_intersect(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
+	MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
+	Oid			mltrngtypoid = MultirangeTypeGetOid(mr1);
+	TypeCacheEntry *typcache;
+	TypeCacheEntry *rangetyp;
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+
+	typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
+	rangetyp = typcache->rngtype;
+
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		PG_RETURN_MULTIRANGE_P(make_empty_multirange(mltrngtypoid, rangetyp));
+
+	multirange_deserialize(rangetyp, mr1, &range_count1, &ranges1);
+	multirange_deserialize(rangetyp, mr2, &range_count2, &ranges2);
+
+	PG_RETURN_MULTIRANGE_P(multirange_intersect_internal(mltrngtypoid,
+														 rangetyp,
+														 range_count1,
+														 ranges1,
+														 range_count2,
+														 ranges2));
+}
+
+MultirangeType *
+multirange_intersect_internal(Oid mltrngtypoid, TypeCacheEntry *rangetyp,
+							  int32 range_count1, RangeType **ranges1,
+							  int32 range_count2, RangeType **ranges2)
+{
+	RangeType  *r1;
+	RangeType  *r2;
+	RangeType **ranges3;
+	int32		range_count3;
+	int32		i1;
+	int32		i2;
+
+	if (range_count1 == 0 || range_count2 == 0)
+		return make_multirange(mltrngtypoid, rangetyp, 0, NULL);
+
+	/*-----------------------------------------------
+	 * Worst case is a stitching pattern like this:
+	 *
+	 * mr1: --- --- --- ---
+	 * mr2:   --- --- ---
+	 * mr3:   - - - - - -
+	 *
+	 * That seems to be range_count1 + range_count2 - 1,
+	 * but one extra won't hurt.
+	 *-----------------------------------------------
+	 */
+	ranges3 = palloc0((range_count1 + range_count2) * sizeof(RangeType *));
+	range_count3 = 0;
+
+	/*
+	 * For each range in mr1, keep intersecting until the ranges in mr2 have
+	 * passed it. The parallel progress through mr1 and mr2 is similar to
+	 * multirange_minus_multirange_internal, but we don't have to assign back
+	 * to r1.
+	 */
+	r2 = ranges2[0];
+	for (i1 = 0, i2 = 0; i1 < range_count1; i1++)
+	{
+		r1 = ranges1[i1];
+
+		/* Discard r2s while r2 << r1 */
+		while (r2 != NULL && range_before_internal(rangetyp, r2, r1))
+		{
+			r2 = ++i2 >= range_count2 ? NULL : ranges2[i2];
+		}
+
+		while (r2 != NULL)
+		{
+			if (range_overlaps_internal(rangetyp, r1, r2))
+			{
+				/* Keep the overlapping part */
+				ranges3[range_count3++] = range_intersect_internal(rangetyp, r1, r2);
+
+				/* If we "used up" all of r2, go to the next one... */
+				if (range_overleft_internal(rangetyp, r2, r1))
+					r2 = ++i2 >= range_count2 ? NULL : ranges2[i2];
+
+				/* ...otherwise go to the next r1 */
+				else
+					break;
+			}
+			else
+				/* We're past r1, so move to the next one */
+				break;
+		}
+
+		/* If we're out of r2s, there can be no more intersections */
+		if (r2 == NULL)
+			break;
+	}
+
+	return make_multirange(mltrngtypoid, rangetyp, range_count3, ranges3);
+}
+
+/*
+ * range_agg_transfn: combine adjacent/overlapping ranges.
+ *
+ * All we do here is gather the input ranges into an array
+ * so that the finalfn can sort and combine them.
+ */
+Datum
+range_agg_transfn(PG_FUNCTION_ARGS)
+{
+	MemoryContext aggContext;
+	Oid			rngtypoid;
+	ArrayBuildState *state;
+
+	if (!AggCheckCallContext(fcinfo, &aggContext))
+		elog(ERROR, "range_agg_transfn called in non-aggregate context");
+
+	rngtypoid = get_fn_expr_argtype(fcinfo->flinfo, 1);
+	if (!type_is_range(rngtypoid))
+		ereport(ERROR,
+				(errcode(ERRCODE_DATATYPE_MISMATCH),
+				 errmsg("range_agg must be called with a range")));
+
+	if (PG_ARGISNULL(0))
+		state = initArrayResult(rngtypoid, aggContext, false);
+	else
+		state = (ArrayBuildState *) PG_GETARG_POINTER(0);
+
+	/* skip NULLs */
+	if (!PG_ARGISNULL(1))
+		accumArrayResult(state, PG_GETARG_DATUM(1), false, rngtypoid, aggContext);
+
+	PG_RETURN_POINTER(state);
+}
+
+/*
+ * range_agg_finalfn: use our internal array to merge touching ranges.
+ */
+Datum
+range_agg_finalfn(PG_FUNCTION_ARGS)
+{
+	MemoryContext aggContext;
+	Oid			mltrngtypoid;
+	TypeCacheEntry *typcache;
+	ArrayBuildState *state;
+	int32		range_count;
+	RangeType **ranges;
+	int			i;
+
+	if (!AggCheckCallContext(fcinfo, &aggContext))
+		elog(ERROR, "range_agg_finalfn called in non-aggregate context");
+
+	state = PG_ARGISNULL(0) ? NULL : (ArrayBuildState *) PG_GETARG_POINTER(0);
+	if (state == NULL)
+		/* This shouldn't be possible, but just in case.... */
+		PG_RETURN_NULL();
+
+	/* Also return NULL if we had zero inputs, like other aggregates */
+	range_count = state->nelems;
+	if (range_count == 0)
+		PG_RETURN_NULL();
+
+	mltrngtypoid = get_fn_expr_rettype(fcinfo->flinfo);
+	typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
+
+	ranges = palloc0(range_count * sizeof(RangeType *));
+	for (i = 0; i < range_count; i++)
+		ranges[i] = DatumGetRangeTypeP(state->dvalues[i]);
+
+	PG_RETURN_MULTIRANGE_P(make_multirange(mltrngtypoid, typcache->rngtype, range_count, ranges));
+}
+
+Datum
+multirange_intersect_agg_transfn(PG_FUNCTION_ARGS)
+{
+	MemoryContext aggContext;
+	Oid			mltrngtypoid;
+	TypeCacheEntry *typcache;
+	MultirangeType *result;
+	MultirangeType *current;
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+
+	if (!AggCheckCallContext(fcinfo, &aggContext))
+		elog(ERROR, "multirange_intersect_agg_transfn called in non-aggregate context");
+
+	mltrngtypoid = get_fn_expr_argtype(fcinfo->flinfo, 1);
+	if (!type_is_multirange(mltrngtypoid))
+		ereport(ERROR,
+				(errcode(ERRCODE_DATATYPE_MISMATCH),
+				 errmsg("range_intersect_agg must be called with a multirange")));
+
+	typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
+
+	/* strictness ensures these are non-null */
+	result = PG_GETARG_MULTIRANGE_P(0);
+	current = PG_GETARG_MULTIRANGE_P(1);
+
+	multirange_deserialize(typcache->rngtype, result, &range_count1, &ranges1);
+	multirange_deserialize(typcache->rngtype, current, &range_count2, &ranges2);
+
+	result = multirange_intersect_internal(mltrngtypoid,
+										   typcache->rngtype,
+										   range_count1,
+										   ranges1,
+										   range_count2,
+										   ranges2);
+	PG_RETURN_RANGE_P(result);
+}
+
+
+/* multirange -> element type functions */
+
+/* extract lower bound value */
+Datum
+multirange_lower(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	TypeCacheEntry *typcache;
+	RangeBound	lower;
+	RangeBound	upper;
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_NULL();
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_get_bounds(typcache->rngtype, mr, 0,
+						  &lower, &upper);
+
+	if (!lower.infinite)
+		PG_RETURN_DATUM(lower.val);
+	else
+		PG_RETURN_NULL();
+}
+
+/* extract upper bound value */
+Datum
+multirange_upper(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	TypeCacheEntry *typcache;
+	RangeBound	lower;
+	RangeBound	upper;
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_NULL();
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_get_bounds(typcache->rngtype, mr, mr->rangeCount - 1,
+						  &lower, &upper);
+
+	if (!upper.infinite)
+		PG_RETURN_DATUM(upper.val);
+	else
+		PG_RETURN_NULL();
+}
+
+
+/* multirange -> bool functions */
+
+/* is multirange empty? */
+Datum
+multirange_empty(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+
+	PG_RETURN_BOOL(MultirangeIsEmpty(mr));
+}
+
+/* is lower bound inclusive? */
+Datum
+multirange_lower_inc(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	TypeCacheEntry *typcache;
+	RangeBound	lower;
+	RangeBound	upper;
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+	multirange_get_bounds(typcache->rngtype, mr, 0,
+						  &lower, &upper);
+
+	PG_RETURN_BOOL(lower.inclusive);
+}
+
+/* is upper bound inclusive? */
+Datum
+multirange_upper_inc(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	TypeCacheEntry *typcache;
+	RangeBound	lower;
+	RangeBound	upper;
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+	multirange_get_bounds(typcache->rngtype, mr, mr->rangeCount - 1,
+						  &lower, &upper);
+
+	PG_RETURN_BOOL(upper.inclusive);
+}
+
+/* is lower bound infinite? */
+Datum
+multirange_lower_inf(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	TypeCacheEntry *typcache;
+	RangeBound	lower;
+	RangeBound	upper;
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+	multirange_get_bounds(typcache->rngtype, mr, 0,
+						  &lower, &upper);
+
+	PG_RETURN_BOOL(lower.infinite);
+}
+
+/* is upper bound infinite? */
+Datum
+multirange_upper_inf(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	TypeCacheEntry *typcache;
+	RangeBound	lower;
+	RangeBound	upper;
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+	multirange_get_bounds(typcache->rngtype, mr, mr->rangeCount - 1,
+						  &lower, &upper);
+
+	PG_RETURN_BOOL(upper.infinite);
+}
+
+
+
+/* multirange, element -> bool functions */
+
+static bool
+range_bounds_overlaps(TypeCacheEntry *typcache,
+					  RangeBound *lower1, RangeBound *upper1,
+					  RangeBound *lower2, RangeBound *upper2)
+{
+	if (range_cmp_bounds(typcache, lower1, lower2) >= 0 &&
+		range_cmp_bounds(typcache, lower1, upper2) <= 0)
+		return true;
+
+	if (range_cmp_bounds(typcache, lower2, lower1) >= 0 &&
+		range_cmp_bounds(typcache, lower2, upper1) <= 0)
+		return true;
+
+	return false;
+}
+
+static bool
+range_bounds_contains(TypeCacheEntry *typcache,
+					  RangeBound *lower1, RangeBound *upper1,
+					  RangeBound *lower2, RangeBound *upper2)
+{
+	if (range_cmp_bounds(typcache, lower1, lower2) <= 0 &&
+		range_cmp_bounds(typcache, upper1, upper2) >= 0)
+		return true;
+
+	return false;
+}
+
+/* contains? */
+Datum
+multirange_contains_elem(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	Datum		val = PG_GETARG_DATUM(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(multirange_contains_elem_internal(typcache, mr, val));
+}
+
+/* contained by? */
+Datum
+elem_contained_by_multirange(PG_FUNCTION_ARGS)
+{
+	Datum		val = PG_GETARG_DATUM(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(multirange_contains_elem_internal(typcache, mr, val));
+}
+
+/*
+ * Test whether multirange mr contains a specific element value.
+ */
+
+static bool
+multirange_bsearch_match(TypeCacheEntry *typcache, MultirangeType *mr,
+						 void *key, multirange_bsearch_callback callback)
+{
+	uint32		l,
+				u,
+				idx;
+	int			comparison;
+	bool		match = false;
+
+	l = 0;
+	u = mr->rangeCount;
+	while (l < u)
+	{
+		RangeBound	lower,
+					upper;
+
+		idx = (l + u) / 2;
+		multirange_get_bounds(typcache, mr, idx, &lower, &upper);
+		comparison = (*callback) (typcache, &lower, &upper, key, &match);
+
+		if (comparison < 0)
+			u = idx;
+		else if (comparison > 0)
+			l = idx + 1;
+		else
+			return match;
+	}
+
+	return false;
+}
+
+static int
+multirange_elem_bsearch_callback(TypeCacheEntry *typcache,
+								 RangeBound *lower, RangeBound *upper,
+								 void *key, bool *match)
+{
+	Datum	val = *((Datum *) key);
+	int		cmp;
+
+	if (!lower->infinite)
+	{
+		cmp = DatumGetInt32(FunctionCall2Coll(&typcache->rng_cmp_proc_finfo,
+											  typcache->rng_collation,
+											  lower->val, val));
+		if (cmp > 0 || (cmp == 0 && !lower->inclusive))
+			return -1;
+	}
+
+	if (!upper->infinite)
+	{
+		cmp = DatumGetInt32(FunctionCall2Coll(&typcache->rng_cmp_proc_finfo,
+											  typcache->rng_collation,
+											  upper->val, val));
+		if (cmp < 0 || (cmp == 0 && !upper->inclusive))
+			return 1;
+	}
+
+	*match = true;
+	return 0;
+}
+
+static int
+multirange_range_contains_bsearch_callback(TypeCacheEntry *typcache,
+										   RangeBound *lower, RangeBound *upper,
+										   void *key, bool *match)
+{
+	RangeBound *keyLower = (RangeBound *) key;
+	RangeBound *keyUpper = (RangeBound *) key + 1;
+
+	if (range_cmp_bounds(typcache, keyUpper, lower) < 0)
+		return -1;
+
+	if (range_cmp_bounds(typcache, keyLower, upper) > 0)
+		return -1;
+
+	*match = range_bounds_contains(typcache, lower, upper, keyLower, keyUpper);
+	return 0;
+}
+
+static int
+multirange_range_overlaps_bsearch_callback(TypeCacheEntry *typcache,
+										   RangeBound *lower, RangeBound *upper,
+										   void *key, bool *match)
+{
+	RangeBound *keyLower = (RangeBound *) key;
+	RangeBound *keyUpper = (RangeBound *) key + 1;
+
+	if (range_cmp_bounds(typcache, keyUpper, lower) < 0)
+		return -1;
+
+	if (range_cmp_bounds(typcache, keyLower, upper) > 0)
+		return -1;
+
+	*match = true;
+	return 0;
+}
+
+bool
+multirange_contains_elem_internal(TypeCacheEntry *typcache, MultirangeType *mr, Datum val)
+{
+	if (MultirangeIsEmpty(mr))
+		return false;
+
+	return multirange_bsearch_match(typcache->rngtype, mr, &val,
+								 	multirange_elem_bsearch_callback);
+}
+
+/* multirange, range -> bool functions */
+
+/* contains? */
+Datum
+multirange_contains_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(multirange_contains_range_internal(typcache, mr, r));
+}
+
+/* contained by? */
+Datum
+range_contained_by_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(multirange_contains_range_internal(typcache, mr, r));
+}
+
+/*
+ * Test whether multirange mr contains a specific range r.
+ */
+bool
+multirange_contains_range_internal(TypeCacheEntry *typcache, MultirangeType *mr, RangeType *r)
+{
+	TypeCacheEntry *rangetyp;
+	RangeBound	bounds[2];
+	bool		empty;
+
+	rangetyp = typcache->rngtype;
+
+	/*
+	 * Every multirange contains an infinite number of empty ranges, even an
+	 * empty one.
+	 */
+	if (RangeIsEmpty(r))
+		return true;
+
+	if (MultirangeIsEmpty(mr))
+		return false;
+
+	range_deserialize(rangetyp, r, &bounds[0], &bounds[1], &empty);
+	Assert(!empty);
+
+	return multirange_bsearch_match(rangetyp, mr, bounds,
+								 	multirange_range_contains_bsearch_callback);
+}
+
+
+/* multirange, multirange -> bool functions */
+
+/* equality (internal version) */
+bool
+multirange_eq_internal(TypeCacheEntry *typcache, MultirangeType *mr1, MultirangeType *mr2)
+{
+	TypeCacheEntry *rangetyp = typcache->rngtype;
+	int32		range_count_1;
+	int32		range_count_2;
+	int32		i;
+	RangeBound	lower1,
+				upper1,
+				lower2,
+				upper2;
+
+	/* Different types should be prevented by ANYMULTIRANGE matching rules */
+	if (MultirangeTypeGetOid(mr1) != MultirangeTypeGetOid(mr2))
+		elog(ERROR, "multirange types do not match");
+
+	range_count_1 = mr1->rangeCount;
+	range_count_2 = mr2->rangeCount;
+
+	if (range_count_1 != range_count_2)
+		return false;
+
+	for (i = 0; i < range_count_1; i++)
+	{
+		multirange_get_bounds(rangetyp, mr1, i, &lower1, &upper1);
+		multirange_get_bounds(rangetyp, mr2, i, &lower2, &upper2);
+
+		if (range_cmp_bounds(rangetyp, &lower1, &lower2) != 0 ||
+			range_cmp_bounds(rangetyp, &upper1, &upper2) != 0)
+			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));
+}
+
+/* overlaps? */
+Datum
+range_overlaps_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(range_overlaps_multirange_internal(typcache, r, mr));
+}
+
+Datum
+multirange_overlaps_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(range_overlaps_multirange_internal(typcache, r, mr));
+}
+
+Datum
+multirange_overlaps_multirange(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_overlaps_multirange_internal(typcache, mr1, mr2));
+}
+
+bool
+range_overlaps_multirange_internal(TypeCacheEntry *typcache, RangeType *r, MultirangeType *mr)
+{
+	TypeCacheEntry *rangetyp;
+	RangeBound	bounds[2];
+	bool		empty;
+
+	rangetyp = typcache->rngtype;
+
+	/*
+	 * Empties never overlap, even with empties. (This seems strange since
+	 * they *do* contain each other, but we want to follow how ranges work.)
+	 */
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		return false;
+
+	range_deserialize(rangetyp, r, &bounds[0], &bounds[1], &empty);
+	Assert(!empty);
+
+	return multirange_bsearch_match(rangetyp, mr, bounds,
+								 	multirange_range_overlaps_bsearch_callback);
+}
+
+bool
+multirange_overlaps_multirange_internal(TypeCacheEntry *typcache, MultirangeType *mr1,
+										MultirangeType *mr2)
+{
+	TypeCacheEntry *rangetyp;
+	int32		range_count1;
+	int32		range_count2;
+	int32		i1;
+	int32		i2;
+	RangeBound	lower1,
+				upper1,
+				lower2,
+				upper2;
+
+	/*
+	 * Empties never overlap, even with empties. (This seems strange since
+	 * they *do* contain each other, but we want to follow how ranges work.)
+	 */
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		return false;
+
+	rangetyp = typcache->rngtype;
+
+	range_count1 = mr1->rangeCount;
+	range_count2 = mr2->rangeCount;
+
+	/*
+	 * Every range in mr1 gets a chance to overlap with the ranges in mr2, but
+	 * we can use their ordering to avoid O(n^2). This is similar to
+	 * range_overlaps_multirange where r1 : r2 :: mrr : r, but there if we
+	 * don't find an overlap with r we're done, and here if we don't find an
+	 * overlap with r2 we try the next r2.
+	 */
+	i1 = 0;
+	multirange_get_bounds(rangetyp, mr1, i1, &lower1, &upper1);
+	for (i1 = 0, i2 = 0; i2 < range_count2; i2++)
+	{
+		multirange_get_bounds(rangetyp, mr2, i2, &lower2, &upper2);
+
+		/* Discard r1s while r1 << r2 */
+		while (range_cmp_bounds(rangetyp, &upper1, &lower2) < 0)
+		{
+			if (++i1 >= range_count1)
+				return false;
+			multirange_get_bounds(rangetyp, mr1, i1, &lower1, &upper1);
+		}
+
+		/*
+		 * If r1 && r2, we're done, otherwise we failed to find an overlap for
+		 * r2, so go to the next one.
+		 */
+		if (range_bounds_overlaps(rangetyp, &lower1, &upper1, &lower2, &upper2))
+			return true;
+	}
+
+	/* We looked through all of mr2 without finding an overlap */
+	return false;
+}
+
+/* does not extend to right of? */
+Datum
+range_overleft_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	RangeBound	lower1,
+				upper1,
+				lower2,
+				upper2;
+	bool		empty;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	range_deserialize(typcache->rngtype, r, &lower1, &upper1, &empty);
+	Assert(!empty);
+	multirange_get_bounds(typcache->rngtype, mr, mr->rangeCount - 1,
+						  &lower2, &upper2);
+
+	PG_RETURN_BOOL(range_cmp_bounds(typcache->rngtype, &upper1, &upper2) <= 0);
+}
+
+Datum
+multirange_overleft_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+	RangeBound	lower1,
+				upper1,
+				lower2,
+				upper2;
+	bool		empty;
+
+	if (MultirangeIsEmpty(mr) || RangeIsEmpty(r))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_get_bounds(typcache->rngtype, mr, mr->rangeCount - 1,
+						  &lower1, &upper1);
+	range_deserialize(typcache->rngtype, r, &lower2, &upper2, &empty);
+	Assert(!empty);
+
+	PG_RETURN_BOOL(range_cmp_bounds(typcache->rngtype, &upper1, &upper2) <= 0);
+}
+
+Datum
+multirange_overleft_multirange(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
+	MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	RangeBound	lower1,
+				upper1,
+				lower2,
+				upper2;
+
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr1));
+
+	multirange_get_bounds(typcache->rngtype, mr1, mr1->rangeCount - 1,
+						  &lower1, &upper1);
+	multirange_get_bounds(typcache->rngtype, mr2, mr2->rangeCount - 1,
+						  &lower2, &upper2);
+
+	PG_RETURN_BOOL(range_cmp_bounds(typcache->rngtype, &upper1, &upper2) <= 0);
+}
+
+/* does not extend to left of? */
+Datum
+range_overright_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	RangeBound	lower1,
+				upper1,
+				lower2,
+				upper2;
+	bool		empty;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	range_deserialize(typcache->rngtype, r, &lower1, &upper1, &empty);
+	Assert(!empty);
+	multirange_get_bounds(typcache->rngtype, mr, 0, &lower2, &upper2);
+
+	PG_RETURN_BOOL(range_cmp_bounds(typcache->rngtype, &lower1, &lower2) >= 0);
+}
+
+Datum
+multirange_overright_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+	RangeBound	lower1,
+				upper1,
+				lower2,
+				upper2;
+	bool		empty;
+
+	if (MultirangeIsEmpty(mr) || RangeIsEmpty(r))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_get_bounds(typcache->rngtype, mr, 0, &lower1, &upper1);
+	range_deserialize(typcache->rngtype, r, &lower2, &upper2, &empty);
+	Assert(!empty);
+
+	PG_RETURN_BOOL(range_cmp_bounds(typcache->rngtype, &lower1, &lower2) >= 0);
+}
+
+Datum
+multirange_overright_multirange(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
+	MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	RangeBound	lower1,
+				upper1,
+				lower2,
+				upper2;
+
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr1));
+
+	multirange_get_bounds(typcache->rngtype, mr1, 0, &lower1, &upper1);
+	multirange_get_bounds(typcache->rngtype, mr2, 0, &lower2, &upper2);
+
+	PG_RETURN_BOOL(range_cmp_bounds(typcache->rngtype, &lower1, &lower2) >= 0);
+}
+
+/* contains? */
+Datum
+multirange_contains_multirange(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_contains_multirange_internal(typcache, mr1, mr2));
+}
+
+/* contained by? */
+Datum
+multirange_contained_by_multirange(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_contains_multirange_internal(typcache, mr2, mr1));
+}
+
+/*
+ * Test whether multirange mr1 contains every range from another multirange mr2.
+ */
+bool
+multirange_contains_multirange_internal(TypeCacheEntry *typcache,
+										MultirangeType *mr1, MultirangeType *mr2)
+{
+	TypeCacheEntry *rangetyp;
+	int32		range_count1 = mr1->rangeCount;
+	int32		range_count2 = mr2->rangeCount;
+	int			i1,
+				i2;
+	RangeBound	lower1,
+				upper1,
+				lower2,
+				upper2;
+
+	rangetyp = typcache->rngtype;
+
+	/*
+	 * We follow the same logic for empties as ranges: - an empty multirange
+	 * contains an empty range/multirange. - an empty multirange can't contain
+	 * any other range/multirange. - an empty multirange is contained by any
+	 * other range/multirange.
+	 */
+
+	if (range_count2 == 0)
+		return true;
+	if (range_count1 == 0)
+		return false;
+
+	/*
+	 * Every range in mr2 must be contained by some range in mr1. To avoid
+	 * O(n^2) we walk through both ranges in tandem.
+	 */
+	i1 = 0;
+	multirange_get_bounds(rangetyp, mr1, i1, &lower1, &upper1);
+	for (i2 = 0; i2 < range_count2; i2++)
+	{
+		multirange_get_bounds(rangetyp, mr2, i2, &lower2, &upper2);
+
+		/* Discard r1s while r1 << r2 */
+		while (range_cmp_bounds(rangetyp, &upper1, &lower2) < 0)
+		{
+			if (++i1 >= range_count1)
+				return false;
+			multirange_get_bounds(rangetyp, mr1, i1, &lower1, &upper1);
+		}
+
+		/*
+		 * If r1 @> r2, go to the next r2, otherwise return false (since every
+		 * r1[n] and r1[n+1] must have a gap). Note this will give weird
+		 * answers if you don't canonicalize, e.g. with a custom
+		 * int2multirange {[1,1], [2,2]} there is a "gap". But that is
+		 * consistent with other range operators, e.g. '[1,1]'::int2range -|-
+		 * '[2,2]'::int2range is false.
+		 */
+		if (!range_bounds_contains(rangetyp, &lower1, &upper1,
+								   &lower2, &upper2))
+			return false;
+	}
+
+	/* All ranges in mr2 are satisfied */
+	return true;
+}
+
+/* strictly left of? */
+Datum
+range_before_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(range_before_multirange_internal(typcache, r, mr));
+}
+
+Datum
+multirange_before_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(range_after_multirange_internal(typcache, r, mr));
+}
+
+Datum
+multirange_before_multirange(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_before_multirange_internal(typcache, mr1, mr2));
+}
+
+/* strictly right of? */
+Datum
+range_after_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(range_after_multirange_internal(typcache, r, mr));
+}
+
+Datum
+multirange_after_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(range_before_multirange_internal(typcache, r, mr));
+}
+
+Datum
+multirange_after_multirange(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_before_multirange_internal(typcache, mr2, mr1));
+}
+
+/* strictly left of? (internal version) */
+bool
+range_before_multirange_internal(TypeCacheEntry *typcache, RangeType *r,
+								 MultirangeType *mr)
+{
+	RangeBound	lower1,
+				upper1,
+				lower2,
+				upper2;
+	bool		empty;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		return false;
+
+	range_deserialize(typcache->rngtype, r, &lower1, &upper1, &empty);
+	Assert(!empty);
+
+	multirange_get_bounds(typcache->rngtype, mr, 0,
+						  &lower2, &upper2);
+
+	return (range_cmp_bounds(typcache->rngtype, &upper1, &lower2) < 0);
+}
+
+bool
+multirange_before_multirange_internal(TypeCacheEntry *typcache,
+									  MultirangeType *mr1,
+									  MultirangeType *mr2)
+{
+	RangeBound	lower1,
+				upper1,
+				lower2,
+				upper2;
+
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		return false;
+
+	multirange_get_bounds(typcache->rngtype, mr1, mr1->rangeCount - 1,
+						  &lower1, &upper1);
+	multirange_get_bounds(typcache->rngtype, mr2, 0,
+						  &lower2, &upper2);
+
+	return (range_cmp_bounds(typcache->rngtype, &upper1, &lower2) < 0);
+}
+
+/* strictly right of? (internal version) */
+bool
+range_after_multirange_internal(TypeCacheEntry *typcache, RangeType *r,
+								MultirangeType *mr)
+{
+	RangeBound	lower1,
+				upper1,
+				lower2,
+				upper2;
+	bool		empty;
+	int32		range_count;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		return false;
+
+	range_deserialize(typcache->rngtype, r, &lower1, &upper1, &empty);
+	Assert(!empty);
+
+	range_count = mr->rangeCount;
+	multirange_get_bounds(typcache->rngtype, mr, range_count - 1,
+						  &lower2, &upper2);
+
+	return (range_cmp_bounds(typcache->rngtype, &lower1, &upper2) > 0);
+}
+
+bool
+range_adjacent_multirange_internal(TypeCacheEntry *typcache, RangeType *r,
+								   MultirangeType *mr)
+{
+	RangeBound	lower1,
+				upper1,
+				lower2,
+				upper2;
+	bool		empty;
+	int32		range_count;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		return false;
+
+	range_deserialize(typcache->rngtype, r, &lower1, &upper1, &empty);
+	Assert(!empty);
+
+	range_count = mr->rangeCount;
+	multirange_get_bounds(typcache->rngtype, mr, 0,
+						  &lower2, &upper2);
+
+	if (bounds_adjacent(typcache->rngtype, upper1, lower2))
+		return true;
+
+	if (range_count > 1)
+		multirange_get_bounds(typcache->rngtype, mr, range_count - 1,
+							  &lower2, &upper2);
+
+	if (bounds_adjacent(typcache->rngtype, upper2, lower1))
+		return true;
+
+	return false;
+}
+
+/* adjacent to? */
+Datum
+range_adjacent_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(range_adjacent_multirange_internal(typcache, r, mr));
+}
+
+Datum
+multirange_adjacent_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		return false;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(range_adjacent_multirange_internal(typcache, r, mr));
+}
+
+Datum
+multirange_adjacent_multirange(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
+	MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count1;
+	int32		range_count2;
+	RangeBound	lower1,
+				upper1,
+				lower2,
+				upper2;
+
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		return false;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr1));
+
+	range_count1 = mr1->rangeCount;
+	range_count2 = mr2->rangeCount;
+	multirange_get_bounds(typcache->rngtype, mr1, range_count1 - 1,
+						  &lower1, &upper1);
+	multirange_get_bounds(typcache->rngtype, mr2, 0,
+						  &lower2, &upper2);
+	if (bounds_adjacent(typcache->rngtype, upper1, lower2))
+		PG_RETURN_BOOL(true);
+
+	if (range_count1 > 1)
+		multirange_get_bounds(typcache->rngtype, mr1, 0,
+							  &lower1, &upper1);
+	if (range_count2 > 1)
+		multirange_get_bounds(typcache->rngtype, mr2, range_count2 - 1,
+							  &lower2, &upper2);
+	if (bounds_adjacent(typcache->rngtype, upper2, lower1))
+		PG_RETURN_BOOL(true);
+	PG_RETURN_BOOL(false);
+}
+
+/* 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;
+	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));
+
+	range_count_1 = mr1->rangeCount;
+	range_count_2 = mr2->rangeCount;
+
+	/* Loop over source data */
+	range_count_max = Max(range_count_1, range_count_2);
+	for (i = 0; i < range_count_max; i++)
+	{
+		RangeBound	lower1,
+					upper1,
+					lower2,
+					upper2;
+
+		/*
+		 * 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;
+		}
+
+		multirange_get_bounds(typcache->rngtype, mr1, i, &lower1, &upper1);
+		multirange_get_bounds(typcache->rngtype, mr2, i, &lower2, &upper2);
+
+		cmp = range_cmp_bounds(typcache->rngtype, &lower1, &lower2);
+		if (cmp == 0)
+			cmp = range_cmp_bounds(typcache->rngtype, &upper1, &upper2);
+		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);
+}
+
+/* multirange -> range functions */
+
+/* Find the smallest range that includes everything in the multirange */
+Datum
+range_merge_from_multirange(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	Oid			mltrngtypoid = MultirangeTypeGetOid(mr);
+	TypeCacheEntry *typcache;
+	RangeType  *result;
+
+	typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
+
+	if (MultirangeIsEmpty(mr))
+	{
+		result = make_empty_range(typcache->rngtype);
+	}
+	else if (mr->rangeCount == 1)
+	{
+		result = multirange_get_range(typcache->rngtype, mr, 0);
+	}
+	else
+	{
+		RangeBound	firstLower,
+					firstUpper,
+					lastLower,
+					lastUpper;
+
+		multirange_get_bounds(typcache->rngtype, mr, 0,
+							  &firstLower, &firstUpper);
+		multirange_get_bounds(typcache->rngtype, mr, mr->rangeCount - 1,
+							  &lastLower, &lastUpper);
+
+		result = make_range(typcache->rngtype, &firstLower, &lastUpper, false);
+	}
+
+	PG_RETURN_RANGE_P(result);
+}
+
+/* Hash support */
+
+/* hash a multirange value */
+Datum
+hash_multirange(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	uint32		result = 1;
+	TypeCacheEntry *typcache,
+			   *scache;
+	int32		range_count,
+				i;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+	scache = typcache->rngtype->rngelemtype;
+	if (!OidIsValid(scache->hash_proc_finfo.fn_oid))
+	{
+		scache = lookup_type_cache(scache->type_id,
+								   TYPECACHE_HASH_PROC_FINFO);
+		if (!OidIsValid(scache->hash_proc_finfo.fn_oid))
+			ereport(ERROR,
+					(errcode(ERRCODE_UNDEFINED_FUNCTION),
+					 errmsg("could not identify a hash function for type %s",
+							format_type_be(scache->type_id))));
+	}
+
+	range_count = mr->rangeCount;
+	for (i = 0; i < range_count; i++)
+	{
+		RangeBound	lower,
+					upper;
+		uint8		flags = MultirangeGetFlagsPtr(mr)[i];
+		uint32		lower_hash;
+		uint32		upper_hash;
+		uint32		range_hash;
+
+		multirange_get_bounds(typcache->rngtype, mr, i, &lower, &upper);
+
+		if (RANGE_HAS_LBOUND(flags))
+			lower_hash = DatumGetUInt32(FunctionCall1Coll(&scache->hash_proc_finfo,
+														  typcache->rngtype->rng_collation,
+														  lower.val));
+		else
+			lower_hash = 0;
+
+		if (RANGE_HAS_UBOUND(flags))
+			upper_hash = DatumGetUInt32(FunctionCall1Coll(&scache->hash_proc_finfo,
+														  typcache->rngtype->rng_collation,
+														  upper.val));
+		else
+			upper_hash = 0;
+
+		/* Merge hashes of flags and bounds */
+		range_hash = hash_uint32((uint32) flags);
+		range_hash ^= lower_hash;
+		range_hash = (range_hash << 1) | (range_hash >> 31);
+		range_hash ^= upper_hash;
+
+		/*
+		 * Use the same approach as hash_array to combine the individual
+		 * elements' hash values:
+		 */
+		result = (result << 5) - result + range_hash;
+	}
+
+	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,
+			   *scache;
+	int32		range_count,
+				i;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+	scache = typcache->rngtype->rngelemtype;
+	if (!OidIsValid(scache->hash_extended_proc_finfo.fn_oid))
+	{
+		scache = lookup_type_cache(scache->type_id,
+								   TYPECACHE_HASH_EXTENDED_PROC_FINFO);
+		if (!OidIsValid(scache->hash_extended_proc_finfo.fn_oid))
+			ereport(ERROR,
+					(errcode(ERRCODE_UNDEFINED_FUNCTION),
+					 errmsg("could not identify a hash function for type %s",
+							format_type_be(scache->type_id))));
+	}
+
+	range_count = mr->rangeCount;
+	for (i = 0; i < range_count; i++)
+	{
+		RangeBound	lower,
+					upper;
+		uint8		flags = MultirangeGetFlagsPtr(mr)[i];
+		uint64		lower_hash;
+		uint64		upper_hash;
+		uint64		range_hash;
+
+		multirange_get_bounds(typcache->rngtype, mr, i, &lower, &upper);
+
+		if (RANGE_HAS_LBOUND(flags))
+			lower_hash = DatumGetUInt64(FunctionCall2Coll(&scache->hash_extended_proc_finfo,
+														  typcache->rngtype->rng_collation,
+														  lower.val,
+														  seed));
+		else
+			lower_hash = 0;
+
+		if (RANGE_HAS_UBOUND(flags))
+			upper_hash = DatumGetUInt64(FunctionCall2Coll(&scache->hash_extended_proc_finfo,
+														  typcache->rngtype->rng_collation,
+														  upper.val,
+														  seed));
+		else
+			upper_hash = 0;
+
+		/* Merge hashes of flags and bounds */
+		range_hash = DatumGetUInt64(hash_uint32_extended((uint32) flags,
+														 DatumGetInt64(seed)));
+		range_hash ^= lower_hash;
+		range_hash = ROTATE_HIGH_AND_LOW_32BITS(range_hash);
+		range_hash ^= upper_hash;
+
+		/*
+		 * Use the same approach as hash_array to combine the individual
+		 * elements' hash values:
+		 */
+		result = (result << 5) - result + range_hash;
+	}
+
+	PG_FREE_IF_COPY(mr, 0);
+
+	PG_RETURN_UINT64(result);
+}
diff --git a/src/backend/utils/adt/multirangetypes_selfuncs.c b/src/backend/utils/adt/multirangetypes_selfuncs.c
new file mode 100644
index 00000000000..b1e56841ccd
--- /dev/null
+++ b/src/backend/utils/adt/multirangetypes_selfuncs.c
@@ -0,0 +1,1307 @@
+/*-------------------------------------------------------------------------
+ *
+ * multirangetypes_selfuncs.c
+ *	  Functions for selectivity estimation of multirange operators
+ *
+ * Estimates are based on histograms of lower and upper bounds, and the
+ * fraction of empty multiranges.
+ *
+ * Portions Copyright (c) 1996-2020, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ *	  src/backend/utils/adt/multirangetypes_selfuncs.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include <math.h>
+
+#include "access/htup_details.h"
+#include "catalog/pg_operator.h"
+#include "catalog/pg_statistic.h"
+#include "catalog/pg_type.h"
+#include "utils/float.h"
+#include "utils/fmgrprotos.h"
+#include "utils/lsyscache.h"
+#include "utils/rangetypes.h"
+#include "utils/multirangetypes.h"
+#include "utils/selfuncs.h"
+#include "utils/typcache.h"
+
+static double calc_multirangesel(TypeCacheEntry *typcache, VariableStatData *vardata,
+								 const MultirangeType *constval, Oid operator);
+static double default_multirange_selectivity(Oid operator);
+static double default_multirange_selectivity(Oid operator);
+static double calc_hist_selectivity(TypeCacheEntry *typcache,
+									VariableStatData *vardata, const MultirangeType *constval,
+									Oid operator);
+static double calc_hist_selectivity_scalar(TypeCacheEntry *typcache,
+										   const RangeBound *constbound,
+										   const RangeBound *hist, int hist_nvalues,
+										   bool equal);
+static int	rbound_bsearch(TypeCacheEntry *typcache, const RangeBound *value,
+						   const RangeBound *hist, int hist_length, bool equal);
+static float8 get_position(TypeCacheEntry *typcache, const RangeBound *value,
+						   const RangeBound *hist1, const RangeBound *hist2);
+static float8 get_len_position(double value, double hist1, double hist2);
+static float8 get_distance(TypeCacheEntry *typcache, const RangeBound *bound1,
+						   const RangeBound *bound2);
+static int	length_hist_bsearch(Datum *length_hist_values,
+								int length_hist_nvalues, double value, bool equal);
+static double calc_length_hist_frac(Datum *length_hist_values,
+									int length_hist_nvalues, double length1, double length2, bool equal);
+static double calc_hist_selectivity_contained(TypeCacheEntry *typcache,
+											  const RangeBound *lower, RangeBound *upper,
+											  const RangeBound *hist_lower, int hist_nvalues,
+											  Datum *length_hist_values, int length_hist_nvalues);
+static double calc_hist_selectivity_contains(TypeCacheEntry *typcache,
+											 const RangeBound *lower, const RangeBound *upper,
+											 const RangeBound *hist_lower, int hist_nvalues,
+											 Datum *length_hist_values, int length_hist_nvalues);
+
+/*
+ * Returns a default selectivity estimate for given operator, when we don't
+ * have statistics or cannot use them for some reason.
+ */
+static double
+default_multirange_selectivity(Oid operator)
+{
+	switch (operator)
+	{
+		case OID_MULTIRANGE_OVERLAPS_MULTIRANGE_OP:
+		case OID_MULTIRANGE_OVERLAPS_RANGE_OP:
+		case OID_RANGE_OVERLAPS_MULTIRANGE_OP:
+			return 0.01;
+
+		case OID_MULTIRANGE_CONTAINS_RANGE_OP:
+		case OID_MULTIRANGE_CONTAINS_MULTIRANGE_OP:
+		case OID_MULTIRANGE_RANGE_CONTAINED_OP:
+		case OID_MULTIRANGE_MULTIRANGE_CONTAINED_OP:
+			return 0.005;
+
+		case OID_MULTIRANGE_CONTAINS_ELEM_OP:
+		case OID_MULTIRANGE_ELEM_CONTAINED_OP:
+
+			/*
+			 * "multirange @> elem" is more or less identical to a scalar
+			 * inequality "A >= b AND A <= c".
+			 */
+			return DEFAULT_MULTIRANGE_INEQ_SEL;
+
+		case OID_MULTIRANGE_LESS_OP:
+		case OID_MULTIRANGE_LESS_EQUAL_OP:
+		case OID_MULTIRANGE_GREATER_OP:
+		case OID_MULTIRANGE_GREATER_EQUAL_OP:
+		case OID_MULTIRANGE_LEFT_RANGE_OP:
+		case OID_MULTIRANGE_LEFT_MULTIRANGE_OP:
+		case OID_RANGE_LEFT_MULTIRANGE_OP:
+		case OID_MULTIRANGE_RIGHT_RANGE_OP:
+		case OID_MULTIRANGE_RIGHT_MULTIRANGE_OP:
+		case OID_RANGE_RIGHT_MULTIRANGE_OP:
+		case OID_MULTIRANGE_OVERLAPS_LEFT_RANGE_OP:
+		case OID_RANGE_OVERLAPS_LEFT_MULTIRANGE_OP:
+		case OID_MULTIRANGE_OVERLAPS_LEFT_MULTIRANGE_OP:
+		case OID_MULTIRANGE_OVERLAPS_RIGHT_RANGE_OP:
+		case OID_RANGE_OVERLAPS_RIGHT_MULTIRANGE_OP:
+		case OID_MULTIRANGE_OVERLAPS_RIGHT_MULTIRANGE_OP:
+			/* these are similar to regular scalar inequalities */
+			return DEFAULT_INEQ_SEL;
+
+		default:
+
+			/*
+			 * all multirange operators should be handled above, but just in
+			 * case
+			 */
+			return 0.01;
+	}
+}
+
+/*
+ * multirangesel -- restriction selectivity for multirange operators
+ */
+Datum
+multirangesel(PG_FUNCTION_ARGS)
+{
+	PlannerInfo *root = (PlannerInfo *) PG_GETARG_POINTER(0);
+	Oid			operator = PG_GETARG_OID(1);
+	List	   *args = (List *) PG_GETARG_POINTER(2);
+	int			varRelid = PG_GETARG_INT32(3);
+	VariableStatData vardata;
+	Node	   *other;
+	bool		varonleft;
+	Selectivity selec;
+	TypeCacheEntry *typcache = NULL;
+	MultirangeType *constmultirange = NULL;
+	RangeType  *constrange = NULL;
+
+	/*
+	 * If expression is not (variable op something) or (something op
+	 * variable), then punt and return a default estimate.
+	 */
+	if (!get_restriction_variable(root, args, varRelid,
+								  &vardata, &other, &varonleft))
+		PG_RETURN_FLOAT8(default_multirange_selectivity(operator));
+
+	/*
+	 * Can't do anything useful if the something is not a constant, either.
+	 */
+	if (!IsA(other, Const))
+	{
+		ReleaseVariableStats(vardata);
+		PG_RETURN_FLOAT8(default_multirange_selectivity(operator));
+	}
+
+	/*
+	 * All the multirange operators are strict, so we can cope with a NULL
+	 * constant right away.
+	 */
+	if (((Const *) other)->constisnull)
+	{
+		ReleaseVariableStats(vardata);
+		PG_RETURN_FLOAT8(0.0);
+	}
+
+	/*
+	 * If var is on the right, commute the operator, so that we can assume the
+	 * var is on the left in what follows.
+	 */
+	if (!varonleft)
+	{
+		/* we have other Op var, commute to make var Op other */
+		operator = get_commutator(operator);
+		if (!operator)
+		{
+			/* Use default selectivity (should we raise an error instead?) */
+			ReleaseVariableStats(vardata);
+			PG_RETURN_FLOAT8(default_multirange_selectivity(operator));
+		}
+	}
+
+	/*
+	 * OK, there's a Var and a Const we're dealing with here.  We need the
+	 * Const to be of same multirange type as the column, else we can't do
+	 * anything useful. (Such cases will likely fail at runtime, but here we'd
+	 * rather just return a default estimate.)
+	 *
+	 * If the operator is "multirange @> element", the constant should be of
+	 * the element type of the multirange column. Convert it to a multirange
+	 * that includes only that single point, so that we don't need special
+	 * handling for that in what follows.
+	 */
+	if (operator == OID_MULTIRANGE_CONTAINS_ELEM_OP)
+	{
+		typcache = multirange_get_typcache(fcinfo, vardata.vartype);
+
+		if (((Const *) other)->consttype == typcache->rngtype->rngelemtype->type_id)
+		{
+			RangeBound	lower,
+						upper;
+
+			lower.inclusive = true;
+			lower.val = ((Const *) other)->constvalue;
+			lower.infinite = false;
+			lower.lower = true;
+			upper.inclusive = true;
+			upper.val = ((Const *) other)->constvalue;
+			upper.infinite = false;
+			upper.lower = false;
+			constrange = range_serialize(typcache->rngtype, &lower, &upper, false);
+			constmultirange = make_multirange(typcache->type_id, typcache->rngtype,
+											  1, &constrange);
+		}
+	}
+	else if (operator == OID_MULTIRANGE_CONTAINS_RANGE_OP ||
+			 operator == OID_MULTIRANGE_OVERLAPS_RANGE_OP ||
+			 operator == OID_MULTIRANGE_OVERLAPS_LEFT_RANGE_OP ||
+			 operator == OID_MULTIRANGE_OVERLAPS_RIGHT_RANGE_OP ||
+			 operator == OID_MULTIRANGE_LEFT_RANGE_OP ||
+			 operator == OID_MULTIRANGE_RIGHT_RANGE_OP)
+	{
+		/*
+		 * Promote a range in "multirange OP range" just like we do an element
+		 * in "multirange OP element".
+		 */
+		typcache = multirange_get_typcache(fcinfo, vardata.vartype);
+		if (((Const *) other)->consttype == typcache->rngtype->type_id)
+		{
+			constrange = DatumGetRangeTypeP(((Const *) other)->constvalue);
+			constmultirange = make_multirange(typcache->type_id, typcache->rngtype,
+											  1, &constrange);
+		}
+	}
+	else if (operator == OID_RANGE_OVERLAPS_MULTIRANGE_OP ||
+			 operator == OID_RANGE_OVERLAPS_LEFT_MULTIRANGE_OP ||
+			 operator == OID_RANGE_OVERLAPS_RIGHT_MULTIRANGE_OP ||
+			 operator == OID_RANGE_LEFT_MULTIRANGE_OP ||
+			 operator == OID_RANGE_RIGHT_MULTIRANGE_OP ||
+			 operator == OID_MULTIRANGE_ELEM_CONTAINED_OP ||
+			 operator == OID_MULTIRANGE_RANGE_CONTAINED_OP)
+	{
+		/*
+		 * Here, the Var is the elem/range, not the multirange.  For now we
+		 * just punt and return the default estimate.  In future we could
+		 * disassemble the multirange constant to do something more
+		 * intelligent.
+		 */
+	}
+	else if (((Const *) other)->consttype == vardata.vartype)
+	{
+		/* Both sides are the same multirange type */
+		typcache = multirange_get_typcache(fcinfo, vardata.vartype);
+
+		constmultirange = DatumGetMultirangeTypeP(((Const *) other)->constvalue);
+	}
+
+	/*
+	 * If we got a valid constant on one side of the operator, proceed to
+	 * estimate using statistics. Otherwise punt and return a default constant
+	 * estimate.  Note that calc_multirangesel need not handle
+	 * OID_MULTIRANGE_*_CONTAINED_OP.
+	 */
+	if (constmultirange)
+		selec = calc_multirangesel(typcache, &vardata, constmultirange, operator);
+	else
+		selec = default_multirange_selectivity(operator);
+
+	ReleaseVariableStats(vardata);
+
+	CLAMP_PROBABILITY(selec);
+
+	PG_RETURN_FLOAT8((float8) selec);
+}
+
+static double
+calc_multirangesel(TypeCacheEntry *typcache, VariableStatData *vardata,
+				   const MultirangeType *constval, Oid operator)
+{
+	double		hist_selec;
+	double		selec;
+	float4		empty_frac,
+				null_frac;
+
+	/*
+	 * First look up the fraction of NULLs and empty multiranges from
+	 * pg_statistic.
+	 */
+	if (HeapTupleIsValid(vardata->statsTuple))
+	{
+		Form_pg_statistic stats;
+		AttStatsSlot sslot;
+
+		stats = (Form_pg_statistic) GETSTRUCT(vardata->statsTuple);
+		null_frac = stats->stanullfrac;
+
+		/* Try to get fraction of empty multiranges */
+		if (get_attstatsslot(&sslot, vardata->statsTuple,
+							 STATISTIC_KIND_RANGE_LENGTH_HISTOGRAM,
+							 InvalidOid,
+							 ATTSTATSSLOT_NUMBERS))
+		{
+			if (sslot.nnumbers != 1)
+				elog(ERROR, "invalid empty fraction statistic");	/* shouldn't happen */
+			empty_frac = sslot.numbers[0];
+			free_attstatsslot(&sslot);
+		}
+		else
+		{
+			/* No empty fraction statistic. Assume no empty ranges. */
+			empty_frac = 0.0;
+		}
+	}
+	else
+	{
+		/*
+		 * No stats are available. Follow through the calculations below
+		 * anyway, assuming no NULLs and no empty multiranges. This still
+		 * allows us to give a better-than-nothing estimate based on whether
+		 * the constant is an empty multirange or not.
+		 */
+		null_frac = 0.0;
+		empty_frac = 0.0;
+	}
+
+	if (MultirangeIsEmpty(constval))
+	{
+		/*
+		 * An empty multirange matches all multiranges, all empty multiranges,
+		 * or nothing, depending on the operator
+		 */
+		switch (operator)
+		{
+				/* these return false if either argument is empty */
+			case OID_RANGE_OVERLAPS_MULTIRANGE_OP:
+			case OID_MULTIRANGE_OVERLAPS_RANGE_OP:
+			case OID_MULTIRANGE_OVERLAPS_MULTIRANGE_OP:
+			case OID_RANGE_OVERLAPS_LEFT_MULTIRANGE_OP:
+			case OID_MULTIRANGE_OVERLAPS_LEFT_RANGE_OP:
+			case OID_MULTIRANGE_OVERLAPS_LEFT_MULTIRANGE_OP:
+			case OID_RANGE_OVERLAPS_RIGHT_MULTIRANGE_OP:
+			case OID_MULTIRANGE_OVERLAPS_RIGHT_RANGE_OP:
+			case OID_MULTIRANGE_OVERLAPS_RIGHT_MULTIRANGE_OP:
+			case OID_MULTIRANGE_LEFT_MULTIRANGE_OP:
+			case OID_MULTIRANGE_RIGHT_MULTIRANGE_OP:
+				/* nothing is less than an empty multirange */
+			case OID_MULTIRANGE_LESS_OP:
+				selec = 0.0;
+				break;
+
+				/*
+				 * only empty multiranges can be contained by an empty
+				 * multirange
+				 */
+			case OID_MULTIRANGE_RANGE_CONTAINED_OP:
+			case OID_MULTIRANGE_MULTIRANGE_CONTAINED_OP:
+				/* only empty ranges are <= an empty multirange */
+			case OID_MULTIRANGE_LESS_EQUAL_OP:
+				selec = empty_frac;
+				break;
+
+				/* everything contains an empty multirange */
+			case OID_MULTIRANGE_CONTAINS_RANGE_OP:
+			case OID_MULTIRANGE_CONTAINS_MULTIRANGE_OP:
+				/* everything is >= an empty multirange */
+			case OID_MULTIRANGE_GREATER_EQUAL_OP:
+				selec = 1.0;
+				break;
+
+				/* all non-empty multiranges are > an empty multirange */
+			case OID_MULTIRANGE_GREATER_OP:
+				selec = 1.0 - empty_frac;
+				break;
+
+				/* an element cannot be empty */
+			case OID_MULTIRANGE_ELEM_CONTAINED_OP:
+			case OID_MULTIRANGE_CONTAINS_ELEM_OP:
+			default:
+				elog(ERROR, "unexpected operator %u", operator);
+				selec = 0.0;	/* keep compiler quiet */
+				break;
+		}
+	}
+	else
+	{
+		/*
+		 * Calculate selectivity using bound histograms. If that fails for
+		 * some reason, e.g no histogram in pg_statistic, use the default
+		 * constant estimate for the fraction of non-empty values. This is
+		 * still somewhat better than just returning the default estimate,
+		 * because this still takes into account the fraction of empty and
+		 * NULL tuples, if we had statistics for them.
+		 */
+		hist_selec = calc_hist_selectivity(typcache, vardata, constval,
+										   operator);
+		if (hist_selec < 0.0)
+			hist_selec = default_multirange_selectivity(operator);
+
+		/*
+		 * Now merge the results for the empty multiranges and histogram
+		 * calculations, realizing that the histogram covers only the
+		 * non-null, non-empty values.
+		 */
+		if (operator == OID_MULTIRANGE_ELEM_CONTAINED_OP ||
+			operator == OID_MULTIRANGE_RANGE_CONTAINED_OP ||
+			operator == OID_MULTIRANGE_MULTIRANGE_CONTAINED_OP)
+		{
+			/* empty is contained by anything non-empty */
+			selec = (1.0 - empty_frac) * hist_selec + empty_frac;
+		}
+		else
+		{
+			/* with any other operator, empty Op non-empty matches nothing */
+			selec = (1.0 - empty_frac) * hist_selec;
+		}
+	}
+
+	/* all multirange operators are strict */
+	selec *= (1.0 - null_frac);
+
+	/* result should be in range, but make sure... */
+	CLAMP_PROBABILITY(selec);
+
+	return selec;
+}
+
+/*
+ * Calculate multirange operator selectivity using histograms of multirange bounds.
+ *
+ * This estimate is for the portion of values that are not empty and not
+ * NULL.
+ */
+static double
+calc_hist_selectivity(TypeCacheEntry *typcache, VariableStatData *vardata,
+					  const MultirangeType *constval, Oid operator)
+{
+	TypeCacheEntry *rng_typcache = typcache->rngtype;
+	AttStatsSlot hslot;
+	AttStatsSlot lslot;
+	int			nhist;
+	RangeBound *hist_lower;
+	RangeBound *hist_upper;
+	int			i;
+	RangeBound	const_lower;
+	RangeBound	const_upper;
+	bool		empty;
+	double		hist_selec;
+	int			range_count;
+	RangeType **ranges;
+	RangeType  *constrange;
+
+	/* Can't use the histogram with insecure multirange support functions */
+	if (!statistic_proc_security_check(vardata,
+									   rng_typcache->rng_cmp_proc_finfo.fn_oid))
+		return -1;
+	if (OidIsValid(rng_typcache->rng_subdiff_finfo.fn_oid) &&
+		!statistic_proc_security_check(vardata,
+									   rng_typcache->rng_subdiff_finfo.fn_oid))
+		return -1;
+
+	/* Try to get histogram of ranges */
+	if (!(HeapTupleIsValid(vardata->statsTuple) &&
+		  get_attstatsslot(&hslot, vardata->statsTuple,
+						   STATISTIC_KIND_BOUNDS_HISTOGRAM, InvalidOid,
+						   ATTSTATSSLOT_VALUES)))
+		return -1.0;
+
+	/* check that it's a histogram, not just a dummy entry */
+	if (hslot.nvalues < 2)
+	{
+		free_attstatsslot(&hslot);
+		return -1.0;
+	}
+
+	/*
+	 * Convert histogram of ranges into histograms of its lower and upper
+	 * bounds.
+	 */
+	nhist = hslot.nvalues;
+	hist_lower = (RangeBound *) palloc(sizeof(RangeBound) * nhist);
+	hist_upper = (RangeBound *) palloc(sizeof(RangeBound) * nhist);
+	for (i = 0; i < nhist; i++)
+	{
+		range_deserialize(rng_typcache, DatumGetRangeTypeP(hslot.values[i]),
+						  &hist_lower[i], &hist_upper[i], &empty);
+		/* The histogram should not contain any empty ranges */
+		if (empty)
+			elog(ERROR, "bounds histogram contains an empty range");
+	}
+
+	/* @> and @< also need a histogram of range lengths */
+	if (operator == OID_MULTIRANGE_CONTAINS_RANGE_OP ||
+		operator == OID_MULTIRANGE_CONTAINS_MULTIRANGE_OP ||
+		operator == OID_MULTIRANGE_RANGE_CONTAINED_OP ||
+		operator == OID_MULTIRANGE_MULTIRANGE_CONTAINED_OP)
+	{
+		if (!(HeapTupleIsValid(vardata->statsTuple) &&
+			  get_attstatsslot(&lslot, vardata->statsTuple,
+							   STATISTIC_KIND_RANGE_LENGTH_HISTOGRAM,
+							   InvalidOid,
+							   ATTSTATSSLOT_VALUES)))
+		{
+			free_attstatsslot(&hslot);
+			return -1.0;
+		}
+
+		/* check that it's a histogram, not just a dummy entry */
+		if (lslot.nvalues < 2)
+		{
+			free_attstatsslot(&lslot);
+			free_attstatsslot(&hslot);
+			return -1.0;
+		}
+	}
+	else
+		memset(&lslot, 0, sizeof(lslot));
+
+	/* Extract the bounds of the constant value. */
+	multirange_deserialize(rng_typcache, constval, &range_count, &ranges);
+	Assert(range_count > 0);
+	constrange = range_union_internal(rng_typcache, ranges[0],
+									  ranges[range_count - 1], false);
+	range_deserialize(rng_typcache, constrange, &const_lower, &const_upper, &empty);
+
+	/*
+	 * Calculate selectivity comparing the lower or upper bound of the
+	 * constant with the histogram of lower or upper bounds.
+	 */
+	switch (operator)
+	{
+		case OID_MULTIRANGE_LESS_OP:
+
+			/*
+			 * The regular b-tree comparison operators (<, <=, >, >=) compare
+			 * the lower bounds first, and the upper bounds for values with
+			 * equal lower bounds. Estimate that by comparing the lower bounds
+			 * only. This gives a fairly accurate estimate assuming there
+			 * aren't many rows with a lower bound equal to the constant's
+			 * lower bound.
+			 */
+			hist_selec =
+				calc_hist_selectivity_scalar(rng_typcache, &const_lower,
+											 hist_lower, nhist, false);
+			break;
+
+		case OID_MULTIRANGE_LESS_EQUAL_OP:
+			hist_selec =
+				calc_hist_selectivity_scalar(rng_typcache, &const_lower,
+											 hist_lower, nhist, true);
+			break;
+
+		case OID_MULTIRANGE_GREATER_OP:
+			hist_selec =
+				1 - calc_hist_selectivity_scalar(rng_typcache, &const_lower,
+												 hist_lower, nhist, false);
+			break;
+
+		case OID_MULTIRANGE_GREATER_EQUAL_OP:
+			hist_selec =
+				1 - calc_hist_selectivity_scalar(rng_typcache, &const_lower,
+												 hist_lower, nhist, true);
+			break;
+
+		case OID_RANGE_LEFT_MULTIRANGE_OP:
+		case OID_MULTIRANGE_LEFT_RANGE_OP:
+		case OID_MULTIRANGE_LEFT_MULTIRANGE_OP:
+			/* var << const when upper(var) < lower(const) */
+			hist_selec =
+				calc_hist_selectivity_scalar(rng_typcache, &const_lower,
+											 hist_upper, nhist, false);
+			break;
+
+		case OID_RANGE_RIGHT_MULTIRANGE_OP:
+		case OID_MULTIRANGE_RIGHT_RANGE_OP:
+		case OID_MULTIRANGE_RIGHT_MULTIRANGE_OP:
+			/* var >> const when lower(var) > upper(const) */
+			hist_selec =
+				1 - calc_hist_selectivity_scalar(rng_typcache, &const_upper,
+												 hist_lower, nhist, true);
+			break;
+
+		case OID_RANGE_OVERLAPS_RIGHT_MULTIRANGE_OP:
+		case OID_MULTIRANGE_OVERLAPS_RIGHT_RANGE_OP:
+		case OID_MULTIRANGE_OVERLAPS_RIGHT_MULTIRANGE_OP:
+			/* compare lower bounds */
+			hist_selec =
+				1 - calc_hist_selectivity_scalar(rng_typcache, &const_lower,
+												 hist_lower, nhist, false);
+			break;
+
+		case OID_RANGE_OVERLAPS_LEFT_MULTIRANGE_OP:
+		case OID_MULTIRANGE_OVERLAPS_LEFT_RANGE_OP:
+		case OID_MULTIRANGE_OVERLAPS_LEFT_MULTIRANGE_OP:
+			/* compare upper bounds */
+			hist_selec =
+				calc_hist_selectivity_scalar(rng_typcache, &const_upper,
+											 hist_upper, nhist, true);
+			break;
+
+		case OID_RANGE_OVERLAPS_MULTIRANGE_OP:
+		case OID_MULTIRANGE_OVERLAPS_RANGE_OP:
+		case OID_MULTIRANGE_OVERLAPS_MULTIRANGE_OP:
+		case OID_MULTIRANGE_CONTAINS_ELEM_OP:
+
+			/*
+			 * A && B <=> NOT (A << B OR A >> B).
+			 *
+			 * Since A << B and A >> B are mutually exclusive events we can
+			 * sum their probabilities to find probability of (A << B OR A >>
+			 * B).
+			 *
+			 * "multirange @> elem" is equivalent to "multirange &&
+			 * {[elem,elem]}". The caller already constructed the singular
+			 * range from the element constant, so just treat it the same as
+			 * &&.
+			 */
+			hist_selec =
+				calc_hist_selectivity_scalar(rng_typcache, &const_lower, hist_upper,
+											 nhist, false);
+			hist_selec +=
+				(1.0 - calc_hist_selectivity_scalar(rng_typcache, &const_upper, hist_lower,
+													nhist, true));
+			hist_selec = 1.0 - hist_selec;
+			break;
+
+		case OID_MULTIRANGE_CONTAINS_RANGE_OP:
+		case OID_MULTIRANGE_CONTAINS_MULTIRANGE_OP:
+			hist_selec =
+				calc_hist_selectivity_contains(rng_typcache, &const_lower,
+											   &const_upper, hist_lower, nhist,
+											   lslot.values, lslot.nvalues);
+			break;
+
+		case OID_MULTIRANGE_RANGE_CONTAINED_OP:
+		case OID_MULTIRANGE_MULTIRANGE_CONTAINED_OP:
+			if (const_lower.infinite)
+			{
+				/*
+				 * Lower bound no longer matters. Just estimate the fraction
+				 * with an upper bound <= const upper bound
+				 */
+				hist_selec =
+					calc_hist_selectivity_scalar(rng_typcache, &const_upper,
+												 hist_upper, nhist, true);
+			}
+			else if (const_upper.infinite)
+			{
+				hist_selec =
+					1.0 - calc_hist_selectivity_scalar(rng_typcache, &const_lower,
+													   hist_lower, nhist, false);
+			}
+			else
+			{
+				hist_selec =
+					calc_hist_selectivity_contained(rng_typcache, &const_lower,
+													&const_upper, hist_lower, nhist,
+													lslot.values, lslot.nvalues);
+			}
+			break;
+
+		default:
+			elog(ERROR, "unknown multirange operator %u", operator);
+			hist_selec = -1.0;	/* keep compiler quiet */
+			break;
+	}
+
+	free_attstatsslot(&lslot);
+	free_attstatsslot(&hslot);
+
+	return hist_selec;
+}
+
+
+/*
+ * Look up the fraction of values less than (or equal, if 'equal' argument
+ * is true) a given const in a histogram of range bounds.
+ */
+static double
+calc_hist_selectivity_scalar(TypeCacheEntry *typcache, const RangeBound *constbound,
+							 const RangeBound *hist, int hist_nvalues, bool equal)
+{
+	Selectivity selec;
+	int			index;
+
+	/*
+	 * Find the histogram bin the given constant falls into. Estimate
+	 * selectivity as the number of preceding whole bins.
+	 */
+	index = rbound_bsearch(typcache, constbound, hist, hist_nvalues, equal);
+	selec = (Selectivity) (Max(index, 0)) / (Selectivity) (hist_nvalues - 1);
+
+	/* Adjust using linear interpolation within the bin */
+	if (index >= 0 && index < hist_nvalues - 1)
+		selec += get_position(typcache, constbound, &hist[index],
+							  &hist[index + 1]) / (Selectivity) (hist_nvalues - 1);
+
+	return selec;
+}
+
+/*
+ * Binary search on an array of range bounds. Returns greatest index of range
+ * bound in array which is less(less or equal) than given range bound. If all
+ * range bounds in array are greater or equal(greater) than given range bound,
+ * return -1. When "equal" flag is set conditions in brackets are used.
+ *
+ * This function is used in scalar operator selectivity estimation. Another
+ * goal of this function is to find a histogram bin where to stop
+ * interpolation of portion of bounds which are less or equal to given bound.
+ */
+static int
+rbound_bsearch(TypeCacheEntry *typcache, const RangeBound *value, const RangeBound *hist,
+			   int hist_length, bool equal)
+{
+	int			lower = -1,
+				upper = hist_length - 1,
+				cmp,
+				middle;
+
+	while (lower < upper)
+	{
+		middle = (lower + upper + 1) / 2;
+		cmp = range_cmp_bounds(typcache, &hist[middle], value);
+
+		if (cmp < 0 || (equal && cmp == 0))
+			lower = middle;
+		else
+			upper = middle - 1;
+	}
+	return lower;
+}
+
+
+/*
+ * Binary search on length histogram. Returns greatest index of range length in
+ * histogram which is less than (less than or equal) the given length value. If
+ * all lengths in the histogram are greater than (greater than or equal) the
+ * given length, returns -1.
+ */
+static int
+length_hist_bsearch(Datum *length_hist_values, int length_hist_nvalues,
+					double value, bool equal)
+{
+	int			lower = -1,
+				upper = length_hist_nvalues - 1,
+				middle;
+
+	while (lower < upper)
+	{
+		double		middleval;
+
+		middle = (lower + upper + 1) / 2;
+
+		middleval = DatumGetFloat8(length_hist_values[middle]);
+		if (middleval < value || (equal && middleval <= value))
+			lower = middle;
+		else
+			upper = middle - 1;
+	}
+	return lower;
+}
+
+/*
+ * Get relative position of value in histogram bin in [0,1] range.
+ */
+static float8
+get_position(TypeCacheEntry *typcache, const RangeBound *value, const RangeBound *hist1,
+			 const RangeBound *hist2)
+{
+	bool		has_subdiff = OidIsValid(typcache->rng_subdiff_finfo.fn_oid);
+	float8		position;
+
+	if (!hist1->infinite && !hist2->infinite)
+	{
+		float8		bin_width;
+
+		/*
+		 * Both bounds are finite. Assuming the subtype's comparison function
+		 * works sanely, the value must be finite, too, because it lies
+		 * somewhere between the bounds.  If it doesn't, arbitrarily return
+		 * 0.5.
+		 */
+		if (value->infinite)
+			return 0.5;
+
+		/* Can't interpolate without subdiff function */
+		if (!has_subdiff)
+			return 0.5;
+
+		/* Calculate relative position using subdiff function. */
+		bin_width = DatumGetFloat8(FunctionCall2Coll(&typcache->rng_subdiff_finfo,
+													 typcache->rng_collation,
+													 hist2->val,
+													 hist1->val));
+		if (isnan(bin_width) || bin_width <= 0.0)
+			return 0.5;			/* punt for NaN or zero-width bin */
+
+		position = DatumGetFloat8(FunctionCall2Coll(&typcache->rng_subdiff_finfo,
+													typcache->rng_collation,
+													value->val,
+													hist1->val))
+			/ bin_width;
+
+		if (isnan(position))
+			return 0.5;			/* punt for NaN from subdiff, Inf/Inf, etc */
+
+		/* Relative position must be in [0,1] range */
+		position = Max(position, 0.0);
+		position = Min(position, 1.0);
+		return position;
+	}
+	else if (hist1->infinite && !hist2->infinite)
+	{
+		/*
+		 * Lower bin boundary is -infinite, upper is finite. If the value is
+		 * -infinite, return 0.0 to indicate it's equal to the lower bound.
+		 * Otherwise return 1.0 to indicate it's infinitely far from the lower
+		 * bound.
+		 */
+		return ((value->infinite && value->lower) ? 0.0 : 1.0);
+	}
+	else if (!hist1->infinite && hist2->infinite)
+	{
+		/* same as above, but in reverse */
+		return ((value->infinite && !value->lower) ? 1.0 : 0.0);
+	}
+	else
+	{
+		/*
+		 * If both bin boundaries are infinite, they should be equal to each
+		 * other, and the value should also be infinite and equal to both
+		 * bounds. (But don't Assert that, to avoid crashing if a user creates
+		 * a datatype with a broken comparison function).
+		 *
+		 * Assume the value to lie in the middle of the infinite bounds.
+		 */
+		return 0.5;
+	}
+}
+
+
+/*
+ * Get relative position of value in a length histogram bin in [0,1] range.
+ */
+static double
+get_len_position(double value, double hist1, double hist2)
+{
+	if (!isinf(hist1) && !isinf(hist2))
+	{
+		/*
+		 * Both bounds are finite. The value should be finite too, because it
+		 * lies somewhere between the bounds. If it doesn't, just return
+		 * something.
+		 */
+		if (isinf(value))
+			return 0.5;
+
+		return 1.0 - (hist2 - value) / (hist2 - hist1);
+	}
+	else if (isinf(hist1) && !isinf(hist2))
+	{
+		/*
+		 * Lower bin boundary is -infinite, upper is finite. Return 1.0 to
+		 * indicate the value is infinitely far from the lower bound.
+		 */
+		return 1.0;
+	}
+	else if (isinf(hist1) && isinf(hist2))
+	{
+		/* same as above, but in reverse */
+		return 0.0;
+	}
+	else
+	{
+		/*
+		 * If both bin boundaries are infinite, they should be equal to each
+		 * other, and the value should also be infinite and equal to both
+		 * bounds. (But don't Assert that, to avoid crashing unnecessarily if
+		 * the caller messes up)
+		 *
+		 * Assume the value to lie in the middle of the infinite bounds.
+		 */
+		return 0.5;
+	}
+}
+
+/*
+ * Measure distance between two range bounds.
+ */
+static float8
+get_distance(TypeCacheEntry *typcache, const RangeBound *bound1, const RangeBound *bound2)
+{
+	bool		has_subdiff = OidIsValid(typcache->rng_subdiff_finfo.fn_oid);
+
+	if (!bound1->infinite && !bound2->infinite)
+	{
+		/*
+		 * Neither bound is infinite, use subdiff function or return default
+		 * value of 1.0 if no subdiff is available.
+		 */
+		if (has_subdiff)
+		{
+			float8		res;
+
+			res = DatumGetFloat8(FunctionCall2Coll(&typcache->rng_subdiff_finfo,
+												   typcache->rng_collation,
+												   bound2->val,
+												   bound1->val));
+			/* Reject possible NaN result, also negative result */
+			if (isnan(res) || res < 0.0)
+				return 1.0;
+			else
+				return res;
+		}
+		else
+			return 1.0;
+	}
+	else if (bound1->infinite && bound2->infinite)
+	{
+		/* Both bounds are infinite */
+		if (bound1->lower == bound2->lower)
+			return 0.0;
+		else
+			return get_float8_infinity();
+	}
+	else
+	{
+		/* One bound is infinite, the other is not */
+		return get_float8_infinity();
+	}
+}
+
+/*
+ * Calculate the average of function P(x), in the interval [length1, length2],
+ * where P(x) is the fraction of tuples with length < x (or length <= x if
+ * 'equal' is true).
+ */
+static double
+calc_length_hist_frac(Datum *length_hist_values, int length_hist_nvalues,
+					  double length1, double length2, bool equal)
+{
+	double		frac;
+	double		A,
+				B,
+				PA,
+				PB;
+	double		pos;
+	int			i;
+	double		area;
+
+	Assert(length2 >= length1);
+
+	if (length2 < 0.0)
+		return 0.0;				/* shouldn't happen, but doesn't hurt to check */
+
+	/* All lengths in the table are <= infinite. */
+	if (isinf(length2) && equal)
+		return 1.0;
+
+	/*----------
+	 * The average of a function between A and B can be calculated by the
+	 * formula:
+	 *
+	 *			B
+	 *	  1		/
+	 * -------	| P(x)dx
+	 *	B - A	/
+	 *			A
+	 *
+	 * The geometrical interpretation of the integral is the area under the
+	 * graph of P(x). P(x) is defined by the length histogram. We calculate
+	 * the area in a piecewise fashion, iterating through the length histogram
+	 * bins. Each bin is a trapezoid:
+	 *
+	 *		 P(x2)
+	 *		  /|
+	 *		 / |
+	 * P(x1)/  |
+	 *	   |   |
+	 *	   |   |
+	 *	---+---+--
+	 *	   x1  x2
+	 *
+	 * where x1 and x2 are the boundaries of the current histogram, and P(x1)
+	 * and P(x1) are the cumulative fraction of tuples at the boundaries.
+	 *
+	 * The area of each trapezoid is 1/2 * (P(x2) + P(x1)) * (x2 - x1)
+	 *
+	 * The first bin contains the lower bound passed by the caller, so we
+	 * use linear interpolation between the previous and next histogram bin
+	 * boundary to calculate P(x1). Likewise for the last bin: we use linear
+	 * interpolation to calculate P(x2). For the bins in between, x1 and x2
+	 * lie on histogram bin boundaries, so P(x1) and P(x2) are simply:
+	 * P(x1) =	  (bin index) / (number of bins)
+	 * P(x2) = (bin index + 1 / (number of bins)
+	 */
+
+	/* First bin, the one that contains lower bound */
+	i = length_hist_bsearch(length_hist_values, length_hist_nvalues, length1, equal);
+	if (i >= length_hist_nvalues - 1)
+		return 1.0;
+
+	if (i < 0)
+	{
+		i = 0;
+		pos = 0.0;
+	}
+	else
+	{
+		/* interpolate length1's position in the bin */
+		pos = get_len_position(length1,
+							   DatumGetFloat8(length_hist_values[i]),
+							   DatumGetFloat8(length_hist_values[i + 1]));
+	}
+	PB = (((double) i) + pos) / (double) (length_hist_nvalues - 1);
+	B = length1;
+
+	/*
+	 * In the degenerate case that length1 == length2, simply return
+	 * P(length1). This is not merely an optimization: if length1 == length2,
+	 * we'd divide by zero later on.
+	 */
+	if (length2 == length1)
+		return PB;
+
+	/*
+	 * Loop through all the bins, until we hit the last bin, the one that
+	 * contains the upper bound. (if lower and upper bounds are in the same
+	 * bin, this falls out immediately)
+	 */
+	area = 0.0;
+	for (; i < length_hist_nvalues - 1; i++)
+	{
+		double		bin_upper = DatumGetFloat8(length_hist_values[i + 1]);
+
+		/* check if we've reached the last bin */
+		if (!(bin_upper < length2 || (equal && bin_upper <= length2)))
+			break;
+
+		/* the upper bound of previous bin is the lower bound of this bin */
+		A = B;
+		PA = PB;
+
+		B = bin_upper;
+		PB = (double) i / (double) (length_hist_nvalues - 1);
+
+		/*
+		 * Add the area of this trapezoid to the total. The point of the
+		 * if-check is to avoid NaN, in the corner case that PA == PB == 0,
+		 * and B - A == Inf. The area of a zero-height trapezoid (PA == PB ==
+		 * 0) is zero, regardless of the width (B - A).
+		 */
+		if (PA > 0 || PB > 0)
+			area += 0.5 * (PB + PA) * (B - A);
+	}
+
+	/* Last bin */
+	A = B;
+	PA = PB;
+
+	B = length2;				/* last bin ends at the query upper bound */
+	if (i >= length_hist_nvalues - 1)
+		pos = 0.0;
+	else
+	{
+		if (DatumGetFloat8(length_hist_values[i]) == DatumGetFloat8(length_hist_values[i + 1]))
+			pos = 0.0;
+		else
+			pos = get_len_position(length2, DatumGetFloat8(length_hist_values[i]), DatumGetFloat8(length_hist_values[i + 1]));
+	}
+	PB = (((double) i) + pos) / (double) (length_hist_nvalues - 1);
+
+	if (PA > 0 || PB > 0)
+		area += 0.5 * (PB + PA) * (B - A);
+
+	/*
+	 * Ok, we have calculated the area, ie. the integral. Divide by width to
+	 * get the requested average.
+	 *
+	 * Avoid NaN arising from infinite / infinite. This happens at least if
+	 * length2 is infinite. It's not clear what the correct value would be in
+	 * that case, so 0.5 seems as good as any value.
+	 */
+	if (isinf(area) && isinf(length2))
+		frac = 0.5;
+	else
+		frac = area / (length2 - length1);
+
+	return frac;
+}
+
+/*
+ * Calculate selectivity of "var <@ const" operator, ie. estimate the fraction
+ * of multiranges that fall within the constant lower and upper bounds. This uses
+ * the histograms of range lower bounds and range lengths, on the assumption
+ * that the range lengths are independent of the lower bounds.
+ *
+ * The caller has already checked that constant lower and upper bounds are
+ * finite.
+ */
+static double
+calc_hist_selectivity_contained(TypeCacheEntry *typcache,
+								const RangeBound *lower, RangeBound *upper,
+								const RangeBound *hist_lower, int hist_nvalues,
+								Datum *length_hist_values, int length_hist_nvalues)
+{
+	int			i,
+				upper_index;
+	float8		prev_dist;
+	double		bin_width;
+	double		upper_bin_width;
+	double		sum_frac;
+
+	/*
+	 * Begin by finding the bin containing the upper bound, in the lower bound
+	 * histogram. Any range with a lower bound > constant upper bound can't
+	 * match, ie. there are no matches in bins greater than upper_index.
+	 */
+	upper->inclusive = !upper->inclusive;
+	upper->lower = true;
+	upper_index = rbound_bsearch(typcache, upper, hist_lower, hist_nvalues,
+								 false);
+
+	/*
+	 * If the upper bound value is below the histogram's lower limit, there
+	 * are no matches.
+	 */
+	if (upper_index < 0)
+		return 0.0;
+
+	/*
+	 * If the upper bound value is at or beyond the histogram's upper limit,
+	 * start our loop at the last actual bin, as though the upper bound were
+	 * within that bin; get_position will clamp its result to 1.0 anyway.
+	 * (This corresponds to assuming that the data population above the
+	 * histogram's upper limit is empty, exactly like what we just assumed for
+	 * the lower limit.)
+	 */
+	upper_index = Min(upper_index, hist_nvalues - 2);
+
+	/*
+	 * Calculate upper_bin_width, ie. the fraction of the (upper_index,
+	 * upper_index + 1) bin which is greater than upper bound of query range
+	 * using linear interpolation of subdiff function.
+	 */
+	upper_bin_width = get_position(typcache, upper,
+								   &hist_lower[upper_index],
+								   &hist_lower[upper_index + 1]);
+
+	/*
+	 * In the loop, dist and prev_dist are the distance of the "current" bin's
+	 * lower and upper bounds from the constant upper bound.
+	 *
+	 * bin_width represents the width of the current bin. Normally it is 1.0,
+	 * meaning a full width bin, but can be less in the corner cases: start
+	 * and end of the loop. We start with bin_width = upper_bin_width, because
+	 * we begin at the bin containing the upper bound.
+	 */
+	prev_dist = 0.0;
+	bin_width = upper_bin_width;
+
+	sum_frac = 0.0;
+	for (i = upper_index; i >= 0; i--)
+	{
+		double		dist;
+		double		length_hist_frac;
+		bool		final_bin = false;
+
+		/*
+		 * dist -- distance from upper bound of query range to lower bound of
+		 * the current bin in the lower bound histogram. Or to the lower bound
+		 * of the constant range, if this is the final bin, containing the
+		 * constant lower bound.
+		 */
+		if (range_cmp_bounds(typcache, &hist_lower[i], lower) < 0)
+		{
+			dist = get_distance(typcache, lower, upper);
+
+			/*
+			 * Subtract from bin_width the portion of this bin that we want to
+			 * ignore.
+			 */
+			bin_width -= get_position(typcache, lower, &hist_lower[i],
+									  &hist_lower[i + 1]);
+			if (bin_width < 0.0)
+				bin_width = 0.0;
+			final_bin = true;
+		}
+		else
+			dist = get_distance(typcache, &hist_lower[i], upper);
+
+		/*
+		 * Estimate the fraction of tuples in this bin that are narrow enough
+		 * to not exceed the distance to the upper bound of the query range.
+		 */
+		length_hist_frac = calc_length_hist_frac(length_hist_values,
+												 length_hist_nvalues,
+												 prev_dist, dist, true);
+
+		/*
+		 * Add the fraction of tuples in this bin, with a suitable length, to
+		 * the total.
+		 */
+		sum_frac += length_hist_frac * bin_width / (double) (hist_nvalues - 1);
+
+		if (final_bin)
+			break;
+
+		bin_width = 1.0;
+		prev_dist = dist;
+	}
+
+	return sum_frac;
+}
+
+/*
+ * Calculate selectivity of "var @> const" operator, ie. estimate the fraction
+ * of multiranges that contain the constant lower and upper bounds. This uses
+ * the histograms of range lower bounds and range lengths, on the assumption
+ * that the range lengths are independent of the lower bounds.
+ */
+static double
+calc_hist_selectivity_contains(TypeCacheEntry *typcache,
+							   const RangeBound *lower, const RangeBound *upper,
+							   const RangeBound *hist_lower, int hist_nvalues,
+							   Datum *length_hist_values, int length_hist_nvalues)
+{
+	int			i,
+				lower_index;
+	double		bin_width,
+				lower_bin_width;
+	double		sum_frac;
+	float8		prev_dist;
+
+	/* Find the bin containing the lower bound of query range. */
+	lower_index = rbound_bsearch(typcache, lower, hist_lower, hist_nvalues,
+								 true);
+
+	/*
+	 * If the lower bound value is below the histogram's lower limit, there
+	 * are no matches.
+	 */
+	if (lower_index < 0)
+		return 0.0;
+
+	/*
+	 * If the lower bound value is at or beyond the histogram's upper limit,
+	 * start our loop at the last actual bin, as though the upper bound were
+	 * within that bin; get_position will clamp its result to 1.0 anyway.
+	 * (This corresponds to assuming that the data population above the
+	 * histogram's upper limit is empty, exactly like what we just assumed for
+	 * the lower limit.)
+	 */
+	lower_index = Min(lower_index, hist_nvalues - 2);
+
+	/*
+	 * Calculate lower_bin_width, ie. the fraction of the of (lower_index,
+	 * lower_index + 1) bin which is greater than lower bound of query range
+	 * using linear interpolation of subdiff function.
+	 */
+	lower_bin_width = get_position(typcache, lower, &hist_lower[lower_index],
+								   &hist_lower[lower_index + 1]);
+
+	/*
+	 * Loop through all the lower bound bins, smaller than the query lower
+	 * bound. In the loop, dist and prev_dist are the distance of the
+	 * "current" bin's lower and upper bounds from the constant upper bound.
+	 * We begin from query lower bound, and walk backwards, so the first bin's
+	 * upper bound is the query lower bound, and its distance to the query
+	 * upper bound is the length of the query range.
+	 *
+	 * bin_width represents the width of the current bin. Normally it is 1.0,
+	 * meaning a full width bin, except for the first bin, which is only
+	 * counted up to the constant lower bound.
+	 */
+	prev_dist = get_distance(typcache, lower, upper);
+	sum_frac = 0.0;
+	bin_width = lower_bin_width;
+	for (i = lower_index; i >= 0; i--)
+	{
+		float8		dist;
+		double		length_hist_frac;
+
+		/*
+		 * dist -- distance from upper bound of query range to current value
+		 * of lower bound histogram or lower bound of query range (if we've
+		 * reach it).
+		 */
+		dist = get_distance(typcache, &hist_lower[i], upper);
+
+		/*
+		 * Get average fraction of length histogram which covers intervals
+		 * longer than (or equal to) distance to upper bound of query range.
+		 */
+		length_hist_frac =
+			1.0 - calc_length_hist_frac(length_hist_values,
+										length_hist_nvalues,
+										prev_dist, dist, false);
+
+		sum_frac += length_hist_frac * bin_width / (double) (hist_nvalues - 1);
+
+		bin_width = 1.0;
+		prev_dist = dist;
+	}
+
+	return sum_frac;
+}
diff --git a/src/backend/utils/adt/pg_upgrade_support.c b/src/backend/utils/adt/pg_upgrade_support.c
index 8a5953881ee..521ebaaab6d 100644
--- a/src/backend/utils/adt/pg_upgrade_support.c
+++ b/src/backend/utils/adt/pg_upgrade_support.c
@@ -52,6 +52,28 @@ binary_upgrade_set_next_array_pg_type_oid(PG_FUNCTION_ARGS)
 	PG_RETURN_VOID();
 }
 
+Datum
+binary_upgrade_set_next_multirange_pg_type_oid(PG_FUNCTION_ARGS)
+{
+	Oid			typoid = PG_GETARG_OID(0);
+
+	CHECK_IS_BINARY_UPGRADE;
+	binary_upgrade_next_mrng_pg_type_oid = typoid;
+
+	PG_RETURN_VOID();
+}
+
+Datum
+binary_upgrade_set_next_multirange_array_pg_type_oid(PG_FUNCTION_ARGS)
+{
+	Oid			typoid = PG_GETARG_OID(0);
+
+	CHECK_IS_BINARY_UPGRADE;
+	binary_upgrade_next_mrng_array_pg_type_oid = typoid;
+
+	PG_RETURN_VOID();
+}
+
 Datum
 binary_upgrade_set_next_heap_pg_class_oid(PG_FUNCTION_ARGS)
 {
diff --git a/src/backend/utils/adt/pseudotypes.c b/src/backend/utils/adt/pseudotypes.c
index 3d6b2f90935..99a93271fe1 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"
 
 
 /*
@@ -227,6 +228,43 @@ anycompatiblerange_out(PG_FUNCTION_ARGS)
 	return range_out(fcinfo);
 }
 
+/*
+ * anycompatiblemultirange
+ *
+ * We may as well allow output, since multirange_out will in fact work.
+ */
+PSEUDOTYPE_DUMMY_INPUT_FUNC(anycompatiblemultirange);
+
+Datum
+anycompatiblemultirange_out(PG_FUNCTION_ARGS)
+{
+	return multirange_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
  *
diff --git a/src/backend/utils/adt/rangetypes.c b/src/backend/utils/adt/rangetypes.c
index 01ad8bc240b..ba4caa2d36a 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
 {
@@ -64,10 +62,6 @@ static const char *range_parse_bound(const char *string, const char *ptr,
 static char *range_deparse(char flags, const char *lbound_str,
 						   const char *ubound_str);
 static char *range_bound_escape(const char *value);
-static Size datum_compute_size(Size sz, Datum datum, bool typbyval,
-							   char typalign, int16 typlen, char typstorage);
-static Pointer datum_write(Pointer ptr, Datum datum, bool typbyval,
-						   char typalign, int16 typlen, char typstorage);
 
 
 /*
@@ -431,19 +425,37 @@ range_lower(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
 	TypeCacheEntry *typcache;
+	bool		isnull;
+	Datum		result;
+
+	typcache = range_get_typcache(fcinfo, RangeTypeGetOid(r1));
+
+	result = range_lower_internal(typcache, r1, &isnull);
+
+	if (isnull)
+		PG_RETURN_NULL();
+	else
+		PG_RETURN_DATUM(result);
+}
+
+Datum
+range_lower_internal(TypeCacheEntry *typcache, const RangeType *r1, bool *isnull)
+{
 	RangeBound	lower;
 	RangeBound	upper;
 	bool		empty;
 
-	typcache = range_get_typcache(fcinfo, RangeTypeGetOid(r1));
-
 	range_deserialize(typcache, r1, &lower, &upper, &empty);
 
 	/* Return NULL if there's no finite lower bound */
 	if (empty || lower.infinite)
-		PG_RETURN_NULL();
+	{
+		*isnull = true;
+		return 0;
+	}
 
-	PG_RETURN_DATUM(lower.val);
+	*isnull = false;
+	return lower.val;
 }
 
 /* extract upper bound value */
@@ -452,19 +464,37 @@ range_upper(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
 	TypeCacheEntry *typcache;
+	bool		isnull;
+	Datum		result;
+
+	typcache = range_get_typcache(fcinfo, RangeTypeGetOid(r1));
+
+	result = range_upper_internal(typcache, r1, &isnull);
+
+	if (isnull)
+		PG_RETURN_NULL();
+	else
+		PG_RETURN_DATUM(result);
+}
+
+Datum
+range_upper_internal(TypeCacheEntry *typcache, const RangeType *r1, bool *isnull)
+{
 	RangeBound	lower;
 	RangeBound	upper;
 	bool		empty;
 
-	typcache = range_get_typcache(fcinfo, RangeTypeGetOid(r1));
-
 	range_deserialize(typcache, r1, &lower, &upper, &empty);
 
 	/* Return NULL if there's no finite upper bound */
 	if (empty || upper.infinite)
-		PG_RETURN_NULL();
+	{
+		*isnull = true;
+		return 0;
+	}
 
-	PG_RETURN_DATUM(upper.val);
+	*isnull = false;
+	return upper.val;
 }
 
 
@@ -475,9 +505,8 @@ Datum
 range_empty(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
-	char		flags = range_get_flags(r1);
 
-	PG_RETURN_BOOL(flags & RANGE_EMPTY);
+	PG_RETURN_BOOL(range_has_flag(r1, RANGE_EMPTY));
 }
 
 /* is lower bound inclusive? */
@@ -485,9 +514,8 @@ Datum
 range_lower_inc(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
-	char		flags = range_get_flags(r1);
 
-	PG_RETURN_BOOL(flags & RANGE_LB_INC);
+	PG_RETURN_BOOL(range_has_flag(r1, RANGE_LB_INC));
 }
 
 /* is upper bound inclusive? */
@@ -495,9 +523,8 @@ Datum
 range_upper_inc(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
-	char		flags = range_get_flags(r1);
 
-	PG_RETURN_BOOL(flags & RANGE_UB_INC);
+	PG_RETURN_BOOL(range_has_flag(r1, RANGE_UB_INC));
 }
 
 /* is lower bound infinite? */
@@ -505,9 +532,8 @@ Datum
 range_lower_inf(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
-	char		flags = range_get_flags(r1);
 
-	PG_RETURN_BOOL(flags & RANGE_LB_INF);
+	PG_RETURN_BOOL(range_has_flag(r1, RANGE_LB_INF));
 }
 
 /* is upper bound infinite? */
@@ -515,9 +541,8 @@ Datum
 range_upper_inf(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
-	char		flags = range_get_flags(r1);
 
-	PG_RETURN_BOOL(flags & RANGE_UB_INF);
+	PG_RETURN_BOOL(range_has_flag(r1, RANGE_UB_INF));
 }
 
 
@@ -957,7 +982,25 @@ range_minus(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
 	RangeType  *r2 = PG_GETARG_RANGE_P(1);
+	RangeType  *ret;
 	TypeCacheEntry *typcache;
+
+	/* 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));
+
+	ret = range_minus_internal(typcache, r1, r2);
+	if (ret)
+		PG_RETURN_RANGE_P(ret);
+	else
+		PG_RETURN_NULL();
+}
+
+RangeType *
+range_minus_internal(TypeCacheEntry *typcache, RangeType *r1, RangeType *r2)
+{
 	RangeBound	lower1,
 				lower2;
 	RangeBound	upper1,
@@ -969,18 +1012,12 @@ range_minus(PG_FUNCTION_ARGS)
 				cmp_u1l2,
 				cmp_u1u2;
 
-	/* 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));
-
 	range_deserialize(typcache, r1, &lower1, &upper1, &empty1);
 	range_deserialize(typcache, r2, &lower2, &upper2, &empty2);
 
 	/* if either is empty, r1 is the correct answer */
 	if (empty1 || empty2)
-		PG_RETURN_RANGE_P(r1);
+		return r1;
 
 	cmp_l1l2 = range_cmp_bounds(typcache, &lower1, &lower2);
 	cmp_l1u2 = range_cmp_bounds(typcache, &lower1, &upper2);
@@ -993,34 +1030,34 @@ range_minus(PG_FUNCTION_ARGS)
 				 errmsg("result of range difference would not be contiguous")));
 
 	if (cmp_l1u2 > 0 || cmp_u1l2 < 0)
-		PG_RETURN_RANGE_P(r1);
+		return r1;
 
 	if (cmp_l1l2 >= 0 && cmp_u1u2 <= 0)
-		PG_RETURN_RANGE_P(make_empty_range(typcache));
+		return make_empty_range(typcache);
 
 	if (cmp_l1l2 <= 0 && cmp_u1l2 >= 0 && cmp_u1u2 <= 0)
 	{
 		lower2.inclusive = !lower2.inclusive;
 		lower2.lower = false;	/* it will become the upper bound */
-		PG_RETURN_RANGE_P(make_range(typcache, &lower1, &lower2, false));
+		return make_range(typcache, &lower1, &lower2, false);
 	}
 
 	if (cmp_l1l2 >= 0 && cmp_u1u2 >= 0 && cmp_l1u2 <= 0)
 	{
 		upper2.inclusive = !upper2.inclusive;
 		upper2.lower = true;	/* it will become the lower bound */
-		PG_RETURN_RANGE_P(make_range(typcache, &upper2, &upper1, false));
+		return make_range(typcache, &upper2, &upper1, false);
 	}
 
 	elog(ERROR, "unexpected case in range_minus");
-	PG_RETURN_NULL();
+	return NULL;
 }
 
 /*
  * Set union.  If strict is true, it is an error that the two input ranges
  * are not adjacent or overlapping.
  */
-static RangeType *
+RangeType *
 range_union_internal(TypeCacheEntry *typcache, RangeType *r1, RangeType *r2,
 					 bool strict)
 {
@@ -1101,6 +1138,19 @@ range_intersect(PG_FUNCTION_ARGS)
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
 	RangeType  *r2 = PG_GETARG_RANGE_P(1);
 	TypeCacheEntry *typcache;
+
+	/* 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));
+
+	PG_RETURN_RANGE_P(range_intersect_internal(typcache, r1, r2));
+}
+
+RangeType *
+range_intersect_internal(TypeCacheEntry *typcache, const RangeType *r1, const RangeType *r2)
+{
 	RangeBound	lower1,
 				lower2;
 	RangeBound	upper1,
@@ -1110,17 +1160,11 @@ range_intersect(PG_FUNCTION_ARGS)
 	RangeBound *result_lower;
 	RangeBound *result_upper;
 
-	/* 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));
-
 	range_deserialize(typcache, r1, &lower1, &upper1, &empty1);
 	range_deserialize(typcache, r2, &lower2, &upper2, &empty2);
 
-	if (empty1 || empty2 || !DatumGetBool(range_overlaps(fcinfo)))
-		PG_RETURN_RANGE_P(make_empty_range(typcache));
+	if (empty1 || empty2 || !range_overlaps_internal(typcache, r1, r2))
+		return make_empty_range(typcache);
 
 	if (range_cmp_bounds(typcache, &lower1, &lower2) >= 0)
 		result_lower = &lower1;
@@ -1132,9 +1176,81 @@ range_intersect(PG_FUNCTION_ARGS)
 	else
 		result_upper = &upper2;
 
-	PG_RETURN_RANGE_P(make_range(typcache, result_lower, result_upper, false));
+	return make_range(typcache, result_lower, result_upper, false);
+}
+
+/* range, range -> range, range functions */
+
+/*
+ * range_split_internal - if r2 intersects the middle of r1, leaving non-empty
+ * ranges on both sides, then return true and set output1 and output2 to the
+ * results of r1 - r2 (in order). Otherwise return false and don't set output1
+ * or output2. Neither input range should be empty.
+ */
+bool
+range_split_internal(TypeCacheEntry *typcache, const RangeType *r1, const RangeType *r2,
+					 RangeType **output1, RangeType **output2)
+{
+	RangeBound	lower1,
+				lower2;
+	RangeBound	upper1,
+				upper2;
+	bool		empty1,
+				empty2;
+
+	range_deserialize(typcache, r1, &lower1, &upper1, &empty1);
+	range_deserialize(typcache, r2, &lower2, &upper2, &empty2);
+
+	if (range_cmp_bounds(typcache, &lower1, &lower2) < 0 &&
+		range_cmp_bounds(typcache, &upper1, &upper2) > 0)
+	{
+		/*
+		 * Need to invert inclusive/exclusive for the lower2 and upper2
+		 * points. They can't be infinite though. We're allowed to overwrite
+		 * these RangeBounds since they only exist locally.
+		 */
+		lower2.inclusive = !lower2.inclusive;
+		lower2.lower = false;
+		upper2.inclusive = !upper2.inclusive;
+		upper2.lower = true;
+
+		*output1 = make_range(typcache, &lower1, &lower2, false);
+		*output2 = make_range(typcache, &upper2, &upper1, false);
+		return true;
+	}
+
+	return false;
 }
 
+/* range -> range aggregate functions */
+
+Datum
+range_intersect_agg_transfn(PG_FUNCTION_ARGS)
+{
+	MemoryContext aggContext;
+	Oid			rngtypoid;
+	TypeCacheEntry *typcache;
+	RangeType  *result;
+	RangeType  *current;
+
+	if (!AggCheckCallContext(fcinfo, &aggContext))
+		elog(ERROR, "range_intersect_agg_transfn called in non-aggregate context");
+
+	rngtypoid = get_fn_expr_argtype(fcinfo->flinfo, 1);
+	if (!type_is_range(rngtypoid))
+		ereport(ERROR, (errmsg("range_intersect_agg must be called with a range")));
+
+	typcache = range_get_typcache(fcinfo, rngtypoid);
+
+	/* strictness ensures these are non-null */
+	result = PG_GETARG_RANGE_P(0);
+	current = PG_GETARG_RANGE_P(1);
+
+	result = range_intersect_internal(typcache, result, current);
+	PG_RETURN_RANGE_P(result);
+}
+
+
 /* Btree support */
 
 /* btree comparator */
@@ -1144,12 +1260,6 @@ range_cmp(PG_FUNCTION_ARGS)
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
 	RangeType  *r2 = PG_GETARG_RANGE_P(1);
 	TypeCacheEntry *typcache;
-	RangeBound	lower1,
-				lower2;
-	RangeBound	upper1,
-				upper2;
-	bool		empty1,
-				empty2;
 	int			cmp;
 
 	check_stack_depth();		/* recurses when subtype is a range type */
@@ -1160,6 +1270,28 @@ range_cmp(PG_FUNCTION_ARGS)
 
 	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, const RangeType *r1, const 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);
 
@@ -1177,10 +1309,7 @@ range_cmp(PG_FUNCTION_ARGS)
 			cmp = range_cmp_bounds(typcache, &upper1, &upper2);
 	}
 
-	PG_FREE_IF_COPY(r1, 0);
-	PG_FREE_IF_COPY(r2, 1);
-
-	PG_RETURN_INT32(cmp);
+	return cmp;
 }
 
 /* inequality operators using the range_cmp function */
@@ -1218,13 +1347,10 @@ range_gt(PG_FUNCTION_ARGS)
 
 /* Hash support */
 
-/* hash a range value */
-Datum
-hash_range(PG_FUNCTION_ARGS)
+uint32
+hash_range_internal(TypeCacheEntry *typcache, const RangeType *r)
 {
-	RangeType  *r = PG_GETARG_RANGE_P(0);
 	uint32		result;
-	TypeCacheEntry *typcache;
 	TypeCacheEntry *scache;
 	RangeBound	lower;
 	RangeBound	upper;
@@ -1233,10 +1359,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 +1400,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, const RangeType *r, Datum seed)
+{
+	uint64		result;
 	TypeCacheEntry *scache;
 	RangeBound	lower;
 	RangeBound	upper;
@@ -1300,10 +1429,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 +1467,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));
 }
 
 /*
@@ -1769,6 +1912,21 @@ range_get_flags(const RangeType *range)
 	return *((char *) range + VARSIZE(range) - 1);
 }
 
+/*
+ * range_has_flag: set whether a range has a specific flag.
+ *
+ * This lets expose some of our functions that just check flags
+ * to the rest of the code base (like to multiranges)
+ * without writing full-fledged *_internal versions.
+ */
+bool
+range_has_flag(const RangeType *r1, char flag)
+{
+	char		flags = range_get_flags(r1);
+
+	return flags & flag;
+}
+
 /*
  * range_set_contain_empty: set the RANGE_CONTAIN_EMPTY bit in the value.
  *
@@ -1937,6 +2095,46 @@ range_cmp_bound_values(TypeCacheEntry *typcache, const RangeBound *b1,
 										   b1->val, b2->val));
 }
 
+/*
+ * qsort callback for sorting ranges.
+ *
+ * Two empty ranges compare equal; an empty range sorts to the left of any
+ * non-empty range.  Two non-empty ranges are sorted by lower bound first
+ * and by upper bound next.
+ */
+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.
  */
@@ -2395,7 +2593,7 @@ range_contains_elem_internal(TypeCacheEntry *typcache, const RangeType *r, Datum
  * Increment data_length by the space needed by the datum, including any
  * preceding alignment padding.
  */
-static Size
+Size
 datum_compute_size(Size data_length, Datum val, bool typbyval, char typalign,
 				   int16 typlen, char typstorage)
 {
@@ -2421,7 +2619,7 @@ datum_compute_size(Size data_length, Datum val, bool typbyval, char typalign,
  * Write the given datum beginning at ptr (after advancing to correct
  * alignment, if needed).  Return the pointer incremented by space used.
  */
-static Pointer
+Pointer
 datum_write(Pointer ptr, Datum datum, bool typbyval, char typalign,
 			int16 typlen, char typstorage)
 {
diff --git a/src/backend/utils/adt/rangetypes_typanalyze.c b/src/backend/utils/adt/rangetypes_typanalyze.c
index 57413b07201..dcef09b13b7 100644
--- a/src/backend/utils/adt/rangetypes_typanalyze.c
+++ b/src/backend/utils/adt/rangetypes_typanalyze.c
@@ -30,6 +30,7 @@
 #include "utils/fmgrprotos.h"
 #include "utils/lsyscache.h"
 #include "utils/rangetypes.h"
+#include "utils/multirangetypes.h"
 
 static int	float8_qsort_cmp(const void *a1, const void *a2);
 static int	range_bound_qsort_cmp(const void *a1, const void *a2, void *arg);
@@ -60,6 +61,33 @@ range_typanalyze(PG_FUNCTION_ARGS)
 	PG_RETURN_BOOL(true);
 }
 
+/*
+ * multirange_typanalyze -- typanalyze function for multirange columns
+ *
+ * We do the same analysis as for ranges, but on the smallest range that
+ * completely includes the multirange.
+ */
+Datum
+multirange_typanalyze(PG_FUNCTION_ARGS)
+{
+	VacAttrStats *stats = (VacAttrStats *) PG_GETARG_POINTER(0);
+	TypeCacheEntry *typcache;
+	Form_pg_attribute attr = stats->attr;
+
+	/* Get information about multirange type; note column might be a domain */
+	typcache = multirange_get_typcache(fcinfo, getBaseType(stats->attrtypid));
+
+	if (attr->attstattarget < 0)
+		attr->attstattarget = default_statistics_target;
+
+	stats->compute_stats = compute_range_stats;
+	stats->extra_data = typcache;
+	/* same as in std_typanalyze */
+	stats->minrows = 300 * attr->attstattarget;
+
+	PG_RETURN_BOOL(true);
+}
+
 /*
  * Comparison function for sorting float8s, used for range lengths.
  */
@@ -98,7 +126,8 @@ compute_range_stats(VacAttrStats *stats, AnalyzeAttrFetchFunc fetchfunc,
 					int samplerows, double totalrows)
 {
 	TypeCacheEntry *typcache = (TypeCacheEntry *) stats->extra_data;
-	bool		has_subdiff = OidIsValid(typcache->rng_subdiff_finfo.fn_oid);
+	TypeCacheEntry *mltrng_typcache = NULL;
+	bool		has_subdiff;
 	int			null_cnt = 0;
 	int			non_null_cnt = 0;
 	int			non_empty_cnt = 0;
@@ -112,6 +141,15 @@ compute_range_stats(VacAttrStats *stats, AnalyzeAttrFetchFunc fetchfunc,
 			   *uppers;
 	double		total_width = 0;
 
+	if (typcache->typtype == TYPTYPE_MULTIRANGE)
+	{
+		mltrng_typcache = typcache;
+		typcache = typcache->rngtype;
+	}
+	else
+		Assert(typcache->typtype == TYPTYPE_RANGE);
+	has_subdiff = OidIsValid(typcache->rng_subdiff_finfo.fn_oid);
+
 	/* Allocate memory to hold range bounds and lengths of the sample ranges. */
 	lowers = (RangeBound *) palloc(sizeof(RangeBound) * samplerows);
 	uppers = (RangeBound *) palloc(sizeof(RangeBound) * samplerows);
@@ -123,6 +161,7 @@ compute_range_stats(VacAttrStats *stats, AnalyzeAttrFetchFunc fetchfunc,
 		Datum		value;
 		bool		isnull,
 					empty;
+		MultirangeType *multirange;
 		RangeType  *range;
 		RangeBound	lower,
 					upper;
@@ -145,7 +184,24 @@ compute_range_stats(VacAttrStats *stats, AnalyzeAttrFetchFunc fetchfunc,
 		total_width += VARSIZE_ANY(DatumGetPointer(value));
 
 		/* Get range and deserialize it for further analysis. */
-		range = DatumGetRangeTypeP(value);
+		if (mltrng_typcache != NULL)
+		{
+			/* Treat multiranges like a big range without gaps. */
+			multirange = DatumGetMultirangeTypeP(value);
+			if (MultirangeIsEmpty(multirange))
+				range = make_empty_range(typcache);
+			else
+			{
+				int			range_count;
+				RangeType **ranges;
+
+				multirange_deserialize(typcache, multirange, &range_count, &ranges);
+				range = range_union_internal(typcache, ranges[0],
+											 ranges[range_count - 1], false);
+			}
+		}
+		else
+			range = DatumGetRangeTypeP(value);
 		range_deserialize(typcache, range, &lower, &upper, &empty);
 
 		if (!empty)
@@ -262,6 +318,13 @@ compute_range_stats(VacAttrStats *stats, AnalyzeAttrFetchFunc fetchfunc,
 			stats->stakind[slot_idx] = STATISTIC_KIND_BOUNDS_HISTOGRAM;
 			stats->stavalues[slot_idx] = bound_hist_values;
 			stats->numvalues[slot_idx] = num_hist;
+
+			/* Store ranges even if we're analyzing a multirange column */
+			stats->statypid[slot_idx] = typcache->type_id;
+			stats->statyplen[slot_idx] = typcache->typlen;
+			stats->statypbyval[slot_idx] = typcache->typbyval;
+			stats->statypalign[slot_idx] = 'd';
+
 			slot_idx++;
 		}
 
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index 204bcd03c0b..ad92636f7f1 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -2610,6 +2610,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
  *
@@ -3308,7 +3318,7 @@ get_namespace_name_or_temp(Oid nspid)
 		return get_namespace_name(nspid);
 }
 
-/*				---------- PG_RANGE CACHE ----------				 */
+/*				---------- PG_RANGE CACHES ----------				 */
 
 /*
  * get_range_subtype
@@ -3361,6 +3371,56 @@ get_range_collation(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->rngmultitypid;
+		ReleaseSysCache(tp);
+		return result;
+	}
+	else
+		return InvalidOid;
+}
+
+/*
+ * get_multirange_range
+ *		Returns the range type of a given multirange
+ *
+ * Returns InvalidOid if the type is not a multirange.
+ */
+Oid
+get_multirange_range(Oid multirangeOid)
+{
+	HeapTuple	tp;
+
+	tp = SearchSysCache1(RANGEMULTIRANGE, 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 809b27a038f..89f08a4f43c 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -650,6 +650,18 @@ static const struct cachedesc cacheinfo[] = {
 		},
 		64
 	},
+	{RangeRelationId,			/* RANGEMULTIRANGE */
+		RangeMultirangeTypidIndexId,
+		1,
+		{
+			Anum_pg_range_rngmultitypid,
+			0,
+			0,
+			0
+		},
+		4
+	},
+
 	{RangeRelationId,			/* RANGETYPE */
 		RangeTypidIndexId,
 		1,
diff --git a/src/backend/utils/cache/typcache.c b/src/backend/utils/cache/typcache.c
index 1e331098c0d..8c97ef39557 100644
--- a/src/backend/utils/cache/typcache.c
+++ b/src/backend/utils/cache/typcache.c
@@ -287,6 +287,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);
@@ -305,6 +306,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 TypeCacheTypCallback(Datum arg, int cacheid, uint32 hashvalue);
 static void TypeCacheOpcCallback(Datum arg, int cacheid, uint32 hashvalue);
@@ -557,8 +561,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))
@@ -595,7 +599,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))
@@ -620,7 +624,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))
@@ -645,7 +649,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))
@@ -695,6 +699,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;
@@ -737,6 +748,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;
@@ -816,6 +834,16 @@ lookup_type_cache(Oid type_id, int flags)
 			(void) lookup_type_cache(typentry->rngelemtype->type_id, 0);
 	}
 
+	/*
+	 * 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
 	 */
@@ -927,6 +955,22 @@ load_rangetype_info(TypeCacheEntry *typentry)
 	typentry->rngelemtype = lookup_type_cache(subtypeOid, 0);
 }
 
+/*
+ * load_multirangetype_info --- helper routine to set up multirange type
+ * information
+ */
+static void
+load_multirangetype_info(TypeCacheEntry *typentry)
+{
+	Oid			rangetypeOid;
+
+	rangetypeOid = get_multirange_range(typentry->type_id);
+	if (!OidIsValid(rangetypeOid))
+		elog(ERROR, "cache lookup failed for multirange type %u",
+			 typentry->type_id);
+
+	typentry->rngtype = lookup_type_cache(rangetypeOid, TYPECACHE_RANGE_INFO);
+}
 
 /*
  * load_domaintype_info --- helper routine to set up domain constraint info
@@ -1559,11 +1603,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
@@ -1606,6 +1650,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 9696c88f241..f6fa4ab2fb2 100644
--- a/src/backend/utils/fmgr/funcapi.c
+++ b/src/backend/utils/fmgr/funcapi.c
@@ -35,6 +35,7 @@ typedef struct polymorphic_actuals
 	Oid			anyelement_type;	/* anyelement mapping, if known */
 	Oid			anyarray_type;	/* anyarray mapping, if known */
 	Oid			anyrange_type;	/* anyrange mapping, if known */
+	Oid			anymultirange_type; /* anymultirange mapping, if known */
 } polymorphic_actuals;
 
 static void shutdown_MultiFuncCall(Datum arg);
@@ -46,6 +47,7 @@ static TypeFuncClass internal_get_result_type(Oid funcid,
 static void resolve_anyelement_from_others(polymorphic_actuals *actuals);
 static void resolve_anyarray_from_others(polymorphic_actuals *actuals);
 static void resolve_anyrange_from_others(polymorphic_actuals *actuals);
+static void resolve_anymultirange_from_others(polymorphic_actuals *actuals);
 static bool resolve_polymorphic_tupdesc(TupleDesc tupdesc,
 										oidvector *declared_args,
 										Node *call_expr);
@@ -503,6 +505,34 @@ resolve_anyelement_from_others(polymorphic_actuals *actuals)
 							format_type_be(range_base_type))));
 		actuals->anyelement_type = range_typelem;
 	}
+	else if (OidIsValid(actuals->anymultirange_type))
+	{
+		/* Use the element type based on the multirange type */
+		Oid			multirange_base_type;
+		Oid			multirange_typelem;
+		Oid			range_base_type;
+		Oid			range_typelem;
+
+		multirange_base_type = getBaseType(actuals->anymultirange_type);
+		multirange_typelem = get_multirange_range(multirange_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(multirange_base_type))));
+
+		range_base_type = getBaseType(multirange_typelem);
+		range_typelem = get_range_subtype(range_base_type);
+
+		if (!OidIsValid(range_typelem))
+			ereport(ERROR,
+					(errcode(ERRCODE_DATATYPE_MISMATCH),
+					 errmsg("argument declared %s does not contain a range type but type %s",
+							"anymultirange",
+							format_type_be(range_base_type))));
+		actuals->anyelement_type = range_typelem;
+	}
 	else
 		elog(ERROR, "could not determine polymorphic type");
 }
@@ -540,10 +570,53 @@ static void
 resolve_anyrange_from_others(polymorphic_actuals *actuals)
 {
 	/*
-	 * We can't deduce a range type from other polymorphic inputs, because
-	 * there may be multiple range types with the same subtype.
+	 * We can't deduce a range type from other polymorphic array or base
+	 * types, because there may be multiple range types with the same subtype,
+	 * but we can deduce it from a polymorphic multirange type.
+	 */
+	if (OidIsValid(actuals->anymultirange_type))
+	{
+		/* Use the element type based on the multirange type */
+		Oid			multirange_base_type = getBaseType(actuals->anymultirange_type);
+		Oid			multirange_typelem = get_multirange_range(multirange_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(multirange_base_type))));
+		actuals->anyrange_type = multirange_typelem;
+	}
+	else
+		elog(ERROR, "could not determine polymorphic type");
+}
+
+/*
+ * Resolve actual type of ANYMULTIRANGE from other polymorphic inputs
+ */
+static void
+resolve_anymultirange_from_others(polymorphic_actuals *actuals)
+{
+	/*
+	 * We can't deduce a multirange type from polymorphic array or base types,
+	 * because there may be multiple range types with the same subtype, but we
+	 * can deduce it from a polymorphic range type.
 	 */
-	elog(ERROR, "could not determine polymorphic type");
+	if (OidIsValid(actuals->anyrange_type))
+	{
+		Oid			range_base_type = getBaseType(actuals->anyrange_type);
+		Oid			multirange_typeid = get_range_multirange(range_base_type);
+
+		if (!OidIsValid(multirange_typeid))
+			ereport(ERROR,
+					(errcode(ERRCODE_UNDEFINED_OBJECT),
+					 errmsg("could not find multirange type for data type %s",
+							format_type_be(actuals->anyrange_type))));
+		actuals->anymultirange_type = multirange_typeid;
+	}
+	else
+		elog(ERROR, "could not determine polymorphic type");
 }
 
 /*
@@ -566,9 +639,11 @@ 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_anycompatible_result = false;
 	bool		have_anycompatible_array_result = false;
 	bool		have_anycompatible_range_result = false;
+	bool		have_anycompatible_multirange_result = false;
 	polymorphic_actuals poly_actuals;
 	polymorphic_actuals anyc_actuals;
 	Oid			anycollation = InvalidOid;
@@ -594,6 +669,10 @@ resolve_polymorphic_tupdesc(TupleDesc tupdesc, oidvector *declared_args,
 				have_polymorphic_result = true;
 				have_anyrange_result = true;
 				break;
+			case ANYMULTIRANGEOID:
+				have_polymorphic_result = true;
+				have_anymultirange_result = true;
+				break;
 			case ANYCOMPATIBLEOID:
 			case ANYCOMPATIBLENONARRAYOID:
 				have_polymorphic_result = true;
@@ -607,6 +686,10 @@ resolve_polymorphic_tupdesc(TupleDesc tupdesc, oidvector *declared_args,
 				have_polymorphic_result = true;
 				have_anycompatible_range_result = true;
 				break;
+			case ANYCOMPATIBLEMULTIRANGEOID:
+				have_polymorphic_result = true;
+				have_anycompatible_multirange_result = true;
+				break;
 			default:
 				break;
 		}
@@ -660,6 +743,15 @@ resolve_polymorphic_tupdesc(TupleDesc tupdesc, oidvector *declared_args,
 						return false;
 				}
 				break;
+			case ANYMULTIRANGEOID:
+				if (!OidIsValid(poly_actuals.anymultirange_type))
+				{
+					poly_actuals.anymultirange_type =
+						get_call_expr_argtype(call_expr, i);
+					if (!OidIsValid(poly_actuals.anymultirange_type))
+						return false;
+				}
+				break;
 			case ANYCOMPATIBLEOID:
 			case ANYCOMPATIBLENONARRAYOID:
 				if (!OidIsValid(anyc_actuals.anyelement_type))
@@ -688,6 +780,15 @@ resolve_polymorphic_tupdesc(TupleDesc tupdesc, oidvector *declared_args,
 						return false;
 				}
 				break;
+			case ANYCOMPATIBLEMULTIRANGEOID:
+				if (!OidIsValid(anyc_actuals.anymultirange_type))
+				{
+					anyc_actuals.anymultirange_type =
+						get_call_expr_argtype(call_expr, i);
+					if (!OidIsValid(anyc_actuals.anymultirange_type))
+						return false;
+				}
+				break;
 			default:
 				break;
 		}
@@ -703,6 +804,9 @@ resolve_polymorphic_tupdesc(TupleDesc tupdesc, oidvector *declared_args,
 	if (have_anyrange_result && !OidIsValid(poly_actuals.anyrange_type))
 		resolve_anyrange_from_others(&poly_actuals);
 
+	if (have_anymultirange_result && !OidIsValid(poly_actuals.anymultirange_type))
+		resolve_anymultirange_from_others(&poly_actuals);
+
 	if (have_anycompatible_result && !OidIsValid(anyc_actuals.anyelement_type))
 		resolve_anyelement_from_others(&anyc_actuals);
 
@@ -712,6 +816,9 @@ resolve_polymorphic_tupdesc(TupleDesc tupdesc, oidvector *declared_args,
 	if (have_anycompatible_range_result && !OidIsValid(anyc_actuals.anyrange_type))
 		resolve_anyrange_from_others(&anyc_actuals);
 
+	if (have_anycompatible_multirange_result && !OidIsValid(anyc_actuals.anymultirange_type))
+		resolve_anymultirange_from_others(&anyc_actuals);
+
 	/*
 	 * Identify the collation to use for polymorphic OUT parameters. (It'll
 	 * necessarily be the same for both anyelement and anyarray, likewise for
@@ -780,6 +887,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),
+								   poly_actuals.anymultirange_type,
+								   -1,
+								   0);
+				/* no collation should be attached to a multirange type */
+				break;
 			case ANYCOMPATIBLEOID:
 			case ANYCOMPATIBLENONARRAYOID:
 				TupleDescInitEntry(tupdesc, i + 1,
@@ -805,6 +920,14 @@ resolve_polymorphic_tupdesc(TupleDesc tupdesc, oidvector *declared_args,
 								   0);
 				/* no collation should be attached to a range type */
 				break;
+			case ANYCOMPATIBLEMULTIRANGEOID:
+				TupleDescInitEntry(tupdesc, i + 1,
+								   NameStr(att->attname),
+								   anyc_actuals.anymultirange_type,
+								   -1,
+								   0);
+				/* no collation should be attached to a multirange type */
+				break;
 			default:
 				break;
 		}
@@ -834,9 +957,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;
 	bool		have_anycompatible_result = false;
 	bool		have_anycompatible_array_result = false;
 	bool		have_anycompatible_range_result = false;
+	bool		have_anycompatible_multirange_result = false;
 	polymorphic_actuals poly_actuals;
 	polymorphic_actuals anyc_actuals;
 	int			inargno;
@@ -912,6 +1037,24 @@ resolve_polymorphic_argtypes(int numargs, Oid *argtypes, char *argmodes,
 					argtypes[i] = poly_actuals.anyrange_type;
 				}
 				break;
+			case ANYMULTIRANGEOID:
+				if (argmode == PROARGMODE_OUT || argmode == PROARGMODE_TABLE)
+				{
+					have_polymorphic_result = true;
+					have_anymultirange_result = true;
+				}
+				else
+				{
+					if (!OidIsValid(poly_actuals.anymultirange_type))
+					{
+						poly_actuals.anymultirange_type =
+							get_call_expr_argtype(call_expr, inargno);
+						if (!OidIsValid(poly_actuals.anymultirange_type))
+							return false;
+					}
+					argtypes[i] = poly_actuals.anymultirange_type;
+				}
+				break;
 			case ANYCOMPATIBLEOID:
 			case ANYCOMPATIBLENONARRAYOID:
 				if (argmode == PROARGMODE_OUT || argmode == PROARGMODE_TABLE)
@@ -967,6 +1110,24 @@ resolve_polymorphic_argtypes(int numargs, Oid *argtypes, char *argmodes,
 					argtypes[i] = anyc_actuals.anyrange_type;
 				}
 				break;
+			case ANYCOMPATIBLEMULTIRANGEOID:
+				if (argmode == PROARGMODE_OUT || argmode == PROARGMODE_TABLE)
+				{
+					have_polymorphic_result = true;
+					have_anycompatible_multirange_result = true;
+				}
+				else
+				{
+					if (!OidIsValid(anyc_actuals.anymultirange_type))
+					{
+						anyc_actuals.anymultirange_type =
+							get_call_expr_argtype(call_expr, inargno);
+						if (!OidIsValid(anyc_actuals.anymultirange_type))
+							return false;
+					}
+					argtypes[i] = anyc_actuals.anymultirange_type;
+				}
+				break;
 			default:
 				break;
 		}
@@ -988,6 +1149,9 @@ resolve_polymorphic_argtypes(int numargs, Oid *argtypes, char *argmodes,
 	if (have_anyrange_result && !OidIsValid(poly_actuals.anyrange_type))
 		resolve_anyrange_from_others(&poly_actuals);
 
+	if (have_anymultirange_result && !OidIsValid(poly_actuals.anymultirange_type))
+		resolve_anymultirange_from_others(&poly_actuals);
+
 	if (have_anycompatible_result && !OidIsValid(anyc_actuals.anyelement_type))
 		resolve_anyelement_from_others(&anyc_actuals);
 
@@ -997,6 +1161,9 @@ resolve_polymorphic_argtypes(int numargs, Oid *argtypes, char *argmodes,
 	if (have_anycompatible_range_result && !OidIsValid(anyc_actuals.anyrange_type))
 		resolve_anyrange_from_others(&anyc_actuals);
 
+	if (have_anycompatible_multirange_result && !OidIsValid(anyc_actuals.anymultirange_type))
+		resolve_anymultirange_from_others(&anyc_actuals);
+
 	/* And finally replace the output column types as needed */
 	for (i = 0; i < numargs; i++)
 	{
@@ -1013,6 +1180,9 @@ resolve_polymorphic_argtypes(int numargs, Oid *argtypes, char *argmodes,
 			case ANYRANGEOID:
 				argtypes[i] = poly_actuals.anyrange_type;
 				break;
+			case ANYMULTIRANGEOID:
+				argtypes[i] = poly_actuals.anymultirange_type;
+				break;
 			case ANYCOMPATIBLEOID:
 			case ANYCOMPATIBLENONARRAYOID:
 				argtypes[i] = anyc_actuals.anyelement_type;
@@ -1023,6 +1193,9 @@ resolve_polymorphic_argtypes(int numargs, Oid *argtypes, char *argmodes,
 			case ANYCOMPATIBLERANGEOID:
 				argtypes[i] = anyc_actuals.anyrange_type;
 				break;
+			case ANYCOMPATIBLEMULTIRANGEOID:
+				argtypes[i] = anyc_actuals.anymultirange_type;
+				break;
 			default:
 				break;
 		}
@@ -1052,6 +1225,7 @@ get_type_func_class(Oid typid, Oid *base_typeid)
 		case TYPTYPE_BASE:
 		case TYPTYPE_ENUM:
 		case TYPTYPE_RANGE:
+		case TYPTYPE_MULTIRANGE:
 			return TYPEFUNC_SCALAR;
 		case TYPTYPE_DOMAIN:
 			*base_typeid = typid = getBaseType(typid);
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 673a6703475..03023a382c0 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -272,7 +272,8 @@ static void dumpSearchPath(Archive *AH);
 static void binary_upgrade_set_type_oids_by_type_oid(Archive *fout,
 													 PQExpBuffer upgrade_buffer,
 													 Oid pg_type_oid,
-													 bool force_array_type);
+													 bool force_array_type,
+													 bool include_multirange_type);
 static void binary_upgrade_set_type_oids_by_rel_oid(Archive *fout,
 													PQExpBuffer upgrade_buffer, Oid pg_rel_oid);
 static void binary_upgrade_set_pg_class_oids(Archive *fout,
@@ -1653,7 +1654,7 @@ selectDumpableType(TypeInfo *tyinfo, Archive *fout)
 	}
 
 	/* skip auto-generated array types */
-	if (tyinfo->isArray)
+	if (tyinfo->isArray || tyinfo->isMultirange)
 	{
 		tyinfo->dobj.objType = DO_DUMMY_TYPE;
 
@@ -4461,16 +4462,49 @@ append_depends_on_extension(Archive *fout,
 	}
 }
 
+static Oid
+get_next_possible_free_pg_type_oid(Archive *fout, PQExpBuffer upgrade_query)
+{
+	/*
+	 * If the old version didn't assign an array type, but the new version
+	 * does, we must select an unused type OID to assign.  This currently only
+	 * happens for domains, when upgrading pre-v11 to v11 and up.
+	 *
+	 * Note: local state here is kind of ugly, but we must have some, since we
+	 * mustn't choose the same unused OID more than once.
+	 */
+	static Oid	next_possible_free_oid = FirstNormalObjectId;
+	PGresult   *res;
+	bool		is_dup;
+
+	do
+	{
+		++next_possible_free_oid;
+		printfPQExpBuffer(upgrade_query,
+						  "SELECT EXISTS(SELECT 1 "
+						  "FROM pg_catalog.pg_type "
+						  "WHERE oid = '%u'::pg_catalog.oid);",
+						  next_possible_free_oid);
+		res = ExecuteSqlQueryForSingleRow(fout, upgrade_query->data);
+		is_dup = (PQgetvalue(res, 0, 0)[0] == 't');
+		PQclear(res);
+	} while (is_dup);
+
+	return next_possible_free_oid;
+}
 
 static void
 binary_upgrade_set_type_oids_by_type_oid(Archive *fout,
 										 PQExpBuffer upgrade_buffer,
 										 Oid pg_type_oid,
-										 bool force_array_type)
+										 bool force_array_type,
+										 bool include_multirange_type)
 {
 	PQExpBuffer upgrade_query = createPQExpBuffer();
 	PGresult   *res;
 	Oid			pg_type_array_oid;
+	Oid			pg_type_multirange_oid;
+	Oid			pg_type_multirange_array_oid;
 
 	appendPQExpBufferStr(upgrade_buffer, "\n-- For binary upgrade, must preserve pg_type oid\n");
 	appendPQExpBuffer(upgrade_buffer,
@@ -4491,33 +4525,7 @@ binary_upgrade_set_type_oids_by_type_oid(Archive *fout,
 	PQclear(res);
 
 	if (!OidIsValid(pg_type_array_oid) && force_array_type)
-	{
-		/*
-		 * If the old version didn't assign an array type, but the new version
-		 * does, we must select an unused type OID to assign.  This currently
-		 * only happens for domains, when upgrading pre-v11 to v11 and up.
-		 *
-		 * Note: local state here is kind of ugly, but we must have some,
-		 * since we mustn't choose the same unused OID more than once.
-		 */
-		static Oid	next_possible_free_oid = FirstNormalObjectId;
-		bool		is_dup;
-
-		do
-		{
-			++next_possible_free_oid;
-			printfPQExpBuffer(upgrade_query,
-							  "SELECT EXISTS(SELECT 1 "
-							  "FROM pg_catalog.pg_type "
-							  "WHERE oid = '%u'::pg_catalog.oid);",
-							  next_possible_free_oid);
-			res = ExecuteSqlQueryForSingleRow(fout, upgrade_query->data);
-			is_dup = (PQgetvalue(res, 0, 0)[0] == 't');
-			PQclear(res);
-		} while (is_dup);
-
-		pg_type_array_oid = next_possible_free_oid;
-	}
+		pg_type_array_oid = get_next_possible_free_pg_type_oid(fout, upgrade_query);
 
 	if (OidIsValid(pg_type_array_oid))
 	{
@@ -4528,6 +4536,46 @@ binary_upgrade_set_type_oids_by_type_oid(Archive *fout,
 						  pg_type_array_oid);
 	}
 
+	/*
+	 * Pre-set the multirange type oid and its own array type oid.
+	 */
+	if (include_multirange_type)
+	{
+		if (fout->remoteVersion >= 130000)
+		{
+			appendPQExpBuffer(upgrade_query,
+							  "SELECT t.oid, t.typarray "
+							  "FROM pg_catalog.pg_type t "
+							  "JOIN pg_catalog.pg_range r "
+							  "ON t.oid = r.rngmultitypid "
+							  "WHERE r.rngtypid = '%u'::pg_catalog.oid;",
+							  pg_type_oid);
+
+			res = ExecuteSqlQueryForSingleRow(fout, upgrade_query->data);
+
+			pg_type_multirange_oid = atooid(PQgetvalue(res, 0, PQfnumber(res, "oid")));
+			pg_type_multirange_array_oid = atooid(PQgetvalue(res, 0, PQfnumber(res, "typarray")));
+
+			PQclear(res);
+		}
+		else
+		{
+			pg_type_multirange_oid = get_next_possible_free_pg_type_oid(fout, upgrade_query);
+			pg_type_multirange_array_oid = get_next_possible_free_pg_type_oid(fout, upgrade_query);
+		}
+
+		appendPQExpBufferStr(upgrade_buffer,
+							 "\n-- For binary upgrade, must preserve multirange pg_type oid\n");
+		appendPQExpBuffer(upgrade_buffer,
+						  "SELECT pg_catalog.binary_upgrade_set_next_multirange_pg_type_oid('%u'::pg_catalog.oid);\n\n",
+						  pg_type_multirange_oid);
+		appendPQExpBufferStr(upgrade_buffer,
+							 "\n-- For binary upgrade, must preserve multirange pg_type array oid\n");
+		appendPQExpBuffer(upgrade_buffer,
+						  "SELECT pg_catalog.binary_upgrade_set_next_multirange_array_pg_type_oid('%u'::pg_catalog.oid);\n\n",
+						  pg_type_multirange_array_oid);
+	}
+
 	destroyPQExpBuffer(upgrade_query);
 }
 
@@ -4552,7 +4600,7 @@ binary_upgrade_set_type_oids_by_rel_oid(Archive *fout,
 
 	if (OidIsValid(pg_type_oid))
 		binary_upgrade_set_type_oids_by_type_oid(fout, upgrade_buffer,
-												 pg_type_oid, false);
+												 pg_type_oid, false, false);
 
 	PQclear(upgrade_res);
 	destroyPQExpBuffer(upgrade_query);
@@ -5097,6 +5145,11 @@ getTypes(Archive *fout, int *numTypes)
 		else
 			tyinfo[i].isArray = false;
 
+		if (tyinfo[i].typtype == 'm')
+			tyinfo[i].isMultirange = true;
+		else
+			tyinfo[i].isMultirange = false;
+
 		/* Decide whether we want to dump it */
 		selectDumpableType(&tyinfo[i], fout);
 
@@ -8326,9 +8379,12 @@ getProcLangs(Archive *fout, int *numProcLangs)
 
 /*
  * getCasts
- *	  get basic information about every cast in the system
+ *	  get basic information about most casts in the system
  *
  * numCasts is set to the number of casts read in
+ *
+ * Skip casts from a range to its multirange, since we'll create those
+ * automatically.
  */
 CastInfo *
 getCasts(Archive *fout, int *numCasts)
@@ -8346,7 +8402,20 @@ getCasts(Archive *fout, int *numCasts)
 	int			i_castcontext;
 	int			i_castmethod;
 
-	if (fout->remoteVersion >= 80400)
+	if (fout->remoteVersion >= 130000)
+	{
+		appendPQExpBufferStr(query, "SELECT tableoid, oid, "
+							 "castsource, casttarget, castfunc, castcontext, "
+							 "castmethod "
+							 "FROM pg_cast c "
+							 "WHERE NOT EXISTS ( "
+							 "SELECT 1 FROM pg_range r "
+							 "WHERE c.castsource = r.rngtypid "
+							 "AND c.casttarget = r.rngmultitypid "
+							 ") "
+							 "ORDER BY 3,4");
+	}
+	else if (fout->remoteVersion >= 80400)
 	{
 		appendPQExpBufferStr(query, "SELECT tableoid, oid, "
 							 "castsource, casttarget, castfunc, castcontext, "
@@ -10496,7 +10565,7 @@ dumpEnumType(Archive *fout, TypeInfo *tyinfo)
 	if (dopt->binary_upgrade)
 		binary_upgrade_set_type_oids_by_type_oid(fout, q,
 												 tyinfo->dobj.catId.oid,
-												 false);
+												 false, false);
 
 	appendPQExpBuffer(q, "CREATE TYPE %s AS ENUM (",
 					  qualtypname);
@@ -10594,7 +10663,17 @@ dumpRangeType(Archive *fout, TypeInfo *tyinfo)
 	char	   *procname;
 
 	appendPQExpBuffer(query,
-					  "SELECT pg_catalog.format_type(rngsubtype, NULL) AS rngsubtype, "
+					  "SELECT ");
+
+	if (fout->remoteVersion >= 140000)
+		appendPQExpBuffer(query,
+						  "pg_catalog.format_type(rngmultitypid, NULL) AS rngmultitype, ");
+	else
+		appendPQExpBuffer(query,
+						  "NULL AS rngmultitype, ");
+
+	appendPQExpBuffer(query,
+					  "pg_catalog.format_type(rngsubtype, NULL) AS rngsubtype, "
 					  "opc.opcname AS opcname, "
 					  "(SELECT nspname FROM pg_catalog.pg_namespace nsp "
 					  "  WHERE nsp.oid = opc.opcnamespace) AS opcnsp, "
@@ -10622,7 +10701,7 @@ dumpRangeType(Archive *fout, TypeInfo *tyinfo)
 	if (dopt->binary_upgrade)
 		binary_upgrade_set_type_oids_by_type_oid(fout, q,
 												 tyinfo->dobj.catId.oid,
-												 false);
+												 false, true);
 
 	appendPQExpBuffer(q, "CREATE TYPE %s AS RANGE (",
 					  qualtypname);
@@ -10630,6 +10709,10 @@ dumpRangeType(Archive *fout, TypeInfo *tyinfo)
 	appendPQExpBuffer(q, "\n    subtype = %s",
 					  PQgetvalue(res, 0, PQfnumber(res, "rngsubtype")));
 
+	if (PQgetvalue(res, 0, PQfnumber(res, "rngmultitype")))
+		appendPQExpBuffer(q, ",\n    multirange_type_name = %s",
+						  PQgetvalue(res, 0, PQfnumber(res, "rngmultitype")));
+
 	/* print subtype_opclass only if not default for subtype */
 	if (PQgetvalue(res, 0, PQfnumber(res, "opcdefault"))[0] != 't')
 	{
@@ -10728,7 +10811,7 @@ dumpUndefinedType(Archive *fout, TypeInfo *tyinfo)
 	if (dopt->binary_upgrade)
 		binary_upgrade_set_type_oids_by_type_oid(fout, q,
 												 tyinfo->dobj.catId.oid,
-												 false);
+												 false, false);
 
 	appendPQExpBuffer(q, "CREATE TYPE %s;\n",
 					  qualtypname);
@@ -10913,7 +10996,7 @@ dumpBaseType(Archive *fout, TypeInfo *tyinfo)
 	if (dopt->binary_upgrade)
 		binary_upgrade_set_type_oids_by_type_oid(fout, q,
 												 tyinfo->dobj.catId.oid,
-												 false);
+												 false, false);
 
 	appendPQExpBuffer(q,
 					  "CREATE TYPE %s (\n"
@@ -11103,7 +11186,8 @@ dumpDomain(Archive *fout, TypeInfo *tyinfo)
 	if (dopt->binary_upgrade)
 		binary_upgrade_set_type_oids_by_type_oid(fout, q,
 												 tyinfo->dobj.catId.oid,
-												 true); /* force array type */
+												 true,		/* force array type */
+												 false);	/* force multirange type */
 
 	qtypname = pg_strdup(fmtId(tyinfo->dobj.name));
 	qualtypname = pg_strdup(fmtQualifiedDumpable(tyinfo));
@@ -11291,7 +11375,7 @@ dumpCompositeType(Archive *fout, TypeInfo *tyinfo)
 	{
 		binary_upgrade_set_type_oids_by_type_oid(fout, q,
 												 tyinfo->dobj.catId.oid,
-												 false);
+												 false, false);
 		binary_upgrade_set_pg_class_oids(fout, q, tyinfo->typrelid, false);
 	}
 
@@ -11565,7 +11649,7 @@ dumpShellType(Archive *fout, ShellTypeInfo *stinfo)
 	if (dopt->binary_upgrade)
 		binary_upgrade_set_type_oids_by_type_oid(fout, q,
 												 stinfo->baseType->dobj.catId.oid,
-												 false);
+												 false, false);
 
 	appendPQExpBuffer(q, "CREATE TYPE %s;\n",
 					  fmtQualifiedDumpable(stinfo));
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 317bb839702..d7f77f1d3e0 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -176,6 +176,7 @@ typedef struct _typeInfo
 	char		typrelkind;		/* 'r', 'v', 'c', etc */
 	char		typtype;		/* 'b', 'c', etc */
 	bool		isArray;		/* true if auto-generated array type */
+	bool		isMultirange;	/* true if auto-generated multirange type */
 	bool		isDefined;		/* true if typisdefined */
 	/* If needed, we'll create a "shell type" entry for it; link that here: */
 	struct _shellTypeInfo *shellType;	/* shell-type entry, or NULL */
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index ec636620601..11dc98ee0a5 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -1601,6 +1601,7 @@ my %tests = (
 			\QOPERATOR 3 dump_test.~~(integer,integer);\E\n.+
 			\QCREATE TYPE dump_test.range_type_custom AS RANGE (\E\n\s+
 			\Qsubtype = integer,\E\n\s+
+			\Qmultirange_type_name = dump_test.multirange_type_custom,\E\n\s+
 			\Qsubtype_opclass = dump_test.op_class_custom\E\n
 			\Q);\E
 			/xms,
@@ -1698,6 +1699,7 @@ my %tests = (
 		regexp => qr/^
 			\QCREATE TYPE dump_test.textrange AS RANGE (\E
 			\n\s+\Qsubtype = text,\E
+			\n\s+\Qmultirange_type_name = dump_test.textmultirange,\E
 			\n\s+\Qcollation = pg_catalog."C"\E
 			\n\);/xm,
 		like =>
diff --git a/src/include/access/tupmacs.h b/src/include/access/tupmacs.h
index 70157cf90ab..c262265b951 100644
--- a/src/include/access/tupmacs.h
+++ b/src/include/access/tupmacs.h
@@ -139,8 +139,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/binary_upgrade.h b/src/include/catalog/binary_upgrade.h
index 02fecb90f79..1ff62e9f8e6 100644
--- a/src/include/catalog/binary_upgrade.h
+++ b/src/include/catalog/binary_upgrade.h
@@ -16,6 +16,8 @@
 
 extern PGDLLIMPORT Oid binary_upgrade_next_pg_type_oid;
 extern PGDLLIMPORT Oid binary_upgrade_next_array_pg_type_oid;
+extern PGDLLIMPORT Oid binary_upgrade_next_mrng_pg_type_oid;
+extern PGDLLIMPORT Oid binary_upgrade_next_mrng_array_pg_type_oid;
 
 extern PGDLLIMPORT Oid binary_upgrade_next_heap_pg_class_oid;
 extern PGDLLIMPORT Oid binary_upgrade_next_index_pg_class_oid;
diff --git a/src/include/catalog/pg_aggregate.dat b/src/include/catalog/pg_aggregate.dat
index ffabe275c00..03bab74253d 100644
--- a/src/include/catalog/pg_aggregate.dat
+++ b/src/include/catalog/pg_aggregate.dat
@@ -538,6 +538,17 @@
   aggtransfn => 'bytea_string_agg_transfn',
   aggfinalfn => 'bytea_string_agg_finalfn', aggtranstype => 'internal' },
 
+# range
+{ aggfnoid => 'range_intersect_agg(anyrange)',
+  aggtransfn => 'range_intersect_agg_transfn',
+  aggcombinefn => 'range_intersect_agg_transfn', aggtranstype => 'anyrange' },
+{ aggfnoid => 'range_intersect_agg(anymultirange)',
+  aggtransfn => 'multirange_intersect_agg_transfn',
+  aggcombinefn => 'multirange_intersect_agg_transfn', aggtranstype => 'anymultirange' },
+{ aggfnoid => 'range_agg(anyrange)', aggtransfn => 'range_agg_transfn',
+  aggfinalfn => 'range_agg_finalfn', aggfinalextra => 't',
+  aggtranstype => 'internal' },
+
 # json
 { aggfnoid => 'json_agg', aggtransfn => 'json_agg_transfn',
   aggfinalfn => 'json_agg_finalfn', aggtranstype => 'internal' },
diff --git a/src/include/catalog/pg_amop.dat b/src/include/catalog/pg_amop.dat
index 2c899f19d92..78d7d2c5232 100644
--- a/src/include/catalog/pg_amop.dat
+++ b/src/include/catalog/pg_amop.dat
@@ -1374,6 +1374,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 db3e8c2d01a..9d423d535cd 100644
--- a/src/include/catalog/pg_amproc.dat
+++ b/src/include/catalog/pg_amproc.dat
@@ -285,6 +285,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' },
 { amprocfamily => 'btree/xid8_ops', amproclefttype => 'xid8',
@@ -457,6 +459,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',
@@ -1280,12 +1287,13 @@
   amprocrighttype => 'anyrange', amprocnum => '4',
   amproc => 'brin_inclusion_union' },
 { amprocfamily => 'brin/range_inclusion_ops', amproclefttype => 'anyrange',
-  amprocrighttype => 'anyrange', amprocnum => '11', amproc => 'range_merge' },
+  amprocrighttype => 'anyrange', amprocnum => '11',
+  amproc => 'range_merge(anyrange,anyrange)' },
 { amprocfamily => 'brin/range_inclusion_ops', amproclefttype => 'anyrange',
   amprocrighttype => 'anyrange', amprocnum => '13',
   amproc => 'range_contains' },
 { amprocfamily => 'brin/range_inclusion_ops', amproclefttype => 'anyrange',
-  amprocrighttype => 'anyrange', amprocnum => '14', amproc => 'isempty' },
+  amprocrighttype => 'anyrange', amprocnum => '14', amproc => 'isempty(anyrange)' },
 
 # minmax pg_lsn
 { amprocfamily => 'brin/pg_lsn_minmax_ops', amproclefttype => 'pg_lsn',
diff --git a/src/include/catalog/pg_cast.dat b/src/include/catalog/pg_cast.dat
index 5a58f50fbb2..d6ca624addc 100644
--- a/src/include/catalog/pg_cast.dat
+++ b/src/include/catalog/pg_cast.dat
@@ -530,4 +530,17 @@
 { castsource => 'jsonb', casttarget => 'float8', castfunc => 'float8(jsonb)',
   castcontext => 'e', castmethod => 'f' },
 
+# range to multirange
+{ castsource => 'int4range', casttarget => 'int4multirange', castfunc => 'int4multirange(int4range)',
+  castcontext => 'e', castmethod => 'f' },
+{ castsource => 'int8range', casttarget => 'int8multirange', castfunc => 'int8multirange(int8range)',
+  castcontext => 'e', castmethod => 'f' },
+{ castsource => 'numrange', casttarget => 'nummultirange', castfunc => 'nummultirange(numrange)',
+  castcontext => 'e', castmethod => 'f' },
+{ castsource => 'daterange', casttarget => 'datemultirange', castfunc => 'datemultirange(daterange)',
+  castcontext => 'e', castmethod => 'f' },
+{ castsource => 'tsrange', casttarget => 'tsmultirange', castfunc => 'tsmultirange(tsrange)',
+  castcontext => 'e', castmethod => 'f' },
+{ castsource => 'tstzrange', casttarget => 'tstzmultirange', castfunc => 'tstzmultirange(tstzrange)',
+  castcontext => 'e', castmethod => 'f' },
 ]
diff --git a/src/include/catalog/pg_opclass.dat b/src/include/catalog/pg_opclass.dat
index be5712692fe..4c5e475ff7b 100644
--- a/src/include/catalog/pg_opclass.dat
+++ b/src/include/catalog/pg_opclass.dat
@@ -232,6 +232,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 7ae19235ee2..f44a826e2e9 100644
--- a/src/include/catalog/pg_operator.dat
+++ b/src/include/catalog/pg_operator.dat
@@ -3277,5 +3277,174 @@
   oprname => '@@', oprleft => 'jsonb', oprright => 'jsonpath',
   oprresult => 'bool', oprcode => 'jsonb_path_match_opr(jsonb,jsonpath)',
   oprrest => 'matchingsel', oprjoin => 'matchingjoinsel' },
+{ 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 => 'multirangesel', 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 => 'multirangesel', 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 => 'multirangesel', 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 => 'multirangesel', oprjoin => 'scalargtjoinsel' },
+{ oid => '8073', oid_symbol => 'OID_RANGE_OVERLAPS_MULTIRANGE_OP',
+  descr => 'overlaps',
+  oprname => '&&', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '&&(anymultirange,anyrange)',
+  oprcode => 'range_overlaps_multirange', oprrest => 'multirangesel',
+  oprjoin => 'areajoinsel' },
+{ oid => '8074', oid_symbol => 'OID_MULTIRANGE_OVERLAPS_RANGE_OP', descr => 'contains',
+  oprname => '&&', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcom => '&&(anyrange,anymultirange)',
+  oprcode => 'multirange_overlaps_range', oprrest => 'multirangesel',
+  oprjoin => 'areajoinsel' },
+{ oid => '8075', oid_symbol => 'OID_MULTIRANGE_OVERLAPS_MULTIRANGE_OP', descr => 'contains',
+  oprname => '&&', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '&&(anymultirange,anymultirange)',
+  oprcode => 'multirange_overlaps_multirange', oprrest => 'multirangesel',
+  oprjoin => 'areajoinsel' },
+{ oid => '8064', oid_symbol => 'OID_MULTIRANGE_CONTAINS_ELEM_OP',
+  descr => 'contains',
+  oprname => '@>', oprleft => 'anymultirange', oprright => 'anyelement',
+  oprresult => 'bool', oprcom => '<@(anyelement,anymultirange)',
+  oprcode => 'multirange_contains_elem', oprrest => 'multirangesel',
+  oprjoin => 'contjoinsel' },
+{ oid => '8065', oid_symbol => 'OID_MULTIRANGE_CONTAINS_RANGE_OP', descr => 'contains',
+  oprname => '@>', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcom => '<@(anyrange,anymultirange)',
+  oprcode => 'multirange_contains_range', oprrest => 'multirangesel',
+  oprjoin => 'contjoinsel' },
+{ oid => '8066', oid_symbol => 'OID_MULTIRANGE_CONTAINS_MULTIRANGE_OP', descr => 'contains',
+  oprname => '@>', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '<@(anymultirange,anymultirange)',
+  oprcode => 'multirange_contains_multirange', oprrest => 'multirangesel',
+  oprjoin => 'contjoinsel' },
+{ oid => '8067', oid_symbol => 'OID_MULTIRANGE_ELEM_CONTAINED_OP',
+  descr => 'is contained by',
+  oprname => '<@', oprleft => 'anyelement', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '@>(anymultirange,anyelement)',
+  oprcode => 'elem_contained_by_multirange', oprrest => 'multirangesel',
+  oprjoin => 'contjoinsel' },
+{ oid => '8068', oid_symbol => 'OID_MULTIRANGE_RANGE_CONTAINED_OP',
+  descr => 'is contained by',
+  oprname => '<@', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '@>(anymultirange,anyrange)',
+  oprcode => 'range_contained_by_multirange', oprrest => 'multirangesel',
+  oprjoin => 'contjoinsel' },
+{ oid => '8069', oid_symbol => 'OID_MULTIRANGE_MULTIRANGE_CONTAINED_OP',
+  descr => 'is contained by',
+  oprname => '<@', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '@>(anymultirange,anymultirange)',
+  oprcode => 'multirange_contained_by_multirange', oprrest => 'multirangesel',
+  oprjoin => 'contjoinsel' },
+{ oid => '8106', oid_symbol => 'OID_RANGE_OVERLAPS_LEFT_MULTIRANGE_OP',
+  descr => 'overlaps or is left of',
+  oprname => '&<', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcode => 'range_overleft_multirange', oprrest => 'multirangesel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8107', oid_symbol => 'OID_MULTIRANGE_OVERLAPS_LEFT_RANGE_OP',
+  descr => 'overlaps or is left of',
+  oprname => '&<', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcode => 'multirange_overleft_range', oprrest => 'multirangesel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8108', oid_symbol => 'OID_MULTIRANGE_OVERLAPS_LEFT_MULTIRANGE_OP',
+  descr => 'overlaps or is left of',
+  oprname => '&<', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcode => 'multirange_overleft_multirange', oprrest => 'multirangesel',
+  oprjoin => 'scalargtjoinsel' },
+{ oid => '8109', oid_symbol => 'OID_RANGE_OVERLAPS_RIGHT_MULTIRANGE_OP',
+  descr => 'overlaps or is right of',
+  oprname => '&>', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcode => 'range_overright_multirange', oprrest => 'multirangesel',
+  oprjoin => 'scalargtjoinsel' },
+{ oid => '8110', oid_symbol => 'OID_MULTIRANGE_OVERLAPS_RIGHT_RANGE_OP',
+  descr => 'overlaps or is right of',
+  oprname => '&>', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcode => 'multirange_overright_range', oprrest => 'multirangesel',
+  oprjoin => 'scalargtjoinsel' },
+{ oid => '8111', oid_symbol => 'OID_MULTIRANGE_OVERLAPS_RIGHT_MULTIRANGE_OP',
+  descr => 'overlaps or is right of',
+  oprname => '&>', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcode => 'multirange_overright_multirange', oprrest => 'multirangesel',
+  oprjoin => 'scalargtjoinsel' },
+{ oid => '8091', oid_symbol => 'OID_RANGE_ADJACENT_MULTIRANGE_OP', descr => 'is adjacent to',
+  oprname => '-|-', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '-|-(anymultirange,anyrange)',
+  oprcode => 'range_adjacent_multirange', oprrest => 'matchingsel',
+  oprjoin => 'matchingjoinsel' },
+{ oid => '8092', oid_symbol => 'OID_MULTIRANGE_ADJACENT_RANGE_OP', descr => 'is adjacent to',
+  oprname => '-|-', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcom => '-|-(anyrange,anymultirange)',
+  oprcode => 'multirange_adjacent_range', oprrest => 'matchingsel',
+  oprjoin => 'matchingjoinsel' },
+{ oid => '8093', oid_symbol => 'OID_MULTIRANGE_ADJACENT_MULTIRANGE_OP', descr => 'is adjacent to',
+  oprname => '-|-', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '-|-(anymultirange,anymultirange)',
+  oprcode => 'multirange_adjacent_multirange', oprrest => 'matchingsel',
+  oprjoin => 'matchingjoinsel' },
+{ oid => '8117', descr => 'multirange union',
+  oprname => '+', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'anymultirange', oprcom => '+(anymultirange,anymultirange)',
+  oprcode => 'multirange_union' },
+{ oid => '8123', descr => 'multirange minus',
+  oprname => '-', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'anymultirange', oprcode => 'multirange_minus' },
+{ oid => '8129', descr => 'multirange intersect',
+  oprname => '*', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'anymultirange', oprcom => '*(anymultirange,anymultirange)',
+  oprcode => 'multirange_intersect' },
+{ oid => '8082', oid_symbol => 'OID_RANGE_LEFT_MULTIRANGE_OP', descr => 'is left of',
+  oprname => '<<', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '>>(anymultirange,anyrange)',
+  oprcode => 'range_before_multirange', oprrest => 'multirangesel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8083', oid_symbol => 'OID_MULTIRANGE_LEFT_RANGE_OP', descr => 'is left of',
+  oprname => '<<', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcom => '>>(anyrange,anymultirange)',
+  oprcode => 'multirange_before_range', oprrest => 'multirangesel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8084', oid_symbol => 'OID_MULTIRANGE_LEFT_MULTIRANGE_OP', descr => 'is left of',
+  oprname => '<<', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '>>(anymultirange,anymultirange)',
+  oprcode => 'multirange_before_multirange', oprrest => 'multirangesel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8085', oid_symbol => 'OID_RANGE_RIGHT_MULTIRANGE_OP', descr => 'is right of',
+  oprname => '>>', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '<<(anymultirange,anyrange)',
+  oprcode => 'range_after_multirange', oprrest => 'multirangesel',
+  oprjoin => 'scalargtjoinsel' },
+{ oid => '8086', oid_symbol => 'OID_MULTIRANGE_RIGHT_RANGE_OP', descr => 'is right of',
+  oprname => '>>', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcom => '<<(anyrange,anymultirange)',
+  oprcode => 'multirange_after_range', oprrest => 'multirangesel',
+  oprjoin => 'scalargtjoinsel' },
+{ oid => '8087', oid_symbol => 'OID_MULTIRANGE_RIGHT_MULTIRANGE_OP', descr => 'is right of',
+  oprname => '>>', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '<<(anymultirange,anymultirange)',
+  oprcode => 'multirange_after_multirange', oprrest => 'multirangesel',
+  oprjoin => 'scalargtjoinsel' },
 
 ]
diff --git a/src/include/catalog/pg_opfamily.dat b/src/include/catalog/pg_opfamily.dat
index 11c7ad2c145..3ff81026fc7 100644
--- a/src/include/catalog/pg_opfamily.dat
+++ b/src/include/catalog/pg_opfamily.dat
@@ -232,5 +232,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 e6c7b070f64..e86ef06b9f7 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -7253,6 +7253,14 @@
   proname => 'anycompatiblerange_out', provolatile => 's',
   prorettype => 'cstring', proargtypes => 'anycompatiblerange',
   prosrc => 'anycompatiblerange_out' },
+{ oid => '8154', descr => 'I/O',
+  proname => 'anycompatiblemultirange_in', provolatile => 's',
+  prorettype => 'anycompatiblemultirange', proargtypes => 'cstring oid int4',
+  prosrc => 'anycompatiblemultirange_in' },
+{ oid => '8155', descr => 'I/O',
+  proname => 'anycompatiblemultirange_out', provolatile => 's',
+  prorettype => 'cstring', proargtypes => 'anycompatiblemultirange',
+  prosrc => 'anycompatiblemultirange_out' },
 
 # tablesample method handlers
 { oid => '3313', descr => 'BERNOULLI tablesample method handler',
@@ -9873,6 +9881,10 @@
   descr => 'the smallest range which includes both of the given ranges',
   proname => 'range_merge', prorettype => 'anyrange',
   proargtypes => 'anyrange anyrange', prosrc => 'range_merge' },
+{ oid => '8143',
+  descr => 'the smallest range which includes the whole multirange',
+  proname => 'range_merge', prorettype => 'anyrange',
+  proargtypes => 'anymultirange', prosrc => 'range_merge_from_multirange' },
 { oid => '3868',
   proname => 'range_intersect', prorettype => 'anyrange',
   proargtypes => 'anyrange anyrange', prosrc => 'range_intersect' },
@@ -9922,6 +9934,13 @@
 { oid => '3169', descr => 'restriction selectivity for range operators',
   proname => 'rangesel', provolatile => 's', prorettype => 'float8',
   proargtypes => 'internal oid internal int4', prosrc => 'rangesel' },
+{ oid => '8133', descr => 'range aggregate by intersecting',
+  proname => 'range_intersect_agg_transfn', prorettype => 'anyrange',
+  proargtypes => 'anyrange anyrange', prosrc => 'range_intersect_agg_transfn'},
+{ oid => '8134', descr => 'range aggregate by intersecting',
+  proname => 'range_intersect_agg', prokind => 'a', proisstrict => 'f',
+  prorettype => 'anyrange', proargtypes => 'anyrange',
+  prosrc => 'aggregate_dummy'},
 
 { oid => '3914', descr => 'convert an int4 range to canonical form',
   proname => 'int4range_canonical', prorettype => 'int4range',
@@ -9990,6 +10009,261 @@
   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 => '8094', descr => 'lower bound of multirange',
+  proname => 'lower', prorettype => 'anyelement', proargtypes => 'anymultirange',
+  prosrc => 'multirange_lower' },
+{ oid => '8095', descr => 'upper bound of multirange',
+  proname => 'upper', prorettype => 'anyelement', proargtypes => 'anymultirange',
+  prosrc => 'multirange_upper' },
+{ oid => '8057', descr => 'is the multirange empty?',
+  proname => 'isempty', prorettype => 'bool',
+  proargtypes => 'anymultirange', prosrc => 'multirange_empty' },
+{ oid => '8096', descr => 'is the multirange\'s lower bound inclusive?',
+  proname => 'lower_inc', prorettype => 'bool', proargtypes => 'anymultirange',
+  prosrc => 'multirange_lower_inc' },
+{ oid => '8097', descr => 'is the multirange\'s upper bound inclusive?',
+  proname => 'upper_inc', prorettype => 'bool', proargtypes => 'anymultirange',
+  prosrc => 'multirange_upper_inc' },
+{ oid => '8098', descr => 'is the multirange\'s lower bound infinite?',
+  proname => 'lower_inf', prorettype => 'bool', proargtypes => 'anymultirange',
+  prosrc => 'multirange_lower_inf' },
+{ oid => '8099', descr => 'is the multirange\'s upper bound infinite?',
+  proname => 'upper_inf', prorettype => 'bool', proargtypes => 'anymultirange',
+  prosrc => 'multirange_upper_inf' },
+{ oid => '8008', descr => 'multirange typanalyze',
+  proname => 'multirange_typanalyze', provolatile => 's', prorettype => 'bool',
+  proargtypes => 'internal', prosrc => 'multirange_typanalyze' },
+{ oid => '8156', descr => 'restriction selectivity for multirange operators',
+  proname => 'multirangesel', provolatile => 's', prorettype => 'float8',
+  proargtypes => 'internal oid internal int4', prosrc => 'multirangesel' },
+{ oid => '8033',
+  proname => 'multirange_eq', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_eq' },
+{ oid => '8034',
+  proname => 'multirange_ne', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_ne' },
+{ oid => '8070',
+  proname => 'range_overlaps_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_overlaps_multirange' },
+{ oid => '8071',
+  proname => 'multirange_overlaps_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_overlaps_range' },
+{ oid => '8072',
+  proname => 'multirange_overlaps_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_overlaps_multirange' },
+{ oid => '8058',
+  proname => 'multirange_contains_elem', prorettype => 'bool',
+  proargtypes => 'anymultirange anyelement', prosrc => 'multirange_contains_elem' },
+{ oid => '8059',
+  proname => 'multirange_contains_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_contains_range' },
+{ oid => '8060',
+  proname => 'multirange_contains_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_contains_multirange' },
+{ oid => '8061',
+  proname => 'elem_contained_by_multirange', prorettype => 'bool',
+  proargtypes => 'anyelement anymultirange', prosrc => 'elem_contained_by_multirange' },
+{ oid => '8062',
+  proname => 'range_contained_by_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_contained_by_multirange' },
+{ oid => '8063',
+  proname => 'multirange_contained_by_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_contained_by_multirange' },
+{ oid => '8088',
+  proname => 'range_adjacent_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_adjacent_multirange' },
+{ oid => '8089',
+  proname => 'multirange_adjacent_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_adjacent_multirange' },
+{ oid => '8090',
+  proname => 'multirange_adjacent_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_adjacent_range' },
+{ oid => '8076',
+  proname => 'range_before_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_before_multirange' },
+{ oid => '8077',
+  proname => 'multirange_before_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_before_range' },
+{ oid => '8078',
+  proname => 'multirange_before_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_before_multirange' },
+{ oid => '8079',
+  proname => 'range_after_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_after_multirange' },
+{ oid => '8080',
+  proname => 'multirange_after_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_after_range' },
+{ oid => '8081',
+  proname => 'multirange_after_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_after_multirange' },
+{ oid => '8100',
+  proname => 'range_overleft_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_overleft_multirange' },
+{ oid => '8101',
+  proname => 'multirange_overleft_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_overleft_range' },
+{ oid => '8102',
+  proname => 'multirange_overleft_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_overleft_multirange' },
+{ oid => '8103',
+  proname => 'range_overright_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_overright_multirange' },
+{ oid => '8104',
+  proname => 'multirange_overright_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_overright_range' },
+{ oid => '8105',
+  proname => 'multirange_overright_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_overright_multirange' },
+{ oid => '8114',
+  proname => 'multirange_union', prorettype => 'anymultirange',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_union' },
+{ oid => '8120',
+  proname => 'multirange_minus', prorettype => 'anymultirange',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_minus' },
+{ oid => '8126',
+  proname => 'multirange_intersect', prorettype => 'anymultirange',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_intersect' },
+{ oid => '8022', descr => 'less-equal-greater',
+  proname => 'multirange_cmp', prorettype => 'int4',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_cmp' },
+{ oid => '8023',
+  proname => 'multirange_lt', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_lt' },
+{ oid => '8024',
+  proname => 'multirange_le', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_le' },
+{ oid => '8025',
+  proname => 'multirange_ge', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_ge' },
+{ oid => '8026',
+  proname => 'multirange_gt', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_gt' },
+{ oid => '8038', descr => 'hash a multirange',
+  proname => 'hash_multirange', prorettype => 'int4', proargtypes => 'anymultirange',
+  prosrc => 'hash_multirange' },
+{ oid => '8039', descr => 'hash a multirange',
+  proname => 'hash_multirange_extended', prorettype => 'int8',
+  proargtypes => 'anymultirange int8', prosrc => 'hash_multirange_extended' },
+
+{ oid => '8045', descr => 'int4multirange constructor',
+  proname => 'int4multirange', proisstrict => 'f',
+  prorettype => 'int4multirange', proargtypes => '',
+  prosrc => 'multirange_constructor0' },
+{ oid => '8146', descr => 'int4multirange constructor',
+  proname => 'int4multirange', proisstrict => 't',
+  prorettype => 'int4multirange', proargtypes => 'int4range',
+  prosrc => 'multirange_constructor1' },
+{ oid => '8046', descr => 'int4multirange constructor',
+  proname => 'int4multirange', provariadic => 'int4range', proisstrict => 'f',
+  prorettype => 'int4multirange', proargtypes => '_int4range',
+  proallargtypes => '{_int4range}', proargmodes => '{v}',
+  prosrc => 'multirange_constructor2' },
+{ oid => '8047', descr => 'nummultirange constructor',
+  proname => 'nummultirange', proisstrict => 'f',
+  prorettype => 'nummultirange', proargtypes => '',
+  prosrc => 'multirange_constructor0' },
+{ oid => '8147', descr => 'nummultirange constructor',
+  proname => 'nummultirange', proisstrict => 't',
+  prorettype => 'nummultirange', proargtypes => 'numrange',
+  prosrc => 'multirange_constructor1' },
+{ oid => '8048', descr => 'nummultirange constructor',
+  proname => 'nummultirange', provariadic => 'numrange', proisstrict => 'f',
+  prorettype => 'nummultirange', proargtypes => '_numrange',
+  proallargtypes => '{_numrange}', proargmodes => '{v}',
+  prosrc => 'multirange_constructor2' },
+{ oid => '8049', descr => 'tsmultirange constructor',
+  proname => 'tsmultirange', proisstrict => 'f',
+  prorettype => 'tsmultirange', proargtypes => '',
+  prosrc => 'multirange_constructor0' },
+{ oid => '8148', descr => 'tsmultirange constructor',
+  proname => 'tsmultirange', proisstrict => 't',
+  prorettype => 'tsmultirange', proargtypes => 'tsrange',
+  prosrc => 'multirange_constructor1' },
+{ oid => '8050', descr => 'tsmultirange constructor',
+  proname => 'tsmultirange', provariadic => 'tsrange', proisstrict => 'f',
+  prorettype => 'tsmultirange', proargtypes => '_tsrange',
+  proallargtypes => '{_tsrange}', proargmodes => '{v}',
+  prosrc => 'multirange_constructor2' },
+{ oid => '8051', descr => 'tstzmultirange constructor',
+  proname => 'tstzmultirange', proisstrict => 'f',
+  prorettype => 'tstzmultirange', proargtypes => '',
+  prosrc => 'multirange_constructor0' },
+{ oid => '8149', descr => 'tstzmultirange constructor',
+  proname => 'tstzmultirange', proisstrict => 't',
+  prorettype => 'tstzmultirange', proargtypes => 'tstzrange',
+  prosrc => 'multirange_constructor1' },
+{ oid => '8052', descr => 'tstzmultirange constructor',
+  proname => 'tstzmultirange', provariadic => 'tstzrange', proisstrict => 'f',
+  prorettype => 'tstzmultirange', proargtypes => '_tstzrange',
+  proallargtypes => '{_tstzrange}', proargmodes => '{v}',
+  prosrc => 'multirange_constructor2' },
+{ oid => '8053', descr => 'datemultirange constructor',
+  proname => 'datemultirange', proisstrict => 'f',
+  prorettype => 'datemultirange', proargtypes => '',
+  prosrc => 'multirange_constructor0' },
+{ oid => '8150', descr => 'datemultirange constructor',
+  proname => 'datemultirange', proisstrict => 't',
+  prorettype => 'datemultirange', proargtypes => 'daterange',
+  prosrc => 'multirange_constructor1' },
+{ oid => '8054', descr => 'datemultirange constructor',
+  proname => 'datemultirange', provariadic => 'daterange', proisstrict => 'f',
+  prorettype => 'datemultirange', proargtypes => '_daterange',
+  proallargtypes => '{_daterange}', proargmodes => '{v}',
+  prosrc => 'multirange_constructor2' },
+{ oid => '8055', descr => 'int8multirange constructor',
+  proname => 'int8multirange', proisstrict => 'f',
+  prorettype => 'int8multirange', proargtypes => '',
+  prosrc => 'multirange_constructor0' },
+{ oid => '8151', descr => 'int8multirange constructor',
+  proname => 'int8multirange', proisstrict => 't',
+  prorettype => 'int8multirange', proargtypes => 'int8range',
+  prosrc => 'multirange_constructor1' },
+{ oid => '8056', descr => 'int8multirange constructor',
+  proname => 'int8multirange', provariadic => 'int8range', proisstrict => 'f',
+  prorettype => 'int8multirange', proargtypes => '_int8range',
+  proallargtypes => '{_int8range}', proargmodes => '{v}',
+  prosrc => 'multirange_constructor2' },
+{ oid => '8152', descr => 'anymultirange cast',
+  proname => 'multirange', proisstrict => 't',
+  prorettype => 'anymultirange', proargtypes => 'anyrange',
+  prosrc => 'multirange_constructor1' },
+{ oid => '8130', descr => 'aggregate transition function',
+  proname => 'range_agg_transfn', proisstrict => 'f', prorettype => 'internal',
+  proargtypes => 'internal anyrange', prosrc => 'range_agg_transfn' },
+{ oid => '8131', descr => 'aggregate final function',
+  proname => 'range_agg_finalfn', proisstrict => 'f', prorettype => 'anymultirange',
+  proargtypes => 'internal anyrange', prosrc => 'range_agg_finalfn' },
+{ oid => '8132', descr => 'combine aggregate input into a multirange',
+  proname => 'range_agg', prokind => 'a', proisstrict => 'f',
+  prorettype => 'anymultirange', proargtypes => 'anyrange',
+  prosrc => 'aggregate_dummy' },
+{ oid => '8135', descr => 'range aggregate by intersecting',
+  proname => 'multirange_intersect_agg_transfn', prorettype => 'anymultirange',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_intersect_agg_transfn'},
+{ oid => '8136', descr => 'range aggregate by intersecting',
+  proname => 'range_intersect_agg', prokind => 'a', proisstrict => 'f',
+  prorettype => 'anymultirange', proargtypes => 'anymultirange',
+  prosrc => 'aggregate_dummy'},
+
 # date, time, timestamp constructors
 { oid => '3846', descr => 'construct date',
   proname => 'make_date', prorettype => 'date', proargtypes => 'int4 int4 int4',
@@ -10372,6 +10646,14 @@
   proname => 'binary_upgrade_set_next_array_pg_type_oid', provolatile => 'v',
   proparallel => 'r', prorettype => 'void', proargtypes => 'oid',
   prosrc => 'binary_upgrade_set_next_array_pg_type_oid' },
+{ oid => '8144', descr => 'for use by pg_upgrade',
+  proname => 'binary_upgrade_set_next_multirange_pg_type_oid', provolatile => 'v',
+  proparallel => 'r', prorettype => 'void', proargtypes => 'oid',
+  prosrc => 'binary_upgrade_set_next_multirange_pg_type_oid' },
+{ oid => '8145', descr => 'for use by pg_upgrade',
+  proname => 'binary_upgrade_set_next_multirange_array_pg_type_oid', provolatile => 'v',
+  proparallel => 'r', prorettype => 'void', proargtypes => 'oid',
+  prosrc => 'binary_upgrade_set_next_multirange_array_pg_type_oid' },
 { oid => '3586', descr => 'for use by pg_upgrade',
   proname => 'binary_upgrade_set_next_heap_pg_class_oid', provolatile => 'v',
   proparallel => 'r', prorettype => 'void', proargtypes => 'oid',
diff --git a/src/include/catalog/pg_range.dat b/src/include/catalog/pg_range.dat
index 479754c2455..10060255c95 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',
+  rngmultitypid => 'int4multirange' },
 { rngtypid => 'numrange', rngsubtype => 'numeric',
   rngsubopc => 'btree/numeric_ops', rngcanonical => '-',
-  rngsubdiff => 'numrange_subdiff' },
+  rngsubdiff => 'numrange_subdiff', rngmultitypid => 'nummultirange' },
 { rngtypid => 'tsrange', rngsubtype => 'timestamp',
   rngsubopc => 'btree/timestamp_ops', rngcanonical => '-',
-  rngsubdiff => 'tsrange_subdiff' },
+  rngsubdiff => 'tsrange_subdiff', rngmultitypid => 'tsmultirange' },
 { rngtypid => 'tstzrange', rngsubtype => 'timestamptz',
   rngsubopc => 'btree/timestamptz_ops', rngcanonical => '-',
-  rngsubdiff => 'tstzrange_subdiff' },
+  rngsubdiff => 'tstzrange_subdiff', rngmultitypid => 'tstzmultirange' },
 { rngtypid => 'daterange', rngsubtype => 'date', rngsubopc => 'btree/date_ops',
-  rngcanonical => 'daterange_canonical', rngsubdiff => 'daterange_subdiff' },
+  rngcanonical => 'daterange_canonical', rngsubdiff => 'daterange_subdiff',
+  rngmultitypid => 'datemultirange' },
 { rngtypid => 'int8range', rngsubtype => 'int8', rngsubopc => 'btree/int8_ops',
-  rngcanonical => 'int8range_canonical', rngsubdiff => 'int8range_subdiff' },
+  rngcanonical => 'int8range_canonical', rngsubdiff => 'int8range_subdiff',
+  rngmultitypid => 'int8multirange' },
 
 ]
diff --git a/src/include/catalog/pg_range.h b/src/include/catalog/pg_range.h
index c28bb57b6c9..07e6dbba667 100644
--- a/src/include/catalog/pg_range.h
+++ b/src/include/catalog/pg_range.h
@@ -34,6 +34,9 @@ CATALOG(pg_range,3541,RangeRelationId)
 	/* OID of range's element type (subtype) */
 	Oid			rngsubtype BKI_LOOKUP(pg_type);
 
+	/* OID of the range's multirange type */
+	Oid			rngmultitypid BKI_LOOKUP(pg_type);
+
 	/* collation for this range type, or 0 */
 	Oid			rngcollation BKI_DEFAULT(0);
 
@@ -57,13 +60,17 @@ typedef FormData_pg_range *Form_pg_range;
 DECLARE_UNIQUE_INDEX(pg_range_rngtypid_index, 3542, on pg_range using btree(rngtypid oid_ops));
 #define RangeTypidIndexId					3542
 
+DECLARE_UNIQUE_INDEX(pg_range_rngmultitypid_index, 8001, on pg_range using btree(rngmultitypid oid_ops));
+#define RangeMultirangeTypidIndexId			8001
+
+
 /*
  * prototypes for functions in pg_range.c
  */
 
 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 28240bdce39..da8c5a3e9b3 100644
--- a/src/include/catalog/pg_type.dat
+++ b/src/include/catalog/pg_type.dat
@@ -496,6 +496,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 => 'R', 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 => 'R', 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 => 'R', 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 => 'R', 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 => 'R', 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 => 'R', 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
@@ -626,5 +660,16 @@
   typtype => 'p', typcategory => 'P', typinput => 'anycompatiblerange_in',
   typoutput => 'anycompatiblerange_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' },
+{ oid => '8153',
+  descr => 'pseudo-type representing a multirange over a polymorphic common type',
+  typname => 'anycompatiblemultirange', typlen => '-1', typbyval => 'f',
+  typtype => 'p', typcategory => 'P', typinput => 'anycompatiblemultirange_in',
+  typoutput => 'anycompatiblemultirange_out', typreceive => '-', typsend => '-',
+  typalign => 'd', typstorage => 'x' },
 
 ]
diff --git a/src/include/catalog/pg_type.h b/src/include/catalog/pg_type.h
index 70563a6408b..545b789608a 100644
--- a/src/include/catalog/pg_type.h
+++ b/src/include/catalog/pg_type.h
@@ -276,6 +276,7 @@ DECLARE_UNIQUE_INDEX(pg_type_typname_nsp_index, 2704, on pg_type using btree(typ
 #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 */
 
@@ -317,13 +318,15 @@ DECLARE_UNIQUE_INDEX(pg_type_typname_nsp_index, 2704, on pg_type using btree(typ
 	 (typid) == ANYARRAYOID || \
 	 (typid) == ANYNONARRAYOID || \
 	 (typid) == ANYENUMOID || \
-	 (typid) == ANYRANGEOID)
+	 (typid) == ANYRANGEOID || \
+	 (typid) == ANYMULTIRANGEOID)
 
 #define IsPolymorphicTypeFamily2(typid)  \
 	((typid) == ANYCOMPATIBLEOID || \
 	 (typid) == ANYCOMPATIBLEARRAYOID || \
 	 (typid) == ANYCOMPATIBLENONARRAYOID || \
-	 (typid) == ANYCOMPATIBLERANGEOID)
+	 (typid) == ANYCOMPATIBLERANGEOID || \
+	 (typid) == ANYCOMPATIBLEMULTIRANGEOID)
 
 /* Is this a "true" array type?  (Requires fmgroids.h) */
 #define IsTrueArrayType(typeForm)  \
@@ -397,4 +400,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/commands/typecmds.h b/src/include/commands/typecmds.h
index 23130895af4..989914dfe11 100644
--- a/src/include/commands/typecmds.h
+++ b/src/include/commands/typecmds.h
@@ -29,6 +29,8 @@ extern ObjectAddress DefineRange(CreateRangeStmt *stmt);
 extern ObjectAddress AlterEnum(AlterEnumStmt *stmt);
 extern ObjectAddress DefineCompositeType(RangeVar *typevar, List *coldeflist);
 extern Oid	AssignTypeArrayOid(void);
+extern Oid	AssignTypeMultirangeOid(void);
+extern Oid	AssignTypeMultirangeArrayOid(void);
 
 extern ObjectAddress AlterDomainDefault(List *names, Node *defaultRaw);
 extern ObjectAddress AlterDomainNotNull(List *names, bool notNull);
diff --git a/src/include/utils/lsyscache.h b/src/include/utils/lsyscache.h
index a990d11ea86..e5ad7b95d17 100644
--- a/src/include/utils/lsyscache.h
+++ b/src/include/utils/lsyscache.h
@@ -161,6 +161,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);
@@ -190,6 +191,8 @@ 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_collation(Oid rangeOid);
+extern Oid	get_range_multirange(Oid rangeOid);
+extern Oid	get_multirange_range(Oid multirangeOid);
 extern Oid	get_index_column_opclass(Oid index_oid, int attno);
 extern bool get_index_isreplident(Oid index_oid);
 extern bool get_index_isvalid(Oid index_oid);
diff --git a/src/include/utils/multirangetypes.h b/src/include/utils/multirangetypes.h
new file mode 100644
index 00000000000..ba70f9f4f1c
--- /dev/null
+++ b/src/include/utils/multirangetypes.h
@@ -0,0 +1,107 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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"
+#include "utils/expandeddatum.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
+{
+	int32		vl_len_;		/* varlena header (do not touch directly!) */
+	Oid			multirangetypid;	/* multirange type's own OID */
+	uint32		rangeCount;		/* the number of ranges */
+
+	/*
+	 * Following the count are the range objects themselves, as ShortRangeType
+	 * structs. 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 these macros in preference to accessing these fields 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);
+extern bool multirange_contains_elem_internal(TypeCacheEntry *typcache, MultirangeType *mr,
+											  Datum elem);
+extern bool multirange_contains_range_internal(TypeCacheEntry *typcache, MultirangeType *mr,
+											   RangeType *r);
+extern bool multirange_contains_multirange_internal(TypeCacheEntry *typcache,
+													MultirangeType *mr1,
+													MultirangeType *mr2);
+extern bool range_overlaps_multirange_internal(TypeCacheEntry *typcache, RangeType *r,
+											   MultirangeType *mr);
+extern bool multirange_overlaps_multirange_internal(TypeCacheEntry *typcache,
+													MultirangeType *mr1,
+													MultirangeType *mr2);
+extern bool range_before_multirange_internal(TypeCacheEntry *typcache, RangeType *r,
+											 MultirangeType *mr);
+extern bool range_after_multirange_internal(TypeCacheEntry *typcache, RangeType *r,
+											MultirangeType *mr);
+extern bool range_adjacent_multirange_internal(TypeCacheEntry *typcache, RangeType *r,
+											MultirangeType *mr);
+extern bool multirange_before_multirange_internal(TypeCacheEntry *typcache,
+												  MultirangeType *mr1,
+												  MultirangeType *mr2);
+extern MultirangeType *multirange_minus_internal(Oid mltrngtypoid,
+												 TypeCacheEntry *rangetyp,
+												 int32 range_count1,
+												 RangeType **ranges1,
+												 int32 range_count2,
+												 RangeType **ranges2);
+extern MultirangeType *multirange_intersect_internal(Oid mltrngtypoid,
+													 TypeCacheEntry *rangetyp,
+													 int32 range_count1,
+													 RangeType **ranges1,
+													 int32 range_count2,
+													 RangeType **ranges2);
+
+/* assorted support functions */
+extern TypeCacheEntry *multirange_get_typcache(FunctionCallInfo fcinfo,
+											   Oid mltrngtypid);
+extern void multirange_deserialize(TypeCacheEntry *rangetyp,
+								   const 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 b77c41cf1b8..139e19c7d66 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)
 
@@ -92,13 +94,21 @@ typedef struct
  * prototypes for functions defined in rangetypes.c
  */
 
-extern bool range_contains_elem_internal(TypeCacheEntry *typcache, const RangeType *r, Datum val);
-
 /* internal versions of the above */
+extern int	range_cmp_internal(TypeCacheEntry *typcache, const RangeType *r1,
+							   const RangeType *r2);
+extern uint32 hash_range_internal(TypeCacheEntry *typcache, const RangeType *r);
+extern uint64 hash_range_extended_internal(TypeCacheEntry *typcache, const RangeType *r,
+										   Datum seed);
+extern Datum range_lower_internal(TypeCacheEntry *typcache, const RangeType *r1,
+								  bool *isnull);
+extern Datum range_upper_internal(TypeCacheEntry *typcache, const RangeType *r1,
+								  bool *isnull);
 extern bool range_eq_internal(TypeCacheEntry *typcache, const RangeType *r1,
 							  const RangeType *r2);
 extern bool range_ne_internal(TypeCacheEntry *typcache, const RangeType *r1,
 							  const RangeType *r2);
+extern bool range_contains_elem_internal(TypeCacheEntry *typcache, const RangeType *r, Datum val);
 extern bool range_contains_internal(TypeCacheEntry *typcache, const RangeType *r1,
 									const RangeType *r2);
 extern bool range_contained_by_internal(TypeCacheEntry *typcache, const RangeType *r1,
@@ -115,8 +125,18 @@ extern bool range_overleft_internal(TypeCacheEntry *typcache, const RangeType *r
 									const RangeType *r2);
 extern bool range_overright_internal(TypeCacheEntry *typcache, const RangeType *r1,
 									 const RangeType *r2);
+extern RangeType *range_union_internal(TypeCacheEntry *typcache, RangeType *r1,
+									   RangeType *r2, bool strict);
+extern RangeType *range_minus_internal(TypeCacheEntry *typcache, RangeType *r1,
+									   RangeType *r2);
+extern RangeType *range_intersect_internal(TypeCacheEntry *typcache, const RangeType *r1,
+										   const RangeType *r2);
 
 /* assorted support functions */
+extern Size datum_compute_size(Size sz, Datum datum, bool typbyval,
+							   char typalign, int16 typlen, char typstorage);
+extern Pointer datum_write(Pointer ptr, Datum datum, bool typbyval,
+						   char typalign, int16 typlen, char typstorage);
 extern TypeCacheEntry *range_get_typcache(FunctionCallInfo fcinfo,
 										  Oid rngtypid);
 extern RangeType *range_serialize(TypeCacheEntry *typcache, RangeBound *lower,
@@ -125,6 +145,7 @@ extern void range_deserialize(TypeCacheEntry *typcache, const RangeType *range,
 							  RangeBound *lower, RangeBound *upper,
 							  bool *empty);
 extern char range_get_flags(const RangeType *range);
+extern bool range_has_flag(const RangeType *range, char flag);
 extern void range_set_contain_empty(RangeType *range);
 extern RangeType *make_range(TypeCacheEntry *typcache, RangeBound *lower,
 							 RangeBound *upper, bool empty);
@@ -132,8 +153,12 @@ extern int	range_cmp_bounds(TypeCacheEntry *typcache, const RangeBound *b1,
 							 const RangeBound *b2);
 extern int	range_cmp_bound_values(TypeCacheEntry *typcache, const RangeBound *b1,
 								   const RangeBound *b2);
+extern int	range_compare(const void *key1, const void *key2, void *arg);
 extern bool bounds_adjacent(TypeCacheEntry *typcache, RangeBound bound1,
 							RangeBound bound2);
 extern RangeType *make_empty_range(TypeCacheEntry *typcache);
+extern bool range_split_internal(TypeCacheEntry *typcache, const RangeType *r1,
+								 const RangeType *r2, RangeType **output1,
+								 RangeType **output2);
 
 #endif							/* RANGETYPES_H */
diff --git a/src/include/utils/selfuncs.h b/src/include/utils/selfuncs.h
index 3a2cfb7efa6..68ffd7761f1 100644
--- a/src/include/utils/selfuncs.h
+++ b/src/include/utils/selfuncs.h
@@ -39,6 +39,9 @@
 /* default selectivity estimate for range inequalities "A > b AND A < c" */
 #define DEFAULT_RANGE_INEQ_SEL	0.005
 
+/* default selectivity estimate for multirange inequalities "A > b AND A < c" */
+#define DEFAULT_MULTIRANGE_INEQ_SEL	0.005
+
 /* default selectivity estimate for pattern-match operators such as LIKE */
 #define DEFAULT_MATCH_SEL	0.005
 
diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h
index f27b73d76d0..e8f393520a3 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -79,6 +79,7 @@ enum SysCacheIdentifier
 	PUBLICATIONOID,
 	PUBLICATIONREL,
 	PUBLICATIONRELMAP,
+	RANGEMULTIRANGE,
 	RANGETYPE,
 	RELNAMENSP,
 	RELOID,
diff --git a/src/include/utils/typcache.h b/src/include/utils/typcache.h
index 38c8fe01929..dd69a06342f 100644
--- a/src/include/utils/typcache.h
+++ b/src/include/utils/typcache.h
@@ -101,6 +101,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; /* multirange's range underlying type */
+
 	/*
 	 * Domain's base type and typmod if it's a domain type.  Zeroes if not
 	 * domain, or if information hasn't been requested.
@@ -128,22 +133,23 @@ typedef struct TypeCacheEntry
 } TypeCacheEntry;
 
 /* Bit flags to indicate which fields a given caller needs to have set */
-#define TYPECACHE_EQ_OPR			0x0001
-#define TYPECACHE_LT_OPR			0x0002
-#define TYPECACHE_GT_OPR			0x0004
-#define TYPECACHE_CMP_PROC			0x0008
-#define TYPECACHE_HASH_PROC			0x0010
-#define TYPECACHE_EQ_OPR_FINFO		0x0020
-#define TYPECACHE_CMP_PROC_FINFO	0x0040
-#define TYPECACHE_HASH_PROC_FINFO	0x0080
-#define TYPECACHE_TUPDESC			0x0100
-#define TYPECACHE_BTREE_OPFAMILY	0x0200
-#define TYPECACHE_HASH_OPFAMILY		0x0400
-#define TYPECACHE_RANGE_INFO		0x0800
-#define TYPECACHE_DOMAIN_BASE_INFO			0x1000
-#define TYPECACHE_DOMAIN_CONSTR_INFO		0x2000
-#define TYPECACHE_HASH_EXTENDED_PROC		0x4000
-#define TYPECACHE_HASH_EXTENDED_PROC_FINFO	0x8000
+#define TYPECACHE_EQ_OPR			0x00001
+#define TYPECACHE_LT_OPR			0x00002
+#define TYPECACHE_GT_OPR			0x00004
+#define TYPECACHE_CMP_PROC			0x00008
+#define TYPECACHE_HASH_PROC			0x00010
+#define TYPECACHE_EQ_OPR_FINFO		0x00020
+#define TYPECACHE_CMP_PROC_FINFO	0x00040
+#define TYPECACHE_HASH_PROC_FINFO	0x00080
+#define TYPECACHE_TUPDESC			0x00100
+#define TYPECACHE_BTREE_OPFAMILY	0x00200
+#define TYPECACHE_HASH_OPFAMILY		0x00400
+#define TYPECACHE_RANGE_INFO		0x00800
+#define TYPECACHE_DOMAIN_BASE_INFO			0x01000
+#define TYPECACHE_DOMAIN_CONSTR_INFO		0x02000
+#define TYPECACHE_HASH_EXTENDED_PROC		0x04000
+#define TYPECACHE_HASH_EXTENDED_PROC_FINFO	0x08000
+#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 555da952e1b..042deb2a96c 100644
--- a/src/pl/plpgsql/src/pl_comp.c
+++ b/src/pl/plpgsql/src/pl_comp.c
@@ -515,6 +515,8 @@ do_compile(FunctionCallInfo fcinfo,
 					else if (rettypeid == ANYRANGEOID ||
 							 rettypeid == ANYCOMPATIBLERANGEOID)
 						rettypeid = INT4RANGEOID;
+					else if (rettypeid == ANYMULTIRANGEOID)
+						rettypeid = INT4MULTIRANGEOID;
 					else		/* ANYELEMENT or ANYNONARRAY or ANYCOMPATIBLE */
 						rettypeid = INT4OID;
 					/* XXX what could we use for ANYENUM? */
@@ -2109,6 +2111,7 @@ build_datatype(HeapTuple typeTup, int32 typmod,
 		case TYPTYPE_BASE:
 		case TYPTYPE_ENUM:
 		case TYPTYPE_RANGE:
+		case TYPTYPE_MULTIRANGE:
 			typ->ttype = PLPGSQL_TTYPE_SCALAR;
 			break;
 		case TYPTYPE_COMPOSITE:
@@ -2526,6 +2529,9 @@ plpgsql_resolve_polymorphic_argtypes(int numargs,
 				case ANYCOMPATIBLERANGEOID:
 					argtypes[i] = INT4RANGEOID;
 					break;
+				case ANYMULTIRANGEOID:
+					argtypes[i] = INT4MULTIRANGEOID;
+					break;
 				default:
 					break;
 			}
diff --git a/src/test/regress/expected/dependency.out b/src/test/regress/expected/dependency.out
index b4a11b8aa9b..82327951487 100644
--- a/src/test/regress/expected/dependency.out
+++ b/src/test/regress/expected/dependency.out
@@ -140,6 +140,7 @@ owner of sequence deptest_a_seq
 owner of table deptest
 owner of function deptest_func()
 owner of type deptest_enum
+owner of type deptest_multirange
 owner of type deptest_range
 owner of table deptest2
 owner of sequence ss1
diff --git a/src/test/regress/expected/hash_func.out b/src/test/regress/expected/hash_func.out
index 827e69fc7ab..dca31365bcf 100644
--- a/src/test/regress/expected/hash_func.out
+++ b/src/test/regress/expected/hash_func.out
@@ -305,6 +305,19 @@ 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)
+
 CREATE TYPE hash_test_t1 AS (a int, b text);
 SELECT v as value, hash_record(v)::bit(32) as standard,
        hash_record_extended(v, 0)::bit(32) as extended0,
diff --git a/src/test/regress/expected/multirangetypes.out b/src/test/regress/expected/multirangetypes.out
new file mode 100644
index 00000000000..fa34fcd5bcb
--- /dev/null
+++ b/src/test/regress/expected/multirangetypes.out
@@ -0,0 +1,2443 @@
+-- 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)
+
+--
+-- test casts, both a built-in range type and a user-defined one:
+--
+select 'empty'::int4range::int4multirange;
+ int4multirange 
+----------------
+ {}
+(1 row)
+
+select int4range(1, 3)::int4multirange;
+ int4range 
+-----------
+ {[1,3)}
+(1 row)
+
+select int4range(1, null)::int4multirange;
+ int4range 
+-----------
+ {[1,)}
+(1 row)
+
+select int4range(null, null)::int4multirange;
+ int4range 
+-----------
+ {(,)}
+(1 row)
+
+select 'empty'::textrange::textmultirange;
+ textmultirange 
+----------------
+ {}
+(1 row)
+
+select textrange('a', 'c')::textmultirange;
+ textrange 
+-----------
+ {[a,c)}
+(1 row)
+
+select textrange('a', null)::textmultirange;
+ textrange 
+-----------
+ {[a,)}
+(1 row)
+
+select textrange(null, null)::textmultirange;
+ textrange 
+-----------
+ {(,)}
+(1 row)
+
+--
+-- create some test data and test the operators
+--
+CREATE TABLE nummultirange_test (nmr NUMMULTIRANGE);
+CREATE INDEX nummultirange_test_btree ON nummultirange_test(nmr);
+INSERT INTO nummultirange_test VALUES('{}');
+INSERT INTO nummultirange_test VALUES('{[,)}');
+INSERT INTO nummultirange_test VALUES('{[3,]}');
+INSERT INTO nummultirange_test VALUES('{[,), [3,]}');
+INSERT INTO nummultirange_test VALUES('{[, 5)}');
+INSERT INTO nummultirange_test VALUES(nummultirange());
+INSERT INTO nummultirange_test VALUES(nummultirange(variadic '{}'::numrange[]));
+INSERT INTO nummultirange_test VALUES(nummultirange(numrange(1.1, 2.2)));
+INSERT INTO nummultirange_test VALUES('{empty}');
+INSERT INTO nummultirange_test VALUES(nummultirange(numrange(1.7, 1.7, '[]'), numrange(1.7, 1.9)));
+INSERT INTO nummultirange_test VALUES(nummultirange(numrange(1.7, 1.7, '[]'), numrange(1.9, 2.1)));
+SELECT nmr, isempty(nmr), lower(nmr), upper(nmr) FROM nummultirange_test ORDER BY nmr;
+          nmr          | isempty | lower | upper 
+-----------------------+---------+-------+-------
+ {}                    | t       |       |      
+ {}                    | t       |       |      
+ {}                    | t       |       |      
+ {}                    | t       |       |      
+ {(,5)}                | f       |       |     5
+ {(,)}                 | f       |       |      
+ {(,)}                 | f       |       |      
+ {[1.1,2.2)}           | f       |   1.1 |   2.2
+ {[1.7,1.7],[1.9,2.1)} | f       |   1.7 |   2.1
+ {[1.7,1.9)}           | f       |   1.7 |   1.9
+ {[3,)}                | f       |     3 |      
+(11 rows)
+
+SELECT nmr, lower_inc(nmr), lower_inf(nmr), upper_inc(nmr), upper_inf(nmr) FROM nummultirange_test ORDER BY nmr;
+          nmr          | lower_inc | lower_inf | upper_inc | upper_inf 
+-----------------------+-----------+-----------+-----------+-----------
+ {}                    | f         | f         | f         | f
+ {}                    | f         | f         | f         | f
+ {}                    | f         | f         | f         | f
+ {}                    | f         | f         | f         | f
+ {(,5)}                | f         | t         | f         | f
+ {(,)}                 | f         | t         | f         | t
+ {(,)}                 | f         | t         | f         | t
+ {[1.1,2.2)}           | t         | f         | f         | f
+ {[1.7,1.7],[1.9,2.1)} | t         | f         | f         | f
+ {[1.7,1.9)}           | t         | f         | f         | f
+ {[3,)}                | t         | f         | f         | t
+(11 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr = '{}';
+ nmr 
+-----
+ {}
+ {}
+ {}
+ {}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr = '{(,5)}';
+  nmr   
+--------
+ {(,5)}
+(1 row)
+
+SELECT * FROM nummultirange_test WHERE nmr = '{[3,)}';
+  nmr   
+--------
+ {[3,)}
+(1 row)
+
+SELECT * FROM nummultirange_test WHERE nmr = '{[1.7,1.7]}';
+ nmr 
+-----
+(0 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr = '{[1.7,1.7],[1.9,2.1)}';
+          nmr          
+-----------------------
+ {[1.7,1.7],[1.9,2.1)}
+(1 row)
+
+SELECT * FROM nummultirange_test WHERE nmr < '{}';
+ nmr 
+-----
+(0 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr < '{[-1000.0, -1000.0]}';
+  nmr   
+--------
+ {}
+ {(,)}
+ {(,)}
+ {(,5)}
+ {}
+ {}
+ {}
+(7 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr < '{[0.0, 1.0]}';
+  nmr   
+--------
+ {}
+ {(,)}
+ {(,)}
+ {(,5)}
+ {}
+ {}
+ {}
+(7 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr < '{[1000.0, 1001.0]}';
+          nmr          
+-----------------------
+ {}
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+ {}
+ {}
+ {[1.1,2.2)}
+ {}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(11 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr <= '{}';
+ nmr 
+-----
+ {}
+ {}
+ {}
+ {}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr <= '{[3,)}';
+          nmr          
+-----------------------
+ {}
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+ {}
+ {}
+ {[1.1,2.2)}
+ {}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(11 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr >= '{}';
+          nmr          
+-----------------------
+ {}
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+ {}
+ {}
+ {[1.1,2.2)}
+ {}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(11 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr >= '{[3,)}';
+  nmr   
+--------
+ {[3,)}
+(1 row)
+
+SELECT * FROM nummultirange_test WHERE nmr > '{}';
+          nmr          
+-----------------------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+ {[1.1,2.2)}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(7 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr > '{[-1000.0, -1000.0]}';
+          nmr          
+-----------------------
+ {[3,)}
+ {[1.1,2.2)}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr > '{[0.0, 1.0]}';
+          nmr          
+-----------------------
+ {[3,)}
+ {[1.1,2.2)}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr > '{[1000.0, 1001.0]}';
+ nmr 
+-----
+(0 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr <> '{}';
+          nmr          
+-----------------------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+ {[1.1,2.2)}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(7 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr <> '{(,5)}';
+          nmr          
+-----------------------
+ {}
+ {(,)}
+ {[3,)}
+ {(,)}
+ {}
+ {}
+ {[1.1,2.2)}
+ {}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(10 rows)
+
+select nummultirange(numrange(2.0, 1.0));
+ERROR:  range lower bound must be less than or equal to range upper bound
+select nummultirange(numrange(5.0, 6.0), numrange(1.0, 2.0));
+     nummultirange     
+-----------------------
+ {[1.0,2.0),[5.0,6.0)}
+(1 row)
+
+analyze nummultirange_test;
+-- overlaps
+SELECT * FROM nummultirange_test WHERE range_overlaps_multirange(numrange(4.0, 4.2), nmr);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE numrange(4.0, 4.2) && nmr;
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE multirange_overlaps_range(nmr, numrange(4.0, 4.2));
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr && numrange(4.0, 4.2);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE multirange_overlaps_multirange(nmr, nummultirange(numrange(4.0, 4.2), numrange(6.0, 7.0)));
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr && nummultirange(numrange(4.0, 4.2), numrange(6.0, 7.0));
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr && nummultirange(numrange(6.0, 7.0));
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+(3 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr && nummultirange(numrange(6.0, 7.0), numrange(8.0, 9.0));
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+(3 rows)
+
+-- mr contains x
+SELECT * FROM nummultirange_test WHERE multirange_contains_elem(nmr, 4.0);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr @> 4.0;
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE multirange_contains_range(nmr, numrange(4.0, 4.2));
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr @> numrange(4.0, 4.2);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE multirange_contains_multirange(nmr, '{[4.0,4.2), [6.0, 8.0)}');
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+(3 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr @> '{[4.0,4.2), [6.0, 8.0)}'::nummultirange;
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+(3 rows)
+
+-- x is contained by mr
+SELECT * FROM nummultirange_test WHERE elem_contained_by_multirange(4.0, nmr);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE 4.0 <@ nmr;
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE range_contained_by_multirange(numrange(4.0, 4.2), nmr);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE numrange(4.0, 4.2) <@ nmr;
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE multirange_contained_by_multirange('{[4.0,4.2), [6.0, 8.0)}', nmr);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+(3 rows)
+
+SELECT * FROM nummultirange_test WHERE '{[4.0,4.2), [6.0, 8.0)}'::nummultirange <@ nmr;
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+(3 rows)
+
+-- overlaps
+SELECT 'empty'::numrange && nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT 'empty'::numrange && nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() && 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) && 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() && nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() && nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) && nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) && nummultirange(numrange(1,2), numrange(7,8));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(7,8)) && nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) && nummultirange(numrange(1,2), numrange(3.5,8));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(3.5,8)) && numrange(3,4);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(3.5,8)) && nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+-- contains
+SELECT nummultirange() @> nummultirange();
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange() @> 'empty'::numrange;
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(null,null)) @> numrange(1,2);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(null,null)) @> numrange(null,2);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(null,null)) @> numrange(2,null);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(null,5)) @> numrange(null,3);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(null,5)) @> numrange(null,8);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(5,null)) @> numrange(8,null);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(5,null)) @> numrange(3,null);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,5)) @> numrange(8,9);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,5)) @> numrange(3,9);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,5)) @> numrange(1,4);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,5)) @> numrange(1,5);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(-4,-2), numrange(1,5)) @> numrange(1,5);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,5), numrange(8,9)) @> numrange(1,5);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,5), numrange(8,9)) @> numrange(6,7);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,5), numrange(6,9)) @> numrange(6,7);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[1,5)}'::nummultirange @> '{[1,5)}';
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[-4,-2), [1,5)}'::nummultirange @> '{[1,5)}';
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[1,5), [8,9)}'::nummultirange @> '{[1,5)}';
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[1,5), [8,9)}'::nummultirange @> '{[6,7)}';
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT '{[1,5), [6,9)}'::nummultirange @> '{[6,7)}';
+ ?column? 
+----------
+ t
+(1 row)
+
+-- is contained by
+SELECT nummultirange() <@ nummultirange();
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT 'empty'::numrange <@ nummultirange();
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,2) <@ nummultirange(numrange(null,null));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(null,2) <@ nummultirange(numrange(null,null));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(2,null) <@ nummultirange(numrange(null,null));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(null,3) <@ nummultirange(numrange(null,5));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(null,8) <@ nummultirange(numrange(null,5));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(8,null) <@ nummultirange(numrange(5,null));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(3,null) <@ nummultirange(numrange(5,null));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(8,9) <@ nummultirange(numrange(1,5));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(3,9) <@ nummultirange(numrange(1,5));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(1,4) <@ nummultirange(numrange(1,5));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,5) <@ nummultirange(numrange(1,5));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,5) <@ nummultirange(numrange(-4,-2), numrange(1,5));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,5) <@ nummultirange(numrange(1,5), numrange(8,9));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(6,7) <@ nummultirange(numrange(1,5), numrange(8,9));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(6,7) <@ nummultirange(numrange(1,5), numrange(6,9));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[1,5)}' <@ '{[1,5)}'::nummultirange;
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[1,5)}' <@ '{[-4,-2), [1,5)}'::nummultirange;
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[1,5)}' <@ '{[1,5), [8,9)}'::nummultirange;
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[6,7)}' <@ '{[1,5), [8,9)}'::nummultirange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT '{[6,7)}' <@ '{[1,5), [6,9)}'::nummultirange;
+ ?column? 
+----------
+ t
+(1 row)
+
+-- overleft
+SELECT 'empty'::numrange &< nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT 'empty'::numrange &< nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() &< 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) &< 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() &< nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) &< nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() &< nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(6,7) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(1,2) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,4) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,6) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(3.5,6) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(6,7)) &< numrange(3,4);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) &< numrange(3,4);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,4)) &< numrange(3,4);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,6)) &< numrange(3,4);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3.5,6)) &< numrange(3,4);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(6,7)) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,4)) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,6)) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3.5,6)) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+-- overright
+SELECT nummultirange() &> 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) &> 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT 'empty'::numrange &> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT 'empty'::numrange &> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() &> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() &> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) &> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> numrange(6,7);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> numrange(1,2);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> numrange(1,4);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> numrange(1,6);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> numrange(3.5,6);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(3,4) &> nummultirange(numrange(6,7));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(3,4) &> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(3,4) &> nummultirange(numrange(1,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(3,4) &> nummultirange(numrange(1,6));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(3,4) &> nummultirange(numrange(3.5,6));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(6,7));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(1,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(1,6));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(3.5,6));
+ ?column? 
+----------
+ f
+(1 row)
+
+-- meets
+SELECT 'empty'::numrange -|- nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT 'empty'::numrange -|- nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() -|- 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() -|- nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() -|- nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(1,2) -|- nummultirange(numrange(2,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,2) -|- nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- numrange(2,4);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- numrange(3,4);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- nummultirange(numrange(2,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(5,6)) -|- nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(5,6)) -|- nummultirange(numrange(6,7));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(5,6)) -|- nummultirange(numrange(8,9));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- nummultirange(numrange(2,4), numrange(6,7));
+ ?column? 
+----------
+ t
+(1 row)
+
+-- strictly left
+select 'empty'::numrange << nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(1,2) << nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(1,2) << nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+select numrange(1,2) << nummultirange(numrange(0,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(1,2) << nummultirange(numrange(0,4), numrange(7,8));
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange() << 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange() << numrange(1,2);
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(3,4)) << numrange(3,6);
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(0,2)) << numrange(3,6);
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange(numrange(0,2), numrange(7,8)) << numrange(3,6);
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(-4,-2), numrange(0,2)) << numrange(3,6);
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange() << nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange() << nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(1,2)) << nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(1,2)) << nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(1,2)) << nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange(numrange(1,2)) << nummultirange(numrange(3,4), numrange(7,8));
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange(numrange(1,2), numrange(4,5)) << nummultirange(numrange(3,4), numrange(7,8));
+ ?column? 
+----------
+ f
+(1 row)
+
+-- strictly right
+select nummultirange() >> 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange() >> numrange(1,2);
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(3,4)) >> numrange(1,2);
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange(numrange(0,4)) >> numrange(1,2);
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(0,4), numrange(7,8)) >> numrange(1,2);
+ ?column? 
+----------
+ f
+(1 row)
+
+select 'empty'::numrange >> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(1,2) >> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(3,6) >> nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(3,6) >> nummultirange(numrange(0,2));
+ ?column? 
+----------
+ t
+(1 row)
+
+select numrange(3,6) >> nummultirange(numrange(0,2), numrange(7,8));
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(3,6) >> nummultirange(numrange(-4,-2), numrange(0,2));
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange() >> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(1,2)) >> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange() >> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(1,2)) >> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(3,4)) >> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange(numrange(3,4), numrange(7,8)) >> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange(numrange(3,4), numrange(7,8)) >> nummultirange(numrange(1,2), numrange(4,5));
+ ?column? 
+----------
+ f
+(1 row)
+
+-- union
+SELECT nummultirange() + nummultirange();
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange() + nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) + nummultirange();
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) + nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) + nummultirange(numrange(2,4));
+ ?column? 
+----------
+ {[1,4)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) + nummultirange(numrange(3,4));
+   ?column?    
+---------------
+ {[1,2),[3,4)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) + nummultirange(numrange(2,4));
+ ?column? 
+----------
+ {[1,5)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) + nummultirange(numrange(3,4));
+   ?column?    
+---------------
+ {[1,2),[3,5)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) + nummultirange(numrange(0,9));
+ ?column? 
+----------
+ {[0,9)}
+(1 row)
+
+-- merge
+SELECT range_merge(nummultirange());
+ range_merge 
+-------------
+ empty
+(1 row)
+
+SELECT range_merge(nummultirange(numrange(1,2)));
+ range_merge 
+-------------
+ [1,2)
+(1 row)
+
+SELECT range_merge(nummultirange(numrange(1,2), numrange(7,8)));
+ range_merge 
+-------------
+ [1,8)
+(1 row)
+
+-- minus
+SELECT nummultirange() - nummultirange();
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange() - nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) - nummultirange();
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(3,4)) - nummultirange();
+   ?column?    
+---------------
+ {[1,2),[3,4)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) - nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) - nummultirange(numrange(2,4));
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) - nummultirange(numrange(3,4));
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {[2,4)}
+(1 row)
+
+SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(2,3));
+   ?column?    
+---------------
+ {[1,2),[3,4)}
+(1 row)
+
+SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(0,8));
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(0,2));
+ ?column? 
+----------
+ {[2,4)}
+(1 row)
+
+SELECT nummultirange(numrange(1,8)) - nummultirange(numrange(0,2), numrange(3,4));
+   ?column?    
+---------------
+ {[2,3),[4,8)}
+(1 row)
+
+SELECT nummultirange(numrange(1,8)) - nummultirange(numrange(2,3), numrange(5,null));
+   ?column?    
+---------------
+ {[1,2),[3,5)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(-2,0));
+   ?column?    
+---------------
+ {[1,2),[4,5)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(2,4));
+   ?column?    
+---------------
+ {[1,2),[4,5)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(3,5));
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(0,9));
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange(numrange(1,3), numrange(4,5)) - nummultirange(numrange(2,9));
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(8,9));
+   ?column?    
+---------------
+ {[1,2),[4,5)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(-2,0), numrange(8,9));
+   ?column?    
+---------------
+ {[1,2),[4,5)}
+(1 row)
+
+-- intersection
+SELECT nummultirange() * nummultirange();
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange() * nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) * nummultirange();
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT '{[1,3)}'::nummultirange * '{[1,5)}'::nummultirange;
+ ?column? 
+----------
+ {[1,3)}
+(1 row)
+
+SELECT '{[1,3)}'::nummultirange * '{[0,5)}'::nummultirange;
+ ?column? 
+----------
+ {[1,3)}
+(1 row)
+
+SELECT '{[1,3)}'::nummultirange * '{[0,2)}'::nummultirange;
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT '{[1,3)}'::nummultirange * '{[2,5)}'::nummultirange;
+ ?column? 
+----------
+ {[2,3)}
+(1 row)
+
+SELECT '{[1,4)}'::nummultirange * '{[2,3)}'::nummultirange;
+ ?column? 
+----------
+ {[2,3)}
+(1 row)
+
+SELECT '{[1,4)}'::nummultirange * '{[0,2), [3,5)}'::nummultirange;
+   ?column?    
+---------------
+ {[1,2),[3,4)}
+(1 row)
+
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[0,8), [9,12)}'::nummultirange;
+       ?column?       
+----------------------
+ {[1,4),[7,8),[9,10)}
+(1 row)
+
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[9,12)}'::nummultirange;
+ ?column? 
+----------
+ {[9,10)}
+(1 row)
+
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[-5,-4), [5,6), [9,12)}'::nummultirange;
+ ?column? 
+----------
+ {[9,10)}
+(1 row)
+
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[0,2), [3,8), [9,12)}'::nummultirange;
+          ?column?          
+----------------------------
+ {[1,2),[3,4),[7,8),[9,10)}
+(1 row)
+
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[0,2), [3,8), [9,12)}'::nummultirange;
+          ?column?          
+----------------------------
+ {[1,2),[3,4),[7,8),[9,10)}
+(1 row)
+
+--
+-- range_agg function
+--
+create table reservations ( room_id integer not null, booked_during daterange );
+insert into reservations values
+-- 1: has a meets and a gap
+(1, daterange('2018-07-01', '2018-07-07')),
+(1, daterange('2018-07-07', '2018-07-14')),
+(1, daterange('2018-07-20', '2018-07-22')),
+-- 2: just a single row
+(2, daterange('2018-07-01', '2018-07-03')),
+-- 3: one null range
+(3, NULL),
+-- 4: two null ranges
+(4, NULL),
+(4, NULL),
+-- 5: a null range and a non-null range
+(5, NULL),
+(5, daterange('2018-07-01', '2018-07-03')),
+-- 6: has overlap
+(6, daterange('2018-07-01', '2018-07-07')),
+(6, daterange('2018-07-05', '2018-07-10')),
+-- 7: two ranges that meet: no gap or overlap
+(7, daterange('2018-07-01', '2018-07-07')),
+(7, daterange('2018-07-07', '2018-07-14')),
+-- 8: an empty range
+(8, 'empty'::daterange)
+;
+SELECT   room_id, range_agg(booked_during)
+FROM     reservations
+GROUP BY room_id
+ORDER BY room_id;
+ room_id |                     range_agg                     
+---------+---------------------------------------------------
+       1 | {[07-01-2018,07-14-2018),[07-20-2018,07-22-2018)}
+       2 | {[07-01-2018,07-03-2018)}
+       3 | 
+       4 | 
+       5 | {[07-01-2018,07-03-2018)}
+       6 | {[07-01-2018,07-10-2018)}
+       7 | {[07-01-2018,07-14-2018)}
+       8 | {}
+(8 rows)
+
+-- range_agg on a custom range type too
+SELECT  range_agg(r)
+FROM    (VALUES
+          ('[a,c]'::textrange),
+          ('[b,b]'::textrange),
+          ('[c,f]'::textrange),
+          ('[g,h)'::textrange),
+          ('[h,j)'::textrange)
+        ) t(r);
+   range_agg   
+---------------
+ {[a,f],[g,j)}
+(1 row)
+
+select range_intersect_agg(nmr) from nummultirange_test;
+ range_intersect_agg 
+---------------------
+ {}
+(1 row)
+
+select range_intersect_agg(nmr) from nummultirange_test where false;
+ range_intersect_agg 
+---------------------
+ 
+(1 row)
+
+-- test with just one input:
+select range_intersect_agg(nmr) from (values ('{[1,2]}'::nummultirange)) t(nmr);
+ range_intersect_agg 
+---------------------
+ {[1,2]}
+(1 row)
+
+select range_intersect_agg(nmr) from nummultirange_test where nmr @> 4.0;
+ range_intersect_agg 
+---------------------
+ {[3,5)}
+(1 row)
+
+create table nummultirange_test2(nmr nummultirange);
+create index nummultirange_test2_hash_idx on nummultirange_test2 using hash (nmr);
+INSERT INTO nummultirange_test2 VALUES('{[, 5)}');
+INSERT INTO nummultirange_test2 VALUES(nummultirange(numrange(1.1, 2.2)));
+INSERT INTO nummultirange_test2 VALUES(nummultirange(numrange(1.1, 2.2)));
+INSERT INTO nummultirange_test2 VALUES(nummultirange(numrange(1.1, 2.2,'()')));
+INSERT INTO nummultirange_test2 VALUES('{}');
+select * from nummultirange_test2 where nmr = '{}';
+ nmr 
+-----
+ {}
+(1 row)
+
+select * from nummultirange_test2 where nmr = nummultirange(numrange(1.1, 2.2));
+     nmr     
+-------------
+ {[1.1,2.2)}
+ {[1.1,2.2)}
+(2 rows)
+
+select * from nummultirange_test2 where nmr = nummultirange(numrange(1.1, 2.3));
+ nmr 
+-----
+(0 rows)
+
+set enable_nestloop=t;
+set enable_hashjoin=f;
+set enable_mergejoin=f;
+select * from nummultirange_test natural join nummultirange_test2 order by nmr;
+     nmr     
+-------------
+ {}
+ {}
+ {}
+ {}
+ {(,5)}
+ {[1.1,2.2)}
+ {[1.1,2.2)}
+(7 rows)
+
+set enable_nestloop=f;
+set enable_hashjoin=t;
+set enable_mergejoin=f;
+select * from nummultirange_test natural join nummultirange_test2 order by nmr;
+     nmr     
+-------------
+ {}
+ {}
+ {}
+ {}
+ {(,5)}
+ {[1.1,2.2)}
+ {[1.1,2.2)}
+(7 rows)
+
+set enable_nestloop=f;
+set enable_hashjoin=f;
+set enable_mergejoin=t;
+select * from nummultirange_test natural join nummultirange_test2 order by nmr;
+     nmr     
+-------------
+ {}
+ {}
+ {}
+ {}
+ {(,5)}
+ {[1.1,2.2)}
+ {[1.1,2.2)}
+(7 rows)
+
+set enable_nestloop to default;
+set enable_hashjoin to default;
+set enable_mergejoin to default;
+DROP TABLE nummultirange_test2;
+--
+-- Test user-defined multirange of floats
+--
+select '{[123.001, 5.e9)}'::float8multirange @> 888.882::float8;
+ ?column? 
+----------
+ t
+(1 row)
+
+create table float8multirange_test(f8mr float8multirange, i int);
+insert into float8multirange_test values(float8multirange(float8range(-100.00007, '1.111113e9')), 42);
+select * from float8multirange_test;
+           f8mr            | i  
+---------------------------+----
+ {[-100.00007,1111113000)} | 42
+(1 row)
+
+drop table float8multirange_test;
+--
+-- Test multirange types over domains
+--
+create domain mydomain as int4;
+create type mydomainrange as range(subtype=mydomain);
+select '{[4,50)}'::mydomainmultirange @> 7::mydomain;
+ ?column? 
+----------
+ t
+(1 row)
+
+drop domain mydomain cascade;
+NOTICE:  drop cascades to type mydomainrange
+--
+-- Test domains over multirange types
+--
+create domain restrictedmultirange as int4multirange check (upper(value) < 10);
+select '{[4,5)}'::restrictedmultirange @> 7;
+ ?column? 
+----------
+ f
+(1 row)
+
+select '{[4,50)}'::restrictedmultirange @> 7; -- should fail
+ERROR:  value for domain restrictedmultirange violates check constraint "restrictedmultirange_check"
+drop domain restrictedmultirange;
+--
+-- Test multiple multirange types over the same subtype and manual naming of
+-- the multirange type.
+--
+-- should fail
+create type textrange1 as range(subtype=text, multirange_type_name=int, collation="C");
+ERROR:  type "int4" already exists
+-- should pass
+create type textrange1 as range(subtype=text, multirange_type_name=multirange_of_text, collation="C");
+-- should pass, because existing _textrange1 is automatically renamed
+create type textrange2 as range(subtype=text, multirange_type_name=_textrange1, collation="C");
+select multirange_of_text(textrange2('a','Z'));  -- should fail
+ERROR:  function multirange_of_text(textrange2) does not exist
+LINE 1: select multirange_of_text(textrange2('a','Z'));
+               ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+select multirange_of_text(textrange1('a','Z')) @> 'b'::text;
+ERROR:  range lower bound must be less than or equal to range upper bound
+select _textrange1(textrange2('a','z')) @> 'b'::text;
+ ?column? 
+----------
+ t
+(1 row)
+
+drop type textrange1;
+drop type textrange2;
+--
+-- Test polymorphic type system
+--
+create function anyarray_anymultirange_func(a anyarray, r anymultirange)
+  returns anyelement as 'select $1[1] + lower($2);' language sql;
+select anyarray_anymultirange_func(ARRAY[1,2], int4multirange(int4range(10,20)));
+ anyarray_anymultirange_func 
+-----------------------------
+                          11
+(1 row)
+
+-- should fail
+select anyarray_anymultirange_func(ARRAY[1,2], nummultirange(numrange(10,20)));
+ERROR:  function anyarray_anymultirange_func(integer[], nummultirange) does not exist
+LINE 1: select anyarray_anymultirange_func(ARRAY[1,2], nummultirange...
+               ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+drop function anyarray_anymultirange_func(anyarray, anymultirange);
+-- should fail
+create function bogus_func(anyelement)
+  returns anymultirange as 'select int4multirange(int4range(1,10))' language sql;
+ERROR:  cannot determine result data type
+DETAIL:  A result of type anymultirange requires at least one input of type anyrange or anymultirange.
+-- should fail
+create function bogus_func(int)
+  returns anymultirange as 'select int4multirange(int4range(1,10))' language sql;
+ERROR:  cannot determine result data type
+DETAIL:  A result of type anymultirange requires at least one input of type anyrange or anymultirange.
+create function range_add_bounds(anymultirange)
+  returns anyelement as 'select lower($1) + upper($1)' language sql;
+select range_add_bounds(int4multirange(int4range(1, 17)));
+ range_add_bounds 
+------------------
+               18
+(1 row)
+
+select range_add_bounds(nummultirange(numrange(1.0001, 123.123)));
+ range_add_bounds 
+------------------
+         124.1231
+(1 row)
+
+create function multirangetypes_sql(q anymultirange, b anyarray, out c anyelement)
+  as $$ select upper($1) + $2[1] $$
+  language sql;
+select multirangetypes_sql(int4multirange(int4range(1,10)), ARRAY[2,20]);
+ multirangetypes_sql 
+---------------------
+                  12
+(1 row)
+
+select multirangetypes_sql(nummultirange(numrange(1,10)), ARRAY[2,20]);  -- match failure
+ERROR:  function multirangetypes_sql(nummultirange, integer[]) does not exist
+LINE 1: select multirangetypes_sql(nummultirange(numrange(1,10)), AR...
+               ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+create function anycompatiblearray_anycompatiblemultirange_func(a anycompatiblearray, mr anycompatiblemultirange)
+  returns anycompatible as 'select $1[1] + lower($2);' language sql;
+select anycompatiblearray_anycompatiblemultirange_func(ARRAY[1,2], multirange(int4range(10,20)));
+ anycompatiblearray_anycompatiblemultirange_func 
+-------------------------------------------------
+                                              11
+(1 row)
+
+select anycompatiblearray_anycompatiblemultirange_func(ARRAY[1,2], multirange(numrange(10,20)));
+ anycompatiblearray_anycompatiblemultirange_func 
+-------------------------------------------------
+                                              11
+(1 row)
+
+-- should fail
+select anycompatiblearray_anycompatiblemultirange_func(ARRAY[1.1,2], multirange(int4range(10,20)));
+ERROR:  function anycompatiblearray_anycompatiblemultirange_func(numeric[], int4multirange) does not exist
+LINE 1: select anycompatiblearray_anycompatiblemultirange_func(ARRAY...
+               ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+drop function anycompatiblearray_anycompatiblemultirange_func(anycompatiblearray, anycompatiblemultirange);
+create function anycompatiblerange_anycompatiblemultirange_func(r anycompatiblerange, mr anycompatiblemultirange)
+  returns anycompatible as 'select lower($1) + lower($2);' language sql;
+select anycompatiblerange_anycompatiblemultirange_func(int4range(1,2), multirange(int4range(10,20)));
+ anycompatiblerange_anycompatiblemultirange_func 
+-------------------------------------------------
+                                              11
+(1 row)
+
+-- should fail
+select anycompatiblerange_anycompatiblemultirange_func(numrange(1,2), multirange(int4range(10,20)));
+ERROR:  function anycompatiblerange_anycompatiblemultirange_func(numrange, int4multirange) does not exist
+LINE 1: select anycompatiblerange_anycompatiblemultirange_func(numra...
+               ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+drop function anycompatiblerange_anycompatiblemultirange_func(anycompatiblerange, anycompatiblemultirange);
+-- should fail
+create function bogus_func(anycompatible)
+  returns anycompatiblerange as 'select int4range(1,10)' language sql;
+ERROR:  cannot determine result data type
+DETAIL:  A result of type anycompatiblerange requires at least one input of type anycompatiblerange or anycompatiblemultirange.
+--
+-- Arrays of multiranges
+--
+select ARRAY[nummultirange(numrange(1.1, 1.2)), nummultirange(numrange(12.3, 155.5))];
+              array               
+----------------------------------
+ {"{[1.1,1.2)}","{[12.3,155.5)}"}
+(1 row)
+
+create table i8mr_array (f1 int, f2 int8multirange[]);
+insert into i8mr_array values (42, array[int8multirange(int8range(1,10)), int8multirange(int8range(2,20))]);
+select * from i8mr_array;
+ f1 |           f2            
+----+-------------------------
+ 42 | {"{[1,10)}","{[2,20)}"}
+(1 row)
+
+drop table i8mr_array;
+--
+-- Multiranges of arrays
+--
+select arraymultirange(arrayrange(ARRAY[1,2], ARRAY[2,1]));
+   arraymultirange   
+---------------------
+ {["{1,2}","{2,1}")}
+(1 row)
+
+select arraymultirange(arrayrange(ARRAY[2,1], ARRAY[1,2]));  -- fail
+ERROR:  range lower bound must be less than or equal to range upper bound
+select array[1,1] <@ arraymultirange(arrayrange(array[1,2], array[2,1]));
+ ?column? 
+----------
+ f
+(1 row)
+
+select array[1,3] <@ arraymultirange(arrayrange(array[1,2], array[2,1]));
+ ?column? 
+----------
+ t
+(1 row)
+
+--
+-- Ranges of composites
+--
+create type two_ints as (a int, b int);
+create type two_ints_range as range (subtype = two_ints);
+-- with force_parallel_mode on, this exercises tqueue.c's range remapping
+select *, row_to_json(upper(t)) as u from
+  (values (two_ints_multirange(two_ints_range(row(1,2), row(3,4)))),
+          (two_ints_multirange(two_ints_range(row(5,6), row(7,8))))) v(t);
+          t          |       u       
+---------------------+---------------
+ {["(1,2)","(3,4)")} | {"a":3,"b":4}
+ {["(5,6)","(7,8)")} | {"a":7,"b":8}
+(2 rows)
+
+drop type two_ints cascade;
+NOTICE:  drop cascades to type two_ints_range
+--
+-- Check behavior when subtype lacks a hash function
+--
+set enable_sort = off;  -- try to make it pick a hash setop implementation
+select '{(2,5)}'::cashmultirange except select '{(5,6)}'::cashmultirange;
+ cashmultirange  
+-----------------
+ {($2.00,$5.00)}
+(1 row)
+
+reset enable_sort;
+--
+-- OUT/INOUT/TABLE functions
+--
+-- infer anymultirange from anymultirange
+create function mr_outparam_succeed(i anymultirange, out r anymultirange, out t text)
+  as $$ select $1, 'foo'::text $$ language sql;
+select * from mr_outparam_succeed(int4multirange(int4range(1,2)));
+    r    |  t  
+---------+-----
+ {[1,2)} | foo
+(1 row)
+
+-- infer anyarray from anymultirange
+create function mr_outparam_succeed2(i anymultirange, out r anyarray, out t text)
+  as $$ select ARRAY[upper($1)], 'foo'::text $$ language sql;
+select * from mr_outparam_succeed2(int4multirange(int4range(1,2)));
+  r  |  t  
+-----+-----
+ {2} | foo
+(1 row)
+
+-- infer anyrange from anymultirange
+create function mr_outparam_succeed3(i anymultirange, out r anyrange, out t text)
+  as $$ select range_merge($1), 'foo'::text $$ language sql;
+select * from mr_outparam_succeed3(int4multirange(int4range(1,2)));
+   r   |  t  
+-------+-----
+ [1,2) | foo
+(1 row)
+
+-- infer anymultirange from anyrange
+create function mr_outparam_succeed4(i anyrange, out r anymultirange, out t text)
+  as $$ select multirange($1), 'foo'::text $$ language sql;
+select * from mr_outparam_succeed4(int4range(1,2));
+    r    |  t  
+---------+-----
+ {[1,2)} | foo
+(1 row)
+
+-- infer anyelement from anymultirange
+create function mr_inoutparam_succeed(out i anyelement, inout r anymultirange)
+  as $$ select upper($1), $1 $$ language sql;
+select * from mr_inoutparam_succeed(int4multirange(int4range(1,2)));
+ i |    r    
+---+---------
+ 2 | {[1,2)}
+(1 row)
+
+-- infer anyelement+anymultirange from anyelement+anymultirange
+create function mr_table_succeed(i anyelement, r anymultirange) returns table(i anyelement, r anymultirange)
+  as $$ select $1, $2 $$ language sql;
+select * from mr_table_succeed(123, int4multirange(int4range(1,11)));
+  i  |    r     
+-----+----------
+ 123 | {[1,11)}
+(1 row)
+
+-- use anymultirange in plpgsql
+create function mr_polymorphic(i anyrange) returns anymultirange
+  as $$ begin return multirange($1); end; $$ language plpgsql;
+select mr_polymorphic(int4range(1, 4));
+ mr_polymorphic 
+----------------
+ {[1,4)}
+(1 row)
+
+-- should fail
+create function mr_outparam_fail(i anyelement, out r anymultirange, out t text)
+  as $$ select '[1,10]', 'foo' $$ language sql;
+ERROR:  cannot determine result data type
+DETAIL:  A result of type anymultirange requires at least one input of type anyrange or anymultirange.
+--should fail
+create function mr_inoutparam_fail(inout i anyelement, out r anymultirange)
+  as $$ select $1, '[1,10]' $$ language sql;
+ERROR:  cannot determine result data type
+DETAIL:  A result of type anymultirange requires at least one input of type anyrange or anymultirange.
+--should fail
+create function mr_table_fail(i anyelement) returns table(i anyelement, r anymultirange)
+  as $$ select $1, '[1,10]' $$ language sql;
+ERROR:  cannot determine result data type
+DETAIL:  A result of type anymultirange requires at least one input of type anyrange or anymultirange.
diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out
index 507b474b1bb..5cac48e02a7 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -39,6 +39,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;
@@ -183,7 +187,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::regtype, p2.prorettype::regtype
 FROM pg_proc AS p1, pg_proc AS p2
 WHERE p1.oid != p2.oid AND
@@ -192,6 +197,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        
@@ -210,6 +217,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        
@@ -230,6 +239,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        
@@ -342,7 +353,8 @@ WHERE p1.prorettype IN
      '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     
 ------+----------------
@@ -357,20 +369,25 @@ ORDER BY 2;
  3532 | enum_recv
 (9 rows)
 
--- anyrange is tighter than the rest, can only resolve from anyrange input
+-- anyrange and anymultirange are tighter than the rest, can only resolve
+-- from each other
 SELECT p1.oid, p1.proname
 FROM pg_proc as p1
-WHERE p1.prorettype = 'anyrange'::regtype
+WHERE p1.prorettype IN ('anyrange'::regtype, 'anymultirange'::regtype)
   AND NOT
-     'anyrange'::regtype = ANY (p1.proargtypes)
+    ('anyrange'::regtype = ANY (p1.proargtypes) OR
+      'anymultirange'::regtype = ANY (p1.proargtypes))
 ORDER BY 2;
  oid  |     proname      
 ------+------------------
+ 8002 | anymultirange_in
  3832 | anyrange_in
+ 8004 | multirange_in
+ 8006 | multirange_recv
  3876 | range_gist_union
  3834 | range_in
  3836 | range_recv
-(4 rows)
+(7 rows)
 
 -- similarly for the anycompatible family
 SELECT p1.oid, p1.proname
@@ -2204,13 +2221,14 @@ ORDER BY 1, 2, 3;
                     | float_ops        | float4_ops       | real
                     | float_ops        | float8_ops       | double precision
                     | jsonb_ops        | jsonb_ops        | jsonb
+                    | multirange_ops   | multirange_ops   | anymultirange
                     | numeric_ops      | numeric_ops      | numeric
                     | range_ops        | range_ops        | anyrange
                     | record_image_ops | record_image_ops | record
                     | record_ops       | record_ops       | record
                     | tsquery_ops      | tsquery_ops      | tsquery
                     | tsvector_ops     | tsvector_ops     | tsvector
-(14 rows)
+(15 rows)
 
 -- **************** pg_index ****************
 -- Look for illegal values in pg_index fields.
diff --git a/src/test/regress/expected/plpgsql.out b/src/test/regress/expected/plpgsql.out
index d0a6b630b8f..d55006d8c95 100644
--- a/src/test/regress/expected/plpgsql.out
+++ b/src/test/regress/expected/plpgsql.out
@@ -1811,7 +1811,7 @@ begin
   return array[x + 1, x + 2];
 end$$ language plpgsql;
 ERROR:  cannot determine result data type
-DETAIL:  A result of type anyrange requires at least one input of type anyrange.
+DETAIL:  A result of type anyrange requires at least one input of type anyrange or anymultirange.
 create function f1(x anyrange) returns anyarray as $$
 begin
   return array[lower(x), upper(x)];
@@ -1856,7 +1856,7 @@ begin
   return array[x + 1, x + 2];
 end$$ language plpgsql;
 ERROR:  cannot determine result data type
-DETAIL:  A result of type anycompatiblerange requires at least one input of type anycompatiblerange.
+DETAIL:  A result of type anycompatiblerange requires at least one input of type anycompatiblerange or anycompatiblemultirange.
 create function f1(x anycompatiblerange, y anycompatiblearray) returns anycompatiblerange as $$
 begin
   return x;
diff --git a/src/test/regress/expected/polymorphism.out b/src/test/regress/expected/polymorphism.out
index f5dfdf617fd..772345584f0 100644
--- a/src/test/regress/expected/polymorphism.out
+++ b/src/test/regress/expected/polymorphism.out
@@ -61,7 +61,7 @@ create function polyf(x anyelement) returns anyrange as $$
   select array[x + 1, x + 2]
 $$ language sql;
 ERROR:  cannot determine result data type
-DETAIL:  A result of type anyrange requires at least one input of type anyrange.
+DETAIL:  A result of type anyrange requires at least one input of type anyrange or anymultirange.
 create function polyf(x anyrange) returns anyarray as $$
   select array[lower(x), upper(x)]
 $$ language sql;
@@ -97,12 +97,27 @@ LINE 1: select polyf(int4range(42, 49), 11, 4.5) as fail;
                ^
 HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
 drop function polyf(x anycompatiblerange, y anycompatible, z anycompatible);
+create function polyf(x anycompatiblemultirange, y anycompatible, z anycompatible) returns anycompatiblearray as $$
+  select array[lower(x), upper(x), y, z]
+$$ language sql;
+select polyf(multirange(int4range(42, 49)), 11, 2::smallint) as int, polyf(multirange(float8range(4.5, 7.8)), 7.8, 11::real) as num;
+     int      |       num        
+--------------+------------------
+ {42,49,11,2} | {4.5,7.8,7.8,11}
+(1 row)
+
+select polyf(multirange(int4range(42, 49)), 11, 4.5) as fail;  -- range type doesn't fit
+ERROR:  function polyf(int4multirange, integer, numeric) does not exist
+LINE 1: select polyf(multirange(int4range(42, 49)), 11, 4.5) as fail...
+               ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+drop function polyf(x anycompatiblemultirange, y anycompatible, z anycompatible);
 -- fail, can't infer type:
 create function polyf(x anycompatible) returns anycompatiblerange as $$
   select array[x + 1, x + 2]
 $$ language sql;
 ERROR:  cannot determine result data type
-DETAIL:  A result of type anycompatiblerange requires at least one input of type anycompatiblerange.
+DETAIL:  A result of type anycompatiblerange requires at least one input of type anycompatiblerange or anycompatiblemultirange.
 create function polyf(x anycompatiblerange, y anycompatiblearray) returns anycompatiblerange as $$
   select x
 $$ language sql;
@@ -113,6 +128,22 @@ select polyf(int4range(42, 49), array[11]) as int, polyf(float8range(4.5, 7.8),
 (1 row)
 
 drop function polyf(x anycompatiblerange, y anycompatiblearray);
+-- fail, can't infer type:
+create function polyf(x anycompatible) returns anycompatiblemultirange as $$
+  select array[x + 1, x + 2]
+$$ language sql;
+ERROR:  cannot determine result data type
+DETAIL:  A result of type anycompatiblemultirange requires at least one input of type anycompatiblerange or anycompatiblemultirange.
+create function polyf(x anycompatiblemultirange, y anycompatiblearray) returns anycompatiblemultirange as $$
+  select x
+$$ language sql;
+select polyf(multirange(int4range(42, 49)), array[11]) as int, polyf(multirange(float8range(4.5, 7.8)), array[7]) as num;
+    int    |     num     
+-----------+-------------
+ {[42,49)} | {[4.5,7.8)}
+(1 row)
+
+drop function polyf(x anycompatiblemultirange, y anycompatiblearray);
 create function polyf(a anyelement, b anyarray,
                       c anycompatible, d anycompatible,
                       OUT x anyarray, OUT y anycompatiblearray)
@@ -231,7 +262,7 @@ CREATE AGGREGATE myaggp01a(*) (SFUNC = stfnp, STYPE = int4[],
 CREATE AGGREGATE myaggp02a(*) (SFUNC = stfnp, STYPE = anyarray,
   FINALFUNC = ffp, INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 --     N    P
 -- should CREATE
 CREATE AGGREGATE myaggp03a(*) (SFUNC = stfp, STYPE = int4[],
@@ -243,11 +274,11 @@ CREATE AGGREGATE myaggp03b(*) (SFUNC = stfp, STYPE = int4[],
 CREATE AGGREGATE myaggp04a(*) (SFUNC = stfp, STYPE = anyarray,
   FINALFUNC = ffp, INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 CREATE AGGREGATE myaggp04b(*) (SFUNC = stfp, STYPE = anyarray,
   INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 --    Case2 (R = P) && ((B = P) || (B = N))
 --    -------------------------------------
 --    S    tf1      B    tf2
@@ -302,13 +333,13 @@ ERROR:  function tfp(integer[], anyelement) does not exist
 CREATE AGGREGATE myaggp13a(BASETYPE = int, SFUNC = tfnp, STYPE = anyarray,
   FINALFUNC = ffp, INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 --    P    N        N    P
 -- should ERROR: tf2p(anyarray, int) not matched by tf2p(int[],anyelement)
 CREATE AGGREGATE myaggp14a(BASETYPE = int, SFUNC = tf2p, STYPE = anyarray,
   FINALFUNC = ffp, INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 --    P    N        P    N
 -- should ERROR: tfnp(anyarray, anyelement) not matched by tfnp(int[],int)
 CREATE AGGREGATE myaggp15a(BASETYPE = anyelement, SFUNC = tfnp,
@@ -324,21 +355,21 @@ ERROR:  function tf2p(anyarray, anyelement) does not exist
 CREATE AGGREGATE myaggp17a(BASETYPE = int, SFUNC = tf1p, STYPE = anyarray,
   FINALFUNC = ffp, INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 CREATE AGGREGATE myaggp17b(BASETYPE = int, SFUNC = tf1p, STYPE = anyarray,
   INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 --    P    P        N    P
 -- should ERROR: tfp(anyarray, int) not matched by tfp(anyarray, anyelement)
 CREATE AGGREGATE myaggp18a(BASETYPE = int, SFUNC = tfp, STYPE = anyarray,
   FINALFUNC = ffp, INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 CREATE AGGREGATE myaggp18b(BASETYPE = int, SFUNC = tfp, STYPE = anyarray,
   INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 --    P    P        P    N
 -- should ERROR: tf1p(anyarray, anyelement) not matched by tf1p(anyarray, int)
 CREATE AGGREGATE myaggp19a(BASETYPE = anyelement, SFUNC = tf1p,
@@ -368,11 +399,11 @@ CREATE AGGREGATE myaggn01b(*) (SFUNC = stfnp, STYPE = int4[],
 CREATE AGGREGATE myaggn02a(*) (SFUNC = stfnp, STYPE = anyarray,
   FINALFUNC = ffnp, INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 CREATE AGGREGATE myaggn02b(*) (SFUNC = stfnp, STYPE = anyarray,
   INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 --     N    P
 -- should CREATE
 CREATE AGGREGATE myaggn03a(*) (SFUNC = stfp, STYPE = int4[],
@@ -382,7 +413,7 @@ CREATE AGGREGATE myaggn03a(*) (SFUNC = stfp, STYPE = int4[],
 CREATE AGGREGATE myaggn04a(*) (SFUNC = stfp, STYPE = anyarray,
   FINALFUNC = ffnp, INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 --    Case4 (R = N) && ((B = P) || (B = N))
 --    -------------------------------------
 --    S    tf1      B    tf2
@@ -436,21 +467,21 @@ ERROR:  function tfp(integer[], anyelement) does not exist
 CREATE AGGREGATE myaggn13a(BASETYPE = int, SFUNC = tfnp, STYPE = anyarray,
   FINALFUNC = ffnp, INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 CREATE AGGREGATE myaggn13b(BASETYPE = int, SFUNC = tfnp, STYPE = anyarray,
   INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 --    P    N        N    P
 -- should ERROR: tf2p(anyarray, int) not matched by tf2p(int[],anyelement)
 CREATE AGGREGATE myaggn14a(BASETYPE = int, SFUNC = tf2p, STYPE = anyarray,
   FINALFUNC = ffnp, INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 CREATE AGGREGATE myaggn14b(BASETYPE = int, SFUNC = tf2p, STYPE = anyarray,
   INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 --    P    N        P    N
 -- should ERROR: tfnp(anyarray, anyelement) not matched by tfnp(int[],int)
 CREATE AGGREGATE myaggn15a(BASETYPE = anyelement, SFUNC = tfnp,
@@ -472,13 +503,13 @@ ERROR:  function tf2p(anyarray, anyelement) does not exist
 CREATE AGGREGATE myaggn17a(BASETYPE = int, SFUNC = tf1p, STYPE = anyarray,
   FINALFUNC = ffnp, INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 --    P    P        N    P
 -- should ERROR: tfp(anyarray, int) not matched by tfp(anyarray, anyelement)
 CREATE AGGREGATE myaggn18a(BASETYPE = int, SFUNC = tfp, STYPE = anyarray,
   FINALFUNC = ffnp, INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 --    P    P        P    N
 -- should ERROR: tf1p(anyarray, anyelement) not matched by tf1p(anyarray, int)
 CREATE AGGREGATE myaggn19a(BASETYPE = anyelement, SFUNC = tf1p,
@@ -1855,7 +1886,59 @@ returns anycompatiblerange as $$
   select $1
 $$ language sql;
 ERROR:  cannot determine result data type
-DETAIL:  A result of type anycompatiblerange requires at least one input of type anycompatiblerange.
+DETAIL:  A result of type anycompatiblerange requires at least one input of type anycompatiblerange or anycompatiblemultirange.
+create function anyctest(anycompatible, anycompatiblemultirange)
+returns anycompatiblemultirange as $$
+  select $2
+$$ language sql;
+select x, pg_typeof(x) from anyctest(11, multirange(int4range(4,7))) x;
+    x    |   pg_typeof    
+---------+----------------
+ {[4,7)} | int4multirange
+(1 row)
+
+select x, pg_typeof(x) from anyctest(11, multirange(numrange(4,7))) x;
+    x    |   pg_typeof   
+---------+---------------
+ {[4,7)} | nummultirange
+(1 row)
+
+select x, pg_typeof(x) from anyctest(11, 12) x;  -- fail
+ERROR:  function anyctest(integer, integer) does not exist
+LINE 1: select x, pg_typeof(x) from anyctest(11, 12) x;
+                                    ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+select x, pg_typeof(x) from anyctest(11.2, multirange(int4range(4,7))) x;  -- fail
+ERROR:  function anyctest(numeric, int4multirange) does not exist
+LINE 1: select x, pg_typeof(x) from anyctest(11.2, multirange(int4ra...
+                                    ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+select x, pg_typeof(x) from anyctest(11.2, '{[4,7)}') x;  -- fail
+ERROR:  could not identify anycompatiblemultirange type
+drop function anyctest(anycompatible, anycompatiblemultirange);
+create function anyctest(anycompatiblemultirange, anycompatiblemultirange)
+returns anycompatible as $$
+  select lower($1) + upper($2)
+$$ language sql;
+select x, pg_typeof(x) from anyctest(multirange(int4range(11,12)), multirange(int4range(4,7))) x;
+ x  | pg_typeof 
+----+-----------
+ 18 | integer
+(1 row)
+
+select x, pg_typeof(x) from anyctest(multirange(int4range(11,12)), multirange(numrange(4,7))) x; -- fail
+ERROR:  function anyctest(int4multirange, nummultirange) does not exist
+LINE 1: select x, pg_typeof(x) from anyctest(multirange(int4range(11...
+                                    ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+drop function anyctest(anycompatiblemultirange, anycompatiblemultirange);
+-- fail, can't infer result type:
+create function anyctest(anycompatible)
+returns anycompatiblemultirange as $$
+  select $1
+$$ language sql;
+ERROR:  cannot determine result data type
+DETAIL:  A result of type anycompatiblemultirange requires at least one input of type anycompatiblerange or anycompatiblemultirange.
 create function anyctest(anycompatiblenonarray, anycompatiblenonarray)
 returns anycompatiblearray as $$
   select array[$1, $2]
diff --git a/src/test/regress/expected/rangefuncs.out b/src/test/regress/expected/rangefuncs.out
index 06bd129fd22..5bbd5f6c0bd 100644
--- a/src/test/regress/expected/rangefuncs.out
+++ b/src/test/regress/expected/rangefuncs.out
@@ -1556,7 +1556,7 @@ DROP FUNCTION dup(anyelement);
 CREATE FUNCTION bad (f1 int, out f2 anyelement, out f3 anyarray)
 AS 'select $1, array[$1,$1]' LANGUAGE sql;
 ERROR:  cannot determine result data type
-DETAIL:  A result of type anyelement requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyelement requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 CREATE FUNCTION dup (f1 anycompatible, f2 anycompatiblearray, f3 out anycompatible, f4 out anycompatiblearray)
 AS 'select $1, $2' LANGUAGE sql;
 SELECT dup(22, array[44]);
diff --git a/src/test/regress/expected/rangetypes.out b/src/test/regress/expected/rangetypes.out
index c421f5394fe..6a1bbadc91a 100644
--- a/src/test/regress/expected/rangetypes.out
+++ b/src/test/regress/expected/rangetypes.out
@@ -518,6 +518,25 @@ select numrange(1.0, 2.0) * numrange(2.5, 3.0);
  empty
 (1 row)
 
+select range_intersect_agg(nr) from numrange_test;
+ range_intersect_agg 
+---------------------
+ empty
+(1 row)
+
+select range_intersect_agg(nr) from numrange_test where false;
+ range_intersect_agg 
+---------------------
+ 
+(1 row)
+
+select range_intersect_agg(nr) from numrange_test where nr @> 4.0;
+ range_intersect_agg 
+---------------------
+ [3,5)
+(1 row)
+
+analyze numrange_test;
 create table numrange_test2(nr numrange);
 create index numrange_test2_hash_idx on numrange_test2 using hash (nr);
 INSERT INTO numrange_test2 VALUES('[, 5)');
@@ -1371,12 +1390,12 @@ drop function anyarray_anyrange_func(anyarray, anyrange);
 create function bogus_func(anyelement)
   returns anyrange as 'select int4range(1,10)' language sql;
 ERROR:  cannot determine result data type
-DETAIL:  A result of type anyrange requires at least one input of type anyrange.
+DETAIL:  A result of type anyrange requires at least one input of type anyrange or anymultirange.
 -- should fail
 create function bogus_func(int)
   returns anyrange as 'select int4range(1,10)' language sql;
 ERROR:  cannot determine result data type
-DETAIL:  A result of type anyrange requires at least one input of type anyrange.
+DETAIL:  A result of type anyrange requires at least one input of type anyrange or anymultirange.
 create function range_add_bounds(anyrange)
   returns anyelement as 'select lower($1) + upper($1)' language sql;
 select range_add_bounds(int4range(1, 17));
@@ -1430,7 +1449,7 @@ drop function anycompatiblearray_anycompatiblerange_func(anycompatiblearray, any
 create function bogus_func(anycompatible)
   returns anycompatiblerange as 'select int4range(1,10)' language sql;
 ERROR:  cannot determine result data type
-DETAIL:  A result of type anycompatiblerange requires at least one input of type anycompatiblerange.
+DETAIL:  A result of type anycompatiblerange requires at least one input of type anycompatiblerange or anycompatiblemultirange.
 --
 -- Arrays of ranges
 --
@@ -1508,6 +1527,7 @@ reset enable_sort;
 --
 -- OUT/INOUT/TABLE functions
 --
+-- infer anyrange from anyrange
 create function outparam_succeed(i anyrange, out r anyrange, out t text)
   as $$ select $1, 'foo'::text $$ language sql;
 select * from outparam_succeed(int4range(1,2));
@@ -1525,6 +1545,16 @@ select * from outparam2_succeed(int4range(1,11));
  {1,11} | {11,1}
 (1 row)
 
+-- infer anyarray from anyrange
+create function outparam_succeed2(i anyrange, out r anyarray, out t text)
+  as $$ select ARRAY[upper($1)], 'foo'::text $$ language sql;
+select * from outparam_succeed2(int4range(int4range(1,2)));
+  r  |  t  
+-----+-----
+ {2} | foo
+(1 row)
+
+-- infer anyelement from anyrange
 create function inoutparam_succeed(out i anyelement, inout r anyrange)
   as $$ select upper($1), $1 $$ language sql;
 select * from inoutparam_succeed(int4range(1,2));
@@ -1547,14 +1577,14 @@ select * from table_succeed(int4range(1,11));
 create function outparam_fail(i anyelement, out r anyrange, out t text)
   as $$ select '[1,10]', 'foo' $$ language sql;
 ERROR:  cannot determine result data type
-DETAIL:  A result of type anyrange requires at least one input of type anyrange.
+DETAIL:  A result of type anyrange requires at least one input of type anyrange or anymultirange.
 --should fail
 create function inoutparam_fail(inout i anyelement, out r anyrange)
   as $$ select $1, '[1,10]' $$ language sql;
 ERROR:  cannot determine result data type
-DETAIL:  A result of type anyrange requires at least one input of type anyrange.
+DETAIL:  A result of type anyrange requires at least one input of type anyrange or anymultirange.
 --should fail
 create function table_fail(i anyelement) returns table(i anyelement, r anyrange)
   as $$ select $1, '[1,10]' $$ language sql;
 ERROR:  cannot determine result data type
-DETAIL:  A result of type anyrange requires at least one input of type anyrange.
+DETAIL:  A result of type anyrange requires at least one input of type anyrange or anymultirange.
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index 192445878d2..d9ce961be2b 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -92,6 +92,7 @@ num_exp_sqrt|t
 num_exp_sub|t
 num_input_test|f
 num_result|f
+nummultirange_test|t
 numrange_test|t
 onek|t
 onek2|t
@@ -172,6 +173,7 @@ quad_poly_tbl|t
 radix_text_tbl|t
 ramp|f
 real_city|f
+reservations|f
 road|t
 shighway|t
 slow_emp4000|f
diff --git a/src/test/regress/expected/type_sanity.out b/src/test/regress/expected/type_sanity.out
index 13567ddf84b..0c74dc96a87 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'));
@@ -196,18 +196,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
@@ -241,17 +242,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
@@ -319,18 +321,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
@@ -364,17 +367,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
@@ -660,3 +664,11 @@ WHERE pronargs != 2
 ----------+------------+---------
 (0 rows)
 
+-- every range should have a valid multirange
+SELECT p1.rngtypid, p1.rngsubtype, p1.rngmultitypid
+FROM pg_range p1
+WHERE p1.rngmultitypid IS NULL OR p1.rngmultitypid = 0;
+ rngtypid | rngsubtype | rngmultitypid 
+----------+------------+---------------
+(0 rows)
+
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index ae89ed7f0b4..432f454aa17 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -19,15 +19,16 @@ 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 xid
+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
 # geometry depends on point, lseg, box, path, polygon and circle
 # horology depends on interval, timetz, timestamp, timestamptz
 # ----------
-test: geometry horology regex oidjoins type_sanity opr_sanity misc_sanity comments expressions unicode
+test: geometry horology regex oidjoins type_sanity opr_sanity misc_sanity comments expressions unicode xid
 
 # ----------
 # These four each depend on the previous one
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index 525bdc804f6..081fce32e75 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -20,6 +20,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 681cc92ac20..0292dedd15a 100644
--- a/src/test/regress/sql/hash_func.sql
+++ b/src/test/regress/sql/hash_func.sql
@@ -227,6 +227,16 @@ FROM   (VALUES (int4range(10, 20)), (int4range(23, 43)),
 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);
+
 CREATE TYPE hash_test_t1 AS (a int, b text);
 SELECT v as value, hash_record(v)::bit(32) as standard,
        hash_record_extended(v, 0)::bit(32) as extended0,
diff --git a/src/test/regress/sql/multirangetypes.sql b/src/test/regress/sql/multirangetypes.sql
new file mode 100644
index 00000000000..31375e63238
--- /dev/null
+++ b/src/test/regress/sql/multirangetypes.sql
@@ -0,0 +1,663 @@
+-- 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'));
+
+--
+-- test casts, both a built-in range type and a user-defined one:
+--
+select 'empty'::int4range::int4multirange;
+select int4range(1, 3)::int4multirange;
+select int4range(1, null)::int4multirange;
+select int4range(null, null)::int4multirange;
+select 'empty'::textrange::textmultirange;
+select textrange('a', 'c')::textmultirange;
+select textrange('a', null)::textmultirange;
+select textrange(null, null)::textmultirange;
+
+--
+-- create some test data and test the operators
+--
+
+CREATE TABLE nummultirange_test (nmr NUMMULTIRANGE);
+CREATE INDEX nummultirange_test_btree ON nummultirange_test(nmr);
+
+INSERT INTO nummultirange_test VALUES('{}');
+INSERT INTO nummultirange_test VALUES('{[,)}');
+INSERT INTO nummultirange_test VALUES('{[3,]}');
+INSERT INTO nummultirange_test VALUES('{[,), [3,]}');
+INSERT INTO nummultirange_test VALUES('{[, 5)}');
+INSERT INTO nummultirange_test VALUES(nummultirange());
+INSERT INTO nummultirange_test VALUES(nummultirange(variadic '{}'::numrange[]));
+INSERT INTO nummultirange_test VALUES(nummultirange(numrange(1.1, 2.2)));
+INSERT INTO nummultirange_test VALUES('{empty}');
+INSERT INTO nummultirange_test VALUES(nummultirange(numrange(1.7, 1.7, '[]'), numrange(1.7, 1.9)));
+INSERT INTO nummultirange_test VALUES(nummultirange(numrange(1.7, 1.7, '[]'), numrange(1.9, 2.1)));
+
+SELECT nmr, isempty(nmr), lower(nmr), upper(nmr) FROM nummultirange_test ORDER BY nmr;
+SELECT nmr, lower_inc(nmr), lower_inf(nmr), upper_inc(nmr), upper_inf(nmr) FROM nummultirange_test ORDER BY nmr;
+
+SELECT * FROM nummultirange_test WHERE nmr = '{}';
+SELECT * FROM nummultirange_test WHERE nmr = '{(,5)}';
+SELECT * FROM nummultirange_test WHERE nmr = '{[3,)}';
+SELECT * FROM nummultirange_test WHERE nmr = '{[1.7,1.7]}';
+SELECT * FROM nummultirange_test WHERE nmr = '{[1.7,1.7],[1.9,2.1)}';
+SELECT * FROM nummultirange_test WHERE nmr < '{}';
+SELECT * FROM nummultirange_test WHERE nmr < '{[-1000.0, -1000.0]}';
+SELECT * FROM nummultirange_test WHERE nmr < '{[0.0, 1.0]}';
+SELECT * FROM nummultirange_test WHERE nmr < '{[1000.0, 1001.0]}';
+SELECT * FROM nummultirange_test WHERE nmr <= '{}';
+SELECT * FROM nummultirange_test WHERE nmr <= '{[3,)}';
+SELECT * FROM nummultirange_test WHERE nmr >= '{}';
+SELECT * FROM nummultirange_test WHERE nmr >= '{[3,)}';
+SELECT * FROM nummultirange_test WHERE nmr > '{}';
+SELECT * FROM nummultirange_test WHERE nmr > '{[-1000.0, -1000.0]}';
+SELECT * FROM nummultirange_test WHERE nmr > '{[0.0, 1.0]}';
+SELECT * FROM nummultirange_test WHERE nmr > '{[1000.0, 1001.0]}';
+SELECT * FROM nummultirange_test WHERE nmr <> '{}';
+SELECT * FROM nummultirange_test WHERE nmr <> '{(,5)}';
+
+select nummultirange(numrange(2.0, 1.0));
+select nummultirange(numrange(5.0, 6.0), numrange(1.0, 2.0));
+
+analyze nummultirange_test;
+
+-- overlaps
+SELECT * FROM nummultirange_test WHERE range_overlaps_multirange(numrange(4.0, 4.2), nmr);
+SELECT * FROM nummultirange_test WHERE numrange(4.0, 4.2) && nmr;
+SELECT * FROM nummultirange_test WHERE multirange_overlaps_range(nmr, numrange(4.0, 4.2));
+SELECT * FROM nummultirange_test WHERE nmr && numrange(4.0, 4.2);
+SELECT * FROM nummultirange_test WHERE multirange_overlaps_multirange(nmr, nummultirange(numrange(4.0, 4.2), numrange(6.0, 7.0)));
+SELECT * FROM nummultirange_test WHERE nmr && nummultirange(numrange(4.0, 4.2), numrange(6.0, 7.0));
+SELECT * FROM nummultirange_test WHERE nmr && nummultirange(numrange(6.0, 7.0));
+SELECT * FROM nummultirange_test WHERE nmr && nummultirange(numrange(6.0, 7.0), numrange(8.0, 9.0));
+
+-- mr contains x
+SELECT * FROM nummultirange_test WHERE multirange_contains_elem(nmr, 4.0);
+SELECT * FROM nummultirange_test WHERE nmr @> 4.0;
+SELECT * FROM nummultirange_test WHERE multirange_contains_range(nmr, numrange(4.0, 4.2));
+SELECT * FROM nummultirange_test WHERE nmr @> numrange(4.0, 4.2);
+SELECT * FROM nummultirange_test WHERE multirange_contains_multirange(nmr, '{[4.0,4.2), [6.0, 8.0)}');
+SELECT * FROM nummultirange_test WHERE nmr @> '{[4.0,4.2), [6.0, 8.0)}'::nummultirange;
+
+-- x is contained by mr
+SELECT * FROM nummultirange_test WHERE elem_contained_by_multirange(4.0, nmr);
+SELECT * FROM nummultirange_test WHERE 4.0 <@ nmr;
+SELECT * FROM nummultirange_test WHERE range_contained_by_multirange(numrange(4.0, 4.2), nmr);
+SELECT * FROM nummultirange_test WHERE numrange(4.0, 4.2) <@ nmr;
+SELECT * FROM nummultirange_test WHERE multirange_contained_by_multirange('{[4.0,4.2), [6.0, 8.0)}', nmr);
+SELECT * FROM nummultirange_test WHERE '{[4.0,4.2), [6.0, 8.0)}'::nummultirange <@ nmr;
+
+-- overlaps
+SELECT 'empty'::numrange && nummultirange();
+SELECT 'empty'::numrange && nummultirange(numrange(1,2));
+SELECT nummultirange() && 'empty'::numrange;
+SELECT nummultirange(numrange(1,2)) && 'empty'::numrange;
+SELECT nummultirange() && nummultirange();
+SELECT nummultirange() && nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) && nummultirange();
+SELECT nummultirange(numrange(3,4)) && nummultirange(numrange(1,2), numrange(7,8));
+SELECT nummultirange(numrange(1,2), numrange(7,8)) && nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(3,4)) && nummultirange(numrange(1,2), numrange(3.5,8));
+SELECT nummultirange(numrange(1,2), numrange(3.5,8)) && numrange(3,4);
+SELECT nummultirange(numrange(1,2), numrange(3.5,8)) && nummultirange(numrange(3,4));
+
+-- contains
+SELECT nummultirange() @> nummultirange();
+SELECT nummultirange() @> 'empty'::numrange;
+SELECT nummultirange(numrange(null,null)) @> numrange(1,2);
+SELECT nummultirange(numrange(null,null)) @> numrange(null,2);
+SELECT nummultirange(numrange(null,null)) @> numrange(2,null);
+SELECT nummultirange(numrange(null,5)) @> numrange(null,3);
+SELECT nummultirange(numrange(null,5)) @> numrange(null,8);
+SELECT nummultirange(numrange(5,null)) @> numrange(8,null);
+SELECT nummultirange(numrange(5,null)) @> numrange(3,null);
+SELECT nummultirange(numrange(1,5)) @> numrange(8,9);
+SELECT nummultirange(numrange(1,5)) @> numrange(3,9);
+SELECT nummultirange(numrange(1,5)) @> numrange(1,4);
+SELECT nummultirange(numrange(1,5)) @> numrange(1,5);
+SELECT nummultirange(numrange(-4,-2), numrange(1,5)) @> numrange(1,5);
+SELECT nummultirange(numrange(1,5), numrange(8,9)) @> numrange(1,5);
+SELECT nummultirange(numrange(1,5), numrange(8,9)) @> numrange(6,7);
+SELECT nummultirange(numrange(1,5), numrange(6,9)) @> numrange(6,7);
+SELECT '{[1,5)}'::nummultirange @> '{[1,5)}';
+SELECT '{[-4,-2), [1,5)}'::nummultirange @> '{[1,5)}';
+SELECT '{[1,5), [8,9)}'::nummultirange @> '{[1,5)}';
+SELECT '{[1,5), [8,9)}'::nummultirange @> '{[6,7)}';
+SELECT '{[1,5), [6,9)}'::nummultirange @> '{[6,7)}';
+
+-- is contained by
+SELECT nummultirange() <@ nummultirange();
+SELECT 'empty'::numrange <@ nummultirange();
+SELECT numrange(1,2) <@ nummultirange(numrange(null,null));
+SELECT numrange(null,2) <@ nummultirange(numrange(null,null));
+SELECT numrange(2,null) <@ nummultirange(numrange(null,null));
+SELECT numrange(null,3) <@ nummultirange(numrange(null,5));
+SELECT numrange(null,8) <@ nummultirange(numrange(null,5));
+SELECT numrange(8,null) <@ nummultirange(numrange(5,null));
+SELECT numrange(3,null) <@ nummultirange(numrange(5,null));
+SELECT numrange(8,9) <@ nummultirange(numrange(1,5));
+SELECT numrange(3,9) <@ nummultirange(numrange(1,5));
+SELECT numrange(1,4) <@ nummultirange(numrange(1,5));
+SELECT numrange(1,5) <@ nummultirange(numrange(1,5));
+SELECT numrange(1,5) <@ nummultirange(numrange(-4,-2), numrange(1,5));
+SELECT numrange(1,5) <@ nummultirange(numrange(1,5), numrange(8,9));
+SELECT numrange(6,7) <@ nummultirange(numrange(1,5), numrange(8,9));
+SELECT numrange(6,7) <@ nummultirange(numrange(1,5), numrange(6,9));
+SELECT '{[1,5)}' <@ '{[1,5)}'::nummultirange;
+SELECT '{[1,5)}' <@ '{[-4,-2), [1,5)}'::nummultirange;
+SELECT '{[1,5)}' <@ '{[1,5), [8,9)}'::nummultirange;
+SELECT '{[6,7)}' <@ '{[1,5), [8,9)}'::nummultirange;
+SELECT '{[6,7)}' <@ '{[1,5), [6,9)}'::nummultirange;
+
+-- overleft
+SELECT 'empty'::numrange &< nummultirange();
+SELECT 'empty'::numrange &< nummultirange(numrange(1,2));
+SELECT nummultirange() &< 'empty'::numrange;
+SELECT nummultirange(numrange(1,2)) &< 'empty'::numrange;
+SELECT nummultirange() &< nummultirange();
+SELECT nummultirange(numrange(1,2)) &< nummultirange();
+SELECT nummultirange() &< nummultirange(numrange(1,2));
+SELECT numrange(6,7) &< nummultirange(numrange(3,4));
+SELECT numrange(1,2) &< nummultirange(numrange(3,4));
+SELECT numrange(1,4) &< nummultirange(numrange(3,4));
+SELECT numrange(1,6) &< nummultirange(numrange(3,4));
+SELECT numrange(3.5,6) &< nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(6,7)) &< numrange(3,4);
+SELECT nummultirange(numrange(1,2)) &< numrange(3,4);
+SELECT nummultirange(numrange(1,4)) &< numrange(3,4);
+SELECT nummultirange(numrange(1,6)) &< numrange(3,4);
+SELECT nummultirange(numrange(3.5,6)) &< numrange(3,4);
+SELECT nummultirange(numrange(6,7)) &< nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,2)) &< nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,4)) &< nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,6)) &< nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(3.5,6)) &< nummultirange(numrange(3,4));
+
+-- overright
+SELECT nummultirange() &> 'empty'::numrange;
+SELECT nummultirange(numrange(1,2)) &> 'empty'::numrange;
+SELECT 'empty'::numrange &> nummultirange();
+SELECT 'empty'::numrange &> nummultirange(numrange(1,2));
+SELECT nummultirange() &> nummultirange();
+SELECT nummultirange() &> nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) &> nummultirange();
+SELECT nummultirange(numrange(3,4)) &> numrange(6,7);
+SELECT nummultirange(numrange(3,4)) &> numrange(1,2);
+SELECT nummultirange(numrange(3,4)) &> numrange(1,4);
+SELECT nummultirange(numrange(3,4)) &> numrange(1,6);
+SELECT nummultirange(numrange(3,4)) &> numrange(3.5,6);
+SELECT numrange(3,4) &> nummultirange(numrange(6,7));
+SELECT numrange(3,4) &> nummultirange(numrange(1,2));
+SELECT numrange(3,4) &> nummultirange(numrange(1,4));
+SELECT numrange(3,4) &> nummultirange(numrange(1,6));
+SELECT numrange(3,4) &> nummultirange(numrange(3.5,6));
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(6,7));
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(1,4));
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(1,6));
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(3.5,6));
+
+-- meets
+SELECT 'empty'::numrange -|- nummultirange();
+SELECT 'empty'::numrange -|- nummultirange(numrange(1,2));
+SELECT nummultirange() -|- 'empty'::numrange;
+SELECT nummultirange(numrange(1,2)) -|- 'empty'::numrange;
+SELECT nummultirange() -|- nummultirange();
+SELECT nummultirange(numrange(1,2)) -|- nummultirange();
+SELECT nummultirange() -|- nummultirange(numrange(1,2));
+SELECT numrange(1,2) -|- nummultirange(numrange(2,4));
+SELECT numrange(1,2) -|- nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,2)) -|- numrange(2,4);
+SELECT nummultirange(numrange(1,2)) -|- numrange(3,4);
+SELECT nummultirange(numrange(1,2)) -|- nummultirange(numrange(2,4));
+SELECT nummultirange(numrange(1,2)) -|- nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,2), numrange(5,6)) -|- nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,2), numrange(5,6)) -|- nummultirange(numrange(6,7));
+SELECT nummultirange(numrange(1,2), numrange(5,6)) -|- nummultirange(numrange(8,9));
+SELECT nummultirange(numrange(1,2)) -|- nummultirange(numrange(2,4), numrange(6,7));
+
+-- strictly left
+select 'empty'::numrange << nummultirange();
+select numrange(1,2) << nummultirange();
+select numrange(1,2) << nummultirange(numrange(3,4));
+select numrange(1,2) << nummultirange(numrange(0,4));
+select numrange(1,2) << nummultirange(numrange(0,4), numrange(7,8));
+select nummultirange() << 'empty'::numrange;
+select nummultirange() << numrange(1,2);
+select nummultirange(numrange(3,4)) << numrange(3,6);
+select nummultirange(numrange(0,2)) << numrange(3,6);
+select nummultirange(numrange(0,2), numrange(7,8)) << numrange(3,6);
+select nummultirange(numrange(-4,-2), numrange(0,2)) << numrange(3,6);
+select nummultirange() << nummultirange();
+select nummultirange() << nummultirange(numrange(1,2));
+select nummultirange(numrange(1,2)) << nummultirange();
+select nummultirange(numrange(1,2)) << nummultirange(numrange(1,2));
+select nummultirange(numrange(1,2)) << nummultirange(numrange(3,4));
+select nummultirange(numrange(1,2)) << nummultirange(numrange(3,4), numrange(7,8));
+select nummultirange(numrange(1,2), numrange(4,5)) << nummultirange(numrange(3,4), numrange(7,8));
+
+-- strictly right
+select nummultirange() >> 'empty'::numrange;
+select nummultirange() >> numrange(1,2);
+select nummultirange(numrange(3,4)) >> numrange(1,2);
+select nummultirange(numrange(0,4)) >> numrange(1,2);
+select nummultirange(numrange(0,4), numrange(7,8)) >> numrange(1,2);
+select 'empty'::numrange >> nummultirange();
+select numrange(1,2) >> nummultirange();
+select numrange(3,6) >> nummultirange(numrange(3,4));
+select numrange(3,6) >> nummultirange(numrange(0,2));
+select numrange(3,6) >> nummultirange(numrange(0,2), numrange(7,8));
+select numrange(3,6) >> nummultirange(numrange(-4,-2), numrange(0,2));
+select nummultirange() >> nummultirange();
+select nummultirange(numrange(1,2)) >> nummultirange();
+select nummultirange() >> nummultirange(numrange(1,2));
+select nummultirange(numrange(1,2)) >> nummultirange(numrange(1,2));
+select nummultirange(numrange(3,4)) >> nummultirange(numrange(1,2));
+select nummultirange(numrange(3,4), numrange(7,8)) >> nummultirange(numrange(1,2));
+select nummultirange(numrange(3,4), numrange(7,8)) >> nummultirange(numrange(1,2), numrange(4,5));
+
+-- union
+SELECT nummultirange() + nummultirange();
+SELECT nummultirange() + nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) + nummultirange();
+SELECT nummultirange(numrange(1,2)) + nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) + nummultirange(numrange(2,4));
+SELECT nummultirange(numrange(1,2)) + nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) + nummultirange(numrange(2,4));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) + nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) + nummultirange(numrange(0,9));
+
+-- merge
+SELECT range_merge(nummultirange());
+SELECT range_merge(nummultirange(numrange(1,2)));
+SELECT range_merge(nummultirange(numrange(1,2), numrange(7,8)));
+
+-- minus
+SELECT nummultirange() - nummultirange();
+SELECT nummultirange() - nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) - nummultirange();
+SELECT nummultirange(numrange(1,2), numrange(3,4)) - nummultirange();
+SELECT nummultirange(numrange(1,2)) - nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) - nummultirange(numrange(2,4));
+SELECT nummultirange(numrange(1,2)) - nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(2,3));
+SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(0,8));
+SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(0,2));
+SELECT nummultirange(numrange(1,8)) - nummultirange(numrange(0,2), numrange(3,4));
+SELECT nummultirange(numrange(1,8)) - nummultirange(numrange(2,3), numrange(5,null));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(-2,0));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(2,4));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(3,5));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(0,9));
+SELECT nummultirange(numrange(1,3), numrange(4,5)) - nummultirange(numrange(2,9));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(8,9));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(-2,0), numrange(8,9));
+
+-- intersection
+SELECT nummultirange() * nummultirange();
+SELECT nummultirange() * nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) * nummultirange();
+SELECT '{[1,3)}'::nummultirange * '{[1,5)}'::nummultirange;
+SELECT '{[1,3)}'::nummultirange * '{[0,5)}'::nummultirange;
+SELECT '{[1,3)}'::nummultirange * '{[0,2)}'::nummultirange;
+SELECT '{[1,3)}'::nummultirange * '{[2,5)}'::nummultirange;
+SELECT '{[1,4)}'::nummultirange * '{[2,3)}'::nummultirange;
+SELECT '{[1,4)}'::nummultirange * '{[0,2), [3,5)}'::nummultirange;
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[0,8), [9,12)}'::nummultirange;
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[9,12)}'::nummultirange;
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[-5,-4), [5,6), [9,12)}'::nummultirange;
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[0,2), [3,8), [9,12)}'::nummultirange;
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[0,2), [3,8), [9,12)}'::nummultirange;
+
+--
+-- range_agg function
+--
+create table reservations ( room_id integer not null, booked_during daterange );
+insert into reservations values
+-- 1: has a meets and a gap
+(1, daterange('2018-07-01', '2018-07-07')),
+(1, daterange('2018-07-07', '2018-07-14')),
+(1, daterange('2018-07-20', '2018-07-22')),
+-- 2: just a single row
+(2, daterange('2018-07-01', '2018-07-03')),
+-- 3: one null range
+(3, NULL),
+-- 4: two null ranges
+(4, NULL),
+(4, NULL),
+-- 5: a null range and a non-null range
+(5, NULL),
+(5, daterange('2018-07-01', '2018-07-03')),
+-- 6: has overlap
+(6, daterange('2018-07-01', '2018-07-07')),
+(6, daterange('2018-07-05', '2018-07-10')),
+-- 7: two ranges that meet: no gap or overlap
+(7, daterange('2018-07-01', '2018-07-07')),
+(7, daterange('2018-07-07', '2018-07-14')),
+-- 8: an empty range
+(8, 'empty'::daterange)
+;
+SELECT   room_id, range_agg(booked_during)
+FROM     reservations
+GROUP BY room_id
+ORDER BY room_id;
+
+-- range_agg on a custom range type too
+SELECT  range_agg(r)
+FROM    (VALUES
+          ('[a,c]'::textrange),
+          ('[b,b]'::textrange),
+          ('[c,f]'::textrange),
+          ('[g,h)'::textrange),
+          ('[h,j)'::textrange)
+        ) t(r);
+
+select range_intersect_agg(nmr) from nummultirange_test;
+select range_intersect_agg(nmr) from nummultirange_test where false;
+-- test with just one input:
+select range_intersect_agg(nmr) from (values ('{[1,2]}'::nummultirange)) t(nmr);
+select range_intersect_agg(nmr) from nummultirange_test where nmr @> 4.0;
+
+create table nummultirange_test2(nmr nummultirange);
+create index nummultirange_test2_hash_idx on nummultirange_test2 using hash (nmr);
+
+INSERT INTO nummultirange_test2 VALUES('{[, 5)}');
+INSERT INTO nummultirange_test2 VALUES(nummultirange(numrange(1.1, 2.2)));
+INSERT INTO nummultirange_test2 VALUES(nummultirange(numrange(1.1, 2.2)));
+INSERT INTO nummultirange_test2 VALUES(nummultirange(numrange(1.1, 2.2,'()')));
+INSERT INTO nummultirange_test2 VALUES('{}');
+
+select * from nummultirange_test2 where nmr = '{}';
+select * from nummultirange_test2 where nmr = nummultirange(numrange(1.1, 2.2));
+select * from nummultirange_test2 where nmr = nummultirange(numrange(1.1, 2.3));
+
+set enable_nestloop=t;
+set enable_hashjoin=f;
+set enable_mergejoin=f;
+select * from nummultirange_test natural join nummultirange_test2 order by nmr;
+set enable_nestloop=f;
+set enable_hashjoin=t;
+set enable_mergejoin=f;
+select * from nummultirange_test natural join nummultirange_test2 order by nmr;
+set enable_nestloop=f;
+set enable_hashjoin=f;
+set enable_mergejoin=t;
+select * from nummultirange_test natural join nummultirange_test2 order by nmr;
+
+set enable_nestloop to default;
+set enable_hashjoin to default;
+set enable_mergejoin to default;
+
+DROP TABLE nummultirange_test2;
+
+--
+-- Test user-defined multirange of floats
+--
+
+select '{[123.001, 5.e9)}'::float8multirange @> 888.882::float8;
+create table float8multirange_test(f8mr float8multirange, i int);
+insert into float8multirange_test values(float8multirange(float8range(-100.00007, '1.111113e9')), 42);
+select * from float8multirange_test;
+drop table float8multirange_test;
+
+--
+-- Test multirange types over domains
+--
+
+create domain mydomain as int4;
+create type mydomainrange as range(subtype=mydomain);
+select '{[4,50)}'::mydomainmultirange @> 7::mydomain;
+drop domain mydomain cascade;
+
+--
+-- Test domains over multirange types
+--
+
+create domain restrictedmultirange as int4multirange check (upper(value) < 10);
+select '{[4,5)}'::restrictedmultirange @> 7;
+select '{[4,50)}'::restrictedmultirange @> 7; -- should fail
+drop domain restrictedmultirange;
+
+--
+-- Test multiple multirange types over the same subtype and manual naming of
+-- the multirange type.
+--
+
+-- should fail
+create type textrange1 as range(subtype=text, multirange_type_name=int, collation="C");
+-- should pass
+create type textrange1 as range(subtype=text, multirange_type_name=multirange_of_text, collation="C");
+-- should pass, because existing _textrange1 is automatically renamed
+create type textrange2 as range(subtype=text, multirange_type_name=_textrange1, collation="C");
+
+select multirange_of_text(textrange2('a','Z'));  -- should fail
+select multirange_of_text(textrange1('a','Z')) @> 'b'::text;
+select _textrange1(textrange2('a','z')) @> 'b'::text;
+
+drop type textrange1;
+drop type textrange2;
+
+--
+-- Test polymorphic type system
+--
+
+create function anyarray_anymultirange_func(a anyarray, r anymultirange)
+  returns anyelement as 'select $1[1] + lower($2);' language sql;
+
+select anyarray_anymultirange_func(ARRAY[1,2], int4multirange(int4range(10,20)));
+
+-- should fail
+select anyarray_anymultirange_func(ARRAY[1,2], nummultirange(numrange(10,20)));
+
+drop function anyarray_anymultirange_func(anyarray, anymultirange);
+
+-- should fail
+create function bogus_func(anyelement)
+  returns anymultirange as 'select int4multirange(int4range(1,10))' language sql;
+
+-- should fail
+create function bogus_func(int)
+  returns anymultirange as 'select int4multirange(int4range(1,10))' language sql;
+
+create function range_add_bounds(anymultirange)
+  returns anyelement as 'select lower($1) + upper($1)' language sql;
+
+select range_add_bounds(int4multirange(int4range(1, 17)));
+select range_add_bounds(nummultirange(numrange(1.0001, 123.123)));
+
+create function multirangetypes_sql(q anymultirange, b anyarray, out c anyelement)
+  as $$ select upper($1) + $2[1] $$
+  language sql;
+
+select multirangetypes_sql(int4multirange(int4range(1,10)), ARRAY[2,20]);
+select multirangetypes_sql(nummultirange(numrange(1,10)), ARRAY[2,20]);  -- match failure
+
+create function anycompatiblearray_anycompatiblemultirange_func(a anycompatiblearray, mr anycompatiblemultirange)
+  returns anycompatible as 'select $1[1] + lower($2);' language sql;
+
+select anycompatiblearray_anycompatiblemultirange_func(ARRAY[1,2], multirange(int4range(10,20)));
+
+select anycompatiblearray_anycompatiblemultirange_func(ARRAY[1,2], multirange(numrange(10,20)));
+
+-- should fail
+select anycompatiblearray_anycompatiblemultirange_func(ARRAY[1.1,2], multirange(int4range(10,20)));
+
+drop function anycompatiblearray_anycompatiblemultirange_func(anycompatiblearray, anycompatiblemultirange);
+
+create function anycompatiblerange_anycompatiblemultirange_func(r anycompatiblerange, mr anycompatiblemultirange)
+  returns anycompatible as 'select lower($1) + lower($2);' language sql;
+
+select anycompatiblerange_anycompatiblemultirange_func(int4range(1,2), multirange(int4range(10,20)));
+
+-- should fail
+select anycompatiblerange_anycompatiblemultirange_func(numrange(1,2), multirange(int4range(10,20)));
+
+drop function anycompatiblerange_anycompatiblemultirange_func(anycompatiblerange, anycompatiblemultirange);
+
+-- should fail
+create function bogus_func(anycompatible)
+  returns anycompatiblerange as 'select int4range(1,10)' language sql;
+
+--
+-- Arrays of multiranges
+--
+
+select ARRAY[nummultirange(numrange(1.1, 1.2)), nummultirange(numrange(12.3, 155.5))];
+
+create table i8mr_array (f1 int, f2 int8multirange[]);
+insert into i8mr_array values (42, array[int8multirange(int8range(1,10)), int8multirange(int8range(2,20))]);
+select * from i8mr_array;
+drop table i8mr_array;
+
+--
+-- Multiranges of arrays
+--
+
+select arraymultirange(arrayrange(ARRAY[1,2], ARRAY[2,1]));
+select arraymultirange(arrayrange(ARRAY[2,1], ARRAY[1,2]));  -- fail
+
+select array[1,1] <@ arraymultirange(arrayrange(array[1,2], array[2,1]));
+select array[1,3] <@ arraymultirange(arrayrange(array[1,2], array[2,1]));
+
+--
+-- Ranges of composites
+--
+
+create type two_ints as (a int, b int);
+create type two_ints_range as range (subtype = two_ints);
+
+-- with force_parallel_mode on, this exercises tqueue.c's range remapping
+select *, row_to_json(upper(t)) as u from
+  (values (two_ints_multirange(two_ints_range(row(1,2), row(3,4)))),
+          (two_ints_multirange(two_ints_range(row(5,6), row(7,8))))) v(t);
+
+drop type two_ints cascade;
+
+--
+-- Check behavior when subtype lacks a hash function
+--
+
+set enable_sort = off;  -- try to make it pick a hash setop implementation
+
+select '{(2,5)}'::cashmultirange except select '{(5,6)}'::cashmultirange;
+
+reset enable_sort;
+
+--
+-- OUT/INOUT/TABLE functions
+--
+
+-- infer anymultirange from anymultirange
+create function mr_outparam_succeed(i anymultirange, out r anymultirange, out t text)
+  as $$ select $1, 'foo'::text $$ language sql;
+
+select * from mr_outparam_succeed(int4multirange(int4range(1,2)));
+
+-- infer anyarray from anymultirange
+create function mr_outparam_succeed2(i anymultirange, out r anyarray, out t text)
+  as $$ select ARRAY[upper($1)], 'foo'::text $$ language sql;
+
+select * from mr_outparam_succeed2(int4multirange(int4range(1,2)));
+
+-- infer anyrange from anymultirange
+create function mr_outparam_succeed3(i anymultirange, out r anyrange, out t text)
+  as $$ select range_merge($1), 'foo'::text $$ language sql;
+select * from mr_outparam_succeed3(int4multirange(int4range(1,2)));
+
+-- infer anymultirange from anyrange
+create function mr_outparam_succeed4(i anyrange, out r anymultirange, out t text)
+  as $$ select multirange($1), 'foo'::text $$ language sql;
+
+select * from mr_outparam_succeed4(int4range(1,2));
+
+-- infer anyelement from anymultirange
+create function mr_inoutparam_succeed(out i anyelement, inout r anymultirange)
+  as $$ select upper($1), $1 $$ language sql;
+
+select * from mr_inoutparam_succeed(int4multirange(int4range(1,2)));
+
+-- infer anyelement+anymultirange from anyelement+anymultirange
+create function mr_table_succeed(i anyelement, r anymultirange) returns table(i anyelement, r anymultirange)
+  as $$ select $1, $2 $$ language sql;
+
+select * from mr_table_succeed(123, int4multirange(int4range(1,11)));
+
+-- use anymultirange in plpgsql
+create function mr_polymorphic(i anyrange) returns anymultirange
+  as $$ begin return multirange($1); end; $$ language plpgsql;
+select mr_polymorphic(int4range(1, 4));
+
+-- should fail
+create function mr_outparam_fail(i anyelement, out r anymultirange, out t text)
+  as $$ select '[1,10]', 'foo' $$ language sql;
+
+--should fail
+create function mr_inoutparam_fail(inout i anyelement, out r anymultirange)
+  as $$ select $1, '[1,10]' $$ language sql;
+
+--should fail
+create function mr_table_fail(i anyelement) returns table(i anyelement, r anymultirange)
+  as $$ select $1, '[1,10]' $$ language sql;
diff --git a/src/test/regress/sql/opr_sanity.sql b/src/test/regress/sql/opr_sanity.sql
index 4189a5a4e09..bbd3834b634 100644
--- a/src/test/regress/sql/opr_sanity.sql
+++ b/src/test/regress/sql/opr_sanity.sql
@@ -42,6 +42,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;
@@ -166,7 +170,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::regtype, p2.prorettype::regtype
 FROM pg_proc AS p1, pg_proc AS p2
@@ -176,6 +181,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;
 
@@ -187,6 +194,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;
 
@@ -198,6 +207,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;
 
@@ -281,16 +292,19 @@ WHERE p1.prorettype IN
      '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;
 
--- anyrange is tighter than the rest, can only resolve from anyrange input
+-- anyrange and anymultirange are tighter than the rest, can only resolve
+-- from each other
 
 SELECT p1.oid, p1.proname
 FROM pg_proc as p1
-WHERE p1.prorettype = 'anyrange'::regtype
+WHERE p1.prorettype IN ('anyrange'::regtype, 'anymultirange'::regtype)
   AND NOT
-     'anyrange'::regtype = ANY (p1.proargtypes)
+    ('anyrange'::regtype = ANY (p1.proargtypes) OR
+      'anymultirange'::regtype = ANY (p1.proargtypes))
 ORDER BY 2;
 
 -- similarly for the anycompatible family
diff --git a/src/test/regress/sql/polymorphism.sql b/src/test/regress/sql/polymorphism.sql
index ff517fea41a..891b023ada0 100644
--- a/src/test/regress/sql/polymorphism.sql
+++ b/src/test/regress/sql/polymorphism.sql
@@ -71,6 +71,16 @@ select polyf(int4range(42, 49), 11, 4.5) as fail;  -- range type doesn't fit
 
 drop function polyf(x anycompatiblerange, y anycompatible, z anycompatible);
 
+create function polyf(x anycompatiblemultirange, y anycompatible, z anycompatible) returns anycompatiblearray as $$
+  select array[lower(x), upper(x), y, z]
+$$ language sql;
+
+select polyf(multirange(int4range(42, 49)), 11, 2::smallint) as int, polyf(multirange(float8range(4.5, 7.8)), 7.8, 11::real) as num;
+
+select polyf(multirange(int4range(42, 49)), 11, 4.5) as fail;  -- range type doesn't fit
+
+drop function polyf(x anycompatiblemultirange, y anycompatible, z anycompatible);
+
 -- fail, can't infer type:
 create function polyf(x anycompatible) returns anycompatiblerange as $$
   select array[x + 1, x + 2]
@@ -84,6 +94,19 @@ select polyf(int4range(42, 49), array[11]) as int, polyf(float8range(4.5, 7.8),
 
 drop function polyf(x anycompatiblerange, y anycompatiblearray);
 
+-- fail, can't infer type:
+create function polyf(x anycompatible) returns anycompatiblemultirange as $$
+  select array[x + 1, x + 2]
+$$ language sql;
+
+create function polyf(x anycompatiblemultirange, y anycompatiblearray) returns anycompatiblemultirange as $$
+  select x
+$$ language sql;
+
+select polyf(multirange(int4range(42, 49)), array[11]) as int, polyf(multirange(float8range(4.5, 7.8)), array[7]) as num;
+
+drop function polyf(x anycompatiblemultirange, y anycompatiblearray);
+
 create function polyf(a anyelement, b anyarray,
                       c anycompatible, d anycompatible,
                       OUT x anyarray, OUT y anycompatiblearray)
@@ -997,6 +1020,35 @@ returns anycompatiblerange as $$
   select $1
 $$ language sql;
 
+create function anyctest(anycompatible, anycompatiblemultirange)
+returns anycompatiblemultirange as $$
+  select $2
+$$ language sql;
+
+select x, pg_typeof(x) from anyctest(11, multirange(int4range(4,7))) x;
+select x, pg_typeof(x) from anyctest(11, multirange(numrange(4,7))) x;
+select x, pg_typeof(x) from anyctest(11, 12) x;  -- fail
+select x, pg_typeof(x) from anyctest(11.2, multirange(int4range(4,7))) x;  -- fail
+select x, pg_typeof(x) from anyctest(11.2, '{[4,7)}') x;  -- fail
+
+drop function anyctest(anycompatible, anycompatiblemultirange);
+
+create function anyctest(anycompatiblemultirange, anycompatiblemultirange)
+returns anycompatible as $$
+  select lower($1) + upper($2)
+$$ language sql;
+
+select x, pg_typeof(x) from anyctest(multirange(int4range(11,12)), multirange(int4range(4,7))) x;
+select x, pg_typeof(x) from anyctest(multirange(int4range(11,12)), multirange(numrange(4,7))) x; -- fail
+
+drop function anyctest(anycompatiblemultirange, anycompatiblemultirange);
+
+-- fail, can't infer result type:
+create function anyctest(anycompatible)
+returns anycompatiblemultirange as $$
+  select $1
+$$ language sql;
+
 create function anyctest(anycompatiblenonarray, anycompatiblenonarray)
 returns anycompatiblearray as $$
   select array[$1, $2]
diff --git a/src/test/regress/sql/rangetypes.sql b/src/test/regress/sql/rangetypes.sql
index 4048b1d1854..b69efede3ae 100644
--- a/src/test/regress/sql/rangetypes.sql
+++ b/src/test/regress/sql/rangetypes.sql
@@ -118,6 +118,12 @@ select numrange(1.0, 2.0) * numrange(2.0, 3.0);
 select numrange(1.0, 2.0) * numrange(1.5, 3.0);
 select numrange(1.0, 2.0) * numrange(2.5, 3.0);
 
+select range_intersect_agg(nr) from numrange_test;
+select range_intersect_agg(nr) from numrange_test where false;
+select range_intersect_agg(nr) from numrange_test where nr @> 4.0;
+
+analyze numrange_test;
+
 create table numrange_test2(nr numrange);
 create index numrange_test2_hash_idx on numrange_test2 using hash (nr);
 
@@ -528,6 +534,7 @@ reset enable_sort;
 -- OUT/INOUT/TABLE functions
 --
 
+-- infer anyrange from anyrange
 create function outparam_succeed(i anyrange, out r anyrange, out t text)
   as $$ select $1, 'foo'::text $$ language sql;
 
@@ -539,6 +546,13 @@ create function outparam2_succeed(r anyrange, out lu anyarray, out ul anyarray)
 
 select * from outparam2_succeed(int4range(1,11));
 
+-- infer anyarray from anyrange
+create function outparam_succeed2(i anyrange, out r anyarray, out t text)
+  as $$ select ARRAY[upper($1)], 'foo'::text $$ language sql;
+
+select * from outparam_succeed2(int4range(int4range(1,2)));
+
+-- infer anyelement from anyrange
 create function inoutparam_succeed(out i anyelement, inout r anyrange)
   as $$ select upper($1), $1 $$ language sql;
 
diff --git a/src/test/regress/sql/type_sanity.sql b/src/test/regress/sql/type_sanity.sql
index 8c6e614f20a..4739aca84a3 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'));
@@ -154,7 +154,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')
@@ -183,7 +183,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')
@@ -235,7 +235,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')
@@ -264,7 +264,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')
@@ -489,3 +489,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.rngmultitypid
+FROM pg_range p1
+WHERE p1.rngmultitypid IS NULL OR p1.rngmultitypid = 0;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index a9dca717a6d..abbce78210e 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -626,6 +626,7 @@ ExecutorStart_hook_type
 ExpandedArrayHeader
 ExpandedObjectHeader
 ExpandedObjectMethods
+ExpandedMultirangeHeader
 ExpandedRecordFieldInfo
 ExpandedRecordHeader
 ExplainDirectModify_function
@@ -1395,6 +1396,9 @@ MultiXactMember
 MultiXactOffset
 MultiXactStateData
 MultiXactStatus
+MultirangeIOData
+MultirangeParseState
+MultirangeType
 NDBOX
 NODE
 NUMCacheEntry
@@ -2276,6 +2280,7 @@ ShellTypeInfo
 ShippableCacheEntry
 ShippableCacheKey
 ShmemIndexEnt
+ShortRangeType
 ShutdownForeignScan_function
 ShutdownInformation
 ShutdownMode
#142Alexander Korotkov
aekorotkov@gmail.com
In reply to: Alexander Korotkov (#141)
1 attachment(s)
Re: range_agg

On Wed, Dec 16, 2020 at 7:14 AM Alexander Korotkov <aekorotkov@gmail.com> wrote:

On Wed, Dec 16, 2020 at 2:21 AM Alexander Korotkov <aekorotkov@gmail.com> wrote:

I decided to work on this patch myself. The next revision is attached.

The changes are as follows.

1. CREATE TYPE ... AS RANGE command now accepts new argument
multirange_type_name. If multirange_type_name isn't specified, then
multirange type name is selected automatically. pg_dump always
specifies multirange_type_name (if dumping at least pg14). Thanks to
that dumps are always restorable.
2. Multiranges now have a new binary format. After the MultirangeType
struct, an array of offsets comes, then an array of flags and finally
bounds themselves. Offsets points to the bounds of particular range
within multirange. Thanks to that particular range could be accessed
by number without deserialization of the whole multirange. Offsets
are stored in compression-friendly format similar to jsonb (actually
only every 4th of those "offsets" is really offsets, others are
lengths).
3. Most of simple functions working with multirages now don't
deserialize the whole multirange. Instead they fetch bounds of
particular ranges, and that doesn't even require any additional memory
allocation.
4. I've removed ExpandedObject support from the patch. I don't see
much point in it assuming all the functions are returning serialized
multirage anyway. We can add ExpandedObject support in future if
needed.
5. multirange_contains_element(), multirange_contains_range(),
multirange_overlaps_range() now use binary search. Thanks to binary
format, which doesn't require full deserialization, these functions
now work with O(log N) complexity.

Comments and documentation still need revision according to these
changes. I'm going to continue with this.

The next 27th revision is attached. It contains minor documentation
and code changes, in particular it should address
commitfest.cputube.org complaints.

The next 28th revision is attached. It comes with minor code
improvements, comments and commit message.

Also, given now we have a manual multirange type naming mechanism,
I've removed logic for prepending automatically generated names with
underscores to evade collision. Instead, user is advised to name
multirange manually (as discussed in [1]).

I think this patch is very close to committable. I'm going to spend
some more time further polishing it and commit (if I don't find a
major issue or face objections).

Links
1. /messages/by-id/CALNJ-vSUpQ_Y=jXvTxt1VYFztaBSsWVXeF1y6gTYQ4bOiWDLgQ@mail.gmail.com

------
Regards,
Alexander Korotkov

Attachments:

v28-multirange.patchapplication/x-patch; name=v28-multirange.patchDownload
commit 3166f08868d92a407b59f92d925408bc06ebd0a3
Author: Alexander Korotkov <akorotkov@postgresql.org>
Date:   Sun Nov 29 22:32:30 2020 +0300

    Multirange datatypes
    
    Multiranges are basically sorted arrays of non-overlapping ranges with
    set-theoretic operations defined over them.
    
    Since v14, each range type automatically gets a corresponding multirange datatype.
    There are both manual and automatic mechanisms for naming multirange types.
    Once can specify multirange type name using multirange_type_name attribute
    in CREATE TYPE.  Otherwise, a multirange type name is generated automatically.
    If the range type name contains "range" then we change that to "multirange".
    Otherwise we add "_multirange" to the end.
    
    Implementation of multiranges comes with a space-efficient internal representation
    format, which evades extra paddings and duplicated storage of oids.  Altogether
    this format allows to fetch a particular range by its index in O(n).
    
    Statistic gathering and selectivity estimation are implemented for multiranges.
    For this purpose, stored multirange is approximated as union range without gaps.
    This field will likely need improvements in future.
    
    Discussion: https://postgr.es/m/CALNJ-vSUpQ_Y%3DjXvTxt1VYFztaBSsWVXeF1y6gTYQ4bOiWDLgQ%40mail.gmail.com
    Discussion: https://postgr.es/m/a0b8026459d1e6167933be2104a6174e7d40d0ab.camel%40j-davis.com#fe7218c83b08068bfffb0c5293eceda0
    Author: Paul Jungwirth, revised by me
    Reviewed-by: David Fetter, Corey Huinker, Jeff Davis, Pavel Stehule
    Reviewed-by: Alvaro Herrera, Tom Lane, Isaac Morland, David G. Johnston
    Reviewed-by: Zhihong Yu

diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 62711ee83ff..34918485972 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -6237,6 +6237,16 @@ SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</replaceable>:<replaceable>&l
       </para></entry>
      </row>
 
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>rngmultitypid</structfield> <type>oid</type>
+       (references <link linkend="catalog-pg-type"><structname>pg_type</structname></link>.<structfield>oid</structfield>)
+      </para>
+      <para>
+       OID of the multirange type for this range type
+      </para></entry>
+     </row>
+
      <row>
       <entry role="catalog_table_entry"><para role="column_definition">
        <structfield>rngcollation</structfield> <type>oid</type>
@@ -8671,8 +8681,9 @@ SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</replaceable>:<replaceable>&l
        <literal>c</literal> for a composite type (e.g., a table's row type),
        <literal>d</literal> for a domain,
        <literal>e</literal> for an enum type,
-       <literal>p</literal> for a pseudo-type, or
-       <literal>r</literal> for a range type.
+       <literal>p</literal> for a pseudo-type,
+       <literal>r</literal> for a range type, or
+       <literal>m</literal> for a multirange type.
        See also <structfield>typrelid</structfield> and
        <structfield>typbasetype</structfield>.
       </para></entry>
diff --git a/doc/src/sgml/datatype.sgml b/doc/src/sgml/datatype.sgml
index 9eb19a1c616..58d168c763e 100644
--- a/doc/src/sgml/datatype.sgml
+++ b/doc/src/sgml/datatype.sgml
@@ -4907,6 +4907,10 @@ SELECT * FROM pg_attribute
     <primary>anyrange</primary>
    </indexterm>
 
+   <indexterm zone="datatype-pseudo">
+    <primary>anymultirange</primary>
+   </indexterm>
+
    <indexterm zone="datatype-pseudo">
     <primary>anycompatible</primary>
    </indexterm>
@@ -4923,6 +4927,10 @@ SELECT * FROM pg_attribute
     <primary>anycompatiblerange</primary>
    </indexterm>
 
+   <indexterm zone="datatype-pseudo">
+    <primary>anycompatiblemultirange</primary>
+   </indexterm>
+
    <indexterm zone="datatype-pseudo">
     <primary>void</primary>
    </indexterm>
@@ -5034,6 +5042,13 @@ SELECT * FROM pg_attribute
         <xref linkend="rangetypes"/>).</entry>
        </row>
 
+       <row>
+        <entry><type>anymultirange</type></entry>
+        <entry>Indicates that a function accepts any multirange data type
+        (see <xref linkend="extend-types-polymorphic"/> and
+        <xref linkend="rangetypes"/>).</entry>
+       </row>
+
        <row>
         <entry><type>anycompatible</type></entry>
         <entry>Indicates that a function accepts any data type,
@@ -5063,6 +5078,14 @@ SELECT * FROM pg_attribute
         <xref linkend="rangetypes"/>).</entry>
        </row>
 
+       <row>
+        <entry><type>anycompatiblemultirange</type></entry>
+        <entry>Indicates that a function accepts any multirange data type,
+        with automatic promotion of multiple arguments to a common data type
+        (see <xref linkend="extend-types-polymorphic"/> and
+        <xref linkend="rangetypes"/>).</entry>
+       </row>
+
        <row>
         <entry><type>cstring</type></entry>
         <entry>Indicates that a function accepts or returns a null-terminated C string.</entry>
diff --git a/doc/src/sgml/extend.sgml b/doc/src/sgml/extend.sgml
index 1c37026bb05..6e3d82b85b8 100644
--- a/doc/src/sgml/extend.sgml
+++ b/doc/src/sgml/extend.sgml
@@ -288,6 +288,14 @@
         </entry>
        </row>
 
+       <row>
+        <entry><type>anymultirange</type></entry>
+        <entry>Simple</entry>
+        <entry>Indicates that a function accepts any multirange data type
+        (see <xref linkend="rangetypes"/>)
+        </entry>
+       </row>
+
        <row>
         <entry><type>anycompatible</type></entry>
         <entry>Common</entry>
@@ -319,6 +327,14 @@
         with automatic promotion of multiple arguments to a common data type
         </entry>
        </row>
+
+       <row>
+        <entry><type>anycompatiblemultirange</type></entry>
+        <entry>Common</entry>
+        <entry>Indicates that a function accepts any multirange data type,
+        with automatic promotion of multiple arguments to a common data type
+        </entry>
+       </row>
       </tbody>
      </tgroup>
     </table>
@@ -346,17 +362,15 @@
      position declared as <type>anyarray</type> can have any array data type,
      but similarly they must all be the same type.  And similarly,
      positions declared as <type>anyrange</type> must all be the same range
-     type.  Furthermore, if there are
+     type.  Likewise for <type>anymultirange</type>.
+    </para>
+
+    <para>
+     Furthermore, if there are
      positions declared <type>anyarray</type> and others declared
      <type>anyelement</type>, the actual array type in the
      <type>anyarray</type> positions must be an array whose elements are
      the same type appearing in the <type>anyelement</type> positions.
-     Similarly, if there are positions declared <type>anyrange</type>
-     and others declared <type>anyelement</type> or <type>anyarray</type>,
-     the actual range type in the <type>anyrange</type> positions must be a
-     range whose subtype is the same type appearing in
-     the <type>anyelement</type> positions and the same as the element type
-     of the <type>anyarray</type> positions.
      <type>anynonarray</type> is treated exactly the same as <type>anyelement</type>,
      but adds the additional constraint that the actual type must not be
      an array type.
@@ -365,6 +379,19 @@
      be an enum type.
     </para>
 
+    <para>
+     Similarly, if there are positions declared <type>anyrange</type>
+     and others declared <type>anyelement</type> or <type>anyarray</type>,
+     the actual range type in the <type>anyrange</type> positions must be a
+     range whose subtype is the same type appearing in
+     the <type>anyelement</type> positions and the same as the element type
+     of the <type>anyarray</type> positions.
+     If there are positions declared <type>anymultirange</type>,
+     their actual multirange type must contain ranges matching parameters declared
+     <type>anyrange</type> and base elements matching parameters declared
+     <type>anyelement</type> and <type>anyarray</type>.
+    </para>
+
     <para>
      Thus, when more than one argument position is declared with a polymorphic
      type, the net effect is that only certain combinations of actual argument
@@ -420,7 +447,8 @@
      Selection of the common type considers the actual types
      of <type>anycompatible</type> and <type>anycompatiblenonarray</type>
      inputs, the array element types of <type>anycompatiblearray</type>
-     inputs, and the range subtypes of <type>anycompatiblerange</type>
+     inputs, the range subtypes of <type>anycompatiblerange</type> inputs,
+     and the multirange subtypes of <type>anycompatiablemultirange</type>
      inputs.  If <type>anycompatiblenonarray</type> is present then the
      common type is required to be a non-array type.  Once a common type is
      identified, arguments in <type>anycompatible</type>
@@ -431,12 +459,15 @@
 
     <para>
      Since there is no way to select a range type knowing only its subtype,
-     use of <type>anycompatiblerange</type> requires that all arguments
-     declared with that type have the same actual range type, and that that
-     type's subtype agree with the selected common type, so that no casting
-     of the range values is required.  As with <type>anyrange</type>, use
-     of <type>anycompatiblerange</type> as a function result type requires
-     that there be an <type>anycompatiblerange</type> argument.
+     use of <type>anycompatiblerange</type> and/or
+     <type>anycompatiblemultirange</type> requires that all arguments declared
+     with that type have the same actual range and/or multirange type, and that
+     that type's subtype agree with the selected common type, so that no casting
+     of the range values is required.  As with <type>anyrange</type> and
+     <type>anymultirange</type>, use of <type>anycompatiblerange</type> and
+     <type>anymultirange</type> as a function result type requires that there be
+     an <type>anycompatiblerange</type> or <type>anycompatiblemultirange</type>
+     argument.
     </para>
 
     <para>
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index df29af6371a..d5cd705eebb 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -17884,12 +17884,15 @@ SELECT NULLIF(value, '(none)') ...
   <para>
    <xref linkend="range-operators-table"/> shows the specialized operators
    available for range types.
+   <xref linkend="multirange-operators-table"/> shows the specialized operators
+   available for multirange types.
    In addition to those, the usual comparison operators shown in
    <xref linkend="functions-comparison-op-table"/> are available for range
-   types.  The comparison operators order first by the range lower bounds, and
-   only if those are equal do they compare the upper bounds.  This does not
-   usually result in a useful overall ordering, but the operators are provided
-   to allow unique indexes to be constructed on ranges.
+   and multirange types.  The comparison operators order first by the range lower
+   bounds, and only if those are equal do they compare the upper bounds.  The
+   multirange operators compare each range until one is unequal. This
+   does not usually result in a useful overall ordering, but the operators are
+   provided to allow unique indexes to be constructed on ranges.
   </para>
 
    <table id="range-operators-table">
@@ -18099,15 +18102,449 @@ SELECT NULLIF(value, '(none)') ...
     </tgroup>
    </table>
 
+   <table id="multirange-operators-table">
+    <title>Multirange Operators</title>
+    <tgroup cols="1">
+     <thead>
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        Operator
+       </para>
+       <para>
+        Description
+       </para>
+       <para>
+        Example(s)
+       </para></entry>
+      </row>
+     </thead>
+
+     <tbody>
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>@&gt;</literal> <type>anymultirange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Does the first multirange contain the second?
+       </para>
+       <para>
+        <literal>'{[2,4)}'::int4multirange @&gt; '{[2,3)}'::int4multirange</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>@&gt;</literal> <type>anyrange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Does the multirange contain the range?
+       </para>
+       <para>
+        <literal>'{[2,4)}'::int4multirange @&gt; int4range(2,3)</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>@&gt;</literal> <type>anyelement</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Does the multirange contain the element?
+       </para>
+       <para>
+        <literal>'{[2011-01-01,2011-03-01)}'::tsmultirange @&gt; '2011-01-10'::timestamp</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>&lt;@</literal> <type>anymultirange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the first multirange contained by the second?
+       </para>
+       <para>
+        <literal>'{[2,4)}'::int4multirange &lt;@ '{[1,7)}'::int4multirange</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>&lt;@</literal> <type>anyrange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the multirange contained by the range?
+       </para>
+       <para>
+        <literal>'{[2,4)}'::int4multirange &lt;@ int4range(1,7)</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anyrange</type> <literal>&lt;@</literal> <type>anymultirange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the range contained by the multirange?
+       </para>
+       <para>
+        <literal>int4range(2,4) &lt;@ '{[1,7)}'::int4multirange</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anyelement</type> <literal>&lt;@</literal> <type>anymultirange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the element contained by the multirange?
+       </para>
+       <para>
+        <literal>42 &lt;@ '{[1,7)}'::int4multirange</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>&amp;&amp;</literal> <type>anymultirange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Do the multiranges overlap, that is, have any elements in common?
+       </para>
+       <para>
+        <literal>'{[3,7)}'::int8multirange &amp;&amp; '{[4,12)}'::int8multirange</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>&amp;&amp;</literal> <type>anyrange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Does the multirange overlap the range?
+       </para>
+       <para>
+        <literal>'{[3,7)}'::int8multirange &amp;&amp; int8range(4,12)</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anyrange</type> <literal>&amp;&amp;</literal> <type>anymultirange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Does the range overlap the multirange?
+       </para>
+       <para>
+        <literal>int8range(3,7) &amp;&amp; '{[4,12)}'::int8multirange</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>&lt;&lt;</literal> <type>anymultirange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the first multirange strictly left of the second?
+       </para>
+       <para>
+        <literal>'{[1,10)}'::int8multirange &lt;&lt; '{[100,110)}'::int8multirange</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>&lt;&lt;</literal> <type>anyrange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the multirange strictly left of the range?
+       </para>
+       <para>
+        <literal>'{[1,10)}'::int8multirange &lt;&lt; int8range(100,110)</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anyrange</type> <literal>&lt;&lt;</literal> <type>anymultirange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the range strictly left of the multirange?
+       </para>
+       <para>
+        <literal>int8range(1,10) &lt;&lt; '{[100,110)}'::int8multirange</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>&gt;&gt;</literal> <type>anymultirange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the first multirange strictly right of the second?
+       </para>
+       <para>
+        <literal>'{[50,60)}'::int8multirange &gt;&gt; '{[20,30)}'::int8multirange</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>&gt;&gt;</literal> <type>anyrange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the multirange strictly right of the range?
+       </para>
+       <para>
+        <literal>'{[50,60)}'::int8multirange &gt;&gt; int8range(20,30)</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anyrange</type> <literal>&gt;&gt;</literal> <type>anymultirange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the range strictly right of the multirange?
+       </para>
+       <para>
+        <literal>int8range(50,60) &gt;&gt; '{[20,30)}'::int8multirange</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>&amp;&lt;</literal> <type>anymultirange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Does the first multirange not extend to the right of the second?
+       </para>
+       <para>
+        <literal>'{[1,20)}'::int8multirange &amp;&lt; '{[18,20)}'::int8multirange</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>&amp;&lt;</literal> <type>anyrange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Does the multirange not extend to the right of the range?
+       </para>
+       <para>
+        <literal>'{[1,20)}'::int8multirange &amp;&lt; int8range(18,20)</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anyrange</type> <literal>&amp;&lt;</literal> <type>anymultirange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Does the range not extend to the right of the multirange?
+       </para>
+       <para>
+        <literal>int8range(1,20) &amp;&lt; '{[18,20)}'::int8multirange</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>&amp;&gt;</literal> <type>anymultirange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Does the first multirange not extend to the left of the second?
+       </para>
+       <para>
+        <literal>'{[7,20)}'::int8multirange &amp;&gt; '{[5,10)}'::int8multirange</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>&amp;&gt;</literal> <type>anyrange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Does the multirange not extend to the left of the range?
+       </para>
+       <para>
+        <literal>'{[7,20)}'::int8multirange &amp;&gt; int8range(5,10)</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anyrange</type> <literal>&amp;&gt;</literal> <type>anymultirange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Does the range not extend to the left of the multirange?
+       </para>
+       <para>
+        <literal>int8range(7,20) &amp;&gt; '{[5,10)}'::int8multirange</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>-|-</literal> <type>anymultirange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Are the multiranges adjacent?
+       </para>
+       <para>
+        <literal>'{[1.1,2.2)}'::nummultirange -|- '{[2.2,3.3)}'::nummultirange</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>-|-</literal> <type>anyrange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the multirange adjacent to the range?
+       </para>
+       <para>
+        <literal>'{[1.1,2.2)}'::nummultirange -|- numrange(2.2,3.3)</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anyrange</type> <literal>-|-</literal> <type>anymultirange</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the range adjacent to the multirange?
+       </para>
+       <para>
+        <literal>numrange(1.1,2.2) -|- '{[2.2,3.3)}'::nummultirange</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>+</literal> <type>anymultirange</type>
+        <returnvalue>anymultirange</returnvalue>
+       </para>
+       <para>
+        Computes the union of the multiranges.  The multiranges need not overlap
+        or be adjacent.
+       </para>
+       <para>
+        <literal>'{[5,10)}'::nummultirange + '{[15,20)}'::nummultirange</literal>
+        <returnvalue>{[5,10), [15,20)}</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>*</literal> <type>anymultirange</type>
+        <returnvalue>anymultirange</returnvalue>
+       </para>
+       <para>
+        Computes the intersection of the multiranges.
+       </para>
+       <para>
+        <literal>'{[5,15)}'::int8multirange * '{[10,20)}'::int8multirange</literal>
+        <returnvalue>{[10,15)}</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anymultirange</type> <literal>-</literal> <type>anymultirange</type>
+        <returnvalue>anymultirange</returnvalue>
+       </para>
+       <para>
+        Computes the difference of the multiranges.
+       </para>
+       <para>
+        <literal>'{[5,20)}'::int8multirange - '{[10,15)}'::int8multirange</literal>
+        <returnvalue>{[5,10), [15,20)}</returnvalue>
+       </para></entry>
+      </row>
+     </tbody>
+    </tgroup>
+   </table>
+
   <para>
    The left-of/right-of/adjacent operators always return false when an empty
-   range is involved; that is, an empty range is not considered to be either
-   before or after any other range.
+   range or multirange is involved; that is, an empty range is not considered to
+   be either before or after any other range.
+  </para>
+
+  <para>
+   Elsewhere empty ranges and multiranges are treated as the additive identity:
+   anything unioned with an empty value is itself. Anything minus an empty
+   value is itself. An empty multirange has exactly the same points as an empty
+   range. Every range contains the empty range. Every multirange contains as many
+   empty ranges as you like.
+  </para>
+
+  <para>
+   The range union and difference operators will fail if the resulting range would
+   need to contain two disjoint sub-ranges, as such a range cannot be
+   represented. There are separate operators for union and difference that take
+   multirange parameters and return a multirange, and they do not fail even if
+   their arguments are disjoint. So if you need a union or difference operation
+   for ranges that may be disjoint, you can avoid errors by first casting your
+   ranges to multiranges.
   </para>
 
   <para>
    <xref linkend="range-functions-table"/> shows the functions
    available for use with range types.
+   <xref linkend="multirange-functions-table"/> shows the functions
+   available for use with multirange types.
   </para>
 
    <table id="range-functions-table">
@@ -18269,10 +18706,185 @@ SELECT NULLIF(value, '(none)') ...
     </tgroup>
    </table>
 
+   <table id="multirange-functions-table">
+    <title>Multirange Functions</title>
+    <tgroup cols="1">
+     <thead>
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        Function
+       </para>
+       <para>
+        Description
+       </para>
+       <para>
+        Example(s)
+       </para></entry>
+      </row>
+     </thead>
+     <tbody>
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>lower</primary>
+        </indexterm>
+        <function>lower</function> ( <type>anymultirange</type> )
+        <returnvalue>anyelement</returnvalue>
+       </para>
+       <para>
+        Extracts the lower bound of the multirange (<literal>NULL</literal> if the
+        multirange is empty or the lower bound is infinite).
+       </para>
+       <para>
+        <literal>lower('{[1.1,2.2)}'::nummultirange)</literal>
+        <returnvalue>1.1</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>upper</primary>
+        </indexterm>
+        <function>upper</function> ( <type>anymultirange</type> )
+        <returnvalue>anyelement</returnvalue>
+       </para>
+       <para>
+        Extracts the upper bound of the multirange (<literal>NULL</literal> if the
+        multirange is empty or the upper bound is infinite).
+       </para>
+       <para>
+        <literal>upper('{[1.1,2.2)}'::nummultirange)</literal>
+        <returnvalue>2.2</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>isempty</primary>
+        </indexterm>
+        <function>isempty</function> ( <type>anymultirange</type> )
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the multirange empty?
+       </para>
+       <para>
+        <literal>isempty('{[1.1,2.2)}'::nummultirange)</literal>
+        <returnvalue>f</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>lower_inc</primary>
+        </indexterm>
+        <function>lower_inc</function> ( <type>anymultirange</type> )
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the multirange's lower bound inclusive?
+       </para>
+       <para>
+        <literal>lower_inc('{[1.1,2.2)}'::nummultirange)</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>upper_inc</primary>
+        </indexterm>
+        <function>upper_inc</function> ( <type>anymultirange</type> )
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the multirange's upper bound inclusive?
+       </para>
+       <para>
+        <literal>upper_inc('{[1.1,2.2)}'::nummultirange)</literal>
+        <returnvalue>f</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>lower_inf</primary>
+        </indexterm>
+        <function>lower_inf</function> ( <type>anymultirange</type> )
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the multirange's lower bound infinite?
+       </para>
+       <para>
+        <literal>lower_inf('{(,)}'::datemultirange)</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>upper_inf</primary>
+        </indexterm>
+        <function>upper_inf</function> ( <type>anymultirange</type> )
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the multirange's upper bound infinite?
+       </para>
+       <para>
+        <literal>upper_inf('{(,)}'::datemultirange)</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>range_merge</primary>
+        </indexterm>
+        <function>range_merge</function> ( <type>anymultirange</type> )
+        <returnvalue>anyrange</returnvalue>
+       </para>
+       <para>
+        Computes the smallest range that includes the entire multirange.
+       </para>
+       <para>
+        <literal>range_merge('{[1,2), [3,4)}'::int4multirange)</literal>
+        <returnvalue>[1,4)</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>multirange</primary>
+        </indexterm>
+        <function>multirange</function> ( <type>anyrange</type> )
+        <returnvalue>anymultirange</returnvalue>
+       </para>
+       <para>
+        Returns a multirange containing just the given range.
+       </para>
+       <para>
+        <literal>multirange('[1,2)'::int4range)</literal>
+        <returnvalue>{[1,2)}</returnvalue>
+       </para></entry>
+      </row>
+     </tbody>
+    </tgroup>
+   </table>
+
   <para>
    The <function>lower_inc</function>, <function>upper_inc</function>,
    <function>lower_inf</function>, and <function>upper_inf</function>
-   functions all return false for an empty range.
+   functions all return false for an empty range or multirange.
   </para>
   </sect1>
 
@@ -18604,6 +19216,36 @@ SELECT NULLIF(value, '(none)') ...
        <entry>Yes</entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>range_agg</primary>
+        </indexterm>
+        <function>range_agg</function> ( <parameter>value</parameter>
+         <type>anyrange</type> )
+        <returnvalue>anymultirange</returnvalue>
+       </para>
+       <para>
+        Computes the union of the non-null input values.
+       </para></entry>
+       <entry>No</entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>range_intersect_agg</primary>
+        </indexterm>
+        <function>range_intersect_agg</function> ( <parameter>value</parameter>
+         <type>anyrange</type> )
+        <returnvalue>anymultirange</returnvalue>
+       </para>
+       <para>
+        Computes the intersection of the non-null input values.
+       </para></entry>
+       <entry>No</entry>
+      </row>
+
       <row>
        <entry role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/doc/src/sgml/rangetypes.sgml b/doc/src/sgml/rangetypes.sgml
index b75fb3a3929..83aa9bc4e9e 100644
--- a/doc/src/sgml/rangetypes.sgml
+++ b/doc/src/sgml/rangetypes.sgml
@@ -27,40 +27,53 @@
   ranges from an instrument, and so forth can also be useful.
  </para>
 
+ <para>
+  Every range type has a corresponding multirange type. A multirange is
+  an ordered list of non-continguous, non-empty, non-null ranges. Most
+  range operators also work on multiranges, and they have a few functions
+  of their own.
+ </para>
+
  <sect2 id="rangetypes-builtin">
-  <title>Built-in Range Types</title>
+  <title>Built-in Range and Multirange Types</title>
 
  <para>
   PostgreSQL comes with the following built-in range types:
   <itemizedlist>
     <listitem>
       <para>
-       <type>int4range</type> &mdash; Range of <type>integer</type>
+       <type>int4range</type> &mdash; Range of <type>integer</type>,
+       <type>int4multirange</type> &mdash; corresponding Multirange
       </para>
     </listitem>
     <listitem>
       <para>
-       <type>int8range</type> &mdash; Range of <type>bigint</type>
+       <type>int8range</type> &mdash; Range of <type>bigint</type>,
+       <type>int8multirange</type> &mdash; corresponding Multirange
       </para>
     </listitem>
     <listitem>
       <para>
-       <type>numrange</type> &mdash; Range of <type>numeric</type>
+       <type>numrange</type> &mdash; Range of <type>numeric</type>,
+       <type>nummultirange</type> &mdash; corresponding Multirange
       </para>
     </listitem>
     <listitem>
       <para>
-       <type>tsrange</type> &mdash; Range of <type>timestamp without time zone</type>
+       <type>tsrange</type> &mdash; Range of <type>timestamp without time zone</type>,
+       <type>tsmultirange</type> &mdash; corresponding Multirange
       </para>
     </listitem>
     <listitem>
       <para>
-       <type>tstzrange</type> &mdash; Range of <type>timestamp with time zone</type>
+       <type>tstzrange</type> &mdash; Range of <type>timestamp with time zone</type>,
+       <type>tstzmultirange</type> &mdash; corresponding Multirange
       </para>
     </listitem>
     <listitem>
       <para>
-       <type>daterange</type> &mdash; Range of <type>date</type>
+       <type>daterange</type> &mdash; Range of <type>date</type>,
+       <type>datemultirange</type> &mdash; corresponding Multirange
       </para>
     </listitem>
   </itemizedlist>
@@ -232,10 +245,30 @@ SELECT '[4,4]'::int4range;
 SELECT '[4,4)'::int4range;
 </programlisting>
   </para>
+
+  <para>
+   The input for a multirange is curly brackets (<literal>{</literal> and
+   <literal>}</literal>) containing zero or more valid ranges,
+   separated by commas. Whitespace is permitted around the brackets and
+   commas. This is intended to be reminiscent of array syntax, although
+   multiranges are much simpler: they have just one dimension and there is
+   no need to quote their contents. (The bounds of their ranges may be
+   quoted as above however.)
+  </para>
+
+  <para>
+  Examples:
+<programlisting>
+SELECT '{}'::int4multirange;
+SELECT '{[3,7)}'::int4multirange;
+SELECT '{[3,7), [8,9)}'::int4multirange;
+</programlisting>
+  </para>
+
  </sect2>
 
  <sect2 id="rangetypes-construct">
-  <title>Constructing Ranges</title>
+  <title>Constructing Ranges and Multiranges</title>
 
   <para>
    Each range type has a constructor function with the same name as the range
@@ -267,6 +300,19 @@ SELECT int8range(1, 14, '(]');
 
 -- Using NULL for either bound causes the range to be unbounded on that side.
 SELECT numrange(NULL, 2.2);
+</programlisting>
+  </para>
+
+  <para>
+   Each range type also has a multirange constructor with the same name as the
+   multirange type.  The constructor function takes zero or more arguments
+   which are all ranges of the appropriate type.
+   For example:
+
+<programlisting>
+SELECT nummultirange();
+SELECT nummultirange(numrange(1.0, 14.0));
+SELECT nummultirange(numrange(1.0, 14.0), numrange(20.0, 25.0));
 </programlisting>
   </para>
  </sect2>
@@ -341,6 +387,11 @@ SELECT '[1.234, 5.678]'::floatrange;
    function in this example.
   </para>
 
+  <para>
+   When you define your own range you automatically get a corresponding
+   multirange type.
+  </para>
+
   <para>
    Defining your own range type also allows you to specify a different
    subtype B-tree operator class or collation to use, so as to change the sort
diff --git a/doc/src/sgml/ref/create_type.sgml b/doc/src/sgml/ref/create_type.sgml
index d575f166142..7d2d6aa0af8 100644
--- a/doc/src/sgml/ref/create_type.sgml
+++ b/doc/src/sgml/ref/create_type.sgml
@@ -33,6 +33,7 @@ CREATE TYPE <replaceable class="parameter">name</replaceable> AS RANGE (
     [ , COLLATION = <replaceable class="parameter">collation</replaceable> ]
     [ , CANONICAL = <replaceable class="parameter">canonical_function</replaceable> ]
     [ , SUBTYPE_DIFF = <replaceable class="parameter">subtype_diff_function</replaceable> ]
+    [ , MULTIRANGE_TYPE_NAME = <replaceable class="parameter">multirange_type_name</replaceable> ]
 )
 
 CREATE TYPE <replaceable class="parameter">name</replaceable> (
@@ -174,6 +175,17 @@ CREATE TYPE <replaceable class="parameter">name</replaceable>
     the range type.  See <xref linkend="rangetypes-defining"/> for more
     information.
    </para>
+
+   <para>
+    The optional <replaceable class="parameter">multirange_type_name</replaceable>
+    parameter specifies the name of the corresponding multirange type.  If not
+    specified, this name is chosen automatically as follows.
+    If range type name contains <literal>range</literal> substring, then
+    multirange type name is formed by replacement of the <literal>range</literal>
+    substring with <literal>multirange</literal> substring in the range
+    type name.  Otherwise, multirange type name is formed by appending
+    <literal>_multirange</literal> suffix to the range type name.
+   </para>
   </refsect2>
 
   <refsect2>
@@ -630,6 +642,15 @@ CREATE TYPE <replaceable class="parameter">name</replaceable>
     </listitem>
    </varlistentry>
 
+   <varlistentry>
+    <term><replaceable class="parameter">multirange_type_name</replaceable></term>
+    <listitem>
+     <para>
+      The name of the corresponding multirange type.
+     </para>
+    </listitem>
+   </varlistentry>
+
    <varlistentry>
     <term><replaceable class="parameter">input_function</replaceable></term>
     <listitem>
diff --git a/src/backend/catalog/pg_range.c b/src/backend/catalog/pg_range.c
index a606d8c3adb..91b0fb0611a 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;
 	ObjectAddresses *addrs;
 
 	pg_range = table_open(RangeRelationId, RowExclusiveLock);
@@ -55,6 +56,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_rngmultitypid - 1] = ObjectIdGetDatum(multirangeTypeOid);
 
 	tup = heap_form_tuple(RelationGetDescr(pg_range), values, nulls);
 
@@ -93,6 +95,12 @@ RangeCreate(Oid rangeTypeOid, Oid rangeSubType, Oid rangeCollation,
 	record_object_address_dependencies(&myself, addrs, DEPENDENCY_NORMAL);
 	free_object_addresses(addrs);
 
+	/* 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 4252875ef50..4dc6828c79c 100644
--- a/src/backend/catalog/pg_type.c
+++ b/src/backend/catalog/pg_type.c
@@ -28,6 +28,7 @@
 #include "catalog/pg_proc.h"
 #include "catalog/pg_type.h"
 #include "commands/typecmds.h"
+#include "mb/pg_wchar.h"
 #include "miscadmin.h"
 #include "parser/scansup.h"
 #include "utils/acl.h"
@@ -37,6 +38,9 @@
 #include "utils/rel.h"
 #include "utils/syscache.h"
 
+static char *makeUniqueTypeName(const char *typeName, Oid typeNamespace,
+								bool tryOriginal);
+
 /* Potentially set by pg_upgrade_support functions */
 Oid			binary_upgrade_next_pg_type_oid = InvalidOid;
 
@@ -863,31 +867,10 @@ RenameTypeInternal(Oid typeOid, const char *newTypeName, Oid typeNamespace)
 char *
 makeArrayTypeName(const char *typeName, Oid typeNamespace)
 {
-	char	   *arr = (char *) palloc(NAMEDATALEN);
-	int			namelen = strlen(typeName);
-	int			i;
-
-	/*
-	 * 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;
-	}
+	char	   *arr;
 
-	if (i >= NAMEDATALEN - 1)
+	arr = makeUniqueTypeName(typeName, typeNamespace, false);
+	if (arr == NULL)
 		ereport(ERROR,
 				(errcode(ERRCODE_DUPLICATE_OBJECT),
 				 errmsg("could not form array type name for type \"%s\"",
@@ -958,3 +941,90 @@ moveArrayTypeName(Oid typeOid, const char *typeName, Oid typeNamespace)
 
 	return true;
 }
+
+
+/*
+ * makeMultirangeTypeName
+ *	  - given a range type name, make a multirange type name for it
+ *
+ * caller is responsible for pfreeing the result
+ */
+char *
+makeMultirangeTypeName(const char *rangeTypeName, Oid typeNamespace)
+{
+	char	   *buf;
+	char	   *rangestr;
+
+	/*
+	 * If the range type name contains "range" then change that to
+	 * "multirange". Otherwise add "_multirange" to the end.
+	 */
+	rangestr = strstr(rangeTypeName, "range");
+	if (rangestr)
+	{
+		char	   *prefix = pnstrdup(rangeTypeName, rangestr - rangeTypeName);
+
+		buf = psprintf("%s%s%s", prefix, "multi", rangestr);
+	}
+	else
+		buf = psprintf("%s_multirange", pnstrdup(rangeTypeName, NAMEDATALEN - 12));
+
+	/* clip it at NAMEDATALEN-1 bytes */
+	buf[pg_mbcliplen(buf, strlen(buf), NAMEDATALEN - 1)] = '\0';
+
+	if (SearchSysCacheExists2(TYPENAMENSP,
+							  CStringGetDatum(buf),
+							  ObjectIdGetDatum(typeNamespace)))
+		ereport(ERROR,
+				(errcode(ERRCODE_DUPLICATE_OBJECT),
+				 errmsg("type \"%s\" already exists", buf),
+				 errdetail("Failed while creating a multirange type for type \"%s\".", rangeTypeName),
+				 errhint("You can manually specify multirange type name using \"multirange_type_name\" attribute")));
+
+	return pstrdup(buf);
+}
+
+/*
+ * makeUniqueTypeName
+ *		Generate a unique name for a prospective new type
+ *
+ * Given a typeName, return a new palloc'ed name by preprending underscores
+ * until a non-conflicting name results.
+ *
+ * If tryOriginal, first try with zero underscores.
+ */
+static char *
+makeUniqueTypeName(const char *typeName, Oid typeNamespace, bool tryOriginal)
+{
+	int			i;
+	int			namelen;
+	char		dest[NAMEDATALEN];
+
+	Assert(strlen(typeName) <= NAMEDATALEN - 1);
+
+	if (tryOriginal &&
+		!SearchSysCacheExists2(TYPENAMENSP,
+							   CStringGetDatum(typeName),
+							   ObjectIdGetDatum(typeNamespace)))
+		return pstrdup(typeName);
+
+	/*
+	 * The idea is to prepend underscores as needed until we make a name that
+	 * doesn't collide with anything ...
+	 */
+	namelen = strlen(typeName);
+	for (i = 1; i < NAMEDATALEN - 1; i++)
+	{
+		dest[i - 1] = '_';
+		strlcpy(dest + i, typeName, NAMEDATALEN - i);
+		if (namelen + i >= NAMEDATALEN)
+			truncate_identifier(dest, NAMEDATALEN, false);
+
+		if (!SearchSysCacheExists2(TYPENAMENSP,
+								   CStringGetDatum(dest),
+								   ObjectIdGetDatum(typeNamespace)))
+			return pstrdup(dest);
+	}
+
+	return NULL;
+}
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index 7c0b2c3bf02..0fcd8c8f16e 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -107,9 +107,14 @@ typedef struct
 
 /* Potentially set by pg_upgrade_support functions */
 Oid			binary_upgrade_next_array_pg_type_oid = InvalidOid;
+Oid			binary_upgrade_next_mrng_pg_type_oid = InvalidOid;
+Oid			binary_upgrade_next_mrng_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 rangeOid,
+									   Oid rangeArrayOid, Oid *castFuncOid);
 static Oid	findTypeInputFunction(List *procname, Oid typeOid);
 static Oid	findTypeOutputFunction(List *procname, Oid typeOid);
 static Oid	findTypeReceiveFunction(List *procname, Oid typeOid);
@@ -772,7 +777,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",
@@ -1323,6 +1329,11 @@ checkEnumOwner(HeapTuple tup)
 /*
  * DefineRange
  *		Registers a new range type.
+ *
+ * Perhaps it might be worthwhile to set pg_type.typelem to the base type,
+ * and likewise on multiranges to set it to the range type. But having a
+ * non-zero typelem is treated elsewhere as a synonym for being an array,
+ * and users might have queries with that same assumption.
  */
 ObjectAddress
 DefineRange(CreateRangeStmt *stmt)
@@ -1331,7 +1342,12 @@ DefineRange(CreateRangeStmt *stmt)
 	Oid			typeNamespace;
 	Oid			typoid;
 	char	   *rangeArrayName;
+	char	   *multirangeTypeName = NULL;
+	char	   *multirangeArrayName;
+	Oid			multirangeNamespace = InvalidOid;
 	Oid			rangeArrayOid;
+	Oid			multirangeOid;
+	Oid			multirangeArrayOid;
 	Oid			rangeSubtype = InvalidOid;
 	List	   *rangeSubOpclassName = NIL;
 	List	   *rangeCollationName = NIL;
@@ -1348,6 +1364,8 @@ DefineRange(CreateRangeStmt *stmt)
 	AclResult	aclresult;
 	ListCell   *lc;
 	ObjectAddress address;
+	ObjectAddress mltrngaddress;
+	Oid			castFuncOid;
 
 	/* Convert list of names to a name and namespace */
 	typeNamespace = QualifiedNameGetCreationNamespace(stmt->typeName,
@@ -1431,6 +1449,16 @@ DefineRange(CreateRangeStmt *stmt)
 						 errmsg("conflicting or redundant options")));
 			rangeSubtypeDiffName = defGetQualifiedName(defel);
 		}
+		else if (strcmp(defel->defname, "multirange_type_name") == 0)
+		{
+			if (multirangeTypeName != NULL)
+				ereport(ERROR,
+						(errcode(ERRCODE_SYNTAX_ERROR),
+						 errmsg("conflicting or redundant options")));
+			/* we can look up the subtype name immediately */
+			multirangeNamespace = QualifiedNameGetCreationNamespace(defGetQualifiedName(defel),
+																	&multirangeTypeName);
+		}
 		else
 			ereport(ERROR,
 					(errcode(ERRCODE_SYNTAX_ERROR),
@@ -1496,8 +1524,10 @@ DefineRange(CreateRangeStmt *stmt)
 	/* alignment must be TYPALIGN_INT or TYPALIGN_DOUBLE for ranges */
 	alignment = (subtypalign == TYPALIGN_DOUBLE) ? TYPALIGN_DOUBLE : TYPALIGN_INT;
 
-	/* Allocate OID for array type */
+	/* Allocate OID for array type, its multirange, and its multirange array */
 	rangeArrayOid = AssignTypeArrayOid();
+	multirangeOid = AssignTypeMultirangeOid();
+	multirangeArrayOid = AssignTypeMultirangeArrayOid();
 
 	/* Create the pg_type entry */
 	address =
@@ -1536,9 +1566,75 @@ DefineRange(CreateRangeStmt *stmt)
 	Assert(typoid == InvalidOid || typoid == address.objectId);
 	typoid = address.objectId;
 
+	/* Create the multirange that goes with it */
+	if (multirangeTypeName)
+	{
+		Oid		old_typoid;
+
+		/*
+		 * Look to see if multirange type already exists.
+		 */
+		old_typoid = GetSysCacheOid2(TYPENAMENSP, Anum_pg_type_oid,
+									 CStringGetDatum(multirangeTypeName),
+									 ObjectIdGetDatum(multirangeNamespace));
+
+		/*
+		 * If it's not a shell, see if it's an autogenerated array type, and if so
+		 * rename it out of the way.
+		 */
+		if (OidIsValid(old_typoid) && get_typisdefined(old_typoid))
+		{
+			if (!moveArrayTypeName(old_typoid, multirangeTypeName, multirangeNamespace))
+				ereport(ERROR,
+						(errcode(ERRCODE_DUPLICATE_OBJECT),
+						 errmsg("type \"%s\" already exists", multirangeTypeName)));
+		}
+	}
+	else
+	{
+		/* Generate multirange name automatically */
+		multirangeNamespace = typeNamespace;
+		multirangeTypeName = makeMultirangeTypeName(typeName, multirangeNamespace);
+	}
+
+	mltrngaddress =
+		TypeCreate(multirangeOid,	/* force assignment of this type OID */
+				   multirangeTypeName,	/* type name */
+				   multirangeNamespace,	/* 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_RANGE,	/* type-category (range 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,	/* subscript procedure - none */
+				   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) */
+	Assert(multirangeOid == mltrngaddress.objectId);
+
 	/* Create the entry in pg_range */
 	RangeCreate(typoid, rangeSubtype, rangeCollation, rangeSubOpclass,
-				rangeCanonical, rangeSubtypeDiff);
+				rangeCanonical, rangeSubtypeDiff, multirangeOid);
 
 	/*
 	 * Create the array type that goes with it.
@@ -1580,8 +1676,54 @@ 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 */
+			   multirangeNamespace,	/* 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 */
+			   F_ARRAY_SUBSCRIPT_HANDLER,	/* array subscript procedure */
+			   multirangeOid,	/* 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,
+							   multirangeOid, typoid, rangeArrayOid,
+							   &castFuncOid);
+
+	/* Create cast from the range type to its multirange type */
+	CastCreate(typoid, multirangeOid, castFuncOid, 'e', 'f', DEPENDENCY_INTERNAL);
+
+	pfree(multirangeTypeName);
+	pfree(multirangeArrayName);
 
 	return address;
 }
@@ -1659,6 +1801,149 @@ 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.
+ *
+ * Sets castFuncOid to the oid of the new constructor that can be used
+ * to cast from a range to a multirange.
+ */
+static void
+makeMultirangeConstructors(const char *name, Oid namespace,
+						   Oid multirangeOid, Oid rangeOid, Oid rangeArrayOid,
+						   Oid *castFuncOid)
+{
+	ObjectAddress myself,
+				referenced;
+	oidvector  *argtypes;
+	Datum		allParamTypes;
+	ArrayType  *allParameterTypes;
+	Datum		paramModes;
+	ArrayType  *parameterModes;
+
+	referenced.classId = TypeRelationId;
+	referenced.objectId = multirangeOid;
+	referenced.objectSubId = 0;
+
+	/* 0-arg constructor - for empty multiranges */
+	argtypes = buildoidvector(NULL, 0);
+	myself = ProcedureCreate(name,	/* name: same as multirange type */
+							 namespace,
+							 false, /* replace */
+							 false, /* returns set */
+							 multirangeOid, /* return type */
+							 BOOTSTRAP_SUPERUSERID, /* proowner */
+							 INTERNALlanguageId,	/* language */
+							 F_FMGR_INTERNAL_VALIDATOR,
+							 "multirange_constructor0", /* prosrc */
+							 NULL,	/* probin */
+							 PROKIND_FUNCTION,
+							 false, /* security_definer */
+							 false, /* leakproof */
+							 false, /* isStrict */
+							 PROVOLATILE_IMMUTABLE, /* volatility */
+							 PROPARALLEL_SAFE,	/* parallel safety */
+							 argtypes,	/* parameterTypes */
+							 PointerGetDatum(NULL), /* allParameterTypes */
+							 PointerGetDatum(NULL), /* parameterModes */
+							 PointerGetDatum(NULL), /* parameterNames */
+							 NIL,	/* parameterDefaults */
+							 PointerGetDatum(NULL), /* trftypes */
+							 PointerGetDatum(NULL), /* proconfig */
+							 InvalidOid,	/* prosupport */
+							 1.0,	/* procost */
+							 0.0);	/* prorows */
+
+	/*
+	 * Make the constructor 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);
+	pfree(argtypes);
+
+	/*
+	 * 1-arg constructor - for casts
+	 *
+	 * In theory we shouldn't need both this and the vararg (n-arg)
+	 * constructor, but having a separate 1-arg function lets us define casts
+	 * against it.
+	 */
+	argtypes = buildoidvector(&rangeOid, 1);
+	myself = ProcedureCreate(name,	/* name: same as multirange type */
+							 namespace,
+							 false, /* replace */
+							 false, /* returns set */
+							 multirangeOid, /* return type */
+							 BOOTSTRAP_SUPERUSERID, /* proowner */
+							 INTERNALlanguageId,	/* language */
+							 F_FMGR_INTERNAL_VALIDATOR,
+							 "multirange_constructor1", /* prosrc */
+							 NULL,	/* probin */
+							 PROKIND_FUNCTION,
+							 false, /* security_definer */
+							 false, /* leakproof */
+							 true,	/* isStrict */
+							 PROVOLATILE_IMMUTABLE, /* volatility */
+							 PROPARALLEL_SAFE,	/* parallel safety */
+							 argtypes,	/* parameterTypes */
+							 PointerGetDatum(NULL), /* allParameterTypes */
+							 PointerGetDatum(NULL), /* parameterModes */
+							 PointerGetDatum(NULL), /* parameterNames */
+							 NIL,	/* parameterDefaults */
+							 PointerGetDatum(NULL), /* trftypes */
+							 PointerGetDatum(NULL), /* proconfig */
+							 InvalidOid,	/* prosupport */
+							 1.0,	/* procost */
+							 0.0);	/* prorows */
+	/* ditto */
+	recordDependencyOn(&myself, &referenced, DEPENDENCY_INTERNAL);
+	pfree(argtypes);
+	*castFuncOid = myself.objectId;
+
+	/* n-arg constructor - vararg */
+	argtypes = buildoidvector(&rangeArrayOid, 1);
+	allParamTypes = ObjectIdGetDatum(rangeArrayOid);
+	allParameterTypes = construct_array(&allParamTypes,
+										1, OIDOID,
+										sizeof(Oid), true, 'i');
+	paramModes = CharGetDatum(FUNC_PARAM_VARIADIC);
+	parameterModes = construct_array(&paramModes, 1, CHAROID,
+									 1, true, 'c');
+	myself = ProcedureCreate(name,	/* name: same as multirange type */
+							 namespace,
+							 false, /* replace */
+							 false, /* returns set */
+							 multirangeOid, /* return type */
+							 BOOTSTRAP_SUPERUSERID, /* proowner */
+							 INTERNALlanguageId,	/* language */
+							 F_FMGR_INTERNAL_VALIDATOR,
+							 "multirange_constructor2", /* prosrc */
+							 NULL,	/* probin */
+							 PROKIND_FUNCTION,
+							 false, /* security_definer */
+							 false, /* leakproof */
+							 false, /* isStrict */
+							 PROVOLATILE_IMMUTABLE, /* volatility */
+							 PROPARALLEL_SAFE,	/* parallel safety */
+							 argtypes,	/* parameterTypes */
+							 PointerGetDatum(allParameterTypes),	/* allParameterTypes */
+							 PointerGetDatum(parameterModes),	/* parameterModes */
+							 PointerGetDatum(NULL), /* parameterNames */
+							 NIL,	/* parameterDefaults */
+							 PointerGetDatum(NULL), /* trftypes */
+							 PointerGetDatum(NULL), /* proconfig */
+							 InvalidOid,	/* prosupport */
+							 1.0,	/* procost */
+							 0.0);	/* prorows */
+	/* ditto */
+	recordDependencyOn(&myself, &referenced, DEPENDENCY_INTERNAL);
+	pfree(argtypes);
+	pfree(allParameterTypes);
+	pfree(parameterModes);
+}
 
 /*
  * Find suitable I/O and other support functions for a type.
@@ -2152,6 +2437,72 @@ AssignTypeArrayOid(void)
 	return type_array_oid;
 }
 
+/*
+ *	AssignTypeMultirangeOid
+ *
+ *	Pre-assign the range type's multirange OID for use in pg_type.oid
+ */
+Oid
+AssignTypeMultirangeOid(void)
+{
+	Oid			type_multirange_oid;
+
+	/* Use binary-upgrade override for pg_type.oid? */
+	if (IsBinaryUpgrade)
+	{
+		if (!OidIsValid(binary_upgrade_next_mrng_pg_type_oid))
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+					 errmsg("pg_type multirange OID value not set when in binary upgrade mode")));
+
+		type_multirange_oid = binary_upgrade_next_mrng_pg_type_oid;
+		binary_upgrade_next_mrng_pg_type_oid = InvalidOid;
+	}
+	else
+	{
+		Relation	pg_type = table_open(TypeRelationId, AccessShareLock);
+
+		type_multirange_oid = GetNewOidWithIndex(pg_type, TypeOidIndexId,
+												 Anum_pg_type_oid);
+		table_close(pg_type, AccessShareLock);
+	}
+
+	return type_multirange_oid;
+}
+
+/*
+ *	AssignTypeMultirangeArrayOid
+ *
+ *	Pre-assign the range type's multirange array OID for use in pg_type.typarray
+ */
+Oid
+AssignTypeMultirangeArrayOid(void)
+{
+	Oid			type_multirange_array_oid;
+
+	/* Use binary-upgrade override for pg_type.oid? */
+	if (IsBinaryUpgrade)
+	{
+		if (!OidIsValid(binary_upgrade_next_mrng_array_pg_type_oid))
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+					 errmsg("pg_type multirange array OID value not set when in binary upgrade mode")));
+
+		type_multirange_array_oid = binary_upgrade_next_mrng_array_pg_type_oid;
+		binary_upgrade_next_mrng_array_pg_type_oid = InvalidOid;
+	}
+	else
+	{
+		Relation	pg_type = table_open(TypeRelationId, AccessShareLock);
+
+		type_multirange_array_oid = GetNewOidWithIndex(pg_type, TypeOidIndexId,
+													   Anum_pg_type_oid);
+		table_close(pg_type, AccessShareLock);
+	}
+
+	return type_multirange_array_oid;
+}
+
 
 /*-------------------------------------------------------------------
  * DefineCompositeType
diff --git a/src/backend/executor/functions.c b/src/backend/executor/functions.c
index 459a33375b1..ca8d637e73a 100644
--- a/src/backend/executor/functions.c
+++ b/src/backend/executor/functions.c
@@ -1711,7 +1711,8 @@ check_sql_fn_retval(List *queryTreeLists,
 	if (fn_typtype == TYPTYPE_BASE ||
 		fn_typtype == TYPTYPE_DOMAIN ||
 		fn_typtype == TYPTYPE_ENUM ||
-		fn_typtype == TYPTYPE_RANGE)
+		fn_typtype == TYPTYPE_RANGE ||
+		fn_typtype == TYPTYPE_MULTIRANGE)
 	{
 		/*
 		 * For scalar-type returns, the target list must have exactly one
diff --git a/src/backend/parser/parse_coerce.c b/src/backend/parser/parse_coerce.c
index da6c3ae4b5f..e33618f9744 100644
--- a/src/backend/parser/parse_coerce.c
+++ b/src/backend/parser/parse_coerce.c
@@ -190,19 +190,21 @@ coerce_type(ParseState *pstate, Node *node,
 	if (targetTypeId == ANYARRAYOID ||
 		targetTypeId == ANYENUMOID ||
 		targetTypeId == ANYRANGEOID ||
+		targetTypeId == ANYMULTIRANGEOID ||
 		targetTypeId == ANYCOMPATIBLEARRAYOID ||
-		targetTypeId == ANYCOMPATIBLERANGEOID)
+		targetTypeId == ANYCOMPATIBLERANGEOID ||
+		targetTypeId == ANYCOMPATIBLEMULTIRANGEOID)
 	{
 		/*
 		 * 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 the pseudotype's input
-		 * function, 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 the
+		 * pseudotype's input function, 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
@@ -1570,8 +1572,8 @@ select_common_typmod(ParseState *pstate, List *exprs, Oid common_type)
  * 1) All arguments declared ANYELEMENT must have the same datatype.
  * 2) All arguments declared ANYARRAY must have the same datatype,
  *	  which must be a varlena array type.
- * 3) All arguments declared ANYRANGE must have the same datatype,
- *	  which must be a range type.
+ * 3) All arguments declared ANYRANGE or ANYMULTIRANGE must be a range or
+ *	  multirange type, all derived from the same base datatype.
  * 4) If there are arguments of more than one of these polymorphic types,
  *	  the array element type and/or range subtype must be the same as each
  *	  other and the same as the ANYELEMENT type.
@@ -1586,8 +1588,8 @@ select_common_typmod(ParseState *pstate, List *exprs, Oid common_type)
  *	  to a common supertype (chosen as per select_common_type's rules).
  *	  ANYCOMPATIBLENONARRAY works like ANYCOMPATIBLE but also requires the
  *	  common supertype to not be an array.  If there are ANYCOMPATIBLEARRAY
- *	  or ANYCOMPATIBLERANGE arguments, their element types or subtypes are
- *	  included while making the choice of common supertype.
+ *	  or ANYCOMPATIBLERANGE or ANYCOMPATIBLEMULTIRANGE arguments, their element
+ *	  types or subtypes are included while making the choice of common supertype.
  * 8) The resolved type of ANYCOMPATIBLEARRAY arguments will be the array
  *	  type over the common supertype (which might not be the same array type
  *	  as any of the original arrays).
@@ -1595,6 +1597,10 @@ select_common_typmod(ParseState *pstate, List *exprs, Oid common_type)
  *	  (after domain flattening), since we have no preference rule that would
  *	  let us choose one over another.  Furthermore, that range's subtype
  *	  must exactly match the common supertype chosen by rule 7.
+ * 10) All ANYCOMPATIBLEMULTIRANGE arguments must be the exact same multirange
+ *	  type (after domain flattening), since we have no preference rule that would
+ *	  let us choose one over another.  Furthermore, that multirange's range's
+ *	  subtype must exactly match the common supertype chosen by rule 7.
  *
  * Domains over arrays match ANYARRAY, and are immediately flattened to their
  * base type.  (Thus, for example, we will consider it a match if one ANYARRAY
@@ -1603,7 +1609,9 @@ select_common_typmod(ParseState *pstate, List *exprs, Oid common_type)
  * for ANYCOMPATIBLEARRAY and ANYCOMPATIBLENONARRAY.
  *
  * Similarly, domains over ranges match ANYRANGE or ANYCOMPATIBLERANGE,
- * and are immediately flattened to their base type.
+ * and are immediately flattened to their base type, and domains over
+ * multiranges match ANYMULTIRANGE or ANYCOMPATIBLEMULTIRANGE and are immediately
+ * flattened to their base type.
  *
  * Note that domains aren't currently considered to match ANYENUM,
  * even if their base type would match.
@@ -1621,8 +1629,12 @@ check_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			anycompatible_range_typeid = InvalidOid;
 	Oid			anycompatible_range_typelem = InvalidOid;
+	Oid			anycompatible_multirange_typeid = InvalidOid;
+	Oid			anycompatible_multirange_typelem = InvalidOid;
+	Oid			range_typelem = InvalidOid;
 	bool		have_anynonarray = false;
 	bool		have_anyenum = false;
 	bool		have_anycompatible_nonarray = false;
@@ -1671,6 +1683,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;
+		}
 		else if (decl_type == ANYCOMPATIBLEOID ||
 				 decl_type == ANYCOMPATIBLENONARRAYOID)
 		{
@@ -1715,6 +1736,45 @@ check_generic_type_consistency(const Oid *actual_arg_types,
 				anycompatible_actual_types[n_anycompatible_args++] = anycompatible_range_typelem;
 			}
 		}
+		else if (decl_type == ANYCOMPATIBLEMULTIRANGEOID)
+		{
+			if (actual_type == UNKNOWNOID)
+				continue;
+			actual_type = getBaseType(actual_type); /* flatten domains */
+			if (OidIsValid(anycompatible_multirange_typeid))
+			{
+				/* All ANYCOMPATIBLEMULTIRANGE arguments must be the same type */
+				if (anycompatible_multirange_typeid != actual_type)
+					return false;
+			}
+			else
+			{
+				anycompatible_multirange_typeid = actual_type;
+				anycompatible_multirange_typelem = get_multirange_range(actual_type);
+				if (!OidIsValid(anycompatible_multirange_typelem))
+					return false;	/* not a multirange type */
+
+				if (OidIsValid(anycompatible_range_typeid))
+				{
+					/*
+					 * ANYCOMPATIBLEMULTIRANGE and ANYCOMPATIBLERANGE
+					 * arguments must match
+					 */
+					if (anycompatible_range_typeid != anycompatible_multirange_typelem)
+						return false;
+				}
+				else
+				{
+					anycompatible_range_typeid = anycompatible_multirange_typelem;
+					anycompatible_range_typelem = get_range_subtype(anycompatible_range_typeid);
+					if (!OidIsValid(anycompatible_range_typelem))
+						return false;	/* not a range type */
+				}
+				/* collect the subtype for common-supertype choice */
+				anycompatible_actual_types[n_anycompatible_args++] =
+					anycompatible_range_typelem;
+			}
+		}
 	}
 
 	/* Get the element type based on the array type, if we have one */
@@ -1761,8 +1821,6 @@ check_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))
 	{
-		Oid			range_typelem;
-
 		range_typelem = get_range_subtype(range_typeid);
 		if (!OidIsValid(range_typelem))
 			return false;		/* should be a range, but isn't */
@@ -1781,6 +1839,45 @@ 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))
+	{
+		Oid			multirange_typelem;
+
+		multirange_typelem = get_multirange_range(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 */
+		}
+		else if (multirange_typelem != range_typeid)
+		{
+			/* otherwise, they better match */
+			return false;
+		}
+
+		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 */
@@ -1819,8 +1916,10 @@ check_generic_type_consistency(const Oid *actual_arg_types,
 		}
 
 		/*
-		 * the anycompatible type must exactly match the range element type,
-		 * if we were able to identify one
+		 * The anycompatible type must exactly match the range element type,
+		 * if we were able to identify one. This checks compatibility for
+		 * anycompatiblemultirange too since that also sets
+		 * anycompatible_range_typelem above.
 		 */
 		if (OidIsValid(anycompatible_range_typelem) &&
 			anycompatible_range_typelem != anycompatible_typeid)
@@ -1859,21 +1958,27 @@ check_generic_type_consistency(const Oid *actual_arg_types,
  *	  argument's actual type as the function's return type.
  * 2) If return type is ANYARRAY, and any argument is ANYARRAY, use the
  *	  argument's actual type as the function's return type.
- * 3) Similarly, if return type is ANYRANGE, and any argument is ANYRANGE,
- *	  use the argument's actual type as the function's return type.
- * 4) Otherwise, if return type is ANYELEMENT or ANYARRAY, and there is
+ * 3) Similarly, if return type is ANYRANGE or ANYMULTIRANGE, and any
+ *	  argument is ANYRANGE or ANYMULTIRANGE, use that argument's
+ *	  actual type, range type or multirange type as the function's return
+ *	  type.
+ * 4) Otherwise, if return type is ANYMULTIRANGE, and any argument is
+ *	  ANYMULTIRANGE, use the argument's actual type as the function's return
+ *	  type. Or if any argument is ANYRANGE, use its multirange type as the
+ *	  function's return type.
+ * 5) Otherwise, if return type is ANYELEMENT or ANYARRAY, and there is
  *	  at least one ANYELEMENT, ANYARRAY, or ANYRANGE input, deduce the
  *	  return type from those inputs, or throw error if we can't.
- * 5) Otherwise, if return type is ANYRANGE, throw error.  (We have no way to
- *	  select a specific range type if the arguments don't include ANYRANGE.)
- * 6) ANYENUM is treated the same as ANYELEMENT except that if it is used
+ * 6) Otherwise, if return type is ANYRANGE or ANYMULTIRANGE, throw error.
+ *	  (We have no way to select a specific range type if the arguments don't
+ *	  include ANYRANGE.)
  *	  (alone or in combination with plain ANYELEMENT), we add the extra
  *	  condition that the ANYELEMENT type must be an enum.
- * 7) ANYNONARRAY is treated the same as ANYELEMENT except that if it is used,
+ * 8) ANYNONARRAY is treated the same as ANYELEMENT except that if it is used,
  *	  we add the extra condition that the ANYELEMENT type must not be an array.
  *	  (This is a no-op if used in combination with ANYARRAY or ANYENUM, but
  *	  is an extra restriction if not.)
- * 8) ANYCOMPATIBLE, ANYCOMPATIBLEARRAY, ANYCOMPATIBLENONARRAY, and
+ * 9) ANYCOMPATIBLE, ANYCOMPATIBLEARRAY, ANYCOMPATIBLENONARRAY, and
  *	  ANYCOMPATIBLERANGE are handled by resolving the common supertype
  *	  of those arguments (or their element types/subtypes, for array and range
  *	  inputs), and then coercing all those arguments to the common supertype,
@@ -1927,10 +2032,15 @@ 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			anycompatible_typeid = InvalidOid;
 	Oid			anycompatible_array_typeid = InvalidOid;
 	Oid			anycompatible_range_typeid = InvalidOid;
 	Oid			anycompatible_range_typelem = InvalidOid;
+	Oid			anycompatible_multirange_typeid = InvalidOid;
+	Oid			anycompatible_multirange_typelem = InvalidOid;
+	Oid			range_typelem;
+	Oid			multirange_typelem;
 	bool		have_anynonarray = (rettype == ANYNONARRAYOID);
 	bool		have_anyenum = (rettype == ANYENUMOID);
 	bool		have_anycompatible_nonarray = (rettype == ANYCOMPATIBLENONARRAYOID);
@@ -2015,6 +2125,26 @@ enforce_generic_type_consistency(const Oid *actual_arg_types,
 								   format_type_be(actual_type))));
 			range_typeid = actual_type;
 		}
+		else if (decl_type == ANYMULTIRANGEOID)
+		{
+			n_poly_args++;
+			if (actual_type == UNKNOWNOID)
+			{
+				have_poly_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;
+		}
 		else if (decl_type == ANYCOMPATIBLEOID ||
 				 decl_type == ANYCOMPATIBLENONARRAYOID)
 		{
@@ -2083,6 +2213,40 @@ enforce_generic_type_consistency(const Oid *actual_arg_types,
 				anycompatible_actual_types[n_anycompatible_args++] = anycompatible_range_typelem;
 			}
 		}
+		else if (decl_type == ANYCOMPATIBLEMULTIRANGEOID)
+		{
+			have_poly_anycompatible = true;
+			if (actual_type == UNKNOWNOID)
+				continue;
+			if (allow_poly && decl_type == actual_type)
+				continue;		/* no new information here */
+			actual_type = getBaseType(actual_type); /* flatten domains */
+			if (OidIsValid(anycompatible_multirange_typeid))
+			{
+				/* All ANYCOMPATIBLEMULTIRANGE arguments must be the same type */
+				if (anycompatible_multirange_typeid != actual_type)
+					ereport(ERROR,
+							(errcode(ERRCODE_DATATYPE_MISMATCH),
+							 errmsg("arguments declared \"anycompatiblemultirange\" are not all alike"),
+							 errdetail("%s versus %s",
+									   format_type_be(anycompatible_multirange_typeid),
+									   format_type_be(actual_type))));
+			}
+			else
+			{
+				anycompatible_multirange_typeid = actual_type;
+				anycompatible_multirange_typelem = get_multirange_range(actual_type);
+				anycompatible_range_typelem = get_range_subtype(anycompatible_multirange_typelem);
+				if (!OidIsValid(anycompatible_multirange_typelem))
+					ereport(ERROR,
+							(errcode(ERRCODE_DATATYPE_MISMATCH),
+							 errmsg("argument declared %s is not a multirange type but type %s",
+									"anycompatiblemultirange",
+									format_type_be(actual_type))));
+				/* collect the subtype for common-supertype choice */
+				anycompatible_actual_types[n_anycompatible_args++] = anycompatible_range_typelem;
+			}
+		}
 	}
 
 	/*
@@ -2151,8 +2315,6 @@ 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))
 		{
-			Oid			range_typelem;
-
 			range_typelem = get_range_subtype(range_typeid);
 			if (!OidIsValid(range_typelem))
 				ereport(ERROR,
@@ -2181,6 +2343,61 @@ enforce_generic_type_consistency(const Oid *actual_arg_types,
 								   format_type_be(elem_typeid))));
 			}
 		}
+		else
+			range_typelem = InvalidOid;
+
+		/* Get the element type based on the multirange type, if we have one */
+		if (OidIsValid(multirange_typeid))
+		{
+			multirange_typelem = get_multirange_range(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))));
+			}
+
+			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 */
+				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))));
+			}
+		}
+		else
+			multirange_typelem = InvalidOid;
 
 		if (!OidIsValid(elem_typeid))
 		{
@@ -2189,6 +2406,7 @@ enforce_generic_type_consistency(const Oid *actual_arg_types,
 				elem_typeid = ANYELEMENTOID;
 				array_typeid = ANYARRAYOID;
 				range_typeid = ANYRANGEOID;
+				multirange_typeid = ANYMULTIRANGEOID;
 			}
 			else
 			{
@@ -2288,6 +2506,7 @@ enforce_generic_type_consistency(const Oid *actual_arg_types,
 				anycompatible_typeid = ANYCOMPATIBLEOID;
 				anycompatible_array_typeid = ANYCOMPATIBLEARRAYOID;
 				anycompatible_range_typeid = ANYCOMPATIBLERANGEOID;
+				anycompatible_multirange_typeid = ANYCOMPATIBLEMULTIRANGEOID;
 			}
 			else
 			{
@@ -2319,6 +2538,8 @@ enforce_generic_type_consistency(const Oid *actual_arg_types,
 				declared_arg_types[j] = anycompatible_array_typeid;
 			else if (decl_type == ANYCOMPATIBLERANGEOID)
 				declared_arg_types[j] = anycompatible_range_typeid;
+			else if (decl_type == ANYCOMPATIBLEMULTIRANGEOID)
+				declared_arg_types[j] = anycompatible_multirange_typeid;
 		}
 	}
 
@@ -2369,6 +2590,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;
+			}
 		}
 	}
 
@@ -2405,6 +2637,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 ANYCOMPATIBLE use the appropriate type */
 	if (rettype == ANYCOMPATIBLEOID ||
 		rettype == ANYCOMPATIBLENONARRAYOID)
@@ -2439,6 +2687,17 @@ enforce_generic_type_consistency(const Oid *actual_arg_types,
 		return anycompatible_range_typeid;
 	}
 
+	/* if we return ANYCOMPATIBLEMULTIRANGE use the appropriate argument type */
+	if (rettype == ANYCOMPATIBLEMULTIRANGEOID)
+	{
+		/* this error is unreachable if the function signature is valid: */
+		if (!OidIsValid(anycompatible_multirange_typeid))
+			ereport(ERROR,
+					(errcode(ERRCODE_DATATYPE_MISMATCH),
+					 errmsg_internal("could not identify anycompatiblemultirange type")));
+		return anycompatible_multirange_typeid;
+	}
+
 	/* we don't return a generic type; send back the original return type */
 	return rettype;
 }
@@ -2456,20 +2715,38 @@ check_valid_polymorphic_signature(Oid ret_type,
 								  const Oid *declared_arg_types,
 								  int nargs)
 {
-	if (ret_type == ANYRANGEOID || ret_type == ANYCOMPATIBLERANGEOID)
+	if (ret_type == ANYRANGEOID || ret_type == ANYMULTIRANGEOID)
 	{
 		/*
-		 * ANYRANGE requires an ANYRANGE input, else we can't tell which of
-		 * several range types with the same element type to use.  Likewise
-		 * for ANYCOMPATIBLERANGE.
+		 * ANYRANGE and ANYMULTIRANGE require an ANYRANGE or ANYMULTIRANGE
+		 * input, else we can't tell which of several range types with the
+		 * same element type to use.
 		 */
 		for (int i = 0; i < nargs; i++)
 		{
-			if (declared_arg_types[i] == ret_type)
+			if (declared_arg_types[i] == ANYRANGEOID ||
+				declared_arg_types[i] == ANYMULTIRANGEOID)
 				return NULL;	/* OK */
 		}
-		return psprintf(_("A result of type %s requires at least one input of type %s."),
-						format_type_be(ret_type), format_type_be(ret_type));
+		return psprintf(_("A result of type %s requires at least one input of type anyrange or anymultirange."),
+						format_type_be(ret_type));
+	}
+	else if (ret_type == ANYCOMPATIBLERANGEOID || ret_type == ANYCOMPATIBLEMULTIRANGEOID)
+	{
+		/*
+		 * ANYCOMPATIBLERANGE and ANYCOMPATIBLEMULTIRANGE require an
+		 * ANYCOMPATIBLERANGE or ANYCOMPATIBLEMULTIRANGE input, else we can't
+		 * tell which of several range types with the same element type to
+		 * use.
+		 */
+		for (int i = 0; i < nargs; i++)
+		{
+			if (declared_arg_types[i] == ANYCOMPATIBLERANGEOID ||
+				declared_arg_types[i] == ANYCOMPATIBLEMULTIRANGEOID)
+				return NULL;	/* OK */
+		}
+		return psprintf(_("A result of type %s requires at least one input of type anycompatiblerange or anycompatiblemultirange."),
+						format_type_be(ret_type));
 	}
 	else if (IsPolymorphicTypeFamily1(ret_type))
 	{
@@ -2480,7 +2757,7 @@ check_valid_polymorphic_signature(Oid ret_type,
 				return NULL;	/* OK */
 		}
 		/* Keep this list in sync with IsPolymorphicTypeFamily1! */
-		return psprintf(_("A result of type %s requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange."),
+		return psprintf(_("A result of type %s requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange."),
 						format_type_be(ret_type));
 	}
 	else if (IsPolymorphicTypeFamily2(ret_type))
@@ -2632,6 +2909,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 || targettype == ANYCOMPATIBLEMULTIRANGEOID)
+		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 ce09ad73754..82732146d3d 100644
--- a/src/backend/utils/adt/Makefile
+++ b/src/backend/utils/adt/Makefile
@@ -60,6 +60,8 @@ OBJS = \
 	mac8.o \
 	mcxtfuncs.o \
 	misc.o \
+	multirangetypes.o \
+	multirangetypes_selfuncs.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 00000000000..1d9f4e10f75
--- /dev/null
+++ b/src/backend/utils/adt/multirangetypes.c
@@ -0,0 +1,2666 @@
+/*-------------------------------------------------------------------------
+ *
+ * multirangetypes.c
+ *	  I/O functions, operators, and support functions for multirange types.
+ *
+ * The stored (serialized) format of a multirange value is:
+ *
+ *	12 bytes: MultirangeType struct including varlena header, multirange
+ *			  type's OID and the number of ranges in the multirange.
+ *	4 * (rangesCount - 1) bytes: 32-bit items pointing to the each range
+ *								 in the multirange starting from
+ *								 the second one.
+ *	1 * rangesCount bytes : 8-bit flags for each range in the multirange
+ *	The rest of multirange are range bound values pointed by multirange items.
+ *
+ *	Majority of items contain lengths of corresponding range bound values.
+ *	Thanks to that items are typically low numbers.  This makes multiranges
+ *	compression-friendly.  Every MULTIRANGE_ITEM_OFFSET_STRIDE item contains
+ *	an offset of the corresponding range bound values.  That allows fast lookups
+ *	for particular range index.  Offsets are counted starting from end of flags
+ *	aligned to the bound type.
+ *
+ * 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 "common/hashfn.h"
+#include "lib/stringinfo.h"
+#include "libpq/pqformat.h"
+#include "miscadmin.h"
+#include "utils/builtins.h"
+#include "utils/lsyscache.h"
+#include "utils/rangetypes.h"
+#include "utils/multirangetypes.h"
+#include "utils/array.h"
+#include "utils/memutils.h"
+
+/* fn_extra cache entry for one of the range I/O functions */
+typedef struct MultirangeIOData
+{
+	TypeCacheEntry *typcache;	/* multirange type's typcache entry */
+	FmgrInfo	typioproc;		/* range type's I/O proc */
+	Oid			typioparam;		/* range type's I/O parameter */
+} 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;
+
+#define MultirangeGetItemsPtr(mr) ((uint32 *) ((Pointer) (mr) + sizeof(MultirangeType)))
+#define MultirangeGetFlagsPtr(mr) ((uint8 *) ((Pointer) (mr) + sizeof(MultirangeType) + ((mr)->rangeCount - 1) * sizeof(uint32)))
+#define MultirangeGetBoundariesPtr(mr, align) ((Pointer) (mr) + att_align_nominal(sizeof(MultirangeType) + ((mr)->rangeCount - 1) * sizeof(uint32) + (mr)->rangeCount * sizeof(uint8), (align)))
+
+#define MULTIRANGE_ITEM_OFF_BIT 0x80000000
+#define MULTIRANGE_ITEM_GET_OFFLEN(item) ((item) & 0x7FFFFFFF)
+#define MULTIRANGE_ITEM_HAS_OFF(item) ((item) & MULTIRANGE_ITEM_OFF_BIT)
+#define MULTIRANGE_ITEM_OFFSET_STRIDE 4
+
+typedef int (*multirange_bsearch_comparison) (TypeCacheEntry *typcache,
+											  RangeBound *lower,
+											  RangeBound *upper,
+											  void *key,
+											  bool *match);
+
+static MultirangeIOData *get_multirange_io_data(FunctionCallInfo fcinfo, Oid mltrngtypid,
+												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 with 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;
+	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 = NULL;
+	int32		range_str_len;
+	char	   *range_str_copy;
+
+	cache = get_multirange_io_data(fcinfo, mltrngtypoid, IOFunc_input);
+	rangetyp = cache->typcache->rngtype;
+
+	/* 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 + 1;
+					range_str_copy = pnstrdup(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->typioproc,
+																 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;
+	int32		range_count;
+	int32		i;
+	RangeType **ranges;
+
+	cache = get_multirange_io_data(fcinfo, mltrngtypoid, IOFunc_output);
+
+	initStringInfo(&buf);
+
+	appendStringInfoChar(&buf, '{');
+
+	multirange_deserialize(cache->typcache->rngtype, multirange, &range_count, &ranges);
+	for (i = 0; i < range_count; i++)
+	{
+		if (i > 0)
+			appendStringInfoChar(&buf, ',');
+		range = ranges[i];
+		rangeStr = OutputFunctionCall(&cache->typioproc, RangeTypePGetDatum(range));
+		appendStringInfoString(&buf, rangeStr);
+	}
+
+	appendStringInfoChar(&buf, '}');
+
+	PG_RETURN_CSTRING(buf.data);
+}
+
+/*
+ * Binary representation: First a int32-sized count of ranges, followed by
+ * ranges in their native binary representation.
+ */
+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;
+	uint32		range_count;
+	RangeType **ranges;
+	MultirangeType *ret;
+	StringInfoData tmpbuf;
+
+	cache = get_multirange_io_data(fcinfo, mltrngtypoid, IOFunc_receive);
+
+	range_count = pq_getmsgint(buf, 4);
+	ranges = palloc(range_count * sizeof(RangeType *));
+
+	initStringInfo(&tmpbuf);
+	for (int i = 0; i < range_count; i++)
+	{
+		uint32		range_len = pq_getmsgint(buf, 4);
+		const char *range_data = pq_getmsgbytes(buf, range_len);
+
+		resetStringInfo(&tmpbuf);
+		appendBinaryStringInfo(&tmpbuf, range_data, range_len);
+
+		ranges[i] = DatumGetRangeTypeP(ReceiveFunctionCall(&cache->typioproc,
+														   &tmpbuf,
+														   cache->typioparam,
+														   typmod));
+	}
+	pfree(tmpbuf.data);
+
+	pq_getmsgend(buf);
+
+	ret = make_multirange(mltrngtypoid, cache->typcache->rngtype,
+						  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;
+	MultirangeIOData *cache;
+
+	cache = get_multirange_io_data(fcinfo, mltrngtypoid, IOFunc_send);
+
+	/* construct output */
+	pq_begintypsend(buf);
+
+	pq_sendint32(buf, multirange->rangeCount);
+
+	multirange_deserialize(cache->typcache->rngtype, multirange, &range_count, &ranges);
+	for (int i = 0; i < range_count; i++)
+	{
+		Datum		range;
+
+		range = RangeTypePGetDatum(ranges[i]);
+		range = PointerGetDatum(SendFunctionCall(&cache->typioproc, range));
+
+		pq_sendint32(buf, VARSIZE(range) - VARHDRSZ);
+		pq_sendbytes(buf, VARDATA(range), VARSIZE(range) - VARHDRSZ);
+	}
+
+	PG_RETURN_BYTEA_P(pq_endtypsend(buf));
+}
+
+/*
+ * 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)
+	{
+		Oid			typiofunc;
+		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,
+						 &typiofunc);
+
+		if (!OidIsValid(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(typiofunc, &cache->typioproc,
+					  fcinfo->flinfo->fn_mcxt);
+
+		fcinfo->flinfo->fn_extra = (void *) cache;
+	}
+
+	return cache;
+}
+
+/*
+ * Converts a list of arbitrary ranges 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;
+}
+
+/*
+ *----------------------------------------------------------
+ * 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;
+}
+
+
+/*
+ * Estimate size occupied by serialized multirage.
+ */
+static Size
+multirange_size_estimate(TypeCacheEntry *rangetyp, int32 range_count,
+						 RangeType **ranges)
+{
+	char		elemalign = rangetyp->rngelemtype->typalign;
+	Size		size;
+	int32		i;
+
+	/*
+	 * Count space for MultirangeType struct, items and flags.
+	 */
+	size = att_align_nominal(sizeof(MultirangeType) +
+							 Max(range_count - 1, 0) * sizeof(uint32) +
+							 range_count * sizeof(uint8), elemalign);
+
+	/* Count space for range bounds */
+	for (i = 0; i < range_count; i++)
+		size += att_align_nominal(VARSIZE(ranges[i]) -
+								  sizeof(RangeType) -
+								  sizeof(char), elemalign);
+
+	return size;
+}
+
+/*
+ * Write multirange data into pre-allocated space.
+ */
+static void
+write_multirange_data(MultirangeType *multirange, TypeCacheEntry *rangetyp,
+					  int32 range_count, RangeType **ranges)
+{
+	uint32	   *items;
+	uint32 		prev_offset = 0;
+	uint8	   *flags;
+	int32		i;
+	Pointer		begin,
+				ptr;
+	char		elemalign = rangetyp->rngelemtype->typalign;
+
+	items = MultirangeGetItemsPtr(multirange);
+	flags = MultirangeGetFlagsPtr(multirange);
+	ptr = begin = MultirangeGetBoundariesPtr(multirange, elemalign);
+	for (i = 0; i < range_count; i++)
+	{
+		uint32		len;
+
+		if (i > 0)
+		{
+			/*
+			 * Every range except first one has item.  Every
+			 * MULTIRANGE_ITEM_OFFSET_STRIDE item contains an offset, others
+			 * contain lengths.
+			 */
+			items[i - 1] = ptr - begin;
+			if ((i % MULTIRANGE_ITEM_OFFSET_STRIDE) != 0)
+				items[i - 1] -= prev_offset;
+			else
+				items[i - 1] |= MULTIRANGE_ITEM_OFF_BIT;
+			prev_offset = ptr - begin;
+		}
+		flags[i] = *((Pointer) ranges[i] + VARSIZE(ranges[i]) - sizeof(char));
+		len = VARSIZE(ranges[i]) - sizeof(RangeType) - sizeof(char);
+		memcpy(ptr, (Pointer) (ranges[i] + 1), len);
+		ptr += att_align_nominal(len, elemalign);
+	}
+}
+
+
+/*
+ * 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;
+	Size		size;
+
+	/* Sort and merge input ranges. */
+	range_count = multirange_canonicalize(rangetyp, range_count, ranges);
+
+	/* Note: zero-fill is required here, just as in heap tuples */
+	size = multirange_size_estimate(rangetyp, range_count, ranges);
+	multirange = palloc0(size);
+	SET_VARSIZE(multirange, size);
+
+	/* Now fill in the datum */
+	multirange->multirangetypid = mltrngtypoid;
+	multirange->rangeCount = range_count;
+
+	write_multirange_data(multirange, rangetyp, range_count, ranges);
+
+	return multirange;
+}
+
+/*
+ * Get offset of bounds values of i'th range in the multirange.
+ */
+static uint32
+multirange_get_bounds_offset(const MultirangeType *multirange, int32 i)
+{
+	uint32 *items = MultirangeGetItemsPtr(multirange);
+	uint32	offset = 0;
+
+	/*
+	 * Summarize lengths till we meet an offset.
+	 */
+	while (i > 0)
+	{
+		offset += MULTIRANGE_ITEM_GET_OFFLEN(items[i - 1]);
+		if (MULTIRANGE_ITEM_HAS_OFF(items[i - 1]))
+			break;
+		i--;
+	}
+	return offset;
+}
+
+/*
+ * Fetch the i'th range from the multirange.
+ */
+RangeType *
+multirange_get_range(TypeCacheEntry *rangetyp,
+					 const MultirangeType *multirange, int i)
+{
+	uint32		offset;
+	uint8		flags;
+	Pointer		begin,
+				ptr;
+	int16		typlen = rangetyp->rngelemtype->typlen;
+	char		typalign = rangetyp->rngelemtype->typalign;
+	uint32		len;
+	RangeType  *range;
+
+	Assert(i < multirange->rangeCount);
+
+	offset = multirange_get_bounds_offset(multirange, i);
+	flags = MultirangeGetFlagsPtr(multirange)[i];
+	ptr = begin = MultirangeGetBoundariesPtr(multirange, typalign) + offset;
+
+	/*
+	 * Calculate size of bound values.  In principle, we could get offset of
+	 * the next range bound values and calculate accordingly.  But range bound
+	 * values are aligned, so we have to walk the values to get the exact size.
+	 */
+	if (RANGE_HAS_LBOUND(flags))
+		ptr = (Pointer) att_addlength_pointer(ptr, typlen, ptr);
+	if (RANGE_HAS_UBOUND(flags))
+		ptr = (Pointer) att_addlength_pointer(ptr, typlen, ptr);
+	len = (ptr - begin) + sizeof(RangeType) + sizeof(uint8);
+
+	range = palloc0(len);
+	SET_VARSIZE(range, len);
+	range->rangetypid = rangetyp->type_id;
+
+	memcpy(range + 1, begin, ptr - begin);
+	*((uint8 *) (range + 1) + (ptr - begin)) = flags;
+
+	return range;
+}
+
+/*
+ * Fetch bounds from i'th range of the multirange.  This is shortcut for
+ * doing the same thing as multirange_get_range() + range_deserialize(), but
+ * performing less operations.
+ */
+void
+multirange_get_bounds(TypeCacheEntry *rangetyp,
+					  const MultirangeType *multirange,
+					  uint32 i, RangeBound *lower, RangeBound *upper)
+{
+	uint32		offset;
+	uint8		flags;
+	Pointer		ptr;
+	int16		typlen = rangetyp->rngelemtype->typlen;
+	char		typalign = rangetyp->rngelemtype->typalign;
+	bool		typbyval = rangetyp->rngelemtype->typbyval;
+	Datum		lbound;
+	Datum		ubound;
+
+	Assert(i < multirange->rangeCount);
+
+	offset = multirange_get_bounds_offset(multirange, i);
+	flags = MultirangeGetFlagsPtr(multirange)[i];
+	ptr = MultirangeGetBoundariesPtr(multirange, typalign) + offset;
+
+	/* multirange can't contain empty ranges */
+	Assert((flags & RANGE_EMPTY) == 0);
+
+	/* fetch lower bound, if any */
+	if (RANGE_HAS_LBOUND(flags))
+	{
+		/* att_align_pointer cannot be necessary here */
+		lbound = fetch_att(ptr, typbyval, typlen);
+		ptr = (Pointer) att_addlength_pointer(ptr, typlen, ptr);
+	}
+	else
+		lbound = (Datum) 0;
+
+	/* fetch upper bound, if any */
+	if (RANGE_HAS_UBOUND(flags))
+	{
+		ptr = (Pointer) att_align_pointer(ptr, typalign, typlen, ptr);
+		ubound = fetch_att(ptr, typbyval, typlen);
+		/* no need for att_addlength_pointer */
+	}
+	else
+		ubound = (Datum) 0;
+
+	/* emit results */
+	lower->val = lbound;
+	lower->infinite = (flags & RANGE_LB_INF) != 0;
+	lower->inclusive = (flags & RANGE_LB_INC) != 0;
+	lower->lower = true;
+
+	upper->val = ubound;
+	upper->infinite = (flags & RANGE_UB_INF) != 0;
+	upper->inclusive = (flags & RANGE_UB_INC) != 0;
+	upper->lower = false;
+}
+
+/*
+ * 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(TypeCacheEntry *rangetyp,
+					   const MultirangeType *multirange, int32 *range_count,
+					   RangeType ***ranges)
+{
+	*range_count = multirange->rangeCount;
+
+	/* Convert each ShortRangeType into a RangeType */
+	if (*range_count > 0)
+	{
+		int			i;
+
+		*ranges = palloc(*range_count * sizeof(RangeType *));
+		for (i = 0; i < *range_count; i++)
+			(*ranges)[i] = multirange_get_range(rangetyp, multirange, i);
+	}
+	else
+	{
+		*ranges = NULL;
+	}
+}
+
+MultirangeType *
+make_empty_multirange(Oid mltrngtypoid, TypeCacheEntry *rangetyp)
+{
+	return make_multirange(mltrngtypoid, rangetyp, 0, NULL);
+}
+
+/*
+ * Similar to range_overlaps_internal(), but takes range bounds instead of
+ * ranges as arguments.
+ */
+static bool
+range_bounds_overlaps(TypeCacheEntry *typcache,
+					  RangeBound *lower1, RangeBound *upper1,
+					  RangeBound *lower2, RangeBound *upper2)
+{
+	if (range_cmp_bounds(typcache, lower1, lower2) >= 0 &&
+		range_cmp_bounds(typcache, lower1, upper2) <= 0)
+		return true;
+
+	if (range_cmp_bounds(typcache, lower2, lower1) >= 0 &&
+		range_cmp_bounds(typcache, lower2, upper1) <= 0)
+		return true;
+
+	return false;
+}
+
+/*
+ * Similar to range_contains_internal(), but takes range bounds instead of
+ * ranges as arguments.
+ */
+static bool
+range_bounds_contains(TypeCacheEntry *typcache,
+					  RangeBound *lower1, RangeBound *upper1,
+					  RangeBound *lower2, RangeBound *upper2)
+{
+	if (range_cmp_bounds(typcache, lower1, lower2) <= 0 &&
+		range_cmp_bounds(typcache, upper1, upper2) >= 0)
+		return true;
+
+	return false;
+}
+
+/*
+ * Check if given key matches any range in multirange using binary search.
+ * If required range isn't found, that counts as mismatch.  When required
+ * range is found, comparison function can still report this as either match
+ * or mismatch.  For instance, if we search for containment, we can found
+ * a range overlapping but not containing key range, and that would could as
+ * mismatch.
+ */
+static bool
+multirange_bsearch_match(TypeCacheEntry *typcache, MultirangeType *mr,
+						 void *key, multirange_bsearch_comparison cmp_func)
+{
+	uint32		l,
+				u,
+				idx;
+	int			comparison;
+	bool		match = false;
+
+	l = 0;
+	u = mr->rangeCount;
+	while (l < u)
+	{
+		RangeBound	lower,
+					upper;
+
+		idx = (l + u) / 2;
+		multirange_get_bounds(typcache, mr, idx, &lower, &upper);
+		comparison = (*cmp_func) (typcache, &lower, &upper, key, &match);
+
+		if (comparison < 0)
+			u = idx;
+		else if (comparison > 0)
+			l = idx + 1;
+		else
+			return match;
+	}
+
+	return false;
+}
+
+/*
+ *----------------------------------------------------------
+ * 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 ranges
+ * that match our return value, and there must be no NULLs.
+ */
+Datum
+multirange_constructor2(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,
+				(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+				 errmsg("multirange values cannot contain NULL members")));
+
+	rangeArray = PG_GETARG_ARRAYTYPE_P(0);
+
+	dims = ARR_NDIM(rangeArray);
+	if (dims > 1)
+		ereport(ERROR,
+				(errcode(ERRCODE_CARDINALITY_VIOLATION),
+				 errmsg("multiranges cannot be constructed from multi-dimensional arrays")));
+
+	rngtypid = ARR_ELEMTYPE(rangeArray);
+	if (rngtypid != rangetyp->type_id)
+		ereport(ERROR,
+				(errcode(ERRCODE_DATATYPE_MISMATCH),
+				 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,
+						(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+						 errmsg("multirange values cannot contain NULL members")));
+
+			/* make_multirange will do its own copy */
+			ranges[i] = DatumGetRangeTypeP(elements[i]);
+		}
+	}
+
+	PG_RETURN_MULTIRANGE_P(make_multirange(mltrngtypid, rangetyp, range_count, ranges));
+}
+
+/*
+ * Construct multirange value from a single range.  It'd be nice if we could
+ * just use multirange_constructor2 for this case, but we need a non-variadic
+ * single-arg function to let us define a CAST from a range to its multirange.
+ */
+Datum
+multirange_constructor1(PG_FUNCTION_ARGS)
+{
+	Oid			mltrngtypid = get_fn_expr_rettype(fcinfo->flinfo);
+	Oid			rngtypid;
+	TypeCacheEntry *typcache;
+	TypeCacheEntry *rangetyp;
+	RangeType  *range;
+
+	typcache = multirange_get_typcache(fcinfo, mltrngtypid);
+	rangetyp = typcache->rngtype;
+
+	/*
+	 * These checks should be guaranteed by our signature, but let's do them
+	 * just in case.
+	 */
+
+	if (PG_ARGISNULL(0))
+		ereport(ERROR,
+				(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+				 errmsg("multirange values cannot contain NULL members")));
+
+	range = PG_GETARG_RANGE_P(0);
+
+	/* Make sure the range type matches. */
+	rngtypid = RangeTypeGetOid(range);
+	if (rngtypid != rangetyp->type_id)
+		ereport(ERROR,
+				(errcode(ERRCODE_DATATYPE_MISMATCH),
+				 errmsg("type %u does not match constructor type", rngtypid)));
+
+	PG_RETURN_MULTIRANGE_P(make_multirange(mltrngtypid, rangetyp, 1, &range));
+}
+
+/*
+ * 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
+		elog(ERROR,				/* can't happen */
+			 "niladic multirange constructor must not receive arguments");
+}
+
+
+/* multirange, multirange -> multirange type functions */
+
+/* multirange union */
+Datum
+multirange_union(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
+	MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count1;
+	int32		range_count2;
+	int32		range_count3;
+	RangeType **ranges1;
+	RangeType **ranges2;
+	RangeType **ranges3;
+
+	if (MultirangeIsEmpty(mr1))
+		PG_RETURN_MULTIRANGE_P(mr2);
+	if (MultirangeIsEmpty(mr2))
+		PG_RETURN_MULTIRANGE_P(mr1);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr1));
+
+	multirange_deserialize(typcache->rngtype, mr1, &range_count1, &ranges1);
+	multirange_deserialize(typcache->rngtype, mr2, &range_count2, &ranges2);
+
+	range_count3 = range_count1 + range_count2;
+	ranges3 = palloc0(range_count3 * sizeof(RangeType *));
+	memcpy(ranges3, ranges1, range_count1 * sizeof(RangeType *));
+	memcpy(ranges3 + range_count1, ranges2, range_count2 * sizeof(RangeType *));
+	PG_RETURN_MULTIRANGE_P(make_multirange(typcache->type_id, typcache->rngtype,
+										   range_count3, ranges3));
+}
+
+/* multirange minus */
+Datum
+multirange_minus(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
+	MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
+	Oid			mltrngtypoid = MultirangeTypeGetOid(mr1);
+	TypeCacheEntry *typcache;
+	TypeCacheEntry *rangetyp;
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+
+	typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
+	rangetyp = typcache->rngtype;
+
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		PG_RETURN_MULTIRANGE_P(mr1);
+
+	multirange_deserialize(typcache->rngtype, mr1, &range_count1, &ranges1);
+	multirange_deserialize(typcache->rngtype, mr2, &range_count2, &ranges2);
+
+	PG_RETURN_MULTIRANGE_P(multirange_minus_internal(mltrngtypoid,
+													 rangetyp,
+													 range_count1,
+													 ranges1,
+													 range_count2,
+													 ranges2));
+}
+
+MultirangeType *
+multirange_minus_internal(Oid mltrngtypoid, TypeCacheEntry *rangetyp,
+						  int32 range_count1, RangeType **ranges1,
+						  int32 range_count2, RangeType **ranges2)
+{
+	RangeType  *r1;
+	RangeType  *r2;
+	RangeType **ranges3;
+	int32		range_count3;
+	int32		i1;
+	int32		i2;
+
+	/*
+	 * Worst case: every range in ranges1 makes a different cut to some range
+	 * in ranges2.
+	 */
+	ranges3 = palloc0((range_count1 + range_count2) * sizeof(RangeType *));
+	range_count3 = 0;
+
+	/*
+	 * For each range in mr1, keep subtracting until it's gone or the ranges
+	 * in mr2 have passed it. After a subtraction we assign what's left back
+	 * to r1. The parallel progress through mr1 and mr2 is similar to
+	 * multirange_overlaps_multirange_internal.
+	 */
+	r2 = ranges2[0];
+	for (i1 = 0, i2 = 0; i1 < range_count1; i1++)
+	{
+		r1 = ranges1[i1];
+
+		/* Discard r2s while r2 << r1 */
+		while (r2 != NULL && range_before_internal(rangetyp, r2, r1))
+		{
+			r2 = ++i2 >= range_count2 ? NULL : ranges2[i2];
+		}
+
+		while (r2 != NULL)
+		{
+			if (range_split_internal(rangetyp, r1, r2, &ranges3[range_count3], &r1))
+			{
+				/*
+				 * If r2 takes a bite out of the middle of r1, we need two
+				 * outputs
+				 */
+				range_count3++;
+				r2 = ++i2 >= range_count2 ? NULL : ranges2[i2];
+
+			}
+			else if (range_overlaps_internal(rangetyp, r1, r2))
+			{
+				/*
+				 * If r2 overlaps r1, replace r1 with r1 - r2.
+				 */
+				r1 = range_minus_internal(rangetyp, r1, r2);
+
+				/*
+				 * If r2 goes past r1, then we need to stay with it, in case
+				 * it hits future r1s. Otherwise we need to keep r1, in case
+				 * future r2s hit it. Since we already subtracted, there's no
+				 * point in using the overright/overleft calls.
+				 */
+				if (RangeIsEmpty(r1) || range_before_internal(rangetyp, r1, r2))
+					break;
+				else
+					r2 = ++i2 >= range_count2 ? NULL : ranges2[i2];
+
+			}
+			else
+			{
+				/*
+				 * This and all future r2s are past r1, so keep them. Also
+				 * assign whatever is left of r1 to the result.
+				 */
+				break;
+			}
+		}
+
+		/*
+		 * Nothing else can remove anything from r1, so keep it. Even if r1 is
+		 * empty here, make_multirange will remove it.
+		 */
+		ranges3[range_count3++] = r1;
+	}
+
+	return make_multirange(mltrngtypoid, rangetyp, range_count3, ranges3);
+}
+
+/* multirange intersection */
+Datum
+multirange_intersect(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
+	MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
+	Oid			mltrngtypoid = MultirangeTypeGetOid(mr1);
+	TypeCacheEntry *typcache;
+	TypeCacheEntry *rangetyp;
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+
+	typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
+	rangetyp = typcache->rngtype;
+
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		PG_RETURN_MULTIRANGE_P(make_empty_multirange(mltrngtypoid, rangetyp));
+
+	multirange_deserialize(rangetyp, mr1, &range_count1, &ranges1);
+	multirange_deserialize(rangetyp, mr2, &range_count2, &ranges2);
+
+	PG_RETURN_MULTIRANGE_P(multirange_intersect_internal(mltrngtypoid,
+														 rangetyp,
+														 range_count1,
+														 ranges1,
+														 range_count2,
+														 ranges2));
+}
+
+MultirangeType *
+multirange_intersect_internal(Oid mltrngtypoid, TypeCacheEntry *rangetyp,
+							  int32 range_count1, RangeType **ranges1,
+							  int32 range_count2, RangeType **ranges2)
+{
+	RangeType  *r1;
+	RangeType  *r2;
+	RangeType **ranges3;
+	int32		range_count3;
+	int32		i1;
+	int32		i2;
+
+	if (range_count1 == 0 || range_count2 == 0)
+		return make_multirange(mltrngtypoid, rangetyp, 0, NULL);
+
+	/*-----------------------------------------------
+	 * Worst case is a stitching pattern like this:
+	 *
+	 * mr1: --- --- --- ---
+	 * mr2:   --- --- ---
+	 * mr3:   - - - - - -
+	 *
+	 * That seems to be range_count1 + range_count2 - 1,
+	 * but one extra won't hurt.
+	 *-----------------------------------------------
+	 */
+	ranges3 = palloc0((range_count1 + range_count2) * sizeof(RangeType *));
+	range_count3 = 0;
+
+	/*
+	 * For each range in mr1, keep intersecting until the ranges in mr2 have
+	 * passed it. The parallel progress through mr1 and mr2 is similar to
+	 * multirange_minus_multirange_internal, but we don't have to assign back
+	 * to r1.
+	 */
+	r2 = ranges2[0];
+	for (i1 = 0, i2 = 0; i1 < range_count1; i1++)
+	{
+		r1 = ranges1[i1];
+
+		/* Discard r2s while r2 << r1 */
+		while (r2 != NULL && range_before_internal(rangetyp, r2, r1))
+		{
+			r2 = ++i2 >= range_count2 ? NULL : ranges2[i2];
+		}
+
+		while (r2 != NULL)
+		{
+			if (range_overlaps_internal(rangetyp, r1, r2))
+			{
+				/* Keep the overlapping part */
+				ranges3[range_count3++] = range_intersect_internal(rangetyp, r1, r2);
+
+				/* If we "used up" all of r2, go to the next one... */
+				if (range_overleft_internal(rangetyp, r2, r1))
+					r2 = ++i2 >= range_count2 ? NULL : ranges2[i2];
+
+				/* ...otherwise go to the next r1 */
+				else
+					break;
+			}
+			else
+				/* We're past r1, so move to the next one */
+				break;
+		}
+
+		/* If we're out of r2s, there can be no more intersections */
+		if (r2 == NULL)
+			break;
+	}
+
+	return make_multirange(mltrngtypoid, rangetyp, range_count3, ranges3);
+}
+
+/*
+ * range_agg_transfn: combine adjacent/overlapping ranges.
+ *
+ * All we do here is gather the input ranges into an array
+ * so that the finalfn can sort and combine them.
+ */
+Datum
+range_agg_transfn(PG_FUNCTION_ARGS)
+{
+	MemoryContext aggContext;
+	Oid			rngtypoid;
+	ArrayBuildState *state;
+
+	if (!AggCheckCallContext(fcinfo, &aggContext))
+		elog(ERROR, "range_agg_transfn called in non-aggregate context");
+
+	rngtypoid = get_fn_expr_argtype(fcinfo->flinfo, 1);
+	if (!type_is_range(rngtypoid))
+		ereport(ERROR,
+				(errcode(ERRCODE_DATATYPE_MISMATCH),
+				 errmsg("range_agg must be called with a range")));
+
+	if (PG_ARGISNULL(0))
+		state = initArrayResult(rngtypoid, aggContext, false);
+	else
+		state = (ArrayBuildState *) PG_GETARG_POINTER(0);
+
+	/* skip NULLs */
+	if (!PG_ARGISNULL(1))
+		accumArrayResult(state, PG_GETARG_DATUM(1), false, rngtypoid, aggContext);
+
+	PG_RETURN_POINTER(state);
+}
+
+/*
+ * range_agg_finalfn: use our internal array to merge touching ranges.
+ */
+Datum
+range_agg_finalfn(PG_FUNCTION_ARGS)
+{
+	MemoryContext aggContext;
+	Oid			mltrngtypoid;
+	TypeCacheEntry *typcache;
+	ArrayBuildState *state;
+	int32		range_count;
+	RangeType **ranges;
+	int			i;
+
+	if (!AggCheckCallContext(fcinfo, &aggContext))
+		elog(ERROR, "range_agg_finalfn called in non-aggregate context");
+
+	state = PG_ARGISNULL(0) ? NULL : (ArrayBuildState *) PG_GETARG_POINTER(0);
+	if (state == NULL)
+		/* This shouldn't be possible, but just in case.... */
+		PG_RETURN_NULL();
+
+	/* Also return NULL if we had zero inputs, like other aggregates */
+	range_count = state->nelems;
+	if (range_count == 0)
+		PG_RETURN_NULL();
+
+	mltrngtypoid = get_fn_expr_rettype(fcinfo->flinfo);
+	typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
+
+	ranges = palloc0(range_count * sizeof(RangeType *));
+	for (i = 0; i < range_count; i++)
+		ranges[i] = DatumGetRangeTypeP(state->dvalues[i]);
+
+	PG_RETURN_MULTIRANGE_P(make_multirange(mltrngtypoid, typcache->rngtype, range_count, ranges));
+}
+
+Datum
+multirange_intersect_agg_transfn(PG_FUNCTION_ARGS)
+{
+	MemoryContext aggContext;
+	Oid			mltrngtypoid;
+	TypeCacheEntry *typcache;
+	MultirangeType *result;
+	MultirangeType *current;
+	int32		range_count1;
+	int32		range_count2;
+	RangeType **ranges1;
+	RangeType **ranges2;
+
+	if (!AggCheckCallContext(fcinfo, &aggContext))
+		elog(ERROR, "multirange_intersect_agg_transfn called in non-aggregate context");
+
+	mltrngtypoid = get_fn_expr_argtype(fcinfo->flinfo, 1);
+	if (!type_is_multirange(mltrngtypoid))
+		ereport(ERROR,
+				(errcode(ERRCODE_DATATYPE_MISMATCH),
+				 errmsg("range_intersect_agg must be called with a multirange")));
+
+	typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
+
+	/* strictness ensures these are non-null */
+	result = PG_GETARG_MULTIRANGE_P(0);
+	current = PG_GETARG_MULTIRANGE_P(1);
+
+	multirange_deserialize(typcache->rngtype, result, &range_count1, &ranges1);
+	multirange_deserialize(typcache->rngtype, current, &range_count2, &ranges2);
+
+	result = multirange_intersect_internal(mltrngtypoid,
+										   typcache->rngtype,
+										   range_count1,
+										   ranges1,
+										   range_count2,
+										   ranges2);
+	PG_RETURN_RANGE_P(result);
+}
+
+
+/* multirange -> element type functions */
+
+/* extract lower bound value */
+Datum
+multirange_lower(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	TypeCacheEntry *typcache;
+	RangeBound	lower;
+	RangeBound	upper;
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_NULL();
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_get_bounds(typcache->rngtype, mr, 0,
+						  &lower, &upper);
+
+	if (!lower.infinite)
+		PG_RETURN_DATUM(lower.val);
+	else
+		PG_RETURN_NULL();
+}
+
+/* extract upper bound value */
+Datum
+multirange_upper(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	TypeCacheEntry *typcache;
+	RangeBound	lower;
+	RangeBound	upper;
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_NULL();
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_get_bounds(typcache->rngtype, mr, mr->rangeCount - 1,
+						  &lower, &upper);
+
+	if (!upper.infinite)
+		PG_RETURN_DATUM(upper.val);
+	else
+		PG_RETURN_NULL();
+}
+
+
+/* multirange -> bool functions */
+
+/* is multirange empty? */
+Datum
+multirange_empty(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+
+	PG_RETURN_BOOL(MultirangeIsEmpty(mr));
+}
+
+/* is lower bound inclusive? */
+Datum
+multirange_lower_inc(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	TypeCacheEntry *typcache;
+	RangeBound	lower;
+	RangeBound	upper;
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+	multirange_get_bounds(typcache->rngtype, mr, 0,
+						  &lower, &upper);
+
+	PG_RETURN_BOOL(lower.inclusive);
+}
+
+/* is upper bound inclusive? */
+Datum
+multirange_upper_inc(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	TypeCacheEntry *typcache;
+	RangeBound	lower;
+	RangeBound	upper;
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+	multirange_get_bounds(typcache->rngtype, mr, mr->rangeCount - 1,
+						  &lower, &upper);
+
+	PG_RETURN_BOOL(upper.inclusive);
+}
+
+/* is lower bound infinite? */
+Datum
+multirange_lower_inf(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	TypeCacheEntry *typcache;
+	RangeBound	lower;
+	RangeBound	upper;
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+	multirange_get_bounds(typcache->rngtype, mr, 0,
+						  &lower, &upper);
+
+	PG_RETURN_BOOL(lower.infinite);
+}
+
+/* is upper bound infinite? */
+Datum
+multirange_upper_inf(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	TypeCacheEntry *typcache;
+	RangeBound	lower;
+	RangeBound	upper;
+
+	if (MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+	multirange_get_bounds(typcache->rngtype, mr, mr->rangeCount - 1,
+						  &lower, &upper);
+
+	PG_RETURN_BOOL(upper.infinite);
+}
+
+
+
+/* multirange, element -> bool functions */
+
+/* contains? */
+Datum
+multirange_contains_elem(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	Datum		val = PG_GETARG_DATUM(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(multirange_contains_elem_internal(typcache, mr, val));
+}
+
+/* contained by? */
+Datum
+elem_contained_by_multirange(PG_FUNCTION_ARGS)
+{
+	Datum		val = PG_GETARG_DATUM(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(multirange_contains_elem_internal(typcache, mr, val));
+}
+
+/*
+ * Comparison function for checking if any range of multirange contains given
+ * key element using binary search.
+ */
+static int
+multirange_elem_bsearch_comparison(TypeCacheEntry *typcache,
+								   RangeBound *lower, RangeBound *upper,
+								   void *key, bool *match)
+{
+	Datum	val = *((Datum *) key);
+	int		cmp;
+
+	if (!lower->infinite)
+	{
+		cmp = DatumGetInt32(FunctionCall2Coll(&typcache->rng_cmp_proc_finfo,
+											  typcache->rng_collation,
+											  lower->val, val));
+		if (cmp > 0 || (cmp == 0 && !lower->inclusive))
+			return -1;
+	}
+
+	if (!upper->infinite)
+	{
+		cmp = DatumGetInt32(FunctionCall2Coll(&typcache->rng_cmp_proc_finfo,
+											  typcache->rng_collation,
+											  upper->val, val));
+		if (cmp < 0 || (cmp == 0 && !upper->inclusive))
+			return 1;
+	}
+
+	*match = true;
+	return 0;
+}
+
+/*
+ * Test whether multirange mr contains a specific element value.
+ */
+bool
+multirange_contains_elem_internal(TypeCacheEntry *typcache,
+								  MultirangeType *mr, Datum val)
+{
+	if (MultirangeIsEmpty(mr))
+		return false;
+
+	return multirange_bsearch_match(typcache->rngtype, mr, &val,
+									multirange_elem_bsearch_comparison);
+}
+
+/* multirange, range -> bool functions */
+
+/* contains? */
+Datum
+multirange_contains_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(multirange_contains_range_internal(typcache, mr, r));
+}
+
+/* contained by? */
+Datum
+range_contained_by_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(multirange_contains_range_internal(typcache, mr, r));
+}
+
+/*
+ * Comparison function for checking if any range of multirange contains given
+ * key range using binary search.
+ */
+static int
+multirange_range_contains_bsearch_comparison(TypeCacheEntry *typcache,
+											 RangeBound *lower, RangeBound *upper,
+											 void *key, bool *match)
+{
+	RangeBound *keyLower = (RangeBound *) key;
+	RangeBound *keyUpper = (RangeBound *) key + 1;
+
+	/* Check if key range is strictly in the left or in the right */
+	if (range_cmp_bounds(typcache, keyUpper, lower) < 0)
+		return -1;
+	if (range_cmp_bounds(typcache, keyLower, upper) > 0)
+		return -1;
+
+	/*
+	 * At this point we found overlapping range.  But we have to check if it
+	 * really contains the key range.  Anyway, we have to stop our search here,
+	 * because multirange contains only non-overlapping ranges.
+	 */
+	*match = range_bounds_contains(typcache, lower, upper, keyLower, keyUpper);
+
+	return 0;
+}
+
+/*
+ * Test whether multirange mr contains a specific range r.
+ */
+bool
+multirange_contains_range_internal(TypeCacheEntry *typcache, MultirangeType *mr, RangeType *r)
+{
+	TypeCacheEntry *rangetyp;
+	RangeBound	bounds[2];
+	bool		empty;
+
+	rangetyp = typcache->rngtype;
+
+	/*
+	 * Every multirange contains an infinite number of empty ranges, even an
+	 * empty one.
+	 */
+	if (RangeIsEmpty(r))
+		return true;
+
+	if (MultirangeIsEmpty(mr))
+		return false;
+
+	range_deserialize(rangetyp, r, &bounds[0], &bounds[1], &empty);
+	Assert(!empty);
+
+	return multirange_bsearch_match(rangetyp, mr, bounds,
+									multirange_range_contains_bsearch_comparison);
+}
+
+
+/* multirange, multirange -> bool functions */
+
+/* equality (internal version) */
+bool
+multirange_eq_internal(TypeCacheEntry *typcache, MultirangeType *mr1, MultirangeType *mr2)
+{
+	TypeCacheEntry *rangetyp = typcache->rngtype;
+	int32		range_count_1;
+	int32		range_count_2;
+	int32		i;
+	RangeBound	lower1,
+				upper1,
+				lower2,
+				upper2;
+
+	/* Different types should be prevented by ANYMULTIRANGE matching rules */
+	if (MultirangeTypeGetOid(mr1) != MultirangeTypeGetOid(mr2))
+		elog(ERROR, "multirange types do not match");
+
+	range_count_1 = mr1->rangeCount;
+	range_count_2 = mr2->rangeCount;
+
+	if (range_count_1 != range_count_2)
+		return false;
+
+	for (i = 0; i < range_count_1; i++)
+	{
+		multirange_get_bounds(rangetyp, mr1, i, &lower1, &upper1);
+		multirange_get_bounds(rangetyp, mr2, i, &lower2, &upper2);
+
+		if (range_cmp_bounds(rangetyp, &lower1, &lower2) != 0 ||
+			range_cmp_bounds(rangetyp, &upper1, &upper2) != 0)
+			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));
+}
+
+/* overlaps? */
+Datum
+range_overlaps_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(range_overlaps_multirange_internal(typcache, r, mr));
+}
+
+Datum
+multirange_overlaps_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(range_overlaps_multirange_internal(typcache, r, mr));
+}
+
+Datum
+multirange_overlaps_multirange(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_overlaps_multirange_internal(typcache, mr1, mr2));
+}
+
+/*
+ * Comparison function for checking if any range of multirange overlaps given
+ * key range using binary search.
+ */
+static int
+multirange_range_overlaps_bsearch_comparison(TypeCacheEntry *typcache,
+											 RangeBound *lower, RangeBound *upper,
+											 void *key, bool *match)
+{
+	RangeBound *keyLower = (RangeBound *) key;
+	RangeBound *keyUpper = (RangeBound *) key + 1;
+
+	if (range_cmp_bounds(typcache, keyUpper, lower) < 0)
+		return -1;
+	if (range_cmp_bounds(typcache, keyLower, upper) > 0)
+		return -1;
+
+	*match = true;
+	return 0;
+}
+
+bool
+range_overlaps_multirange_internal(TypeCacheEntry *typcache, RangeType *r, MultirangeType *mr)
+{
+	TypeCacheEntry *rangetyp;
+	RangeBound	bounds[2];
+	bool		empty;
+
+	rangetyp = typcache->rngtype;
+
+	/*
+	 * Empties never overlap, even with empties. (This seems strange since
+	 * they *do* contain each other, but we want to follow how ranges work.)
+	 */
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		return false;
+
+	range_deserialize(rangetyp, r, &bounds[0], &bounds[1], &empty);
+	Assert(!empty);
+
+	return multirange_bsearch_match(rangetyp, mr, bounds,
+									multirange_range_overlaps_bsearch_comparison);
+}
+
+bool
+multirange_overlaps_multirange_internal(TypeCacheEntry *typcache, MultirangeType *mr1,
+										MultirangeType *mr2)
+{
+	TypeCacheEntry *rangetyp;
+	int32		range_count1;
+	int32		range_count2;
+	int32		i1;
+	int32		i2;
+	RangeBound	lower1,
+				upper1,
+				lower2,
+				upper2;
+
+	/*
+	 * Empties never overlap, even with empties. (This seems strange since
+	 * they *do* contain each other, but we want to follow how ranges work.)
+	 */
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		return false;
+
+	rangetyp = typcache->rngtype;
+
+	range_count1 = mr1->rangeCount;
+	range_count2 = mr2->rangeCount;
+
+	/*
+	 * Every range in mr1 gets a chance to overlap with the ranges in mr2, but
+	 * we can use their ordering to avoid O(n^2). This is similar to
+	 * range_overlaps_multirange where r1 : r2 :: mrr : r, but there if we
+	 * don't find an overlap with r we're done, and here if we don't find an
+	 * overlap with r2 we try the next r2.
+	 */
+	i1 = 0;
+	multirange_get_bounds(rangetyp, mr1, i1, &lower1, &upper1);
+	for (i1 = 0, i2 = 0; i2 < range_count2; i2++)
+	{
+		multirange_get_bounds(rangetyp, mr2, i2, &lower2, &upper2);
+
+		/* Discard r1s while r1 << r2 */
+		while (range_cmp_bounds(rangetyp, &upper1, &lower2) < 0)
+		{
+			if (++i1 >= range_count1)
+				return false;
+			multirange_get_bounds(rangetyp, mr1, i1, &lower1, &upper1);
+		}
+
+		/*
+		 * If r1 && r2, we're done, otherwise we failed to find an overlap for
+		 * r2, so go to the next one.
+		 */
+		if (range_bounds_overlaps(rangetyp, &lower1, &upper1, &lower2, &upper2))
+			return true;
+	}
+
+	/* We looked through all of mr2 without finding an overlap */
+	return false;
+}
+
+/* does not extend to right of? */
+Datum
+range_overleft_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	RangeBound	lower1,
+				upper1,
+				lower2,
+				upper2;
+	bool		empty;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	range_deserialize(typcache->rngtype, r, &lower1, &upper1, &empty);
+	Assert(!empty);
+	multirange_get_bounds(typcache->rngtype, mr, mr->rangeCount - 1,
+						  &lower2, &upper2);
+
+	PG_RETURN_BOOL(range_cmp_bounds(typcache->rngtype, &upper1, &upper2) <= 0);
+}
+
+Datum
+multirange_overleft_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+	RangeBound	lower1,
+				upper1,
+				lower2,
+				upper2;
+	bool		empty;
+
+	if (MultirangeIsEmpty(mr) || RangeIsEmpty(r))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_get_bounds(typcache->rngtype, mr, mr->rangeCount - 1,
+						  &lower1, &upper1);
+	range_deserialize(typcache->rngtype, r, &lower2, &upper2, &empty);
+	Assert(!empty);
+
+	PG_RETURN_BOOL(range_cmp_bounds(typcache->rngtype, &upper1, &upper2) <= 0);
+}
+
+Datum
+multirange_overleft_multirange(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
+	MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	RangeBound	lower1,
+				upper1,
+				lower2,
+				upper2;
+
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr1));
+
+	multirange_get_bounds(typcache->rngtype, mr1, mr1->rangeCount - 1,
+						  &lower1, &upper1);
+	multirange_get_bounds(typcache->rngtype, mr2, mr2->rangeCount - 1,
+						  &lower2, &upper2);
+
+	PG_RETURN_BOOL(range_cmp_bounds(typcache->rngtype, &upper1, &upper2) <= 0);
+}
+
+/* does not extend to left of? */
+Datum
+range_overright_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	RangeBound	lower1,
+				upper1,
+				lower2,
+				upper2;
+	bool		empty;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	range_deserialize(typcache->rngtype, r, &lower1, &upper1, &empty);
+	Assert(!empty);
+	multirange_get_bounds(typcache->rngtype, mr, 0, &lower2, &upper2);
+
+	PG_RETURN_BOOL(range_cmp_bounds(typcache->rngtype, &lower1, &lower2) >= 0);
+}
+
+Datum
+multirange_overright_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+	RangeBound	lower1,
+				upper1,
+				lower2,
+				upper2;
+	bool		empty;
+
+	if (MultirangeIsEmpty(mr) || RangeIsEmpty(r))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	multirange_get_bounds(typcache->rngtype, mr, 0, &lower1, &upper1);
+	range_deserialize(typcache->rngtype, r, &lower2, &upper2, &empty);
+	Assert(!empty);
+
+	PG_RETURN_BOOL(range_cmp_bounds(typcache->rngtype, &lower1, &lower2) >= 0);
+}
+
+Datum
+multirange_overright_multirange(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
+	MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	RangeBound	lower1,
+				upper1,
+				lower2,
+				upper2;
+
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		PG_RETURN_BOOL(false);
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr1));
+
+	multirange_get_bounds(typcache->rngtype, mr1, 0, &lower1, &upper1);
+	multirange_get_bounds(typcache->rngtype, mr2, 0, &lower2, &upper2);
+
+	PG_RETURN_BOOL(range_cmp_bounds(typcache->rngtype, &lower1, &lower2) >= 0);
+}
+
+/* contains? */
+Datum
+multirange_contains_multirange(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_contains_multirange_internal(typcache, mr1, mr2));
+}
+
+/* contained by? */
+Datum
+multirange_contained_by_multirange(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_contains_multirange_internal(typcache, mr2, mr1));
+}
+
+/*
+ * Test whether multirange mr1 contains every range from another multirange mr2.
+ */
+bool
+multirange_contains_multirange_internal(TypeCacheEntry *typcache,
+										MultirangeType *mr1, MultirangeType *mr2)
+{
+	TypeCacheEntry *rangetyp;
+	int32		range_count1 = mr1->rangeCount;
+	int32		range_count2 = mr2->rangeCount;
+	int			i1,
+				i2;
+	RangeBound	lower1,
+				upper1,
+				lower2,
+				upper2;
+
+	rangetyp = typcache->rngtype;
+
+	/*
+	 * We follow the same logic for empties as ranges: - an empty multirange
+	 * contains an empty range/multirange. - an empty multirange can't contain
+	 * any other range/multirange. - an empty multirange is contained by any
+	 * other range/multirange.
+	 */
+
+	if (range_count2 == 0)
+		return true;
+	if (range_count1 == 0)
+		return false;
+
+	/*
+	 * Every range in mr2 must be contained by some range in mr1. To avoid
+	 * O(n^2) we walk through both ranges in tandem.
+	 */
+	i1 = 0;
+	multirange_get_bounds(rangetyp, mr1, i1, &lower1, &upper1);
+	for (i2 = 0; i2 < range_count2; i2++)
+	{
+		multirange_get_bounds(rangetyp, mr2, i2, &lower2, &upper2);
+
+		/* Discard r1s while r1 << r2 */
+		while (range_cmp_bounds(rangetyp, &upper1, &lower2) < 0)
+		{
+			if (++i1 >= range_count1)
+				return false;
+			multirange_get_bounds(rangetyp, mr1, i1, &lower1, &upper1);
+		}
+
+		/*
+		 * If r1 @> r2, go to the next r2, otherwise return false (since every
+		 * r1[n] and r1[n+1] must have a gap). Note this will give weird
+		 * answers if you don't canonicalize, e.g. with a custom
+		 * int2multirange {[1,1], [2,2]} there is a "gap". But that is
+		 * consistent with other range operators, e.g. '[1,1]'::int2range -|-
+		 * '[2,2]'::int2range is false.
+		 */
+		if (!range_bounds_contains(rangetyp, &lower1, &upper1,
+								   &lower2, &upper2))
+			return false;
+	}
+
+	/* All ranges in mr2 are satisfied */
+	return true;
+}
+
+/* strictly left of? */
+Datum
+range_before_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(range_before_multirange_internal(typcache, r, mr));
+}
+
+Datum
+multirange_before_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(range_after_multirange_internal(typcache, r, mr));
+}
+
+Datum
+multirange_before_multirange(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_before_multirange_internal(typcache, mr1, mr2));
+}
+
+/* strictly right of? */
+Datum
+range_after_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(range_after_multirange_internal(typcache, r, mr));
+}
+
+Datum
+multirange_after_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(range_before_multirange_internal(typcache, r, mr));
+}
+
+Datum
+multirange_after_multirange(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_before_multirange_internal(typcache, mr2, mr1));
+}
+
+/* strictly left of? (internal version) */
+bool
+range_before_multirange_internal(TypeCacheEntry *typcache, RangeType *r,
+								 MultirangeType *mr)
+{
+	RangeBound	lower1,
+				upper1,
+				lower2,
+				upper2;
+	bool		empty;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		return false;
+
+	range_deserialize(typcache->rngtype, r, &lower1, &upper1, &empty);
+	Assert(!empty);
+
+	multirange_get_bounds(typcache->rngtype, mr, 0,
+						  &lower2, &upper2);
+
+	return (range_cmp_bounds(typcache->rngtype, &upper1, &lower2) < 0);
+}
+
+bool
+multirange_before_multirange_internal(TypeCacheEntry *typcache,
+									  MultirangeType *mr1,
+									  MultirangeType *mr2)
+{
+	RangeBound	lower1,
+				upper1,
+				lower2,
+				upper2;
+
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		return false;
+
+	multirange_get_bounds(typcache->rngtype, mr1, mr1->rangeCount - 1,
+						  &lower1, &upper1);
+	multirange_get_bounds(typcache->rngtype, mr2, 0,
+						  &lower2, &upper2);
+
+	return (range_cmp_bounds(typcache->rngtype, &upper1, &lower2) < 0);
+}
+
+/* strictly right of? (internal version) */
+bool
+range_after_multirange_internal(TypeCacheEntry *typcache, RangeType *r,
+								MultirangeType *mr)
+{
+	RangeBound	lower1,
+				upper1,
+				lower2,
+				upper2;
+	bool		empty;
+	int32		range_count;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		return false;
+
+	range_deserialize(typcache->rngtype, r, &lower1, &upper1, &empty);
+	Assert(!empty);
+
+	range_count = mr->rangeCount;
+	multirange_get_bounds(typcache->rngtype, mr, range_count - 1,
+						  &lower2, &upper2);
+
+	return (range_cmp_bounds(typcache->rngtype, &lower1, &upper2) > 0);
+}
+
+bool
+range_adjacent_multirange_internal(TypeCacheEntry *typcache, RangeType *r,
+								   MultirangeType *mr)
+{
+	RangeBound	lower1,
+				upper1,
+				lower2,
+				upper2;
+	bool		empty;
+	int32		range_count;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		return false;
+
+	range_deserialize(typcache->rngtype, r, &lower1, &upper1, &empty);
+	Assert(!empty);
+
+	range_count = mr->rangeCount;
+	multirange_get_bounds(typcache->rngtype, mr, 0,
+						  &lower2, &upper2);
+
+	if (bounds_adjacent(typcache->rngtype, upper1, lower2))
+		return true;
+
+	if (range_count > 1)
+		multirange_get_bounds(typcache->rngtype, mr, range_count - 1,
+							  &lower2, &upper2);
+
+	if (bounds_adjacent(typcache->rngtype, upper2, lower1))
+		return true;
+
+	return false;
+}
+
+/* adjacent to? */
+Datum
+range_adjacent_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(range_adjacent_multirange_internal(typcache, r, mr));
+}
+
+Datum
+multirange_adjacent_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		return false;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(range_adjacent_multirange_internal(typcache, r, mr));
+}
+
+Datum
+multirange_adjacent_multirange(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr1 = PG_GETARG_MULTIRANGE_P(0);
+	MultirangeType *mr2 = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+	int32		range_count1;
+	int32		range_count2;
+	RangeBound	lower1,
+				upper1,
+				lower2,
+				upper2;
+
+	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
+		return false;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr1));
+
+	range_count1 = mr1->rangeCount;
+	range_count2 = mr2->rangeCount;
+	multirange_get_bounds(typcache->rngtype, mr1, range_count1 - 1,
+						  &lower1, &upper1);
+	multirange_get_bounds(typcache->rngtype, mr2, 0,
+						  &lower2, &upper2);
+	if (bounds_adjacent(typcache->rngtype, upper1, lower2))
+		PG_RETURN_BOOL(true);
+
+	if (range_count1 > 1)
+		multirange_get_bounds(typcache->rngtype, mr1, 0,
+							  &lower1, &upper1);
+	if (range_count2 > 1)
+		multirange_get_bounds(typcache->rngtype, mr2, range_count2 - 1,
+							  &lower2, &upper2);
+	if (bounds_adjacent(typcache->rngtype, upper2, lower1))
+		PG_RETURN_BOOL(true);
+	PG_RETURN_BOOL(false);
+}
+
+/* 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;
+	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));
+
+	range_count_1 = mr1->rangeCount;
+	range_count_2 = mr2->rangeCount;
+
+	/* Loop over source data */
+	range_count_max = Max(range_count_1, range_count_2);
+	for (i = 0; i < range_count_max; i++)
+	{
+		RangeBound	lower1,
+					upper1,
+					lower2,
+					upper2;
+
+		/*
+		 * 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;
+		}
+
+		multirange_get_bounds(typcache->rngtype, mr1, i, &lower1, &upper1);
+		multirange_get_bounds(typcache->rngtype, mr2, i, &lower2, &upper2);
+
+		cmp = range_cmp_bounds(typcache->rngtype, &lower1, &lower2);
+		if (cmp == 0)
+			cmp = range_cmp_bounds(typcache->rngtype, &upper1, &upper2);
+		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);
+}
+
+/* multirange -> range functions */
+
+/* Find the smallest range that includes everything in the multirange */
+Datum
+range_merge_from_multirange(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	Oid			mltrngtypoid = MultirangeTypeGetOid(mr);
+	TypeCacheEntry *typcache;
+	RangeType  *result;
+
+	typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
+
+	if (MultirangeIsEmpty(mr))
+	{
+		result = make_empty_range(typcache->rngtype);
+	}
+	else if (mr->rangeCount == 1)
+	{
+		result = multirange_get_range(typcache->rngtype, mr, 0);
+	}
+	else
+	{
+		RangeBound	firstLower,
+					firstUpper,
+					lastLower,
+					lastUpper;
+
+		multirange_get_bounds(typcache->rngtype, mr, 0,
+							  &firstLower, &firstUpper);
+		multirange_get_bounds(typcache->rngtype, mr, mr->rangeCount - 1,
+							  &lastLower, &lastUpper);
+
+		result = make_range(typcache->rngtype, &firstLower, &lastUpper, false);
+	}
+
+	PG_RETURN_RANGE_P(result);
+}
+
+/* Hash support */
+
+/* hash a multirange value */
+Datum
+hash_multirange(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	uint32		result = 1;
+	TypeCacheEntry *typcache,
+			   *scache;
+	int32		range_count,
+				i;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+	scache = typcache->rngtype->rngelemtype;
+	if (!OidIsValid(scache->hash_proc_finfo.fn_oid))
+	{
+		scache = lookup_type_cache(scache->type_id,
+								   TYPECACHE_HASH_PROC_FINFO);
+		if (!OidIsValid(scache->hash_proc_finfo.fn_oid))
+			ereport(ERROR,
+					(errcode(ERRCODE_UNDEFINED_FUNCTION),
+					 errmsg("could not identify a hash function for type %s",
+							format_type_be(scache->type_id))));
+	}
+
+	range_count = mr->rangeCount;
+	for (i = 0; i < range_count; i++)
+	{
+		RangeBound	lower,
+					upper;
+		uint8		flags = MultirangeGetFlagsPtr(mr)[i];
+		uint32		lower_hash;
+		uint32		upper_hash;
+		uint32		range_hash;
+
+		multirange_get_bounds(typcache->rngtype, mr, i, &lower, &upper);
+
+		if (RANGE_HAS_LBOUND(flags))
+			lower_hash = DatumGetUInt32(FunctionCall1Coll(&scache->hash_proc_finfo,
+														  typcache->rngtype->rng_collation,
+														  lower.val));
+		else
+			lower_hash = 0;
+
+		if (RANGE_HAS_UBOUND(flags))
+			upper_hash = DatumGetUInt32(FunctionCall1Coll(&scache->hash_proc_finfo,
+														  typcache->rngtype->rng_collation,
+														  upper.val));
+		else
+			upper_hash = 0;
+
+		/* Merge hashes of flags and bounds */
+		range_hash = hash_uint32((uint32) flags);
+		range_hash ^= lower_hash;
+		range_hash = (range_hash << 1) | (range_hash >> 31);
+		range_hash ^= upper_hash;
+
+		/*
+		 * Use the same approach as hash_array to combine the individual
+		 * elements' hash values:
+		 */
+		result = (result << 5) - result + range_hash;
+	}
+
+	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,
+			   *scache;
+	int32		range_count,
+				i;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+	scache = typcache->rngtype->rngelemtype;
+	if (!OidIsValid(scache->hash_extended_proc_finfo.fn_oid))
+	{
+		scache = lookup_type_cache(scache->type_id,
+								   TYPECACHE_HASH_EXTENDED_PROC_FINFO);
+		if (!OidIsValid(scache->hash_extended_proc_finfo.fn_oid))
+			ereport(ERROR,
+					(errcode(ERRCODE_UNDEFINED_FUNCTION),
+					 errmsg("could not identify a hash function for type %s",
+							format_type_be(scache->type_id))));
+	}
+
+	range_count = mr->rangeCount;
+	for (i = 0; i < range_count; i++)
+	{
+		RangeBound	lower,
+					upper;
+		uint8		flags = MultirangeGetFlagsPtr(mr)[i];
+		uint64		lower_hash;
+		uint64		upper_hash;
+		uint64		range_hash;
+
+		multirange_get_bounds(typcache->rngtype, mr, i, &lower, &upper);
+
+		if (RANGE_HAS_LBOUND(flags))
+			lower_hash = DatumGetUInt64(FunctionCall2Coll(&scache->hash_extended_proc_finfo,
+														  typcache->rngtype->rng_collation,
+														  lower.val,
+														  seed));
+		else
+			lower_hash = 0;
+
+		if (RANGE_HAS_UBOUND(flags))
+			upper_hash = DatumGetUInt64(FunctionCall2Coll(&scache->hash_extended_proc_finfo,
+														  typcache->rngtype->rng_collation,
+														  upper.val,
+														  seed));
+		else
+			upper_hash = 0;
+
+		/* Merge hashes of flags and bounds */
+		range_hash = DatumGetUInt64(hash_uint32_extended((uint32) flags,
+														 DatumGetInt64(seed)));
+		range_hash ^= lower_hash;
+		range_hash = ROTATE_HIGH_AND_LOW_32BITS(range_hash);
+		range_hash ^= upper_hash;
+
+		/*
+		 * Use the same approach as hash_array to combine the individual
+		 * elements' hash values:
+		 */
+		result = (result << 5) - result + range_hash;
+	}
+
+	PG_FREE_IF_COPY(mr, 0);
+
+	PG_RETURN_UINT64(result);
+}
diff --git a/src/backend/utils/adt/multirangetypes_selfuncs.c b/src/backend/utils/adt/multirangetypes_selfuncs.c
new file mode 100644
index 00000000000..b99dc2f0ccf
--- /dev/null
+++ b/src/backend/utils/adt/multirangetypes_selfuncs.c
@@ -0,0 +1,1320 @@
+/*-------------------------------------------------------------------------
+ *
+ * multirangetypes_selfuncs.c
+ *	  Functions for selectivity estimation of multirange operators
+ *
+ * Estimates are based on histograms of lower and upper bounds, and the
+ * fraction of empty multiranges.
+ *
+ * Portions Copyright (c) 1996-2020, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ *	  src/backend/utils/adt/multirangetypes_selfuncs.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include <math.h>
+
+#include "access/htup_details.h"
+#include "catalog/pg_operator.h"
+#include "catalog/pg_statistic.h"
+#include "catalog/pg_type.h"
+#include "utils/float.h"
+#include "utils/fmgrprotos.h"
+#include "utils/lsyscache.h"
+#include "utils/rangetypes.h"
+#include "utils/multirangetypes.h"
+#include "utils/selfuncs.h"
+#include "utils/typcache.h"
+
+static double calc_multirangesel(TypeCacheEntry *typcache,
+								 VariableStatData *vardata,
+								 const MultirangeType *constval, Oid operator);
+static double default_multirange_selectivity(Oid operator);
+static double default_multirange_selectivity(Oid operator);
+static double calc_hist_selectivity(TypeCacheEntry *typcache,
+									VariableStatData *vardata,
+									const MultirangeType *constval,
+									Oid operator);
+static double calc_hist_selectivity_scalar(TypeCacheEntry *typcache,
+										   const RangeBound *constbound,
+										   const RangeBound *hist,
+										   int hist_nvalues, bool equal);
+static int	rbound_bsearch(TypeCacheEntry *typcache, const RangeBound *value,
+						   const RangeBound *hist, int hist_length, bool equal);
+static float8 get_position(TypeCacheEntry *typcache, const RangeBound *value,
+						   const RangeBound *hist1, const RangeBound *hist2);
+static float8 get_len_position(double value, double hist1, double hist2);
+static float8 get_distance(TypeCacheEntry *typcache, const RangeBound *bound1,
+						   const RangeBound *bound2);
+static int	length_hist_bsearch(Datum *length_hist_values,
+								int length_hist_nvalues, double value,
+								bool equal);
+static double calc_length_hist_frac(Datum *length_hist_values,
+									int length_hist_nvalues, double length1,
+									double length2, bool equal);
+static double calc_hist_selectivity_contained(TypeCacheEntry *typcache,
+											  const RangeBound *lower,
+											  RangeBound *upper,
+											  const RangeBound *hist_lower,
+											  int hist_nvalues,
+											  Datum *length_hist_values,
+											  int length_hist_nvalues);
+static double calc_hist_selectivity_contains(TypeCacheEntry *typcache,
+											 const RangeBound *lower,
+											 const RangeBound *upper,
+											 const RangeBound *hist_lower,
+											 int hist_nvalues,
+											 Datum *length_hist_values,
+											 int length_hist_nvalues);
+
+/*
+ * Returns a default selectivity estimate for given operator, when we don't
+ * have statistics or cannot use them for some reason.
+ */
+static double
+default_multirange_selectivity(Oid operator)
+{
+	switch (operator)
+	{
+		case OID_MULTIRANGE_OVERLAPS_MULTIRANGE_OP:
+		case OID_MULTIRANGE_OVERLAPS_RANGE_OP:
+		case OID_RANGE_OVERLAPS_MULTIRANGE_OP:
+			return 0.01;
+
+		case OID_MULTIRANGE_CONTAINS_RANGE_OP:
+		case OID_MULTIRANGE_CONTAINS_MULTIRANGE_OP:
+		case OID_MULTIRANGE_RANGE_CONTAINED_OP:
+		case OID_MULTIRANGE_MULTIRANGE_CONTAINED_OP:
+			return 0.005;
+
+		case OID_MULTIRANGE_CONTAINS_ELEM_OP:
+		case OID_MULTIRANGE_ELEM_CONTAINED_OP:
+
+			/*
+			 * "multirange @> elem" is more or less identical to a scalar
+			 * inequality "A >= b AND A <= c".
+			 */
+			return DEFAULT_MULTIRANGE_INEQ_SEL;
+
+		case OID_MULTIRANGE_LESS_OP:
+		case OID_MULTIRANGE_LESS_EQUAL_OP:
+		case OID_MULTIRANGE_GREATER_OP:
+		case OID_MULTIRANGE_GREATER_EQUAL_OP:
+		case OID_MULTIRANGE_LEFT_RANGE_OP:
+		case OID_MULTIRANGE_LEFT_MULTIRANGE_OP:
+		case OID_RANGE_LEFT_MULTIRANGE_OP:
+		case OID_MULTIRANGE_RIGHT_RANGE_OP:
+		case OID_MULTIRANGE_RIGHT_MULTIRANGE_OP:
+		case OID_RANGE_RIGHT_MULTIRANGE_OP:
+		case OID_MULTIRANGE_OVERLAPS_LEFT_RANGE_OP:
+		case OID_RANGE_OVERLAPS_LEFT_MULTIRANGE_OP:
+		case OID_MULTIRANGE_OVERLAPS_LEFT_MULTIRANGE_OP:
+		case OID_MULTIRANGE_OVERLAPS_RIGHT_RANGE_OP:
+		case OID_RANGE_OVERLAPS_RIGHT_MULTIRANGE_OP:
+		case OID_MULTIRANGE_OVERLAPS_RIGHT_MULTIRANGE_OP:
+			/* these are similar to regular scalar inequalities */
+			return DEFAULT_INEQ_SEL;
+
+		default:
+
+			/*
+			 * all multirange operators should be handled above, but just in
+			 * case
+			 */
+			return 0.01;
+	}
+}
+
+/*
+ * multirangesel -- restriction selectivity for multirange operators
+ */
+Datum
+multirangesel(PG_FUNCTION_ARGS)
+{
+	PlannerInfo *root = (PlannerInfo *) PG_GETARG_POINTER(0);
+	Oid			operator = PG_GETARG_OID(1);
+	List	   *args = (List *) PG_GETARG_POINTER(2);
+	int			varRelid = PG_GETARG_INT32(3);
+	VariableStatData vardata;
+	Node	   *other;
+	bool		varonleft;
+	Selectivity selec;
+	TypeCacheEntry *typcache = NULL;
+	MultirangeType *constmultirange = NULL;
+	RangeType  *constrange = NULL;
+
+	/*
+	 * If expression is not (variable op something) or (something op
+	 * variable), then punt and return a default estimate.
+	 */
+	if (!get_restriction_variable(root, args, varRelid,
+								  &vardata, &other, &varonleft))
+		PG_RETURN_FLOAT8(default_multirange_selectivity(operator));
+
+	/*
+	 * Can't do anything useful if the something is not a constant, either.
+	 */
+	if (!IsA(other, Const))
+	{
+		ReleaseVariableStats(vardata);
+		PG_RETURN_FLOAT8(default_multirange_selectivity(operator));
+	}
+
+	/*
+	 * All the multirange operators are strict, so we can cope with a NULL
+	 * constant right away.
+	 */
+	if (((Const *) other)->constisnull)
+	{
+		ReleaseVariableStats(vardata);
+		PG_RETURN_FLOAT8(0.0);
+	}
+
+	/*
+	 * If var is on the right, commute the operator, so that we can assume the
+	 * var is on the left in what follows.
+	 */
+	if (!varonleft)
+	{
+		/* we have other Op var, commute to make var Op other */
+		operator = get_commutator(operator);
+		if (!operator)
+		{
+			/* Use default selectivity (should we raise an error instead?) */
+			ReleaseVariableStats(vardata);
+			PG_RETURN_FLOAT8(default_multirange_selectivity(operator));
+		}
+	}
+
+	/*
+	 * OK, there's a Var and a Const we're dealing with here.  We need the
+	 * Const to be of same multirange type as the column, else we can't do
+	 * anything useful. (Such cases will likely fail at runtime, but here we'd
+	 * rather just return a default estimate.)
+	 *
+	 * If the operator is "multirange @> element", the constant should be of
+	 * the element type of the multirange column. Convert it to a multirange
+	 * that includes only that single point, so that we don't need special
+	 * handling for that in what follows.
+	 */
+	if (operator == OID_MULTIRANGE_CONTAINS_ELEM_OP)
+	{
+		typcache = multirange_get_typcache(fcinfo, vardata.vartype);
+
+		if (((Const *) other)->consttype == typcache->rngtype->rngelemtype->type_id)
+		{
+			RangeBound	lower,
+						upper;
+
+			lower.inclusive = true;
+			lower.val = ((Const *) other)->constvalue;
+			lower.infinite = false;
+			lower.lower = true;
+			upper.inclusive = true;
+			upper.val = ((Const *) other)->constvalue;
+			upper.infinite = false;
+			upper.lower = false;
+			constrange = range_serialize(typcache->rngtype, &lower, &upper, false);
+			constmultirange = make_multirange(typcache->type_id, typcache->rngtype,
+											  1, &constrange);
+		}
+	}
+	else if (operator == OID_MULTIRANGE_CONTAINS_RANGE_OP ||
+			 operator == OID_MULTIRANGE_OVERLAPS_RANGE_OP ||
+			 operator == OID_MULTIRANGE_OVERLAPS_LEFT_RANGE_OP ||
+			 operator == OID_MULTIRANGE_OVERLAPS_RIGHT_RANGE_OP ||
+			 operator == OID_MULTIRANGE_LEFT_RANGE_OP ||
+			 operator == OID_MULTIRANGE_RIGHT_RANGE_OP)
+	{
+		/*
+		 * Promote a range in "multirange OP range" just like we do an element
+		 * in "multirange OP element".
+		 */
+		typcache = multirange_get_typcache(fcinfo, vardata.vartype);
+		if (((Const *) other)->consttype == typcache->rngtype->type_id)
+		{
+			constrange = DatumGetRangeTypeP(((Const *) other)->constvalue);
+			constmultirange = make_multirange(typcache->type_id, typcache->rngtype,
+											  1, &constrange);
+		}
+	}
+	else if (operator == OID_RANGE_OVERLAPS_MULTIRANGE_OP ||
+			 operator == OID_RANGE_OVERLAPS_LEFT_MULTIRANGE_OP ||
+			 operator == OID_RANGE_OVERLAPS_RIGHT_MULTIRANGE_OP ||
+			 operator == OID_RANGE_LEFT_MULTIRANGE_OP ||
+			 operator == OID_RANGE_RIGHT_MULTIRANGE_OP ||
+			 operator == OID_MULTIRANGE_ELEM_CONTAINED_OP ||
+			 operator == OID_MULTIRANGE_RANGE_CONTAINED_OP)
+	{
+		/*
+		 * Here, the Var is the elem/range, not the multirange.  For now we
+		 * just punt and return the default estimate.  In future we could
+		 * disassemble the multirange constant to do something more
+		 * intelligent.
+		 */
+	}
+	else if (((Const *) other)->consttype == vardata.vartype)
+	{
+		/* Both sides are the same multirange type */
+		typcache = multirange_get_typcache(fcinfo, vardata.vartype);
+
+		constmultirange = DatumGetMultirangeTypeP(((Const *) other)->constvalue);
+	}
+
+	/*
+	 * If we got a valid constant on one side of the operator, proceed to
+	 * estimate using statistics. Otherwise punt and return a default constant
+	 * estimate.  Note that calc_multirangesel need not handle
+	 * OID_MULTIRANGE_*_CONTAINED_OP.
+	 */
+	if (constmultirange)
+		selec = calc_multirangesel(typcache, &vardata, constmultirange, operator);
+	else
+		selec = default_multirange_selectivity(operator);
+
+	ReleaseVariableStats(vardata);
+
+	CLAMP_PROBABILITY(selec);
+
+	PG_RETURN_FLOAT8((float8) selec);
+}
+
+static double
+calc_multirangesel(TypeCacheEntry *typcache, VariableStatData *vardata,
+				   const MultirangeType *constval, Oid operator)
+{
+	double		hist_selec;
+	double		selec;
+	float4		empty_frac,
+				null_frac;
+
+	/*
+	 * First look up the fraction of NULLs and empty multiranges from
+	 * pg_statistic.
+	 */
+	if (HeapTupleIsValid(vardata->statsTuple))
+	{
+		Form_pg_statistic stats;
+		AttStatsSlot sslot;
+
+		stats = (Form_pg_statistic) GETSTRUCT(vardata->statsTuple);
+		null_frac = stats->stanullfrac;
+
+		/* Try to get fraction of empty multiranges */
+		if (get_attstatsslot(&sslot, vardata->statsTuple,
+							 STATISTIC_KIND_RANGE_LENGTH_HISTOGRAM,
+							 InvalidOid,
+							 ATTSTATSSLOT_NUMBERS))
+		{
+			if (sslot.nnumbers != 1)
+				elog(ERROR, "invalid empty fraction statistic");	/* shouldn't happen */
+			empty_frac = sslot.numbers[0];
+			free_attstatsslot(&sslot);
+		}
+		else
+		{
+			/* No empty fraction statistic. Assume no empty ranges. */
+			empty_frac = 0.0;
+		}
+	}
+	else
+	{
+		/*
+		 * No stats are available. Follow through the calculations below
+		 * anyway, assuming no NULLs and no empty multiranges. This still
+		 * allows us to give a better-than-nothing estimate based on whether
+		 * the constant is an empty multirange or not.
+		 */
+		null_frac = 0.0;
+		empty_frac = 0.0;
+	}
+
+	if (MultirangeIsEmpty(constval))
+	{
+		/*
+		 * An empty multirange matches all multiranges, all empty multiranges,
+		 * or nothing, depending on the operator
+		 */
+		switch (operator)
+		{
+				/* these return false if either argument is empty */
+			case OID_RANGE_OVERLAPS_MULTIRANGE_OP:
+			case OID_MULTIRANGE_OVERLAPS_RANGE_OP:
+			case OID_MULTIRANGE_OVERLAPS_MULTIRANGE_OP:
+			case OID_RANGE_OVERLAPS_LEFT_MULTIRANGE_OP:
+			case OID_MULTIRANGE_OVERLAPS_LEFT_RANGE_OP:
+			case OID_MULTIRANGE_OVERLAPS_LEFT_MULTIRANGE_OP:
+			case OID_RANGE_OVERLAPS_RIGHT_MULTIRANGE_OP:
+			case OID_MULTIRANGE_OVERLAPS_RIGHT_RANGE_OP:
+			case OID_MULTIRANGE_OVERLAPS_RIGHT_MULTIRANGE_OP:
+			case OID_MULTIRANGE_LEFT_MULTIRANGE_OP:
+			case OID_MULTIRANGE_RIGHT_MULTIRANGE_OP:
+				/* nothing is less than an empty multirange */
+			case OID_MULTIRANGE_LESS_OP:
+				selec = 0.0;
+				break;
+
+				/*
+				 * only empty multiranges can be contained by an empty
+				 * multirange
+				 */
+			case OID_MULTIRANGE_RANGE_CONTAINED_OP:
+			case OID_MULTIRANGE_MULTIRANGE_CONTAINED_OP:
+				/* only empty ranges are <= an empty multirange */
+			case OID_MULTIRANGE_LESS_EQUAL_OP:
+				selec = empty_frac;
+				break;
+
+				/* everything contains an empty multirange */
+			case OID_MULTIRANGE_CONTAINS_RANGE_OP:
+			case OID_MULTIRANGE_CONTAINS_MULTIRANGE_OP:
+				/* everything is >= an empty multirange */
+			case OID_MULTIRANGE_GREATER_EQUAL_OP:
+				selec = 1.0;
+				break;
+
+				/* all non-empty multiranges are > an empty multirange */
+			case OID_MULTIRANGE_GREATER_OP:
+				selec = 1.0 - empty_frac;
+				break;
+
+				/* an element cannot be empty */
+			case OID_MULTIRANGE_ELEM_CONTAINED_OP:
+			case OID_MULTIRANGE_CONTAINS_ELEM_OP:
+			default:
+				elog(ERROR, "unexpected operator %u", operator);
+				selec = 0.0;	/* keep compiler quiet */
+				break;
+		}
+	}
+	else
+	{
+		/*
+		 * Calculate selectivity using bound histograms. If that fails for
+		 * some reason, e.g no histogram in pg_statistic, use the default
+		 * constant estimate for the fraction of non-empty values. This is
+		 * still somewhat better than just returning the default estimate,
+		 * because this still takes into account the fraction of empty and
+		 * NULL tuples, if we had statistics for them.
+		 */
+		hist_selec = calc_hist_selectivity(typcache, vardata, constval,
+										   operator);
+		if (hist_selec < 0.0)
+			hist_selec = default_multirange_selectivity(operator);
+
+		/*
+		 * Now merge the results for the empty multiranges and histogram
+		 * calculations, realizing that the histogram covers only the
+		 * non-null, non-empty values.
+		 */
+		if (operator == OID_MULTIRANGE_ELEM_CONTAINED_OP ||
+			operator == OID_MULTIRANGE_RANGE_CONTAINED_OP ||
+			operator == OID_MULTIRANGE_MULTIRANGE_CONTAINED_OP)
+		{
+			/* empty is contained by anything non-empty */
+			selec = (1.0 - empty_frac) * hist_selec + empty_frac;
+		}
+		else
+		{
+			/* with any other operator, empty Op non-empty matches nothing */
+			selec = (1.0 - empty_frac) * hist_selec;
+		}
+	}
+
+	/* all multirange operators are strict */
+	selec *= (1.0 - null_frac);
+
+	/* result should be in range, but make sure... */
+	CLAMP_PROBABILITY(selec);
+
+	return selec;
+}
+
+/*
+ * Calculate multirange operator selectivity using histograms of multirange bounds.
+ *
+ * This estimate is for the portion of values that are not empty and not
+ * NULL.
+ */
+static double
+calc_hist_selectivity(TypeCacheEntry *typcache, VariableStatData *vardata,
+					  const MultirangeType *constval, Oid operator)
+{
+	TypeCacheEntry *rng_typcache = typcache->rngtype;
+	AttStatsSlot hslot;
+	AttStatsSlot lslot;
+	int			nhist;
+	RangeBound *hist_lower;
+	RangeBound *hist_upper;
+	int			i;
+	RangeBound	const_lower;
+	RangeBound	const_upper;
+	RangeBound	tmp;
+	double		hist_selec;
+
+	/* Can't use the histogram with insecure multirange support functions */
+	if (!statistic_proc_security_check(vardata,
+									   rng_typcache->rng_cmp_proc_finfo.fn_oid))
+		return -1;
+	if (OidIsValid(rng_typcache->rng_subdiff_finfo.fn_oid) &&
+		!statistic_proc_security_check(vardata,
+									   rng_typcache->rng_subdiff_finfo.fn_oid))
+		return -1;
+
+	/* Try to get histogram of ranges */
+	if (!(HeapTupleIsValid(vardata->statsTuple) &&
+		  get_attstatsslot(&hslot, vardata->statsTuple,
+						   STATISTIC_KIND_BOUNDS_HISTOGRAM, InvalidOid,
+						   ATTSTATSSLOT_VALUES)))
+		return -1.0;
+
+	/* check that it's a histogram, not just a dummy entry */
+	if (hslot.nvalues < 2)
+	{
+		free_attstatsslot(&hslot);
+		return -1.0;
+	}
+
+	/*
+	 * Convert histogram of ranges into histograms of its lower and upper
+	 * bounds.
+	 */
+	nhist = hslot.nvalues;
+	hist_lower = (RangeBound *) palloc(sizeof(RangeBound) * nhist);
+	hist_upper = (RangeBound *) palloc(sizeof(RangeBound) * nhist);
+	for (i = 0; i < nhist; i++)
+	{
+		bool	empty;
+
+		range_deserialize(rng_typcache, DatumGetRangeTypeP(hslot.values[i]),
+						  &hist_lower[i], &hist_upper[i], &empty);
+		/* The histogram should not contain any empty ranges */
+		if (empty)
+			elog(ERROR, "bounds histogram contains an empty range");
+	}
+
+	/* @> and @< also need a histogram of range lengths */
+	if (operator == OID_MULTIRANGE_CONTAINS_RANGE_OP ||
+		operator == OID_MULTIRANGE_CONTAINS_MULTIRANGE_OP ||
+		operator == OID_MULTIRANGE_RANGE_CONTAINED_OP ||
+		operator == OID_MULTIRANGE_MULTIRANGE_CONTAINED_OP)
+	{
+		if (!(HeapTupleIsValid(vardata->statsTuple) &&
+			  get_attstatsslot(&lslot, vardata->statsTuple,
+							   STATISTIC_KIND_RANGE_LENGTH_HISTOGRAM,
+							   InvalidOid,
+							   ATTSTATSSLOT_VALUES)))
+		{
+			free_attstatsslot(&hslot);
+			return -1.0;
+		}
+
+		/* check that it's a histogram, not just a dummy entry */
+		if (lslot.nvalues < 2)
+		{
+			free_attstatsslot(&lslot);
+			free_attstatsslot(&hslot);
+			return -1.0;
+		}
+	}
+	else
+		memset(&lslot, 0, sizeof(lslot));
+
+	/* Extract the bounds of the constant value. */
+	Assert(constval->rangeCount > 0);
+	multirange_get_bounds(rng_typcache, constval, 0,
+						  &const_lower, &tmp);
+	multirange_get_bounds(rng_typcache, constval, constval->rangeCount - 1,
+						  &tmp, &const_upper);
+
+	/*
+	 * Calculate selectivity comparing the lower or upper bound of the
+	 * constant with the histogram of lower or upper bounds.
+	 */
+	switch (operator)
+	{
+		case OID_MULTIRANGE_LESS_OP:
+
+			/*
+			 * The regular b-tree comparison operators (<, <=, >, >=) compare
+			 * the lower bounds first, and the upper bounds for values with
+			 * equal lower bounds. Estimate that by comparing the lower bounds
+			 * only. This gives a fairly accurate estimate assuming there
+			 * aren't many rows with a lower bound equal to the constant's
+			 * lower bound.
+			 */
+			hist_selec =
+				calc_hist_selectivity_scalar(rng_typcache, &const_lower,
+											 hist_lower, nhist, false);
+			break;
+
+		case OID_MULTIRANGE_LESS_EQUAL_OP:
+			hist_selec =
+				calc_hist_selectivity_scalar(rng_typcache, &const_lower,
+											 hist_lower, nhist, true);
+			break;
+
+		case OID_MULTIRANGE_GREATER_OP:
+			hist_selec =
+				1 - calc_hist_selectivity_scalar(rng_typcache, &const_lower,
+												 hist_lower, nhist, false);
+			break;
+
+		case OID_MULTIRANGE_GREATER_EQUAL_OP:
+			hist_selec =
+				1 - calc_hist_selectivity_scalar(rng_typcache, &const_lower,
+												 hist_lower, nhist, true);
+			break;
+
+		case OID_RANGE_LEFT_MULTIRANGE_OP:
+		case OID_MULTIRANGE_LEFT_RANGE_OP:
+		case OID_MULTIRANGE_LEFT_MULTIRANGE_OP:
+			/* var << const when upper(var) < lower(const) */
+			hist_selec =
+				calc_hist_selectivity_scalar(rng_typcache, &const_lower,
+											 hist_upper, nhist, false);
+			break;
+
+		case OID_RANGE_RIGHT_MULTIRANGE_OP:
+		case OID_MULTIRANGE_RIGHT_RANGE_OP:
+		case OID_MULTIRANGE_RIGHT_MULTIRANGE_OP:
+			/* var >> const when lower(var) > upper(const) */
+			hist_selec =
+				1 - calc_hist_selectivity_scalar(rng_typcache, &const_upper,
+												 hist_lower, nhist, true);
+			break;
+
+		case OID_RANGE_OVERLAPS_RIGHT_MULTIRANGE_OP:
+		case OID_MULTIRANGE_OVERLAPS_RIGHT_RANGE_OP:
+		case OID_MULTIRANGE_OVERLAPS_RIGHT_MULTIRANGE_OP:
+			/* compare lower bounds */
+			hist_selec =
+				1 - calc_hist_selectivity_scalar(rng_typcache, &const_lower,
+												 hist_lower, nhist, false);
+			break;
+
+		case OID_RANGE_OVERLAPS_LEFT_MULTIRANGE_OP:
+		case OID_MULTIRANGE_OVERLAPS_LEFT_RANGE_OP:
+		case OID_MULTIRANGE_OVERLAPS_LEFT_MULTIRANGE_OP:
+			/* compare upper bounds */
+			hist_selec =
+				calc_hist_selectivity_scalar(rng_typcache, &const_upper,
+											 hist_upper, nhist, true);
+			break;
+
+		case OID_RANGE_OVERLAPS_MULTIRANGE_OP:
+		case OID_MULTIRANGE_OVERLAPS_RANGE_OP:
+		case OID_MULTIRANGE_OVERLAPS_MULTIRANGE_OP:
+		case OID_MULTIRANGE_CONTAINS_ELEM_OP:
+
+			/*
+			 * A && B <=> NOT (A << B OR A >> B).
+			 *
+			 * Since A << B and A >> B are mutually exclusive events we can
+			 * sum their probabilities to find probability of (A << B OR A >>
+			 * B).
+			 *
+			 * "multirange @> elem" is equivalent to "multirange &&
+			 * {[elem,elem]}". The caller already constructed the singular
+			 * range from the element constant, so just treat it the same as
+			 * &&.
+			 */
+			hist_selec =
+				calc_hist_selectivity_scalar(rng_typcache,
+								 			 &const_lower, hist_upper,
+											 nhist, false);
+			hist_selec +=
+				(1.0 - calc_hist_selectivity_scalar(rng_typcache,
+													&const_upper, hist_lower,
+													nhist, true));
+			hist_selec = 1.0 - hist_selec;
+			break;
+
+		case OID_MULTIRANGE_CONTAINS_RANGE_OP:
+		case OID_MULTIRANGE_CONTAINS_MULTIRANGE_OP:
+			hist_selec =
+				calc_hist_selectivity_contains(rng_typcache, &const_lower,
+											   &const_upper, hist_lower, nhist,
+											   lslot.values, lslot.nvalues);
+			break;
+
+		case OID_MULTIRANGE_RANGE_CONTAINED_OP:
+		case OID_MULTIRANGE_MULTIRANGE_CONTAINED_OP:
+			if (const_lower.infinite)
+			{
+				/*
+				 * Lower bound no longer matters. Just estimate the fraction
+				 * with an upper bound <= const upper bound
+				 */
+				hist_selec =
+					calc_hist_selectivity_scalar(rng_typcache, &const_upper,
+												 hist_upper, nhist, true);
+			}
+			else if (const_upper.infinite)
+			{
+				hist_selec =
+					1.0 - calc_hist_selectivity_scalar(rng_typcache, &const_lower,
+													   hist_lower, nhist, false);
+			}
+			else
+			{
+				hist_selec =
+					calc_hist_selectivity_contained(rng_typcache, &const_lower,
+													&const_upper, hist_lower, nhist,
+													lslot.values, lslot.nvalues);
+			}
+			break;
+
+		default:
+			elog(ERROR, "unknown multirange operator %u", operator);
+			hist_selec = -1.0;	/* keep compiler quiet */
+			break;
+	}
+
+	free_attstatsslot(&lslot);
+	free_attstatsslot(&hslot);
+
+	return hist_selec;
+}
+
+
+/*
+ * Look up the fraction of values less than (or equal, if 'equal' argument
+ * is true) a given const in a histogram of range bounds.
+ */
+static double
+calc_hist_selectivity_scalar(TypeCacheEntry *typcache, const RangeBound *constbound,
+							 const RangeBound *hist, int hist_nvalues, bool equal)
+{
+	Selectivity selec;
+	int			index;
+
+	/*
+	 * Find the histogram bin the given constant falls into. Estimate
+	 * selectivity as the number of preceding whole bins.
+	 */
+	index = rbound_bsearch(typcache, constbound, hist, hist_nvalues, equal);
+	selec = (Selectivity) (Max(index, 0)) / (Selectivity) (hist_nvalues - 1);
+
+	/* Adjust using linear interpolation within the bin */
+	if (index >= 0 && index < hist_nvalues - 1)
+		selec += get_position(typcache, constbound, &hist[index],
+							  &hist[index + 1]) / (Selectivity) (hist_nvalues - 1);
+
+	return selec;
+}
+
+/*
+ * Binary search on an array of range bounds. Returns greatest index of range
+ * bound in array which is less(less or equal) than given range bound. If all
+ * range bounds in array are greater or equal(greater) than given range bound,
+ * return -1. When "equal" flag is set conditions in brackets are used.
+ *
+ * This function is used in scalar operator selectivity estimation. Another
+ * goal of this function is to find a histogram bin where to stop
+ * interpolation of portion of bounds which are less or equal to given bound.
+ */
+static int
+rbound_bsearch(TypeCacheEntry *typcache, const RangeBound *value, const RangeBound *hist,
+			   int hist_length, bool equal)
+{
+	int			lower = -1,
+				upper = hist_length - 1,
+				cmp,
+				middle;
+
+	while (lower < upper)
+	{
+		middle = (lower + upper + 1) / 2;
+		cmp = range_cmp_bounds(typcache, &hist[middle], value);
+
+		if (cmp < 0 || (equal && cmp == 0))
+			lower = middle;
+		else
+			upper = middle - 1;
+	}
+	return lower;
+}
+
+
+/*
+ * Binary search on length histogram. Returns greatest index of range length in
+ * histogram which is less than (less than or equal) the given length value. If
+ * all lengths in the histogram are greater than (greater than or equal) the
+ * given length, returns -1.
+ */
+static int
+length_hist_bsearch(Datum *length_hist_values, int length_hist_nvalues,
+					double value, bool equal)
+{
+	int			lower = -1,
+				upper = length_hist_nvalues - 1,
+				middle;
+
+	while (lower < upper)
+	{
+		double		middleval;
+
+		middle = (lower + upper + 1) / 2;
+
+		middleval = DatumGetFloat8(length_hist_values[middle]);
+		if (middleval < value || (equal && middleval <= value))
+			lower = middle;
+		else
+			upper = middle - 1;
+	}
+	return lower;
+}
+
+/*
+ * Get relative position of value in histogram bin in [0,1] range.
+ */
+static float8
+get_position(TypeCacheEntry *typcache, const RangeBound *value, const RangeBound *hist1,
+			 const RangeBound *hist2)
+{
+	bool		has_subdiff = OidIsValid(typcache->rng_subdiff_finfo.fn_oid);
+	float8		position;
+
+	if (!hist1->infinite && !hist2->infinite)
+	{
+		float8		bin_width;
+
+		/*
+		 * Both bounds are finite. Assuming the subtype's comparison function
+		 * works sanely, the value must be finite, too, because it lies
+		 * somewhere between the bounds.  If it doesn't, arbitrarily return
+		 * 0.5.
+		 */
+		if (value->infinite)
+			return 0.5;
+
+		/* Can't interpolate without subdiff function */
+		if (!has_subdiff)
+			return 0.5;
+
+		/* Calculate relative position using subdiff function. */
+		bin_width = DatumGetFloat8(FunctionCall2Coll(&typcache->rng_subdiff_finfo,
+													 typcache->rng_collation,
+													 hist2->val,
+													 hist1->val));
+		if (isnan(bin_width) || bin_width <= 0.0)
+			return 0.5;			/* punt for NaN or zero-width bin */
+
+		position = DatumGetFloat8(FunctionCall2Coll(&typcache->rng_subdiff_finfo,
+													typcache->rng_collation,
+													value->val,
+													hist1->val))
+			/ bin_width;
+
+		if (isnan(position))
+			return 0.5;			/* punt for NaN from subdiff, Inf/Inf, etc */
+
+		/* Relative position must be in [0,1] range */
+		position = Max(position, 0.0);
+		position = Min(position, 1.0);
+		return position;
+	}
+	else if (hist1->infinite && !hist2->infinite)
+	{
+		/*
+		 * Lower bin boundary is -infinite, upper is finite. If the value is
+		 * -infinite, return 0.0 to indicate it's equal to the lower bound.
+		 * Otherwise return 1.0 to indicate it's infinitely far from the lower
+		 * bound.
+		 */
+		return ((value->infinite && value->lower) ? 0.0 : 1.0);
+	}
+	else if (!hist1->infinite && hist2->infinite)
+	{
+		/* same as above, but in reverse */
+		return ((value->infinite && !value->lower) ? 1.0 : 0.0);
+	}
+	else
+	{
+		/*
+		 * If both bin boundaries are infinite, they should be equal to each
+		 * other, and the value should also be infinite and equal to both
+		 * bounds. (But don't Assert that, to avoid crashing if a user creates
+		 * a datatype with a broken comparison function).
+		 *
+		 * Assume the value to lie in the middle of the infinite bounds.
+		 */
+		return 0.5;
+	}
+}
+
+
+/*
+ * Get relative position of value in a length histogram bin in [0,1] range.
+ */
+static double
+get_len_position(double value, double hist1, double hist2)
+{
+	if (!isinf(hist1) && !isinf(hist2))
+	{
+		/*
+		 * Both bounds are finite. The value should be finite too, because it
+		 * lies somewhere between the bounds. If it doesn't, just return
+		 * something.
+		 */
+		if (isinf(value))
+			return 0.5;
+
+		return 1.0 - (hist2 - value) / (hist2 - hist1);
+	}
+	else if (isinf(hist1) && !isinf(hist2))
+	{
+		/*
+		 * Lower bin boundary is -infinite, upper is finite. Return 1.0 to
+		 * indicate the value is infinitely far from the lower bound.
+		 */
+		return 1.0;
+	}
+	else if (isinf(hist1) && isinf(hist2))
+	{
+		/* same as above, but in reverse */
+		return 0.0;
+	}
+	else
+	{
+		/*
+		 * If both bin boundaries are infinite, they should be equal to each
+		 * other, and the value should also be infinite and equal to both
+		 * bounds. (But don't Assert that, to avoid crashing unnecessarily if
+		 * the caller messes up)
+		 *
+		 * Assume the value to lie in the middle of the infinite bounds.
+		 */
+		return 0.5;
+	}
+}
+
+/*
+ * Measure distance between two range bounds.
+ */
+static float8
+get_distance(TypeCacheEntry *typcache, const RangeBound *bound1, const RangeBound *bound2)
+{
+	bool		has_subdiff = OidIsValid(typcache->rng_subdiff_finfo.fn_oid);
+
+	if (!bound1->infinite && !bound2->infinite)
+	{
+		/*
+		 * Neither bound is infinite, use subdiff function or return default
+		 * value of 1.0 if no subdiff is available.
+		 */
+		if (has_subdiff)
+		{
+			float8		res;
+
+			res = DatumGetFloat8(FunctionCall2Coll(&typcache->rng_subdiff_finfo,
+												   typcache->rng_collation,
+												   bound2->val,
+												   bound1->val));
+			/* Reject possible NaN result, also negative result */
+			if (isnan(res) || res < 0.0)
+				return 1.0;
+			else
+				return res;
+		}
+		else
+			return 1.0;
+	}
+	else if (bound1->infinite && bound2->infinite)
+	{
+		/* Both bounds are infinite */
+		if (bound1->lower == bound2->lower)
+			return 0.0;
+		else
+			return get_float8_infinity();
+	}
+	else
+	{
+		/* One bound is infinite, the other is not */
+		return get_float8_infinity();
+	}
+}
+
+/*
+ * Calculate the average of function P(x), in the interval [length1, length2],
+ * where P(x) is the fraction of tuples with length < x (or length <= x if
+ * 'equal' is true).
+ */
+static double
+calc_length_hist_frac(Datum *length_hist_values, int length_hist_nvalues,
+					  double length1, double length2, bool equal)
+{
+	double		frac;
+	double		A,
+				B,
+				PA,
+				PB;
+	double		pos;
+	int			i;
+	double		area;
+
+	Assert(length2 >= length1);
+
+	if (length2 < 0.0)
+		return 0.0;				/* shouldn't happen, but doesn't hurt to check */
+
+	/* All lengths in the table are <= infinite. */
+	if (isinf(length2) && equal)
+		return 1.0;
+
+	/*----------
+	 * The average of a function between A and B can be calculated by the
+	 * formula:
+	 *
+	 *			B
+	 *	  1		/
+	 * -------	| P(x)dx
+	 *	B - A	/
+	 *			A
+	 *
+	 * The geometrical interpretation of the integral is the area under the
+	 * graph of P(x). P(x) is defined by the length histogram. We calculate
+	 * the area in a piecewise fashion, iterating through the length histogram
+	 * bins. Each bin is a trapezoid:
+	 *
+	 *		 P(x2)
+	 *		  /|
+	 *		 / |
+	 * P(x1)/  |
+	 *	   |   |
+	 *	   |   |
+	 *	---+---+--
+	 *	   x1  x2
+	 *
+	 * where x1 and x2 are the boundaries of the current histogram, and P(x1)
+	 * and P(x1) are the cumulative fraction of tuples at the boundaries.
+	 *
+	 * The area of each trapezoid is 1/2 * (P(x2) + P(x1)) * (x2 - x1)
+	 *
+	 * The first bin contains the lower bound passed by the caller, so we
+	 * use linear interpolation between the previous and next histogram bin
+	 * boundary to calculate P(x1). Likewise for the last bin: we use linear
+	 * interpolation to calculate P(x2). For the bins in between, x1 and x2
+	 * lie on histogram bin boundaries, so P(x1) and P(x2) are simply:
+	 * P(x1) =	  (bin index) / (number of bins)
+	 * P(x2) = (bin index + 1 / (number of bins)
+	 */
+
+	/* First bin, the one that contains lower bound */
+	i = length_hist_bsearch(length_hist_values, length_hist_nvalues, length1, equal);
+	if (i >= length_hist_nvalues - 1)
+		return 1.0;
+
+	if (i < 0)
+	{
+		i = 0;
+		pos = 0.0;
+	}
+	else
+	{
+		/* interpolate length1's position in the bin */
+		pos = get_len_position(length1,
+							   DatumGetFloat8(length_hist_values[i]),
+							   DatumGetFloat8(length_hist_values[i + 1]));
+	}
+	PB = (((double) i) + pos) / (double) (length_hist_nvalues - 1);
+	B = length1;
+
+	/*
+	 * In the degenerate case that length1 == length2, simply return
+	 * P(length1). This is not merely an optimization: if length1 == length2,
+	 * we'd divide by zero later on.
+	 */
+	if (length2 == length1)
+		return PB;
+
+	/*
+	 * Loop through all the bins, until we hit the last bin, the one that
+	 * contains the upper bound. (if lower and upper bounds are in the same
+	 * bin, this falls out immediately)
+	 */
+	area = 0.0;
+	for (; i < length_hist_nvalues - 1; i++)
+	{
+		double		bin_upper = DatumGetFloat8(length_hist_values[i + 1]);
+
+		/* check if we've reached the last bin */
+		if (!(bin_upper < length2 || (equal && bin_upper <= length2)))
+			break;
+
+		/* the upper bound of previous bin is the lower bound of this bin */
+		A = B;
+		PA = PB;
+
+		B = bin_upper;
+		PB = (double) i / (double) (length_hist_nvalues - 1);
+
+		/*
+		 * Add the area of this trapezoid to the total. The point of the
+		 * if-check is to avoid NaN, in the corner case that PA == PB == 0,
+		 * and B - A == Inf. The area of a zero-height trapezoid (PA == PB ==
+		 * 0) is zero, regardless of the width (B - A).
+		 */
+		if (PA > 0 || PB > 0)
+			area += 0.5 * (PB + PA) * (B - A);
+	}
+
+	/* Last bin */
+	A = B;
+	PA = PB;
+
+	B = length2;				/* last bin ends at the query upper bound */
+	if (i >= length_hist_nvalues - 1)
+		pos = 0.0;
+	else
+	{
+		if (DatumGetFloat8(length_hist_values[i]) == DatumGetFloat8(length_hist_values[i + 1]))
+			pos = 0.0;
+		else
+			pos = get_len_position(length2,
+						  		   DatumGetFloat8(length_hist_values[i]),
+						  		   DatumGetFloat8(length_hist_values[i + 1]));
+	}
+	PB = (((double) i) + pos) / (double) (length_hist_nvalues - 1);
+
+	if (PA > 0 || PB > 0)
+		area += 0.5 * (PB + PA) * (B - A);
+
+	/*
+	 * Ok, we have calculated the area, ie. the integral. Divide by width to
+	 * get the requested average.
+	 *
+	 * Avoid NaN arising from infinite / infinite. This happens at least if
+	 * length2 is infinite. It's not clear what the correct value would be in
+	 * that case, so 0.5 seems as good as any value.
+	 */
+	if (isinf(area) && isinf(length2))
+		frac = 0.5;
+	else
+		frac = area / (length2 - length1);
+
+	return frac;
+}
+
+/*
+ * Calculate selectivity of "var <@ const" operator, ie. estimate the fraction
+ * of multiranges that fall within the constant lower and upper bounds. This uses
+ * the histograms of range lower bounds and range lengths, on the assumption
+ * that the range lengths are independent of the lower bounds.
+ *
+ * The caller has already checked that constant lower and upper bounds are
+ * finite.
+ */
+static double
+calc_hist_selectivity_contained(TypeCacheEntry *typcache,
+								const RangeBound *lower, RangeBound *upper,
+								const RangeBound *hist_lower, int hist_nvalues,
+								Datum *length_hist_values, int length_hist_nvalues)
+{
+	int			i,
+				upper_index;
+	float8		prev_dist;
+	double		bin_width;
+	double		upper_bin_width;
+	double		sum_frac;
+
+	/*
+	 * Begin by finding the bin containing the upper bound, in the lower bound
+	 * histogram. Any range with a lower bound > constant upper bound can't
+	 * match, ie. there are no matches in bins greater than upper_index.
+	 */
+	upper->inclusive = !upper->inclusive;
+	upper->lower = true;
+	upper_index = rbound_bsearch(typcache, upper, hist_lower, hist_nvalues,
+								 false);
+
+	/*
+	 * If the upper bound value is below the histogram's lower limit, there
+	 * are no matches.
+	 */
+	if (upper_index < 0)
+		return 0.0;
+
+	/*
+	 * If the upper bound value is at or beyond the histogram's upper limit,
+	 * start our loop at the last actual bin, as though the upper bound were
+	 * within that bin; get_position will clamp its result to 1.0 anyway.
+	 * (This corresponds to assuming that the data population above the
+	 * histogram's upper limit is empty, exactly like what we just assumed for
+	 * the lower limit.)
+	 */
+	upper_index = Min(upper_index, hist_nvalues - 2);
+
+	/*
+	 * Calculate upper_bin_width, ie. the fraction of the (upper_index,
+	 * upper_index + 1) bin which is greater than upper bound of query range
+	 * using linear interpolation of subdiff function.
+	 */
+	upper_bin_width = get_position(typcache, upper,
+								   &hist_lower[upper_index],
+								   &hist_lower[upper_index + 1]);
+
+	/*
+	 * In the loop, dist and prev_dist are the distance of the "current" bin's
+	 * lower and upper bounds from the constant upper bound.
+	 *
+	 * bin_width represents the width of the current bin. Normally it is 1.0,
+	 * meaning a full width bin, but can be less in the corner cases: start
+	 * and end of the loop. We start with bin_width = upper_bin_width, because
+	 * we begin at the bin containing the upper bound.
+	 */
+	prev_dist = 0.0;
+	bin_width = upper_bin_width;
+
+	sum_frac = 0.0;
+	for (i = upper_index; i >= 0; i--)
+	{
+		double		dist;
+		double		length_hist_frac;
+		bool		final_bin = false;
+
+		/*
+		 * dist -- distance from upper bound of query range to lower bound of
+		 * the current bin in the lower bound histogram. Or to the lower bound
+		 * of the constant range, if this is the final bin, containing the
+		 * constant lower bound.
+		 */
+		if (range_cmp_bounds(typcache, &hist_lower[i], lower) < 0)
+		{
+			dist = get_distance(typcache, lower, upper);
+
+			/*
+			 * Subtract from bin_width the portion of this bin that we want to
+			 * ignore.
+			 */
+			bin_width -= get_position(typcache, lower, &hist_lower[i],
+									  &hist_lower[i + 1]);
+			if (bin_width < 0.0)
+				bin_width = 0.0;
+			final_bin = true;
+		}
+		else
+			dist = get_distance(typcache, &hist_lower[i], upper);
+
+		/*
+		 * Estimate the fraction of tuples in this bin that are narrow enough
+		 * to not exceed the distance to the upper bound of the query range.
+		 */
+		length_hist_frac = calc_length_hist_frac(length_hist_values,
+												 length_hist_nvalues,
+												 prev_dist, dist, true);
+
+		/*
+		 * Add the fraction of tuples in this bin, with a suitable length, to
+		 * the total.
+		 */
+		sum_frac += length_hist_frac * bin_width / (double) (hist_nvalues - 1);
+
+		if (final_bin)
+			break;
+
+		bin_width = 1.0;
+		prev_dist = dist;
+	}
+
+	return sum_frac;
+}
+
+/*
+ * Calculate selectivity of "var @> const" operator, ie. estimate the fraction
+ * of multiranges that contain the constant lower and upper bounds. This uses
+ * the histograms of range lower bounds and range lengths, on the assumption
+ * that the range lengths are independent of the lower bounds.
+ */
+static double
+calc_hist_selectivity_contains(TypeCacheEntry *typcache,
+							   const RangeBound *lower, const RangeBound *upper,
+							   const RangeBound *hist_lower, int hist_nvalues,
+							   Datum *length_hist_values, int length_hist_nvalues)
+{
+	int			i,
+				lower_index;
+	double		bin_width,
+				lower_bin_width;
+	double		sum_frac;
+	float8		prev_dist;
+
+	/* Find the bin containing the lower bound of query range. */
+	lower_index = rbound_bsearch(typcache, lower, hist_lower, hist_nvalues,
+								 true);
+
+	/*
+	 * If the lower bound value is below the histogram's lower limit, there
+	 * are no matches.
+	 */
+	if (lower_index < 0)
+		return 0.0;
+
+	/*
+	 * If the lower bound value is at or beyond the histogram's upper limit,
+	 * start our loop at the last actual bin, as though the upper bound were
+	 * within that bin; get_position will clamp its result to 1.0 anyway.
+	 * (This corresponds to assuming that the data population above the
+	 * histogram's upper limit is empty, exactly like what we just assumed for
+	 * the lower limit.)
+	 */
+	lower_index = Min(lower_index, hist_nvalues - 2);
+
+	/*
+	 * Calculate lower_bin_width, ie. the fraction of the of (lower_index,
+	 * lower_index + 1) bin which is greater than lower bound of query range
+	 * using linear interpolation of subdiff function.
+	 */
+	lower_bin_width = get_position(typcache, lower, &hist_lower[lower_index],
+								   &hist_lower[lower_index + 1]);
+
+	/*
+	 * Loop through all the lower bound bins, smaller than the query lower
+	 * bound. In the loop, dist and prev_dist are the distance of the
+	 * "current" bin's lower and upper bounds from the constant upper bound.
+	 * We begin from query lower bound, and walk backwards, so the first bin's
+	 * upper bound is the query lower bound, and its distance to the query
+	 * upper bound is the length of the query range.
+	 *
+	 * bin_width represents the width of the current bin. Normally it is 1.0,
+	 * meaning a full width bin, except for the first bin, which is only
+	 * counted up to the constant lower bound.
+	 */
+	prev_dist = get_distance(typcache, lower, upper);
+	sum_frac = 0.0;
+	bin_width = lower_bin_width;
+	for (i = lower_index; i >= 0; i--)
+	{
+		float8		dist;
+		double		length_hist_frac;
+
+		/*
+		 * dist -- distance from upper bound of query range to current value
+		 * of lower bound histogram or lower bound of query range (if we've
+		 * reach it).
+		 */
+		dist = get_distance(typcache, &hist_lower[i], upper);
+
+		/*
+		 * Get average fraction of length histogram which covers intervals
+		 * longer than (or equal to) distance to upper bound of query range.
+		 */
+		length_hist_frac =
+			1.0 - calc_length_hist_frac(length_hist_values,
+										length_hist_nvalues,
+										prev_dist, dist, false);
+
+		sum_frac += length_hist_frac * bin_width / (double) (hist_nvalues - 1);
+
+		bin_width = 1.0;
+		prev_dist = dist;
+	}
+
+	return sum_frac;
+}
diff --git a/src/backend/utils/adt/pg_upgrade_support.c b/src/backend/utils/adt/pg_upgrade_support.c
index 8a5953881ee..521ebaaab6d 100644
--- a/src/backend/utils/adt/pg_upgrade_support.c
+++ b/src/backend/utils/adt/pg_upgrade_support.c
@@ -52,6 +52,28 @@ binary_upgrade_set_next_array_pg_type_oid(PG_FUNCTION_ARGS)
 	PG_RETURN_VOID();
 }
 
+Datum
+binary_upgrade_set_next_multirange_pg_type_oid(PG_FUNCTION_ARGS)
+{
+	Oid			typoid = PG_GETARG_OID(0);
+
+	CHECK_IS_BINARY_UPGRADE;
+	binary_upgrade_next_mrng_pg_type_oid = typoid;
+
+	PG_RETURN_VOID();
+}
+
+Datum
+binary_upgrade_set_next_multirange_array_pg_type_oid(PG_FUNCTION_ARGS)
+{
+	Oid			typoid = PG_GETARG_OID(0);
+
+	CHECK_IS_BINARY_UPGRADE;
+	binary_upgrade_next_mrng_array_pg_type_oid = typoid;
+
+	PG_RETURN_VOID();
+}
+
 Datum
 binary_upgrade_set_next_heap_pg_class_oid(PG_FUNCTION_ARGS)
 {
diff --git a/src/backend/utils/adt/pseudotypes.c b/src/backend/utils/adt/pseudotypes.c
index 3d6b2f90935..99a93271fe1 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"
 
 
 /*
@@ -227,6 +228,43 @@ anycompatiblerange_out(PG_FUNCTION_ARGS)
 	return range_out(fcinfo);
 }
 
+/*
+ * anycompatiblemultirange
+ *
+ * We may as well allow output, since multirange_out will in fact work.
+ */
+PSEUDOTYPE_DUMMY_INPUT_FUNC(anycompatiblemultirange);
+
+Datum
+anycompatiblemultirange_out(PG_FUNCTION_ARGS)
+{
+	return multirange_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
  *
diff --git a/src/backend/utils/adt/rangetypes.c b/src/backend/utils/adt/rangetypes.c
index 01ad8bc240b..ba4caa2d36a 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
 {
@@ -64,10 +62,6 @@ static const char *range_parse_bound(const char *string, const char *ptr,
 static char *range_deparse(char flags, const char *lbound_str,
 						   const char *ubound_str);
 static char *range_bound_escape(const char *value);
-static Size datum_compute_size(Size sz, Datum datum, bool typbyval,
-							   char typalign, int16 typlen, char typstorage);
-static Pointer datum_write(Pointer ptr, Datum datum, bool typbyval,
-						   char typalign, int16 typlen, char typstorage);
 
 
 /*
@@ -431,19 +425,37 @@ range_lower(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
 	TypeCacheEntry *typcache;
+	bool		isnull;
+	Datum		result;
+
+	typcache = range_get_typcache(fcinfo, RangeTypeGetOid(r1));
+
+	result = range_lower_internal(typcache, r1, &isnull);
+
+	if (isnull)
+		PG_RETURN_NULL();
+	else
+		PG_RETURN_DATUM(result);
+}
+
+Datum
+range_lower_internal(TypeCacheEntry *typcache, const RangeType *r1, bool *isnull)
+{
 	RangeBound	lower;
 	RangeBound	upper;
 	bool		empty;
 
-	typcache = range_get_typcache(fcinfo, RangeTypeGetOid(r1));
-
 	range_deserialize(typcache, r1, &lower, &upper, &empty);
 
 	/* Return NULL if there's no finite lower bound */
 	if (empty || lower.infinite)
-		PG_RETURN_NULL();
+	{
+		*isnull = true;
+		return 0;
+	}
 
-	PG_RETURN_DATUM(lower.val);
+	*isnull = false;
+	return lower.val;
 }
 
 /* extract upper bound value */
@@ -452,19 +464,37 @@ range_upper(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
 	TypeCacheEntry *typcache;
+	bool		isnull;
+	Datum		result;
+
+	typcache = range_get_typcache(fcinfo, RangeTypeGetOid(r1));
+
+	result = range_upper_internal(typcache, r1, &isnull);
+
+	if (isnull)
+		PG_RETURN_NULL();
+	else
+		PG_RETURN_DATUM(result);
+}
+
+Datum
+range_upper_internal(TypeCacheEntry *typcache, const RangeType *r1, bool *isnull)
+{
 	RangeBound	lower;
 	RangeBound	upper;
 	bool		empty;
 
-	typcache = range_get_typcache(fcinfo, RangeTypeGetOid(r1));
-
 	range_deserialize(typcache, r1, &lower, &upper, &empty);
 
 	/* Return NULL if there's no finite upper bound */
 	if (empty || upper.infinite)
-		PG_RETURN_NULL();
+	{
+		*isnull = true;
+		return 0;
+	}
 
-	PG_RETURN_DATUM(upper.val);
+	*isnull = false;
+	return upper.val;
 }
 
 
@@ -475,9 +505,8 @@ Datum
 range_empty(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
-	char		flags = range_get_flags(r1);
 
-	PG_RETURN_BOOL(flags & RANGE_EMPTY);
+	PG_RETURN_BOOL(range_has_flag(r1, RANGE_EMPTY));
 }
 
 /* is lower bound inclusive? */
@@ -485,9 +514,8 @@ Datum
 range_lower_inc(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
-	char		flags = range_get_flags(r1);
 
-	PG_RETURN_BOOL(flags & RANGE_LB_INC);
+	PG_RETURN_BOOL(range_has_flag(r1, RANGE_LB_INC));
 }
 
 /* is upper bound inclusive? */
@@ -495,9 +523,8 @@ Datum
 range_upper_inc(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
-	char		flags = range_get_flags(r1);
 
-	PG_RETURN_BOOL(flags & RANGE_UB_INC);
+	PG_RETURN_BOOL(range_has_flag(r1, RANGE_UB_INC));
 }
 
 /* is lower bound infinite? */
@@ -505,9 +532,8 @@ Datum
 range_lower_inf(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
-	char		flags = range_get_flags(r1);
 
-	PG_RETURN_BOOL(flags & RANGE_LB_INF);
+	PG_RETURN_BOOL(range_has_flag(r1, RANGE_LB_INF));
 }
 
 /* is upper bound infinite? */
@@ -515,9 +541,8 @@ Datum
 range_upper_inf(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
-	char		flags = range_get_flags(r1);
 
-	PG_RETURN_BOOL(flags & RANGE_UB_INF);
+	PG_RETURN_BOOL(range_has_flag(r1, RANGE_UB_INF));
 }
 
 
@@ -957,7 +982,25 @@ range_minus(PG_FUNCTION_ARGS)
 {
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
 	RangeType  *r2 = PG_GETARG_RANGE_P(1);
+	RangeType  *ret;
 	TypeCacheEntry *typcache;
+
+	/* 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));
+
+	ret = range_minus_internal(typcache, r1, r2);
+	if (ret)
+		PG_RETURN_RANGE_P(ret);
+	else
+		PG_RETURN_NULL();
+}
+
+RangeType *
+range_minus_internal(TypeCacheEntry *typcache, RangeType *r1, RangeType *r2)
+{
 	RangeBound	lower1,
 				lower2;
 	RangeBound	upper1,
@@ -969,18 +1012,12 @@ range_minus(PG_FUNCTION_ARGS)
 				cmp_u1l2,
 				cmp_u1u2;
 
-	/* 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));
-
 	range_deserialize(typcache, r1, &lower1, &upper1, &empty1);
 	range_deserialize(typcache, r2, &lower2, &upper2, &empty2);
 
 	/* if either is empty, r1 is the correct answer */
 	if (empty1 || empty2)
-		PG_RETURN_RANGE_P(r1);
+		return r1;
 
 	cmp_l1l2 = range_cmp_bounds(typcache, &lower1, &lower2);
 	cmp_l1u2 = range_cmp_bounds(typcache, &lower1, &upper2);
@@ -993,34 +1030,34 @@ range_minus(PG_FUNCTION_ARGS)
 				 errmsg("result of range difference would not be contiguous")));
 
 	if (cmp_l1u2 > 0 || cmp_u1l2 < 0)
-		PG_RETURN_RANGE_P(r1);
+		return r1;
 
 	if (cmp_l1l2 >= 0 && cmp_u1u2 <= 0)
-		PG_RETURN_RANGE_P(make_empty_range(typcache));
+		return make_empty_range(typcache);
 
 	if (cmp_l1l2 <= 0 && cmp_u1l2 >= 0 && cmp_u1u2 <= 0)
 	{
 		lower2.inclusive = !lower2.inclusive;
 		lower2.lower = false;	/* it will become the upper bound */
-		PG_RETURN_RANGE_P(make_range(typcache, &lower1, &lower2, false));
+		return make_range(typcache, &lower1, &lower2, false);
 	}
 
 	if (cmp_l1l2 >= 0 && cmp_u1u2 >= 0 && cmp_l1u2 <= 0)
 	{
 		upper2.inclusive = !upper2.inclusive;
 		upper2.lower = true;	/* it will become the lower bound */
-		PG_RETURN_RANGE_P(make_range(typcache, &upper2, &upper1, false));
+		return make_range(typcache, &upper2, &upper1, false);
 	}
 
 	elog(ERROR, "unexpected case in range_minus");
-	PG_RETURN_NULL();
+	return NULL;
 }
 
 /*
  * Set union.  If strict is true, it is an error that the two input ranges
  * are not adjacent or overlapping.
  */
-static RangeType *
+RangeType *
 range_union_internal(TypeCacheEntry *typcache, RangeType *r1, RangeType *r2,
 					 bool strict)
 {
@@ -1101,6 +1138,19 @@ range_intersect(PG_FUNCTION_ARGS)
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
 	RangeType  *r2 = PG_GETARG_RANGE_P(1);
 	TypeCacheEntry *typcache;
+
+	/* 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));
+
+	PG_RETURN_RANGE_P(range_intersect_internal(typcache, r1, r2));
+}
+
+RangeType *
+range_intersect_internal(TypeCacheEntry *typcache, const RangeType *r1, const RangeType *r2)
+{
 	RangeBound	lower1,
 				lower2;
 	RangeBound	upper1,
@@ -1110,17 +1160,11 @@ range_intersect(PG_FUNCTION_ARGS)
 	RangeBound *result_lower;
 	RangeBound *result_upper;
 
-	/* 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));
-
 	range_deserialize(typcache, r1, &lower1, &upper1, &empty1);
 	range_deserialize(typcache, r2, &lower2, &upper2, &empty2);
 
-	if (empty1 || empty2 || !DatumGetBool(range_overlaps(fcinfo)))
-		PG_RETURN_RANGE_P(make_empty_range(typcache));
+	if (empty1 || empty2 || !range_overlaps_internal(typcache, r1, r2))
+		return make_empty_range(typcache);
 
 	if (range_cmp_bounds(typcache, &lower1, &lower2) >= 0)
 		result_lower = &lower1;
@@ -1132,9 +1176,81 @@ range_intersect(PG_FUNCTION_ARGS)
 	else
 		result_upper = &upper2;
 
-	PG_RETURN_RANGE_P(make_range(typcache, result_lower, result_upper, false));
+	return make_range(typcache, result_lower, result_upper, false);
+}
+
+/* range, range -> range, range functions */
+
+/*
+ * range_split_internal - if r2 intersects the middle of r1, leaving non-empty
+ * ranges on both sides, then return true and set output1 and output2 to the
+ * results of r1 - r2 (in order). Otherwise return false and don't set output1
+ * or output2. Neither input range should be empty.
+ */
+bool
+range_split_internal(TypeCacheEntry *typcache, const RangeType *r1, const RangeType *r2,
+					 RangeType **output1, RangeType **output2)
+{
+	RangeBound	lower1,
+				lower2;
+	RangeBound	upper1,
+				upper2;
+	bool		empty1,
+				empty2;
+
+	range_deserialize(typcache, r1, &lower1, &upper1, &empty1);
+	range_deserialize(typcache, r2, &lower2, &upper2, &empty2);
+
+	if (range_cmp_bounds(typcache, &lower1, &lower2) < 0 &&
+		range_cmp_bounds(typcache, &upper1, &upper2) > 0)
+	{
+		/*
+		 * Need to invert inclusive/exclusive for the lower2 and upper2
+		 * points. They can't be infinite though. We're allowed to overwrite
+		 * these RangeBounds since they only exist locally.
+		 */
+		lower2.inclusive = !lower2.inclusive;
+		lower2.lower = false;
+		upper2.inclusive = !upper2.inclusive;
+		upper2.lower = true;
+
+		*output1 = make_range(typcache, &lower1, &lower2, false);
+		*output2 = make_range(typcache, &upper2, &upper1, false);
+		return true;
+	}
+
+	return false;
 }
 
+/* range -> range aggregate functions */
+
+Datum
+range_intersect_agg_transfn(PG_FUNCTION_ARGS)
+{
+	MemoryContext aggContext;
+	Oid			rngtypoid;
+	TypeCacheEntry *typcache;
+	RangeType  *result;
+	RangeType  *current;
+
+	if (!AggCheckCallContext(fcinfo, &aggContext))
+		elog(ERROR, "range_intersect_agg_transfn called in non-aggregate context");
+
+	rngtypoid = get_fn_expr_argtype(fcinfo->flinfo, 1);
+	if (!type_is_range(rngtypoid))
+		ereport(ERROR, (errmsg("range_intersect_agg must be called with a range")));
+
+	typcache = range_get_typcache(fcinfo, rngtypoid);
+
+	/* strictness ensures these are non-null */
+	result = PG_GETARG_RANGE_P(0);
+	current = PG_GETARG_RANGE_P(1);
+
+	result = range_intersect_internal(typcache, result, current);
+	PG_RETURN_RANGE_P(result);
+}
+
+
 /* Btree support */
 
 /* btree comparator */
@@ -1144,12 +1260,6 @@ range_cmp(PG_FUNCTION_ARGS)
 	RangeType  *r1 = PG_GETARG_RANGE_P(0);
 	RangeType  *r2 = PG_GETARG_RANGE_P(1);
 	TypeCacheEntry *typcache;
-	RangeBound	lower1,
-				lower2;
-	RangeBound	upper1,
-				upper2;
-	bool		empty1,
-				empty2;
 	int			cmp;
 
 	check_stack_depth();		/* recurses when subtype is a range type */
@@ -1160,6 +1270,28 @@ range_cmp(PG_FUNCTION_ARGS)
 
 	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, const RangeType *r1, const 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);
 
@@ -1177,10 +1309,7 @@ range_cmp(PG_FUNCTION_ARGS)
 			cmp = range_cmp_bounds(typcache, &upper1, &upper2);
 	}
 
-	PG_FREE_IF_COPY(r1, 0);
-	PG_FREE_IF_COPY(r2, 1);
-
-	PG_RETURN_INT32(cmp);
+	return cmp;
 }
 
 /* inequality operators using the range_cmp function */
@@ -1218,13 +1347,10 @@ range_gt(PG_FUNCTION_ARGS)
 
 /* Hash support */
 
-/* hash a range value */
-Datum
-hash_range(PG_FUNCTION_ARGS)
+uint32
+hash_range_internal(TypeCacheEntry *typcache, const RangeType *r)
 {
-	RangeType  *r = PG_GETARG_RANGE_P(0);
 	uint32		result;
-	TypeCacheEntry *typcache;
 	TypeCacheEntry *scache;
 	RangeBound	lower;
 	RangeBound	upper;
@@ -1233,10 +1359,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 +1400,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, const RangeType *r, Datum seed)
+{
+	uint64		result;
 	TypeCacheEntry *scache;
 	RangeBound	lower;
 	RangeBound	upper;
@@ -1300,10 +1429,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 +1467,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));
 }
 
 /*
@@ -1769,6 +1912,21 @@ range_get_flags(const RangeType *range)
 	return *((char *) range + VARSIZE(range) - 1);
 }
 
+/*
+ * range_has_flag: set whether a range has a specific flag.
+ *
+ * This lets expose some of our functions that just check flags
+ * to the rest of the code base (like to multiranges)
+ * without writing full-fledged *_internal versions.
+ */
+bool
+range_has_flag(const RangeType *r1, char flag)
+{
+	char		flags = range_get_flags(r1);
+
+	return flags & flag;
+}
+
 /*
  * range_set_contain_empty: set the RANGE_CONTAIN_EMPTY bit in the value.
  *
@@ -1937,6 +2095,46 @@ range_cmp_bound_values(TypeCacheEntry *typcache, const RangeBound *b1,
 										   b1->val, b2->val));
 }
 
+/*
+ * qsort callback for sorting ranges.
+ *
+ * Two empty ranges compare equal; an empty range sorts to the left of any
+ * non-empty range.  Two non-empty ranges are sorted by lower bound first
+ * and by upper bound next.
+ */
+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.
  */
@@ -2395,7 +2593,7 @@ range_contains_elem_internal(TypeCacheEntry *typcache, const RangeType *r, Datum
  * Increment data_length by the space needed by the datum, including any
  * preceding alignment padding.
  */
-static Size
+Size
 datum_compute_size(Size data_length, Datum val, bool typbyval, char typalign,
 				   int16 typlen, char typstorage)
 {
@@ -2421,7 +2619,7 @@ datum_compute_size(Size data_length, Datum val, bool typbyval, char typalign,
  * Write the given datum beginning at ptr (after advancing to correct
  * alignment, if needed).  Return the pointer incremented by space used.
  */
-static Pointer
+Pointer
 datum_write(Pointer ptr, Datum datum, bool typbyval, char typalign,
 			int16 typlen, char typstorage)
 {
diff --git a/src/backend/utils/adt/rangetypes_typanalyze.c b/src/backend/utils/adt/rangetypes_typanalyze.c
index 57413b07201..a71b50ce6ab 100644
--- a/src/backend/utils/adt/rangetypes_typanalyze.c
+++ b/src/backend/utils/adt/rangetypes_typanalyze.c
@@ -30,11 +30,13 @@
 #include "utils/fmgrprotos.h"
 #include "utils/lsyscache.h"
 #include "utils/rangetypes.h"
+#include "utils/multirangetypes.h"
 
 static int	float8_qsort_cmp(const void *a1, const void *a2);
 static int	range_bound_qsort_cmp(const void *a1, const void *a2, void *arg);
 static void compute_range_stats(VacAttrStats *stats,
-								AnalyzeAttrFetchFunc fetchfunc, int samplerows, double totalrows);
+								AnalyzeAttrFetchFunc fetchfunc, int samplerows,
+								double totalrows);
 
 /*
  * range_typanalyze -- typanalyze function for range columns
@@ -60,6 +62,33 @@ range_typanalyze(PG_FUNCTION_ARGS)
 	PG_RETURN_BOOL(true);
 }
 
+/*
+ * multirange_typanalyze -- typanalyze function for multirange columns
+ *
+ * We do the same analysis as for ranges, but on the smallest range that
+ * completely includes the multirange.
+ */
+Datum
+multirange_typanalyze(PG_FUNCTION_ARGS)
+{
+	VacAttrStats *stats = (VacAttrStats *) PG_GETARG_POINTER(0);
+	TypeCacheEntry *typcache;
+	Form_pg_attribute attr = stats->attr;
+
+	/* Get information about multirange type; note column might be a domain */
+	typcache = multirange_get_typcache(fcinfo, getBaseType(stats->attrtypid));
+
+	if (attr->attstattarget < 0)
+		attr->attstattarget = default_statistics_target;
+
+	stats->compute_stats = compute_range_stats;
+	stats->extra_data = typcache;
+	/* same as in std_typanalyze */
+	stats->minrows = 300 * attr->attstattarget;
+
+	PG_RETURN_BOOL(true);
+}
+
 /*
  * Comparison function for sorting float8s, used for range lengths.
  */
@@ -98,7 +127,8 @@ compute_range_stats(VacAttrStats *stats, AnalyzeAttrFetchFunc fetchfunc,
 					int samplerows, double totalrows)
 {
 	TypeCacheEntry *typcache = (TypeCacheEntry *) stats->extra_data;
-	bool		has_subdiff = OidIsValid(typcache->rng_subdiff_finfo.fn_oid);
+	TypeCacheEntry *mltrng_typcache = NULL;
+	bool		has_subdiff;
 	int			null_cnt = 0;
 	int			non_null_cnt = 0;
 	int			non_empty_cnt = 0;
@@ -112,6 +142,15 @@ compute_range_stats(VacAttrStats *stats, AnalyzeAttrFetchFunc fetchfunc,
 			   *uppers;
 	double		total_width = 0;
 
+	if (typcache->typtype == TYPTYPE_MULTIRANGE)
+	{
+		mltrng_typcache = typcache;
+		typcache = typcache->rngtype;
+	}
+	else
+		Assert(typcache->typtype == TYPTYPE_RANGE);
+	has_subdiff = OidIsValid(typcache->rng_subdiff_finfo.fn_oid);
+
 	/* Allocate memory to hold range bounds and lengths of the sample ranges. */
 	lowers = (RangeBound *) palloc(sizeof(RangeBound) * samplerows);
 	uppers = (RangeBound *) palloc(sizeof(RangeBound) * samplerows);
@@ -123,6 +162,7 @@ compute_range_stats(VacAttrStats *stats, AnalyzeAttrFetchFunc fetchfunc,
 		Datum		value;
 		bool		isnull,
 					empty;
+		MultirangeType *multirange;
 		RangeType  *range;
 		RangeBound	lower,
 					upper;
@@ -145,8 +185,30 @@ compute_range_stats(VacAttrStats *stats, AnalyzeAttrFetchFunc fetchfunc,
 		total_width += VARSIZE_ANY(DatumGetPointer(value));
 
 		/* Get range and deserialize it for further analysis. */
-		range = DatumGetRangeTypeP(value);
-		range_deserialize(typcache, range, &lower, &upper, &empty);
+		if (mltrng_typcache != NULL)
+		{
+			/* Treat multiranges like a big range without gaps. */
+			multirange = DatumGetMultirangeTypeP(value);
+			if (!MultirangeIsEmpty(multirange))
+			{
+				RangeBound 	tmp;
+				multirange_get_bounds(typcache, multirange, 0,
+									  &lower, &tmp);
+				multirange_get_bounds(typcache, multirange,
+						  			  multirange->rangeCount - 1,
+									  &tmp, &upper);
+				empty = false;
+			}
+			else
+			{
+				empty = true;
+			}
+		}
+		else
+		{
+			range = DatumGetRangeTypeP(value);
+			range_deserialize(typcache, range, &lower, &upper, &empty);
+		}
 
 		if (!empty)
 		{
@@ -262,6 +324,13 @@ compute_range_stats(VacAttrStats *stats, AnalyzeAttrFetchFunc fetchfunc,
 			stats->stakind[slot_idx] = STATISTIC_KIND_BOUNDS_HISTOGRAM;
 			stats->stavalues[slot_idx] = bound_hist_values;
 			stats->numvalues[slot_idx] = num_hist;
+
+			/* Store ranges even if we're analyzing a multirange column */
+			stats->statypid[slot_idx] = typcache->type_id;
+			stats->statyplen[slot_idx] = typcache->typlen;
+			stats->statypbyval[slot_idx] = typcache->typbyval;
+			stats->statypalign[slot_idx] = 'd';
+
 			slot_idx++;
 		}
 
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index 204bcd03c0b..ad92636f7f1 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -2610,6 +2610,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
  *
@@ -3308,7 +3318,7 @@ get_namespace_name_or_temp(Oid nspid)
 		return get_namespace_name(nspid);
 }
 
-/*				---------- PG_RANGE CACHE ----------				 */
+/*				---------- PG_RANGE CACHES ----------				 */
 
 /*
  * get_range_subtype
@@ -3361,6 +3371,56 @@ get_range_collation(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->rngmultitypid;
+		ReleaseSysCache(tp);
+		return result;
+	}
+	else
+		return InvalidOid;
+}
+
+/*
+ * get_multirange_range
+ *		Returns the range type of a given multirange
+ *
+ * Returns InvalidOid if the type is not a multirange.
+ */
+Oid
+get_multirange_range(Oid multirangeOid)
+{
+	HeapTuple	tp;
+
+	tp = SearchSysCache1(RANGEMULTIRANGE, 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 809b27a038f..89f08a4f43c 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -650,6 +650,18 @@ static const struct cachedesc cacheinfo[] = {
 		},
 		64
 	},
+	{RangeRelationId,			/* RANGEMULTIRANGE */
+		RangeMultirangeTypidIndexId,
+		1,
+		{
+			Anum_pg_range_rngmultitypid,
+			0,
+			0,
+			0
+		},
+		4
+	},
+
 	{RangeRelationId,			/* RANGETYPE */
 		RangeTypidIndexId,
 		1,
diff --git a/src/backend/utils/cache/typcache.c b/src/backend/utils/cache/typcache.c
index 1e331098c0d..8c97ef39557 100644
--- a/src/backend/utils/cache/typcache.c
+++ b/src/backend/utils/cache/typcache.c
@@ -287,6 +287,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);
@@ -305,6 +306,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 TypeCacheTypCallback(Datum arg, int cacheid, uint32 hashvalue);
 static void TypeCacheOpcCallback(Datum arg, int cacheid, uint32 hashvalue);
@@ -557,8 +561,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))
@@ -595,7 +599,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))
@@ -620,7 +624,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))
@@ -645,7 +649,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))
@@ -695,6 +699,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;
@@ -737,6 +748,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;
@@ -816,6 +834,16 @@ lookup_type_cache(Oid type_id, int flags)
 			(void) lookup_type_cache(typentry->rngelemtype->type_id, 0);
 	}
 
+	/*
+	 * 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
 	 */
@@ -927,6 +955,22 @@ load_rangetype_info(TypeCacheEntry *typentry)
 	typentry->rngelemtype = lookup_type_cache(subtypeOid, 0);
 }
 
+/*
+ * load_multirangetype_info --- helper routine to set up multirange type
+ * information
+ */
+static void
+load_multirangetype_info(TypeCacheEntry *typentry)
+{
+	Oid			rangetypeOid;
+
+	rangetypeOid = get_multirange_range(typentry->type_id);
+	if (!OidIsValid(rangetypeOid))
+		elog(ERROR, "cache lookup failed for multirange type %u",
+			 typentry->type_id);
+
+	typentry->rngtype = lookup_type_cache(rangetypeOid, TYPECACHE_RANGE_INFO);
+}
 
 /*
  * load_domaintype_info --- helper routine to set up domain constraint info
@@ -1559,11 +1603,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
@@ -1606,6 +1650,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 9696c88f241..f6fa4ab2fb2 100644
--- a/src/backend/utils/fmgr/funcapi.c
+++ b/src/backend/utils/fmgr/funcapi.c
@@ -35,6 +35,7 @@ typedef struct polymorphic_actuals
 	Oid			anyelement_type;	/* anyelement mapping, if known */
 	Oid			anyarray_type;	/* anyarray mapping, if known */
 	Oid			anyrange_type;	/* anyrange mapping, if known */
+	Oid			anymultirange_type; /* anymultirange mapping, if known */
 } polymorphic_actuals;
 
 static void shutdown_MultiFuncCall(Datum arg);
@@ -46,6 +47,7 @@ static TypeFuncClass internal_get_result_type(Oid funcid,
 static void resolve_anyelement_from_others(polymorphic_actuals *actuals);
 static void resolve_anyarray_from_others(polymorphic_actuals *actuals);
 static void resolve_anyrange_from_others(polymorphic_actuals *actuals);
+static void resolve_anymultirange_from_others(polymorphic_actuals *actuals);
 static bool resolve_polymorphic_tupdesc(TupleDesc tupdesc,
 										oidvector *declared_args,
 										Node *call_expr);
@@ -503,6 +505,34 @@ resolve_anyelement_from_others(polymorphic_actuals *actuals)
 							format_type_be(range_base_type))));
 		actuals->anyelement_type = range_typelem;
 	}
+	else if (OidIsValid(actuals->anymultirange_type))
+	{
+		/* Use the element type based on the multirange type */
+		Oid			multirange_base_type;
+		Oid			multirange_typelem;
+		Oid			range_base_type;
+		Oid			range_typelem;
+
+		multirange_base_type = getBaseType(actuals->anymultirange_type);
+		multirange_typelem = get_multirange_range(multirange_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(multirange_base_type))));
+
+		range_base_type = getBaseType(multirange_typelem);
+		range_typelem = get_range_subtype(range_base_type);
+
+		if (!OidIsValid(range_typelem))
+			ereport(ERROR,
+					(errcode(ERRCODE_DATATYPE_MISMATCH),
+					 errmsg("argument declared %s does not contain a range type but type %s",
+							"anymultirange",
+							format_type_be(range_base_type))));
+		actuals->anyelement_type = range_typelem;
+	}
 	else
 		elog(ERROR, "could not determine polymorphic type");
 }
@@ -540,10 +570,53 @@ static void
 resolve_anyrange_from_others(polymorphic_actuals *actuals)
 {
 	/*
-	 * We can't deduce a range type from other polymorphic inputs, because
-	 * there may be multiple range types with the same subtype.
+	 * We can't deduce a range type from other polymorphic array or base
+	 * types, because there may be multiple range types with the same subtype,
+	 * but we can deduce it from a polymorphic multirange type.
+	 */
+	if (OidIsValid(actuals->anymultirange_type))
+	{
+		/* Use the element type based on the multirange type */
+		Oid			multirange_base_type = getBaseType(actuals->anymultirange_type);
+		Oid			multirange_typelem = get_multirange_range(multirange_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(multirange_base_type))));
+		actuals->anyrange_type = multirange_typelem;
+	}
+	else
+		elog(ERROR, "could not determine polymorphic type");
+}
+
+/*
+ * Resolve actual type of ANYMULTIRANGE from other polymorphic inputs
+ */
+static void
+resolve_anymultirange_from_others(polymorphic_actuals *actuals)
+{
+	/*
+	 * We can't deduce a multirange type from polymorphic array or base types,
+	 * because there may be multiple range types with the same subtype, but we
+	 * can deduce it from a polymorphic range type.
 	 */
-	elog(ERROR, "could not determine polymorphic type");
+	if (OidIsValid(actuals->anyrange_type))
+	{
+		Oid			range_base_type = getBaseType(actuals->anyrange_type);
+		Oid			multirange_typeid = get_range_multirange(range_base_type);
+
+		if (!OidIsValid(multirange_typeid))
+			ereport(ERROR,
+					(errcode(ERRCODE_UNDEFINED_OBJECT),
+					 errmsg("could not find multirange type for data type %s",
+							format_type_be(actuals->anyrange_type))));
+		actuals->anymultirange_type = multirange_typeid;
+	}
+	else
+		elog(ERROR, "could not determine polymorphic type");
 }
 
 /*
@@ -566,9 +639,11 @@ 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_anycompatible_result = false;
 	bool		have_anycompatible_array_result = false;
 	bool		have_anycompatible_range_result = false;
+	bool		have_anycompatible_multirange_result = false;
 	polymorphic_actuals poly_actuals;
 	polymorphic_actuals anyc_actuals;
 	Oid			anycollation = InvalidOid;
@@ -594,6 +669,10 @@ resolve_polymorphic_tupdesc(TupleDesc tupdesc, oidvector *declared_args,
 				have_polymorphic_result = true;
 				have_anyrange_result = true;
 				break;
+			case ANYMULTIRANGEOID:
+				have_polymorphic_result = true;
+				have_anymultirange_result = true;
+				break;
 			case ANYCOMPATIBLEOID:
 			case ANYCOMPATIBLENONARRAYOID:
 				have_polymorphic_result = true;
@@ -607,6 +686,10 @@ resolve_polymorphic_tupdesc(TupleDesc tupdesc, oidvector *declared_args,
 				have_polymorphic_result = true;
 				have_anycompatible_range_result = true;
 				break;
+			case ANYCOMPATIBLEMULTIRANGEOID:
+				have_polymorphic_result = true;
+				have_anycompatible_multirange_result = true;
+				break;
 			default:
 				break;
 		}
@@ -660,6 +743,15 @@ resolve_polymorphic_tupdesc(TupleDesc tupdesc, oidvector *declared_args,
 						return false;
 				}
 				break;
+			case ANYMULTIRANGEOID:
+				if (!OidIsValid(poly_actuals.anymultirange_type))
+				{
+					poly_actuals.anymultirange_type =
+						get_call_expr_argtype(call_expr, i);
+					if (!OidIsValid(poly_actuals.anymultirange_type))
+						return false;
+				}
+				break;
 			case ANYCOMPATIBLEOID:
 			case ANYCOMPATIBLENONARRAYOID:
 				if (!OidIsValid(anyc_actuals.anyelement_type))
@@ -688,6 +780,15 @@ resolve_polymorphic_tupdesc(TupleDesc tupdesc, oidvector *declared_args,
 						return false;
 				}
 				break;
+			case ANYCOMPATIBLEMULTIRANGEOID:
+				if (!OidIsValid(anyc_actuals.anymultirange_type))
+				{
+					anyc_actuals.anymultirange_type =
+						get_call_expr_argtype(call_expr, i);
+					if (!OidIsValid(anyc_actuals.anymultirange_type))
+						return false;
+				}
+				break;
 			default:
 				break;
 		}
@@ -703,6 +804,9 @@ resolve_polymorphic_tupdesc(TupleDesc tupdesc, oidvector *declared_args,
 	if (have_anyrange_result && !OidIsValid(poly_actuals.anyrange_type))
 		resolve_anyrange_from_others(&poly_actuals);
 
+	if (have_anymultirange_result && !OidIsValid(poly_actuals.anymultirange_type))
+		resolve_anymultirange_from_others(&poly_actuals);
+
 	if (have_anycompatible_result && !OidIsValid(anyc_actuals.anyelement_type))
 		resolve_anyelement_from_others(&anyc_actuals);
 
@@ -712,6 +816,9 @@ resolve_polymorphic_tupdesc(TupleDesc tupdesc, oidvector *declared_args,
 	if (have_anycompatible_range_result && !OidIsValid(anyc_actuals.anyrange_type))
 		resolve_anyrange_from_others(&anyc_actuals);
 
+	if (have_anycompatible_multirange_result && !OidIsValid(anyc_actuals.anymultirange_type))
+		resolve_anymultirange_from_others(&anyc_actuals);
+
 	/*
 	 * Identify the collation to use for polymorphic OUT parameters. (It'll
 	 * necessarily be the same for both anyelement and anyarray, likewise for
@@ -780,6 +887,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),
+								   poly_actuals.anymultirange_type,
+								   -1,
+								   0);
+				/* no collation should be attached to a multirange type */
+				break;
 			case ANYCOMPATIBLEOID:
 			case ANYCOMPATIBLENONARRAYOID:
 				TupleDescInitEntry(tupdesc, i + 1,
@@ -805,6 +920,14 @@ resolve_polymorphic_tupdesc(TupleDesc tupdesc, oidvector *declared_args,
 								   0);
 				/* no collation should be attached to a range type */
 				break;
+			case ANYCOMPATIBLEMULTIRANGEOID:
+				TupleDescInitEntry(tupdesc, i + 1,
+								   NameStr(att->attname),
+								   anyc_actuals.anymultirange_type,
+								   -1,
+								   0);
+				/* no collation should be attached to a multirange type */
+				break;
 			default:
 				break;
 		}
@@ -834,9 +957,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;
 	bool		have_anycompatible_result = false;
 	bool		have_anycompatible_array_result = false;
 	bool		have_anycompatible_range_result = false;
+	bool		have_anycompatible_multirange_result = false;
 	polymorphic_actuals poly_actuals;
 	polymorphic_actuals anyc_actuals;
 	int			inargno;
@@ -912,6 +1037,24 @@ resolve_polymorphic_argtypes(int numargs, Oid *argtypes, char *argmodes,
 					argtypes[i] = poly_actuals.anyrange_type;
 				}
 				break;
+			case ANYMULTIRANGEOID:
+				if (argmode == PROARGMODE_OUT || argmode == PROARGMODE_TABLE)
+				{
+					have_polymorphic_result = true;
+					have_anymultirange_result = true;
+				}
+				else
+				{
+					if (!OidIsValid(poly_actuals.anymultirange_type))
+					{
+						poly_actuals.anymultirange_type =
+							get_call_expr_argtype(call_expr, inargno);
+						if (!OidIsValid(poly_actuals.anymultirange_type))
+							return false;
+					}
+					argtypes[i] = poly_actuals.anymultirange_type;
+				}
+				break;
 			case ANYCOMPATIBLEOID:
 			case ANYCOMPATIBLENONARRAYOID:
 				if (argmode == PROARGMODE_OUT || argmode == PROARGMODE_TABLE)
@@ -967,6 +1110,24 @@ resolve_polymorphic_argtypes(int numargs, Oid *argtypes, char *argmodes,
 					argtypes[i] = anyc_actuals.anyrange_type;
 				}
 				break;
+			case ANYCOMPATIBLEMULTIRANGEOID:
+				if (argmode == PROARGMODE_OUT || argmode == PROARGMODE_TABLE)
+				{
+					have_polymorphic_result = true;
+					have_anycompatible_multirange_result = true;
+				}
+				else
+				{
+					if (!OidIsValid(anyc_actuals.anymultirange_type))
+					{
+						anyc_actuals.anymultirange_type =
+							get_call_expr_argtype(call_expr, inargno);
+						if (!OidIsValid(anyc_actuals.anymultirange_type))
+							return false;
+					}
+					argtypes[i] = anyc_actuals.anymultirange_type;
+				}
+				break;
 			default:
 				break;
 		}
@@ -988,6 +1149,9 @@ resolve_polymorphic_argtypes(int numargs, Oid *argtypes, char *argmodes,
 	if (have_anyrange_result && !OidIsValid(poly_actuals.anyrange_type))
 		resolve_anyrange_from_others(&poly_actuals);
 
+	if (have_anymultirange_result && !OidIsValid(poly_actuals.anymultirange_type))
+		resolve_anymultirange_from_others(&poly_actuals);
+
 	if (have_anycompatible_result && !OidIsValid(anyc_actuals.anyelement_type))
 		resolve_anyelement_from_others(&anyc_actuals);
 
@@ -997,6 +1161,9 @@ resolve_polymorphic_argtypes(int numargs, Oid *argtypes, char *argmodes,
 	if (have_anycompatible_range_result && !OidIsValid(anyc_actuals.anyrange_type))
 		resolve_anyrange_from_others(&anyc_actuals);
 
+	if (have_anycompatible_multirange_result && !OidIsValid(anyc_actuals.anymultirange_type))
+		resolve_anymultirange_from_others(&anyc_actuals);
+
 	/* And finally replace the output column types as needed */
 	for (i = 0; i < numargs; i++)
 	{
@@ -1013,6 +1180,9 @@ resolve_polymorphic_argtypes(int numargs, Oid *argtypes, char *argmodes,
 			case ANYRANGEOID:
 				argtypes[i] = poly_actuals.anyrange_type;
 				break;
+			case ANYMULTIRANGEOID:
+				argtypes[i] = poly_actuals.anymultirange_type;
+				break;
 			case ANYCOMPATIBLEOID:
 			case ANYCOMPATIBLENONARRAYOID:
 				argtypes[i] = anyc_actuals.anyelement_type;
@@ -1023,6 +1193,9 @@ resolve_polymorphic_argtypes(int numargs, Oid *argtypes, char *argmodes,
 			case ANYCOMPATIBLERANGEOID:
 				argtypes[i] = anyc_actuals.anyrange_type;
 				break;
+			case ANYCOMPATIBLEMULTIRANGEOID:
+				argtypes[i] = anyc_actuals.anymultirange_type;
+				break;
 			default:
 				break;
 		}
@@ -1052,6 +1225,7 @@ get_type_func_class(Oid typid, Oid *base_typeid)
 		case TYPTYPE_BASE:
 		case TYPTYPE_ENUM:
 		case TYPTYPE_RANGE:
+		case TYPTYPE_MULTIRANGE:
 			return TYPEFUNC_SCALAR;
 		case TYPTYPE_DOMAIN:
 			*base_typeid = typid = getBaseType(typid);
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 673a6703475..03023a382c0 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -272,7 +272,8 @@ static void dumpSearchPath(Archive *AH);
 static void binary_upgrade_set_type_oids_by_type_oid(Archive *fout,
 													 PQExpBuffer upgrade_buffer,
 													 Oid pg_type_oid,
-													 bool force_array_type);
+													 bool force_array_type,
+													 bool include_multirange_type);
 static void binary_upgrade_set_type_oids_by_rel_oid(Archive *fout,
 													PQExpBuffer upgrade_buffer, Oid pg_rel_oid);
 static void binary_upgrade_set_pg_class_oids(Archive *fout,
@@ -1653,7 +1654,7 @@ selectDumpableType(TypeInfo *tyinfo, Archive *fout)
 	}
 
 	/* skip auto-generated array types */
-	if (tyinfo->isArray)
+	if (tyinfo->isArray || tyinfo->isMultirange)
 	{
 		tyinfo->dobj.objType = DO_DUMMY_TYPE;
 
@@ -4461,16 +4462,49 @@ append_depends_on_extension(Archive *fout,
 	}
 }
 
+static Oid
+get_next_possible_free_pg_type_oid(Archive *fout, PQExpBuffer upgrade_query)
+{
+	/*
+	 * If the old version didn't assign an array type, but the new version
+	 * does, we must select an unused type OID to assign.  This currently only
+	 * happens for domains, when upgrading pre-v11 to v11 and up.
+	 *
+	 * Note: local state here is kind of ugly, but we must have some, since we
+	 * mustn't choose the same unused OID more than once.
+	 */
+	static Oid	next_possible_free_oid = FirstNormalObjectId;
+	PGresult   *res;
+	bool		is_dup;
+
+	do
+	{
+		++next_possible_free_oid;
+		printfPQExpBuffer(upgrade_query,
+						  "SELECT EXISTS(SELECT 1 "
+						  "FROM pg_catalog.pg_type "
+						  "WHERE oid = '%u'::pg_catalog.oid);",
+						  next_possible_free_oid);
+		res = ExecuteSqlQueryForSingleRow(fout, upgrade_query->data);
+		is_dup = (PQgetvalue(res, 0, 0)[0] == 't');
+		PQclear(res);
+	} while (is_dup);
+
+	return next_possible_free_oid;
+}
 
 static void
 binary_upgrade_set_type_oids_by_type_oid(Archive *fout,
 										 PQExpBuffer upgrade_buffer,
 										 Oid pg_type_oid,
-										 bool force_array_type)
+										 bool force_array_type,
+										 bool include_multirange_type)
 {
 	PQExpBuffer upgrade_query = createPQExpBuffer();
 	PGresult   *res;
 	Oid			pg_type_array_oid;
+	Oid			pg_type_multirange_oid;
+	Oid			pg_type_multirange_array_oid;
 
 	appendPQExpBufferStr(upgrade_buffer, "\n-- For binary upgrade, must preserve pg_type oid\n");
 	appendPQExpBuffer(upgrade_buffer,
@@ -4491,33 +4525,7 @@ binary_upgrade_set_type_oids_by_type_oid(Archive *fout,
 	PQclear(res);
 
 	if (!OidIsValid(pg_type_array_oid) && force_array_type)
-	{
-		/*
-		 * If the old version didn't assign an array type, but the new version
-		 * does, we must select an unused type OID to assign.  This currently
-		 * only happens for domains, when upgrading pre-v11 to v11 and up.
-		 *
-		 * Note: local state here is kind of ugly, but we must have some,
-		 * since we mustn't choose the same unused OID more than once.
-		 */
-		static Oid	next_possible_free_oid = FirstNormalObjectId;
-		bool		is_dup;
-
-		do
-		{
-			++next_possible_free_oid;
-			printfPQExpBuffer(upgrade_query,
-							  "SELECT EXISTS(SELECT 1 "
-							  "FROM pg_catalog.pg_type "
-							  "WHERE oid = '%u'::pg_catalog.oid);",
-							  next_possible_free_oid);
-			res = ExecuteSqlQueryForSingleRow(fout, upgrade_query->data);
-			is_dup = (PQgetvalue(res, 0, 0)[0] == 't');
-			PQclear(res);
-		} while (is_dup);
-
-		pg_type_array_oid = next_possible_free_oid;
-	}
+		pg_type_array_oid = get_next_possible_free_pg_type_oid(fout, upgrade_query);
 
 	if (OidIsValid(pg_type_array_oid))
 	{
@@ -4528,6 +4536,46 @@ binary_upgrade_set_type_oids_by_type_oid(Archive *fout,
 						  pg_type_array_oid);
 	}
 
+	/*
+	 * Pre-set the multirange type oid and its own array type oid.
+	 */
+	if (include_multirange_type)
+	{
+		if (fout->remoteVersion >= 130000)
+		{
+			appendPQExpBuffer(upgrade_query,
+							  "SELECT t.oid, t.typarray "
+							  "FROM pg_catalog.pg_type t "
+							  "JOIN pg_catalog.pg_range r "
+							  "ON t.oid = r.rngmultitypid "
+							  "WHERE r.rngtypid = '%u'::pg_catalog.oid;",
+							  pg_type_oid);
+
+			res = ExecuteSqlQueryForSingleRow(fout, upgrade_query->data);
+
+			pg_type_multirange_oid = atooid(PQgetvalue(res, 0, PQfnumber(res, "oid")));
+			pg_type_multirange_array_oid = atooid(PQgetvalue(res, 0, PQfnumber(res, "typarray")));
+
+			PQclear(res);
+		}
+		else
+		{
+			pg_type_multirange_oid = get_next_possible_free_pg_type_oid(fout, upgrade_query);
+			pg_type_multirange_array_oid = get_next_possible_free_pg_type_oid(fout, upgrade_query);
+		}
+
+		appendPQExpBufferStr(upgrade_buffer,
+							 "\n-- For binary upgrade, must preserve multirange pg_type oid\n");
+		appendPQExpBuffer(upgrade_buffer,
+						  "SELECT pg_catalog.binary_upgrade_set_next_multirange_pg_type_oid('%u'::pg_catalog.oid);\n\n",
+						  pg_type_multirange_oid);
+		appendPQExpBufferStr(upgrade_buffer,
+							 "\n-- For binary upgrade, must preserve multirange pg_type array oid\n");
+		appendPQExpBuffer(upgrade_buffer,
+						  "SELECT pg_catalog.binary_upgrade_set_next_multirange_array_pg_type_oid('%u'::pg_catalog.oid);\n\n",
+						  pg_type_multirange_array_oid);
+	}
+
 	destroyPQExpBuffer(upgrade_query);
 }
 
@@ -4552,7 +4600,7 @@ binary_upgrade_set_type_oids_by_rel_oid(Archive *fout,
 
 	if (OidIsValid(pg_type_oid))
 		binary_upgrade_set_type_oids_by_type_oid(fout, upgrade_buffer,
-												 pg_type_oid, false);
+												 pg_type_oid, false, false);
 
 	PQclear(upgrade_res);
 	destroyPQExpBuffer(upgrade_query);
@@ -5097,6 +5145,11 @@ getTypes(Archive *fout, int *numTypes)
 		else
 			tyinfo[i].isArray = false;
 
+		if (tyinfo[i].typtype == 'm')
+			tyinfo[i].isMultirange = true;
+		else
+			tyinfo[i].isMultirange = false;
+
 		/* Decide whether we want to dump it */
 		selectDumpableType(&tyinfo[i], fout);
 
@@ -8326,9 +8379,12 @@ getProcLangs(Archive *fout, int *numProcLangs)
 
 /*
  * getCasts
- *	  get basic information about every cast in the system
+ *	  get basic information about most casts in the system
  *
  * numCasts is set to the number of casts read in
+ *
+ * Skip casts from a range to its multirange, since we'll create those
+ * automatically.
  */
 CastInfo *
 getCasts(Archive *fout, int *numCasts)
@@ -8346,7 +8402,20 @@ getCasts(Archive *fout, int *numCasts)
 	int			i_castcontext;
 	int			i_castmethod;
 
-	if (fout->remoteVersion >= 80400)
+	if (fout->remoteVersion >= 130000)
+	{
+		appendPQExpBufferStr(query, "SELECT tableoid, oid, "
+							 "castsource, casttarget, castfunc, castcontext, "
+							 "castmethod "
+							 "FROM pg_cast c "
+							 "WHERE NOT EXISTS ( "
+							 "SELECT 1 FROM pg_range r "
+							 "WHERE c.castsource = r.rngtypid "
+							 "AND c.casttarget = r.rngmultitypid "
+							 ") "
+							 "ORDER BY 3,4");
+	}
+	else if (fout->remoteVersion >= 80400)
 	{
 		appendPQExpBufferStr(query, "SELECT tableoid, oid, "
 							 "castsource, casttarget, castfunc, castcontext, "
@@ -10496,7 +10565,7 @@ dumpEnumType(Archive *fout, TypeInfo *tyinfo)
 	if (dopt->binary_upgrade)
 		binary_upgrade_set_type_oids_by_type_oid(fout, q,
 												 tyinfo->dobj.catId.oid,
-												 false);
+												 false, false);
 
 	appendPQExpBuffer(q, "CREATE TYPE %s AS ENUM (",
 					  qualtypname);
@@ -10594,7 +10663,17 @@ dumpRangeType(Archive *fout, TypeInfo *tyinfo)
 	char	   *procname;
 
 	appendPQExpBuffer(query,
-					  "SELECT pg_catalog.format_type(rngsubtype, NULL) AS rngsubtype, "
+					  "SELECT ");
+
+	if (fout->remoteVersion >= 140000)
+		appendPQExpBuffer(query,
+						  "pg_catalog.format_type(rngmultitypid, NULL) AS rngmultitype, ");
+	else
+		appendPQExpBuffer(query,
+						  "NULL AS rngmultitype, ");
+
+	appendPQExpBuffer(query,
+					  "pg_catalog.format_type(rngsubtype, NULL) AS rngsubtype, "
 					  "opc.opcname AS opcname, "
 					  "(SELECT nspname FROM pg_catalog.pg_namespace nsp "
 					  "  WHERE nsp.oid = opc.opcnamespace) AS opcnsp, "
@@ -10622,7 +10701,7 @@ dumpRangeType(Archive *fout, TypeInfo *tyinfo)
 	if (dopt->binary_upgrade)
 		binary_upgrade_set_type_oids_by_type_oid(fout, q,
 												 tyinfo->dobj.catId.oid,
-												 false);
+												 false, true);
 
 	appendPQExpBuffer(q, "CREATE TYPE %s AS RANGE (",
 					  qualtypname);
@@ -10630,6 +10709,10 @@ dumpRangeType(Archive *fout, TypeInfo *tyinfo)
 	appendPQExpBuffer(q, "\n    subtype = %s",
 					  PQgetvalue(res, 0, PQfnumber(res, "rngsubtype")));
 
+	if (PQgetvalue(res, 0, PQfnumber(res, "rngmultitype")))
+		appendPQExpBuffer(q, ",\n    multirange_type_name = %s",
+						  PQgetvalue(res, 0, PQfnumber(res, "rngmultitype")));
+
 	/* print subtype_opclass only if not default for subtype */
 	if (PQgetvalue(res, 0, PQfnumber(res, "opcdefault"))[0] != 't')
 	{
@@ -10728,7 +10811,7 @@ dumpUndefinedType(Archive *fout, TypeInfo *tyinfo)
 	if (dopt->binary_upgrade)
 		binary_upgrade_set_type_oids_by_type_oid(fout, q,
 												 tyinfo->dobj.catId.oid,
-												 false);
+												 false, false);
 
 	appendPQExpBuffer(q, "CREATE TYPE %s;\n",
 					  qualtypname);
@@ -10913,7 +10996,7 @@ dumpBaseType(Archive *fout, TypeInfo *tyinfo)
 	if (dopt->binary_upgrade)
 		binary_upgrade_set_type_oids_by_type_oid(fout, q,
 												 tyinfo->dobj.catId.oid,
-												 false);
+												 false, false);
 
 	appendPQExpBuffer(q,
 					  "CREATE TYPE %s (\n"
@@ -11103,7 +11186,8 @@ dumpDomain(Archive *fout, TypeInfo *tyinfo)
 	if (dopt->binary_upgrade)
 		binary_upgrade_set_type_oids_by_type_oid(fout, q,
 												 tyinfo->dobj.catId.oid,
-												 true); /* force array type */
+												 true,		/* force array type */
+												 false);	/* force multirange type */
 
 	qtypname = pg_strdup(fmtId(tyinfo->dobj.name));
 	qualtypname = pg_strdup(fmtQualifiedDumpable(tyinfo));
@@ -11291,7 +11375,7 @@ dumpCompositeType(Archive *fout, TypeInfo *tyinfo)
 	{
 		binary_upgrade_set_type_oids_by_type_oid(fout, q,
 												 tyinfo->dobj.catId.oid,
-												 false);
+												 false, false);
 		binary_upgrade_set_pg_class_oids(fout, q, tyinfo->typrelid, false);
 	}
 
@@ -11565,7 +11649,7 @@ dumpShellType(Archive *fout, ShellTypeInfo *stinfo)
 	if (dopt->binary_upgrade)
 		binary_upgrade_set_type_oids_by_type_oid(fout, q,
 												 stinfo->baseType->dobj.catId.oid,
-												 false);
+												 false, false);
 
 	appendPQExpBuffer(q, "CREATE TYPE %s;\n",
 					  fmtQualifiedDumpable(stinfo));
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 317bb839702..d7f77f1d3e0 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -176,6 +176,7 @@ typedef struct _typeInfo
 	char		typrelkind;		/* 'r', 'v', 'c', etc */
 	char		typtype;		/* 'b', 'c', etc */
 	bool		isArray;		/* true if auto-generated array type */
+	bool		isMultirange;	/* true if auto-generated multirange type */
 	bool		isDefined;		/* true if typisdefined */
 	/* If needed, we'll create a "shell type" entry for it; link that here: */
 	struct _shellTypeInfo *shellType;	/* shell-type entry, or NULL */
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index ec636620601..11dc98ee0a5 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -1601,6 +1601,7 @@ my %tests = (
 			\QOPERATOR 3 dump_test.~~(integer,integer);\E\n.+
 			\QCREATE TYPE dump_test.range_type_custom AS RANGE (\E\n\s+
 			\Qsubtype = integer,\E\n\s+
+			\Qmultirange_type_name = dump_test.multirange_type_custom,\E\n\s+
 			\Qsubtype_opclass = dump_test.op_class_custom\E\n
 			\Q);\E
 			/xms,
@@ -1698,6 +1699,7 @@ my %tests = (
 		regexp => qr/^
 			\QCREATE TYPE dump_test.textrange AS RANGE (\E
 			\n\s+\Qsubtype = text,\E
+			\n\s+\Qmultirange_type_name = dump_test.textmultirange,\E
 			\n\s+\Qcollation = pg_catalog."C"\E
 			\n\);/xm,
 		like =>
diff --git a/src/include/access/tupmacs.h b/src/include/access/tupmacs.h
index 70157cf90ab..c262265b951 100644
--- a/src/include/access/tupmacs.h
+++ b/src/include/access/tupmacs.h
@@ -139,8 +139,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/binary_upgrade.h b/src/include/catalog/binary_upgrade.h
index 02fecb90f79..1ff62e9f8e6 100644
--- a/src/include/catalog/binary_upgrade.h
+++ b/src/include/catalog/binary_upgrade.h
@@ -16,6 +16,8 @@
 
 extern PGDLLIMPORT Oid binary_upgrade_next_pg_type_oid;
 extern PGDLLIMPORT Oid binary_upgrade_next_array_pg_type_oid;
+extern PGDLLIMPORT Oid binary_upgrade_next_mrng_pg_type_oid;
+extern PGDLLIMPORT Oid binary_upgrade_next_mrng_array_pg_type_oid;
 
 extern PGDLLIMPORT Oid binary_upgrade_next_heap_pg_class_oid;
 extern PGDLLIMPORT Oid binary_upgrade_next_index_pg_class_oid;
diff --git a/src/include/catalog/pg_aggregate.dat b/src/include/catalog/pg_aggregate.dat
index ffabe275c00..03bab74253d 100644
--- a/src/include/catalog/pg_aggregate.dat
+++ b/src/include/catalog/pg_aggregate.dat
@@ -538,6 +538,17 @@
   aggtransfn => 'bytea_string_agg_transfn',
   aggfinalfn => 'bytea_string_agg_finalfn', aggtranstype => 'internal' },
 
+# range
+{ aggfnoid => 'range_intersect_agg(anyrange)',
+  aggtransfn => 'range_intersect_agg_transfn',
+  aggcombinefn => 'range_intersect_agg_transfn', aggtranstype => 'anyrange' },
+{ aggfnoid => 'range_intersect_agg(anymultirange)',
+  aggtransfn => 'multirange_intersect_agg_transfn',
+  aggcombinefn => 'multirange_intersect_agg_transfn', aggtranstype => 'anymultirange' },
+{ aggfnoid => 'range_agg(anyrange)', aggtransfn => 'range_agg_transfn',
+  aggfinalfn => 'range_agg_finalfn', aggfinalextra => 't',
+  aggtranstype => 'internal' },
+
 # json
 { aggfnoid => 'json_agg', aggtransfn => 'json_agg_transfn',
   aggfinalfn => 'json_agg_finalfn', aggtranstype => 'internal' },
diff --git a/src/include/catalog/pg_amop.dat b/src/include/catalog/pg_amop.dat
index 2c899f19d92..78d7d2c5232 100644
--- a/src/include/catalog/pg_amop.dat
+++ b/src/include/catalog/pg_amop.dat
@@ -1374,6 +1374,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 db3e8c2d01a..9d423d535cd 100644
--- a/src/include/catalog/pg_amproc.dat
+++ b/src/include/catalog/pg_amproc.dat
@@ -285,6 +285,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' },
 { amprocfamily => 'btree/xid8_ops', amproclefttype => 'xid8',
@@ -457,6 +459,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',
@@ -1280,12 +1287,13 @@
   amprocrighttype => 'anyrange', amprocnum => '4',
   amproc => 'brin_inclusion_union' },
 { amprocfamily => 'brin/range_inclusion_ops', amproclefttype => 'anyrange',
-  amprocrighttype => 'anyrange', amprocnum => '11', amproc => 'range_merge' },
+  amprocrighttype => 'anyrange', amprocnum => '11',
+  amproc => 'range_merge(anyrange,anyrange)' },
 { amprocfamily => 'brin/range_inclusion_ops', amproclefttype => 'anyrange',
   amprocrighttype => 'anyrange', amprocnum => '13',
   amproc => 'range_contains' },
 { amprocfamily => 'brin/range_inclusion_ops', amproclefttype => 'anyrange',
-  amprocrighttype => 'anyrange', amprocnum => '14', amproc => 'isempty' },
+  amprocrighttype => 'anyrange', amprocnum => '14', amproc => 'isempty(anyrange)' },
 
 # minmax pg_lsn
 { amprocfamily => 'brin/pg_lsn_minmax_ops', amproclefttype => 'pg_lsn',
diff --git a/src/include/catalog/pg_cast.dat b/src/include/catalog/pg_cast.dat
index 5a58f50fbb2..d6ca624addc 100644
--- a/src/include/catalog/pg_cast.dat
+++ b/src/include/catalog/pg_cast.dat
@@ -530,4 +530,17 @@
 { castsource => 'jsonb', casttarget => 'float8', castfunc => 'float8(jsonb)',
   castcontext => 'e', castmethod => 'f' },
 
+# range to multirange
+{ castsource => 'int4range', casttarget => 'int4multirange', castfunc => 'int4multirange(int4range)',
+  castcontext => 'e', castmethod => 'f' },
+{ castsource => 'int8range', casttarget => 'int8multirange', castfunc => 'int8multirange(int8range)',
+  castcontext => 'e', castmethod => 'f' },
+{ castsource => 'numrange', casttarget => 'nummultirange', castfunc => 'nummultirange(numrange)',
+  castcontext => 'e', castmethod => 'f' },
+{ castsource => 'daterange', casttarget => 'datemultirange', castfunc => 'datemultirange(daterange)',
+  castcontext => 'e', castmethod => 'f' },
+{ castsource => 'tsrange', casttarget => 'tsmultirange', castfunc => 'tsmultirange(tsrange)',
+  castcontext => 'e', castmethod => 'f' },
+{ castsource => 'tstzrange', casttarget => 'tstzmultirange', castfunc => 'tstzmultirange(tstzrange)',
+  castcontext => 'e', castmethod => 'f' },
 ]
diff --git a/src/include/catalog/pg_opclass.dat b/src/include/catalog/pg_opclass.dat
index be5712692fe..4c5e475ff7b 100644
--- a/src/include/catalog/pg_opclass.dat
+++ b/src/include/catalog/pg_opclass.dat
@@ -232,6 +232,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 7ae19235ee2..f44a826e2e9 100644
--- a/src/include/catalog/pg_operator.dat
+++ b/src/include/catalog/pg_operator.dat
@@ -3277,5 +3277,174 @@
   oprname => '@@', oprleft => 'jsonb', oprright => 'jsonpath',
   oprresult => 'bool', oprcode => 'jsonb_path_match_opr(jsonb,jsonpath)',
   oprrest => 'matchingsel', oprjoin => 'matchingjoinsel' },
+{ 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 => 'multirangesel', 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 => 'multirangesel', 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 => 'multirangesel', 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 => 'multirangesel', oprjoin => 'scalargtjoinsel' },
+{ oid => '8073', oid_symbol => 'OID_RANGE_OVERLAPS_MULTIRANGE_OP',
+  descr => 'overlaps',
+  oprname => '&&', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '&&(anymultirange,anyrange)',
+  oprcode => 'range_overlaps_multirange', oprrest => 'multirangesel',
+  oprjoin => 'areajoinsel' },
+{ oid => '8074', oid_symbol => 'OID_MULTIRANGE_OVERLAPS_RANGE_OP', descr => 'contains',
+  oprname => '&&', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcom => '&&(anyrange,anymultirange)',
+  oprcode => 'multirange_overlaps_range', oprrest => 'multirangesel',
+  oprjoin => 'areajoinsel' },
+{ oid => '8075', oid_symbol => 'OID_MULTIRANGE_OVERLAPS_MULTIRANGE_OP', descr => 'contains',
+  oprname => '&&', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '&&(anymultirange,anymultirange)',
+  oprcode => 'multirange_overlaps_multirange', oprrest => 'multirangesel',
+  oprjoin => 'areajoinsel' },
+{ oid => '8064', oid_symbol => 'OID_MULTIRANGE_CONTAINS_ELEM_OP',
+  descr => 'contains',
+  oprname => '@>', oprleft => 'anymultirange', oprright => 'anyelement',
+  oprresult => 'bool', oprcom => '<@(anyelement,anymultirange)',
+  oprcode => 'multirange_contains_elem', oprrest => 'multirangesel',
+  oprjoin => 'contjoinsel' },
+{ oid => '8065', oid_symbol => 'OID_MULTIRANGE_CONTAINS_RANGE_OP', descr => 'contains',
+  oprname => '@>', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcom => '<@(anyrange,anymultirange)',
+  oprcode => 'multirange_contains_range', oprrest => 'multirangesel',
+  oprjoin => 'contjoinsel' },
+{ oid => '8066', oid_symbol => 'OID_MULTIRANGE_CONTAINS_MULTIRANGE_OP', descr => 'contains',
+  oprname => '@>', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '<@(anymultirange,anymultirange)',
+  oprcode => 'multirange_contains_multirange', oprrest => 'multirangesel',
+  oprjoin => 'contjoinsel' },
+{ oid => '8067', oid_symbol => 'OID_MULTIRANGE_ELEM_CONTAINED_OP',
+  descr => 'is contained by',
+  oprname => '<@', oprleft => 'anyelement', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '@>(anymultirange,anyelement)',
+  oprcode => 'elem_contained_by_multirange', oprrest => 'multirangesel',
+  oprjoin => 'contjoinsel' },
+{ oid => '8068', oid_symbol => 'OID_MULTIRANGE_RANGE_CONTAINED_OP',
+  descr => 'is contained by',
+  oprname => '<@', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '@>(anymultirange,anyrange)',
+  oprcode => 'range_contained_by_multirange', oprrest => 'multirangesel',
+  oprjoin => 'contjoinsel' },
+{ oid => '8069', oid_symbol => 'OID_MULTIRANGE_MULTIRANGE_CONTAINED_OP',
+  descr => 'is contained by',
+  oprname => '<@', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '@>(anymultirange,anymultirange)',
+  oprcode => 'multirange_contained_by_multirange', oprrest => 'multirangesel',
+  oprjoin => 'contjoinsel' },
+{ oid => '8106', oid_symbol => 'OID_RANGE_OVERLAPS_LEFT_MULTIRANGE_OP',
+  descr => 'overlaps or is left of',
+  oprname => '&<', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcode => 'range_overleft_multirange', oprrest => 'multirangesel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8107', oid_symbol => 'OID_MULTIRANGE_OVERLAPS_LEFT_RANGE_OP',
+  descr => 'overlaps or is left of',
+  oprname => '&<', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcode => 'multirange_overleft_range', oprrest => 'multirangesel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8108', oid_symbol => 'OID_MULTIRANGE_OVERLAPS_LEFT_MULTIRANGE_OP',
+  descr => 'overlaps or is left of',
+  oprname => '&<', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcode => 'multirange_overleft_multirange', oprrest => 'multirangesel',
+  oprjoin => 'scalargtjoinsel' },
+{ oid => '8109', oid_symbol => 'OID_RANGE_OVERLAPS_RIGHT_MULTIRANGE_OP',
+  descr => 'overlaps or is right of',
+  oprname => '&>', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcode => 'range_overright_multirange', oprrest => 'multirangesel',
+  oprjoin => 'scalargtjoinsel' },
+{ oid => '8110', oid_symbol => 'OID_MULTIRANGE_OVERLAPS_RIGHT_RANGE_OP',
+  descr => 'overlaps or is right of',
+  oprname => '&>', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcode => 'multirange_overright_range', oprrest => 'multirangesel',
+  oprjoin => 'scalargtjoinsel' },
+{ oid => '8111', oid_symbol => 'OID_MULTIRANGE_OVERLAPS_RIGHT_MULTIRANGE_OP',
+  descr => 'overlaps or is right of',
+  oprname => '&>', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcode => 'multirange_overright_multirange', oprrest => 'multirangesel',
+  oprjoin => 'scalargtjoinsel' },
+{ oid => '8091', oid_symbol => 'OID_RANGE_ADJACENT_MULTIRANGE_OP', descr => 'is adjacent to',
+  oprname => '-|-', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '-|-(anymultirange,anyrange)',
+  oprcode => 'range_adjacent_multirange', oprrest => 'matchingsel',
+  oprjoin => 'matchingjoinsel' },
+{ oid => '8092', oid_symbol => 'OID_MULTIRANGE_ADJACENT_RANGE_OP', descr => 'is adjacent to',
+  oprname => '-|-', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcom => '-|-(anyrange,anymultirange)',
+  oprcode => 'multirange_adjacent_range', oprrest => 'matchingsel',
+  oprjoin => 'matchingjoinsel' },
+{ oid => '8093', oid_symbol => 'OID_MULTIRANGE_ADJACENT_MULTIRANGE_OP', descr => 'is adjacent to',
+  oprname => '-|-', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '-|-(anymultirange,anymultirange)',
+  oprcode => 'multirange_adjacent_multirange', oprrest => 'matchingsel',
+  oprjoin => 'matchingjoinsel' },
+{ oid => '8117', descr => 'multirange union',
+  oprname => '+', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'anymultirange', oprcom => '+(anymultirange,anymultirange)',
+  oprcode => 'multirange_union' },
+{ oid => '8123', descr => 'multirange minus',
+  oprname => '-', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'anymultirange', oprcode => 'multirange_minus' },
+{ oid => '8129', descr => 'multirange intersect',
+  oprname => '*', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'anymultirange', oprcom => '*(anymultirange,anymultirange)',
+  oprcode => 'multirange_intersect' },
+{ oid => '8082', oid_symbol => 'OID_RANGE_LEFT_MULTIRANGE_OP', descr => 'is left of',
+  oprname => '<<', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '>>(anymultirange,anyrange)',
+  oprcode => 'range_before_multirange', oprrest => 'multirangesel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8083', oid_symbol => 'OID_MULTIRANGE_LEFT_RANGE_OP', descr => 'is left of',
+  oprname => '<<', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcom => '>>(anyrange,anymultirange)',
+  oprcode => 'multirange_before_range', oprrest => 'multirangesel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8084', oid_symbol => 'OID_MULTIRANGE_LEFT_MULTIRANGE_OP', descr => 'is left of',
+  oprname => '<<', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '>>(anymultirange,anymultirange)',
+  oprcode => 'multirange_before_multirange', oprrest => 'multirangesel',
+  oprjoin => 'scalarltjoinsel' },
+{ oid => '8085', oid_symbol => 'OID_RANGE_RIGHT_MULTIRANGE_OP', descr => 'is right of',
+  oprname => '>>', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '<<(anymultirange,anyrange)',
+  oprcode => 'range_after_multirange', oprrest => 'multirangesel',
+  oprjoin => 'scalargtjoinsel' },
+{ oid => '8086', oid_symbol => 'OID_MULTIRANGE_RIGHT_RANGE_OP', descr => 'is right of',
+  oprname => '>>', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcom => '<<(anyrange,anymultirange)',
+  oprcode => 'multirange_after_range', oprrest => 'multirangesel',
+  oprjoin => 'scalargtjoinsel' },
+{ oid => '8087', oid_symbol => 'OID_MULTIRANGE_RIGHT_MULTIRANGE_OP', descr => 'is right of',
+  oprname => '>>', oprleft => 'anymultirange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '<<(anymultirange,anymultirange)',
+  oprcode => 'multirange_after_multirange', oprrest => 'multirangesel',
+  oprjoin => 'scalargtjoinsel' },
 
 ]
diff --git a/src/include/catalog/pg_opfamily.dat b/src/include/catalog/pg_opfamily.dat
index 11c7ad2c145..3ff81026fc7 100644
--- a/src/include/catalog/pg_opfamily.dat
+++ b/src/include/catalog/pg_opfamily.dat
@@ -232,5 +232,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 e6c7b070f64..e86ef06b9f7 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -7253,6 +7253,14 @@
   proname => 'anycompatiblerange_out', provolatile => 's',
   prorettype => 'cstring', proargtypes => 'anycompatiblerange',
   prosrc => 'anycompatiblerange_out' },
+{ oid => '8154', descr => 'I/O',
+  proname => 'anycompatiblemultirange_in', provolatile => 's',
+  prorettype => 'anycompatiblemultirange', proargtypes => 'cstring oid int4',
+  prosrc => 'anycompatiblemultirange_in' },
+{ oid => '8155', descr => 'I/O',
+  proname => 'anycompatiblemultirange_out', provolatile => 's',
+  prorettype => 'cstring', proargtypes => 'anycompatiblemultirange',
+  prosrc => 'anycompatiblemultirange_out' },
 
 # tablesample method handlers
 { oid => '3313', descr => 'BERNOULLI tablesample method handler',
@@ -9873,6 +9881,10 @@
   descr => 'the smallest range which includes both of the given ranges',
   proname => 'range_merge', prorettype => 'anyrange',
   proargtypes => 'anyrange anyrange', prosrc => 'range_merge' },
+{ oid => '8143',
+  descr => 'the smallest range which includes the whole multirange',
+  proname => 'range_merge', prorettype => 'anyrange',
+  proargtypes => 'anymultirange', prosrc => 'range_merge_from_multirange' },
 { oid => '3868',
   proname => 'range_intersect', prorettype => 'anyrange',
   proargtypes => 'anyrange anyrange', prosrc => 'range_intersect' },
@@ -9922,6 +9934,13 @@
 { oid => '3169', descr => 'restriction selectivity for range operators',
   proname => 'rangesel', provolatile => 's', prorettype => 'float8',
   proargtypes => 'internal oid internal int4', prosrc => 'rangesel' },
+{ oid => '8133', descr => 'range aggregate by intersecting',
+  proname => 'range_intersect_agg_transfn', prorettype => 'anyrange',
+  proargtypes => 'anyrange anyrange', prosrc => 'range_intersect_agg_transfn'},
+{ oid => '8134', descr => 'range aggregate by intersecting',
+  proname => 'range_intersect_agg', prokind => 'a', proisstrict => 'f',
+  prorettype => 'anyrange', proargtypes => 'anyrange',
+  prosrc => 'aggregate_dummy'},
 
 { oid => '3914', descr => 'convert an int4 range to canonical form',
   proname => 'int4range_canonical', prorettype => 'int4range',
@@ -9990,6 +10009,261 @@
   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 => '8094', descr => 'lower bound of multirange',
+  proname => 'lower', prorettype => 'anyelement', proargtypes => 'anymultirange',
+  prosrc => 'multirange_lower' },
+{ oid => '8095', descr => 'upper bound of multirange',
+  proname => 'upper', prorettype => 'anyelement', proargtypes => 'anymultirange',
+  prosrc => 'multirange_upper' },
+{ oid => '8057', descr => 'is the multirange empty?',
+  proname => 'isempty', prorettype => 'bool',
+  proargtypes => 'anymultirange', prosrc => 'multirange_empty' },
+{ oid => '8096', descr => 'is the multirange\'s lower bound inclusive?',
+  proname => 'lower_inc', prorettype => 'bool', proargtypes => 'anymultirange',
+  prosrc => 'multirange_lower_inc' },
+{ oid => '8097', descr => 'is the multirange\'s upper bound inclusive?',
+  proname => 'upper_inc', prorettype => 'bool', proargtypes => 'anymultirange',
+  prosrc => 'multirange_upper_inc' },
+{ oid => '8098', descr => 'is the multirange\'s lower bound infinite?',
+  proname => 'lower_inf', prorettype => 'bool', proargtypes => 'anymultirange',
+  prosrc => 'multirange_lower_inf' },
+{ oid => '8099', descr => 'is the multirange\'s upper bound infinite?',
+  proname => 'upper_inf', prorettype => 'bool', proargtypes => 'anymultirange',
+  prosrc => 'multirange_upper_inf' },
+{ oid => '8008', descr => 'multirange typanalyze',
+  proname => 'multirange_typanalyze', provolatile => 's', prorettype => 'bool',
+  proargtypes => 'internal', prosrc => 'multirange_typanalyze' },
+{ oid => '8156', descr => 'restriction selectivity for multirange operators',
+  proname => 'multirangesel', provolatile => 's', prorettype => 'float8',
+  proargtypes => 'internal oid internal int4', prosrc => 'multirangesel' },
+{ oid => '8033',
+  proname => 'multirange_eq', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_eq' },
+{ oid => '8034',
+  proname => 'multirange_ne', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_ne' },
+{ oid => '8070',
+  proname => 'range_overlaps_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_overlaps_multirange' },
+{ oid => '8071',
+  proname => 'multirange_overlaps_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_overlaps_range' },
+{ oid => '8072',
+  proname => 'multirange_overlaps_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_overlaps_multirange' },
+{ oid => '8058',
+  proname => 'multirange_contains_elem', prorettype => 'bool',
+  proargtypes => 'anymultirange anyelement', prosrc => 'multirange_contains_elem' },
+{ oid => '8059',
+  proname => 'multirange_contains_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_contains_range' },
+{ oid => '8060',
+  proname => 'multirange_contains_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_contains_multirange' },
+{ oid => '8061',
+  proname => 'elem_contained_by_multirange', prorettype => 'bool',
+  proargtypes => 'anyelement anymultirange', prosrc => 'elem_contained_by_multirange' },
+{ oid => '8062',
+  proname => 'range_contained_by_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_contained_by_multirange' },
+{ oid => '8063',
+  proname => 'multirange_contained_by_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_contained_by_multirange' },
+{ oid => '8088',
+  proname => 'range_adjacent_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_adjacent_multirange' },
+{ oid => '8089',
+  proname => 'multirange_adjacent_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_adjacent_multirange' },
+{ oid => '8090',
+  proname => 'multirange_adjacent_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_adjacent_range' },
+{ oid => '8076',
+  proname => 'range_before_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_before_multirange' },
+{ oid => '8077',
+  proname => 'multirange_before_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_before_range' },
+{ oid => '8078',
+  proname => 'multirange_before_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_before_multirange' },
+{ oid => '8079',
+  proname => 'range_after_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_after_multirange' },
+{ oid => '8080',
+  proname => 'multirange_after_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_after_range' },
+{ oid => '8081',
+  proname => 'multirange_after_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_after_multirange' },
+{ oid => '8100',
+  proname => 'range_overleft_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_overleft_multirange' },
+{ oid => '8101',
+  proname => 'multirange_overleft_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_overleft_range' },
+{ oid => '8102',
+  proname => 'multirange_overleft_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_overleft_multirange' },
+{ oid => '8103',
+  proname => 'range_overright_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_overright_multirange' },
+{ oid => '8104',
+  proname => 'multirange_overright_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_overright_range' },
+{ oid => '8105',
+  proname => 'multirange_overright_multirange', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_overright_multirange' },
+{ oid => '8114',
+  proname => 'multirange_union', prorettype => 'anymultirange',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_union' },
+{ oid => '8120',
+  proname => 'multirange_minus', prorettype => 'anymultirange',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_minus' },
+{ oid => '8126',
+  proname => 'multirange_intersect', prorettype => 'anymultirange',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_intersect' },
+{ oid => '8022', descr => 'less-equal-greater',
+  proname => 'multirange_cmp', prorettype => 'int4',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_cmp' },
+{ oid => '8023',
+  proname => 'multirange_lt', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_lt' },
+{ oid => '8024',
+  proname => 'multirange_le', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_le' },
+{ oid => '8025',
+  proname => 'multirange_ge', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_ge' },
+{ oid => '8026',
+  proname => 'multirange_gt', prorettype => 'bool',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_gt' },
+{ oid => '8038', descr => 'hash a multirange',
+  proname => 'hash_multirange', prorettype => 'int4', proargtypes => 'anymultirange',
+  prosrc => 'hash_multirange' },
+{ oid => '8039', descr => 'hash a multirange',
+  proname => 'hash_multirange_extended', prorettype => 'int8',
+  proargtypes => 'anymultirange int8', prosrc => 'hash_multirange_extended' },
+
+{ oid => '8045', descr => 'int4multirange constructor',
+  proname => 'int4multirange', proisstrict => 'f',
+  prorettype => 'int4multirange', proargtypes => '',
+  prosrc => 'multirange_constructor0' },
+{ oid => '8146', descr => 'int4multirange constructor',
+  proname => 'int4multirange', proisstrict => 't',
+  prorettype => 'int4multirange', proargtypes => 'int4range',
+  prosrc => 'multirange_constructor1' },
+{ oid => '8046', descr => 'int4multirange constructor',
+  proname => 'int4multirange', provariadic => 'int4range', proisstrict => 'f',
+  prorettype => 'int4multirange', proargtypes => '_int4range',
+  proallargtypes => '{_int4range}', proargmodes => '{v}',
+  prosrc => 'multirange_constructor2' },
+{ oid => '8047', descr => 'nummultirange constructor',
+  proname => 'nummultirange', proisstrict => 'f',
+  prorettype => 'nummultirange', proargtypes => '',
+  prosrc => 'multirange_constructor0' },
+{ oid => '8147', descr => 'nummultirange constructor',
+  proname => 'nummultirange', proisstrict => 't',
+  prorettype => 'nummultirange', proargtypes => 'numrange',
+  prosrc => 'multirange_constructor1' },
+{ oid => '8048', descr => 'nummultirange constructor',
+  proname => 'nummultirange', provariadic => 'numrange', proisstrict => 'f',
+  prorettype => 'nummultirange', proargtypes => '_numrange',
+  proallargtypes => '{_numrange}', proargmodes => '{v}',
+  prosrc => 'multirange_constructor2' },
+{ oid => '8049', descr => 'tsmultirange constructor',
+  proname => 'tsmultirange', proisstrict => 'f',
+  prorettype => 'tsmultirange', proargtypes => '',
+  prosrc => 'multirange_constructor0' },
+{ oid => '8148', descr => 'tsmultirange constructor',
+  proname => 'tsmultirange', proisstrict => 't',
+  prorettype => 'tsmultirange', proargtypes => 'tsrange',
+  prosrc => 'multirange_constructor1' },
+{ oid => '8050', descr => 'tsmultirange constructor',
+  proname => 'tsmultirange', provariadic => 'tsrange', proisstrict => 'f',
+  prorettype => 'tsmultirange', proargtypes => '_tsrange',
+  proallargtypes => '{_tsrange}', proargmodes => '{v}',
+  prosrc => 'multirange_constructor2' },
+{ oid => '8051', descr => 'tstzmultirange constructor',
+  proname => 'tstzmultirange', proisstrict => 'f',
+  prorettype => 'tstzmultirange', proargtypes => '',
+  prosrc => 'multirange_constructor0' },
+{ oid => '8149', descr => 'tstzmultirange constructor',
+  proname => 'tstzmultirange', proisstrict => 't',
+  prorettype => 'tstzmultirange', proargtypes => 'tstzrange',
+  prosrc => 'multirange_constructor1' },
+{ oid => '8052', descr => 'tstzmultirange constructor',
+  proname => 'tstzmultirange', provariadic => 'tstzrange', proisstrict => 'f',
+  prorettype => 'tstzmultirange', proargtypes => '_tstzrange',
+  proallargtypes => '{_tstzrange}', proargmodes => '{v}',
+  prosrc => 'multirange_constructor2' },
+{ oid => '8053', descr => 'datemultirange constructor',
+  proname => 'datemultirange', proisstrict => 'f',
+  prorettype => 'datemultirange', proargtypes => '',
+  prosrc => 'multirange_constructor0' },
+{ oid => '8150', descr => 'datemultirange constructor',
+  proname => 'datemultirange', proisstrict => 't',
+  prorettype => 'datemultirange', proargtypes => 'daterange',
+  prosrc => 'multirange_constructor1' },
+{ oid => '8054', descr => 'datemultirange constructor',
+  proname => 'datemultirange', provariadic => 'daterange', proisstrict => 'f',
+  prorettype => 'datemultirange', proargtypes => '_daterange',
+  proallargtypes => '{_daterange}', proargmodes => '{v}',
+  prosrc => 'multirange_constructor2' },
+{ oid => '8055', descr => 'int8multirange constructor',
+  proname => 'int8multirange', proisstrict => 'f',
+  prorettype => 'int8multirange', proargtypes => '',
+  prosrc => 'multirange_constructor0' },
+{ oid => '8151', descr => 'int8multirange constructor',
+  proname => 'int8multirange', proisstrict => 't',
+  prorettype => 'int8multirange', proargtypes => 'int8range',
+  prosrc => 'multirange_constructor1' },
+{ oid => '8056', descr => 'int8multirange constructor',
+  proname => 'int8multirange', provariadic => 'int8range', proisstrict => 'f',
+  prorettype => 'int8multirange', proargtypes => '_int8range',
+  proallargtypes => '{_int8range}', proargmodes => '{v}',
+  prosrc => 'multirange_constructor2' },
+{ oid => '8152', descr => 'anymultirange cast',
+  proname => 'multirange', proisstrict => 't',
+  prorettype => 'anymultirange', proargtypes => 'anyrange',
+  prosrc => 'multirange_constructor1' },
+{ oid => '8130', descr => 'aggregate transition function',
+  proname => 'range_agg_transfn', proisstrict => 'f', prorettype => 'internal',
+  proargtypes => 'internal anyrange', prosrc => 'range_agg_transfn' },
+{ oid => '8131', descr => 'aggregate final function',
+  proname => 'range_agg_finalfn', proisstrict => 'f', prorettype => 'anymultirange',
+  proargtypes => 'internal anyrange', prosrc => 'range_agg_finalfn' },
+{ oid => '8132', descr => 'combine aggregate input into a multirange',
+  proname => 'range_agg', prokind => 'a', proisstrict => 'f',
+  prorettype => 'anymultirange', proargtypes => 'anyrange',
+  prosrc => 'aggregate_dummy' },
+{ oid => '8135', descr => 'range aggregate by intersecting',
+  proname => 'multirange_intersect_agg_transfn', prorettype => 'anymultirange',
+  proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_intersect_agg_transfn'},
+{ oid => '8136', descr => 'range aggregate by intersecting',
+  proname => 'range_intersect_agg', prokind => 'a', proisstrict => 'f',
+  prorettype => 'anymultirange', proargtypes => 'anymultirange',
+  prosrc => 'aggregate_dummy'},
+
 # date, time, timestamp constructors
 { oid => '3846', descr => 'construct date',
   proname => 'make_date', prorettype => 'date', proargtypes => 'int4 int4 int4',
@@ -10372,6 +10646,14 @@
   proname => 'binary_upgrade_set_next_array_pg_type_oid', provolatile => 'v',
   proparallel => 'r', prorettype => 'void', proargtypes => 'oid',
   prosrc => 'binary_upgrade_set_next_array_pg_type_oid' },
+{ oid => '8144', descr => 'for use by pg_upgrade',
+  proname => 'binary_upgrade_set_next_multirange_pg_type_oid', provolatile => 'v',
+  proparallel => 'r', prorettype => 'void', proargtypes => 'oid',
+  prosrc => 'binary_upgrade_set_next_multirange_pg_type_oid' },
+{ oid => '8145', descr => 'for use by pg_upgrade',
+  proname => 'binary_upgrade_set_next_multirange_array_pg_type_oid', provolatile => 'v',
+  proparallel => 'r', prorettype => 'void', proargtypes => 'oid',
+  prosrc => 'binary_upgrade_set_next_multirange_array_pg_type_oid' },
 { oid => '3586', descr => 'for use by pg_upgrade',
   proname => 'binary_upgrade_set_next_heap_pg_class_oid', provolatile => 'v',
   proparallel => 'r', prorettype => 'void', proargtypes => 'oid',
diff --git a/src/include/catalog/pg_range.dat b/src/include/catalog/pg_range.dat
index 479754c2455..10060255c95 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',
+  rngmultitypid => 'int4multirange' },
 { rngtypid => 'numrange', rngsubtype => 'numeric',
   rngsubopc => 'btree/numeric_ops', rngcanonical => '-',
-  rngsubdiff => 'numrange_subdiff' },
+  rngsubdiff => 'numrange_subdiff', rngmultitypid => 'nummultirange' },
 { rngtypid => 'tsrange', rngsubtype => 'timestamp',
   rngsubopc => 'btree/timestamp_ops', rngcanonical => '-',
-  rngsubdiff => 'tsrange_subdiff' },
+  rngsubdiff => 'tsrange_subdiff', rngmultitypid => 'tsmultirange' },
 { rngtypid => 'tstzrange', rngsubtype => 'timestamptz',
   rngsubopc => 'btree/timestamptz_ops', rngcanonical => '-',
-  rngsubdiff => 'tstzrange_subdiff' },
+  rngsubdiff => 'tstzrange_subdiff', rngmultitypid => 'tstzmultirange' },
 { rngtypid => 'daterange', rngsubtype => 'date', rngsubopc => 'btree/date_ops',
-  rngcanonical => 'daterange_canonical', rngsubdiff => 'daterange_subdiff' },
+  rngcanonical => 'daterange_canonical', rngsubdiff => 'daterange_subdiff',
+  rngmultitypid => 'datemultirange' },
 { rngtypid => 'int8range', rngsubtype => 'int8', rngsubopc => 'btree/int8_ops',
-  rngcanonical => 'int8range_canonical', rngsubdiff => 'int8range_subdiff' },
+  rngcanonical => 'int8range_canonical', rngsubdiff => 'int8range_subdiff',
+  rngmultitypid => 'int8multirange' },
 
 ]
diff --git a/src/include/catalog/pg_range.h b/src/include/catalog/pg_range.h
index c28bb57b6c9..07e6dbba667 100644
--- a/src/include/catalog/pg_range.h
+++ b/src/include/catalog/pg_range.h
@@ -34,6 +34,9 @@ CATALOG(pg_range,3541,RangeRelationId)
 	/* OID of range's element type (subtype) */
 	Oid			rngsubtype BKI_LOOKUP(pg_type);
 
+	/* OID of the range's multirange type */
+	Oid			rngmultitypid BKI_LOOKUP(pg_type);
+
 	/* collation for this range type, or 0 */
 	Oid			rngcollation BKI_DEFAULT(0);
 
@@ -57,13 +60,17 @@ typedef FormData_pg_range *Form_pg_range;
 DECLARE_UNIQUE_INDEX(pg_range_rngtypid_index, 3542, on pg_range using btree(rngtypid oid_ops));
 #define RangeTypidIndexId					3542
 
+DECLARE_UNIQUE_INDEX(pg_range_rngmultitypid_index, 8001, on pg_range using btree(rngmultitypid oid_ops));
+#define RangeMultirangeTypidIndexId			8001
+
+
 /*
  * prototypes for functions in pg_range.c
  */
 
 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 28240bdce39..da8c5a3e9b3 100644
--- a/src/include/catalog/pg_type.dat
+++ b/src/include/catalog/pg_type.dat
@@ -496,6 +496,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 => 'R', 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 => 'R', 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 => 'R', 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 => 'R', 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 => 'R', 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 => 'R', 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
@@ -626,5 +660,16 @@
   typtype => 'p', typcategory => 'P', typinput => 'anycompatiblerange_in',
   typoutput => 'anycompatiblerange_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' },
+{ oid => '8153',
+  descr => 'pseudo-type representing a multirange over a polymorphic common type',
+  typname => 'anycompatiblemultirange', typlen => '-1', typbyval => 'f',
+  typtype => 'p', typcategory => 'P', typinput => 'anycompatiblemultirange_in',
+  typoutput => 'anycompatiblemultirange_out', typreceive => '-', typsend => '-',
+  typalign => 'd', typstorage => 'x' },
 
 ]
diff --git a/src/include/catalog/pg_type.h b/src/include/catalog/pg_type.h
index 70563a6408b..545b789608a 100644
--- a/src/include/catalog/pg_type.h
+++ b/src/include/catalog/pg_type.h
@@ -276,6 +276,7 @@ DECLARE_UNIQUE_INDEX(pg_type_typname_nsp_index, 2704, on pg_type using btree(typ
 #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 */
 
@@ -317,13 +318,15 @@ DECLARE_UNIQUE_INDEX(pg_type_typname_nsp_index, 2704, on pg_type using btree(typ
 	 (typid) == ANYARRAYOID || \
 	 (typid) == ANYNONARRAYOID || \
 	 (typid) == ANYENUMOID || \
-	 (typid) == ANYRANGEOID)
+	 (typid) == ANYRANGEOID || \
+	 (typid) == ANYMULTIRANGEOID)
 
 #define IsPolymorphicTypeFamily2(typid)  \
 	((typid) == ANYCOMPATIBLEOID || \
 	 (typid) == ANYCOMPATIBLEARRAYOID || \
 	 (typid) == ANYCOMPATIBLENONARRAYOID || \
-	 (typid) == ANYCOMPATIBLERANGEOID)
+	 (typid) == ANYCOMPATIBLERANGEOID || \
+	 (typid) == ANYCOMPATIBLEMULTIRANGEOID)
 
 /* Is this a "true" array type?  (Requires fmgroids.h) */
 #define IsTrueArrayType(typeForm)  \
@@ -397,4 +400,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/commands/typecmds.h b/src/include/commands/typecmds.h
index 23130895af4..989914dfe11 100644
--- a/src/include/commands/typecmds.h
+++ b/src/include/commands/typecmds.h
@@ -29,6 +29,8 @@ extern ObjectAddress DefineRange(CreateRangeStmt *stmt);
 extern ObjectAddress AlterEnum(AlterEnumStmt *stmt);
 extern ObjectAddress DefineCompositeType(RangeVar *typevar, List *coldeflist);
 extern Oid	AssignTypeArrayOid(void);
+extern Oid	AssignTypeMultirangeOid(void);
+extern Oid	AssignTypeMultirangeArrayOid(void);
 
 extern ObjectAddress AlterDomainDefault(List *names, Node *defaultRaw);
 extern ObjectAddress AlterDomainNotNull(List *names, bool notNull);
diff --git a/src/include/utils/lsyscache.h b/src/include/utils/lsyscache.h
index a990d11ea86..e5ad7b95d17 100644
--- a/src/include/utils/lsyscache.h
+++ b/src/include/utils/lsyscache.h
@@ -161,6 +161,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);
@@ -190,6 +191,8 @@ 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_collation(Oid rangeOid);
+extern Oid	get_range_multirange(Oid rangeOid);
+extern Oid	get_multirange_range(Oid multirangeOid);
 extern Oid	get_index_column_opclass(Oid index_oid, int attno);
 extern bool get_index_isreplident(Oid index_oid);
 extern bool get_index_isvalid(Oid index_oid);
diff --git a/src/include/utils/multirangetypes.h b/src/include/utils/multirangetypes.h
new file mode 100644
index 00000000000..4cf72415701
--- /dev/null
+++ b/src/include/utils/multirangetypes.h
@@ -0,0 +1,116 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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"
+#include "utils/expandeddatum.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
+{
+	int32		vl_len_;		/* varlena header (do not touch directly!) */
+	Oid			multirangetypid;	/* multirange type's own OID */
+	uint32		rangeCount;		/* the number of ranges */
+
+	/*
+	 * Following the count are the range objects themselves, as ShortRangeType
+	 * structs. 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 these macros in preference to accessing these fields 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);
+extern bool multirange_contains_elem_internal(TypeCacheEntry *typcache, MultirangeType *mr,
+											  Datum elem);
+extern bool multirange_contains_range_internal(TypeCacheEntry *typcache, MultirangeType *mr,
+											   RangeType *r);
+extern bool multirange_contains_multirange_internal(TypeCacheEntry *typcache,
+													MultirangeType *mr1,
+													MultirangeType *mr2);
+extern bool range_overlaps_multirange_internal(TypeCacheEntry *typcache, RangeType *r,
+											   MultirangeType *mr);
+extern bool multirange_overlaps_multirange_internal(TypeCacheEntry *typcache,
+													MultirangeType *mr1,
+													MultirangeType *mr2);
+extern bool range_before_multirange_internal(TypeCacheEntry *typcache, RangeType *r,
+											 MultirangeType *mr);
+extern bool range_after_multirange_internal(TypeCacheEntry *typcache, RangeType *r,
+											MultirangeType *mr);
+extern bool range_adjacent_multirange_internal(TypeCacheEntry *typcache, RangeType *r,
+											MultirangeType *mr);
+extern bool multirange_before_multirange_internal(TypeCacheEntry *typcache,
+												  MultirangeType *mr1,
+												  MultirangeType *mr2);
+extern MultirangeType *multirange_minus_internal(Oid mltrngtypoid,
+												 TypeCacheEntry *rangetyp,
+												 int32 range_count1,
+												 RangeType **ranges1,
+												 int32 range_count2,
+												 RangeType **ranges2);
+extern MultirangeType *multirange_intersect_internal(Oid mltrngtypoid,
+													 TypeCacheEntry *rangetyp,
+													 int32 range_count1,
+													 RangeType **ranges1,
+													 int32 range_count2,
+													 RangeType **ranges2);
+
+/* assorted support functions */
+extern TypeCacheEntry *multirange_get_typcache(FunctionCallInfo fcinfo,
+											   Oid mltrngtypid);
+extern void multirange_deserialize(TypeCacheEntry *rangetyp,
+								   const 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);
+extern void multirange_get_bounds(TypeCacheEntry *rangetyp,
+								  const MultirangeType *multirange,
+								  uint32 i,
+								  RangeBound *lower, RangeBound *upper);
+extern RangeType *multirange_get_range(TypeCacheEntry *rangetyp,
+									   const MultirangeType *multirange, int i);
+
+#endif							/* MULTIRANGETYPES_H */
diff --git a/src/include/utils/rangetypes.h b/src/include/utils/rangetypes.h
index b77c41cf1b8..139e19c7d66 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)
 
@@ -92,13 +94,21 @@ typedef struct
  * prototypes for functions defined in rangetypes.c
  */
 
-extern bool range_contains_elem_internal(TypeCacheEntry *typcache, const RangeType *r, Datum val);
-
 /* internal versions of the above */
+extern int	range_cmp_internal(TypeCacheEntry *typcache, const RangeType *r1,
+							   const RangeType *r2);
+extern uint32 hash_range_internal(TypeCacheEntry *typcache, const RangeType *r);
+extern uint64 hash_range_extended_internal(TypeCacheEntry *typcache, const RangeType *r,
+										   Datum seed);
+extern Datum range_lower_internal(TypeCacheEntry *typcache, const RangeType *r1,
+								  bool *isnull);
+extern Datum range_upper_internal(TypeCacheEntry *typcache, const RangeType *r1,
+								  bool *isnull);
 extern bool range_eq_internal(TypeCacheEntry *typcache, const RangeType *r1,
 							  const RangeType *r2);
 extern bool range_ne_internal(TypeCacheEntry *typcache, const RangeType *r1,
 							  const RangeType *r2);
+extern bool range_contains_elem_internal(TypeCacheEntry *typcache, const RangeType *r, Datum val);
 extern bool range_contains_internal(TypeCacheEntry *typcache, const RangeType *r1,
 									const RangeType *r2);
 extern bool range_contained_by_internal(TypeCacheEntry *typcache, const RangeType *r1,
@@ -115,8 +125,18 @@ extern bool range_overleft_internal(TypeCacheEntry *typcache, const RangeType *r
 									const RangeType *r2);
 extern bool range_overright_internal(TypeCacheEntry *typcache, const RangeType *r1,
 									 const RangeType *r2);
+extern RangeType *range_union_internal(TypeCacheEntry *typcache, RangeType *r1,
+									   RangeType *r2, bool strict);
+extern RangeType *range_minus_internal(TypeCacheEntry *typcache, RangeType *r1,
+									   RangeType *r2);
+extern RangeType *range_intersect_internal(TypeCacheEntry *typcache, const RangeType *r1,
+										   const RangeType *r2);
 
 /* assorted support functions */
+extern Size datum_compute_size(Size sz, Datum datum, bool typbyval,
+							   char typalign, int16 typlen, char typstorage);
+extern Pointer datum_write(Pointer ptr, Datum datum, bool typbyval,
+						   char typalign, int16 typlen, char typstorage);
 extern TypeCacheEntry *range_get_typcache(FunctionCallInfo fcinfo,
 										  Oid rngtypid);
 extern RangeType *range_serialize(TypeCacheEntry *typcache, RangeBound *lower,
@@ -125,6 +145,7 @@ extern void range_deserialize(TypeCacheEntry *typcache, const RangeType *range,
 							  RangeBound *lower, RangeBound *upper,
 							  bool *empty);
 extern char range_get_flags(const RangeType *range);
+extern bool range_has_flag(const RangeType *range, char flag);
 extern void range_set_contain_empty(RangeType *range);
 extern RangeType *make_range(TypeCacheEntry *typcache, RangeBound *lower,
 							 RangeBound *upper, bool empty);
@@ -132,8 +153,12 @@ extern int	range_cmp_bounds(TypeCacheEntry *typcache, const RangeBound *b1,
 							 const RangeBound *b2);
 extern int	range_cmp_bound_values(TypeCacheEntry *typcache, const RangeBound *b1,
 								   const RangeBound *b2);
+extern int	range_compare(const void *key1, const void *key2, void *arg);
 extern bool bounds_adjacent(TypeCacheEntry *typcache, RangeBound bound1,
 							RangeBound bound2);
 extern RangeType *make_empty_range(TypeCacheEntry *typcache);
+extern bool range_split_internal(TypeCacheEntry *typcache, const RangeType *r1,
+								 const RangeType *r2, RangeType **output1,
+								 RangeType **output2);
 
 #endif							/* RANGETYPES_H */
diff --git a/src/include/utils/selfuncs.h b/src/include/utils/selfuncs.h
index 3a2cfb7efa6..68ffd7761f1 100644
--- a/src/include/utils/selfuncs.h
+++ b/src/include/utils/selfuncs.h
@@ -39,6 +39,9 @@
 /* default selectivity estimate for range inequalities "A > b AND A < c" */
 #define DEFAULT_RANGE_INEQ_SEL	0.005
 
+/* default selectivity estimate for multirange inequalities "A > b AND A < c" */
+#define DEFAULT_MULTIRANGE_INEQ_SEL	0.005
+
 /* default selectivity estimate for pattern-match operators such as LIKE */
 #define DEFAULT_MATCH_SEL	0.005
 
diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h
index f27b73d76d0..e8f393520a3 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -79,6 +79,7 @@ enum SysCacheIdentifier
 	PUBLICATIONOID,
 	PUBLICATIONREL,
 	PUBLICATIONRELMAP,
+	RANGEMULTIRANGE,
 	RANGETYPE,
 	RELNAMENSP,
 	RELOID,
diff --git a/src/include/utils/typcache.h b/src/include/utils/typcache.h
index 38c8fe01929..dd69a06342f 100644
--- a/src/include/utils/typcache.h
+++ b/src/include/utils/typcache.h
@@ -101,6 +101,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; /* multirange's range underlying type */
+
 	/*
 	 * Domain's base type and typmod if it's a domain type.  Zeroes if not
 	 * domain, or if information hasn't been requested.
@@ -128,22 +133,23 @@ typedef struct TypeCacheEntry
 } TypeCacheEntry;
 
 /* Bit flags to indicate which fields a given caller needs to have set */
-#define TYPECACHE_EQ_OPR			0x0001
-#define TYPECACHE_LT_OPR			0x0002
-#define TYPECACHE_GT_OPR			0x0004
-#define TYPECACHE_CMP_PROC			0x0008
-#define TYPECACHE_HASH_PROC			0x0010
-#define TYPECACHE_EQ_OPR_FINFO		0x0020
-#define TYPECACHE_CMP_PROC_FINFO	0x0040
-#define TYPECACHE_HASH_PROC_FINFO	0x0080
-#define TYPECACHE_TUPDESC			0x0100
-#define TYPECACHE_BTREE_OPFAMILY	0x0200
-#define TYPECACHE_HASH_OPFAMILY		0x0400
-#define TYPECACHE_RANGE_INFO		0x0800
-#define TYPECACHE_DOMAIN_BASE_INFO			0x1000
-#define TYPECACHE_DOMAIN_CONSTR_INFO		0x2000
-#define TYPECACHE_HASH_EXTENDED_PROC		0x4000
-#define TYPECACHE_HASH_EXTENDED_PROC_FINFO	0x8000
+#define TYPECACHE_EQ_OPR			0x00001
+#define TYPECACHE_LT_OPR			0x00002
+#define TYPECACHE_GT_OPR			0x00004
+#define TYPECACHE_CMP_PROC			0x00008
+#define TYPECACHE_HASH_PROC			0x00010
+#define TYPECACHE_EQ_OPR_FINFO		0x00020
+#define TYPECACHE_CMP_PROC_FINFO	0x00040
+#define TYPECACHE_HASH_PROC_FINFO	0x00080
+#define TYPECACHE_TUPDESC			0x00100
+#define TYPECACHE_BTREE_OPFAMILY	0x00200
+#define TYPECACHE_HASH_OPFAMILY		0x00400
+#define TYPECACHE_RANGE_INFO		0x00800
+#define TYPECACHE_DOMAIN_BASE_INFO			0x01000
+#define TYPECACHE_DOMAIN_CONSTR_INFO		0x02000
+#define TYPECACHE_HASH_EXTENDED_PROC		0x04000
+#define TYPECACHE_HASH_EXTENDED_PROC_FINFO	0x08000
+#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 555da952e1b..042deb2a96c 100644
--- a/src/pl/plpgsql/src/pl_comp.c
+++ b/src/pl/plpgsql/src/pl_comp.c
@@ -515,6 +515,8 @@ do_compile(FunctionCallInfo fcinfo,
 					else if (rettypeid == ANYRANGEOID ||
 							 rettypeid == ANYCOMPATIBLERANGEOID)
 						rettypeid = INT4RANGEOID;
+					else if (rettypeid == ANYMULTIRANGEOID)
+						rettypeid = INT4MULTIRANGEOID;
 					else		/* ANYELEMENT or ANYNONARRAY or ANYCOMPATIBLE */
 						rettypeid = INT4OID;
 					/* XXX what could we use for ANYENUM? */
@@ -2109,6 +2111,7 @@ build_datatype(HeapTuple typeTup, int32 typmod,
 		case TYPTYPE_BASE:
 		case TYPTYPE_ENUM:
 		case TYPTYPE_RANGE:
+		case TYPTYPE_MULTIRANGE:
 			typ->ttype = PLPGSQL_TTYPE_SCALAR;
 			break;
 		case TYPTYPE_COMPOSITE:
@@ -2526,6 +2529,9 @@ plpgsql_resolve_polymorphic_argtypes(int numargs,
 				case ANYCOMPATIBLERANGEOID:
 					argtypes[i] = INT4RANGEOID;
 					break;
+				case ANYMULTIRANGEOID:
+					argtypes[i] = INT4MULTIRANGEOID;
+					break;
 				default:
 					break;
 			}
diff --git a/src/test/regress/expected/dependency.out b/src/test/regress/expected/dependency.out
index b4a11b8aa9b..82327951487 100644
--- a/src/test/regress/expected/dependency.out
+++ b/src/test/regress/expected/dependency.out
@@ -140,6 +140,7 @@ owner of sequence deptest_a_seq
 owner of table deptest
 owner of function deptest_func()
 owner of type deptest_enum
+owner of type deptest_multirange
 owner of type deptest_range
 owner of table deptest2
 owner of sequence ss1
diff --git a/src/test/regress/expected/hash_func.out b/src/test/regress/expected/hash_func.out
index 827e69fc7ab..dca31365bcf 100644
--- a/src/test/regress/expected/hash_func.out
+++ b/src/test/regress/expected/hash_func.out
@@ -305,6 +305,19 @@ 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)
+
 CREATE TYPE hash_test_t1 AS (a int, b text);
 SELECT v as value, hash_record(v)::bit(32) as standard,
        hash_record_extended(v, 0)::bit(32) as extended0,
diff --git a/src/test/regress/expected/multirangetypes.out b/src/test/regress/expected/multirangetypes.out
new file mode 100644
index 00000000000..32d1e001836
--- /dev/null
+++ b/src/test/regress/expected/multirangetypes.out
@@ -0,0 +1,2466 @@
+-- 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)
+
+--
+-- test casts, both a built-in range type and a user-defined one:
+--
+select 'empty'::int4range::int4multirange;
+ int4multirange 
+----------------
+ {}
+(1 row)
+
+select int4range(1, 3)::int4multirange;
+ int4range 
+-----------
+ {[1,3)}
+(1 row)
+
+select int4range(1, null)::int4multirange;
+ int4range 
+-----------
+ {[1,)}
+(1 row)
+
+select int4range(null, null)::int4multirange;
+ int4range 
+-----------
+ {(,)}
+(1 row)
+
+select 'empty'::textrange::textmultirange;
+ textmultirange 
+----------------
+ {}
+(1 row)
+
+select textrange('a', 'c')::textmultirange;
+ textrange 
+-----------
+ {[a,c)}
+(1 row)
+
+select textrange('a', null)::textmultirange;
+ textrange 
+-----------
+ {[a,)}
+(1 row)
+
+select textrange(null, null)::textmultirange;
+ textrange 
+-----------
+ {(,)}
+(1 row)
+
+--
+-- create some test data and test the operators
+--
+CREATE TABLE nummultirange_test (nmr NUMMULTIRANGE);
+CREATE INDEX nummultirange_test_btree ON nummultirange_test(nmr);
+INSERT INTO nummultirange_test VALUES('{}');
+INSERT INTO nummultirange_test VALUES('{[,)}');
+INSERT INTO nummultirange_test VALUES('{[3,]}');
+INSERT INTO nummultirange_test VALUES('{[,), [3,]}');
+INSERT INTO nummultirange_test VALUES('{[, 5)}');
+INSERT INTO nummultirange_test VALUES(nummultirange());
+INSERT INTO nummultirange_test VALUES(nummultirange(variadic '{}'::numrange[]));
+INSERT INTO nummultirange_test VALUES(nummultirange(numrange(1.1, 2.2)));
+INSERT INTO nummultirange_test VALUES('{empty}');
+INSERT INTO nummultirange_test VALUES(nummultirange(numrange(1.7, 1.7, '[]'), numrange(1.7, 1.9)));
+INSERT INTO nummultirange_test VALUES(nummultirange(numrange(1.7, 1.7, '[]'), numrange(1.9, 2.1)));
+SELECT nmr, isempty(nmr), lower(nmr), upper(nmr) FROM nummultirange_test ORDER BY nmr;
+          nmr          | isempty | lower | upper 
+-----------------------+---------+-------+-------
+ {}                    | t       |       |      
+ {}                    | t       |       |      
+ {}                    | t       |       |      
+ {}                    | t       |       |      
+ {(,5)}                | f       |       |     5
+ {(,)}                 | f       |       |      
+ {(,)}                 | f       |       |      
+ {[1.1,2.2)}           | f       |   1.1 |   2.2
+ {[1.7,1.7],[1.9,2.1)} | f       |   1.7 |   2.1
+ {[1.7,1.9)}           | f       |   1.7 |   1.9
+ {[3,)}                | f       |     3 |      
+(11 rows)
+
+SELECT nmr, lower_inc(nmr), lower_inf(nmr), upper_inc(nmr), upper_inf(nmr) FROM nummultirange_test ORDER BY nmr;
+          nmr          | lower_inc | lower_inf | upper_inc | upper_inf 
+-----------------------+-----------+-----------+-----------+-----------
+ {}                    | f         | f         | f         | f
+ {}                    | f         | f         | f         | f
+ {}                    | f         | f         | f         | f
+ {}                    | f         | f         | f         | f
+ {(,5)}                | f         | t         | f         | f
+ {(,)}                 | f         | t         | f         | t
+ {(,)}                 | f         | t         | f         | t
+ {[1.1,2.2)}           | t         | f         | f         | f
+ {[1.7,1.7],[1.9,2.1)} | t         | f         | f         | f
+ {[1.7,1.9)}           | t         | f         | f         | f
+ {[3,)}                | t         | f         | f         | t
+(11 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr = '{}';
+ nmr 
+-----
+ {}
+ {}
+ {}
+ {}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr = '{(,5)}';
+  nmr   
+--------
+ {(,5)}
+(1 row)
+
+SELECT * FROM nummultirange_test WHERE nmr = '{[3,)}';
+  nmr   
+--------
+ {[3,)}
+(1 row)
+
+SELECT * FROM nummultirange_test WHERE nmr = '{[1.7,1.7]}';
+ nmr 
+-----
+(0 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr = '{[1.7,1.7],[1.9,2.1)}';
+          nmr          
+-----------------------
+ {[1.7,1.7],[1.9,2.1)}
+(1 row)
+
+SELECT * FROM nummultirange_test WHERE nmr < '{}';
+ nmr 
+-----
+(0 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr < '{[-1000.0, -1000.0]}';
+  nmr   
+--------
+ {}
+ {(,)}
+ {(,)}
+ {(,5)}
+ {}
+ {}
+ {}
+(7 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr < '{[0.0, 1.0]}';
+  nmr   
+--------
+ {}
+ {(,)}
+ {(,)}
+ {(,5)}
+ {}
+ {}
+ {}
+(7 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr < '{[1000.0, 1001.0]}';
+          nmr          
+-----------------------
+ {}
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+ {}
+ {}
+ {[1.1,2.2)}
+ {}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(11 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr <= '{}';
+ nmr 
+-----
+ {}
+ {}
+ {}
+ {}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr <= '{[3,)}';
+          nmr          
+-----------------------
+ {}
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+ {}
+ {}
+ {[1.1,2.2)}
+ {}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(11 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr >= '{}';
+          nmr          
+-----------------------
+ {}
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+ {}
+ {}
+ {[1.1,2.2)}
+ {}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(11 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr >= '{[3,)}';
+  nmr   
+--------
+ {[3,)}
+(1 row)
+
+SELECT * FROM nummultirange_test WHERE nmr > '{}';
+          nmr          
+-----------------------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+ {[1.1,2.2)}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(7 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr > '{[-1000.0, -1000.0]}';
+          nmr          
+-----------------------
+ {[3,)}
+ {[1.1,2.2)}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr > '{[0.0, 1.0]}';
+          nmr          
+-----------------------
+ {[3,)}
+ {[1.1,2.2)}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr > '{[1000.0, 1001.0]}';
+ nmr 
+-----
+(0 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr <> '{}';
+          nmr          
+-----------------------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+ {[1.1,2.2)}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(7 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr <> '{(,5)}';
+          nmr          
+-----------------------
+ {}
+ {(,)}
+ {[3,)}
+ {(,)}
+ {}
+ {}
+ {[1.1,2.2)}
+ {}
+ {[1.7,1.9)}
+ {[1.7,1.7],[1.9,2.1)}
+(10 rows)
+
+select nummultirange(numrange(2.0, 1.0));
+ERROR:  range lower bound must be less than or equal to range upper bound
+select nummultirange(numrange(5.0, 6.0), numrange(1.0, 2.0));
+     nummultirange     
+-----------------------
+ {[1.0,2.0),[5.0,6.0)}
+(1 row)
+
+analyze nummultirange_test;
+-- overlaps
+SELECT * FROM nummultirange_test WHERE range_overlaps_multirange(numrange(4.0, 4.2), nmr);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE numrange(4.0, 4.2) && nmr;
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE multirange_overlaps_range(nmr, numrange(4.0, 4.2));
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr && numrange(4.0, 4.2);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE multirange_overlaps_multirange(nmr, nummultirange(numrange(4.0, 4.2), numrange(6.0, 7.0)));
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr && nummultirange(numrange(4.0, 4.2), numrange(6.0, 7.0));
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr && nummultirange(numrange(6.0, 7.0));
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+(3 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr && nummultirange(numrange(6.0, 7.0), numrange(8.0, 9.0));
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+(3 rows)
+
+-- mr contains x
+SELECT * FROM nummultirange_test WHERE multirange_contains_elem(nmr, 4.0);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr @> 4.0;
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE multirange_contains_range(nmr, numrange(4.0, 4.2));
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr @> numrange(4.0, 4.2);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE multirange_contains_multirange(nmr, '{[4.0,4.2), [6.0, 8.0)}');
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+(3 rows)
+
+SELECT * FROM nummultirange_test WHERE nmr @> '{[4.0,4.2), [6.0, 8.0)}'::nummultirange;
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+(3 rows)
+
+-- x is contained by mr
+SELECT * FROM nummultirange_test WHERE elem_contained_by_multirange(4.0, nmr);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE 4.0 <@ nmr;
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE range_contained_by_multirange(numrange(4.0, 4.2), nmr);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE numrange(4.0, 4.2) <@ nmr;
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+ {(,5)}
+(4 rows)
+
+SELECT * FROM nummultirange_test WHERE multirange_contained_by_multirange('{[4.0,4.2), [6.0, 8.0)}', nmr);
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+(3 rows)
+
+SELECT * FROM nummultirange_test WHERE '{[4.0,4.2), [6.0, 8.0)}'::nummultirange <@ nmr;
+  nmr   
+--------
+ {(,)}
+ {[3,)}
+ {(,)}
+(3 rows)
+
+-- overlaps
+SELECT 'empty'::numrange && nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT 'empty'::numrange && nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() && 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) && 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() && nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() && nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) && nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) && nummultirange(numrange(1,2), numrange(7,8));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(7,8)) && nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) && nummultirange(numrange(1,2), numrange(3.5,8));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(3.5,8)) && numrange(3,4);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(3.5,8)) && nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+-- contains
+SELECT nummultirange() @> nummultirange();
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange() @> 'empty'::numrange;
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(null,null)) @> numrange(1,2);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(null,null)) @> numrange(null,2);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(null,null)) @> numrange(2,null);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(null,5)) @> numrange(null,3);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(null,5)) @> numrange(null,8);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(5,null)) @> numrange(8,null);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(5,null)) @> numrange(3,null);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,5)) @> numrange(8,9);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,5)) @> numrange(3,9);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,5)) @> numrange(1,4);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,5)) @> numrange(1,5);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(-4,-2), numrange(1,5)) @> numrange(1,5);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,5), numrange(8,9)) @> numrange(1,5);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,5), numrange(8,9)) @> numrange(6,7);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,5), numrange(6,9)) @> numrange(6,7);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[1,5)}'::nummultirange @> '{[1,5)}';
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[-4,-2), [1,5)}'::nummultirange @> '{[1,5)}';
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[1,5), [8,9)}'::nummultirange @> '{[1,5)}';
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[1,5), [8,9)}'::nummultirange @> '{[6,7)}';
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT '{[1,5), [6,9)}'::nummultirange @> '{[6,7)}';
+ ?column? 
+----------
+ t
+(1 row)
+
+-- is contained by
+SELECT nummultirange() <@ nummultirange();
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT 'empty'::numrange <@ nummultirange();
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,2) <@ nummultirange(numrange(null,null));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(null,2) <@ nummultirange(numrange(null,null));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(2,null) <@ nummultirange(numrange(null,null));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(null,3) <@ nummultirange(numrange(null,5));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(null,8) <@ nummultirange(numrange(null,5));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(8,null) <@ nummultirange(numrange(5,null));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(3,null) <@ nummultirange(numrange(5,null));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(8,9) <@ nummultirange(numrange(1,5));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(3,9) <@ nummultirange(numrange(1,5));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(1,4) <@ nummultirange(numrange(1,5));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,5) <@ nummultirange(numrange(1,5));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,5) <@ nummultirange(numrange(-4,-2), numrange(1,5));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,5) <@ nummultirange(numrange(1,5), numrange(8,9));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(6,7) <@ nummultirange(numrange(1,5), numrange(8,9));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(6,7) <@ nummultirange(numrange(1,5), numrange(6,9));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[1,5)}' <@ '{[1,5)}'::nummultirange;
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[1,5)}' <@ '{[-4,-2), [1,5)}'::nummultirange;
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[1,5)}' <@ '{[1,5), [8,9)}'::nummultirange;
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[6,7)}' <@ '{[1,5), [8,9)}'::nummultirange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT '{[6,7)}' <@ '{[1,5), [6,9)}'::nummultirange;
+ ?column? 
+----------
+ t
+(1 row)
+
+-- overleft
+SELECT 'empty'::numrange &< nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT 'empty'::numrange &< nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() &< 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) &< 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() &< nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) &< nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() &< nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(6,7) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(1,2) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,4) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,6) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(3.5,6) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(6,7)) &< numrange(3,4);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) &< numrange(3,4);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,4)) &< numrange(3,4);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,6)) &< numrange(3,4);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3.5,6)) &< numrange(3,4);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(6,7)) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,4)) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,6)) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3.5,6)) &< nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+-- overright
+SELECT nummultirange() &> 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) &> 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT 'empty'::numrange &> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT 'empty'::numrange &> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() &> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() &> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) &> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> numrange(6,7);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> numrange(1,2);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> numrange(1,4);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> numrange(1,6);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> numrange(3.5,6);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(3,4) &> nummultirange(numrange(6,7));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(3,4) &> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(3,4) &> nummultirange(numrange(1,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(3,4) &> nummultirange(numrange(1,6));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(3,4) &> nummultirange(numrange(3.5,6));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(6,7));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(1,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(1,6));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(3.5,6));
+ ?column? 
+----------
+ f
+(1 row)
+
+-- meets
+SELECT 'empty'::numrange -|- nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT 'empty'::numrange -|- nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() -|- 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() -|- nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange() -|- nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(1,2) -|- nummultirange(numrange(2,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,2) -|- nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- numrange(2,4);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- numrange(3,4);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- nummultirange(numrange(2,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(5,6)) -|- nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(5,6)) -|- nummultirange(numrange(6,7));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(5,6)) -|- nummultirange(numrange(8,9));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) -|- nummultirange(numrange(2,4), numrange(6,7));
+ ?column? 
+----------
+ t
+(1 row)
+
+-- strictly left
+select 'empty'::numrange << nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(1,2) << nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(1,2) << nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+select numrange(1,2) << nummultirange(numrange(0,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(1,2) << nummultirange(numrange(0,4), numrange(7,8));
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange() << 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange() << numrange(1,2);
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(3,4)) << numrange(3,6);
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(0,2)) << numrange(3,6);
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange(numrange(0,2), numrange(7,8)) << numrange(3,6);
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(-4,-2), numrange(0,2)) << numrange(3,6);
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange() << nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange() << nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(1,2)) << nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(1,2)) << nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(1,2)) << nummultirange(numrange(3,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange(numrange(1,2)) << nummultirange(numrange(3,4), numrange(7,8));
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange(numrange(1,2), numrange(4,5)) << nummultirange(numrange(3,4), numrange(7,8));
+ ?column? 
+----------
+ f
+(1 row)
+
+-- strictly right
+select nummultirange() >> 'empty'::numrange;
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange() >> numrange(1,2);
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(3,4)) >> numrange(1,2);
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange(numrange(0,4)) >> numrange(1,2);
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(0,4), numrange(7,8)) >> numrange(1,2);
+ ?column? 
+----------
+ f
+(1 row)
+
+select 'empty'::numrange >> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(1,2) >> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(3,6) >> nummultirange(numrange(3,4));
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(3,6) >> nummultirange(numrange(0,2));
+ ?column? 
+----------
+ t
+(1 row)
+
+select numrange(3,6) >> nummultirange(numrange(0,2), numrange(7,8));
+ ?column? 
+----------
+ f
+(1 row)
+
+select numrange(3,6) >> nummultirange(numrange(-4,-2), numrange(0,2));
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange() >> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(1,2)) >> nummultirange();
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange() >> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(1,2)) >> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ f
+(1 row)
+
+select nummultirange(numrange(3,4)) >> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange(numrange(3,4), numrange(7,8)) >> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ t
+(1 row)
+
+select nummultirange(numrange(3,4), numrange(7,8)) >> nummultirange(numrange(1,2), numrange(4,5));
+ ?column? 
+----------
+ f
+(1 row)
+
+-- union
+SELECT nummultirange() + nummultirange();
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange() + nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) + nummultirange();
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) + nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) + nummultirange(numrange(2,4));
+ ?column? 
+----------
+ {[1,4)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) + nummultirange(numrange(3,4));
+   ?column?    
+---------------
+ {[1,2),[3,4)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) + nummultirange(numrange(2,4));
+ ?column? 
+----------
+ {[1,5)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) + nummultirange(numrange(3,4));
+   ?column?    
+---------------
+ {[1,2),[3,5)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) + nummultirange(numrange(0,9));
+ ?column? 
+----------
+ {[0,9)}
+(1 row)
+
+-- merge
+SELECT range_merge(nummultirange());
+ range_merge 
+-------------
+ empty
+(1 row)
+
+SELECT range_merge(nummultirange(numrange(1,2)));
+ range_merge 
+-------------
+ [1,2)
+(1 row)
+
+SELECT range_merge(nummultirange(numrange(1,2), numrange(7,8)));
+ range_merge 
+-------------
+ [1,8)
+(1 row)
+
+-- minus
+SELECT nummultirange() - nummultirange();
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange() - nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) - nummultirange();
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(3,4)) - nummultirange();
+   ?column?    
+---------------
+ {[1,2),[3,4)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) - nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) - nummultirange(numrange(2,4));
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) - nummultirange(numrange(3,4));
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {[2,4)}
+(1 row)
+
+SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(2,3));
+   ?column?    
+---------------
+ {[1,2),[3,4)}
+(1 row)
+
+SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(0,8));
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(0,2));
+ ?column? 
+----------
+ {[2,4)}
+(1 row)
+
+SELECT nummultirange(numrange(1,8)) - nummultirange(numrange(0,2), numrange(3,4));
+   ?column?    
+---------------
+ {[2,3),[4,8)}
+(1 row)
+
+SELECT nummultirange(numrange(1,8)) - nummultirange(numrange(2,3), numrange(5,null));
+   ?column?    
+---------------
+ {[1,2),[3,5)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(-2,0));
+   ?column?    
+---------------
+ {[1,2),[4,5)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(2,4));
+   ?column?    
+---------------
+ {[1,2),[4,5)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(3,5));
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(0,9));
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange(numrange(1,3), numrange(4,5)) - nummultirange(numrange(2,9));
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(8,9));
+   ?column?    
+---------------
+ {[1,2),[4,5)}
+(1 row)
+
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(-2,0), numrange(8,9));
+   ?column?    
+---------------
+ {[1,2),[4,5)}
+(1 row)
+
+-- intersection
+SELECT nummultirange() * nummultirange();
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange() * nummultirange(numrange(1,2));
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT nummultirange(numrange(1,2)) * nummultirange();
+ ?column? 
+----------
+ {}
+(1 row)
+
+SELECT '{[1,3)}'::nummultirange * '{[1,5)}'::nummultirange;
+ ?column? 
+----------
+ {[1,3)}
+(1 row)
+
+SELECT '{[1,3)}'::nummultirange * '{[0,5)}'::nummultirange;
+ ?column? 
+----------
+ {[1,3)}
+(1 row)
+
+SELECT '{[1,3)}'::nummultirange * '{[0,2)}'::nummultirange;
+ ?column? 
+----------
+ {[1,2)}
+(1 row)
+
+SELECT '{[1,3)}'::nummultirange * '{[2,5)}'::nummultirange;
+ ?column? 
+----------
+ {[2,3)}
+(1 row)
+
+SELECT '{[1,4)}'::nummultirange * '{[2,3)}'::nummultirange;
+ ?column? 
+----------
+ {[2,3)}
+(1 row)
+
+SELECT '{[1,4)}'::nummultirange * '{[0,2), [3,5)}'::nummultirange;
+   ?column?    
+---------------
+ {[1,2),[3,4)}
+(1 row)
+
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[0,8), [9,12)}'::nummultirange;
+       ?column?       
+----------------------
+ {[1,4),[7,8),[9,10)}
+(1 row)
+
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[9,12)}'::nummultirange;
+ ?column? 
+----------
+ {[9,10)}
+(1 row)
+
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[-5,-4), [5,6), [9,12)}'::nummultirange;
+ ?column? 
+----------
+ {[9,10)}
+(1 row)
+
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[0,2), [3,8), [9,12)}'::nummultirange;
+          ?column?          
+----------------------------
+ {[1,2),[3,4),[7,8),[9,10)}
+(1 row)
+
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[0,2), [3,8), [9,12)}'::nummultirange;
+          ?column?          
+----------------------------
+ {[1,2),[3,4),[7,8),[9,10)}
+(1 row)
+
+--
+-- range_agg function
+--
+create table reservations ( room_id integer not null, booked_during daterange );
+insert into reservations values
+-- 1: has a meets and a gap
+(1, daterange('2018-07-01', '2018-07-07')),
+(1, daterange('2018-07-07', '2018-07-14')),
+(1, daterange('2018-07-20', '2018-07-22')),
+-- 2: just a single row
+(2, daterange('2018-07-01', '2018-07-03')),
+-- 3: one null range
+(3, NULL),
+-- 4: two null ranges
+(4, NULL),
+(4, NULL),
+-- 5: a null range and a non-null range
+(5, NULL),
+(5, daterange('2018-07-01', '2018-07-03')),
+-- 6: has overlap
+(6, daterange('2018-07-01', '2018-07-07')),
+(6, daterange('2018-07-05', '2018-07-10')),
+-- 7: two ranges that meet: no gap or overlap
+(7, daterange('2018-07-01', '2018-07-07')),
+(7, daterange('2018-07-07', '2018-07-14')),
+-- 8: an empty range
+(8, 'empty'::daterange)
+;
+SELECT   room_id, range_agg(booked_during)
+FROM     reservations
+GROUP BY room_id
+ORDER BY room_id;
+ room_id |                     range_agg                     
+---------+---------------------------------------------------
+       1 | {[07-01-2018,07-14-2018),[07-20-2018,07-22-2018)}
+       2 | {[07-01-2018,07-03-2018)}
+       3 | 
+       4 | 
+       5 | {[07-01-2018,07-03-2018)}
+       6 | {[07-01-2018,07-10-2018)}
+       7 | {[07-01-2018,07-14-2018)}
+       8 | {}
+(8 rows)
+
+-- range_agg on a custom range type too
+SELECT  range_agg(r)
+FROM    (VALUES
+          ('[a,c]'::textrange),
+          ('[b,b]'::textrange),
+          ('[c,f]'::textrange),
+          ('[g,h)'::textrange),
+          ('[h,j)'::textrange)
+        ) t(r);
+   range_agg   
+---------------
+ {[a,f],[g,j)}
+(1 row)
+
+select range_intersect_agg(nmr) from nummultirange_test;
+ range_intersect_agg 
+---------------------
+ {}
+(1 row)
+
+select range_intersect_agg(nmr) from nummultirange_test where false;
+ range_intersect_agg 
+---------------------
+ 
+(1 row)
+
+-- test with just one input:
+select range_intersect_agg(nmr) from (values ('{[1,2]}'::nummultirange)) t(nmr);
+ range_intersect_agg 
+---------------------
+ {[1,2]}
+(1 row)
+
+select range_intersect_agg(nmr) from nummultirange_test where nmr @> 4.0;
+ range_intersect_agg 
+---------------------
+ {[3,5)}
+(1 row)
+
+create table nummultirange_test2(nmr nummultirange);
+create index nummultirange_test2_hash_idx on nummultirange_test2 using hash (nmr);
+INSERT INTO nummultirange_test2 VALUES('{[, 5)}');
+INSERT INTO nummultirange_test2 VALUES(nummultirange(numrange(1.1, 2.2)));
+INSERT INTO nummultirange_test2 VALUES(nummultirange(numrange(1.1, 2.2)));
+INSERT INTO nummultirange_test2 VALUES(nummultirange(numrange(1.1, 2.2,'()')));
+INSERT INTO nummultirange_test2 VALUES('{}');
+select * from nummultirange_test2 where nmr = '{}';
+ nmr 
+-----
+ {}
+(1 row)
+
+select * from nummultirange_test2 where nmr = nummultirange(numrange(1.1, 2.2));
+     nmr     
+-------------
+ {[1.1,2.2)}
+ {[1.1,2.2)}
+(2 rows)
+
+select * from nummultirange_test2 where nmr = nummultirange(numrange(1.1, 2.3));
+ nmr 
+-----
+(0 rows)
+
+set enable_nestloop=t;
+set enable_hashjoin=f;
+set enable_mergejoin=f;
+select * from nummultirange_test natural join nummultirange_test2 order by nmr;
+     nmr     
+-------------
+ {}
+ {}
+ {}
+ {}
+ {(,5)}
+ {[1.1,2.2)}
+ {[1.1,2.2)}
+(7 rows)
+
+set enable_nestloop=f;
+set enable_hashjoin=t;
+set enable_mergejoin=f;
+select * from nummultirange_test natural join nummultirange_test2 order by nmr;
+     nmr     
+-------------
+ {}
+ {}
+ {}
+ {}
+ {(,5)}
+ {[1.1,2.2)}
+ {[1.1,2.2)}
+(7 rows)
+
+set enable_nestloop=f;
+set enable_hashjoin=f;
+set enable_mergejoin=t;
+select * from nummultirange_test natural join nummultirange_test2 order by nmr;
+     nmr     
+-------------
+ {}
+ {}
+ {}
+ {}
+ {(,5)}
+ {[1.1,2.2)}
+ {[1.1,2.2)}
+(7 rows)
+
+set enable_nestloop to default;
+set enable_hashjoin to default;
+set enable_mergejoin to default;
+DROP TABLE nummultirange_test2;
+--
+-- Test user-defined multirange of floats
+--
+select '{[123.001, 5.e9)}'::float8multirange @> 888.882::float8;
+ ?column? 
+----------
+ t
+(1 row)
+
+create table float8multirange_test(f8mr float8multirange, i int);
+insert into float8multirange_test values(float8multirange(float8range(-100.00007, '1.111113e9')), 42);
+select * from float8multirange_test;
+           f8mr            | i  
+---------------------------+----
+ {[-100.00007,1111113000)} | 42
+(1 row)
+
+drop table float8multirange_test;
+--
+-- Test multirange types over domains
+--
+create domain mydomain as int4;
+create type mydomainrange as range(subtype=mydomain);
+select '{[4,50)}'::mydomainmultirange @> 7::mydomain;
+ ?column? 
+----------
+ t
+(1 row)
+
+drop domain mydomain cascade;
+NOTICE:  drop cascades to type mydomainrange
+--
+-- Test domains over multirange types
+--
+create domain restrictedmultirange as int4multirange check (upper(value) < 10);
+select '{[4,5)}'::restrictedmultirange @> 7;
+ ?column? 
+----------
+ f
+(1 row)
+
+select '{[4,50)}'::restrictedmultirange @> 7; -- should fail
+ERROR:  value for domain restrictedmultirange violates check constraint "restrictedmultirange_check"
+drop domain restrictedmultirange;
+---
+-- Check automatic naming of multiranges
+---
+create type intr as range(subtype=int);
+select intr_multirange(intr(1,10));
+ intr_multirange 
+-----------------
+ {[1,10)}
+(1 row)
+
+drop type intr;
+create type intmultirange as (x int, y int);
+create type intrange as range(subtype=int); -- should fail
+ERROR:  type "intmultirange" already exists
+DETAIL:  Failed while creating a multirange type for type "intrange".
+HINT:  You can manually specify multirange type name using "multirange_type_name" attribute
+drop type intmultirange;
+create type intr_multirange as (x int, y int);
+create type intr as range(subtype=int); -- should fail
+ERROR:  type "intr_multirange" already exists
+DETAIL:  Failed while creating a multirange type for type "intr".
+HINT:  You can manually specify multirange type name using "multirange_type_name" attribute
+drop type intr_multirange;
+--
+-- Test multiple multirange types over the same subtype and manual naming of
+-- the multirange type.
+--
+-- should fail
+create type textrange1 as range(subtype=text, multirange_type_name=int, collation="C");
+ERROR:  type "int4" already exists
+-- should pass
+create type textrange1 as range(subtype=text, multirange_type_name=multirange_of_text, collation="C");
+-- should pass, because existing _textrange1 is automatically renamed
+create type textrange2 as range(subtype=text, multirange_type_name=_textrange1, collation="C");
+select multirange_of_text(textrange2('a','Z'));  -- should fail
+ERROR:  function multirange_of_text(textrange2) does not exist
+LINE 1: select multirange_of_text(textrange2('a','Z'));
+               ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+select multirange_of_text(textrange1('a','Z')) @> 'b'::text;
+ERROR:  range lower bound must be less than or equal to range upper bound
+select _textrange1(textrange2('a','z')) @> 'b'::text;
+ ?column? 
+----------
+ t
+(1 row)
+
+drop type textrange1;
+drop type textrange2;
+--
+-- Test polymorphic type system
+--
+create function anyarray_anymultirange_func(a anyarray, r anymultirange)
+  returns anyelement as 'select $1[1] + lower($2);' language sql;
+select anyarray_anymultirange_func(ARRAY[1,2], int4multirange(int4range(10,20)));
+ anyarray_anymultirange_func 
+-----------------------------
+                          11
+(1 row)
+
+-- should fail
+select anyarray_anymultirange_func(ARRAY[1,2], nummultirange(numrange(10,20)));
+ERROR:  function anyarray_anymultirange_func(integer[], nummultirange) does not exist
+LINE 1: select anyarray_anymultirange_func(ARRAY[1,2], nummultirange...
+               ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+drop function anyarray_anymultirange_func(anyarray, anymultirange);
+-- should fail
+create function bogus_func(anyelement)
+  returns anymultirange as 'select int4multirange(int4range(1,10))' language sql;
+ERROR:  cannot determine result data type
+DETAIL:  A result of type anymultirange requires at least one input of type anyrange or anymultirange.
+-- should fail
+create function bogus_func(int)
+  returns anymultirange as 'select int4multirange(int4range(1,10))' language sql;
+ERROR:  cannot determine result data type
+DETAIL:  A result of type anymultirange requires at least one input of type anyrange or anymultirange.
+create function range_add_bounds(anymultirange)
+  returns anyelement as 'select lower($1) + upper($1)' language sql;
+select range_add_bounds(int4multirange(int4range(1, 17)));
+ range_add_bounds 
+------------------
+               18
+(1 row)
+
+select range_add_bounds(nummultirange(numrange(1.0001, 123.123)));
+ range_add_bounds 
+------------------
+         124.1231
+(1 row)
+
+create function multirangetypes_sql(q anymultirange, b anyarray, out c anyelement)
+  as $$ select upper($1) + $2[1] $$
+  language sql;
+select multirangetypes_sql(int4multirange(int4range(1,10)), ARRAY[2,20]);
+ multirangetypes_sql 
+---------------------
+                  12
+(1 row)
+
+select multirangetypes_sql(nummultirange(numrange(1,10)), ARRAY[2,20]);  -- match failure
+ERROR:  function multirangetypes_sql(nummultirange, integer[]) does not exist
+LINE 1: select multirangetypes_sql(nummultirange(numrange(1,10)), AR...
+               ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+create function anycompatiblearray_anycompatiblemultirange_func(a anycompatiblearray, mr anycompatiblemultirange)
+  returns anycompatible as 'select $1[1] + lower($2);' language sql;
+select anycompatiblearray_anycompatiblemultirange_func(ARRAY[1,2], multirange(int4range(10,20)));
+ anycompatiblearray_anycompatiblemultirange_func 
+-------------------------------------------------
+                                              11
+(1 row)
+
+select anycompatiblearray_anycompatiblemultirange_func(ARRAY[1,2], multirange(numrange(10,20)));
+ anycompatiblearray_anycompatiblemultirange_func 
+-------------------------------------------------
+                                              11
+(1 row)
+
+-- should fail
+select anycompatiblearray_anycompatiblemultirange_func(ARRAY[1.1,2], multirange(int4range(10,20)));
+ERROR:  function anycompatiblearray_anycompatiblemultirange_func(numeric[], int4multirange) does not exist
+LINE 1: select anycompatiblearray_anycompatiblemultirange_func(ARRAY...
+               ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+drop function anycompatiblearray_anycompatiblemultirange_func(anycompatiblearray, anycompatiblemultirange);
+create function anycompatiblerange_anycompatiblemultirange_func(r anycompatiblerange, mr anycompatiblemultirange)
+  returns anycompatible as 'select lower($1) + lower($2);' language sql;
+select anycompatiblerange_anycompatiblemultirange_func(int4range(1,2), multirange(int4range(10,20)));
+ anycompatiblerange_anycompatiblemultirange_func 
+-------------------------------------------------
+                                              11
+(1 row)
+
+-- should fail
+select anycompatiblerange_anycompatiblemultirange_func(numrange(1,2), multirange(int4range(10,20)));
+ERROR:  function anycompatiblerange_anycompatiblemultirange_func(numrange, int4multirange) does not exist
+LINE 1: select anycompatiblerange_anycompatiblemultirange_func(numra...
+               ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+drop function anycompatiblerange_anycompatiblemultirange_func(anycompatiblerange, anycompatiblemultirange);
+-- should fail
+create function bogus_func(anycompatible)
+  returns anycompatiblerange as 'select int4range(1,10)' language sql;
+ERROR:  cannot determine result data type
+DETAIL:  A result of type anycompatiblerange requires at least one input of type anycompatiblerange or anycompatiblemultirange.
+--
+-- Arrays of multiranges
+--
+select ARRAY[nummultirange(numrange(1.1, 1.2)), nummultirange(numrange(12.3, 155.5))];
+              array               
+----------------------------------
+ {"{[1.1,1.2)}","{[12.3,155.5)}"}
+(1 row)
+
+create table i8mr_array (f1 int, f2 int8multirange[]);
+insert into i8mr_array values (42, array[int8multirange(int8range(1,10)), int8multirange(int8range(2,20))]);
+select * from i8mr_array;
+ f1 |           f2            
+----+-------------------------
+ 42 | {"{[1,10)}","{[2,20)}"}
+(1 row)
+
+drop table i8mr_array;
+--
+-- Multiranges of arrays
+--
+select arraymultirange(arrayrange(ARRAY[1,2], ARRAY[2,1]));
+   arraymultirange   
+---------------------
+ {["{1,2}","{2,1}")}
+(1 row)
+
+select arraymultirange(arrayrange(ARRAY[2,1], ARRAY[1,2]));  -- fail
+ERROR:  range lower bound must be less than or equal to range upper bound
+select array[1,1] <@ arraymultirange(arrayrange(array[1,2], array[2,1]));
+ ?column? 
+----------
+ f
+(1 row)
+
+select array[1,3] <@ arraymultirange(arrayrange(array[1,2], array[2,1]));
+ ?column? 
+----------
+ t
+(1 row)
+
+--
+-- Ranges of composites
+--
+create type two_ints as (a int, b int);
+create type two_ints_range as range (subtype = two_ints);
+-- with force_parallel_mode on, this exercises tqueue.c's range remapping
+select *, row_to_json(upper(t)) as u from
+  (values (two_ints_multirange(two_ints_range(row(1,2), row(3,4)))),
+          (two_ints_multirange(two_ints_range(row(5,6), row(7,8))))) v(t);
+          t          |       u       
+---------------------+---------------
+ {["(1,2)","(3,4)")} | {"a":3,"b":4}
+ {["(5,6)","(7,8)")} | {"a":7,"b":8}
+(2 rows)
+
+drop type two_ints cascade;
+NOTICE:  drop cascades to type two_ints_range
+--
+-- Check behavior when subtype lacks a hash function
+--
+set enable_sort = off;  -- try to make it pick a hash setop implementation
+select '{(2,5)}'::cashmultirange except select '{(5,6)}'::cashmultirange;
+ cashmultirange  
+-----------------
+ {($2.00,$5.00)}
+(1 row)
+
+reset enable_sort;
+--
+-- OUT/INOUT/TABLE functions
+--
+-- infer anymultirange from anymultirange
+create function mr_outparam_succeed(i anymultirange, out r anymultirange, out t text)
+  as $$ select $1, 'foo'::text $$ language sql;
+select * from mr_outparam_succeed(int4multirange(int4range(1,2)));
+    r    |  t  
+---------+-----
+ {[1,2)} | foo
+(1 row)
+
+-- infer anyarray from anymultirange
+create function mr_outparam_succeed2(i anymultirange, out r anyarray, out t text)
+  as $$ select ARRAY[upper($1)], 'foo'::text $$ language sql;
+select * from mr_outparam_succeed2(int4multirange(int4range(1,2)));
+  r  |  t  
+-----+-----
+ {2} | foo
+(1 row)
+
+-- infer anyrange from anymultirange
+create function mr_outparam_succeed3(i anymultirange, out r anyrange, out t text)
+  as $$ select range_merge($1), 'foo'::text $$ language sql;
+select * from mr_outparam_succeed3(int4multirange(int4range(1,2)));
+   r   |  t  
+-------+-----
+ [1,2) | foo
+(1 row)
+
+-- infer anymultirange from anyrange
+create function mr_outparam_succeed4(i anyrange, out r anymultirange, out t text)
+  as $$ select multirange($1), 'foo'::text $$ language sql;
+select * from mr_outparam_succeed4(int4range(1,2));
+    r    |  t  
+---------+-----
+ {[1,2)} | foo
+(1 row)
+
+-- infer anyelement from anymultirange
+create function mr_inoutparam_succeed(out i anyelement, inout r anymultirange)
+  as $$ select upper($1), $1 $$ language sql;
+select * from mr_inoutparam_succeed(int4multirange(int4range(1,2)));
+ i |    r    
+---+---------
+ 2 | {[1,2)}
+(1 row)
+
+-- infer anyelement+anymultirange from anyelement+anymultirange
+create function mr_table_succeed(i anyelement, r anymultirange) returns table(i anyelement, r anymultirange)
+  as $$ select $1, $2 $$ language sql;
+select * from mr_table_succeed(123, int4multirange(int4range(1,11)));
+  i  |    r     
+-----+----------
+ 123 | {[1,11)}
+(1 row)
+
+-- use anymultirange in plpgsql
+create function mr_polymorphic(i anyrange) returns anymultirange
+  as $$ begin return multirange($1); end; $$ language plpgsql;
+select mr_polymorphic(int4range(1, 4));
+ mr_polymorphic 
+----------------
+ {[1,4)}
+(1 row)
+
+-- should fail
+create function mr_outparam_fail(i anyelement, out r anymultirange, out t text)
+  as $$ select '[1,10]', 'foo' $$ language sql;
+ERROR:  cannot determine result data type
+DETAIL:  A result of type anymultirange requires at least one input of type anyrange or anymultirange.
+--should fail
+create function mr_inoutparam_fail(inout i anyelement, out r anymultirange)
+  as $$ select $1, '[1,10]' $$ language sql;
+ERROR:  cannot determine result data type
+DETAIL:  A result of type anymultirange requires at least one input of type anyrange or anymultirange.
+--should fail
+create function mr_table_fail(i anyelement) returns table(i anyelement, r anymultirange)
+  as $$ select $1, '[1,10]' $$ language sql;
+ERROR:  cannot determine result data type
+DETAIL:  A result of type anymultirange requires at least one input of type anyrange or anymultirange.
diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out
index 507b474b1bb..5cac48e02a7 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -39,6 +39,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;
@@ -183,7 +187,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::regtype, p2.prorettype::regtype
 FROM pg_proc AS p1, pg_proc AS p2
 WHERE p1.oid != p2.oid AND
@@ -192,6 +197,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        
@@ -210,6 +217,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        
@@ -230,6 +239,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        
@@ -342,7 +353,8 @@ WHERE p1.prorettype IN
      '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     
 ------+----------------
@@ -357,20 +369,25 @@ ORDER BY 2;
  3532 | enum_recv
 (9 rows)
 
--- anyrange is tighter than the rest, can only resolve from anyrange input
+-- anyrange and anymultirange are tighter than the rest, can only resolve
+-- from each other
 SELECT p1.oid, p1.proname
 FROM pg_proc as p1
-WHERE p1.prorettype = 'anyrange'::regtype
+WHERE p1.prorettype IN ('anyrange'::regtype, 'anymultirange'::regtype)
   AND NOT
-     'anyrange'::regtype = ANY (p1.proargtypes)
+    ('anyrange'::regtype = ANY (p1.proargtypes) OR
+      'anymultirange'::regtype = ANY (p1.proargtypes))
 ORDER BY 2;
  oid  |     proname      
 ------+------------------
+ 8002 | anymultirange_in
  3832 | anyrange_in
+ 8004 | multirange_in
+ 8006 | multirange_recv
  3876 | range_gist_union
  3834 | range_in
  3836 | range_recv
-(4 rows)
+(7 rows)
 
 -- similarly for the anycompatible family
 SELECT p1.oid, p1.proname
@@ -2204,13 +2221,14 @@ ORDER BY 1, 2, 3;
                     | float_ops        | float4_ops       | real
                     | float_ops        | float8_ops       | double precision
                     | jsonb_ops        | jsonb_ops        | jsonb
+                    | multirange_ops   | multirange_ops   | anymultirange
                     | numeric_ops      | numeric_ops      | numeric
                     | range_ops        | range_ops        | anyrange
                     | record_image_ops | record_image_ops | record
                     | record_ops       | record_ops       | record
                     | tsquery_ops      | tsquery_ops      | tsquery
                     | tsvector_ops     | tsvector_ops     | tsvector
-(14 rows)
+(15 rows)
 
 -- **************** pg_index ****************
 -- Look for illegal values in pg_index fields.
diff --git a/src/test/regress/expected/plpgsql.out b/src/test/regress/expected/plpgsql.out
index d0a6b630b8f..d55006d8c95 100644
--- a/src/test/regress/expected/plpgsql.out
+++ b/src/test/regress/expected/plpgsql.out
@@ -1811,7 +1811,7 @@ begin
   return array[x + 1, x + 2];
 end$$ language plpgsql;
 ERROR:  cannot determine result data type
-DETAIL:  A result of type anyrange requires at least one input of type anyrange.
+DETAIL:  A result of type anyrange requires at least one input of type anyrange or anymultirange.
 create function f1(x anyrange) returns anyarray as $$
 begin
   return array[lower(x), upper(x)];
@@ -1856,7 +1856,7 @@ begin
   return array[x + 1, x + 2];
 end$$ language plpgsql;
 ERROR:  cannot determine result data type
-DETAIL:  A result of type anycompatiblerange requires at least one input of type anycompatiblerange.
+DETAIL:  A result of type anycompatiblerange requires at least one input of type anycompatiblerange or anycompatiblemultirange.
 create function f1(x anycompatiblerange, y anycompatiblearray) returns anycompatiblerange as $$
 begin
   return x;
diff --git a/src/test/regress/expected/polymorphism.out b/src/test/regress/expected/polymorphism.out
index f5dfdf617fd..772345584f0 100644
--- a/src/test/regress/expected/polymorphism.out
+++ b/src/test/regress/expected/polymorphism.out
@@ -61,7 +61,7 @@ create function polyf(x anyelement) returns anyrange as $$
   select array[x + 1, x + 2]
 $$ language sql;
 ERROR:  cannot determine result data type
-DETAIL:  A result of type anyrange requires at least one input of type anyrange.
+DETAIL:  A result of type anyrange requires at least one input of type anyrange or anymultirange.
 create function polyf(x anyrange) returns anyarray as $$
   select array[lower(x), upper(x)]
 $$ language sql;
@@ -97,12 +97,27 @@ LINE 1: select polyf(int4range(42, 49), 11, 4.5) as fail;
                ^
 HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
 drop function polyf(x anycompatiblerange, y anycompatible, z anycompatible);
+create function polyf(x anycompatiblemultirange, y anycompatible, z anycompatible) returns anycompatiblearray as $$
+  select array[lower(x), upper(x), y, z]
+$$ language sql;
+select polyf(multirange(int4range(42, 49)), 11, 2::smallint) as int, polyf(multirange(float8range(4.5, 7.8)), 7.8, 11::real) as num;
+     int      |       num        
+--------------+------------------
+ {42,49,11,2} | {4.5,7.8,7.8,11}
+(1 row)
+
+select polyf(multirange(int4range(42, 49)), 11, 4.5) as fail;  -- range type doesn't fit
+ERROR:  function polyf(int4multirange, integer, numeric) does not exist
+LINE 1: select polyf(multirange(int4range(42, 49)), 11, 4.5) as fail...
+               ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+drop function polyf(x anycompatiblemultirange, y anycompatible, z anycompatible);
 -- fail, can't infer type:
 create function polyf(x anycompatible) returns anycompatiblerange as $$
   select array[x + 1, x + 2]
 $$ language sql;
 ERROR:  cannot determine result data type
-DETAIL:  A result of type anycompatiblerange requires at least one input of type anycompatiblerange.
+DETAIL:  A result of type anycompatiblerange requires at least one input of type anycompatiblerange or anycompatiblemultirange.
 create function polyf(x anycompatiblerange, y anycompatiblearray) returns anycompatiblerange as $$
   select x
 $$ language sql;
@@ -113,6 +128,22 @@ select polyf(int4range(42, 49), array[11]) as int, polyf(float8range(4.5, 7.8),
 (1 row)
 
 drop function polyf(x anycompatiblerange, y anycompatiblearray);
+-- fail, can't infer type:
+create function polyf(x anycompatible) returns anycompatiblemultirange as $$
+  select array[x + 1, x + 2]
+$$ language sql;
+ERROR:  cannot determine result data type
+DETAIL:  A result of type anycompatiblemultirange requires at least one input of type anycompatiblerange or anycompatiblemultirange.
+create function polyf(x anycompatiblemultirange, y anycompatiblearray) returns anycompatiblemultirange as $$
+  select x
+$$ language sql;
+select polyf(multirange(int4range(42, 49)), array[11]) as int, polyf(multirange(float8range(4.5, 7.8)), array[7]) as num;
+    int    |     num     
+-----------+-------------
+ {[42,49)} | {[4.5,7.8)}
+(1 row)
+
+drop function polyf(x anycompatiblemultirange, y anycompatiblearray);
 create function polyf(a anyelement, b anyarray,
                       c anycompatible, d anycompatible,
                       OUT x anyarray, OUT y anycompatiblearray)
@@ -231,7 +262,7 @@ CREATE AGGREGATE myaggp01a(*) (SFUNC = stfnp, STYPE = int4[],
 CREATE AGGREGATE myaggp02a(*) (SFUNC = stfnp, STYPE = anyarray,
   FINALFUNC = ffp, INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 --     N    P
 -- should CREATE
 CREATE AGGREGATE myaggp03a(*) (SFUNC = stfp, STYPE = int4[],
@@ -243,11 +274,11 @@ CREATE AGGREGATE myaggp03b(*) (SFUNC = stfp, STYPE = int4[],
 CREATE AGGREGATE myaggp04a(*) (SFUNC = stfp, STYPE = anyarray,
   FINALFUNC = ffp, INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 CREATE AGGREGATE myaggp04b(*) (SFUNC = stfp, STYPE = anyarray,
   INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 --    Case2 (R = P) && ((B = P) || (B = N))
 --    -------------------------------------
 --    S    tf1      B    tf2
@@ -302,13 +333,13 @@ ERROR:  function tfp(integer[], anyelement) does not exist
 CREATE AGGREGATE myaggp13a(BASETYPE = int, SFUNC = tfnp, STYPE = anyarray,
   FINALFUNC = ffp, INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 --    P    N        N    P
 -- should ERROR: tf2p(anyarray, int) not matched by tf2p(int[],anyelement)
 CREATE AGGREGATE myaggp14a(BASETYPE = int, SFUNC = tf2p, STYPE = anyarray,
   FINALFUNC = ffp, INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 --    P    N        P    N
 -- should ERROR: tfnp(anyarray, anyelement) not matched by tfnp(int[],int)
 CREATE AGGREGATE myaggp15a(BASETYPE = anyelement, SFUNC = tfnp,
@@ -324,21 +355,21 @@ ERROR:  function tf2p(anyarray, anyelement) does not exist
 CREATE AGGREGATE myaggp17a(BASETYPE = int, SFUNC = tf1p, STYPE = anyarray,
   FINALFUNC = ffp, INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 CREATE AGGREGATE myaggp17b(BASETYPE = int, SFUNC = tf1p, STYPE = anyarray,
   INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 --    P    P        N    P
 -- should ERROR: tfp(anyarray, int) not matched by tfp(anyarray, anyelement)
 CREATE AGGREGATE myaggp18a(BASETYPE = int, SFUNC = tfp, STYPE = anyarray,
   FINALFUNC = ffp, INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 CREATE AGGREGATE myaggp18b(BASETYPE = int, SFUNC = tfp, STYPE = anyarray,
   INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 --    P    P        P    N
 -- should ERROR: tf1p(anyarray, anyelement) not matched by tf1p(anyarray, int)
 CREATE AGGREGATE myaggp19a(BASETYPE = anyelement, SFUNC = tf1p,
@@ -368,11 +399,11 @@ CREATE AGGREGATE myaggn01b(*) (SFUNC = stfnp, STYPE = int4[],
 CREATE AGGREGATE myaggn02a(*) (SFUNC = stfnp, STYPE = anyarray,
   FINALFUNC = ffnp, INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 CREATE AGGREGATE myaggn02b(*) (SFUNC = stfnp, STYPE = anyarray,
   INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 --     N    P
 -- should CREATE
 CREATE AGGREGATE myaggn03a(*) (SFUNC = stfp, STYPE = int4[],
@@ -382,7 +413,7 @@ CREATE AGGREGATE myaggn03a(*) (SFUNC = stfp, STYPE = int4[],
 CREATE AGGREGATE myaggn04a(*) (SFUNC = stfp, STYPE = anyarray,
   FINALFUNC = ffnp, INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 --    Case4 (R = N) && ((B = P) || (B = N))
 --    -------------------------------------
 --    S    tf1      B    tf2
@@ -436,21 +467,21 @@ ERROR:  function tfp(integer[], anyelement) does not exist
 CREATE AGGREGATE myaggn13a(BASETYPE = int, SFUNC = tfnp, STYPE = anyarray,
   FINALFUNC = ffnp, INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 CREATE AGGREGATE myaggn13b(BASETYPE = int, SFUNC = tfnp, STYPE = anyarray,
   INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 --    P    N        N    P
 -- should ERROR: tf2p(anyarray, int) not matched by tf2p(int[],anyelement)
 CREATE AGGREGATE myaggn14a(BASETYPE = int, SFUNC = tf2p, STYPE = anyarray,
   FINALFUNC = ffnp, INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 CREATE AGGREGATE myaggn14b(BASETYPE = int, SFUNC = tf2p, STYPE = anyarray,
   INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 --    P    N        P    N
 -- should ERROR: tfnp(anyarray, anyelement) not matched by tfnp(int[],int)
 CREATE AGGREGATE myaggn15a(BASETYPE = anyelement, SFUNC = tfnp,
@@ -472,13 +503,13 @@ ERROR:  function tf2p(anyarray, anyelement) does not exist
 CREATE AGGREGATE myaggn17a(BASETYPE = int, SFUNC = tf1p, STYPE = anyarray,
   FINALFUNC = ffnp, INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 --    P    P        N    P
 -- should ERROR: tfp(anyarray, int) not matched by tfp(anyarray, anyelement)
 CREATE AGGREGATE myaggn18a(BASETYPE = int, SFUNC = tfp, STYPE = anyarray,
   FINALFUNC = ffnp, INITCOND = '{}');
 ERROR:  cannot determine transition data type
-DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyarray requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 --    P    P        P    N
 -- should ERROR: tf1p(anyarray, anyelement) not matched by tf1p(anyarray, int)
 CREATE AGGREGATE myaggn19a(BASETYPE = anyelement, SFUNC = tf1p,
@@ -1855,7 +1886,59 @@ returns anycompatiblerange as $$
   select $1
 $$ language sql;
 ERROR:  cannot determine result data type
-DETAIL:  A result of type anycompatiblerange requires at least one input of type anycompatiblerange.
+DETAIL:  A result of type anycompatiblerange requires at least one input of type anycompatiblerange or anycompatiblemultirange.
+create function anyctest(anycompatible, anycompatiblemultirange)
+returns anycompatiblemultirange as $$
+  select $2
+$$ language sql;
+select x, pg_typeof(x) from anyctest(11, multirange(int4range(4,7))) x;
+    x    |   pg_typeof    
+---------+----------------
+ {[4,7)} | int4multirange
+(1 row)
+
+select x, pg_typeof(x) from anyctest(11, multirange(numrange(4,7))) x;
+    x    |   pg_typeof   
+---------+---------------
+ {[4,7)} | nummultirange
+(1 row)
+
+select x, pg_typeof(x) from anyctest(11, 12) x;  -- fail
+ERROR:  function anyctest(integer, integer) does not exist
+LINE 1: select x, pg_typeof(x) from anyctest(11, 12) x;
+                                    ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+select x, pg_typeof(x) from anyctest(11.2, multirange(int4range(4,7))) x;  -- fail
+ERROR:  function anyctest(numeric, int4multirange) does not exist
+LINE 1: select x, pg_typeof(x) from anyctest(11.2, multirange(int4ra...
+                                    ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+select x, pg_typeof(x) from anyctest(11.2, '{[4,7)}') x;  -- fail
+ERROR:  could not identify anycompatiblemultirange type
+drop function anyctest(anycompatible, anycompatiblemultirange);
+create function anyctest(anycompatiblemultirange, anycompatiblemultirange)
+returns anycompatible as $$
+  select lower($1) + upper($2)
+$$ language sql;
+select x, pg_typeof(x) from anyctest(multirange(int4range(11,12)), multirange(int4range(4,7))) x;
+ x  | pg_typeof 
+----+-----------
+ 18 | integer
+(1 row)
+
+select x, pg_typeof(x) from anyctest(multirange(int4range(11,12)), multirange(numrange(4,7))) x; -- fail
+ERROR:  function anyctest(int4multirange, nummultirange) does not exist
+LINE 1: select x, pg_typeof(x) from anyctest(multirange(int4range(11...
+                                    ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+drop function anyctest(anycompatiblemultirange, anycompatiblemultirange);
+-- fail, can't infer result type:
+create function anyctest(anycompatible)
+returns anycompatiblemultirange as $$
+  select $1
+$$ language sql;
+ERROR:  cannot determine result data type
+DETAIL:  A result of type anycompatiblemultirange requires at least one input of type anycompatiblerange or anycompatiblemultirange.
 create function anyctest(anycompatiblenonarray, anycompatiblenonarray)
 returns anycompatiblearray as $$
   select array[$1, $2]
diff --git a/src/test/regress/expected/rangefuncs.out b/src/test/regress/expected/rangefuncs.out
index 06bd129fd22..5bbd5f6c0bd 100644
--- a/src/test/regress/expected/rangefuncs.out
+++ b/src/test/regress/expected/rangefuncs.out
@@ -1556,7 +1556,7 @@ DROP FUNCTION dup(anyelement);
 CREATE FUNCTION bad (f1 int, out f2 anyelement, out f3 anyarray)
 AS 'select $1, array[$1,$1]' LANGUAGE sql;
 ERROR:  cannot determine result data type
-DETAIL:  A result of type anyelement requires at least one input of type anyelement, anyarray, anynonarray, anyenum, or anyrange.
+DETAIL:  A result of type anyelement requires at least one input of type anyelement, anyarray, anynonarray, anyenum, anyrange, or anymultirange.
 CREATE FUNCTION dup (f1 anycompatible, f2 anycompatiblearray, f3 out anycompatible, f4 out anycompatiblearray)
 AS 'select $1, $2' LANGUAGE sql;
 SELECT dup(22, array[44]);
diff --git a/src/test/regress/expected/rangetypes.out b/src/test/regress/expected/rangetypes.out
index c421f5394fe..6a1bbadc91a 100644
--- a/src/test/regress/expected/rangetypes.out
+++ b/src/test/regress/expected/rangetypes.out
@@ -518,6 +518,25 @@ select numrange(1.0, 2.0) * numrange(2.5, 3.0);
  empty
 (1 row)
 
+select range_intersect_agg(nr) from numrange_test;
+ range_intersect_agg 
+---------------------
+ empty
+(1 row)
+
+select range_intersect_agg(nr) from numrange_test where false;
+ range_intersect_agg 
+---------------------
+ 
+(1 row)
+
+select range_intersect_agg(nr) from numrange_test where nr @> 4.0;
+ range_intersect_agg 
+---------------------
+ [3,5)
+(1 row)
+
+analyze numrange_test;
 create table numrange_test2(nr numrange);
 create index numrange_test2_hash_idx on numrange_test2 using hash (nr);
 INSERT INTO numrange_test2 VALUES('[, 5)');
@@ -1371,12 +1390,12 @@ drop function anyarray_anyrange_func(anyarray, anyrange);
 create function bogus_func(anyelement)
   returns anyrange as 'select int4range(1,10)' language sql;
 ERROR:  cannot determine result data type
-DETAIL:  A result of type anyrange requires at least one input of type anyrange.
+DETAIL:  A result of type anyrange requires at least one input of type anyrange or anymultirange.
 -- should fail
 create function bogus_func(int)
   returns anyrange as 'select int4range(1,10)' language sql;
 ERROR:  cannot determine result data type
-DETAIL:  A result of type anyrange requires at least one input of type anyrange.
+DETAIL:  A result of type anyrange requires at least one input of type anyrange or anymultirange.
 create function range_add_bounds(anyrange)
   returns anyelement as 'select lower($1) + upper($1)' language sql;
 select range_add_bounds(int4range(1, 17));
@@ -1430,7 +1449,7 @@ drop function anycompatiblearray_anycompatiblerange_func(anycompatiblearray, any
 create function bogus_func(anycompatible)
   returns anycompatiblerange as 'select int4range(1,10)' language sql;
 ERROR:  cannot determine result data type
-DETAIL:  A result of type anycompatiblerange requires at least one input of type anycompatiblerange.
+DETAIL:  A result of type anycompatiblerange requires at least one input of type anycompatiblerange or anycompatiblemultirange.
 --
 -- Arrays of ranges
 --
@@ -1508,6 +1527,7 @@ reset enable_sort;
 --
 -- OUT/INOUT/TABLE functions
 --
+-- infer anyrange from anyrange
 create function outparam_succeed(i anyrange, out r anyrange, out t text)
   as $$ select $1, 'foo'::text $$ language sql;
 select * from outparam_succeed(int4range(1,2));
@@ -1525,6 +1545,16 @@ select * from outparam2_succeed(int4range(1,11));
  {1,11} | {11,1}
 (1 row)
 
+-- infer anyarray from anyrange
+create function outparam_succeed2(i anyrange, out r anyarray, out t text)
+  as $$ select ARRAY[upper($1)], 'foo'::text $$ language sql;
+select * from outparam_succeed2(int4range(int4range(1,2)));
+  r  |  t  
+-----+-----
+ {2} | foo
+(1 row)
+
+-- infer anyelement from anyrange
 create function inoutparam_succeed(out i anyelement, inout r anyrange)
   as $$ select upper($1), $1 $$ language sql;
 select * from inoutparam_succeed(int4range(1,2));
@@ -1547,14 +1577,14 @@ select * from table_succeed(int4range(1,11));
 create function outparam_fail(i anyelement, out r anyrange, out t text)
   as $$ select '[1,10]', 'foo' $$ language sql;
 ERROR:  cannot determine result data type
-DETAIL:  A result of type anyrange requires at least one input of type anyrange.
+DETAIL:  A result of type anyrange requires at least one input of type anyrange or anymultirange.
 --should fail
 create function inoutparam_fail(inout i anyelement, out r anyrange)
   as $$ select $1, '[1,10]' $$ language sql;
 ERROR:  cannot determine result data type
-DETAIL:  A result of type anyrange requires at least one input of type anyrange.
+DETAIL:  A result of type anyrange requires at least one input of type anyrange or anymultirange.
 --should fail
 create function table_fail(i anyelement) returns table(i anyelement, r anyrange)
   as $$ select $1, '[1,10]' $$ language sql;
 ERROR:  cannot determine result data type
-DETAIL:  A result of type anyrange requires at least one input of type anyrange.
+DETAIL:  A result of type anyrange requires at least one input of type anyrange or anymultirange.
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index 192445878d2..d9ce961be2b 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -92,6 +92,7 @@ num_exp_sqrt|t
 num_exp_sub|t
 num_input_test|f
 num_result|f
+nummultirange_test|t
 numrange_test|t
 onek|t
 onek2|t
@@ -172,6 +173,7 @@ quad_poly_tbl|t
 radix_text_tbl|t
 ramp|f
 real_city|f
+reservations|f
 road|t
 shighway|t
 slow_emp4000|f
diff --git a/src/test/regress/expected/type_sanity.out b/src/test/regress/expected/type_sanity.out
index 13567ddf84b..0c74dc96a87 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'));
@@ -196,18 +196,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
@@ -241,17 +242,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
@@ -319,18 +321,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
@@ -364,17 +367,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
@@ -660,3 +664,11 @@ WHERE pronargs != 2
 ----------+------------+---------
 (0 rows)
 
+-- every range should have a valid multirange
+SELECT p1.rngtypid, p1.rngsubtype, p1.rngmultitypid
+FROM pg_range p1
+WHERE p1.rngmultitypid IS NULL OR p1.rngmultitypid = 0;
+ rngtypid | rngsubtype | rngmultitypid 
+----------+------------+---------------
+(0 rows)
+
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index ae89ed7f0b4..432f454aa17 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -19,15 +19,16 @@ 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 xid
+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
 # geometry depends on point, lseg, box, path, polygon and circle
 # horology depends on interval, timetz, timestamp, timestamptz
 # ----------
-test: geometry horology regex oidjoins type_sanity opr_sanity misc_sanity comments expressions unicode
+test: geometry horology regex oidjoins type_sanity opr_sanity misc_sanity comments expressions unicode xid
 
 # ----------
 # These four each depend on the previous one
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index 525bdc804f6..081fce32e75 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -20,6 +20,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 681cc92ac20..0292dedd15a 100644
--- a/src/test/regress/sql/hash_func.sql
+++ b/src/test/regress/sql/hash_func.sql
@@ -227,6 +227,16 @@ FROM   (VALUES (int4range(10, 20)), (int4range(23, 43)),
 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);
+
 CREATE TYPE hash_test_t1 AS (a int, b text);
 SELECT v as value, hash_record(v)::bit(32) as standard,
        hash_record_extended(v, 0)::bit(32) as extended0,
diff --git a/src/test/regress/sql/multirangetypes.sql b/src/test/regress/sql/multirangetypes.sql
new file mode 100644
index 00000000000..9be26f10d38
--- /dev/null
+++ b/src/test/regress/sql/multirangetypes.sql
@@ -0,0 +1,677 @@
+-- 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'));
+
+--
+-- test casts, both a built-in range type and a user-defined one:
+--
+select 'empty'::int4range::int4multirange;
+select int4range(1, 3)::int4multirange;
+select int4range(1, null)::int4multirange;
+select int4range(null, null)::int4multirange;
+select 'empty'::textrange::textmultirange;
+select textrange('a', 'c')::textmultirange;
+select textrange('a', null)::textmultirange;
+select textrange(null, null)::textmultirange;
+
+--
+-- create some test data and test the operators
+--
+
+CREATE TABLE nummultirange_test (nmr NUMMULTIRANGE);
+CREATE INDEX nummultirange_test_btree ON nummultirange_test(nmr);
+
+INSERT INTO nummultirange_test VALUES('{}');
+INSERT INTO nummultirange_test VALUES('{[,)}');
+INSERT INTO nummultirange_test VALUES('{[3,]}');
+INSERT INTO nummultirange_test VALUES('{[,), [3,]}');
+INSERT INTO nummultirange_test VALUES('{[, 5)}');
+INSERT INTO nummultirange_test VALUES(nummultirange());
+INSERT INTO nummultirange_test VALUES(nummultirange(variadic '{}'::numrange[]));
+INSERT INTO nummultirange_test VALUES(nummultirange(numrange(1.1, 2.2)));
+INSERT INTO nummultirange_test VALUES('{empty}');
+INSERT INTO nummultirange_test VALUES(nummultirange(numrange(1.7, 1.7, '[]'), numrange(1.7, 1.9)));
+INSERT INTO nummultirange_test VALUES(nummultirange(numrange(1.7, 1.7, '[]'), numrange(1.9, 2.1)));
+
+SELECT nmr, isempty(nmr), lower(nmr), upper(nmr) FROM nummultirange_test ORDER BY nmr;
+SELECT nmr, lower_inc(nmr), lower_inf(nmr), upper_inc(nmr), upper_inf(nmr) FROM nummultirange_test ORDER BY nmr;
+
+SELECT * FROM nummultirange_test WHERE nmr = '{}';
+SELECT * FROM nummultirange_test WHERE nmr = '{(,5)}';
+SELECT * FROM nummultirange_test WHERE nmr = '{[3,)}';
+SELECT * FROM nummultirange_test WHERE nmr = '{[1.7,1.7]}';
+SELECT * FROM nummultirange_test WHERE nmr = '{[1.7,1.7],[1.9,2.1)}';
+SELECT * FROM nummultirange_test WHERE nmr < '{}';
+SELECT * FROM nummultirange_test WHERE nmr < '{[-1000.0, -1000.0]}';
+SELECT * FROM nummultirange_test WHERE nmr < '{[0.0, 1.0]}';
+SELECT * FROM nummultirange_test WHERE nmr < '{[1000.0, 1001.0]}';
+SELECT * FROM nummultirange_test WHERE nmr <= '{}';
+SELECT * FROM nummultirange_test WHERE nmr <= '{[3,)}';
+SELECT * FROM nummultirange_test WHERE nmr >= '{}';
+SELECT * FROM nummultirange_test WHERE nmr >= '{[3,)}';
+SELECT * FROM nummultirange_test WHERE nmr > '{}';
+SELECT * FROM nummultirange_test WHERE nmr > '{[-1000.0, -1000.0]}';
+SELECT * FROM nummultirange_test WHERE nmr > '{[0.0, 1.0]}';
+SELECT * FROM nummultirange_test WHERE nmr > '{[1000.0, 1001.0]}';
+SELECT * FROM nummultirange_test WHERE nmr <> '{}';
+SELECT * FROM nummultirange_test WHERE nmr <> '{(,5)}';
+
+select nummultirange(numrange(2.0, 1.0));
+select nummultirange(numrange(5.0, 6.0), numrange(1.0, 2.0));
+
+analyze nummultirange_test;
+
+-- overlaps
+SELECT * FROM nummultirange_test WHERE range_overlaps_multirange(numrange(4.0, 4.2), nmr);
+SELECT * FROM nummultirange_test WHERE numrange(4.0, 4.2) && nmr;
+SELECT * FROM nummultirange_test WHERE multirange_overlaps_range(nmr, numrange(4.0, 4.2));
+SELECT * FROM nummultirange_test WHERE nmr && numrange(4.0, 4.2);
+SELECT * FROM nummultirange_test WHERE multirange_overlaps_multirange(nmr, nummultirange(numrange(4.0, 4.2), numrange(6.0, 7.0)));
+SELECT * FROM nummultirange_test WHERE nmr && nummultirange(numrange(4.0, 4.2), numrange(6.0, 7.0));
+SELECT * FROM nummultirange_test WHERE nmr && nummultirange(numrange(6.0, 7.0));
+SELECT * FROM nummultirange_test WHERE nmr && nummultirange(numrange(6.0, 7.0), numrange(8.0, 9.0));
+
+-- mr contains x
+SELECT * FROM nummultirange_test WHERE multirange_contains_elem(nmr, 4.0);
+SELECT * FROM nummultirange_test WHERE nmr @> 4.0;
+SELECT * FROM nummultirange_test WHERE multirange_contains_range(nmr, numrange(4.0, 4.2));
+SELECT * FROM nummultirange_test WHERE nmr @> numrange(4.0, 4.2);
+SELECT * FROM nummultirange_test WHERE multirange_contains_multirange(nmr, '{[4.0,4.2), [6.0, 8.0)}');
+SELECT * FROM nummultirange_test WHERE nmr @> '{[4.0,4.2), [6.0, 8.0)}'::nummultirange;
+
+-- x is contained by mr
+SELECT * FROM nummultirange_test WHERE elem_contained_by_multirange(4.0, nmr);
+SELECT * FROM nummultirange_test WHERE 4.0 <@ nmr;
+SELECT * FROM nummultirange_test WHERE range_contained_by_multirange(numrange(4.0, 4.2), nmr);
+SELECT * FROM nummultirange_test WHERE numrange(4.0, 4.2) <@ nmr;
+SELECT * FROM nummultirange_test WHERE multirange_contained_by_multirange('{[4.0,4.2), [6.0, 8.0)}', nmr);
+SELECT * FROM nummultirange_test WHERE '{[4.0,4.2), [6.0, 8.0)}'::nummultirange <@ nmr;
+
+-- overlaps
+SELECT 'empty'::numrange && nummultirange();
+SELECT 'empty'::numrange && nummultirange(numrange(1,2));
+SELECT nummultirange() && 'empty'::numrange;
+SELECT nummultirange(numrange(1,2)) && 'empty'::numrange;
+SELECT nummultirange() && nummultirange();
+SELECT nummultirange() && nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) && nummultirange();
+SELECT nummultirange(numrange(3,4)) && nummultirange(numrange(1,2), numrange(7,8));
+SELECT nummultirange(numrange(1,2), numrange(7,8)) && nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(3,4)) && nummultirange(numrange(1,2), numrange(3.5,8));
+SELECT nummultirange(numrange(1,2), numrange(3.5,8)) && numrange(3,4);
+SELECT nummultirange(numrange(1,2), numrange(3.5,8)) && nummultirange(numrange(3,4));
+
+-- contains
+SELECT nummultirange() @> nummultirange();
+SELECT nummultirange() @> 'empty'::numrange;
+SELECT nummultirange(numrange(null,null)) @> numrange(1,2);
+SELECT nummultirange(numrange(null,null)) @> numrange(null,2);
+SELECT nummultirange(numrange(null,null)) @> numrange(2,null);
+SELECT nummultirange(numrange(null,5)) @> numrange(null,3);
+SELECT nummultirange(numrange(null,5)) @> numrange(null,8);
+SELECT nummultirange(numrange(5,null)) @> numrange(8,null);
+SELECT nummultirange(numrange(5,null)) @> numrange(3,null);
+SELECT nummultirange(numrange(1,5)) @> numrange(8,9);
+SELECT nummultirange(numrange(1,5)) @> numrange(3,9);
+SELECT nummultirange(numrange(1,5)) @> numrange(1,4);
+SELECT nummultirange(numrange(1,5)) @> numrange(1,5);
+SELECT nummultirange(numrange(-4,-2), numrange(1,5)) @> numrange(1,5);
+SELECT nummultirange(numrange(1,5), numrange(8,9)) @> numrange(1,5);
+SELECT nummultirange(numrange(1,5), numrange(8,9)) @> numrange(6,7);
+SELECT nummultirange(numrange(1,5), numrange(6,9)) @> numrange(6,7);
+SELECT '{[1,5)}'::nummultirange @> '{[1,5)}';
+SELECT '{[-4,-2), [1,5)}'::nummultirange @> '{[1,5)}';
+SELECT '{[1,5), [8,9)}'::nummultirange @> '{[1,5)}';
+SELECT '{[1,5), [8,9)}'::nummultirange @> '{[6,7)}';
+SELECT '{[1,5), [6,9)}'::nummultirange @> '{[6,7)}';
+
+-- is contained by
+SELECT nummultirange() <@ nummultirange();
+SELECT 'empty'::numrange <@ nummultirange();
+SELECT numrange(1,2) <@ nummultirange(numrange(null,null));
+SELECT numrange(null,2) <@ nummultirange(numrange(null,null));
+SELECT numrange(2,null) <@ nummultirange(numrange(null,null));
+SELECT numrange(null,3) <@ nummultirange(numrange(null,5));
+SELECT numrange(null,8) <@ nummultirange(numrange(null,5));
+SELECT numrange(8,null) <@ nummultirange(numrange(5,null));
+SELECT numrange(3,null) <@ nummultirange(numrange(5,null));
+SELECT numrange(8,9) <@ nummultirange(numrange(1,5));
+SELECT numrange(3,9) <@ nummultirange(numrange(1,5));
+SELECT numrange(1,4) <@ nummultirange(numrange(1,5));
+SELECT numrange(1,5) <@ nummultirange(numrange(1,5));
+SELECT numrange(1,5) <@ nummultirange(numrange(-4,-2), numrange(1,5));
+SELECT numrange(1,5) <@ nummultirange(numrange(1,5), numrange(8,9));
+SELECT numrange(6,7) <@ nummultirange(numrange(1,5), numrange(8,9));
+SELECT numrange(6,7) <@ nummultirange(numrange(1,5), numrange(6,9));
+SELECT '{[1,5)}' <@ '{[1,5)}'::nummultirange;
+SELECT '{[1,5)}' <@ '{[-4,-2), [1,5)}'::nummultirange;
+SELECT '{[1,5)}' <@ '{[1,5), [8,9)}'::nummultirange;
+SELECT '{[6,7)}' <@ '{[1,5), [8,9)}'::nummultirange;
+SELECT '{[6,7)}' <@ '{[1,5), [6,9)}'::nummultirange;
+
+-- overleft
+SELECT 'empty'::numrange &< nummultirange();
+SELECT 'empty'::numrange &< nummultirange(numrange(1,2));
+SELECT nummultirange() &< 'empty'::numrange;
+SELECT nummultirange(numrange(1,2)) &< 'empty'::numrange;
+SELECT nummultirange() &< nummultirange();
+SELECT nummultirange(numrange(1,2)) &< nummultirange();
+SELECT nummultirange() &< nummultirange(numrange(1,2));
+SELECT numrange(6,7) &< nummultirange(numrange(3,4));
+SELECT numrange(1,2) &< nummultirange(numrange(3,4));
+SELECT numrange(1,4) &< nummultirange(numrange(3,4));
+SELECT numrange(1,6) &< nummultirange(numrange(3,4));
+SELECT numrange(3.5,6) &< nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(6,7)) &< numrange(3,4);
+SELECT nummultirange(numrange(1,2)) &< numrange(3,4);
+SELECT nummultirange(numrange(1,4)) &< numrange(3,4);
+SELECT nummultirange(numrange(1,6)) &< numrange(3,4);
+SELECT nummultirange(numrange(3.5,6)) &< numrange(3,4);
+SELECT nummultirange(numrange(6,7)) &< nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,2)) &< nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,4)) &< nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,6)) &< nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(3.5,6)) &< nummultirange(numrange(3,4));
+
+-- overright
+SELECT nummultirange() &> 'empty'::numrange;
+SELECT nummultirange(numrange(1,2)) &> 'empty'::numrange;
+SELECT 'empty'::numrange &> nummultirange();
+SELECT 'empty'::numrange &> nummultirange(numrange(1,2));
+SELECT nummultirange() &> nummultirange();
+SELECT nummultirange() &> nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) &> nummultirange();
+SELECT nummultirange(numrange(3,4)) &> numrange(6,7);
+SELECT nummultirange(numrange(3,4)) &> numrange(1,2);
+SELECT nummultirange(numrange(3,4)) &> numrange(1,4);
+SELECT nummultirange(numrange(3,4)) &> numrange(1,6);
+SELECT nummultirange(numrange(3,4)) &> numrange(3.5,6);
+SELECT numrange(3,4) &> nummultirange(numrange(6,7));
+SELECT numrange(3,4) &> nummultirange(numrange(1,2));
+SELECT numrange(3,4) &> nummultirange(numrange(1,4));
+SELECT numrange(3,4) &> nummultirange(numrange(1,6));
+SELECT numrange(3,4) &> nummultirange(numrange(3.5,6));
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(6,7));
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(1,4));
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(1,6));
+SELECT nummultirange(numrange(3,4)) &> nummultirange(numrange(3.5,6));
+
+-- meets
+SELECT 'empty'::numrange -|- nummultirange();
+SELECT 'empty'::numrange -|- nummultirange(numrange(1,2));
+SELECT nummultirange() -|- 'empty'::numrange;
+SELECT nummultirange(numrange(1,2)) -|- 'empty'::numrange;
+SELECT nummultirange() -|- nummultirange();
+SELECT nummultirange(numrange(1,2)) -|- nummultirange();
+SELECT nummultirange() -|- nummultirange(numrange(1,2));
+SELECT numrange(1,2) -|- nummultirange(numrange(2,4));
+SELECT numrange(1,2) -|- nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,2)) -|- numrange(2,4);
+SELECT nummultirange(numrange(1,2)) -|- numrange(3,4);
+SELECT nummultirange(numrange(1,2)) -|- nummultirange(numrange(2,4));
+SELECT nummultirange(numrange(1,2)) -|- nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,2), numrange(5,6)) -|- nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,2), numrange(5,6)) -|- nummultirange(numrange(6,7));
+SELECT nummultirange(numrange(1,2), numrange(5,6)) -|- nummultirange(numrange(8,9));
+SELECT nummultirange(numrange(1,2)) -|- nummultirange(numrange(2,4), numrange(6,7));
+
+-- strictly left
+select 'empty'::numrange << nummultirange();
+select numrange(1,2) << nummultirange();
+select numrange(1,2) << nummultirange(numrange(3,4));
+select numrange(1,2) << nummultirange(numrange(0,4));
+select numrange(1,2) << nummultirange(numrange(0,4), numrange(7,8));
+select nummultirange() << 'empty'::numrange;
+select nummultirange() << numrange(1,2);
+select nummultirange(numrange(3,4)) << numrange(3,6);
+select nummultirange(numrange(0,2)) << numrange(3,6);
+select nummultirange(numrange(0,2), numrange(7,8)) << numrange(3,6);
+select nummultirange(numrange(-4,-2), numrange(0,2)) << numrange(3,6);
+select nummultirange() << nummultirange();
+select nummultirange() << nummultirange(numrange(1,2));
+select nummultirange(numrange(1,2)) << nummultirange();
+select nummultirange(numrange(1,2)) << nummultirange(numrange(1,2));
+select nummultirange(numrange(1,2)) << nummultirange(numrange(3,4));
+select nummultirange(numrange(1,2)) << nummultirange(numrange(3,4), numrange(7,8));
+select nummultirange(numrange(1,2), numrange(4,5)) << nummultirange(numrange(3,4), numrange(7,8));
+
+-- strictly right
+select nummultirange() >> 'empty'::numrange;
+select nummultirange() >> numrange(1,2);
+select nummultirange(numrange(3,4)) >> numrange(1,2);
+select nummultirange(numrange(0,4)) >> numrange(1,2);
+select nummultirange(numrange(0,4), numrange(7,8)) >> numrange(1,2);
+select 'empty'::numrange >> nummultirange();
+select numrange(1,2) >> nummultirange();
+select numrange(3,6) >> nummultirange(numrange(3,4));
+select numrange(3,6) >> nummultirange(numrange(0,2));
+select numrange(3,6) >> nummultirange(numrange(0,2), numrange(7,8));
+select numrange(3,6) >> nummultirange(numrange(-4,-2), numrange(0,2));
+select nummultirange() >> nummultirange();
+select nummultirange(numrange(1,2)) >> nummultirange();
+select nummultirange() >> nummultirange(numrange(1,2));
+select nummultirange(numrange(1,2)) >> nummultirange(numrange(1,2));
+select nummultirange(numrange(3,4)) >> nummultirange(numrange(1,2));
+select nummultirange(numrange(3,4), numrange(7,8)) >> nummultirange(numrange(1,2));
+select nummultirange(numrange(3,4), numrange(7,8)) >> nummultirange(numrange(1,2), numrange(4,5));
+
+-- union
+SELECT nummultirange() + nummultirange();
+SELECT nummultirange() + nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) + nummultirange();
+SELECT nummultirange(numrange(1,2)) + nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) + nummultirange(numrange(2,4));
+SELECT nummultirange(numrange(1,2)) + nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) + nummultirange(numrange(2,4));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) + nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) + nummultirange(numrange(0,9));
+
+-- merge
+SELECT range_merge(nummultirange());
+SELECT range_merge(nummultirange(numrange(1,2)));
+SELECT range_merge(nummultirange(numrange(1,2), numrange(7,8)));
+
+-- minus
+SELECT nummultirange() - nummultirange();
+SELECT nummultirange() - nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) - nummultirange();
+SELECT nummultirange(numrange(1,2), numrange(3,4)) - nummultirange();
+SELECT nummultirange(numrange(1,2)) - nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) - nummultirange(numrange(2,4));
+SELECT nummultirange(numrange(1,2)) - nummultirange(numrange(3,4));
+SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(2,3));
+SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(0,8));
+SELECT nummultirange(numrange(1,4)) - nummultirange(numrange(0,2));
+SELECT nummultirange(numrange(1,8)) - nummultirange(numrange(0,2), numrange(3,4));
+SELECT nummultirange(numrange(1,8)) - nummultirange(numrange(2,3), numrange(5,null));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(-2,0));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(2,4));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(3,5));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(0,9));
+SELECT nummultirange(numrange(1,3), numrange(4,5)) - nummultirange(numrange(2,9));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(8,9));
+SELECT nummultirange(numrange(1,2), numrange(4,5)) - nummultirange(numrange(-2,0), numrange(8,9));
+
+-- intersection
+SELECT nummultirange() * nummultirange();
+SELECT nummultirange() * nummultirange(numrange(1,2));
+SELECT nummultirange(numrange(1,2)) * nummultirange();
+SELECT '{[1,3)}'::nummultirange * '{[1,5)}'::nummultirange;
+SELECT '{[1,3)}'::nummultirange * '{[0,5)}'::nummultirange;
+SELECT '{[1,3)}'::nummultirange * '{[0,2)}'::nummultirange;
+SELECT '{[1,3)}'::nummultirange * '{[2,5)}'::nummultirange;
+SELECT '{[1,4)}'::nummultirange * '{[2,3)}'::nummultirange;
+SELECT '{[1,4)}'::nummultirange * '{[0,2), [3,5)}'::nummultirange;
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[0,8), [9,12)}'::nummultirange;
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[9,12)}'::nummultirange;
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[-5,-4), [5,6), [9,12)}'::nummultirange;
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[0,2), [3,8), [9,12)}'::nummultirange;
+SELECT '{[1,4), [7,10)}'::nummultirange * '{[0,2), [3,8), [9,12)}'::nummultirange;
+
+--
+-- range_agg function
+--
+create table reservations ( room_id integer not null, booked_during daterange );
+insert into reservations values
+-- 1: has a meets and a gap
+(1, daterange('2018-07-01', '2018-07-07')),
+(1, daterange('2018-07-07', '2018-07-14')),
+(1, daterange('2018-07-20', '2018-07-22')),
+-- 2: just a single row
+(2, daterange('2018-07-01', '2018-07-03')),
+-- 3: one null range
+(3, NULL),
+-- 4: two null ranges
+(4, NULL),
+(4, NULL),
+-- 5: a null range and a non-null range
+(5, NULL),
+(5, daterange('2018-07-01', '2018-07-03')),
+-- 6: has overlap
+(6, daterange('2018-07-01', '2018-07-07')),
+(6, daterange('2018-07-05', '2018-07-10')),
+-- 7: two ranges that meet: no gap or overlap
+(7, daterange('2018-07-01', '2018-07-07')),
+(7, daterange('2018-07-07', '2018-07-14')),
+-- 8: an empty range
+(8, 'empty'::daterange)
+;
+SELECT   room_id, range_agg(booked_during)
+FROM     reservations
+GROUP BY room_id
+ORDER BY room_id;
+
+-- range_agg on a custom range type too
+SELECT  range_agg(r)
+FROM    (VALUES
+          ('[a,c]'::textrange),
+          ('[b,b]'::textrange),
+          ('[c,f]'::textrange),
+          ('[g,h)'::textrange),
+          ('[h,j)'::textrange)
+        ) t(r);
+
+select range_intersect_agg(nmr) from nummultirange_test;
+select range_intersect_agg(nmr) from nummultirange_test where false;
+-- test with just one input:
+select range_intersect_agg(nmr) from (values ('{[1,2]}'::nummultirange)) t(nmr);
+select range_intersect_agg(nmr) from nummultirange_test where nmr @> 4.0;
+
+create table nummultirange_test2(nmr nummultirange);
+create index nummultirange_test2_hash_idx on nummultirange_test2 using hash (nmr);
+
+INSERT INTO nummultirange_test2 VALUES('{[, 5)}');
+INSERT INTO nummultirange_test2 VALUES(nummultirange(numrange(1.1, 2.2)));
+INSERT INTO nummultirange_test2 VALUES(nummultirange(numrange(1.1, 2.2)));
+INSERT INTO nummultirange_test2 VALUES(nummultirange(numrange(1.1, 2.2,'()')));
+INSERT INTO nummultirange_test2 VALUES('{}');
+
+select * from nummultirange_test2 where nmr = '{}';
+select * from nummultirange_test2 where nmr = nummultirange(numrange(1.1, 2.2));
+select * from nummultirange_test2 where nmr = nummultirange(numrange(1.1, 2.3));
+
+set enable_nestloop=t;
+set enable_hashjoin=f;
+set enable_mergejoin=f;
+select * from nummultirange_test natural join nummultirange_test2 order by nmr;
+set enable_nestloop=f;
+set enable_hashjoin=t;
+set enable_mergejoin=f;
+select * from nummultirange_test natural join nummultirange_test2 order by nmr;
+set enable_nestloop=f;
+set enable_hashjoin=f;
+set enable_mergejoin=t;
+select * from nummultirange_test natural join nummultirange_test2 order by nmr;
+
+set enable_nestloop to default;
+set enable_hashjoin to default;
+set enable_mergejoin to default;
+
+DROP TABLE nummultirange_test2;
+
+--
+-- Test user-defined multirange of floats
+--
+
+select '{[123.001, 5.e9)}'::float8multirange @> 888.882::float8;
+create table float8multirange_test(f8mr float8multirange, i int);
+insert into float8multirange_test values(float8multirange(float8range(-100.00007, '1.111113e9')), 42);
+select * from float8multirange_test;
+drop table float8multirange_test;
+
+--
+-- Test multirange types over domains
+--
+
+create domain mydomain as int4;
+create type mydomainrange as range(subtype=mydomain);
+select '{[4,50)}'::mydomainmultirange @> 7::mydomain;
+drop domain mydomain cascade;
+
+--
+-- Test domains over multirange types
+--
+
+create domain restrictedmultirange as int4multirange check (upper(value) < 10);
+select '{[4,5)}'::restrictedmultirange @> 7;
+select '{[4,50)}'::restrictedmultirange @> 7; -- should fail
+drop domain restrictedmultirange;
+
+---
+-- Check automatic naming of multiranges
+---
+
+create type intr as range(subtype=int);
+select intr_multirange(intr(1,10));
+drop type intr;
+create type intmultirange as (x int, y int);
+create type intrange as range(subtype=int); -- should fail
+drop type intmultirange;
+create type intr_multirange as (x int, y int);
+create type intr as range(subtype=int); -- should fail
+drop type intr_multirange;
+
+--
+-- Test multiple multirange types over the same subtype and manual naming of
+-- the multirange type.
+--
+
+-- should fail
+create type textrange1 as range(subtype=text, multirange_type_name=int, collation="C");
+-- should pass
+create type textrange1 as range(subtype=text, multirange_type_name=multirange_of_text, collation="C");
+-- should pass, because existing _textrange1 is automatically renamed
+create type textrange2 as range(subtype=text, multirange_type_name=_textrange1, collation="C");
+
+select multirange_of_text(textrange2('a','Z'));  -- should fail
+select multirange_of_text(textrange1('a','Z')) @> 'b'::text;
+select _textrange1(textrange2('a','z')) @> 'b'::text;
+
+drop type textrange1;
+drop type textrange2;
+
+--
+-- Test polymorphic type system
+--
+
+create function anyarray_anymultirange_func(a anyarray, r anymultirange)
+  returns anyelement as 'select $1[1] + lower($2);' language sql;
+
+select anyarray_anymultirange_func(ARRAY[1,2], int4multirange(int4range(10,20)));
+
+-- should fail
+select anyarray_anymultirange_func(ARRAY[1,2], nummultirange(numrange(10,20)));
+
+drop function anyarray_anymultirange_func(anyarray, anymultirange);
+
+-- should fail
+create function bogus_func(anyelement)
+  returns anymultirange as 'select int4multirange(int4range(1,10))' language sql;
+
+-- should fail
+create function bogus_func(int)
+  returns anymultirange as 'select int4multirange(int4range(1,10))' language sql;
+
+create function range_add_bounds(anymultirange)
+  returns anyelement as 'select lower($1) + upper($1)' language sql;
+
+select range_add_bounds(int4multirange(int4range(1, 17)));
+select range_add_bounds(nummultirange(numrange(1.0001, 123.123)));
+
+create function multirangetypes_sql(q anymultirange, b anyarray, out c anyelement)
+  as $$ select upper($1) + $2[1] $$
+  language sql;
+
+select multirangetypes_sql(int4multirange(int4range(1,10)), ARRAY[2,20]);
+select multirangetypes_sql(nummultirange(numrange(1,10)), ARRAY[2,20]);  -- match failure
+
+create function anycompatiblearray_anycompatiblemultirange_func(a anycompatiblearray, mr anycompatiblemultirange)
+  returns anycompatible as 'select $1[1] + lower($2);' language sql;
+
+select anycompatiblearray_anycompatiblemultirange_func(ARRAY[1,2], multirange(int4range(10,20)));
+
+select anycompatiblearray_anycompatiblemultirange_func(ARRAY[1,2], multirange(numrange(10,20)));
+
+-- should fail
+select anycompatiblearray_anycompatiblemultirange_func(ARRAY[1.1,2], multirange(int4range(10,20)));
+
+drop function anycompatiblearray_anycompatiblemultirange_func(anycompatiblearray, anycompatiblemultirange);
+
+create function anycompatiblerange_anycompatiblemultirange_func(r anycompatiblerange, mr anycompatiblemultirange)
+  returns anycompatible as 'select lower($1) + lower($2);' language sql;
+
+select anycompatiblerange_anycompatiblemultirange_func(int4range(1,2), multirange(int4range(10,20)));
+
+-- should fail
+select anycompatiblerange_anycompatiblemultirange_func(numrange(1,2), multirange(int4range(10,20)));
+
+drop function anycompatiblerange_anycompatiblemultirange_func(anycompatiblerange, anycompatiblemultirange);
+
+-- should fail
+create function bogus_func(anycompatible)
+  returns anycompatiblerange as 'select int4range(1,10)' language sql;
+
+--
+-- Arrays of multiranges
+--
+
+select ARRAY[nummultirange(numrange(1.1, 1.2)), nummultirange(numrange(12.3, 155.5))];
+
+create table i8mr_array (f1 int, f2 int8multirange[]);
+insert into i8mr_array values (42, array[int8multirange(int8range(1,10)), int8multirange(int8range(2,20))]);
+select * from i8mr_array;
+drop table i8mr_array;
+
+--
+-- Multiranges of arrays
+--
+
+select arraymultirange(arrayrange(ARRAY[1,2], ARRAY[2,1]));
+select arraymultirange(arrayrange(ARRAY[2,1], ARRAY[1,2]));  -- fail
+
+select array[1,1] <@ arraymultirange(arrayrange(array[1,2], array[2,1]));
+select array[1,3] <@ arraymultirange(arrayrange(array[1,2], array[2,1]));
+
+--
+-- Ranges of composites
+--
+
+create type two_ints as (a int, b int);
+create type two_ints_range as range (subtype = two_ints);
+
+-- with force_parallel_mode on, this exercises tqueue.c's range remapping
+select *, row_to_json(upper(t)) as u from
+  (values (two_ints_multirange(two_ints_range(row(1,2), row(3,4)))),
+          (two_ints_multirange(two_ints_range(row(5,6), row(7,8))))) v(t);
+
+drop type two_ints cascade;
+
+--
+-- Check behavior when subtype lacks a hash function
+--
+
+set enable_sort = off;  -- try to make it pick a hash setop implementation
+
+select '{(2,5)}'::cashmultirange except select '{(5,6)}'::cashmultirange;
+
+reset enable_sort;
+
+--
+-- OUT/INOUT/TABLE functions
+--
+
+-- infer anymultirange from anymultirange
+create function mr_outparam_succeed(i anymultirange, out r anymultirange, out t text)
+  as $$ select $1, 'foo'::text $$ language sql;
+
+select * from mr_outparam_succeed(int4multirange(int4range(1,2)));
+
+-- infer anyarray from anymultirange
+create function mr_outparam_succeed2(i anymultirange, out r anyarray, out t text)
+  as $$ select ARRAY[upper($1)], 'foo'::text $$ language sql;
+
+select * from mr_outparam_succeed2(int4multirange(int4range(1,2)));
+
+-- infer anyrange from anymultirange
+create function mr_outparam_succeed3(i anymultirange, out r anyrange, out t text)
+  as $$ select range_merge($1), 'foo'::text $$ language sql;
+select * from mr_outparam_succeed3(int4multirange(int4range(1,2)));
+
+-- infer anymultirange from anyrange
+create function mr_outparam_succeed4(i anyrange, out r anymultirange, out t text)
+  as $$ select multirange($1), 'foo'::text $$ language sql;
+
+select * from mr_outparam_succeed4(int4range(1,2));
+
+-- infer anyelement from anymultirange
+create function mr_inoutparam_succeed(out i anyelement, inout r anymultirange)
+  as $$ select upper($1), $1 $$ language sql;
+
+select * from mr_inoutparam_succeed(int4multirange(int4range(1,2)));
+
+-- infer anyelement+anymultirange from anyelement+anymultirange
+create function mr_table_succeed(i anyelement, r anymultirange) returns table(i anyelement, r anymultirange)
+  as $$ select $1, $2 $$ language sql;
+
+select * from mr_table_succeed(123, int4multirange(int4range(1,11)));
+
+-- use anymultirange in plpgsql
+create function mr_polymorphic(i anyrange) returns anymultirange
+  as $$ begin return multirange($1); end; $$ language plpgsql;
+select mr_polymorphic(int4range(1, 4));
+
+-- should fail
+create function mr_outparam_fail(i anyelement, out r anymultirange, out t text)
+  as $$ select '[1,10]', 'foo' $$ language sql;
+
+--should fail
+create function mr_inoutparam_fail(inout i anyelement, out r anymultirange)
+  as $$ select $1, '[1,10]' $$ language sql;
+
+--should fail
+create function mr_table_fail(i anyelement) returns table(i anyelement, r anymultirange)
+  as $$ select $1, '[1,10]' $$ language sql;
diff --git a/src/test/regress/sql/opr_sanity.sql b/src/test/regress/sql/opr_sanity.sql
index 4189a5a4e09..bbd3834b634 100644
--- a/src/test/regress/sql/opr_sanity.sql
+++ b/src/test/regress/sql/opr_sanity.sql
@@ -42,6 +42,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;
@@ -166,7 +170,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::regtype, p2.prorettype::regtype
 FROM pg_proc AS p1, pg_proc AS p2
@@ -176,6 +181,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;
 
@@ -187,6 +194,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;
 
@@ -198,6 +207,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;
 
@@ -281,16 +292,19 @@ WHERE p1.prorettype IN
      '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;
 
--- anyrange is tighter than the rest, can only resolve from anyrange input
+-- anyrange and anymultirange are tighter than the rest, can only resolve
+-- from each other
 
 SELECT p1.oid, p1.proname
 FROM pg_proc as p1
-WHERE p1.prorettype = 'anyrange'::regtype
+WHERE p1.prorettype IN ('anyrange'::regtype, 'anymultirange'::regtype)
   AND NOT
-     'anyrange'::regtype = ANY (p1.proargtypes)
+    ('anyrange'::regtype = ANY (p1.proargtypes) OR
+      'anymultirange'::regtype = ANY (p1.proargtypes))
 ORDER BY 2;
 
 -- similarly for the anycompatible family
diff --git a/src/test/regress/sql/polymorphism.sql b/src/test/regress/sql/polymorphism.sql
index ff517fea41a..891b023ada0 100644
--- a/src/test/regress/sql/polymorphism.sql
+++ b/src/test/regress/sql/polymorphism.sql
@@ -71,6 +71,16 @@ select polyf(int4range(42, 49), 11, 4.5) as fail;  -- range type doesn't fit
 
 drop function polyf(x anycompatiblerange, y anycompatible, z anycompatible);
 
+create function polyf(x anycompatiblemultirange, y anycompatible, z anycompatible) returns anycompatiblearray as $$
+  select array[lower(x), upper(x), y, z]
+$$ language sql;
+
+select polyf(multirange(int4range(42, 49)), 11, 2::smallint) as int, polyf(multirange(float8range(4.5, 7.8)), 7.8, 11::real) as num;
+
+select polyf(multirange(int4range(42, 49)), 11, 4.5) as fail;  -- range type doesn't fit
+
+drop function polyf(x anycompatiblemultirange, y anycompatible, z anycompatible);
+
 -- fail, can't infer type:
 create function polyf(x anycompatible) returns anycompatiblerange as $$
   select array[x + 1, x + 2]
@@ -84,6 +94,19 @@ select polyf(int4range(42, 49), array[11]) as int, polyf(float8range(4.5, 7.8),
 
 drop function polyf(x anycompatiblerange, y anycompatiblearray);
 
+-- fail, can't infer type:
+create function polyf(x anycompatible) returns anycompatiblemultirange as $$
+  select array[x + 1, x + 2]
+$$ language sql;
+
+create function polyf(x anycompatiblemultirange, y anycompatiblearray) returns anycompatiblemultirange as $$
+  select x
+$$ language sql;
+
+select polyf(multirange(int4range(42, 49)), array[11]) as int, polyf(multirange(float8range(4.5, 7.8)), array[7]) as num;
+
+drop function polyf(x anycompatiblemultirange, y anycompatiblearray);
+
 create function polyf(a anyelement, b anyarray,
                       c anycompatible, d anycompatible,
                       OUT x anyarray, OUT y anycompatiblearray)
@@ -997,6 +1020,35 @@ returns anycompatiblerange as $$
   select $1
 $$ language sql;
 
+create function anyctest(anycompatible, anycompatiblemultirange)
+returns anycompatiblemultirange as $$
+  select $2
+$$ language sql;
+
+select x, pg_typeof(x) from anyctest(11, multirange(int4range(4,7))) x;
+select x, pg_typeof(x) from anyctest(11, multirange(numrange(4,7))) x;
+select x, pg_typeof(x) from anyctest(11, 12) x;  -- fail
+select x, pg_typeof(x) from anyctest(11.2, multirange(int4range(4,7))) x;  -- fail
+select x, pg_typeof(x) from anyctest(11.2, '{[4,7)}') x;  -- fail
+
+drop function anyctest(anycompatible, anycompatiblemultirange);
+
+create function anyctest(anycompatiblemultirange, anycompatiblemultirange)
+returns anycompatible as $$
+  select lower($1) + upper($2)
+$$ language sql;
+
+select x, pg_typeof(x) from anyctest(multirange(int4range(11,12)), multirange(int4range(4,7))) x;
+select x, pg_typeof(x) from anyctest(multirange(int4range(11,12)), multirange(numrange(4,7))) x; -- fail
+
+drop function anyctest(anycompatiblemultirange, anycompatiblemultirange);
+
+-- fail, can't infer result type:
+create function anyctest(anycompatible)
+returns anycompatiblemultirange as $$
+  select $1
+$$ language sql;
+
 create function anyctest(anycompatiblenonarray, anycompatiblenonarray)
 returns anycompatiblearray as $$
   select array[$1, $2]
diff --git a/src/test/regress/sql/rangetypes.sql b/src/test/regress/sql/rangetypes.sql
index 4048b1d1854..b69efede3ae 100644
--- a/src/test/regress/sql/rangetypes.sql
+++ b/src/test/regress/sql/rangetypes.sql
@@ -118,6 +118,12 @@ select numrange(1.0, 2.0) * numrange(2.0, 3.0);
 select numrange(1.0, 2.0) * numrange(1.5, 3.0);
 select numrange(1.0, 2.0) * numrange(2.5, 3.0);
 
+select range_intersect_agg(nr) from numrange_test;
+select range_intersect_agg(nr) from numrange_test where false;
+select range_intersect_agg(nr) from numrange_test where nr @> 4.0;
+
+analyze numrange_test;
+
 create table numrange_test2(nr numrange);
 create index numrange_test2_hash_idx on numrange_test2 using hash (nr);
 
@@ -528,6 +534,7 @@ reset enable_sort;
 -- OUT/INOUT/TABLE functions
 --
 
+-- infer anyrange from anyrange
 create function outparam_succeed(i anyrange, out r anyrange, out t text)
   as $$ select $1, 'foo'::text $$ language sql;
 
@@ -539,6 +546,13 @@ create function outparam2_succeed(r anyrange, out lu anyarray, out ul anyarray)
 
 select * from outparam2_succeed(int4range(1,11));
 
+-- infer anyarray from anyrange
+create function outparam_succeed2(i anyrange, out r anyarray, out t text)
+  as $$ select ARRAY[upper($1)], 'foo'::text $$ language sql;
+
+select * from outparam_succeed2(int4range(int4range(1,2)));
+
+-- infer anyelement from anyrange
 create function inoutparam_succeed(out i anyelement, inout r anyrange)
   as $$ select upper($1), $1 $$ language sql;
 
diff --git a/src/test/regress/sql/type_sanity.sql b/src/test/regress/sql/type_sanity.sql
index 8c6e614f20a..4739aca84a3 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'));
@@ -154,7 +154,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')
@@ -183,7 +183,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')
@@ -235,7 +235,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')
@@ -264,7 +264,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')
@@ -489,3 +489,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.rngmultitypid
+FROM pg_range p1
+WHERE p1.rngmultitypid IS NULL OR p1.rngmultitypid = 0;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index a9dca717a6d..abbce78210e 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -626,6 +626,7 @@ ExecutorStart_hook_type
 ExpandedArrayHeader
 ExpandedObjectHeader
 ExpandedObjectMethods
+ExpandedMultirangeHeader
 ExpandedRecordFieldInfo
 ExpandedRecordHeader
 ExplainDirectModify_function
@@ -1395,6 +1396,9 @@ MultiXactMember
 MultiXactOffset
 MultiXactStateData
 MultiXactStatus
+MultirangeIOData
+MultirangeParseState
+MultirangeType
 NDBOX
 NODE
 NUMCacheEntry
@@ -2276,6 +2280,7 @@ ShellTypeInfo
 ShippableCacheEntry
 ShippableCacheKey
 ShmemIndexEnt
+ShortRangeType
 ShutdownForeignScan_function
 ShutdownInformation
 ShutdownMode
#143Alexander Korotkov
aekorotkov@gmail.com
In reply to: Alexander Korotkov (#142)
5 attachment(s)
Re: range_agg

On Thu, Dec 17, 2020 at 10:10 PM Alexander Korotkov
<aekorotkov@gmail.com> wrote:

I think this patch is very close to committable. I'm going to spend
some more time further polishing it and commit (if I don't find a
major issue or face objections).

The main patch is committed. I've prepared a set of improvements.
0001 Fixes bug in bsearch comparison functions
0002 Implements missing @> (range,multirange) operator and its commutator
0003 Does refactors signatures of *_internal() multirange functions
0004 Adds cross-type (range, multirange) operators handling to
existing range GiST opclass
0005 Adds support for GiST multirange indexing by approximation of
multirange as the union range with no gaps

The patchset is quite trivial. I'm going to push it if there are no objections.

The SP-GiST handling is more tricky and requires substantial work.

------
Regards,
Alexander Korotkov

Attachments:

0001-Fix-bugs-in-comparison-functions-for-multirange_bsea.patchapplication/octet-stream; name=0001-Fix-bugs-in-comparison-functions-for-multirange_bsea.patchDownload
From 4dfb6e8434a2d8d3e22b88689ad4482dd9187c91 Mon Sep 17 00:00:00 2001
From: Alexander Korotkov <akorotkov@postgresql.org>
Date: Sat, 26 Dec 2020 19:35:10 +0300
Subject: [PATCH 1/5] Fix bugs in comparison functions for
 multirange_bsearch_match()

Two functions multirange_range_overlaps_bsearch_comparison() and
multirange_range_contains_bsearch_comparison() contain bugs of returning -1
instead of 1.  This commit fixes these bugs and adds corresponding regression
tests.
---
 src/backend/utils/adt/multirangetypes.c       |  4 ++--
 src/test/regress/expected/multirangetypes.out | 12 ++++++++++++
 src/test/regress/sql/multirangetypes.sql      |  2 ++
 3 files changed, 16 insertions(+), 2 deletions(-)

diff --git a/src/backend/utils/adt/multirangetypes.c b/src/backend/utils/adt/multirangetypes.c
index 06316ba6b65..46f661fee49 100644
--- a/src/backend/utils/adt/multirangetypes.c
+++ b/src/backend/utils/adt/multirangetypes.c
@@ -1660,7 +1660,7 @@ multirange_range_contains_bsearch_comparison(TypeCacheEntry *typcache,
 	if (range_cmp_bounds(typcache, keyUpper, lower) < 0)
 		return -1;
 	if (range_cmp_bounds(typcache, keyLower, upper) > 0)
-		return -1;
+		return 1;
 
 	/*
 	 * At this point we found overlapping range.  But we have to check if it
@@ -1825,7 +1825,7 @@ multirange_range_overlaps_bsearch_comparison(TypeCacheEntry *typcache,
 	if (range_cmp_bounds(typcache, keyUpper, lower) < 0)
 		return -1;
 	if (range_cmp_bounds(typcache, keyLower, upper) > 0)
-		return -1;
+		return 1;
 
 	*match = true;
 	return 0;
diff --git a/src/test/regress/expected/multirangetypes.out b/src/test/regress/expected/multirangetypes.out
index e81e565cab7..180aa1e8a53 100644
--- a/src/test/regress/expected/multirangetypes.out
+++ b/src/test/regress/expected/multirangetypes.out
@@ -834,6 +834,12 @@ SELECT nummultirange(numrange(1,2), numrange(3.5,8)) && nummultirange(numrange(3
  t
 (1 row)
 
+select '{(10,20),(30,40),(50,60)}'::nummultirange && '(42,92)'::numrange;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- contains
 SELECT nummultirange() @> nummultirange();
  ?column? 
@@ -967,6 +973,12 @@ SELECT '{[1,5), [6,9)}'::nummultirange @> '{[6,7)}';
  t
 (1 row)
 
+select '{(10,20),(30,40),(50,60)}'::nummultirange @> '(52,56)'::numrange;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- is contained by
 SELECT nummultirange() <@ nummultirange();
  ?column? 
diff --git a/src/test/regress/sql/multirangetypes.sql b/src/test/regress/sql/multirangetypes.sql
index 9be26f10d38..c9f84cf81d4 100644
--- a/src/test/regress/sql/multirangetypes.sql
+++ b/src/test/regress/sql/multirangetypes.sql
@@ -162,6 +162,7 @@ SELECT nummultirange(numrange(1,2), numrange(7,8)) && nummultirange(numrange(3,4
 SELECT nummultirange(numrange(3,4)) && nummultirange(numrange(1,2), numrange(3.5,8));
 SELECT nummultirange(numrange(1,2), numrange(3.5,8)) && numrange(3,4);
 SELECT nummultirange(numrange(1,2), numrange(3.5,8)) && nummultirange(numrange(3,4));
+select '{(10,20),(30,40),(50,60)}'::nummultirange && '(42,92)'::numrange;
 
 -- contains
 SELECT nummultirange() @> nummultirange();
@@ -186,6 +187,7 @@ SELECT '{[-4,-2), [1,5)}'::nummultirange @> '{[1,5)}';
 SELECT '{[1,5), [8,9)}'::nummultirange @> '{[1,5)}';
 SELECT '{[1,5), [8,9)}'::nummultirange @> '{[6,7)}';
 SELECT '{[1,5), [6,9)}'::nummultirange @> '{[6,7)}';
+select '{(10,20),(30,40),(50,60)}'::nummultirange @> '(52,56)'::numrange;
 
 -- is contained by
 SELECT nummultirange() <@ nummultirange();
-- 
2.24.3 (Apple Git-128)

0002-Implement-operators-for-checking-if-the-range-contai.patchapplication/octet-stream; name=0002-Implement-operators-for-checking-if-the-range-contai.patchDownload
From 505b9c4e77168040cd337ac8c8b6152bb0ec3d42 Mon Sep 17 00:00:00 2001
From: Alexander Korotkov <akorotkov@postgresql.org>
Date: Fri, 25 Dec 2020 17:25:01 +0300
Subject: [PATCH 2/5] Implement operators for checking if the range contains a
 multirange

We have operators for checking if the multirange contains a range but don't
have the opposite.  This commit improves completeness of the operator set by
adding two new operators: @> (anyrange,anymultirange) and
<@(anymultirange,anyrange).
---
 src/backend/utils/adt/multirangetypes.c       |  60 +++++
 .../utils/adt/multirangetypes_selfuncs.c      |   6 +-
 src/include/catalog/pg_operator.dat           |  12 +
 src/include/catalog/pg_proc.dat               |   8 +-
 src/include/utils/multirangetypes.h           |   4 +-
 src/test/regress/expected/multirangetypes.out | 240 ++++++++++++++++++
 src/test/regress/sql/multirangetypes.sql      |  40 +++
 7 files changed, 367 insertions(+), 3 deletions(-)

diff --git a/src/backend/utils/adt/multirangetypes.c b/src/backend/utils/adt/multirangetypes.c
index 46f661fee49..4b86be583ef 100644
--- a/src/backend/utils/adt/multirangetypes.c
+++ b/src/backend/utils/adt/multirangetypes.c
@@ -1631,6 +1631,18 @@ multirange_contains_range(PG_FUNCTION_ARGS)
 	PG_RETURN_BOOL(multirange_contains_range_internal(typcache, mr, r));
 }
 
+Datum
+range_contains_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(range_contains_multirange_internal(typcache, r, mr));
+}
+
 /* contained by? */
 Datum
 range_contained_by_multirange(PG_FUNCTION_ARGS)
@@ -1644,6 +1656,18 @@ range_contained_by_multirange(PG_FUNCTION_ARGS)
 	PG_RETURN_BOOL(multirange_contains_range_internal(typcache, mr, r));
 }
 
+Datum
+multirange_contained_by_range(PG_FUNCTION_ARGS)
+{
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(0);
+	RangeType  *r = PG_GETARG_RANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(range_contains_multirange_internal(typcache, r, mr));
+}
+
 /*
  * Comparison function for checking if any range of multirange contains given
  * key range using binary search.
@@ -1701,6 +1725,42 @@ multirange_contains_range_internal(TypeCacheEntry *typcache, MultirangeType *mr,
 									multirange_range_contains_bsearch_comparison);
 }
 
+/*
+ * Test whether range r contains a multirange mr.
+ */
+bool
+range_contains_multirange_internal(TypeCacheEntry *typcache, RangeType *r,
+								   MultirangeType *mr)
+{
+	TypeCacheEntry *rangetyp;
+	RangeBound	lower1,
+				upper1,
+				lower2,
+				upper2,
+				tmp;
+	bool		empty;
+
+	rangetyp = typcache->rngtype;
+
+	/*
+	 * Every range contains an infinite number of empty multiranges, even an
+	 * empty one.
+	 */
+	if (MultirangeIsEmpty(mr))
+		return true;
+
+	if (RangeIsEmpty(r))
+		return false;
+
+	/* Range contains multirange iff it contains its union range. */
+	range_deserialize(rangetyp, r, &lower1, &upper1, &empty);
+	Assert(!empty);
+	multirange_get_bounds(rangetyp, mr, 0, &lower2, &tmp);
+	multirange_get_bounds(rangetyp, mr, mr->rangeCount - 1, &tmp, &upper2);
+
+	return range_bounds_contains(rangetyp, &lower1, &upper1, &lower2, &upper2);
+}
+
 
 /* multirange, multirange -> bool functions */
 
diff --git a/src/backend/utils/adt/multirangetypes_selfuncs.c b/src/backend/utils/adt/multirangetypes_selfuncs.c
index 7259af0b853..bb016b6e987 100644
--- a/src/backend/utils/adt/multirangetypes_selfuncs.c
+++ b/src/backend/utils/adt/multirangetypes_selfuncs.c
@@ -86,6 +86,8 @@ default_multirange_selectivity(Oid operator)
 		case OID_RANGE_OVERLAPS_MULTIRANGE_OP:
 			return 0.01;
 
+		case OID_RANGE_CONTAINS_MULTIRANGE_OP:
+		case OID_RANGE_MULTIRANGE_CONTAINED_OP:
 		case OID_MULTIRANGE_CONTAINS_RANGE_OP:
 		case OID_MULTIRANGE_CONTAINS_MULTIRANGE_OP:
 		case OID_MULTIRANGE_RANGE_CONTAINED_OP:
@@ -224,7 +226,8 @@ multirangesel(PG_FUNCTION_ARGS)
 											  1, &constrange);
 		}
 	}
-	else if (operator == OID_MULTIRANGE_CONTAINS_RANGE_OP ||
+	else if (operator == OID_RANGE_MULTIRANGE_CONTAINED_OP ||
+			 operator == OID_MULTIRANGE_CONTAINS_RANGE_OP ||
 			 operator == OID_MULTIRANGE_OVERLAPS_RANGE_OP ||
 			 operator == OID_MULTIRANGE_OVERLAPS_LEFT_RANGE_OP ||
 			 operator == OID_MULTIRANGE_OVERLAPS_RIGHT_RANGE_OP ||
@@ -248,6 +251,7 @@ multirangesel(PG_FUNCTION_ARGS)
 			 operator == OID_RANGE_OVERLAPS_RIGHT_MULTIRANGE_OP ||
 			 operator == OID_RANGE_LEFT_MULTIRANGE_OP ||
 			 operator == OID_RANGE_RIGHT_MULTIRANGE_OP ||
+			 operator == OID_RANGE_CONTAINS_MULTIRANGE_OP ||
 			 operator == OID_MULTIRANGE_ELEM_CONTAINED_OP ||
 			 operator == OID_MULTIRANGE_RANGE_CONTAINED_OP)
 	{
diff --git a/src/include/catalog/pg_operator.dat b/src/include/catalog/pg_operator.dat
index bd0c3d0f81a..f7fd74826d9 100644
--- a/src/include/catalog/pg_operator.dat
+++ b/src/include/catalog/pg_operator.dat
@@ -3360,6 +3360,18 @@
   oprresult => 'bool', oprcom => '@>(anymultirange,anymultirange)',
   oprcode => 'multirange_contained_by_multirange', oprrest => 'multirangesel',
   oprjoin => 'contjoinsel' },
+{ oid => '8459', oid_symbol => 'OID_RANGE_CONTAINS_MULTIRANGE_OP',
+  descr => 'contains',
+  oprname => '@>', oprleft => 'anyrange', oprright => 'anymultirange',
+  oprresult => 'bool', oprcom => '<@(anymultirange,anyrange)',
+  oprcode => 'range_contains_multirange', oprrest => 'multirangesel',
+  oprjoin => 'contjoinsel' },
+{ oid => '8460', oid_symbol => 'OID_RANGE_MULTIRANGE_CONTAINED_OP',
+  descr => 'is contained by',
+  oprname => '<@', oprleft => 'anymultirange', oprright => 'anyrange',
+  oprresult => 'bool', oprcom => '@>(anyrange,anymultirange)',
+  oprcode => 'multirange_contained_by_range', oprrest => 'multirangesel',
+  oprjoin => 'contjoinsel' },
 { oid => '2875', oid_symbol => 'OID_RANGE_OVERLAPS_LEFT_MULTIRANGE_OP',
   descr => 'overlaps or is left of',
   oprname => '&<', oprleft => 'anyrange', oprright => 'anymultirange',
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 22970f46cd7..9d19eb5f409 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10085,7 +10085,13 @@
 { oid => '4253',
   proname => 'range_contained_by_multirange', prorettype => 'bool',
   proargtypes => 'anyrange anymultirange', prosrc => 'range_contained_by_multirange' },
-{ oid => '4254',
+{ oid => '8457',
+  proname => 'range_contains_multirange', prorettype => 'bool',
+  proargtypes => 'anyrange anymultirange', prosrc => 'range_contains_multirange' },
+{ oid => '8458',
+  proname => 'multirange_contained_by_range', prorettype => 'bool',
+  proargtypes => 'anymultirange anyrange', prosrc => 'multirange_contained_by_range' },
+  { oid => '4254',
   proname => 'multirange_contained_by_multirange', prorettype => 'bool',
   proargtypes => 'anymultirange anymultirange', prosrc => 'multirange_contained_by_multirange' },
 { oid => '4255',
diff --git a/src/include/utils/multirangetypes.h b/src/include/utils/multirangetypes.h
index 4cf72415701..f2290aac274 100644
--- a/src/include/utils/multirangetypes.h
+++ b/src/include/utils/multirangetypes.h
@@ -64,6 +64,8 @@ extern bool multirange_contains_elem_internal(TypeCacheEntry *typcache, Multiran
 											  Datum elem);
 extern bool multirange_contains_range_internal(TypeCacheEntry *typcache, MultirangeType *mr,
 											   RangeType *r);
+extern bool range_contains_multirange_internal(TypeCacheEntry *typcache, RangeType *r,
+											   MultirangeType *mr);
 extern bool multirange_contains_multirange_internal(TypeCacheEntry *typcache,
 													MultirangeType *mr1,
 													MultirangeType *mr2);
@@ -77,7 +79,7 @@ extern bool range_before_multirange_internal(TypeCacheEntry *typcache, RangeType
 extern bool range_after_multirange_internal(TypeCacheEntry *typcache, RangeType *r,
 											MultirangeType *mr);
 extern bool range_adjacent_multirange_internal(TypeCacheEntry *typcache, RangeType *r,
-											MultirangeType *mr);
+											   MultirangeType *mr);
 extern bool multirange_before_multirange_internal(TypeCacheEntry *typcache,
 												  MultirangeType *mr1,
 												  MultirangeType *mr2);
diff --git a/src/test/regress/expected/multirangetypes.out b/src/test/regress/expected/multirangetypes.out
index 180aa1e8a53..aa7232efc57 100644
--- a/src/test/regress/expected/multirangetypes.out
+++ b/src/test/regress/expected/multirangetypes.out
@@ -979,6 +979,126 @@ select '{(10,20),(30,40),(50,60)}'::nummultirange @> '(52,56)'::numrange;
  t
 (1 row)
 
+SELECT numrange(null,null) @> nummultirange(numrange(1,2));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(null,null) @> nummultirange(numrange(null,2));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(null,null) @> nummultirange(numrange(2,null));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(null,5) @> nummultirange(numrange(null,3));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(null,5) @> nummultirange(numrange(null,8));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(5,null) @> nummultirange(numrange(8,null));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(5,null) @> nummultirange(numrange(3,null));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(1,5) @> nummultirange(numrange(8,9));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(1,5) @> nummultirange(numrange(3,9));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(1,5) @> nummultirange(numrange(1,4));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,5) @> nummultirange(numrange(1,5));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,9) @> nummultirange(numrange(-4,-2), numrange(1,5));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT numrange(1,9) @> nummultirange(numrange(1,5), numrange(8,9));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,9) @> nummultirange(numrange(1,5), numrange(6,9));
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT numrange(1,9) @> nummultirange(numrange(1,5), numrange(6,10));
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT '{[1,9)}' @> '{[1,5)}'::nummultirange;
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[1,9)}' @> '{[-4,-2), [1,5)}'::nummultirange;
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT '{[1,9)}' @> '{[1,5), [8,9)}'::nummultirange;
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[1,9)}' @> '{[1,5), [6,9)}'::nummultirange;
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[1,9)}' @> '{[1,5), [6,10)}'::nummultirange;
+ ?column? 
+----------
+ f
+(1 row)
+
 -- is contained by
 SELECT nummultirange() <@ nummultirange();
  ?column? 
@@ -1112,6 +1232,126 @@ SELECT '{[6,7)}' <@ '{[1,5), [6,9)}'::nummultirange;
  t
 (1 row)
 
+SELECT nummultirange(numrange(1,2)) <@ numrange(null,null);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(null,2)) <@ numrange(null,null);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(2,null)) <@ numrange(null,null);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(null,3)) <@ numrange(null,5);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(null,8)) <@ numrange(null,5);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(8,null)) <@ numrange(5,null);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(3,null)) <@ numrange(5,null);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(8,9)) <@ numrange(1,5);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(3,9)) <@ numrange(1,5);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,4)) <@ numrange(1,5);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,5)) <@ numrange(1,5);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(-4,-2), numrange(1,5)) <@ numrange(1,9);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT nummultirange(numrange(1,5), numrange(8,9)) <@ numrange(1,9);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,5), numrange(6,9)) <@ numrange(1,9);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT nummultirange(numrange(1,5), numrange(6,10)) <@ numrange(1,9);
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT '{[1,5)}'::nummultirange <@ '{[1,9)}';
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[-4,-2), [1,5)}'::nummultirange <@ '{[1,9)}';
+ ?column? 
+----------
+ f
+(1 row)
+
+SELECT '{[1,5), [8,9)}'::nummultirange <@ '{[1,9)}';
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[1,5), [6,9)}'::nummultirange <@ '{[1,9)}';
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT '{[1,5), [6,10)}'::nummultirange <@ '{[1,9)}';
+ ?column? 
+----------
+ f
+(1 row)
+
 -- overleft
 SELECT 'empty'::numrange &< nummultirange();
  ?column? 
diff --git a/src/test/regress/sql/multirangetypes.sql b/src/test/regress/sql/multirangetypes.sql
index c9f84cf81d4..9d7af404c98 100644
--- a/src/test/regress/sql/multirangetypes.sql
+++ b/src/test/regress/sql/multirangetypes.sql
@@ -188,6 +188,26 @@ SELECT '{[1,5), [8,9)}'::nummultirange @> '{[1,5)}';
 SELECT '{[1,5), [8,9)}'::nummultirange @> '{[6,7)}';
 SELECT '{[1,5), [6,9)}'::nummultirange @> '{[6,7)}';
 select '{(10,20),(30,40),(50,60)}'::nummultirange @> '(52,56)'::numrange;
+SELECT numrange(null,null) @> nummultirange(numrange(1,2));
+SELECT numrange(null,null) @> nummultirange(numrange(null,2));
+SELECT numrange(null,null) @> nummultirange(numrange(2,null));
+SELECT numrange(null,5) @> nummultirange(numrange(null,3));
+SELECT numrange(null,5) @> nummultirange(numrange(null,8));
+SELECT numrange(5,null) @> nummultirange(numrange(8,null));
+SELECT numrange(5,null) @> nummultirange(numrange(3,null));
+SELECT numrange(1,5) @> nummultirange(numrange(8,9));
+SELECT numrange(1,5) @> nummultirange(numrange(3,9));
+SELECT numrange(1,5) @> nummultirange(numrange(1,4));
+SELECT numrange(1,5) @> nummultirange(numrange(1,5));
+SELECT numrange(1,9) @> nummultirange(numrange(-4,-2), numrange(1,5));
+SELECT numrange(1,9) @> nummultirange(numrange(1,5), numrange(8,9));
+SELECT numrange(1,9) @> nummultirange(numrange(1,5), numrange(6,9));
+SELECT numrange(1,9) @> nummultirange(numrange(1,5), numrange(6,10));
+SELECT '{[1,9)}' @> '{[1,5)}'::nummultirange;
+SELECT '{[1,9)}' @> '{[-4,-2), [1,5)}'::nummultirange;
+SELECT '{[1,9)}' @> '{[1,5), [8,9)}'::nummultirange;
+SELECT '{[1,9)}' @> '{[1,5), [6,9)}'::nummultirange;
+SELECT '{[1,9)}' @> '{[1,5), [6,10)}'::nummultirange;
 
 -- is contained by
 SELECT nummultirange() <@ nummultirange();
@@ -212,6 +232,26 @@ SELECT '{[1,5)}' <@ '{[-4,-2), [1,5)}'::nummultirange;
 SELECT '{[1,5)}' <@ '{[1,5), [8,9)}'::nummultirange;
 SELECT '{[6,7)}' <@ '{[1,5), [8,9)}'::nummultirange;
 SELECT '{[6,7)}' <@ '{[1,5), [6,9)}'::nummultirange;
+SELECT nummultirange(numrange(1,2)) <@ numrange(null,null);
+SELECT nummultirange(numrange(null,2)) <@ numrange(null,null);
+SELECT nummultirange(numrange(2,null)) <@ numrange(null,null);
+SELECT nummultirange(numrange(null,3)) <@ numrange(null,5);
+SELECT nummultirange(numrange(null,8)) <@ numrange(null,5);
+SELECT nummultirange(numrange(8,null)) <@ numrange(5,null);
+SELECT nummultirange(numrange(3,null)) <@ numrange(5,null);
+SELECT nummultirange(numrange(8,9)) <@ numrange(1,5);
+SELECT nummultirange(numrange(3,9)) <@ numrange(1,5);
+SELECT nummultirange(numrange(1,4)) <@ numrange(1,5);
+SELECT nummultirange(numrange(1,5)) <@ numrange(1,5);
+SELECT nummultirange(numrange(-4,-2), numrange(1,5)) <@ numrange(1,9);
+SELECT nummultirange(numrange(1,5), numrange(8,9)) <@ numrange(1,9);
+SELECT nummultirange(numrange(1,5), numrange(6,9)) <@ numrange(1,9);
+SELECT nummultirange(numrange(1,5), numrange(6,10)) <@ numrange(1,9);
+SELECT '{[1,5)}'::nummultirange <@ '{[1,9)}';
+SELECT '{[-4,-2), [1,5)}'::nummultirange <@ '{[1,9)}';
+SELECT '{[1,5), [8,9)}'::nummultirange <@ '{[1,9)}';
+SELECT '{[1,5), [6,9)}'::nummultirange <@ '{[1,9)}';
+SELECT '{[1,5), [6,10)}'::nummultirange <@ '{[1,9)}';
 
 -- overleft
 SELECT 'empty'::numrange &< nummultirange();
-- 
2.24.3 (Apple Git-128)

0003-Improve-the-signature-of-internal-multirange-functio.patchapplication/octet-stream; name=0003-Improve-the-signature-of-internal-multirange-functio.patchDownload
From 5cfc2a69dbe713028f94874a89c47290e485dcc2 Mon Sep 17 00:00:00 2001
From: Alexander Korotkov <akorotkov@postgresql.org>
Date: Fri, 25 Dec 2020 17:38:00 +0300
Subject: [PATCH 3/5] Improve the signature of internal multirange functions

There is a set of *_internal() functions exposed in
include/utils/multirangetypes.h.  This commit improves the signatures of these
functions in two ways.
 * Add const qualifies where applicable.
 * Replace multirange typecache argument with range typecache argument.
   Multirange typecache was used solely to find the range typecache.  At the
   same time, range typecache is easier for the caller to find.
---
 src/backend/utils/adt/multirangetypes.c | 206 +++++++++++++-----------
 src/include/utils/multirangetypes.h     |  69 ++++----
 2 files changed, 153 insertions(+), 122 deletions(-)

diff --git a/src/backend/utils/adt/multirangetypes.c b/src/backend/utils/adt/multirangetypes.c
index 4b86be583ef..a77299147e7 100644
--- a/src/backend/utils/adt/multirangetypes.c
+++ b/src/backend/utils/adt/multirangetypes.c
@@ -847,7 +847,7 @@ range_bounds_contains(TypeCacheEntry *typcache,
  * that would count as a mismatch.
  */
 static bool
-multirange_bsearch_match(TypeCacheEntry *typcache, MultirangeType *mr,
+multirange_bsearch_match(TypeCacheEntry *typcache, const MultirangeType *mr,
 						 void *key, multirange_bsearch_comparison cmp_func)
 {
 	uint32		l,
@@ -1552,7 +1552,7 @@ multirange_contains_elem(PG_FUNCTION_ARGS)
 
 	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
 
-	PG_RETURN_BOOL(multirange_contains_elem_internal(typcache, mr, val));
+	PG_RETURN_BOOL(multirange_contains_elem_internal(typcache->rngtype, mr, val));
 }
 
 /* contained by? */
@@ -1565,7 +1565,7 @@ elem_contained_by_multirange(PG_FUNCTION_ARGS)
 
 	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
 
-	PG_RETURN_BOOL(multirange_contains_elem_internal(typcache, mr, val));
+	PG_RETURN_BOOL(multirange_contains_elem_internal(typcache->rngtype, mr, val));
 }
 
 /*
@@ -1606,13 +1606,13 @@ multirange_elem_bsearch_comparison(TypeCacheEntry *typcache,
  * Test whether multirange mr contains a specific element value.
  */
 bool
-multirange_contains_elem_internal(TypeCacheEntry *typcache,
-								  MultirangeType *mr, Datum val)
+multirange_contains_elem_internal(TypeCacheEntry *rangetyp,
+								  const MultirangeType *mr, Datum val)
 {
 	if (MultirangeIsEmpty(mr))
 		return false;
 
-	return multirange_bsearch_match(typcache->rngtype, mr, &val,
+	return multirange_bsearch_match(rangetyp, mr, &val,
 									multirange_elem_bsearch_comparison);
 }
 
@@ -1628,7 +1628,7 @@ multirange_contains_range(PG_FUNCTION_ARGS)
 
 	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
 
-	PG_RETURN_BOOL(multirange_contains_range_internal(typcache, mr, r));
+	PG_RETURN_BOOL(multirange_contains_range_internal(typcache->rngtype, mr, r));
 }
 
 Datum
@@ -1640,7 +1640,7 @@ range_contains_multirange(PG_FUNCTION_ARGS)
 
 	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
 
-	PG_RETURN_BOOL(range_contains_multirange_internal(typcache, r, mr));
+	PG_RETURN_BOOL(range_contains_multirange_internal(typcache->rngtype, r, mr));
 }
 
 /* contained by? */
@@ -1653,7 +1653,7 @@ range_contained_by_multirange(PG_FUNCTION_ARGS)
 
 	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
 
-	PG_RETURN_BOOL(multirange_contains_range_internal(typcache, mr, r));
+	PG_RETURN_BOOL(multirange_contains_range_internal(typcache->rngtype, mr, r));
 }
 
 Datum
@@ -1665,7 +1665,7 @@ multirange_contained_by_range(PG_FUNCTION_ARGS)
 
 	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
 
-	PG_RETURN_BOOL(range_contains_multirange_internal(typcache, r, mr));
+	PG_RETURN_BOOL(range_contains_multirange_internal(typcache->rngtype, r, mr));
 }
 
 /*
@@ -1700,14 +1700,13 @@ multirange_range_contains_bsearch_comparison(TypeCacheEntry *typcache,
  * Test whether multirange mr contains a specific range r.
  */
 bool
-multirange_contains_range_internal(TypeCacheEntry *typcache, MultirangeType *mr, RangeType *r)
+multirange_contains_range_internal(TypeCacheEntry *rangetyp,
+								   const MultirangeType *mr,
+								   const RangeType *r)
 {
-	TypeCacheEntry *rangetyp;
 	RangeBound	bounds[2];
 	bool		empty;
 
-	rangetyp = typcache->rngtype;
-
 	/*
 	 * Every multirange contains an infinite number of empty ranges, even an
 	 * empty one.
@@ -1729,10 +1728,10 @@ multirange_contains_range_internal(TypeCacheEntry *typcache, MultirangeType *mr,
  * Test whether range r contains a multirange mr.
  */
 bool
-range_contains_multirange_internal(TypeCacheEntry *typcache, RangeType *r,
-								   MultirangeType *mr)
+range_contains_multirange_internal(TypeCacheEntry *rangetyp,
+								   const RangeType *r,
+								   const MultirangeType *mr)
 {
-	TypeCacheEntry *rangetyp;
 	RangeBound	lower1,
 				upper1,
 				lower2,
@@ -1740,8 +1739,6 @@ range_contains_multirange_internal(TypeCacheEntry *typcache, RangeType *r,
 				tmp;
 	bool		empty;
 
-	rangetyp = typcache->rngtype;
-
 	/*
 	 * Every range contains an infinite number of empty multiranges, even an
 	 * empty one.
@@ -1766,9 +1763,10 @@ range_contains_multirange_internal(TypeCacheEntry *typcache, RangeType *r,
 
 /* equality (internal version) */
 bool
-multirange_eq_internal(TypeCacheEntry *typcache, MultirangeType *mr1, MultirangeType *mr2)
+multirange_eq_internal(TypeCacheEntry *rangetyp,
+					   const MultirangeType *mr1,
+					   const MultirangeType *mr2)
 {
-	TypeCacheEntry *rangetyp = typcache->rngtype;
 	int32		range_count_1;
 	int32		range_count_2;
 	int32		i;
@@ -1810,14 +1808,16 @@ multirange_eq(PG_FUNCTION_ARGS)
 
 	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr1));
 
-	PG_RETURN_BOOL(multirange_eq_internal(typcache, mr1, mr2));
+	PG_RETURN_BOOL(multirange_eq_internal(typcache->rngtype, mr1, mr2));
 }
 
 /* inequality (internal version) */
 bool
-multirange_ne_internal(TypeCacheEntry *typcache, MultirangeType *mr1, MultirangeType *mr2)
+multirange_ne_internal(TypeCacheEntry *rangetyp,
+					   const MultirangeType *mr1,
+					   const MultirangeType *mr2)
 {
-	return (!multirange_eq_internal(typcache, mr1, mr2));
+	return (!multirange_eq_internal(rangetyp, mr1, mr2));
 }
 
 /* inequality */
@@ -1830,7 +1830,7 @@ multirange_ne(PG_FUNCTION_ARGS)
 
 	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr1));
 
-	PG_RETURN_BOOL(multirange_ne_internal(typcache, mr1, mr2));
+	PG_RETURN_BOOL(multirange_ne_internal(typcache->rngtype, mr1, mr2));
 }
 
 /* overlaps? */
@@ -1843,7 +1843,7 @@ range_overlaps_multirange(PG_FUNCTION_ARGS)
 
 	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
 
-	PG_RETURN_BOOL(range_overlaps_multirange_internal(typcache, r, mr));
+	PG_RETURN_BOOL(range_overlaps_multirange_internal(typcache->rngtype, r, mr));
 }
 
 Datum
@@ -1855,7 +1855,7 @@ multirange_overlaps_range(PG_FUNCTION_ARGS)
 
 	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
 
-	PG_RETURN_BOOL(range_overlaps_multirange_internal(typcache, r, mr));
+	PG_RETURN_BOOL(range_overlaps_multirange_internal(typcache->rngtype, r, mr));
 }
 
 Datum
@@ -1867,7 +1867,7 @@ multirange_overlaps_multirange(PG_FUNCTION_ARGS)
 
 	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr1));
 
-	PG_RETURN_BOOL(multirange_overlaps_multirange_internal(typcache, mr1, mr2));
+	PG_RETURN_BOOL(multirange_overlaps_multirange_internal(typcache->rngtype, mr1, mr2));
 }
 
 /*
@@ -1892,14 +1892,13 @@ multirange_range_overlaps_bsearch_comparison(TypeCacheEntry *typcache,
 }
 
 bool
-range_overlaps_multirange_internal(TypeCacheEntry *typcache, RangeType *r, MultirangeType *mr)
+range_overlaps_multirange_internal(TypeCacheEntry *rangetyp,
+								   const RangeType *r,
+								   const MultirangeType *mr)
 {
-	TypeCacheEntry *rangetyp;
 	RangeBound	bounds[2];
 	bool		empty;
 
-	rangetyp = typcache->rngtype;
-
 	/*
 	 * Empties never overlap, even with empties. (This seems strange since
 	 * they *do* contain each other, but we want to follow how ranges work.)
@@ -1915,10 +1914,10 @@ range_overlaps_multirange_internal(TypeCacheEntry *typcache, RangeType *r, Multi
 }
 
 bool
-multirange_overlaps_multirange_internal(TypeCacheEntry *typcache, MultirangeType *mr1,
-										MultirangeType *mr2)
+multirange_overlaps_multirange_internal(TypeCacheEntry *rangetyp,
+										const MultirangeType *mr1,
+										const MultirangeType *mr2)
 {
-	TypeCacheEntry *rangetyp;
 	int32		range_count1;
 	int32		range_count2;
 	int32		i1;
@@ -1935,8 +1934,6 @@ multirange_overlaps_multirange_internal(TypeCacheEntry *typcache, MultirangeType
 	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
 		return false;
 
-	rangetyp = typcache->rngtype;
-
 	range_count1 = mr1->rangeCount;
 	range_count2 = mr2->rangeCount;
 
@@ -1974,12 +1971,11 @@ multirange_overlaps_multirange_internal(TypeCacheEntry *typcache, MultirangeType
 }
 
 /* does not extend to right of? */
-Datum
-range_overleft_multirange(PG_FUNCTION_ARGS)
+bool
+range_overleft_multirange_internal(TypeCacheEntry *rangetyp,
+								   const RangeType *r,
+								   const MultirangeType *mr)
 {
-	RangeType  *r = PG_GETARG_RANGE_P(0);
-	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
-	TypeCacheEntry *typcache;
 	RangeBound	lower1,
 				upper1,
 				lower2,
@@ -1989,14 +1985,25 @@ range_overleft_multirange(PG_FUNCTION_ARGS)
 	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
 		PG_RETURN_BOOL(false);
 
-	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
 
-	range_deserialize(typcache->rngtype, r, &lower1, &upper1, &empty);
+	range_deserialize(rangetyp, r, &lower1, &upper1, &empty);
 	Assert(!empty);
-	multirange_get_bounds(typcache->rngtype, mr, mr->rangeCount - 1,
+	multirange_get_bounds(rangetyp, mr, mr->rangeCount - 1,
 						  &lower2, &upper2);
 
-	PG_RETURN_BOOL(range_cmp_bounds(typcache->rngtype, &upper1, &upper2) <= 0);
+	PG_RETURN_BOOL(range_cmp_bounds(rangetyp, &upper1, &upper2) <= 0);
+}
+
+Datum
+range_overleft_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(range_overleft_multirange_internal(typcache->rngtype, r, mr));
 }
 
 Datum
@@ -2049,12 +2056,11 @@ multirange_overleft_multirange(PG_FUNCTION_ARGS)
 }
 
 /* does not extend to left of? */
-Datum
-range_overright_multirange(PG_FUNCTION_ARGS)
+bool
+range_overright_multirange_internal(TypeCacheEntry *rangetyp,
+									const RangeType *r,
+									const MultirangeType *mr)
 {
-	RangeType  *r = PG_GETARG_RANGE_P(0);
-	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
-	TypeCacheEntry *typcache;
 	RangeBound	lower1,
 				upper1,
 				lower2,
@@ -2064,13 +2070,23 @@ range_overright_multirange(PG_FUNCTION_ARGS)
 	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
 		PG_RETURN_BOOL(false);
 
-	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
-
-	range_deserialize(typcache->rngtype, r, &lower1, &upper1, &empty);
+	range_deserialize(rangetyp, r, &lower1, &upper1, &empty);
 	Assert(!empty);
-	multirange_get_bounds(typcache->rngtype, mr, 0, &lower2, &upper2);
+	multirange_get_bounds(rangetyp, mr, 0, &lower2, &upper2);
 
-	PG_RETURN_BOOL(range_cmp_bounds(typcache->rngtype, &lower1, &lower2) >= 0);
+	return (range_cmp_bounds(rangetyp, &lower1, &lower2) >= 0);
+}
+
+Datum
+range_overright_multirange(PG_FUNCTION_ARGS)
+{
+	RangeType  *r = PG_GETARG_RANGE_P(0);
+	MultirangeType *mr = PG_GETARG_MULTIRANGE_P(1);
+	TypeCacheEntry *typcache;
+
+	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+
+	PG_RETURN_BOOL(range_overright_multirange_internal(typcache->rngtype, r, mr));
 }
 
 Datum
@@ -2129,7 +2145,7 @@ multirange_contains_multirange(PG_FUNCTION_ARGS)
 
 	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr1));
 
-	PG_RETURN_BOOL(multirange_contains_multirange_internal(typcache, mr1, mr2));
+	PG_RETURN_BOOL(multirange_contains_multirange_internal(typcache->rngtype, mr1, mr2));
 }
 
 /* contained by? */
@@ -2142,17 +2158,17 @@ multirange_contained_by_multirange(PG_FUNCTION_ARGS)
 
 	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr1));
 
-	PG_RETURN_BOOL(multirange_contains_multirange_internal(typcache, mr2, mr1));
+	PG_RETURN_BOOL(multirange_contains_multirange_internal(typcache->rngtype, mr2, mr1));
 }
 
 /*
  * Test whether multirange mr1 contains every range from another multirange mr2.
  */
 bool
-multirange_contains_multirange_internal(TypeCacheEntry *typcache,
-										MultirangeType *mr1, MultirangeType *mr2)
+multirange_contains_multirange_internal(TypeCacheEntry *rangetyp,
+										const MultirangeType *mr1,
+										const MultirangeType *mr2)
 {
-	TypeCacheEntry *rangetyp;
 	int32		range_count1 = mr1->rangeCount;
 	int32		range_count2 = mr2->rangeCount;
 	int			i1,
@@ -2162,8 +2178,6 @@ multirange_contains_multirange_internal(TypeCacheEntry *typcache,
 				lower2,
 				upper2;
 
-	rangetyp = typcache->rngtype;
-
 	/*
 	 * We follow the same logic for empties as ranges: - an empty multirange
 	 * contains an empty range/multirange. - an empty multirange can't contain
@@ -2221,7 +2235,7 @@ range_before_multirange(PG_FUNCTION_ARGS)
 
 	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
 
-	PG_RETURN_BOOL(range_before_multirange_internal(typcache, r, mr));
+	PG_RETURN_BOOL(range_before_multirange_internal(typcache->rngtype, r, mr));
 }
 
 Datum
@@ -2233,7 +2247,7 @@ multirange_before_range(PG_FUNCTION_ARGS)
 
 	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
 
-	PG_RETURN_BOOL(range_after_multirange_internal(typcache, r, mr));
+	PG_RETURN_BOOL(range_after_multirange_internal(typcache->rngtype, r, mr));
 }
 
 Datum
@@ -2245,7 +2259,7 @@ multirange_before_multirange(PG_FUNCTION_ARGS)
 
 	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr1));
 
-	PG_RETURN_BOOL(multirange_before_multirange_internal(typcache, mr1, mr2));
+	PG_RETURN_BOOL(multirange_before_multirange_internal(typcache->rngtype, mr1, mr2));
 }
 
 /* strictly right of? */
@@ -2258,7 +2272,7 @@ range_after_multirange(PG_FUNCTION_ARGS)
 
 	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
 
-	PG_RETURN_BOOL(range_after_multirange_internal(typcache, r, mr));
+	PG_RETURN_BOOL(range_after_multirange_internal(typcache->rngtype, r, mr));
 }
 
 Datum
@@ -2270,7 +2284,7 @@ multirange_after_range(PG_FUNCTION_ARGS)
 
 	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
 
-	PG_RETURN_BOOL(range_before_multirange_internal(typcache, r, mr));
+	PG_RETURN_BOOL(range_before_multirange_internal(typcache->rngtype, r, mr));
 }
 
 Datum
@@ -2282,13 +2296,14 @@ multirange_after_multirange(PG_FUNCTION_ARGS)
 
 	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr1));
 
-	PG_RETURN_BOOL(multirange_before_multirange_internal(typcache, mr2, mr1));
+	PG_RETURN_BOOL(multirange_before_multirange_internal(typcache->rngtype, mr2, mr1));
 }
 
 /* strictly left of? (internal version) */
 bool
-range_before_multirange_internal(TypeCacheEntry *typcache, RangeType *r,
-								 MultirangeType *mr)
+range_before_multirange_internal(TypeCacheEntry *rangetyp,
+								 const RangeType *r,
+								 const MultirangeType *mr)
 {
 	RangeBound	lower1,
 				upper1,
@@ -2299,19 +2314,18 @@ range_before_multirange_internal(TypeCacheEntry *typcache, RangeType *r,
 	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
 		return false;
 
-	range_deserialize(typcache->rngtype, r, &lower1, &upper1, &empty);
+	range_deserialize(rangetyp, r, &lower1, &upper1, &empty);
 	Assert(!empty);
 
-	multirange_get_bounds(typcache->rngtype, mr, 0,
-						  &lower2, &upper2);
+	multirange_get_bounds(rangetyp, mr, 0, &lower2, &upper2);
 
-	return (range_cmp_bounds(typcache->rngtype, &upper1, &lower2) < 0);
+	return (range_cmp_bounds(rangetyp, &upper1, &lower2) < 0);
 }
 
 bool
-multirange_before_multirange_internal(TypeCacheEntry *typcache,
-									  MultirangeType *mr1,
-									  MultirangeType *mr2)
+multirange_before_multirange_internal(TypeCacheEntry *rangetyp,
+									  const MultirangeType *mr1,
+									  const MultirangeType *mr2)
 {
 	RangeBound	lower1,
 				upper1,
@@ -2321,18 +2335,19 @@ multirange_before_multirange_internal(TypeCacheEntry *typcache,
 	if (MultirangeIsEmpty(mr1) || MultirangeIsEmpty(mr2))
 		return false;
 
-	multirange_get_bounds(typcache->rngtype, mr1, mr1->rangeCount - 1,
+	multirange_get_bounds(rangetyp, mr1, mr1->rangeCount - 1,
 						  &lower1, &upper1);
-	multirange_get_bounds(typcache->rngtype, mr2, 0,
+	multirange_get_bounds(rangetyp, mr2, 0,
 						  &lower2, &upper2);
 
-	return (range_cmp_bounds(typcache->rngtype, &upper1, &lower2) < 0);
+	return (range_cmp_bounds(rangetyp, &upper1, &lower2) < 0);
 }
 
 /* strictly right of? (internal version) */
 bool
-range_after_multirange_internal(TypeCacheEntry *typcache, RangeType *r,
-								MultirangeType *mr)
+range_after_multirange_internal(TypeCacheEntry *rangetyp,
+								const RangeType *r,
+								const MultirangeType *mr)
 {
 	RangeBound	lower1,
 				upper1,
@@ -2344,19 +2359,20 @@ range_after_multirange_internal(TypeCacheEntry *typcache, RangeType *r,
 	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
 		return false;
 
-	range_deserialize(typcache->rngtype, r, &lower1, &upper1, &empty);
+	range_deserialize(rangetyp, r, &lower1, &upper1, &empty);
 	Assert(!empty);
 
 	range_count = mr->rangeCount;
-	multirange_get_bounds(typcache->rngtype, mr, range_count - 1,
+	multirange_get_bounds(rangetyp, mr, range_count - 1,
 						  &lower2, &upper2);
 
-	return (range_cmp_bounds(typcache->rngtype, &lower1, &upper2) > 0);
+	return (range_cmp_bounds(rangetyp, &lower1, &upper2) > 0);
 }
 
 bool
-range_adjacent_multirange_internal(TypeCacheEntry *typcache, RangeType *r,
-								   MultirangeType *mr)
+range_adjacent_multirange_internal(TypeCacheEntry *rangetyp,
+								   const RangeType *r,
+								   const MultirangeType *mr)
 {
 	RangeBound	lower1,
 				upper1,
@@ -2368,21 +2384,21 @@ range_adjacent_multirange_internal(TypeCacheEntry *typcache, RangeType *r,
 	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
 		return false;
 
-	range_deserialize(typcache->rngtype, r, &lower1, &upper1, &empty);
+	range_deserialize(rangetyp, r, &lower1, &upper1, &empty);
 	Assert(!empty);
 
 	range_count = mr->rangeCount;
-	multirange_get_bounds(typcache->rngtype, mr, 0,
+	multirange_get_bounds(rangetyp, mr, 0,
 						  &lower2, &upper2);
 
-	if (bounds_adjacent(typcache->rngtype, upper1, lower2))
+	if (bounds_adjacent(rangetyp, upper1, lower2))
 		return true;
 
 	if (range_count > 1)
-		multirange_get_bounds(typcache->rngtype, mr, range_count - 1,
+		multirange_get_bounds(rangetyp, mr, range_count - 1,
 							  &lower2, &upper2);
 
-	if (bounds_adjacent(typcache->rngtype, upper2, lower1))
+	if (bounds_adjacent(rangetyp, upper2, lower1))
 		return true;
 
 	return false;
@@ -2398,7 +2414,7 @@ range_adjacent_multirange(PG_FUNCTION_ARGS)
 
 	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
 
-	PG_RETURN_BOOL(range_adjacent_multirange_internal(typcache, r, mr));
+	PG_RETURN_BOOL(range_adjacent_multirange_internal(typcache->rngtype, r, mr));
 }
 
 Datum
@@ -2413,7 +2429,7 @@ multirange_adjacent_range(PG_FUNCTION_ARGS)
 
 	typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
 
-	PG_RETURN_BOOL(range_adjacent_multirange_internal(typcache, r, mr));
+	PG_RETURN_BOOL(range_adjacent_multirange_internal(typcache->rngtype, r, mr));
 }
 
 Datum
diff --git a/src/include/utils/multirangetypes.h b/src/include/utils/multirangetypes.h
index f2290aac274..ff2e58744a9 100644
--- a/src/include/utils/multirangetypes.h
+++ b/src/include/utils/multirangetypes.h
@@ -14,8 +14,8 @@
 #ifndef MULTIRANGETYPES_H
 #define MULTIRANGETYPES_H
 
+#include "utils/rangetypes.h"
 #include "utils/typcache.h"
-#include "utils/expandeddatum.h"
 
 
 /*
@@ -56,33 +56,48 @@ typedef struct
  */
 
 /* 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);
-extern bool multirange_contains_elem_internal(TypeCacheEntry *typcache, MultirangeType *mr,
+extern bool multirange_eq_internal(TypeCacheEntry *rangetyp,
+								   const MultirangeType *mr1,
+								   const MultirangeType *mr2);
+extern bool multirange_ne_internal(TypeCacheEntry *rangetyp,
+								   const MultirangeType *mr1,
+								   const MultirangeType *mr2);
+extern bool multirange_contains_elem_internal(TypeCacheEntry *rangetyp,
+											  const MultirangeType *mr,
 											  Datum elem);
-extern bool multirange_contains_range_internal(TypeCacheEntry *typcache, MultirangeType *mr,
-											   RangeType *r);
-extern bool range_contains_multirange_internal(TypeCacheEntry *typcache, RangeType *r,
-											   MultirangeType *mr);
-extern bool multirange_contains_multirange_internal(TypeCacheEntry *typcache,
-													MultirangeType *mr1,
-													MultirangeType *mr2);
-extern bool range_overlaps_multirange_internal(TypeCacheEntry *typcache, RangeType *r,
-											   MultirangeType *mr);
-extern bool multirange_overlaps_multirange_internal(TypeCacheEntry *typcache,
-													MultirangeType *mr1,
-													MultirangeType *mr2);
-extern bool range_before_multirange_internal(TypeCacheEntry *typcache, RangeType *r,
-											 MultirangeType *mr);
-extern bool range_after_multirange_internal(TypeCacheEntry *typcache, RangeType *r,
-											MultirangeType *mr);
-extern bool range_adjacent_multirange_internal(TypeCacheEntry *typcache, RangeType *r,
-											   MultirangeType *mr);
-extern bool multirange_before_multirange_internal(TypeCacheEntry *typcache,
-												  MultirangeType *mr1,
-												  MultirangeType *mr2);
+extern bool multirange_contains_range_internal(TypeCacheEntry *rangetyp,
+											   const MultirangeType *mr,
+											   const RangeType *r);
+extern bool range_contains_multirange_internal(TypeCacheEntry *rangetyp,
+											   const RangeType *r,
+											   const MultirangeType *mr);
+extern bool multirange_contains_multirange_internal(TypeCacheEntry *rangetyp,
+													const MultirangeType *mr1,
+													const MultirangeType *mr2);
+extern bool range_overlaps_multirange_internal(TypeCacheEntry *rangetyp,
+											   const RangeType *r,
+											   const MultirangeType *mr);
+extern bool multirange_overlaps_multirange_internal(TypeCacheEntry *rangetyp,
+													const MultirangeType *mr1,
+													const MultirangeType *mr2);
+extern bool range_overleft_multirange_internal(TypeCacheEntry *rangetyp,
+											   const RangeType *r,
+											   const MultirangeType *mr);
+extern bool range_overright_multirange_internal(TypeCacheEntry *rangetyp,
+												const RangeType *r,
+												const MultirangeType *mr);
+extern bool range_before_multirange_internal(TypeCacheEntry *rangetyp,
+											 const RangeType *r,
+											 const MultirangeType *mr);
+extern bool range_after_multirange_internal(TypeCacheEntry *rangetyp,
+											const RangeType *r,
+											const MultirangeType *mr);
+extern bool range_adjacent_multirange_internal(TypeCacheEntry *rangetyp,
+											   const RangeType *r,
+											   const MultirangeType *mr);
+extern bool multirange_before_multirange_internal(TypeCacheEntry *rangetyp,
+												  const MultirangeType *mr1,
+												  const MultirangeType *mr2);
 extern MultirangeType *multirange_minus_internal(Oid mltrngtypoid,
 												 TypeCacheEntry *rangetyp,
 												 int32 range_count1,
-- 
2.24.3 (Apple Git-128)

0005-Add-GiST-indexes-for-multiranges.patchapplication/octet-stream; name=0005-Add-GiST-indexes-for-multiranges.patchDownload
From 8e25bb4227418ed76bb5ae384899669bd5802c4a Mon Sep 17 00:00:00 2001
From: Alexander Korotkov <akorotkov@postgresql.org>
Date: Sat, 26 Dec 2020 18:03:28 +0300
Subject: [PATCH 5/5] Add GiST indexes for multiranges

This commits adds a pretty trivial way for multirange indexing: approximate
multirange as union range with no gaps.  New multiranges opclass shares the
majority of functions with ranges opclass.

This is not an ideal way to index multirages, but something we can easily have.
---
 src/backend/utils/adt/multirangetypes.c       |  21 ++
 src/backend/utils/adt/rangetypes_gist.c       | 117 ++++++++
 src/include/catalog/pg_amop.dat               |  56 ++++
 src/include/catalog/pg_amproc.dat             |  18 ++
 src/include/catalog/pg_opclass.dat            |   2 +
 src/include/catalog/pg_opfamily.dat           |   2 +
 src/include/catalog/pg_proc.dat               |   8 +
 src/include/utils/multirangetypes.h           |   2 +
 src/test/regress/expected/multirangetypes.out | 257 ++++++++++++++++++
 src/test/regress/sql/multirangetypes.sql      |  63 +++++
 10 files changed, 546 insertions(+)

diff --git a/src/backend/utils/adt/multirangetypes.c b/src/backend/utils/adt/multirangetypes.c
index a77299147e7..2d4cee92bcc 100644
--- a/src/backend/utils/adt/multirangetypes.c
+++ b/src/backend/utils/adt/multirangetypes.c
@@ -768,6 +768,27 @@ multirange_get_bounds(TypeCacheEntry *rangetyp,
 	upper->lower = false;
 }
 
+/*
+ * Construct union range from the multirange.
+ */
+RangeType *
+multirange_get_union_range(TypeCacheEntry *rangetyp,
+						   const MultirangeType *mr)
+{
+	RangeBound	lower,
+				upper,
+				tmp;
+
+	if (MultirangeIsEmpty(mr))
+		return make_empty_range(rangetyp);
+
+	multirange_get_bounds(rangetyp, mr, 0, &lower, &tmp);
+	multirange_get_bounds(rangetyp, mr, mr->rangeCount - 1, &tmp, &upper);
+
+	return make_range(rangetyp, &lower, &upper, false);
+}
+
+
 /*
  * multirange_deserialize: deconstruct a multirange value
  *
diff --git a/src/backend/utils/adt/rangetypes_gist.c b/src/backend/utils/adt/rangetypes_gist.c
index f4bccf41b90..435b242c8ab 100644
--- a/src/backend/utils/adt/rangetypes_gist.c
+++ b/src/backend/utils/adt/rangetypes_gist.c
@@ -237,6 +237,88 @@ range_gist_consistent(PG_FUNCTION_ARGS)
 	PG_RETURN_BOOL(result);
 }
 
+/*
+ * GiST compress method for multiranges: multirange is approximated as union
+ * range with no gaps.
+ */
+Datum
+multirange_gist_compress(PG_FUNCTION_ARGS)
+{
+	GISTENTRY  *entry = (GISTENTRY *) PG_GETARG_POINTER(0);
+
+	if (entry->leafkey)
+	{
+		MultirangeType *mr = DatumGetMultirangeTypeP(entry->key);
+		RangeType  *r;
+		TypeCacheEntry *typcache;
+		GISTENTRY  *retval = palloc(sizeof(GISTENTRY));
+
+		typcache = multirange_get_typcache(fcinfo, MultirangeTypeGetOid(mr));
+		r = multirange_get_union_range(typcache->rngtype, mr);
+
+		gistentryinit(*retval, RangeTypePGetDatum(r),
+					  entry->rel, entry->page, entry->offset, false);
+
+		PG_RETURN_POINTER(retval);
+	}
+
+	PG_RETURN_POINTER(entry);
+}
+
+/* GiST query consistency check for multiranges */
+Datum
+multirange_gist_consistent(PG_FUNCTION_ARGS)
+{
+	GISTENTRY  *entry = (GISTENTRY *) PG_GETARG_POINTER(0);
+	Datum		query = PG_GETARG_DATUM(1);
+	StrategyNumber strategy = (StrategyNumber) PG_GETARG_UINT16(2);
+	bool		result;
+	Oid			subtype = PG_GETARG_OID(3);
+	bool	   *recheck = (bool *) PG_GETARG_POINTER(4);
+	RangeType  *key = DatumGetRangeTypeP(entry->key);
+	TypeCacheEntry *typcache;
+
+	/*
+	 * All operators served by this function are inexact because multirange is
+	 * approximated by union range with no gaps.
+	 */
+	*recheck = true;
+
+	typcache = range_get_typcache(fcinfo, RangeTypeGetOid(key));
+
+	/*
+	 * Perform consistent checking using function corresponding to key type
+	 * (leaf or internal) and query subtype (range, multirange, or element).
+	 * Note that invalid subtype means that query type matches key type
+	 * (multirange).
+	 */
+	if (GIST_LEAF(entry))
+	{
+		if (!OidIsValid(subtype) || subtype == ANYMULTIRANGEOID)
+			result = range_gist_consistent_leaf_multirange(typcache, strategy, key,
+														   DatumGetMultirangeTypeP(query));
+		else if (subtype == ANYRANGEOID)
+			result = range_gist_consistent_leaf_range(typcache, strategy, key,
+													  DatumGetRangeTypeP(query));
+		else
+			result = range_gist_consistent_leaf_element(typcache, strategy,
+														key, query);
+	}
+	else
+	{
+		if (!OidIsValid(subtype) || subtype == ANYMULTIRANGEOID)
+			result = range_gist_consistent_int_multirange(typcache, strategy, key,
+														  DatumGetMultirangeTypeP(query));
+		else if (subtype == ANYRANGEOID)
+			result = range_gist_consistent_int_range(typcache, strategy, key,
+													 DatumGetRangeTypeP(query));
+		else
+			result = range_gist_consistent_int_element(typcache, strategy,
+													   key, query);
+	}
+	PG_RETURN_BOOL(result);
+}
+
 /* form union range */
 Datum
 range_gist_union(PG_FUNCTION_ARGS)
@@ -802,6 +884,30 @@ range_super_union(TypeCacheEntry *typcache, RangeType *r1, RangeType *r2)
 	return result;
 }
 
+static bool
+multirange_union_range_equal(TypeCacheEntry *typcache,
+							 const RangeType *r,
+							 const MultirangeType *mr)
+{
+	RangeBound	lower1,
+				upper1,
+				lower2,
+				upper2,
+				tmp;
+	bool		empty;
+
+	if (RangeIsEmpty(r) || MultirangeIsEmpty(mr))
+		return (RangeIsEmpty(r) && MultirangeIsEmpty(mr));
+
+	range_deserialize(typcache, r, &lower1, &upper1, &empty);
+	Assert(!empty);
+	multirange_get_bounds(typcache, mr, 0, &lower2, &tmp);
+	multirange_get_bounds(typcache, mr, mr->rangeCount - 1, &tmp, &upper2);
+
+	return (range_cmp_bounds(typcache, &lower1, &lower2) == 0 &&
+			range_cmp_bounds(typcache, &upper1, &upper2) == 0);
+}
+
 /*
  * GiST consistent test on an index internal page with range query
  */
@@ -911,6 +1017,15 @@ range_gist_consistent_int_multirange(TypeCacheEntry *typcache,
 			if (RangeIsOrContainsEmpty(key))
 				return true;
 			return range_overlaps_multirange_internal(typcache, key, query);
+		case RANGESTRAT_EQ:
+
+			/*
+			 * If query is empty, descend only if the key is or contains any
+			 * empty ranges.  Otherwise, descend if key contains query.
+			 */
+			if (MultirangeIsEmpty(query))
+				return RangeIsOrContainsEmpty(key);
+			return range_contains_multirange_internal(typcache, key, query);
 		default:
 			elog(ERROR, "unrecognized range strategy: %d", strategy);
 			return false;		/* keep compiler quiet */
@@ -998,6 +1113,8 @@ range_gist_consistent_leaf_multirange(TypeCacheEntry *typcache,
 			return range_contains_multirange_internal(typcache, key, query);
 		case RANGESTRAT_CONTAINED_BY:
 			return multirange_contains_range_internal(typcache, query, key);
+		case RANGESTRAT_EQ:
+			return multirange_union_range_equal(typcache, key, query);
 		default:
 			elog(ERROR, "unrecognized range strategy: %d", strategy);
 			return false;		/* keep compiler quiet */
diff --git a/src/include/catalog/pg_amop.dat b/src/include/catalog/pg_amop.dat
index e055da67eb7..bbe1a6ddf8e 100644
--- a/src/include/catalog/pg_amop.dat
+++ b/src/include/catalog/pg_amop.dat
@@ -1398,6 +1398,62 @@
   amoprighttype => 'anyrange', amopstrategy => '18',
   amopopr => '=(anyrange,anyrange)', amopmethod => 'gist' },
 
+# GiST multirange_ops
+{ amopfamily => 'gist/multirange_ops', amoplefttype => 'anymultirange',
+  amoprighttype => 'anymultirange', amopstrategy => '1',
+  amopopr => '<<(anymultirange,anymultirange)', amopmethod => 'gist' },
+{ amopfamily => 'gist/multirange_ops', amoplefttype => 'anymultirange',
+  amoprighttype => 'anyrange', amopstrategy => '1',
+  amopopr => '<<(anymultirange,anyrange)', amopmethod => 'gist' },
+{ amopfamily => 'gist/multirange_ops', amoplefttype => 'anymultirange',
+  amoprighttype => 'anymultirange', amopstrategy => '2',
+  amopopr => '&<(anymultirange,anymultirange)', amopmethod => 'gist' },
+{ amopfamily => 'gist/multirange_ops', amoplefttype => 'anymultirange',
+  amoprighttype => 'anyrange', amopstrategy => '2',
+  amopopr => '&<(anymultirange,anyrange)', amopmethod => 'gist' },
+{ amopfamily => 'gist/multirange_ops', amoplefttype => 'anymultirange',
+  amoprighttype => 'anymultirange', amopstrategy => '3',
+  amopopr => '&&(anymultirange,anymultirange)', amopmethod => 'gist' },
+{ amopfamily => 'gist/multirange_ops', amoplefttype => 'anymultirange',
+  amoprighttype => 'anyrange', amopstrategy => '3',
+  amopopr => '&&(anymultirange,anyrange)', amopmethod => 'gist' },
+{ amopfamily => 'gist/multirange_ops', amoplefttype => 'anymultirange',
+  amoprighttype => 'anymultirange', amopstrategy => '4',
+  amopopr => '&>(anymultirange,anymultirange)', amopmethod => 'gist' },
+{ amopfamily => 'gist/multirange_ops', amoplefttype => 'anymultirange',
+  amoprighttype => 'anyrange', amopstrategy => '4',
+  amopopr => '&>(anymultirange,anyrange)', amopmethod => 'gist' },
+{ amopfamily => 'gist/multirange_ops', amoplefttype => 'anymultirange',
+  amoprighttype => 'anymultirange', amopstrategy => '5',
+  amopopr => '>>(anymultirange,anymultirange)', amopmethod => 'gist' },
+{ amopfamily => 'gist/multirange_ops', amoplefttype => 'anymultirange',
+  amoprighttype => 'anyrange', amopstrategy => '5',
+  amopopr => '>>(anymultirange,anyrange)', amopmethod => 'gist' },
+{ amopfamily => 'gist/multirange_ops', amoplefttype => 'anymultirange',
+  amoprighttype => 'anymultirange', amopstrategy => '6',
+  amopopr => '-|-(anymultirange,anymultirange)', amopmethod => 'gist' },
+{ amopfamily => 'gist/multirange_ops', amoplefttype => 'anymultirange',
+  amoprighttype => 'anyrange', amopstrategy => '6',
+  amopopr => '-|-(anymultirange,anyrange)', amopmethod => 'gist' },
+{ amopfamily => 'gist/multirange_ops', amoplefttype => 'anymultirange',
+  amoprighttype => 'anymultirange', amopstrategy => '7',
+  amopopr => '@>(anymultirange,anymultirange)', amopmethod => 'gist' },
+{ amopfamily => 'gist/multirange_ops', amoplefttype => 'anymultirange',
+  amoprighttype => 'anyrange', amopstrategy => '7',
+  amopopr => '@>(anymultirange,anyrange)', amopmethod => 'gist' },
+{ amopfamily => 'gist/multirange_ops', amoplefttype => 'anymultirange',
+  amoprighttype => 'anymultirange', amopstrategy => '8',
+  amopopr => '<@(anymultirange,anymultirange)', amopmethod => 'gist' },
+{ amopfamily => 'gist/multirange_ops', amoplefttype => 'anymultirange',
+  amoprighttype => 'anyrange', amopstrategy => '8',
+  amopopr => '<@(anymultirange,anyrange)', amopmethod => 'gist' },
+{ amopfamily => 'gist/multirange_ops', amoplefttype => 'anymultirange',
+  amoprighttype => 'anyelement', amopstrategy => '16',
+  amopopr => '@>(anymultirange,anyelement)', amopmethod => 'gist' },
+{ amopfamily => 'gist/multirange_ops', amoplefttype => 'anymultirange',
+  amoprighttype => 'anymultirange', amopstrategy => '18',
+  amopopr => '=(anymultirange,anymultirange)', amopmethod => 'gist' },
+
 # btree multirange_ops
 { amopfamily => 'btree/multirange_ops', amoplefttype => 'anymultirange',
   amoprighttype => 'anymultirange', amopstrategy => '1',
diff --git a/src/include/catalog/pg_amproc.dat b/src/include/catalog/pg_amproc.dat
index 9d423d535cd..68d72ec732a 100644
--- a/src/include/catalog/pg_amproc.dat
+++ b/src/include/catalog/pg_amproc.dat
@@ -612,6 +612,24 @@
   amprocrighttype => 'inet', amprocnum => '7', amproc => 'inet_gist_same' },
 { amprocfamily => 'gist/network_ops', amproclefttype => 'inet',
   amprocrighttype => 'inet', amprocnum => '9', amproc => 'inet_gist_fetch' },
+{ amprocfamily => 'gist/multirange_ops', amproclefttype => 'anymultirange',
+  amprocrighttype => 'anymultirange', amprocnum => '1',
+  amproc => 'multirange_gist_consistent' },
+{ amprocfamily => 'gist/multirange_ops', amproclefttype => 'anymultirange',
+  amprocrighttype => 'anymultirange', amprocnum => '2',
+  amproc => 'range_gist_union' },
+{ amprocfamily => 'gist/multirange_ops', amproclefttype => 'anymultirange',
+  amprocrighttype => 'anymultirange', amprocnum => '3',
+  amproc => 'multirange_gist_compress' },
+{ amprocfamily => 'gist/multirange_ops', amproclefttype => 'anymultirange',
+  amprocrighttype => 'anymultirange', amprocnum => '5',
+  amproc => 'range_gist_penalty' },
+{ amprocfamily => 'gist/multirange_ops', amproclefttype => 'anymultirange',
+  amprocrighttype => 'anymultirange', amprocnum => '6',
+  amproc => 'range_gist_picksplit' },
+{ amprocfamily => 'gist/multirange_ops', amproclefttype => 'anymultirange',
+  amprocrighttype => 'anymultirange', amprocnum => '7',
+  amproc => 'range_gist_same' },
 
 # gin
 { amprocfamily => 'gin/array_ops', amproclefttype => 'anyarray',
diff --git a/src/include/catalog/pg_opclass.dat b/src/include/catalog/pg_opclass.dat
index 4c5e475ff7b..12cad694186 100644
--- a/src/include/catalog/pg_opclass.dat
+++ b/src/include/catalog/pg_opclass.dat
@@ -236,6 +236,8 @@
   opcintype => 'anymultirange' },
 { opcmethod => 'hash', opcname => 'multirange_ops', opcfamily => 'hash/multirange_ops',
   opcintype => 'anymultirange' },
+{ opcmethod => 'gist', opcname => 'multirange_ops', opcfamily => 'gist/multirange_ops',
+  opcintype => 'anymultirange', opckeytype => 'anyrange' },
 { opcmethod => 'spgist', opcname => 'box_ops', opcfamily => 'spgist/box_ops',
   opcintype => 'box' },
 { opcmethod => 'spgist', opcname => 'quad_point_ops',
diff --git a/src/include/catalog/pg_opfamily.dat b/src/include/catalog/pg_opfamily.dat
index fe42dfc0f8f..ac8338f34b1 100644
--- a/src/include/catalog/pg_opfamily.dat
+++ b/src/include/catalog/pg_opfamily.dat
@@ -236,5 +236,7 @@
   opfmethod => 'btree', opfname => 'multirange_ops' },
 { oid => '4225',
   opfmethod => 'hash', opfname => 'multirange_ops' },
+{ oid => '8021',
+  opfmethod => 'gist', opfname => 'multirange_ops' },
 
 ]
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 9d19eb5f409..d2deb087da3 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -9922,6 +9922,14 @@
 { oid => '3881', descr => 'GiST support',
   proname => 'range_gist_same', prorettype => 'internal',
   proargtypes => 'anyrange anyrange internal', prosrc => 'range_gist_same' },
+{ oid => '8017', descr => 'GiST support',
+  proname => 'multirange_gist_consistent', prorettype => 'bool',
+  proargtypes => 'internal anymultirange int2 oid internal',
+  prosrc => 'multirange_gist_consistent' },
+{ oid => '8019', descr => 'GiST support',
+  proname => 'multirange_gist_compress', prorettype => 'internal',
+  proargtypes => 'internal',
+  prosrc => 'multirange_gist_compress' },
 { oid => '3902', descr => 'hash a range',
   proname => 'hash_range', prorettype => 'int4', proargtypes => 'anyrange',
   prosrc => 'hash_range' },
diff --git a/src/include/utils/multirangetypes.h b/src/include/utils/multirangetypes.h
index ff2e58744a9..1d877f08b56 100644
--- a/src/include/utils/multirangetypes.h
+++ b/src/include/utils/multirangetypes.h
@@ -129,5 +129,7 @@ extern void multirange_get_bounds(TypeCacheEntry *rangetyp,
 								  RangeBound *lower, RangeBound *upper);
 extern RangeType *multirange_get_range(TypeCacheEntry *rangetyp,
 									   const MultirangeType *multirange, int i);
+extern RangeType *multirange_get_union_range(TypeCacheEntry *rangetyp,
+											 const MultirangeType *mr);
 
 #endif							/* MULTIRANGETYPES_H */
diff --git a/src/test/regress/expected/multirangetypes.out b/src/test/regress/expected/multirangetypes.out
index aa7232efc57..86011a02a1e 100644
--- a/src/test/regress/expected/multirangetypes.out
+++ b/src/test/regress/expected/multirangetypes.out
@@ -2219,6 +2219,263 @@ SELECT '{[1,4), [7,10)}'::nummultirange * '{[0,2), [3,8), [9,12)}'::nummultirang
  {[1,2),[3,4),[7,8),[9,10)}
 (1 row)
 
+-- test GiST index
+create table test_multirange_gist(mr int4multirange);
+insert into test_multirange_gist select int4multirange(int4range(g, g+10),int4range(g+20, g+30),int4range(g+40, g+50)) from generate_series(1,2000) g;
+insert into test_multirange_gist select '{}'::int4multirange from generate_series(1,500) g;
+insert into test_multirange_gist select int4multirange(int4range(g, g+10000)) from generate_series(1,1000) g;
+insert into test_multirange_gist select int4multirange(int4range(NULL, g*10, '(]'), int4range(g*10, g*20, '(]')) from generate_series(1,100) g;
+insert into test_multirange_gist select int4multirange(int4range(g*10, g*20, '(]'), int4range(g*20, NULL, '(]')) from generate_series(1,100) g;
+create index test_mulrirange_gist_idx on test_multirange_gist using gist (mr);
+-- first, verify non-indexed results
+SET enable_seqscan    = t;
+SET enable_indexscan  = f;
+SET enable_bitmapscan = f;
+select count(*) from test_multirange_gist where mr @> 'empty'::int4range;
+ count 
+-------
+  3700
+(1 row)
+
+select count(*) from test_multirange_gist where mr = int4multirange(int4range(10,20), int4range(30,40), int4range(50,60));
+ count 
+-------
+     1
+(1 row)
+
+select count(*) from test_multirange_gist where mr @> 10;
+ count 
+-------
+   120
+(1 row)
+
+select count(*) from test_multirange_gist where mr @> int4range(10,20);
+ count 
+-------
+   111
+(1 row)
+
+select count(*) from test_multirange_gist where mr && int4range(10,20);
+ count 
+-------
+   139
+(1 row)
+
+select count(*) from test_multirange_gist where mr <@ int4range(10,50);
+ count 
+-------
+   500
+(1 row)
+
+select count(*) from test_multirange_gist where mr << int4range(100,500);
+ count 
+-------
+    54
+(1 row)
+
+select count(*) from test_multirange_gist where mr >> int4range(100,500);
+ count 
+-------
+  2053
+(1 row)
+
+select count(*) from test_multirange_gist where mr &< int4range(100,500);
+ count 
+-------
+   474
+(1 row)
+
+select count(*) from test_multirange_gist where mr &> int4range(100,500);
+ count 
+-------
+  2893
+(1 row)
+
+select count(*) from test_multirange_gist where mr -|- int4range(100,500);
+ count 
+-------
+     3
+(1 row)
+
+select count(*) from test_multirange_gist where mr @> '{}'::int4multirange;
+ count 
+-------
+  3700
+(1 row)
+
+select count(*) from test_multirange_gist where mr @> int4multirange(int4range(10,20), int4range(30,40));
+ count 
+-------
+   110
+(1 row)
+
+select count(*) from test_multirange_gist where mr && '{(10,20),(30,40),(50,60)}'::int4multirange;
+ count 
+-------
+   218
+(1 row)
+
+select count(*) from test_multirange_gist where mr <@ '{(10,30),(40,60),(70,90)}'::int4multirange;
+ count 
+-------
+   500
+(1 row)
+
+select count(*) from test_multirange_gist where mr << int4multirange(int4range(100,200), int4range(400,500));
+ count 
+-------
+    54
+(1 row)
+
+select count(*) from test_multirange_gist where mr >> int4multirange(int4range(100,200), int4range(400,500));
+ count 
+-------
+  2053
+(1 row)
+
+select count(*) from test_multirange_gist where mr &< int4multirange(int4range(100,200), int4range(400,500));
+ count 
+-------
+   474
+(1 row)
+
+select count(*) from test_multirange_gist where mr &> int4multirange(int4range(100,200), int4range(400,500));
+ count 
+-------
+  2893
+(1 row)
+
+select count(*) from test_multirange_gist where mr -|- int4multirange(int4range(100,200), int4range(400,500));
+ count 
+-------
+     3
+(1 row)
+
+-- now check same queries using index
+SET enable_seqscan    = f;
+SET enable_indexscan  = t;
+SET enable_bitmapscan = f;
+select count(*) from test_multirange_gist where mr @> 'empty'::int4range;
+ count 
+-------
+  3700
+(1 row)
+
+select count(*) from test_multirange_gist where mr = int4multirange(int4range(10,20), int4range(30,40), int4range(50,60));
+ count 
+-------
+     1
+(1 row)
+
+select count(*) from test_multirange_gist where mr @> 10;
+ count 
+-------
+   120
+(1 row)
+
+select count(*) from test_multirange_gist where mr @> int4range(10,20);
+ count 
+-------
+   111
+(1 row)
+
+select count(*) from test_multirange_gist where mr && int4range(10,20);
+ count 
+-------
+   139
+(1 row)
+
+select count(*) from test_multirange_gist where mr <@ int4range(10,50);
+ count 
+-------
+   500
+(1 row)
+
+select count(*) from test_multirange_gist where mr << int4range(100,500);
+ count 
+-------
+    54
+(1 row)
+
+select count(*) from test_multirange_gist where mr >> int4range(100,500);
+ count 
+-------
+  2053
+(1 row)
+
+select count(*) from test_multirange_gist where mr &< int4range(100,500);
+ count 
+-------
+   474
+(1 row)
+
+select count(*) from test_multirange_gist where mr &> int4range(100,500);
+ count 
+-------
+  2893
+(1 row)
+
+select count(*) from test_multirange_gist where mr -|- int4range(100,500);
+ count 
+-------
+     3
+(1 row)
+
+select count(*) from test_multirange_gist where mr @> '{}'::int4multirange;
+ count 
+-------
+  3700
+(1 row)
+
+select count(*) from test_multirange_gist where mr @> int4multirange(int4range(10,20), int4range(30,40));
+ count 
+-------
+   110
+(1 row)
+
+select count(*) from test_multirange_gist where mr && '{(10,20),(30,40),(50,60)}'::int4multirange;
+ count 
+-------
+   218
+(1 row)
+
+select count(*) from test_multirange_gist where mr <@ '{(10,30),(40,60),(70,90)}'::int4multirange;
+ count 
+-------
+   500
+(1 row)
+
+select count(*) from test_multirange_gist where mr << int4multirange(int4range(100,200), int4range(400,500));
+ count 
+-------
+    54
+(1 row)
+
+select count(*) from test_multirange_gist where mr >> int4multirange(int4range(100,200), int4range(400,500));
+ count 
+-------
+  2053
+(1 row)
+
+select count(*) from test_multirange_gist where mr &< int4multirange(int4range(100,200), int4range(400,500));
+ count 
+-------
+   474
+(1 row)
+
+select count(*) from test_multirange_gist where mr &> int4multirange(int4range(100,200), int4range(400,500));
+ count 
+-------
+  2893
+(1 row)
+
+select count(*) from test_multirange_gist where mr -|- int4multirange(int4range(100,200), int4range(400,500));
+ count 
+-------
+     3
+(1 row)
+
+drop table test_multirange_gist;
 --
 -- range_agg function
 --
diff --git a/src/test/regress/sql/multirangetypes.sql b/src/test/regress/sql/multirangetypes.sql
index 9d7af404c98..2a2ee4dcdfd 100644
--- a/src/test/regress/sql/multirangetypes.sql
+++ b/src/test/regress/sql/multirangetypes.sql
@@ -414,6 +414,69 @@ SELECT '{[1,4), [7,10)}'::nummultirange * '{[-5,-4), [5,6), [9,12)}'::nummultira
 SELECT '{[1,4), [7,10)}'::nummultirange * '{[0,2), [3,8), [9,12)}'::nummultirange;
 SELECT '{[1,4), [7,10)}'::nummultirange * '{[0,2), [3,8), [9,12)}'::nummultirange;
 
+-- test GiST index
+create table test_multirange_gist(mr int4multirange);
+insert into test_multirange_gist select int4multirange(int4range(g, g+10),int4range(g+20, g+30),int4range(g+40, g+50)) from generate_series(1,2000) g;
+insert into test_multirange_gist select '{}'::int4multirange from generate_series(1,500) g;
+insert into test_multirange_gist select int4multirange(int4range(g, g+10000)) from generate_series(1,1000) g;
+insert into test_multirange_gist select int4multirange(int4range(NULL, g*10, '(]'), int4range(g*10, g*20, '(]')) from generate_series(1,100) g;
+insert into test_multirange_gist select int4multirange(int4range(g*10, g*20, '(]'), int4range(g*20, NULL, '(]')) from generate_series(1,100) g;
+create index test_mulrirange_gist_idx on test_multirange_gist using gist (mr);
+
+-- first, verify non-indexed results
+SET enable_seqscan    = t;
+SET enable_indexscan  = f;
+SET enable_bitmapscan = f;
+
+select count(*) from test_multirange_gist where mr @> 'empty'::int4range;
+select count(*) from test_multirange_gist where mr = int4multirange(int4range(10,20), int4range(30,40), int4range(50,60));
+select count(*) from test_multirange_gist where mr @> 10;
+select count(*) from test_multirange_gist where mr @> int4range(10,20);
+select count(*) from test_multirange_gist where mr && int4range(10,20);
+select count(*) from test_multirange_gist where mr <@ int4range(10,50);
+select count(*) from test_multirange_gist where mr << int4range(100,500);
+select count(*) from test_multirange_gist where mr >> int4range(100,500);
+select count(*) from test_multirange_gist where mr &< int4range(100,500);
+select count(*) from test_multirange_gist where mr &> int4range(100,500);
+select count(*) from test_multirange_gist where mr -|- int4range(100,500);
+select count(*) from test_multirange_gist where mr @> '{}'::int4multirange;
+select count(*) from test_multirange_gist where mr @> int4multirange(int4range(10,20), int4range(30,40));
+select count(*) from test_multirange_gist where mr && '{(10,20),(30,40),(50,60)}'::int4multirange;
+select count(*) from test_multirange_gist where mr <@ '{(10,30),(40,60),(70,90)}'::int4multirange;
+select count(*) from test_multirange_gist where mr << int4multirange(int4range(100,200), int4range(400,500));
+select count(*) from test_multirange_gist where mr >> int4multirange(int4range(100,200), int4range(400,500));
+select count(*) from test_multirange_gist where mr &< int4multirange(int4range(100,200), int4range(400,500));
+select count(*) from test_multirange_gist where mr &> int4multirange(int4range(100,200), int4range(400,500));
+select count(*) from test_multirange_gist where mr -|- int4multirange(int4range(100,200), int4range(400,500));
+
+-- now check same queries using index
+SET enable_seqscan    = f;
+SET enable_indexscan  = t;
+SET enable_bitmapscan = f;
+
+select count(*) from test_multirange_gist where mr @> 'empty'::int4range;
+select count(*) from test_multirange_gist where mr = int4multirange(int4range(10,20), int4range(30,40), int4range(50,60));
+select count(*) from test_multirange_gist where mr @> 10;
+select count(*) from test_multirange_gist where mr @> int4range(10,20);
+select count(*) from test_multirange_gist where mr && int4range(10,20);
+select count(*) from test_multirange_gist where mr <@ int4range(10,50);
+select count(*) from test_multirange_gist where mr << int4range(100,500);
+select count(*) from test_multirange_gist where mr >> int4range(100,500);
+select count(*) from test_multirange_gist where mr &< int4range(100,500);
+select count(*) from test_multirange_gist where mr &> int4range(100,500);
+select count(*) from test_multirange_gist where mr -|- int4range(100,500);
+select count(*) from test_multirange_gist where mr @> '{}'::int4multirange;
+select count(*) from test_multirange_gist where mr @> int4multirange(int4range(10,20), int4range(30,40));
+select count(*) from test_multirange_gist where mr && '{(10,20),(30,40),(50,60)}'::int4multirange;
+select count(*) from test_multirange_gist where mr <@ '{(10,30),(40,60),(70,90)}'::int4multirange;
+select count(*) from test_multirange_gist where mr << int4multirange(int4range(100,200), int4range(400,500));
+select count(*) from test_multirange_gist where mr >> int4multirange(int4range(100,200), int4range(400,500));
+select count(*) from test_multirange_gist where mr &< int4multirange(int4range(100,200), int4range(400,500));
+select count(*) from test_multirange_gist where mr &> int4multirange(int4range(100,200), int4range(400,500));
+select count(*) from test_multirange_gist where mr -|- int4multirange(int4range(100,200), int4range(400,500));
+
+drop table test_multirange_gist;
+
 --
 -- range_agg function
 --
-- 
2.24.3 (Apple Git-128)

0004-Add-support-of-multirange-matching-to-the-existing-r.patchapplication/octet-stream; name=0004-Add-support-of-multirange-matching-to-the-existing-r.patchDownload
From 914a5644a078144bb2475a51c413d5ff68020578 Mon Sep 17 00:00:00 2001
From: Alexander Korotkov <akorotkov@postgresql.org>
Date: Fri, 25 Dec 2020 17:55:38 +0300
Subject: [PATCH 4/5] Add support of multirange matching to the existing range
 GiST indexes

6df7a9698b has introduced a set of operators between ranges and multiranges.
Existing GiST indexes for ranges could easily support majority of them.
This commit adds support for new operators to the existing range GiST indexes.
New operators resides the same strategy numbers as existing ones.  Appropriate
check function is determined using the subtype.
---
 src/backend/utils/adt/rangetypes_gist.c  | 276 +++++++++++++++++------
 src/include/catalog/pg_amop.dat          |  24 ++
 src/test/regress/expected/rangetypes.out | 162 +++++++++++++
 src/test/regress/sql/rangetypes.sql      |  27 +++
 4 files changed, 426 insertions(+), 63 deletions(-)

diff --git a/src/backend/utils/adt/rangetypes_gist.c b/src/backend/utils/adt/rangetypes_gist.c
index 75069c3ac2c..f4bccf41b90 100644
--- a/src/backend/utils/adt/rangetypes_gist.c
+++ b/src/backend/utils/adt/rangetypes_gist.c
@@ -19,6 +19,7 @@
 #include "utils/datum.h"
 #include "utils/float.h"
 #include "utils/fmgrprotos.h"
+#include "utils/multirangetypes.h"
 #include "utils/rangetypes.h"
 
 /*
@@ -135,12 +136,30 @@ typedef struct
 
 static RangeType *range_super_union(TypeCacheEntry *typcache, RangeType *r1,
 									RangeType *r2);
-static bool range_gist_consistent_int(TypeCacheEntry *typcache,
-									  StrategyNumber strategy, const RangeType *key,
-									  Datum query);
-static bool range_gist_consistent_leaf(TypeCacheEntry *typcache,
-									   StrategyNumber strategy, const RangeType *key,
-									   Datum query);
+static bool range_gist_consistent_int_range(TypeCacheEntry *typcache,
+											StrategyNumber strategy,
+											const RangeType *key,
+											const RangeType *query);
+static bool range_gist_consistent_int_multirange(TypeCacheEntry *typcache,
+												 StrategyNumber strategy,
+												 const RangeType *key,
+												 const MultirangeType *query);
+static bool range_gist_consistent_int_element(TypeCacheEntry *typcache,
+											  StrategyNumber strategy,
+											  const RangeType *key,
+											  Datum query);
+static bool range_gist_consistent_leaf_range(TypeCacheEntry *typcache,
+											 StrategyNumber strategy,
+											 const RangeType *key,
+											 const RangeType *query);
+static bool range_gist_consistent_leaf_multirange(TypeCacheEntry *typcache,
+												  StrategyNumber strategy,
+												  const RangeType *key,
+												  const MultirangeType *query);
+static bool range_gist_consistent_leaf_element(TypeCacheEntry *typcache,
+											   StrategyNumber strategy,
+											   const RangeType *key,
+											   Datum query);
 static void range_gist_fallback_split(TypeCacheEntry *typcache,
 									  GistEntryVector *entryvec,
 									  GIST_SPLITVEC *v);
@@ -174,8 +193,8 @@ range_gist_consistent(PG_FUNCTION_ARGS)
 	GISTENTRY  *entry = (GISTENTRY *) PG_GETARG_POINTER(0);
 	Datum		query = PG_GETARG_DATUM(1);
 	StrategyNumber strategy = (StrategyNumber) PG_GETARG_UINT16(2);
-
-	/* Oid subtype = PG_GETARG_OID(3); */
+	bool		result;
+	Oid			subtype = PG_GETARG_OID(3);
 	bool	   *recheck = (bool *) PG_GETARG_POINTER(4);
 	RangeType  *key = DatumGetRangeTypeP(entry->key);
 	TypeCacheEntry *typcache;
@@ -185,12 +204,37 @@ range_gist_consistent(PG_FUNCTION_ARGS)
 
 	typcache = range_get_typcache(fcinfo, RangeTypeGetOid(key));
 
+	/*
+	 * Perform consistent checking using function corresponding to key type
+	 * (leaf or internal) and query subtype (range, multirange, or element).
+	 * Note that invalid subtype means that query type matches key type
+	 * (range).
+	 */
 	if (GIST_LEAF(entry))
-		PG_RETURN_BOOL(range_gist_consistent_leaf(typcache, strategy,
-												  key, query));
+	{
+		if (!OidIsValid(subtype) || subtype == ANYRANGEOID)
+			result = range_gist_consistent_leaf_range(typcache, strategy, key,
+													  DatumGetRangeTypeP(query));
+		else if (subtype == ANYMULTIRANGEOID)
+			result = range_gist_consistent_leaf_multirange(typcache, strategy, key,
+														   DatumGetMultirangeTypeP(query));
+		else
+			result = range_gist_consistent_leaf_element(typcache, strategy,
+														key, query);
+	}
 	else
-		PG_RETURN_BOOL(range_gist_consistent_int(typcache, strategy,
-												 key, query));
+	{
+		if (!OidIsValid(subtype) || subtype == ANYRANGEOID)
+			result = range_gist_consistent_int_range(typcache, strategy, key,
+													 DatumGetRangeTypeP(query));
+		else if (subtype == ANYMULTIRANGEOID)
+			result = range_gist_consistent_int_multirange(typcache, strategy, key,
+														  DatumGetMultirangeTypeP(query));
+		else
+			result = range_gist_consistent_int_element(typcache, strategy,
+													   key, query);
+	}
+	PG_RETURN_BOOL(result);
 }
 
 /* form union range */
@@ -759,48 +803,42 @@ range_super_union(TypeCacheEntry *typcache, RangeType *r1, RangeType *r2)
 }
 
 /*
- * GiST consistent test on an index internal page
+ * GiST consistent test on an index internal page with range query
  */
 static bool
-range_gist_consistent_int(TypeCacheEntry *typcache, StrategyNumber strategy,
-						  const RangeType *key, Datum query)
+range_gist_consistent_int_range(TypeCacheEntry *typcache,
+								StrategyNumber strategy,
+								const RangeType *key,
+								const RangeType *query)
 {
 	switch (strategy)
 	{
 		case RANGESTRAT_BEFORE:
-			if (RangeIsEmpty(key) || RangeIsEmpty(DatumGetRangeTypeP(query)))
+			if (RangeIsEmpty(key) || RangeIsEmpty(query))
 				return false;
-			return (!range_overright_internal(typcache, key,
-											  DatumGetRangeTypeP(query)));
+			return (!range_overright_internal(typcache, key, query));
 		case RANGESTRAT_OVERLEFT:
-			if (RangeIsEmpty(key) || RangeIsEmpty(DatumGetRangeTypeP(query)))
+			if (RangeIsEmpty(key) || RangeIsEmpty(query))
 				return false;
-			return (!range_after_internal(typcache, key,
-										  DatumGetRangeTypeP(query)));
+			return (!range_after_internal(typcache, key, query));
 		case RANGESTRAT_OVERLAPS:
-			return range_overlaps_internal(typcache, key,
-										   DatumGetRangeTypeP(query));
+			return range_overlaps_internal(typcache, key, query);
 		case RANGESTRAT_OVERRIGHT:
-			if (RangeIsEmpty(key) || RangeIsEmpty(DatumGetRangeTypeP(query)))
+			if (RangeIsEmpty(key) || RangeIsEmpty(query))
 				return false;
-			return (!range_before_internal(typcache, key,
-										   DatumGetRangeTypeP(query)));
+			return (!range_before_internal(typcache, key, query));
 		case RANGESTRAT_AFTER:
-			if (RangeIsEmpty(key) || RangeIsEmpty(DatumGetRangeTypeP(query)))
+			if (RangeIsEmpty(key) || RangeIsEmpty(query))
 				return false;
-			return (!range_overleft_internal(typcache, key,
-											 DatumGetRangeTypeP(query)));
+			return (!range_overleft_internal(typcache, key, query));
 		case RANGESTRAT_ADJACENT:
-			if (RangeIsEmpty(key) || RangeIsEmpty(DatumGetRangeTypeP(query)))
+			if (RangeIsEmpty(key) || RangeIsEmpty(query))
 				return false;
-			if (range_adjacent_internal(typcache, key,
-										DatumGetRangeTypeP(query)))
+			if (range_adjacent_internal(typcache, key, query))
 				return true;
-			return range_overlaps_internal(typcache, key,
-										   DatumGetRangeTypeP(query));
+			return range_overlaps_internal(typcache, key, query);
 		case RANGESTRAT_CONTAINS:
-			return range_contains_internal(typcache, key,
-										   DatumGetRangeTypeP(query));
+			return range_contains_internal(typcache, key, query);
 		case RANGESTRAT_CONTAINED_BY:
 
 			/*
@@ -810,20 +848,16 @@ range_gist_consistent_int(TypeCacheEntry *typcache, StrategyNumber strategy,
 			 */
 			if (RangeIsOrContainsEmpty(key))
 				return true;
-			return range_overlaps_internal(typcache, key,
-										   DatumGetRangeTypeP(query));
-		case RANGESTRAT_CONTAINS_ELEM:
-			return range_contains_elem_internal(typcache, key, query);
+			return range_overlaps_internal(typcache, key, query);
 		case RANGESTRAT_EQ:
 
 			/*
 			 * If query is empty, descend only if the key is or contains any
 			 * empty ranges.  Otherwise, descend if key contains query.
 			 */
-			if (RangeIsEmpty(DatumGetRangeTypeP(query)))
+			if (RangeIsEmpty(query))
 				return RangeIsOrContainsEmpty(key);
-			return range_contains_internal(typcache, key,
-										   DatumGetRangeTypeP(query));
+			return range_contains_internal(typcache, key, query);
 		default:
 			elog(ERROR, "unrecognized range strategy: %d", strategy);
 			return false;		/* keep compiler quiet */
@@ -831,42 +865,158 @@ range_gist_consistent_int(TypeCacheEntry *typcache, StrategyNumber strategy,
 }
 
 /*
- * GiST consistent test on an index leaf page
+ * GiST consistent test on an index internal page with multirange query
  */
 static bool
-range_gist_consistent_leaf(TypeCacheEntry *typcache, StrategyNumber strategy,
-						   const RangeType *key, Datum query)
+range_gist_consistent_int_multirange(TypeCacheEntry *typcache,
+									 StrategyNumber strategy,
+									 const RangeType *key,
+									 const MultirangeType *query)
 {
 	switch (strategy)
 	{
 		case RANGESTRAT_BEFORE:
-			return range_before_internal(typcache, key,
-										 DatumGetRangeTypeP(query));
+			if (RangeIsEmpty(key) || MultirangeIsEmpty(query))
+				return false;
+			return (!range_overright_multirange_internal(typcache, key, query));
 		case RANGESTRAT_OVERLEFT:
-			return range_overleft_internal(typcache, key,
-										   DatumGetRangeTypeP(query));
+			if (RangeIsEmpty(key) || MultirangeIsEmpty(query))
+				return false;
+			return (!range_after_multirange_internal(typcache, key, query));
 		case RANGESTRAT_OVERLAPS:
-			return range_overlaps_internal(typcache, key,
-										   DatumGetRangeTypeP(query));
+			return range_overlaps_multirange_internal(typcache, key, query);
 		case RANGESTRAT_OVERRIGHT:
-			return range_overright_internal(typcache, key,
-											DatumGetRangeTypeP(query));
+			if (RangeIsEmpty(key) || MultirangeIsEmpty(query))
+				return false;
+			return (!range_before_multirange_internal(typcache, key, query));
 		case RANGESTRAT_AFTER:
-			return range_after_internal(typcache, key,
-										DatumGetRangeTypeP(query));
+			if (RangeIsEmpty(key) || MultirangeIsEmpty(query))
+				return false;
+			return (!range_overleft_multirange_internal(typcache, key, query));
 		case RANGESTRAT_ADJACENT:
-			return range_adjacent_internal(typcache, key,
-										   DatumGetRangeTypeP(query));
+			if (RangeIsEmpty(key) || MultirangeIsEmpty(query))
+				return false;
+			if (range_adjacent_multirange_internal(typcache, key, query))
+				return true;
+			return range_overlaps_multirange_internal(typcache, key, query);
 		case RANGESTRAT_CONTAINS:
-			return range_contains_internal(typcache, key,
-										   DatumGetRangeTypeP(query));
+			return range_contains_multirange_internal(typcache, key, query);
 		case RANGESTRAT_CONTAINED_BY:
-			return range_contained_by_internal(typcache, key,
-											   DatumGetRangeTypeP(query));
+
+			/*
+			 * Empty ranges are contained by anything, so if key is or
+			 * contains any empty ranges, we must descend into it.  Otherwise,
+			 * descend only if key overlaps the query.
+			 */
+			if (RangeIsOrContainsEmpty(key))
+				return true;
+			return range_overlaps_multirange_internal(typcache, key, query);
+		default:
+			elog(ERROR, "unrecognized range strategy: %d", strategy);
+			return false;		/* keep compiler quiet */
+	}
+}
+
+/*
+ * GiST consistent test on an index internal page with element query
+ */
+static bool
+range_gist_consistent_int_element(TypeCacheEntry *typcache,
+								  StrategyNumber strategy,
+								  const RangeType *key,
+								  Datum query)
+{
+	switch (strategy)
+	{
 		case RANGESTRAT_CONTAINS_ELEM:
 			return range_contains_elem_internal(typcache, key, query);
+		default:
+			elog(ERROR, "unrecognized range strategy: %d", strategy);
+			return false;		/* keep compiler quiet */
+	}
+}
+
+/*
+ * GiST consistent test on an index leaf page with range query
+ */
+static bool
+range_gist_consistent_leaf_range(TypeCacheEntry *typcache,
+								 StrategyNumber strategy,
+								 const RangeType *key,
+								 const RangeType *query)
+{
+	switch (strategy)
+	{
+		case RANGESTRAT_BEFORE:
+			return range_before_internal(typcache, key, query);
+		case RANGESTRAT_OVERLEFT:
+			return range_overleft_internal(typcache, key, query);
+		case RANGESTRAT_OVERLAPS:
+			return range_overlaps_internal(typcache, key, query);
+		case RANGESTRAT_OVERRIGHT:
+			return range_overright_internal(typcache, key, query);
+		case RANGESTRAT_AFTER:
+			return range_after_internal(typcache, key, query);
+		case RANGESTRAT_ADJACENT:
+			return range_adjacent_internal(typcache, key, query);
+		case RANGESTRAT_CONTAINS:
+			return range_contains_internal(typcache, key, query);
+		case RANGESTRAT_CONTAINED_BY:
+			return range_contained_by_internal(typcache, key, query);
 		case RANGESTRAT_EQ:
-			return range_eq_internal(typcache, key, DatumGetRangeTypeP(query));
+			return range_eq_internal(typcache, key, query);
+		default:
+			elog(ERROR, "unrecognized range strategy: %d", strategy);
+			return false;		/* keep compiler quiet */
+	}
+}
+
+/*
+ * GiST consistent test on an index leaf page with multirange query
+ */
+static bool
+range_gist_consistent_leaf_multirange(TypeCacheEntry *typcache,
+									  StrategyNumber strategy,
+									  const RangeType *key,
+									  const MultirangeType *query)
+{
+	switch (strategy)
+	{
+		case RANGESTRAT_BEFORE:
+			return range_before_multirange_internal(typcache, key, query);
+		case RANGESTRAT_OVERLEFT:
+			return range_overleft_multirange_internal(typcache, key, query);
+		case RANGESTRAT_OVERLAPS:
+			return range_overlaps_multirange_internal(typcache, key, query);
+		case RANGESTRAT_OVERRIGHT:
+			return range_overright_multirange_internal(typcache, key, query);
+		case RANGESTRAT_AFTER:
+			return range_after_multirange_internal(typcache, key, query);
+		case RANGESTRAT_ADJACENT:
+			return range_adjacent_multirange_internal(typcache, key, query);
+		case RANGESTRAT_CONTAINS:
+			return range_contains_multirange_internal(typcache, key, query);
+		case RANGESTRAT_CONTAINED_BY:
+			return multirange_contains_range_internal(typcache, query, key);
+		default:
+			elog(ERROR, "unrecognized range strategy: %d", strategy);
+			return false;		/* keep compiler quiet */
+	}
+}
+
+/*
+ * GiST consistent test on an index leaf page with element query
+ */
+static bool
+range_gist_consistent_leaf_element(TypeCacheEntry *typcache,
+								   StrategyNumber strategy,
+								   const RangeType *key,
+								   Datum query)
+{
+	switch (strategy)
+	{
+		case RANGESTRAT_CONTAINS_ELEM:
+			return range_contains_elem_internal(typcache, key, query);
 		default:
 			elog(ERROR, "unrecognized range strategy: %d", strategy);
 			return false;		/* keep compiler quiet */
diff --git a/src/include/catalog/pg_amop.dat b/src/include/catalog/pg_amop.dat
index 78d7d2c5232..e055da67eb7 100644
--- a/src/include/catalog/pg_amop.dat
+++ b/src/include/catalog/pg_amop.dat
@@ -1346,27 +1346,51 @@
 { amopfamily => 'gist/range_ops', amoplefttype => 'anyrange',
   amoprighttype => 'anyrange', amopstrategy => '1',
   amopopr => '<<(anyrange,anyrange)', amopmethod => 'gist' },
+{ amopfamily => 'gist/range_ops', amoplefttype => 'anyrange',
+  amoprighttype => 'anymultirange', amopstrategy => '1',
+  amopopr => '<<(anyrange,anymultirange)', amopmethod => 'gist' },
 { amopfamily => 'gist/range_ops', amoplefttype => 'anyrange',
   amoprighttype => 'anyrange', amopstrategy => '2',
   amopopr => '&<(anyrange,anyrange)', amopmethod => 'gist' },
+{ amopfamily => 'gist/range_ops', amoplefttype => 'anyrange',
+  amoprighttype => 'anymultirange', amopstrategy => '2',
+  amopopr => '&<(anyrange,anymultirange)', amopmethod => 'gist' },
 { amopfamily => 'gist/range_ops', amoplefttype => 'anyrange',
   amoprighttype => 'anyrange', amopstrategy => '3',
   amopopr => '&&(anyrange,anyrange)', amopmethod => 'gist' },
+{ amopfamily => 'gist/range_ops', amoplefttype => 'anyrange',
+  amoprighttype => 'anymultirange', amopstrategy => '3',
+  amopopr => '&&(anyrange,anymultirange)', amopmethod => 'gist' },
 { amopfamily => 'gist/range_ops', amoplefttype => 'anyrange',
   amoprighttype => 'anyrange', amopstrategy => '4',
   amopopr => '&>(anyrange,anyrange)', amopmethod => 'gist' },
+{ amopfamily => 'gist/range_ops', amoplefttype => 'anyrange',
+  amoprighttype => 'anymultirange', amopstrategy => '4',
+  amopopr => '&>(anyrange,anymultirange)', amopmethod => 'gist' },
 { amopfamily => 'gist/range_ops', amoplefttype => 'anyrange',
   amoprighttype => 'anyrange', amopstrategy => '5',
   amopopr => '>>(anyrange,anyrange)', amopmethod => 'gist' },
+{ amopfamily => 'gist/range_ops', amoplefttype => 'anyrange',
+  amoprighttype => 'anymultirange', amopstrategy => '5',
+  amopopr => '>>(anyrange,anymultirange)', amopmethod => 'gist' },
 { amopfamily => 'gist/range_ops', amoplefttype => 'anyrange',
   amoprighttype => 'anyrange', amopstrategy => '6',
   amopopr => '-|-(anyrange,anyrange)', amopmethod => 'gist' },
+{ amopfamily => 'gist/range_ops', amoplefttype => 'anyrange',
+  amoprighttype => 'anymultirange', amopstrategy => '6',
+  amopopr => '-|-(anyrange,anymultirange)', amopmethod => 'gist' },
 { amopfamily => 'gist/range_ops', amoplefttype => 'anyrange',
   amoprighttype => 'anyrange', amopstrategy => '7',
   amopopr => '@>(anyrange,anyrange)', amopmethod => 'gist' },
+{ amopfamily => 'gist/range_ops', amoplefttype => 'anyrange',
+  amoprighttype => 'anymultirange', amopstrategy => '7',
+  amopopr => '@>(anyrange,anymultirange)', amopmethod => 'gist' },
 { amopfamily => 'gist/range_ops', amoplefttype => 'anyrange',
   amoprighttype => 'anyrange', amopstrategy => '8',
   amopopr => '<@(anyrange,anyrange)', amopmethod => 'gist' },
+{ amopfamily => 'gist/range_ops', amoplefttype => 'anyrange',
+  amoprighttype => 'anymultirange', amopstrategy => '8',
+  amopopr => '<@(anyrange,anymultirange)', amopmethod => 'gist' },
 { amopfamily => 'gist/range_ops', amoplefttype => 'anyrange',
   amoprighttype => 'anyelement', amopstrategy => '16',
   amopopr => '@>(anyrange,anyelement)', amopmethod => 'gist' },
diff --git a/src/test/regress/expected/rangetypes.out b/src/test/regress/expected/rangetypes.out
index 6a1bbadc91a..28dc995e599 100644
--- a/src/test/regress/expected/rangetypes.out
+++ b/src/test/regress/expected/rangetypes.out
@@ -862,6 +862,60 @@ select count(*) from test_range_gist where ir -|- int4range(100,500);
      5
 (1 row)
 
+select count(*) from test_range_gist where ir @> '{}'::int4multirange;
+ count 
+-------
+  6200
+(1 row)
+
+select count(*) from test_range_gist where ir @> int4multirange(int4range(10,20), int4range(30,40));
+ count 
+-------
+   107
+(1 row)
+
+select count(*) from test_range_gist where ir && '{(10,20),(30,40),(50,60)}'::int4multirange;
+ count 
+-------
+   271
+(1 row)
+
+select count(*) from test_range_gist where ir <@ '{(10,30),(40,60),(70,90)}'::int4multirange;
+ count 
+-------
+  1060
+(1 row)
+
+select count(*) from test_range_gist where ir << int4multirange(int4range(100,200), int4range(400,500));
+ count 
+-------
+   189
+(1 row)
+
+select count(*) from test_range_gist where ir >> int4multirange(int4range(100,200), int4range(400,500));
+ count 
+-------
+  3554
+(1 row)
+
+select count(*) from test_range_gist where ir &< int4multirange(int4range(100,200), int4range(400,500));
+ count 
+-------
+  1029
+(1 row)
+
+select count(*) from test_range_gist where ir &> int4multirange(int4range(100,200), int4range(400,500));
+ count 
+-------
+  4794
+(1 row)
+
+select count(*) from test_range_gist where ir -|- int4multirange(int4range(100,200), int4range(400,500));
+ count 
+-------
+     5
+(1 row)
+
 -- now check same queries using index
 SET enable_seqscan    = f;
 SET enable_indexscan  = t;
@@ -932,6 +986,60 @@ select count(*) from test_range_gist where ir -|- int4range(100,500);
      5
 (1 row)
 
+select count(*) from test_range_gist where ir @> '{}'::int4multirange;
+ count 
+-------
+  6200
+(1 row)
+
+select count(*) from test_range_gist where ir @> int4multirange(int4range(10,20), int4range(30,40));
+ count 
+-------
+   107
+(1 row)
+
+select count(*) from test_range_gist where ir && '{(10,20),(30,40),(50,60)}'::int4multirange;
+ count 
+-------
+   271
+(1 row)
+
+select count(*) from test_range_gist where ir <@ '{(10,30),(40,60),(70,90)}'::int4multirange;
+ count 
+-------
+  1060
+(1 row)
+
+select count(*) from test_range_gist where ir << int4multirange(int4range(100,200), int4range(400,500));
+ count 
+-------
+   189
+(1 row)
+
+select count(*) from test_range_gist where ir >> int4multirange(int4range(100,200), int4range(400,500));
+ count 
+-------
+  3554
+(1 row)
+
+select count(*) from test_range_gist where ir &< int4multirange(int4range(100,200), int4range(400,500));
+ count 
+-------
+  1029
+(1 row)
+
+select count(*) from test_range_gist where ir &> int4multirange(int4range(100,200), int4range(400,500));
+ count 
+-------
+  4794
+(1 row)
+
+select count(*) from test_range_gist where ir -|- int4multirange(int4range(100,200), int4range(400,500));
+ count 
+-------
+     5
+(1 row)
+
 -- now check same queries using a bulk-loaded index
 drop index test_range_gist_idx;
 create index test_range_gist_idx on test_range_gist using gist (ir);
@@ -1001,6 +1109,60 @@ select count(*) from test_range_gist where ir -|- int4range(100,500);
      5
 (1 row)
 
+select count(*) from test_range_gist where ir @> '{}'::int4multirange;
+ count 
+-------
+  6200
+(1 row)
+
+select count(*) from test_range_gist where ir @> int4multirange(int4range(10,20), int4range(30,40));
+ count 
+-------
+   107
+(1 row)
+
+select count(*) from test_range_gist where ir && '{(10,20),(30,40),(50,60)}'::int4multirange;
+ count 
+-------
+   271
+(1 row)
+
+select count(*) from test_range_gist where ir <@ '{(10,30),(40,60),(70,90)}'::int4multirange;
+ count 
+-------
+  1060
+(1 row)
+
+select count(*) from test_range_gist where ir << int4multirange(int4range(100,200), int4range(400,500));
+ count 
+-------
+   189
+(1 row)
+
+select count(*) from test_range_gist where ir >> int4multirange(int4range(100,200), int4range(400,500));
+ count 
+-------
+  3554
+(1 row)
+
+select count(*) from test_range_gist where ir &< int4multirange(int4range(100,200), int4range(400,500));
+ count 
+-------
+  1029
+(1 row)
+
+select count(*) from test_range_gist where ir &> int4multirange(int4range(100,200), int4range(400,500));
+ count 
+-------
+  4794
+(1 row)
+
+select count(*) from test_range_gist where ir -|- int4multirange(int4range(100,200), int4range(400,500));
+ count 
+-------
+     5
+(1 row)
+
 -- test SP-GiST index that's been built incrementally
 create table test_range_spgist(ir int4range);
 create index test_range_spgist_idx on test_range_spgist using spgist (ir);
diff --git a/src/test/regress/sql/rangetypes.sql b/src/test/regress/sql/rangetypes.sql
index b69efede3ae..51eddabf60f 100644
--- a/src/test/regress/sql/rangetypes.sql
+++ b/src/test/regress/sql/rangetypes.sql
@@ -232,6 +232,15 @@ select count(*) from test_range_gist where ir >> int4range(100,500);
 select count(*) from test_range_gist where ir &< int4range(100,500);
 select count(*) from test_range_gist where ir &> int4range(100,500);
 select count(*) from test_range_gist where ir -|- int4range(100,500);
+select count(*) from test_range_gist where ir @> '{}'::int4multirange;
+select count(*) from test_range_gist where ir @> int4multirange(int4range(10,20), int4range(30,40));
+select count(*) from test_range_gist where ir && '{(10,20),(30,40),(50,60)}'::int4multirange;
+select count(*) from test_range_gist where ir <@ '{(10,30),(40,60),(70,90)}'::int4multirange;
+select count(*) from test_range_gist where ir << int4multirange(int4range(100,200), int4range(400,500));
+select count(*) from test_range_gist where ir >> int4multirange(int4range(100,200), int4range(400,500));
+select count(*) from test_range_gist where ir &< int4multirange(int4range(100,200), int4range(400,500));
+select count(*) from test_range_gist where ir &> int4multirange(int4range(100,200), int4range(400,500));
+select count(*) from test_range_gist where ir -|- int4multirange(int4range(100,200), int4range(400,500));
 
 -- now check same queries using index
 SET enable_seqscan    = f;
@@ -249,6 +258,15 @@ select count(*) from test_range_gist where ir >> int4range(100,500);
 select count(*) from test_range_gist where ir &< int4range(100,500);
 select count(*) from test_range_gist where ir &> int4range(100,500);
 select count(*) from test_range_gist where ir -|- int4range(100,500);
+select count(*) from test_range_gist where ir @> '{}'::int4multirange;
+select count(*) from test_range_gist where ir @> int4multirange(int4range(10,20), int4range(30,40));
+select count(*) from test_range_gist where ir && '{(10,20),(30,40),(50,60)}'::int4multirange;
+select count(*) from test_range_gist where ir <@ '{(10,30),(40,60),(70,90)}'::int4multirange;
+select count(*) from test_range_gist where ir << int4multirange(int4range(100,200), int4range(400,500));
+select count(*) from test_range_gist where ir >> int4multirange(int4range(100,200), int4range(400,500));
+select count(*) from test_range_gist where ir &< int4multirange(int4range(100,200), int4range(400,500));
+select count(*) from test_range_gist where ir &> int4multirange(int4range(100,200), int4range(400,500));
+select count(*) from test_range_gist where ir -|- int4multirange(int4range(100,200), int4range(400,500));
 
 -- now check same queries using a bulk-loaded index
 drop index test_range_gist_idx;
@@ -265,6 +283,15 @@ select count(*) from test_range_gist where ir >> int4range(100,500);
 select count(*) from test_range_gist where ir &< int4range(100,500);
 select count(*) from test_range_gist where ir &> int4range(100,500);
 select count(*) from test_range_gist where ir -|- int4range(100,500);
+select count(*) from test_range_gist where ir @> '{}'::int4multirange;
+select count(*) from test_range_gist where ir @> int4multirange(int4range(10,20), int4range(30,40));
+select count(*) from test_range_gist where ir && '{(10,20),(30,40),(50,60)}'::int4multirange;
+select count(*) from test_range_gist where ir <@ '{(10,30),(40,60),(70,90)}'::int4multirange;
+select count(*) from test_range_gist where ir << int4multirange(int4range(100,200), int4range(400,500));
+select count(*) from test_range_gist where ir >> int4multirange(int4range(100,200), int4range(400,500));
+select count(*) from test_range_gist where ir &< int4multirange(int4range(100,200), int4range(400,500));
+select count(*) from test_range_gist where ir &> int4multirange(int4range(100,200), int4range(400,500));
+select count(*) from test_range_gist where ir -|- int4multirange(int4range(100,200), int4range(400,500));
 
 -- test SP-GiST index that's been built incrementally
 create table test_range_spgist(ir int4range);
-- 
2.24.3 (Apple Git-128)

#144Zhihong Yu
zyu@yugabyte.com
In reply to: Alexander Korotkov (#143)
Re: range_agg

Hi,

This is not an ideal way to index multirages, but something we can easily
have.

typo: multiranges

Cheers

On Sun, Dec 27, 2020 at 1:50 AM Alexander Korotkov <aekorotkov@gmail.com>
wrote:

Show quoted text

On Thu, Dec 17, 2020 at 10:10 PM Alexander Korotkov
<aekorotkov@gmail.com> wrote:

I think this patch is very close to committable. I'm going to spend
some more time further polishing it and commit (if I don't find a
major issue or face objections).

The main patch is committed. I've prepared a set of improvements.
0001 Fixes bug in bsearch comparison functions
0002 Implements missing @> (range,multirange) operator and its commutator
0003 Does refactors signatures of *_internal() multirange functions
0004 Adds cross-type (range, multirange) operators handling to
existing range GiST opclass
0005 Adds support for GiST multirange indexing by approximation of
multirange as the union range with no gaps

The patchset is quite trivial. I'm going to push it if there are no
objections.

The SP-GiST handling is more tricky and requires substantial work.

------
Regards,
Alexander Korotkov

#145David Fetter
david@fetter.org
In reply to: Zhihong Yu (#144)
Re: range_agg

On Sun, Dec 27, 2020 at 09:53:13AM -0800, Zhihong Yu wrote:

Hi,

This is not an ideal way to index multirages, but something we can
easily have.

What sort of indexing improvements do you have in mind?

Best,
David.
--
David Fetter <david(at)fetter(dot)org> http://fetter.org/
Phone: +1 415 235 3778

Remember to vote!
Consider donating to Postgres: http://www.postgresql.org/about/donate

#146Alexander Korotkov
aekorotkov@gmail.com
In reply to: Zhihong Yu (#144)
Re: range_agg

On Sun, Dec 27, 2020 at 8:52 PM Zhihong Yu <zyu@yugabyte.com> wrote:

This is not an ideal way to index multirages, but something we can easily have.

typo: multiranges

Thanks for catching. I will revise the commit message before committing.

------
Regards,
Alexander Korotkov

#147Alexander Korotkov
aekorotkov@gmail.com
In reply to: David Fetter (#145)
Re: range_agg

On Sun, Dec 27, 2020 at 9:07 PM David Fetter <david@fetter.org> wrote:

On Sun, Dec 27, 2020 at 09:53:13AM -0800, Zhihong Yu wrote:

This is not an ideal way to index multirages, but something we can
easily have.

What sort of indexing improvements do you have in mind?

Approximation of multirange as a range can cause false positives.
It's good if gaps are small, but what if they aren't.

Ideally, we should split multirange to the ranges and index them
separately. So, we would need a GIN-like index. The problem is that
the GIN entry tree is a B-tree, which is not very useful for searching
for ranges. If we could replace the GIN entry tree with GiST or
SP-GiST, that should be good. We could index multirage parts
separately and big gaps wouldn't be a problem. Similar work was
already prototyped (it was prototyped under the name "vodka", but I'm
not a big fan of this name). FWIW, such a new access method would
need a lot of work to bring it to commit. I don't think it would be
reasonable, before multiranges get popular.

Regarding the GiST opclass, it seems the best we can do in GiST.

------
Regards,
Alexander Korotkov